From 96ed66d86e2128a1f57c876d2603b1059e3a2dbb Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 26 May 2022 16:18:36 +0200 Subject: [PATCH] :zap: Improved frame generation performance --- .../src/app/main/data/workspace/changes.cljs | 33 +++++++- .../app/main/data/workspace/persistence.cljs | 21 ++++- .../app/main/data/workspace/thumbnails.cljs | 77 ++++++++++--------- .../app/main/ui/workspace/shapes/frame.cljs | 22 ++---- .../ui/workspace/shapes/frame/node_store.cljs | 8 +- .../shapes/frame/thumbnail_render.cljs | 62 ++++++++------- 6 files changed, 130 insertions(+), 93 deletions(-) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index c8f1f166c..fa2235c1a 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -107,6 +107,30 @@ (rx/of (update-indices page-id changes)))) (rx/empty)))))) +(defn changed-frames + "Extracts the frame-ids changed in the given changes" + [changes objects] + + (let [change->ids + (fn [change] + (case (:type change) + :add-obj + [(:parent-id change)] + + (:mod-obj :del-obj) + [(:id change)] + + :mov-objects + (d/concat-vec (:shapes change) [(:parent-id change)]) + + []))] + (into #{} + (comp (mapcat change->ids) + (keep #(if (= :frame (get-in objects [% :type])) + % + (get-in objects [% :frame-id]))) + (remove #(= uuid/zero %))) + changes))) (defn commit-changes [{:keys [redo-changes undo-changes @@ -115,15 +139,18 @@ (log/debug :msg "commit-changes" :js/redo-changes redo-changes :js/undo-changes undo-changes) - (let [error (volatile! nil)] + (let [error (volatile! nil) + page-id (:current-page-id @st/state) + frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))] (ptk/reify ::commit-changes cljs.core/IDeref (-deref [_] - {:file-id file-id :hint-events @st/last-events :hint-origin (ptk/type origin) - :changes redo-changes}) + :changes redo-changes + :page-id page-id + :frames frames}) ptk/UpdateEvent (update [_ state] diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 788f31ca5..64c237b69 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -6,6 +6,7 @@ (ns app.main.data.workspace.persistence (:require + [app.common.data :as d] [app.common.logging :as log] [app.common.pages :as cp] [app.common.spec :as us] @@ -17,6 +18,7 @@ [app.main.data.fonts :as df] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.thumbnails :as dwt] [app.main.repo :as rp] [app.main.store :as st] [app.util.http :as http] @@ -138,16 +140,27 @@ :revn (:revn file) :session-id sid :changes-with-metadata (into [] changes)}] + (when (= file-id (:id params)) (->> (rp/mutation :update-file params) (rx/mapcat (fn [lagged] (log/debug :hint "changes persisted" :lagged (count lagged)) (let [lagged (cond->> lagged (= #{sid} (into #{} (map :session-id) lagged)) - (map #(assoc % :changes [])))] - (->> (rx/of lagged) - (rx/mapcat seq) - (rx/map #(shapes-changes-persisted file-id %)))))) + (map #(assoc % :changes []))) + + frame-updates + (-> (group-by :page-id changes) + (d/update-vals #(into #{} (mapcat :frames) %)))] + + (rx/merge + (->> (rx/from frame-updates) + (rx/flat-map (fn [[page-id frames]] + (->> frames (map #(vector page-id %))))) + (rx/map (fn [[page-id frame-id]] (dwt/update-thumbnail page-id frame-id)))) + (->> (rx/of lagged) + (rx/mapcat seq) + (rx/map #(shapes-changes-persisted file-id %))))))) (rx/catch (fn [cause] (rx/concat (rx/of (rt/assign-exception cause)) diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 8a5c117ff..7766494a1 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -6,7 +6,6 @@ (ns app.main.data.workspace.thumbnails (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] @@ -14,6 +13,8 @@ [app.main.refs :as refs] [app.main.repo :as rp] [app.main.store :as st] + [app.util.dom :as dom] + [app.util.webapi :as wapi] [beicon.core :as rx] [potok.core :as ptk])) @@ -26,45 +27,47 @@ (rx/filter #(= % id)) (rx/take 1))) +(defn thumbnail-stream + [object-id] + (rx/create + (fn [subs] + (let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'" object-id))] + (if (some? node) + (-> node + (.toBlob (fn [blob] + (rx/push! subs blob) + (rx/end! subs)) + "image/png")) + + ;; If we cannot find the node we send `nil` and the upsert will delete the thumbnail + (do (rx/push! subs nil) + (rx/end! subs))))))) + (defn update-thumbnail "Updates the thumbnail information for the given frame `id`" - [page-id frame-id data] - (let [lock (uuid/next) - object-id (dm/str page-id frame-id)] + [page-id frame-id] + (ptk/reify ::update-thumbnail + ptk/WatchEvent + (watch [_ state _] + (let [object-id (dm/str page-id frame-id) + file-id (:current-file-id state) + blob-result (thumbnail-stream object-id)] - (ptk/reify ::update-thumbnail - IDeref - (-deref [_] {:object-id object-id :data data}) - - ptk/UpdateEvent - (update [_ state] - (-> state - (assoc-in [:workspace-file :thumbnails object-id] data) - (cond-> (nil? (get-in state [::update-thumbnail-lock object-id])) - (assoc-in [::update-thumbnail-lock object-id] lock)))) + (->> blob-result + (rx/merge-map + (fn [blob] + (if (some? blob) + (wapi/read-file-as-data-url blob) + (rx/of nil)))) - ptk/WatchEvent - (watch [_ state stream] - (when (= lock (get-in state [::update-thumbnail-lock object-id])) - (let [stopper (->> stream (rx/filter (ptk/type? :app.main.data.workspace/finalize))) - params {:file-id (:current-file-id state) - :object-id object-id}] - ;; Sends the first event and debounce the rest. Will only make one update once - ;; the 2 second debounce is finished - (rx/merge - (->> stream - (rx/filter (ptk/type? ::update-thumbnail)) - (rx/map deref) - (rx/filter #(= object-id (:object-id %))) - (rx/debounce 2000) - (rx/take 1) - (rx/map :data) - (rx/flat-map #(rp/mutation! :upsert-file-object-thumbnail (assoc params :data %))) - (rx/map #(fn [state] (d/dissoc-in state [::update-thumbnail-lock object-id]))) - (rx/take-until stopper)) - - (->> (rx/of (update-thumbnail page-id frame-id data)) - (rx/observe-on :async))))))))) + (rx/merge-map + (fn [data] + (let [params {:file-id file-id :object-id object-id :data data}] + (rx/merge + ;; Update the local copy of the thumbnails so we don't need to request it again + (rx/of #(assoc-in % [:workspace-file :thumbnails object-id] data)) + (->> (rp/mutation! :upsert-file-object-thumbnail params) + (rx/ignore))))))))))) (defn- extract-frame-changes "Process a changes set in a commit to extract the frames that are changing" @@ -156,5 +159,3 @@ (let [page-id (get state :current-page-id) old-shape-thumbnail (get-in state [:workspace-file :thumbnails (dm/str page-id old-id)])] (-> state (assoc-in [:workspace-file :thumbnails (dm/str page-id new-id)] old-shape-thumbnail)))))) - - diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index a0435e4ea..dcfc8b3e5 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -71,11 +71,6 @@ frame-id (:id shape) page-id (mf/use-ctx ctx/current-page-id) - thumbnail-data-ref (mf/use-memo (mf/deps page-id frame-id) #(refs/thumbnail-frame-data page-id frame-id)) - thumbnail-data (mf/deref thumbnail-data-ref) - - thumbnail? (and thumbnail? (some? thumbnail-data)) - ;; References to the current rendered node and the its parentn node-ref (mf/use-var nil) @@ -88,11 +83,11 @@ disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers])) - [on-load-frame-dom thumb-renderer] - (ftr/use-render-thumbnail page-id shape node-ref rendered? thumbnail-data-ref disable-thumbnail?) + [on-load-frame-dom render-frame? thumbnail-renderer] + (ftr/use-render-thumbnail page-id shape node-ref rendered? disable-thumbnail?) on-frame-load - (fns/use-node-store thumbnail? node-ref rendered?)] + (fns/use-node-store thumbnail? node-ref rendered? render-frame?)] (fdm/use-dynamic-modifiers objects @node-ref modifiers) @@ -115,9 +110,9 @@ (rx/dispose! sub))))) (mf/use-effect - (mf/deps shape fonts thumbnail? on-load-frame-dom @force-render) + (mf/deps shape fonts thumbnail? on-load-frame-dom @force-render render-frame?) (fn [] - (when (and (some? @node-ref) (or @rendered? (not thumbnail?) @force-render)) + (when (and (some? @node-ref) (or @rendered? (not thumbnail?) @force-render render-frame?)) (mf/mount (mf/element frame-shape #js {:ref on-load-frame-dom :shape shape :fonts fonts}) @@ -132,9 +127,4 @@ :opacity (when (:hidden shape) 0)} [:& ff/fontfaces-style {:fonts fonts}] [:g.frame-thumbnail-wrapper {:id (dm/str "thumbnail-container-" (:id shape))} - [:> frame/frame-thumbnail {:key (dm/str (:id shape)) - :shape (cond-> shape - (some? thumbnail-data) - (assoc :thumbnail thumbnail-data))}] - - thumb-renderer]]])))) + thumbnail-renderer]]])))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/node_store.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/node_store.cljs index 52c31d920..6d9730530 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/node_store.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/node_store.cljs @@ -12,7 +12,7 @@ (defn use-node-store "Hook responsible of storing the rendered DOM node in memory while not being used" - [thumbnail? node-ref rendered?] + [thumbnail? node-ref rendered? render-frame?] (let [;; when `true` the node is in memory in-memory? (mf/use-var true) @@ -33,13 +33,13 @@ (swap! re-render inc)))))] (mf/use-effect - (mf/deps thumbnail?) + (mf/deps thumbnail? render-frame?) (fn [] - (when (and (some? @parent-ref) (some? @node-ref) @rendered? thumbnail?) + (when (and (some? @parent-ref) (some? @node-ref) @rendered? (and thumbnail? (not render-frame?))) (.removeChild @parent-ref @node-ref) (reset! in-memory? true)) - (when (and (some? @node-ref) @in-memory? (not thumbnail?)) + (when (and (some? @node-ref) @in-memory? (or (not thumbnail?) render-frame?)) (.appendChild @parent-ref @node-ref) (reset! in-memory? false)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index 74a561b5a..87100daa3 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -9,19 +9,18 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.math :as mth] - [app.main.data.workspace :as dw] - [app.main.store :as st] + [app.main.refs :as refs] [app.main.ui.hooks :as hooks] + [app.main.ui.shapes.frame :as frame] [app.util.dom :as dom] [app.util.object :as obj] [app.util.timers :as ts] [beicon.core :as rx] [cuerdas.core :as str] + [debug :refer [debug?]] [rumext.alpha :as mf])) -;; (def thumbnail-scale-factor 2) - -(defn- draw-thumbnail-canvas +(defn- draw-thumbnail-canvas! [canvas-node img-node] (try (when (and (some? canvas-node) (some? img-node)) @@ -29,19 +28,12 @@ canvas-width (.-width canvas-node) canvas-height (.-height canvas-node)] - ;; TODO: Expermient with different scale factors - ;; (set! (.-width canvas-node) (* thumbnail-scale-factor canvas-width)) - ;; (set! (.-height canvas-node) (* thumbnail-scale-factor canvas-height)) - ;; (.setTransform canvas-context thumbnail-scale-factor 0 0 thumbnail-scale-factor 0 0) - ;; (set! (.-imageSmoothingEnabled canvas-context) true) - ;; (set! (.-imageSmoothingQuality canvas-context) "high") - (.clearRect canvas-context 0 0 canvas-width canvas-height) (.drawImage canvas-context img-node 0 0 canvas-width canvas-height) - (.toDataURL canvas-node "image/png" 1.0))) + true)) (catch :default err (.error js/console err) - nil))) + false))) (defn- remove-image-loading "Remove the changes related to change a url for its embed value. This is necessary @@ -59,7 +51,7 @@ (defn use-render-thumbnail "Hook that will create the thumbnail thata" - [page-id {:keys [id x y width height] :as shape} node-ref rendered? thumbnail-data-ref disable?] + [page-id {:keys [id x y width height] :as shape} node-ref rendered? disable?] (let [frame-canvas-ref (mf/use-ref nil) frame-image-ref (mf/use-ref nil) @@ -78,16 +70,20 @@ updates-str (mf/use-memo #(rx/subject)) + thumbnail-data-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id)) + thumbnail-data (mf/deref thumbnail-data-ref) + + render-frame? (mf/use-state (not thumbnail-data)) + on-image-load (mf/use-callback (fn [] (ts/raf #(let [canvas-node (mf/ref-val frame-canvas-ref) - img-node (mf/ref-val frame-image-ref) - thumb-data (draw-thumbnail-canvas canvas-node img-node)] - (when (some? thumb-data) - (st/emit! (dw/update-thumbnail page-id id thumb-data)) - (reset! image-url nil)))))) + img-node (mf/ref-val frame-image-ref)] + (when (draw-thumbnail-canvas! canvas-node img-node) + (reset! image-url nil) + (reset! render-frame? false)))))) generate-thumbnail (mf/use-callback @@ -172,18 +168,28 @@ (reset! observer-ref nil))))) [on-load-frame-dom - (when (some? @image-url) - (mf/html - [:g.thumbnail-rendering - [:foreignObject {:x x :y y :width width :height height} - [:canvas {:ref frame-canvas-ref - :width fixed-width - :height fixed-height}]] + @render-frame? + (mf/html + [:* + [:> frame/frame-thumbnail {:key (dm/str (:id shape)) + :shape (cond-> shape + (some? thumbnail-data) + (assoc :thumbnail thumbnail-data))}] + [:foreignObject {:x x :y y :width width :height height} + [:canvas.thumbnail-canvas + {:ref frame-canvas-ref + :data-object-id (dm/str page-id (:id shape)) + :width fixed-width + :height fixed-height + ;; DEBUG + :style {:filter (when (debug? :thumbnails) "sepia(1)")}}]] + + (when (some? @image-url) [:image {:ref frame-image-ref :x (:x shape) :y (:y shape) :href @image-url :width (:width shape) :height (:height shape) - :on-load on-image-load}]]))])) + :on-load on-image-load}])])]))