mirror of
https://github.com/penpot/penpot.git
synced 2026-09-07 20:49:37 +00:00
⚡ Coalesce live drag-preview state via RAF atoms
This commit is contained in:
parent
ca1e1dd806
commit
2d32946c06
@ -30,6 +30,7 @@
|
|||||||
[app.main.data.workspace.shapes :as dwsh]
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
[app.main.features :as features]
|
[app.main.features :as features]
|
||||||
|
[app.main.streams :as ms]
|
||||||
[app.render-wasm.api :as wasm.api]
|
[app.render-wasm.api :as wasm.api]
|
||||||
[app.render-wasm.shape :as wasm.shape]
|
[app.render-wasm.shape :as wasm.shape]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
@ -614,19 +615,27 @@
|
|||||||
(filter (fn [[_ {:keys [type]}]]
|
(filter (fn [[_ {:keys [type]}]]
|
||||||
(= type :change-property)))))
|
(= type :change-property)))))
|
||||||
|
|
||||||
|
;; The live preview state for a drag / resize / rotate gesture lives in
|
||||||
|
;; behavior-subjects in `app.main.streams` (`wasm-modifiers` and
|
||||||
|
;; `workspace-selrect`). These were previously the Redux keys
|
||||||
|
;; `:workspace-wasm-modifiers` / `:workspace-selrect`, but at one
|
||||||
|
;; React commit per pointermove they were the dominant per-event cost
|
||||||
|
;; during a drag. Reads now go through RAF-coalesced atoms in
|
||||||
|
;; `app.main.refs`, which update at most once per animation frame.
|
||||||
|
|
||||||
(defn set-temporary-selrect
|
(defn set-temporary-selrect
|
||||||
[selrect]
|
[selrect]
|
||||||
(ptk/reify ::set-temporary-selrect
|
(ptk/reify ::set-temporary-selrect
|
||||||
ptk/UpdateEvent
|
ptk/EffectEvent
|
||||||
(update [_ state]
|
(effect [_ _ _]
|
||||||
(assoc state :workspace-selrect selrect))))
|
(rx/push! ms/workspace-selrect selrect))))
|
||||||
|
|
||||||
(defn set-temporary-modifiers
|
(defn set-temporary-modifiers
|
||||||
[modifiers]
|
[modifiers]
|
||||||
(ptk/reify ::set-temporary-modifiers
|
(ptk/reify ::set-temporary-modifiers
|
||||||
ptk/UpdateEvent
|
ptk/EffectEvent
|
||||||
(update [_ state]
|
(effect [_ _ _]
|
||||||
(assoc state :workspace-wasm-modifiers modifiers))))
|
(rx/push! ms/wasm-modifiers modifiers))))
|
||||||
|
|
||||||
(def ^:private xf:map-key (map key))
|
(def ^:private xf:map-key (map key))
|
||||||
|
|
||||||
@ -658,15 +667,40 @@
|
|||||||
objects (dsh/lookup-page-objects state)
|
objects (dsh/lookup-page-objects state)
|
||||||
snap-pixel?
|
snap-pixel?
|
||||||
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))]
|
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))]
|
||||||
|
;; --- DRAG PERF INSTRUMENTATION (remove after investigation) ---
|
||||||
|
;; Spans appear in DevTools Performance > Timings track as `swm:*`.
|
||||||
|
;; `props=N`/`geom=N` count tags are logged once per call so you can
|
||||||
|
;; correlate per-event work to subtree size.
|
||||||
|
(js/performance.mark "swm:total:start")
|
||||||
|
(js/console.timeStamp (str "swm props=" (count wasm-props)))
|
||||||
|
(js/performance.mark "swm:set-props:start")
|
||||||
(set-wasm-props! objects prev-wasm-props wasm-props)
|
(set-wasm-props! objects prev-wasm-props wasm-props)
|
||||||
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree))
|
(js/performance.measure "swm:set-props" "swm:set-props:start")
|
||||||
(let [geometry-entries (parse-geometry-modifiers modif-tree)
|
(js/performance.clearMarks "swm:set-props:start")
|
||||||
modifiers (wasm.api/propagate-modifiers geometry-entries snap-pixel?)]
|
(let [structure-entries (parse-structure-modifiers modif-tree)]
|
||||||
(wasm.api/set-modifiers modifiers)
|
(js/performance.mark "swm:struct-mods:start")
|
||||||
(let [ids (into [] xf:map-key geometry-entries)
|
(wasm.api/set-structure-modifiers structure-entries)
|
||||||
selrect (wasm.api/get-selection-rect ids)]
|
(js/performance.measure "swm:struct-mods" "swm:struct-mods:start")
|
||||||
(rx/of (set-temporary-selrect selrect)
|
(js/performance.clearMarks "swm:struct-mods:start")
|
||||||
(set-temporary-modifiers modifiers))))))))
|
(let [geometry-entries (parse-geometry-modifiers modif-tree)
|
||||||
|
_ (js/console.timeStamp (str "swm geom=" (count geometry-entries)))
|
||||||
|
_ (js/performance.mark "swm:propagate:start")
|
||||||
|
modifiers (wasm.api/propagate-modifiers geometry-entries snap-pixel?)
|
||||||
|
_ (js/performance.measure "swm:propagate" "swm:propagate:start")
|
||||||
|
_ (js/performance.clearMarks "swm:propagate:start")]
|
||||||
|
(js/performance.mark "swm:set-mods:start")
|
||||||
|
(wasm.api/set-modifiers modifiers)
|
||||||
|
(js/performance.measure "swm:set-mods" "swm:set-mods:start")
|
||||||
|
(js/performance.clearMarks "swm:set-mods:start")
|
||||||
|
(let [ids (into [] xf:map-key geometry-entries)
|
||||||
|
_ (js/performance.mark "swm:selrect:start")
|
||||||
|
selrect (wasm.api/get-selection-rect ids)
|
||||||
|
_ (js/performance.measure "swm:selrect" "swm:selrect:start")
|
||||||
|
_ (js/performance.clearMarks "swm:selrect:start")]
|
||||||
|
(js/performance.measure "swm:total" "swm:total:start")
|
||||||
|
(js/performance.clearMarks "swm:total:start")
|
||||||
|
(rx/of (set-temporary-selrect selrect)
|
||||||
|
(set-temporary-modifiers modifiers)))))))))
|
||||||
|
|
||||||
(defn propagate-structure-modifiers
|
(defn propagate-structure-modifiers
|
||||||
[modif-tree objects]
|
[modif-tree objects]
|
||||||
|
|||||||
@ -137,9 +137,15 @@
|
|||||||
(ptk/reify ::finish-transform
|
(ptk/reify ::finish-transform
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(-> state
|
(update state :workspace-local dissoc :transform :duplicate-move-started?))
|
||||||
(update :workspace-local dissoc :transform :duplicate-move-started?)
|
|
||||||
(dissoc :workspace-selrect :workspace-wasm-modifiers)))))
|
ptk/EffectEvent
|
||||||
|
(effect [_ _ _]
|
||||||
|
;; Clear the live-preview streams so React overlays unmount when
|
||||||
|
;; the gesture ends. (These streams replaced the redux keys
|
||||||
|
;; `:workspace-wasm-modifiers` / `:workspace-selrect`.)
|
||||||
|
(rx/push! ms/wasm-modifiers nil)
|
||||||
|
(rx/push! ms/workspace-selrect nil))))
|
||||||
|
|
||||||
;; -- Resize --------------------------------------------------------
|
;; -- Resize --------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,41 @@
|
|||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.data.workspace.tokens.selected-set :as dwts]
|
[app.main.data.workspace.tokens.selected-set :as dwts]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.main.streams :as ms]
|
||||||
|
[beicon.v2.core :as rx]
|
||||||
[okulary.core :as l]))
|
[okulary.core :as l]))
|
||||||
|
|
||||||
|
(defn- raf-coalesced-atom
|
||||||
|
"Returns a CLJS atom mirroring the latest value pushed to source-stream,
|
||||||
|
but updated at most once per animation frame for non-nil pushes.
|
||||||
|
Components that `mf/deref` the atom re-render aligned to RAF instead
|
||||||
|
of per push, which matters for streams that fire at pointer-event
|
||||||
|
rate (drag / resize / rotate live preview).
|
||||||
|
|
||||||
|
nil pushes apply synchronously and cancel any pending RAF: a
|
||||||
|
`finish-transform` clear must take effect in the same reducer pass
|
||||||
|
that commits the new shape state, otherwise the previous modifier
|
||||||
|
would double-apply to the already-committed shapes for one frame."
|
||||||
|
[source-stream]
|
||||||
|
(let [a (atom nil)
|
||||||
|
latest (volatile! nil)
|
||||||
|
raf-id (volatile! nil)
|
||||||
|
flush! (fn []
|
||||||
|
(vreset! raf-id nil)
|
||||||
|
(reset! a @latest))]
|
||||||
|
(rx/sub! source-stream
|
||||||
|
(fn [v]
|
||||||
|
(vreset! latest v)
|
||||||
|
(if (nil? v)
|
||||||
|
(do
|
||||||
|
(when-some [id @raf-id]
|
||||||
|
(js/cancelAnimationFrame id)
|
||||||
|
(vreset! raf-id nil))
|
||||||
|
(reset! a nil))
|
||||||
|
(when (nil? @raf-id)
|
||||||
|
(vreset! raf-id (js/requestAnimationFrame flush!))))))
|
||||||
|
a))
|
||||||
|
|
||||||
;; ---- Global refs
|
;; ---- Global refs
|
||||||
|
|
||||||
(def route
|
(def route
|
||||||
@ -160,8 +193,14 @@
|
|||||||
"All tokens related ephimeral state"
|
"All tokens related ephimeral state"
|
||||||
(l/derived :workspace-tokens st/state))
|
(l/derived :workspace-tokens st/state))
|
||||||
|
|
||||||
|
;; Live drag-gesture state. These are intentionally NOT in the Redux
|
||||||
|
;; state tree — they are short-lived UI values updated on every gesture
|
||||||
|
;; tick (drag/resize/rotate) and consumed by viewport overlays, the
|
||||||
|
;; sidebar, and on-canvas widgets. Writing to atoms instead of dispatching
|
||||||
|
;; ptk events skips the event-pipeline overhead and keeps temp state out
|
||||||
|
;; of app state. See `app.main.data.workspace.modifiers` for the writers.
|
||||||
(def workspace-selrect
|
(def workspace-selrect
|
||||||
(l/derived :workspace-selrect st/state))
|
(raf-coalesced-atom ms/workspace-selrect))
|
||||||
|
|
||||||
;; WARNING: Don't use directly from components, this is a proxy to
|
;; WARNING: Don't use directly from components, this is a proxy to
|
||||||
;; improve performance of selected-shapes and
|
;; improve performance of selected-shapes and
|
||||||
@ -382,7 +421,7 @@
|
|||||||
(l/derived :workspace-wasm-editor-styles st/state))
|
(l/derived :workspace-wasm-editor-styles st/state))
|
||||||
|
|
||||||
(def workspace-wasm-modifiers
|
(def workspace-wasm-modifiers
|
||||||
(l/derived :workspace-wasm-modifiers st/state))
|
(raf-coalesced-atom ms/wasm-modifiers))
|
||||||
|
|
||||||
(def ^:private workspace-modifiers-with-objects
|
(def ^:private workspace-modifiers-with-objects
|
||||||
(l/derived
|
(l/derived
|
||||||
|
|||||||
@ -22,6 +22,21 @@
|
|||||||
(or ^boolean (kbd/keyboard-event? event)
|
(or ^boolean (kbd/keyboard-event? event)
|
||||||
^boolean (mse/mouse-event? event)))
|
^boolean (mse/mouse-event? event)))
|
||||||
|
|
||||||
|
;; --- Live preview streams (drag / resize / rotate)
|
||||||
|
;;
|
||||||
|
;; Carry the in-flight modifier tree and selrect during an interactive
|
||||||
|
;; transform. Replaces the previous Redux keys
|
||||||
|
;; `:workspace-wasm-modifiers` and `:workspace-selrect`, which churned
|
||||||
|
;; React state on every pointermove. Writes happen from
|
||||||
|
;; `app.main.data.workspace.modifiers/set-temporary-*`; reads happen
|
||||||
|
;; via the RAF-coalesced atoms in `app.main.refs`.
|
||||||
|
|
||||||
|
(defonce wasm-modifiers
|
||||||
|
(rx/behavior-subject nil))
|
||||||
|
|
||||||
|
(defonce workspace-selrect
|
||||||
|
(rx/behavior-subject nil))
|
||||||
|
|
||||||
;; --- Derived streams
|
;; --- Derived streams
|
||||||
|
|
||||||
(defonce ^:private pointer
|
(defonce ^:private pointer
|
||||||
|
|||||||
@ -46,13 +46,10 @@
|
|||||||
|
|
||||||
(mf/defc single-shape-options*
|
(mf/defc single-shape-options*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [shape page-id file-id libraries] :rest props}]
|
[{:keys [shape page-id file-id libraries wasm-modifiers modifiers] :rest props}]
|
||||||
(let [shape-type (dm/get-prop shape :type)
|
(let [shape-type (dm/get-prop shape :type)
|
||||||
shape-id (dm/get-prop shape :id)
|
shape-id (dm/get-prop shape :id)
|
||||||
|
|
||||||
wasm-modifiers (mf/deref refs/workspace-wasm-modifiers)
|
|
||||||
modifiers (mf/deref refs/workspace-modifiers)
|
|
||||||
|
|
||||||
shape
|
shape
|
||||||
(if (features/active-feature? @st/state "render-wasm/v1")
|
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||||
(let [wasm-modifiers (into {} wasm-modifiers)]
|
(let [wasm-modifiers (into {} wasm-modifiers)]
|
||||||
@ -72,17 +69,23 @@
|
|||||||
:bool [:> bool/options* {:shape shape :file-id file-id :page-id page-id}]
|
:bool [:> bool/options* {:shape shape :file-id file-id :page-id page-id}]
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
(mf/defc shape-options*
|
;; Throttled inner: re-renders at most every 100ms when its props change.
|
||||||
|
;; The parent (shape-options*) feeds wasm-modifiers / modifiers as props so
|
||||||
|
;; this throttle actually applies to drag-time modifier updates.
|
||||||
|
(mf/defc shape-options-throttled*
|
||||||
{::mf/wrap [#(mf/throttle % 100)]
|
{::mf/wrap [#(mf/throttle % 100)]
|
||||||
::mf/private true}
|
::mf/private true}
|
||||||
[{:keys [shapes shapes-with-children selected page-id file-id libraries]}]
|
[{:keys [shapes shapes-with-children selected page-id file-id libraries
|
||||||
|
wasm-modifiers modifiers]}]
|
||||||
(if (= 1 (count selected))
|
(if (= 1 (count selected))
|
||||||
[:> single-shape-options*
|
[:> single-shape-options*
|
||||||
{:page-id page-id
|
{:page-id page-id
|
||||||
:file-id file-id
|
:file-id file-id
|
||||||
:libraries libraries
|
:libraries libraries
|
||||||
:shape (first shapes)
|
:shape (first shapes)
|
||||||
:shapes-with-children shapes-with-children}]
|
:shapes-with-children shapes-with-children
|
||||||
|
:wasm-modifiers wasm-modifiers
|
||||||
|
:modifiers modifiers}]
|
||||||
[:> multiple/options*
|
[:> multiple/options*
|
||||||
{:shapes-with-children shapes-with-children
|
{:shapes-with-children shapes-with-children
|
||||||
:shapes shapes
|
:shapes shapes
|
||||||
@ -90,6 +93,21 @@
|
|||||||
:file-id file-id
|
:file-id file-id
|
||||||
:libraries libraries}]))
|
:libraries libraries}]))
|
||||||
|
|
||||||
|
(mf/defc shape-options*
|
||||||
|
{::mf/private true}
|
||||||
|
[{:keys [shapes shapes-with-children selected page-id file-id libraries]}]
|
||||||
|
(let [wasm-modifiers (mf/deref refs/workspace-wasm-modifiers)
|
||||||
|
modifiers (mf/deref refs/workspace-modifiers)]
|
||||||
|
[:> shape-options-throttled*
|
||||||
|
{:shapes shapes
|
||||||
|
:shapes-with-children shapes-with-children
|
||||||
|
:selected selected
|
||||||
|
:page-id page-id
|
||||||
|
:file-id file-id
|
||||||
|
:libraries libraries
|
||||||
|
:wasm-modifiers wasm-modifiers
|
||||||
|
:modifiers modifiers}]))
|
||||||
|
|
||||||
(mf/defc specialized-panel*
|
(mf/defc specialized-panel*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [panel]}]
|
[{:keys [panel]}]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user