optimization: perf improvements
This commit is contained in:
parent
40ff2ecf52
commit
d22bff7654
2 changed files with 81 additions and 38 deletions
|
|
@ -68,7 +68,7 @@
|
||||||
(defonce ^:private last-replaced (atom nil))
|
(defonce ^:private last-replaced (atom nil))
|
||||||
(def ^:private last-url-key "tl/last-url")
|
(def ^:private last-url-key "tl/last-url")
|
||||||
|
|
||||||
(defn replace-project-state! [{:keys [id stack playhead]}]
|
(defn- do-replace! [{:keys [id stack playhead]}]
|
||||||
(when id
|
(when id
|
||||||
(let [url (project-url id stack playhead)]
|
(let [url (project-url id stack playhead)]
|
||||||
(when (not= url @last-replaced)
|
(when (not= url @last-replaced)
|
||||||
|
|
@ -77,6 +77,24 @@
|
||||||
;; remember where we were so an installed PWA can come back here
|
;; remember where we were so an installed PWA can come back here
|
||||||
(.setItem js/localStorage last-url-key url)))))
|
(.setItem js/localStorage last-url-key url)))))
|
||||||
|
|
||||||
|
;; Playback moves the playhead ~60fps, and set-playhead syncs it into the URL's
|
||||||
|
;; ?f= param — but rebuilding the URL + history.replaceState + a synchronous
|
||||||
|
;; localStorage write every frame hitches the main thread. Trailing-throttle so
|
||||||
|
;; the deep-link / PWA-resume state stays roughly current (leading edge keeps
|
||||||
|
;; navigation snappy) without per-frame writes.
|
||||||
|
(defonce ^:private replace-timer (atom nil))
|
||||||
|
(defonce ^:private pending-args (atom nil))
|
||||||
|
|
||||||
|
(defn replace-project-state! [args]
|
||||||
|
(reset! pending-args args)
|
||||||
|
(when-not @replace-timer
|
||||||
|
(do-replace! args) ; leading edge: immediate
|
||||||
|
(reset! replace-timer
|
||||||
|
(js/setTimeout (fn []
|
||||||
|
(reset! replace-timer nil)
|
||||||
|
(do-replace! @pending-args)) ; trailing: final position
|
||||||
|
400))))
|
||||||
|
|
||||||
(defn- standalone?
|
(defn- standalone?
|
||||||
"Running as an installed PWA (added to homescreen), not a browser tab."
|
"Running as an installed PWA (added to homescreen), not a browser tab."
|
||||||
[]
|
[]
|
||||||
|
|
|
||||||
|
|
@ -359,17 +359,30 @@
|
||||||
:ref (fn [n] (when n (reset! video-el n)))}]
|
:ref (fn [n] (when n (reset! video-el n)))}]
|
||||||
[drawing-layer]]))))
|
[drawing-layer]]))))
|
||||||
|
|
||||||
|
(defn- build-label-map
|
||||||
|
"{mark-id → \"Track (occurrence)\"} for every segment, in one pass over `segs`."
|
||||||
|
[scene segs]
|
||||||
|
(into {}
|
||||||
|
(mapcat (fn [[t tsegs]]
|
||||||
|
(map-indexed (fn [i {m :mark}]
|
||||||
|
[m (str (get-in scene [:tracks t :name] (name t)) " (" (inc i) ")")])
|
||||||
|
(sort-by (comp first :local) tsegs)))
|
||||||
|
(group-by :track segs))))
|
||||||
|
|
||||||
|
;; clip-label ran group-by + sort over ALL segments on every call, and it's called
|
||||||
|
;; once per clip per render → O(segs²) on each timeline re-render (zoom, jump, …).
|
||||||
|
;; The labels are stable per `segs`, so memoize the whole map on segs identity
|
||||||
|
;; (::segments returns a stable ref until scene/context changes) → O(1) lookups.
|
||||||
|
(defonce ^:private label-cache (atom {:segs nil :map nil}))
|
||||||
|
|
||||||
(defn- clip-label
|
(defn- clip-label
|
||||||
"Track-local clip label for the content segment whose :mark is `mid`."
|
"Track-local clip label for the content segment whose :mark is `mid`."
|
||||||
[scene segs mid]
|
[scene segs mid]
|
||||||
(or (some (fn [[t tsegs]]
|
(let [c @label-cache
|
||||||
(some (fn [[i {m :mark}]]
|
m (if (identical? segs (:segs c))
|
||||||
(when (= m mid)
|
(:map c)
|
||||||
(str (get-in scene [:tracks t :name] (name t))
|
(:map (reset! label-cache {:segs segs :map (build-label-map scene segs)})))]
|
||||||
" (" (inc i) ")")))
|
(get m mid "clip")))
|
||||||
(map-indexed vector (sort-by (comp first :local) tsegs))))
|
|
||||||
(group-by :track segs))
|
|
||||||
"clip"))
|
|
||||||
|
|
||||||
(defn- clip-at
|
(defn- clip-at
|
||||||
"Describe local frame `lf` the way the annotation editor does: the underlying
|
"Describe local frame `lf` the way the annotation editor does: the underlying
|
||||||
|
|
@ -449,6 +462,43 @@
|
||||||
(.addEventListener js/document "mousemove" move)
|
(.addEventListener js/document "mousemove" move)
|
||||||
(.addEventListener js/document "mouseup" up)))
|
(.addEventListener js/document "mouseup" up)))
|
||||||
|
|
||||||
|
(defn- playhead-fx
|
||||||
|
"Renders nothing. Isolates the playhead-driven side effects — positioning the
|
||||||
|
(imperative) playhead line and the follow-scroll during playback — so a moving
|
||||||
|
playhead re-renders only this empty component, not the whole timeline DOM (all
|
||||||
|
clips + per-clip clip-label) every animation frame. The timeline's hiccup never
|
||||||
|
depended on the playhead; only these effects did."
|
||||||
|
[fps zoom segs anns scene]
|
||||||
|
(let [playhead @(rf/subscribe [::subs/playhead])
|
||||||
|
playing? @(rf/subscribe [::subs/playing?])]
|
||||||
|
(if playing?
|
||||||
|
(r/after-render
|
||||||
|
(fn []
|
||||||
|
(follow! fps zoom playhead)
|
||||||
|
;; first load: if playhead was deep-linked (from query param), scroll to it once
|
||||||
|
(when (and (nil? @scrolled-on-load?) (> playhead 0) (seq segs))
|
||||||
|
(reset! scrolled-on-load? true)
|
||||||
|
(scroll-to-seg-track! segs playhead))
|
||||||
|
;; clip-following: only while the playhead is inside a bar of an annotation
|
||||||
|
;; that opts in (:meta :scroll-to) and is not hidden — scroll each clip into
|
||||||
|
;; view as the playhead crosses onto a new bar. Track [ann-id bar-idx] to
|
||||||
|
;; avoid scrolling within the same bar.
|
||||||
|
(when-let [current-bar (some (fn [a]
|
||||||
|
(when (and (not (get-in (get-in scene [:groups (:id a)]) [:meta :hidden]))
|
||||||
|
(ann-scroll-to? (get-in scene [:groups (:id a)]))
|
||||||
|
(some (fn [[lo hi]] (<= lo playhead hi)) (:bars a)))
|
||||||
|
[(:id a) (first (keep-indexed (fn [j [lo hi]]
|
||||||
|
(when (<= lo playhead hi) j))
|
||||||
|
(:bars a)))]))
|
||||||
|
anns)]
|
||||||
|
(when (not= current-bar @played-bar)
|
||||||
|
(reset! played-bar current-bar)
|
||||||
|
(scroll-to-seg-track! segs playhead)))))
|
||||||
|
(do (reset! played-bar nil)
|
||||||
|
(reset! scrolled-on-load? nil)))
|
||||||
|
(r/after-render #(position-playhead! fps zoom playhead))
|
||||||
|
nil))
|
||||||
|
|
||||||
(defn timeline []
|
(defn timeline []
|
||||||
(let [content (atom nil)
|
(let [content (atom nil)
|
||||||
lanes-scroll (atom nil)
|
lanes-scroll (atom nil)
|
||||||
|
|
@ -462,8 +512,6 @@
|
||||||
tracks @(rf/subscribe [::subs/tracks])
|
tracks @(rf/subscribe [::subs/tracks])
|
||||||
anns @(rf/subscribe [::subs/annotations])
|
anns @(rf/subscribe [::subs/annotations])
|
||||||
thumbs @(rf/subscribe [::subs/thumbnails])
|
thumbs @(rf/subscribe [::subs/thumbnails])
|
||||||
playhead @(rf/subscribe [::subs/playhead])
|
|
||||||
playing? @(rf/subscribe [::subs/playing?])
|
|
||||||
authoring? (some? @(rf/subscribe [::subs/draft-group]))
|
authoring? (some? @(rf/subscribe [::subs/draft-group]))
|
||||||
linking @(rf/subscribe [::subs/linking])
|
linking @(rf/subscribe [::subs/linking])
|
||||||
len @(rf/subscribe [::subs/length])
|
len @(rf/subscribe [::subs/length])
|
||||||
|
|
@ -471,33 +519,8 @@
|
||||||
lane-h (max 18 (* 18 (count anns)))
|
lane-h (max 18 (* 18 (count anns)))
|
||||||
tracks-h (* row-h (count tracks))
|
tracks-h (* row-h (count tracks))
|
||||||
track-y (into {} (map-indexed (fn [i t] [(:id t) i]) tracks))]
|
track-y (into {} (map-indexed (fn [i t] [(:id t) i]) tracks))]
|
||||||
(if playing?
|
|
||||||
(r/after-render
|
|
||||||
(fn []
|
|
||||||
(follow! fps zoom playhead)
|
|
||||||
;; first load: if playhead was deep-linked (from query param), scroll to it once
|
|
||||||
(when (and (nil? @scrolled-on-load?) (> playhead 0) (seq segs))
|
|
||||||
(reset! scrolled-on-load? true)
|
|
||||||
(scroll-to-seg-track! segs playhead))
|
|
||||||
;; clip-following: only while the playhead is actually inside a bar of
|
|
||||||
;; an annotation that opts in (:meta :scroll-to) and is not hidden —
|
|
||||||
;; scroll each of its clips into view as the playhead crosses onto a new bar (discontinuity).
|
|
||||||
;; Track [ann-id bar-idx] to avoid scrolling within the same bar.
|
|
||||||
(when-let [current-bar (some (fn [a]
|
|
||||||
(when (and (not (get-in (get-in scene [:groups (:id a)]) [:meta :hidden]))
|
|
||||||
(ann-scroll-to? (get-in scene [:groups (:id a)]))
|
|
||||||
(some (fn [[lo hi]] (<= lo playhead hi)) (:bars a)))
|
|
||||||
[(:id a) (first (keep-indexed (fn [j [lo hi]]
|
|
||||||
(when (<= lo playhead hi) j))
|
|
||||||
(:bars a)))]))
|
|
||||||
anns)]
|
|
||||||
(when (not= current-bar @played-bar)
|
|
||||||
(reset! played-bar current-bar)
|
|
||||||
(scroll-to-seg-track! segs playhead)))))
|
|
||||||
(do (reset! played-bar nil)
|
|
||||||
(reset! scrolled-on-load? nil)))
|
|
||||||
(r/after-render #(position-playhead! fps zoom playhead))
|
|
||||||
[:div.timeline {:class (when-not @tracks-open? "tracks-collapsed")}
|
[:div.timeline {:class (when-not @tracks-open? "tracks-collapsed")}
|
||||||
|
[playhead-fx fps zoom segs anns scene]
|
||||||
[:div.playhead.timeline-playhead {:ref (fn [n] (reset! playhead-el n))}
|
[:div.playhead.timeline-playhead {:ref (fn [n] (reset! playhead-el n))}
|
||||||
[:div.playhead-handle]]
|
[:div.playhead-handle]]
|
||||||
[:div.timeline-head
|
[:div.timeline-head
|
||||||
|
|
@ -567,7 +590,9 @@
|
||||||
:on-scroll #(let [sl (.. % -target -scrollLeft)]
|
:on-scroll #(let [sl (.. % -target -scrollLeft)]
|
||||||
(when @lanes-scroll
|
(when @lanes-scroll
|
||||||
(set! (.-scrollLeft @lanes-scroll) sl))
|
(set! (.-scrollLeft @lanes-scroll) sl))
|
||||||
(position-playhead! fps zoom playhead))}
|
;; deref at scroll time (not render) so this
|
||||||
|
;; handler needs no render-time playhead dep
|
||||||
|
(position-playhead! fps zoom @(rf/subscribe [::subs/playhead])))}
|
||||||
[:div.content.track-content {:ref (fn [n] (reset! content n))
|
[:div.content.track-content {:ref (fn [n] (reset! content n))
|
||||||
:on-mouse-down #(scrub! @content fps zoom %)
|
:on-mouse-down #(scrub! @content fps zoom %)
|
||||||
:style {:width width :height tracks-h}}
|
:style {:width width :height tracks-h}}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue