0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-10 00:58:26 -05:00

Improved frame generation performance

This commit is contained in:
alonso.torres 2022-05-26 16:18:36 +02:00
parent 57ccb18517
commit 96ed66d86e
6 changed files with 130 additions and 93 deletions

View file

@ -107,6 +107,30 @@
(rx/of (update-indices page-id changes)))) (rx/of (update-indices page-id changes))))
(rx/empty)))))) (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 (defn commit-changes
[{:keys [redo-changes undo-changes [{:keys [redo-changes undo-changes
@ -115,15 +139,18 @@
(log/debug :msg "commit-changes" (log/debug :msg "commit-changes"
:js/redo-changes redo-changes :js/redo-changes redo-changes
:js/undo-changes undo-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 (ptk/reify ::commit-changes
cljs.core/IDeref cljs.core/IDeref
(-deref [_] (-deref [_]
{:file-id file-id {:file-id file-id
:hint-events @st/last-events :hint-events @st/last-events
:hint-origin (ptk/type origin) :hint-origin (ptk/type origin)
:changes redo-changes}) :changes redo-changes
:page-id page-id
:frames frames})
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]

View file

@ -6,6 +6,7 @@
(ns app.main.data.workspace.persistence (ns app.main.data.workspace.persistence
(:require (:require
[app.common.data :as d]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.pages :as cp] [app.common.pages :as cp]
[app.common.spec :as us] [app.common.spec :as us]
@ -17,6 +18,7 @@
[app.main.data.fonts :as df] [app.main.data.fonts :as df]
[app.main.data.workspace.changes :as dch] [app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.util.http :as http] [app.util.http :as http]
@ -138,16 +140,27 @@
:revn (:revn file) :revn (:revn file)
:session-id sid :session-id sid
:changes-with-metadata (into [] changes)}] :changes-with-metadata (into [] changes)}]
(when (= file-id (:id params)) (when (= file-id (:id params))
(->> (rp/mutation :update-file params) (->> (rp/mutation :update-file params)
(rx/mapcat (fn [lagged] (rx/mapcat (fn [lagged]
(log/debug :hint "changes persisted" :lagged (count lagged)) (log/debug :hint "changes persisted" :lagged (count lagged))
(let [lagged (cond->> lagged (let [lagged (cond->> lagged
(= #{sid} (into #{} (map :session-id) lagged)) (= #{sid} (into #{} (map :session-id) lagged))
(map #(assoc % :changes [])))] (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/of lagged)
(rx/mapcat seq) (rx/mapcat seq)
(rx/map #(shapes-changes-persisted file-id %)))))) (rx/map #(shapes-changes-persisted file-id %)))))))
(rx/catch (fn [cause] (rx/catch (fn [cause]
(rx/concat (rx/concat
(rx/of (rt/assign-exception cause)) (rx/of (rt/assign-exception cause))

View file

@ -6,7 +6,6 @@
(ns app.main.data.workspace.thumbnails (ns app.main.data.workspace.thumbnails
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -14,6 +13,8 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.util.dom :as dom]
[app.util.webapi :as wapi]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk])) [potok.core :as ptk]))
@ -26,45 +27,47 @@
(rx/filter #(= % id)) (rx/filter #(= % id))
(rx/take 1))) (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 (defn update-thumbnail
"Updates the thumbnail information for the given frame `id`" "Updates the thumbnail information for the given frame `id`"
[page-id frame-id data] [page-id frame-id]
(let [lock (uuid/next)
object-id (dm/str page-id frame-id)]
(ptk/reify ::update-thumbnail (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))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state _]
(when (= lock (get-in state [::update-thumbnail-lock object-id])) (let [object-id (dm/str page-id frame-id)
(let [stopper (->> stream (rx/filter (ptk/type? :app.main.data.workspace/finalize))) file-id (:current-file-id state)
params {:file-id (:current-file-id state) blob-result (thumbnail-stream object-id)]
: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)) (->> blob-result
(rx/observe-on :async))))))))) (rx/merge-map
(fn [blob]
(if (some? blob)
(wapi/read-file-as-data-url blob)
(rx/of nil))))
(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 (defn- extract-frame-changes
"Process a changes set in a commit to extract the frames that are changing" "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) (let [page-id (get state :current-page-id)
old-shape-thumbnail (get-in state [:workspace-file :thumbnails (dm/str page-id old-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)))))) (-> state (assoc-in [:workspace-file :thumbnails (dm/str page-id new-id)] old-shape-thumbnail))))))

View file

@ -71,11 +71,6 @@
frame-id (:id shape) frame-id (:id shape)
page-id (mf/use-ctx ctx/current-page-id) 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 ;; References to the current rendered node and the its parentn
node-ref (mf/use-var nil) node-ref (mf/use-var nil)
@ -88,11 +83,11 @@
disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers])) disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers]))
[on-load-frame-dom thumb-renderer] [on-load-frame-dom render-frame? thumbnail-renderer]
(ftr/use-render-thumbnail page-id shape node-ref rendered? thumbnail-data-ref disable-thumbnail?) (ftr/use-render-thumbnail page-id shape node-ref rendered? disable-thumbnail?)
on-frame-load 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) (fdm/use-dynamic-modifiers objects @node-ref modifiers)
@ -115,9 +110,9 @@
(rx/dispose! sub))))) (rx/dispose! sub)))))
(mf/use-effect (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 [] (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/mount
(mf/element frame-shape (mf/element frame-shape
#js {:ref on-load-frame-dom :shape shape :fonts fonts}) #js {:ref on-load-frame-dom :shape shape :fonts fonts})
@ -132,9 +127,4 @@
:opacity (when (:hidden shape) 0)} :opacity (when (:hidden shape) 0)}
[:& ff/fontfaces-style {:fonts fonts}] [:& ff/fontfaces-style {:fonts fonts}]
[:g.frame-thumbnail-wrapper {:id (dm/str "thumbnail-container-" (:id shape))} [:g.frame-thumbnail-wrapper {:id (dm/str "thumbnail-container-" (:id shape))}
[:> frame/frame-thumbnail {:key (dm/str (:id shape)) thumbnail-renderer]]]))))
:shape (cond-> shape
(some? thumbnail-data)
(assoc :thumbnail thumbnail-data))}]
thumb-renderer]]]))))

