diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index c5e6992015..ec228d5de7 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -443,6 +443,14 @@ (mf/use-fn (fn [event] (drag-debug-bump-move!) + ;; FIXME(pan-end-debug): log first 3 pointer-moves of any drag so we + ;; can distinguish "user paused" (no moves arriving) from "freeze + ;; in the chain" (moves arriving but set-view-box delayed). + (let [n (.-moveCount drag-debug-state)] + (when (<= n 3) + (js/console.log + "[drag-debug] pointer-move #" n + "@" (.-timeStamp event) "ms (timestamp)"))) (let [raw-pt (dom/get-client-position event) pt (uwvv/point->viewport raw-pt) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 62ae893dda..478eca4f7f 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -1133,6 +1133,10 @@ ;; Fire the deferred full render the moment the gesture ends. We watch the ;; store rather than coupling viewport.cljs to api.cljs (which would risk a ;; circular dep) — render-wasm already depends on app.main.store. +;; FIXME(pan-end-debug): timestamp of the most recent gesture start, used +;; only to measure gesture-start latency. Remove with rest of pan-end debug. +(defonce ^:private _gesture-start-ts (atom nil)) + (defonce ^:private _gesture-end-watcher (add-watch st/state ::gesture-end-watcher @@ -1145,6 +1149,12 @@ now-active? (or (:panning new-ws) (:zooming new-ws) (some? (:transform new-ws)))] + (when (and (not was-active?) now-active?) + ;; FIXME(pan-end-debug): remove with rest of pan-end debug instrumentation + (reset! _gesture-start-ts (js/performance.now)) + (js/console.log + "[pan-end-debug] gesture STARTED -> ts=" + @_gesture-start-ts)) (when (and was-active? (not now-active?)) ;; FIXME(pan-end-debug): remove with rest of pan-end debug instrumentation (js/console.log "[pan-end-debug] gesture ended -> flushing render") @@ -1153,16 +1163,25 @@ (defn set-view-box [zoom vbox] ;; FIXME(pan-end-debug): remove with rest of pan-end debug instrumentation - (js/console.log "[pan-end-debug] set-view-box CALLED (vbox:" (:x vbox) (:y vbox) ")") - (perf/begin-measure "set-view-box") - (h/call wasm/internal-module "_set_view_start") - (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) - (perf/end-measure "set-view-box") - - (perf/begin-measure "render-from-cache") - (h/call wasm/internal-module "_render_from_cache" 0) - (render-finish) - (perf/end-measure "render-from-cache")) + (let [t0 (js/performance.now) + start-ts @_gesture-start-ts + delta-from-start (when start-ts (- t0 start-ts))] + (perf/begin-measure "set-view-box") + (h/call wasm/internal-module "_set_view_start") + (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) + (perf/end-measure "set-view-box") + (let [t1 (js/performance.now)] + (perf/begin-measure "render-from-cache") + (h/call wasm/internal-module "_render_from_cache" 0) + (render-finish) + (perf/end-measure "render-from-cache") + (let [t2 (js/performance.now)] + (js/console.log + "[pan-end-debug] set-view-box CALLED" + "Δfromstart=" (if delta-from-start (str (.toFixed delta-from-start 1) "ms") "n/a") + "viewStart+set=" (str (.toFixed (- t1 t0) 2) "ms") + "renderFromCache=" (str (.toFixed (- t2 t1) 2) "ms") + "TOTAL=" (str (.toFixed (- t2 t0) 2) "ms")))))) (defn- ensure-text-content "Guarantee that the shape always sends a valid text tree to WASM. When the diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 2f5d7ef175..09278b71ec 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -428,8 +428,19 @@ pub extern "C" fn set_view_start() -> Result<()> { VIEW_INTERACTION_START = performance::get_time(); } performance::begin_measure!("set_view_start"); - get_render_state().options.set_fast_mode(true); + let render_state = get_render_state(); + render_state.options.set_fast_mode(true); + // Cancel any in-flight async full-quality render from the previous + // gesture. Without this, an `_render 0` started at the previous + // gesture-end keeps eating rAFs and eventually runs + // `post_full_render_block` mid-gesture, blocking the main thread for + // tens to hundreds of ms — visible as a "freeze" right after the new + // gesture starts. Symmetric with `set_view_end`, which already does + // this on the way out. + render_state.cancel_animation_frame(); performance::end_measure!("set_view_start"); + // FIXME(pan-end-debug): remove with rest of pan-end debug instrumentation + println!("[pan-end-debug] set_view_start: cancelled in-flight render (if any)"); Ok(()) }