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,28 +462,15 @@
|
||||||
(.addEventListener js/document "mousemove" move)
|
(.addEventListener js/document "mousemove" move)
|
||||||
(.addEventListener js/document "mouseup" up)))
|
(.addEventListener js/document "mouseup" up)))
|
||||||
|
|
||||||
(defn timeline []
|
(defn- playhead-fx
|
||||||
(let [content (atom nil)
|
"Renders nothing. Isolates the playhead-driven side effects — positioning the
|
||||||
lanes-scroll (atom nil)
|
(imperative) playhead line and the follow-scroll during playback — so a moving
|
||||||
tracks-open? (r/atom true)]
|
playhead re-renders only this empty component, not the whole timeline DOM (all
|
||||||
(fn []
|
clips + per-clip clip-label) every animation frame. The timeline's hiccup never
|
||||||
(let [scene @(rf/subscribe [::subs/scene])
|
depended on the playhead; only these effects did."
|
||||||
fps @(rf/subscribe [::subs/fps])
|
[fps zoom segs anns scene]
|
||||||
zoom @(rf/subscribe [::subs/zoom])
|
(let [playhead @(rf/subscribe [::subs/playhead])
|
||||||
row-h @(rf/subscribe [::subs/row-h])
|
playing? @(rf/subscribe [::subs/playing?])]
|
||||||
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))]
|
|
||||||
(if playing?
|
(if playing?
|
||||||
(r/after-render
|
(r/after-render
|
||||||
(fn []
|
(fn []
|
||||||
|
|
@ -479,10 +479,10 @@
|
||||||
(when (and (nil? @scrolled-on-load?) (> playhead 0) (seq segs))
|
(when (and (nil? @scrolled-on-load?) (> playhead 0) (seq segs))
|
||||||
(reset! scrolled-on-load? true)
|
(reset! scrolled-on-load? true)
|
||||||
(scroll-to-seg-track! segs playhead))
|
(scroll-to-seg-track! segs playhead))
|
||||||
;; clip-following: only while the playhead is actually inside a bar of
|
;; clip-following: only while the playhead is inside a bar of an annotation
|
||||||
;; an annotation that opts in (:meta :scroll-to) and is not hidden —
|
;; that opts in (:meta :scroll-to) and is not hidden — scroll each clip into
|
||||||
;; scroll each of its clips into view as the playhead crosses onto a new bar (discontinuity).
|
;; view as the playhead crosses onto a new bar. Track [ann-id bar-idx] to
|
||||||
;; Track [ann-id bar-idx] to avoid scrolling within the same bar.
|
;; avoid scrolling within the same bar.
|
||||||
(when-let [current-bar (some (fn [a]
|
(when-let [current-bar (some (fn [a]
|
||||||
(when (and (not (get-in (get-in scene [:groups (:id a)]) [:meta :hidden]))
|
(when (and (not (get-in (get-in scene [:groups (:id a)]) [:meta :hidden]))
|
||||||
(ann-scroll-to? (get-in scene [:groups (:id a)]))
|
(ann-scroll-to? (get-in scene [:groups (:id a)]))
|
||||||
|
|
@ -497,7 +497,30 @@
|
||||||
(do (reset! played-bar nil)
|
(do (reset! played-bar nil)
|
||||||
(reset! scrolled-on-load? nil)))
|
(reset! scrolled-on-load? nil)))
|
||||||
(r/after-render #(position-playhead! fps zoom playhead))
|
(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")}
|
[: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