View file

@ -12,7 +12,7 @@
(defn use-node-store (defn use-node-store
"Hook responsible of storing the rendered DOM node in memory while not being used" "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 (let [;; when `true` the node is in memory
in-memory? (mf/use-var true) in-memory? (mf/use-var true)
@ -33,13 +33,13 @@
(swap! re-render inc)))))] (swap! re-render inc)))))]
(mf/use-effect (mf/use-effect
(mf/deps thumbnail?) (mf/deps thumbnail? render-frame?)
(fn [] (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) (.removeChild @parent-ref @node-ref)
(reset! in-memory? true)) (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) (.appendChild @parent-ref @node-ref)
(reset! in-memory? false)))) (reset! in-memory? false))))

View file

@ -9,19 +9,18 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.math :as mth] [app.common.math :as mth]
[app.main.data.workspace :as dw] [app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.main.ui.shapes.frame :as frame]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.timers :as ts] [app.util.timers :as ts]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[debug :refer [debug?]]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
;; (def thumbnail-scale-factor 2) (defn- draw-thumbnail-canvas!
(defn- draw-thumbnail-canvas
[canvas-node img-node] [canvas-node img-node]
(try (try
(when (and (some? canvas-node) (some? img-node)) (when (and (some? canvas-node) (some? img-node))
@ -29,19 +28,12 @@
canvas-width (.-width canvas-node) canvas-width (.-width canvas-node)
canvas-height (.-height 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) (.clearRect canvas-context 0 0 canvas-width canvas-height)
(.drawImage canvas-context img-node 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 (catch :default err
(.error js/console err) (.error js/console err)
nil))) false)))
(defn- remove-image-loading (defn- remove-image-loading
"Remove the changes related to change a url for its embed value. This is necessary "Remove the changes related to change a url for its embed value. This is necessary
@ -59,7 +51,7 @@
(defn use-render-thumbnail (defn use-render-thumbnail
"Hook that will create the thumbnail thata" "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) (let [frame-canvas-ref (mf/use-ref nil)
frame-image-ref (mf/use-ref nil) frame-image-ref (mf/use-ref nil)
@ -78,16 +70,20 @@
updates-str (mf/use-memo #(rx/subject)) 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 on-image-load
(mf/use-callback (mf/use-callback
(fn [] (fn []
(ts/raf (ts/raf
#(let [canvas-node (mf/ref-val frame-canvas-ref) #(let [canvas-node (mf/ref-val frame-canvas-ref)
img-node (mf/ref-val frame-image-ref) img-node (mf/ref-val frame-image-ref)]
thumb-data (draw-thumbnail-canvas canvas-node img-node)] (when (draw-thumbnail-canvas! canvas-node img-node)
(when (some? thumb-data) (reset! image-url nil)
(st/emit! (dw/update-thumbnail page-id id thumb-data)) (reset! render-frame? false))))))
(reset! image-url nil))))))
generate-thumbnail generate-thumbnail
(mf/use-callback (mf/use-callback
@ -172,18 +168,28 @@
(reset! observer-ref nil))))) (reset! observer-ref nil)))))
[on-load-frame-dom [on-load-frame-dom
(when (some? @image-url) @render-frame?
(mf/html (mf/html
[:g.thumbnail-rendering [:*
[:foreignObject {:x x :y y :width width :height height} [:> frame/frame-thumbnail {:key (dm/str (:id shape))
[:canvas {:ref frame-canvas-ref :shape (cond-> shape
:width fixed-width (some? thumbnail-data)
:height fixed-height}]] (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 [:image {:ref frame-image-ref
:x (:x shape) :x (:x shape)
:y (:y shape) :y (:y shape)
:href @image-url :href @image-url
:width (:width shape) :width (:width shape)
:height (:height shape) :height (:height shape)
:on-load on-image-load}]]))])) :on-load on-image-load}])])]))