From 9e24ba7b39a197ddf6b53f5a42269e4c653b1704 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 23 Jan 2024 15:39:37 +0100 Subject: [PATCH] :zap: Improved performance for hover shapes --- common/src/app/common/types/shape_tree.cljc | 54 +++---- .../app/main/ui/workspace/viewport/hooks.cljs | 132 ++++++++++-------- 2 files changed, 105 insertions(+), 81 deletions(-) diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index f8e50a6cb..064a3eae0 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -176,11 +176,11 @@ (->> (get-root-shapes objects) (mapv :id))) -(defn get-base - [objects id-a id-b] +(defn- get-base + [id-a id-b id-parents] - (let [[parents-a parents-a-index] (cfh/get-parent-ids-with-index objects id-a) - [parents-b parents-b-index] (cfh/get-parent-ids-with-index objects id-b) + (let [[parents-a parents-a-index] (get id-parents id-a) + [parents-b parents-b-index] (get id-parents id-b) parents-a (cons id-a parents-a) parents-b (into #{id-b} parents-b) @@ -194,9 +194,9 @@ [base-id idx-a idx-b])) (defn- is-shape-over-shape? - [objects base-shape-id over-shape-id bottom-frames?] + [objects base-shape-id over-shape-id bottom-frames? id-parents] - (let [[base-id index-a index-b] (get-base objects base-shape-id over-shape-id)] + (let [[base-id index-a index-b] (get-base base-shape-id over-shape-id id-parents)] (cond ;; The base the base shape, so the other item is below (if not bottom-frames) (= base-id base-shape-id) @@ -234,33 +234,37 @@ ([objects ids {:keys [bottom-frames?] :as options :or {bottom-frames? false}}] - (letfn [(comp [id-a id-b] - (cond - (= id-a id-b) - 0 + ;; Create an index of the parents of the shapes. This will speed the sorting because we use + ;; this information down the line. + (let [id-parents (into {} (map #(vector % (cfh/get-parent-ids-with-index objects %))) ids)] + (letfn [(comp [id-a id-b] + (cond + (= id-a id-b) + 0 - (is-shape-over-shape? objects id-a id-b bottom-frames?) - 1 + (is-shape-over-shape? objects id-a id-b bottom-frames? id-parents) + 1 - :else - -1))] - (sort comp ids)))) + :else + -1))] + (sort comp ids))))) (defn sort-z-index-objects ([objects items] (sort-z-index-objects objects items nil)) ([objects items {:keys [bottom-frames?] :or {bottom-frames? false}}] - (d/unstable-sort - (fn [obj-a obj-b] - (let [id-a (dm/get-prop obj-a :id) - id-b (dm/get-prop obj-b :id)] - (if (= id-a id-b) - 0 - (if ^boolean (is-shape-over-shape? objects id-a id-b bottom-frames?) - 1 - -1)))) - items))) + (let [id-parents (into {} (map #(vector (dm/get-prop % :id) (cfh/get-parent-ids-with-index objects (dm/get-prop % :id)))) items)] + (d/unstable-sort + (fn [obj-a obj-b] + (let [id-a (dm/get-prop obj-a :id) + id-b (dm/get-prop obj-b :id)] + (if (= id-a id-b) + 0 + (if ^boolean (is-shape-over-shape? objects id-a id-b bottom-frames? id-parents) + 1 + -1)))) + items)))) (defn get-frame-by-position ([objects position] diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 861d21f83..a9ab0ffb3 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -159,7 +159,6 @@ [group-id objects hover-ids] (and (contains? #{:group :bool} (get-in objects [group-id :type])) - ;; If there are no children in the hover-ids we're in the empty side (->> hover-ids (remove #(contains? #{:group :bool} (get-in objects [% :type]))) @@ -253,77 +252,98 @@ (fn [_] (reset! hover-top-frame-id (ctt/top-nested-frame objects (deref last-point-ref))))) - (hooks/use-stream - over-shapes-stream - (mf/deps page-id objects show-measures?) - (fn [ids] - (let [selected (mf/ref-val selected-ref) - focus (mf/ref-val focus-ref) - mod? (mf/ref-val mod-ref) + ;; This ref is a cache of sorted ids. Sorting is expensive so we save the list + (let [sorted-ids-cache (mf/use-ref {})] + (hooks/use-stream + over-shapes-stream + (mf/deps page-id objects show-measures?) + (fn [ids] + (let [selected (mf/ref-val selected-ref) + focus (mf/ref-val focus-ref) + mod? (mf/ref-val mod-ref) + cached-ids (mf/ref-val sorted-ids-cache) - ids (into (d/ordered-set) - (remove #(dm/get-in objects [% :blocked])) - (ctt/sort-z-index objects ids {:bottom-frames? mod?})) + make-sorted-ids + (fn [mod? ids] + (let [sorted-ids + (into (d/ordered-set) + (comp (remove #(dm/get-in objects [% :blocked])) + (remove (partial cfh/svg-raw-shape? objects))) + (ctt/sort-z-index objects ids {:bottom-frames? mod?}))] + (mf/set-ref-val! sorted-ids-cache (assoc cached-ids [mod? ids] sorted-ids)) + sorted-ids)) - grouped? (fn [id] - (and (cfh/group-shape? objects id) - (not (cfh/mask-shape? objects id)))) + ids (or (get cached-ids [mod? ids]) (make-sorted-ids mod? ids)) - selected-with-parents - (into #{} (mapcat #(cfh/get-parent-ids objects %)) selected) + grouped? + (fn [id] + (and (cfh/group-shape? objects id) + (not (cfh/mask-shape? objects id)))) - root-frame-with-data? - #(as-> (get objects %) obj - (and (cfh/root-frame? obj) - (d/not-empty? (:shapes obj)) - (not (ctk/instance-head? obj)) - (not (ctk/main-instance? obj)))) + selected-with-parents + (into #{} (mapcat #(cfh/get-parent-ids objects %)) selected) - ;; Set with the elements to remove from the hover list - remove-id-xf - (cond - mod? - (filter grouped?) + root-frame-with-data? + #(as-> (get objects %) obj + (and (cfh/root-frame? obj) + (d/not-empty? (:shapes obj)) + (not (ctk/instance-head? obj)) + (not (ctk/main-instance? obj)))) - (not mod?) - (filter #(or (root-frame-with-data? %) - (group-empty-space? % objects ids)))) + ;; Set with the elements to remove from the hover list + remove-id-xf + (cond + mod? + (filter grouped?) - remove-id? - (into selected-with-parents remove-id-xf ids) + (not mod?) + (let [child-parent? + (into #{} + (comp (remove #(cfh/group-like-shape? objects %)) + (mapcat #(cfh/get-parent-ids objects %))) + ids)] + (filter #(or (root-frame-with-data? %) + (and (contains? #{:group :bool} (dm/get-in objects [% :type])) + (not (contains? child-parent? %))))))) - no-fill-nested-frames? - (fn [id] - (let [shape (get objects id)] - (and (cfh/frame-shape? shape) - (not (cfh/is-direct-child-of-root? shape)) - (empty? (get shape :fills))))) + remove-id? + (into selected-with-parents remove-id-xf ids) - hover-shape - (->> ids - (remove remove-id?) - (remove (partial cfh/hidden-parent? objects)) - (remove (partial cfh/svg-raw-shape? objects)) - (remove #(and mod? (no-fill-nested-frames? %))) - (filter #(or (empty? focus) (cpf/is-in-focus? objects focus %))) - (first) - (get objects)) + no-fill-nested-frames? + (fn [id] + (let [shape (get objects id)] + (and (cfh/frame-shape? shape) + (not (cfh/is-direct-child-of-root? shape)) + (empty? (get shape :fills))))) - ;; We keep track of a diferent shape for measures - measure-hover-shape - (when show-measures? + hover-shape (->> ids - (remove #(group-empty-space? % objects ids)) + (remove remove-id?) (remove (partial cfh/hidden-parent? objects)) - (remove (partial cfh/svg-raw-shape? objects)) (remove #(and mod? (no-fill-nested-frames? %))) (filter #(or (empty? focus) (cpf/is-in-focus? objects focus %))) (first) - (get objects)))] + (get objects)) - (reset! hover hover-shape) - (reset! measure-hover measure-hover-shape) - (reset! hover-ids ids)))))) + ;; We keep track of a diferent shape for measures + measure-hover-shape + (when show-measures? + (->> ids + (remove #(group-empty-space? % objects ids)) + (remove (partial cfh/hidden-parent? objects)) + (remove #(and mod? (no-fill-nested-frames? %))) + (filter #(or (empty? focus) (cpf/is-in-focus? objects focus %))) + (first) + (get objects)))] + + + (reset! hover hover-shape) + (reset! measure-hover measure-hover-shape) + (reset! hover-ids ids))) + + (fn [] + ;; Clean the cache + (mf/set-ref-val! sorted-ids-cache {})))))) (defn setup-viewport-modifiers [modifiers objects]