Repository navigation
perf(core): text edits stop rebuilding the preview's caches every frame - #6276
Conversation
Edit accuracy: accurate 271 (base branch 271), smooth 124 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (1)
|
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 0 nits · head d5928892
Scope
Read the four files. Checked every cache keyed to compositionTimingRevision against the new filter: the three queryForTransport selectors (the audio group tag, [data-start], audio[data-start]), the root-composition lookup ([data-composition-id]), the standalone host lookup, the media clip index and the GSAP structure key. Each reads only elements the filter's selector matches, or those elements' ancestry. Changing a timed element's ancestry means moving that element, and the move shows up as a removed and added node, so it still counts. The attribute filter is the same list as before. CI is green on this head.
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 0 nits · d5928892 → 1c068965
Delta
The timing filter moves to mutationRecords.ts with the same selector. The color-grading watcher still refreshes on a data-hf-color-grading attribute edit, or when an added or removed node is or holds graded media. It also refreshes when a removed node holds something already tracked, so media graded through the API is caught too. Media hidden at refresh is still picked up on redraw through adoptShownMedia, so dropping the text-triggered refreshes does not leave a newly shown video ungraded. CI is green on this head.
What
Changing text in a preview no longer makes the runtime throw away and rebuild its timing caches, or rescan the page for color-graded media. Only DOM changes that can move a clip's timing, or add or remove graded media, do.
Why
The runtime watches the document for anything that can change clip windows, durations or composition hosts, and bumps a timing revision when it sees one. Every cache keyed to that revision is rebuilt on the next read: the media clip index, the per-selector query cache, the root-composition cache and the host lookup. The watch listened to child-list changes anywhere in the document (
childList+subtreeon the root), and any record at all bumped the revision.The color-grading runtime has its own observer on
bodywith the same shape: any child-list record re-ranrefresh(), aquerySelectorAllfor graded sources plus a pass over every tracked element.A film whose scenes change text every frame (a typewriter caption setting
textContent) therefore rebuilt all of those caches on every frame of playback. On a 50 s 4K film with 7 scenes, 112 short video clips and animated captions, the watch received 408,206 child-list records during one play-through, 245 per frame: 405k text-node swaps inside caption elements and about 1.3 inserted<span>s per frame, none of which carry timing.Related work
Refs #6189. Independent of it: none of the lines here are inside #6189's changes to
init.ts, and the patch applies cleanly on top of it.How
runtime/mutationRecords.tsadds one predicate builder,createRecordFilter(selector, holds?): attribute records count as before (both observers already filter them), and a child-list record counts only when a node it adds or removes matches or containsselector, orholdssays it contains something the caller tracks. Text and comment nodes never count.createTimingRecordFilter(timingAttributes)builds the timing selector: an element timing is read from is any element carrying one of the observed timing attributes (which includeid, since a start can name another element, andsrc),video,audio,source,script(a script can register a timeline) or an audio group. Text and comment nodes never count.init.tsbuilds that predicate from the same attribute list the observer filters on (COMPOSITION_TIMING_ATTRIBUTES), so the observer and the predicate cannot disagree about what a timing input is. The observer callback and everytakeRecords()site (queryForTransport,readCompositionTimingRevision,withoutOwnTimingRecords,withoutOwnCompositionRecords) now go through it.colorGrading.tsbuilds its filter fromCOLOR_GRADING_SOURCE_SELECTOR, withholdschecking the elements it already tracks, so a removed wrapper still drops media graded throughsetGradingwithout the attribute.Measured
Studio-like playback of that film in
<hyperframes-player>at 1x for the whole 50 s, headless Chrome 152 on an Apple silicon Mac with GPU compositing, GPU rasterization and hardware video decode on. Base ismainwith #6189 merged on top (the film needs #6189 to play at all); "after" adds this change. Three alternating runs each, trace on; the host was shared (load 7 to 15).With the timing filter alone, one profiled run each with an unminified runtime put the runtime's own time over the 50 s (outside the film's scene code) at 8.2 s before and 6.1 s after (5.0 to 3.7 ms per frame); the color-grading
refresh()was 1.3 s of what remained.In the profile, the four functions behind the rebuilds (
queryForTransport,refreshRuntimeMediaCache,mediaStartInput,isCompositionHost, about 2.2 s together) drop out of the top of the list. Frame rate barely moves on this film because its own per-frame scene script is the larger cost; that is outside this repo.Test plan
Unit tests added/updated
Manual testing performed
Documentation updated (if applicable)
Comments follow CONTRIBUTING.md "Comments": they say why, not what, and a bug fix says what the code must do and how to reproduce the bug
timingMutations.test.ts, on realMutationObserverrecords: text replaced inside an untimed element and an inserted untimed element do not count; an inserted clip, a subtree holding a<video>, a removed clip, an inserted element with anid, a timing attribute edit and an inserted<script>do.init.timingMutations.test.ts, through the runtime: after a caption's text changes, or a plain<span>is inserted into it, the next seek and frame do not rebuild the media clip index; adding a clip, removing one or changing a clip'sdata-startdoes. Fails onmain(expected 1 to be +0on the text case).colorGrading.test.ts: a text edit and an inserted plain<span>do not rescan for graded media (fails with the observer refreshing on every record); graded media inserted inside a wrapper is still adopted and dropped when the wrapper is removed; media graded throughsetGradingwithout the attribute is dropped when its wrapper is removed (fails withoutholds).Each part checked by breaking it: counting every record fails both text cases and the runtime text test; dropping the descendant check fails the subtree case; ignoring removed nodes fails the removed-clip case and the runtime rebuild test.
The new and changed test files pass 3 runs in a row. Full
packages/core/src/runtimesuite: 98 files, 2,422 tests, exit 0. Pre-commit lint, format, typecheck and fallow pass;check-comment-citationsandcomment-ratchetpass.