From 0c5456d6df90a28e53ea8f8df729a5b4ef7ce5d5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 1 Jul 2026 20:25:14 -0400 Subject: [PATCH] perf: stop toolbar/share-menu re-rendering every frame in playback Both derefed the raw ::playhead in their render bodies, so the whole toolbar (zoom/row sliders) and the share menu (even while closed) reconciled 60fps during playback. toolbar now subscribes coarse ::at-start?/::at-end? edges (flip only at the timeline ends) and reads the exact playhead at click time; share-menu reads the playhead at click time too. Co-Authored-By: Claude Opus 4.8 --- tl/src/tl/subs.cljs | 7 +++++++ tl/src/tl/views.cljs | 22 ++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/tl/src/tl/subs.cljs b/tl/src/tl/subs.cljs index 9730e26..708dd93 100644 --- a/tl/src/tl/subs.cljs +++ b/tl/src/tl/subs.cljs @@ -71,6 +71,13 @@ ::playhead (fn [db _] (scene/playhead (:view db) (peek (get-in db [:view :stack]))))) +;; coarse boolean edges of the playhead — subscribing these (instead of the raw +;; playhead) keeps a component from re-rendering every frame during playback: they +;; only flip at the timeline ends. Read ::playhead itself at click time for the +;; actual value. +(rf/reg-sub ::at-start? :<- [::playhead] (fn [ph _] (<= ph 0))) +(rf/reg-sub ::at-end? :<- [::playhead] :<- [::length] (fn [[ph len] _] (>= ph len))) + ;; breadcrumb trail of the stack (rf/reg-sub ::breadcrumbs diff --git a/tl/src/tl/views.cljs b/tl/src/tl/views.cljs index bbbc3d2..f2d1e7a 100644 --- a/tl/src/tl/views.cljs +++ b/tl/src/tl/views.cljs @@ -1167,19 +1167,24 @@ :on-click #(rf/dispatch [::events/pop-to id])} name]]))]))) (defn toolbar [] + ;; NB: subscribe the coarse ::at-start?/::at-end? edges, not the raw playhead — + ;; else the whole toolbar (sliders included) re-renders every frame in playback. + ;; The step buttons read the exact playhead at click time. (let [zoom @(rf/subscribe [::subs/zoom]) row-h @(rf/subscribe [::subs/row-h]) playing? @(rf/subscribe [::subs/playing?]) - ph (js/Math.round @(rf/subscribe [::subs/playhead])) + start? @(rf/subscribe [::subs/at-start?]) + end? @(rf/subscribe [::subs/at-end?]) len @(rf/subscribe [::subs/length]) authed? @(rf/subscribe [::subs/authed?]) - authoring? (some? @(rf/subscribe [::subs/draft-group]))] + authoring? (some? @(rf/subscribe [::subs/draft-group])) + ph-now #(js/Math.round @(rf/subscribe [::subs/playhead]))] [:div.toolbar [:div.transport - [:button.step-btn {:title "Previous frame" :disabled (<= ph 0) - :on-click #(goto! (max 0 (dec ph)))} "◁"] + [:button.step-btn {:title "Previous frame" :disabled start? + :on-click #(goto! (max 0 (dec (ph-now))))} "◁"] [:button.play-btn {:on-click toggle-play! :title "Play / pause"} (if playing? "❚❚" "▶")] - [:button.step-btn {:title "Next frame" :disabled (>= ph len) - :on-click #(goto! (min len (inc ph)))} "▷"]] + [:button.step-btn {:title "Next frame" :disabled end? + :on-click #(goto! (min len (inc (ph-now))))} "▷"]] (when (and authed? (not authoring?)) [:button.add-annotation {:title "Add annotation" :on-click #(rf/dispatch [::events/open-draft])} @@ -1540,7 +1545,6 @@ (fn [] (let [proj @(rf/subscribe [::subs/project]) stack @(rf/subscribe [::subs/stack]) - playhead @(rf/subscribe [::subs/playhead]) id (:id proj) grab (fn [rel which] (copy-text! (abs-url rel)) @@ -1552,7 +1556,9 @@ [:<> [:div.menu-backdrop {:on-click #(reset! open? false)}] [:div.menu-pop - [:button {:on-click #(grab (routes/project-url id stack playhead) :moment)} + ;; read the playhead at click time, not in render, so this menu + ;; doesn't re-render every frame during playback + [:button {:on-click #(grab (routes/project-url id stack @(rf/subscribe [::subs/playhead])) :moment)} (if (= @copied :moment) "✓ Link copied" "Copy link to this moment")] [:button {:on-click #(grab (routes/project-url id [:root] nil) :project)} (if (= @copied :project) "✓ Link copied" "Copy link to the whole project")]]])]))))