+;;; Size caches
+
+(defgeneric make-size-cache (object type)
+ (:documentation
+ "Make an object size cache for 'object'."))
+
+;; Note that this gets called on the top-level object being serialized
+;; This means that either all the objects in the tree should be subclasses
+;; of 'base-protobuf-message', or none of them should be. If the root is
+;; not a 'base-protobuf-message', then things will work but be slower; if
+;; the root is a 'base-protobuf-message', but some children are not, then
+;; serialization will fail.
+(defmethod make-size-cache ((object standard-object) type)
+ (declare (ignore type))
+ ;; No '%cached-size' slot in standard objects, create a "visited" table
+ (make-hash-table))
+
+(defmethod make-size-cache ((object base-protobuf-message) type)
+ ;; In classes defined by Protobufs, we will use the '%cached-size' slot
+ ;; This clears the size cache for the tree of objects
+ (clear-size-cache object type)
+ nil)
+
+
+(declaim (inline cached-object-size))
+(defun cached-object-size (object visited)
+ (declare #.$optimize-fast-unsafe)
+ (if visited
+ (gethash object visited)
+ ;; Warning, Will Robinson! Danger!
+ ;; If there's no 'visited' table, assume there's a cached size slot.
+ ;; We should use methods speciaized on 'base-protobuf-message',
+ ;; but we're trying to max out the speed.
+ (slot-value object '%cached-size)))
+
+(defun (setf cached-object-size) (size object visited)
+ (declare #.$optimize-fast-unsafe)
+ (if visited
+ (setf (gethash object visited) size)
+ (setf (slot-value object '%cached-size) size)))
+
+
+(defgeneric clear-size-cache (object type)
+ (:documentation
+ "Clear the size cache for a tree of objects."))
+
+(defmethod clear-size-cache ((object standard-object) type)
+ (declare (ignore type))
+ nil)
+
+(defmethod clear-size-cache ((object base-protobuf-message) type)
+ (let ((message (find-message-for-class type)))
+ (assert message ()
+ "There is no Protobuf message having the type ~S" type)
+ (macrolet ((read-slot (object slot reader)
+ `(if ,reader
+ (funcall ,reader ,object)
+ (slot-value ,object ,slot))))
+ (labels ((do-field (object trace field)
+ (let* ((type (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
+ (slot (proto-value field))
+ (reader (proto-reader field))
+ msg)
+ (when (or slot reader)
+ (cond ((eq (proto-required field) :repeated)
+ (cond ((or (and (proto-packed field) (packed-type-p type))
+ (keywordp type)))
+ ((typep (setq msg (and type (find-message trace type))) 'protobuf-message)
+ (setf (slot-value object '%cached-size) nil)
+ (doseq (v (if slot (read-slot object slot reader) (list object)))
+ (dolist (f (proto-fields msg))
+ (do-field v msg f))))))
+ (t
+ (cond ((keywordp type))
+ ((typep (setq msg (and type (find-message trace type))) 'protobuf-message)
+ (setf (slot-value object '%cached-size) nil)
+ (let ((v (if slot (read-slot object slot reader) object)))
+ (when v
+ (dolist (f (proto-fields msg))
+ (do-field v msg f))))))))))))
+ (declare (dynamic-extent #'do-field))
+ (setf (slot-value object '%cached-size) nil)
+ (dolist (field (proto-fields message))
+ (do-field object message field))
+ nil))))
+
+