From 13a9da3bc3a47bfd4660fdd9d887a92cd7c33ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Torr=C3=B3?= Date: Fri, 2 Oct 2026 11:22:19 +0200 Subject: [PATCH] :zap: Reduce commit cost when dropping large selections (#12032) Collect children ids into one transient vector and track WASM shape changes in a JS Map instead of a volatile persistent map. AI-assisted-by: claude-opus-5-5 --- common/src/app/common/files/helpers.cljc | 17 +++++++++-------- frontend/src/app/main/data/changes.cljs | 4 ++-- frontend/src/app/render_wasm/shape.cljs | 12 ++++++++---- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/common/src/app/common/files/helpers.cljc b/common/src/app/common/files/helpers.cljc index 2c8ff25600..2b83fa75c5 100644 --- a/common/src/app/common/files/helpers.cljc +++ b/common/src/app/common/files/helpers.cljc @@ -162,14 +162,15 @@ ([objects id {:keys [ignore-children-fn] ;;ignore-children-fn should receive a shape and return a boolean :or {ignore-children-fn (constantly false)}}] - (letfn [(get-children-ids-rec [id processed] - (when-not (contains? processed id) - (when-let [shapes (as-> (get objects id) $ - (:shapes $) - (remove ignore-children-fn $) - (some-> $ vec))] - (into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))] - (get-children-ids-rec id #{})))) + (letfn [(collect [result id processed] + (if (contains? processed id) + result + (let [shapes (into [] (remove ignore-children-fn) (dm/get-in objects [id :shapes])) + processed (conj processed id)] + (reduce #(collect %1 %2 processed) + (reduce conj! result shapes) + shapes))))] + (persistent! (collect (transient []) id #{}))))) (defn get-children-ids-with-self [objects id] diff --git a/frontend/src/app/main/data/changes.cljs b/frontend/src/app/main/data/changes.cljs index 865779e5dc..bd8afa6ff7 100644 --- a/frontend/src/app/main/data/changes.cljs +++ b/frontend/src/app/main/data/changes.cljs @@ -133,14 +133,14 @@ (if (and (not ignore-wasm?) (features/active-feature? state "render-wasm/v1")) ;; Update the wasm model - (let [shape-changes (volatile! {}) + (let [shape-changes (js/Map.) state (binding [cts/*shape-changes* shape-changes] (update-in state [:files file-id :data] apply-changes))] (let [objects (dm/get-in state [:files file-id :data :pages-index (:current-page-id state) :objects])] - (wasm.shape/process-shape-changes! objects @shape-changes)) + (wasm.shape/process-shape-changes! objects (es6-iterator-seq (.entries shape-changes)))) state) diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 3d895841c8..d94bd8d070 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -348,10 +348,15 @@ ;; `conj` empty set initialization (def conj* (fnil conj (d/ordered-set))) +(defn- track-change! + [self k] + (when-let [changes shape/*shape-changes*] + (let [id (:id self)] + (.set changes id (conj* (.get changes id) k))))) + (defn- impl-assoc [self k v] - (when shape/*shape-changes* - (vswap! shape/*shape-changes* update (:id self) conj* k)) + (track-change! self k) (case k :id @@ -379,8 +384,7 @@ (defn- impl-dissoc [self k] - (when shape/*shape-changes* - (vswap! shape/*shape-changes* update (:id self) conj* k)) + (track-change! self k) (case k :id