mirror of
https://github.com/penpot/penpot.git
synced 2026-06-16 04:12:03 +00:00
⚡ Cut React reconciliation cost during drag gestures
This commit is contained in:
parent
acb3997ed7
commit
95b21fcf5e
@ -30,6 +30,7 @@
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.render-wasm.shape :as wasm.shape]
|
||||
[beicon.v2.core :as rx]
|
||||
@ -55,6 +56,52 @@
|
||||
(when (compare-and-set! interactive-transform-active? true false)
|
||||
(wasm.api/set-modifiers-end)))
|
||||
|
||||
;; ──────────────────────────────────────────────────────────────────────
|
||||
;; Live drag-gesture state.
|
||||
;;
|
||||
;; `set-wasm-modifiers` runs on every drag pointermove and writes:
|
||||
;; 1. WASM canvas state (synchronous, cheap)
|
||||
;; 2. Live drag values (selrect + per-shape transforms) consumed by
|
||||
;; viewport overlays, the sidebar, and on-canvas widgets.
|
||||
;;
|
||||
;; Step 2 lives in dedicated atoms (refs/workspace-selrect,
|
||||
;; refs/workspace-wasm-modifiers) — NOT in Redux state. Atom writes are
|
||||
;; coalesced to one per animation frame so React subscribers reconcile
|
||||
;; at most once per frame regardless of pointermove arrival rate, and we
|
||||
;; skip the ptk event-pipeline overhead for these high-frequency updates.
|
||||
(defonce ^:private pending-temp-payload (atom nil))
|
||||
(defonce ^:private pending-temp-frame-id (volatile! nil))
|
||||
|
||||
(defn- flush-pending-temp!
|
||||
[]
|
||||
(vreset! pending-temp-frame-id nil)
|
||||
(when-some [payload @pending-temp-payload]
|
||||
(reset! pending-temp-payload nil)
|
||||
(reset! refs/workspace-selrect (:selrect payload))
|
||||
(reset! refs/workspace-wasm-modifiers (:modifiers payload))))
|
||||
|
||||
(defn- schedule-temp-flush!
|
||||
[selrect modifiers]
|
||||
(reset! pending-temp-payload {:selrect selrect :modifiers modifiers})
|
||||
(when (nil? @pending-temp-frame-id)
|
||||
(vreset! pending-temp-frame-id
|
||||
(js/requestAnimationFrame flush-pending-temp!))))
|
||||
|
||||
(defn- cancel-pending-temp-flush!
|
||||
[]
|
||||
(when-some [frame-id @pending-temp-frame-id]
|
||||
(js/cancelAnimationFrame frame-id)
|
||||
(vreset! pending-temp-frame-id nil))
|
||||
(reset! pending-temp-payload nil))
|
||||
|
||||
(defn clear-temp-state!
|
||||
"Reset the live drag-gesture state. Cancels any rAF-queued write so it
|
||||
cannot resurrect the values after they are cleared."
|
||||
[]
|
||||
(cancel-pending-temp-flush!)
|
||||
(reset! refs/workspace-selrect nil)
|
||||
(reset! refs/workspace-wasm-modifiers nil))
|
||||
|
||||
(def ^:private transform-attrs
|
||||
#{:selrect
|
||||
:points
|
||||
@ -296,6 +343,10 @@
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(when (features/active-feature? state "render-wasm/v1")
|
||||
;; Drop any rAF-queued temp dispatch and reset the live drag-gesture
|
||||
;; state — its values are about to be replaced by the committed
|
||||
;; update.
|
||||
(clear-temp-state!)
|
||||
;; End interactive transform mode BEFORE cleaning modifiers so
|
||||
;; the final full-quality render triggered by subsequent shape
|
||||
;; updates is not still classified as "interactive" (which would
|
||||
@ -614,20 +665,6 @@
|
||||
(filter (fn [[_ {:keys [type]}]]
|
||||
(= type :change-property)))))
|
||||
|
||||
(defn set-temporary-selrect
|
||||
[selrect]
|
||||
(ptk/reify ::set-temporary-selrect
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :workspace-selrect selrect))))
|
||||
|
||||
(defn set-temporary-modifiers
|
||||
[modifiers]
|
||||
(ptk/reify ::set-temporary-modifiers
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :workspace-wasm-modifiers modifiers))))
|
||||
|
||||
(def ^:private xf:map-key (map key))
|
||||
|
||||
#_:clj-kondo/ignore
|
||||
@ -666,8 +703,13 @@
|
||||
(wasm.api/set-modifiers modifiers)
|
||||
(let [ids (into [] xf:map-key geometry-entries)
|
||||
selrect (wasm.api/get-selection-rect ids)]
|
||||
(rx/of (set-temporary-selrect selrect)
|
||||
(set-temporary-modifiers modifiers)))))))))
|
||||
;; Coalesce the Redux dispatch to one per animation frame so
|
||||
;; React subscribers (sidebar, viewport overlays, flex
|
||||
;; controls) reconcile at most once per frame regardless of
|
||||
;; pointermove arrival rate. The WASM canvas update above
|
||||
;; already happened synchronously.
|
||||
(schedule-temp-flush! selrect modifiers)
|
||||
(rx/empty))))))))
|
||||
|
||||
(defn propagate-structure-modifiers
|
||||
[modif-tree objects]
|
||||
|
||||
@ -135,11 +135,16 @@
|
||||
|
||||
(defn finish-transform []
|
||||
(ptk/reify ::finish-transform
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
;; Live drag-gesture state lives in dedicated atoms (see
|
||||
;; app.main.data.workspace.modifiers); reset them here so the
|
||||
;; viewport overlays and sidebar drop the in-flight values.
|
||||
(dwm/clear-temp-state!))
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :workspace-local dissoc :transform :duplicate-move-started?)
|
||||
(dissoc :workspace-selrect :workspace-wasm-modifiers)))))
|
||||
(update state :workspace-local dissoc :transform :duplicate-move-started?))))
|
||||
|
||||
;; -- Resize --------------------------------------------------------
|
||||
|
||||
|
||||
@ -160,8 +160,17 @@
|
||||
"All tokens related ephimeral 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
|
||||
(l/derived :workspace-selrect st/state))
|
||||
(l/atom nil))
|
||||
|
||||
(def workspace-wasm-modifiers
|
||||
(l/atom nil))
|
||||
|
||||
;; WARNING: Don't use directly from components, this is a proxy to
|
||||
;; improve performance of selected-shapes and
|
||||
@ -381,8 +390,9 @@
|
||||
(def workspace-wasm-editor-styles
|
||||
(l/derived :workspace-wasm-editor-styles st/state))
|
||||
|
||||
(def workspace-wasm-modifiers
|
||||
(l/derived :workspace-wasm-modifiers st/state))
|
||||
;; `workspace-wasm-modifiers` is defined alongside `workspace-selrect`
|
||||
;; near the top of this file as a plain atom (not derived from app
|
||||
;; state). Keep it in mind when grepping.
|
||||
|
||||
(def ^:private workspace-modifiers-with-objects
|
||||
(l/derived
|
||||
|
||||
@ -46,13 +46,10 @@
|
||||
|
||||
(mf/defc single-shape-options*
|
||||
{::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)
|
||||
shape-id (dm/get-prop shape :id)
|
||||
|
||||
wasm-modifiers (mf/deref refs/workspace-wasm-modifiers)
|
||||
modifiers (mf/deref refs/workspace-modifiers)
|
||||
|
||||
shape
|
||||
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||
(let [wasm-modifiers (into {} wasm-modifiers)]
|
||||
@ -72,17 +69,23 @@
|
||||
:bool [:> bool/options* {:shape shape :file-id file-id :page-id page-id}]
|
||||
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/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))
|
||||
[:> single-shape-options*
|
||||
{:page-id page-id
|
||||
:file-id file-id
|
||||
:libraries libraries
|
||||
:shape (first shapes)
|
||||
:shapes-with-children shapes-with-children}]
|
||||
:shapes-with-children shapes-with-children
|
||||
:wasm-modifiers wasm-modifiers
|
||||
:modifiers modifiers}]
|
||||
[:> multiple/options*
|
||||
{:shapes-with-children shapes-with-children
|
||||
:shapes shapes
|
||||
@ -90,6 +93,21 @@
|
||||
:file-id file-id
|
||||
: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/private true}
|
||||
[{:keys [panel]}]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user