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))
|
||||
(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
|
||||
(let [url (project-url id stack playhead)]
|
||||
(when (not= url @last-replaced)
|
||||
|
|
@ -77,6 +77,24 @@
|
|||
;; remember where we were so an installed PWA can come back here
|
||||
(.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?
|
||||
"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)))}]
|
||||
[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
|
||||
"Track-local clip label for the content segment whose :mark is `mid`."
|
||||
[scene segs mid]
|
||||
(or (some (fn [[t tsegs]]
|
||||
(some (fn [[i {m :mark}]]
|
||||
(when (= m mid)
|
||||
(str (get-in scene [:tracks t :name] (name t))
|
||||
" (" (inc i) ")")))
|
||||
(map-indexed vector (sort-by (comp first :local) tsegs))))
|
||||
(group-by :track segs))
|
||||
"clip"))
|
||||
(let [c @label-cache
|
||||
m (if (identical? segs (:segs c))
|
||||
(:map c)
|
||||
(:map (reset! label-cache {:segs segs :map (build-label-map scene segs)})))]
|
||||
(get m mid "clip")))
|
||||
|
||||
(defn- clip-at
|
||||
"Describe local frame `lf` the way the annotation editor does: the underlying
|
||||
|
|
@ -449,28 +462,15 @@
|
|||
(.addEventListener js/document "mousemove" move)
|
||||
(.addEventListener js/document "mouseup" up)))
|
||||
|
||||
(defn timeline []
|
||||
(let [content (atom nil)
|
||||
lanes-scroll (atom nil)
|
||||
tracks-open? (r/atom true)]
|
||||
(fn []
|
||||
(let [scene @(rf/subscribe [::subs/scene])
|
||||
fps @(rf/subscribe [::subs/fps])
|
||||
zoom @(rf/subscribe [::subs/zoom])
|
||||
row-h @(rf/subscribe [::subs/row-h])
|
||||
segs @(rf/subscribe [::subs/segments])
|
||||
tracks @(rf/subscribe [::subs/tracks])
|
||||
anns @(rf/subscribe [::subs/annotations])
|
||||
thumbs @(rf/subscribe [::subs/thumbnails])
|
||||
playhead @(rf/subscribe [::subs/playhead])
|
||||
playing? @(rf/subscribe [::subs/playing?])
|
||||
authoring? (some? @(rf/subscribe [::subs/draft-group]))
|
||||
linking @(rf/subscribe [::subs/linking])
|
||||
len @(rf/subscribe [::subs/length])
|
||||
width (px len fps zoom)
|
||||
lane-h (max 18 (* 18 (count anns)))
|
||||
tracks-h (* row-h (count tracks))
|
||||
track-y (into {} (map-indexed (fn [i t] [(:id t) i]) tracks))]
|
||||
(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 []
|
||||
|
|
@ -479,10 +479,10 @@
|
|||
(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.
|
||||
;; 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)]))
|
||||
|
|
@ -497,7 +497,30 @@
|
|||
(do (reset! played-bar nil)
|
||||
(reset! scrolled-on-load? nil)))
|
||||
(r/after-render #(position-playhead! fps zoom playhead))
|
||||
nil))
|
||||
|
||||
(defn timeline []
|
||||
(let [content (atom nil)
|
||||
lanes-scroll (atom nil)
|
||||
tracks-open? (r/atom true)]
|
||||
(fn []
|
||||
(let [scene @(rf/subscribe [::subs/scene])
|
||||
fps @(rf/subscribe [::subs/fps])
|
||||
zoom @(rf/subscribe [::subs/zoom])
|
||||
row-h @(rf/subscribe [::subs/row-h])
|
||||
segs @(rf/subscribe [::subs/segments])
|
||||
tracks @(rf/subscribe [::subs/tracks])
|
||||
anns @(rf/subscribe [::subs/annotations])
|
||||
thumbs @(rf/subscribe [::subs/thumbnails])
|
||||
authoring? (some? @(rf/subscribe [::subs/draft-group]))
|
||||
linking @(rf/subscribe [::subs/linking])
|
||||
len @(rf/subscribe [::subs/length])
|
||||
width (px len fps zoom)
|
||||
lane-h (max 18 (* 18 (count anns)))
|
||||
tracks-h (* row-h (count tracks))
|
||||
track-y (into {} (map-indexed (fn [i t] [(:id t) i]) tracks))]
|
||||
[: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-handle]]
|
||||
[:div.timeline-head
|
||||
|
|
@ -567,7 +590,9 @@
|
|||
:on-scroll #(let [sl (.. % -target -scrollLeft)]
|
||||
(when @lanes-scroll
|
||||
(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))
|
||||
:on-mouse-down #(scrub! @content fps zoom %)
|
||||
:style {:width width :height tracks-h}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue