diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc index dc3e37b380..41b503316f 100644 --- a/common/src/app/common/transit.cljc +++ b/common/src/app/common/transit.cljc @@ -99,6 +99,17 @@ ;; --- HANDLERS +#?(:cljs + (defn- keyword-reader + "Return a reader that gives the same keyword instance for each name." + [] + (let [cache (js/Map.)] + (fn [value] + (or (.get cache value) + (let [kw (keyword value)] + (.set cache value kw) + kw)))))) + (add-handlers! #?@(:clj [{:id "file" @@ -119,6 +130,10 @@ {:id "u" :rfn parse-uuid}) + #?(:cljs + {:id ":" + :rfn (keyword-reader)}) + {:id "ordered-map" :class #?(:clj LinkedMap :cljs lkm/LinkedMap) :wfn vec diff --git a/common/src/app/common/types/objects_map.cljc b/common/src/app/common/types/objects_map.cljc index 77289a2b1e..1b4e17ce4c 100644 --- a/common/src/app/common/types/objects_map.cljc +++ b/common/src/app/common/types/objects_map.cljc @@ -206,7 +206,7 @@ c/IWithMeta (-with-meta [this new-meta] - (if (identical? new-meta meta) + (if (identical? new-meta metadata) this (ObjectsMap. new-meta cache diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 0080aae00d..05cb0fc40f 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -368,11 +368,17 @@ (defn start-page-index [objects] - (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) + (with-meta objects {::index-frames (into [] (get-frames objects {:ignore-index? true}))})) (defn update-page-index + "Lazy, so pages that are not open are not decoded. The index reads + from the returned objects, so decoded shapes are shared with them." [objects] - (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) + (let [self (volatile! nil) + objects (with-meta objects + {::index-frames (lazy-seq (get-frames @self {:ignore-index? true}))})] + (vreset! self objects) + objects)) (defn update-object-indices [file page-id] diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index d94bd8d070..1d2980d6c9 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -79,6 +79,16 @@ (-count [_] (+ 1 (count delegate))) + c/IKVReduce + (-kv-reduce [_ f init] + (let [acc (f init :id id)] + (if (reduced? acc) + @acc + (let [acc (f acc :type type)] + (if (reduced? acc) + @acc + (reduce-kv f acc delegate)))))) + c/ILookup (-lookup [coll k] (-lookup coll k nil)) @@ -354,6 +364,18 @@ (let [id (:id self)] (.set changes id (conj* (.get changes id) k))))) +(defn- assoc-delegate + "Keeps an array map delegate as an array map when adding a new key." + [delegate k v] + (if (and (instance? PersistentArrayMap delegate) + (not (contains? delegate k))) + (PersistentArrayMap. (meta delegate) + (inc (count delegate)) + (doto (aclone (.-arr ^PersistentArrayMap delegate)) + (.push k v)) + nil) + (assoc delegate k v))) + (defn- impl-assoc [self k v] (track-change! self k) @@ -369,7 +391,7 @@ (.-delegate ^ShapeProxy self)) (let [delegate (.-delegate ^ShapeProxy self) - delegate' (assoc delegate k v)] + delegate' (assoc-delegate delegate k v)] (if (identical? delegate' delegate) self (ShapeProxy. (.-id ^ShapeProxy self) @@ -418,18 +440,19 @@ (next es)) (throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))) -(def ^:private xf:without-id-and-type - (remove (fn [kvpair] - (let [k (key kvpair)] - (or (= k :id) - (= k :type)))))) - (defn create-shape - "Instantiate a shape from a map" + "Instantiate a shape from a map. The delegate is an array map because + it takes far less memory than a hash map for shape-sized maps." [attrs] - (ShapeProxy. (:id attrs) - (:type attrs) - (into {} xf:without-id-and-type attrs))) + (let [arr (reduce-kv (fn [arr k v] + (when-not (or (= k :id) (= k :type)) + (.push arr k v)) + arr) + #js [] + attrs)] + (ShapeProxy. (:id attrs) + (:type attrs) + (PersistentArrayMap. nil (/ (alength arr) 2) arr nil)))) (t/add-handlers! ;; We only add a write handler, read handler uses the dynamic dispatch