Skip to content

perf(parsers): speed up deleting animated groups - #7463

Merged
miguel-heygen merged 1 commit into
heygen-com:mainfrom
AksharP5:batch-tween-delete
Nov 9, 2026
Merged

miguel-heygen merged 1 commit into
heygen-com:mainfrom
AksharP5:batch-tween-delete

Conversation

@AksharP5

@AksharP5 AksharP5 commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

What

Deleting a group with many animated children can tie up Studio's server for seconds. Batch the GSAP cleanup so a normal deletion parses each script once.

Why

The server helper parses a script twice per removed tween, then once more to discover that nothing remains. A 100-tween group takes 201 parses. The new normal path takes one and produces the same serialized HTML.

Five alternating measured rounds after two warmups, Linux arm64 Neoverse-N1, Node v26.13.0. Values are median [minimum, maximum] in milliseconds. This measures the complete synchronous source-deletion helper, including HTML normalization, deletion, CSS cleanup and serialization, using an esbuild harness with third-party dependencies external. Both versions use the same inputs and dependencies; every output is compared byte for byte. Explicit GC happens outside each timed call.

Removed tweens GSAP parses Base CPU ms This PR CPU ms Base elapsed ms This PR elapsed ms
1 3 → 1 8.63 [7.14, 12.26] 6.87 [3.76, 7.45] 68.78 [17.08, 129.69] 37.90 [3.05, 45.14]
10 21 → 1 32.73 [30.70, 33.21] 8.93 [7.74, 10.88] 143.32 [75.03, 315.25] 36.83 [6.97, 73.69]
100 201 → 1 760.16 [716.64, 853.19] 38.75 [37.19, 51.32] 3022.27 [1819.83, 3594.33] 78.33 [62.25, 112.43]
200 401 → 1 2187.55 [2073.92, 2286.94] 69.77 [52.98, 79.79] 9199.64 [7637.32, 13264.22] 281.67 [167.48, 483.99]

The shared machine was busy, so elapsed-time spread is wide. The 100- and 200-tween CPU medians fall by about 95% and 97%. This is server work, not a measured browser or HTTP latency result. Variant-specific peak memory was not measured; the change adds per-call selection sets and no persistent cache.

Related work

Fixes #7460

#3655 established descendant cleanup. #4932 batches HTTP/HTML work but retains the per-tween parse loop. #4937 optimizes timing edits. Their implementations and review discussions do not cover this remaining parse cost.

How

Select matching AST calls from the original script and remove their spans together. Delete a fully selected statement as a whole; preserve unselected chain links and the existing shared-selector protection.

If removing a callback erases the parser's chosen timeline declaration, remove one match and rediscover the timeline before batching. Astra HIGH review identified this case; it is covered for declarations and both assignment forms. I corrected the finding and reran its reproduction and differential controls. The correction has not received an independent second review.

Design: no behavior or default change; the single-animation writer keeps its existing contract.

Test plan

fails without the fix: parses a script once when deleting a subtree with many tweens in packages/parsers/src/htmlParser.test.ts.

  • Matching output across 64 helper controls and 4,035 differential writer controls.
  • Public built-helper reproduction in the linked issue: 201 → 1 parses for 100 tweens, identical output hash.
  • Three timeline-rediscovery regressions fail before the correction and pass after it.
  • 1,432 parser tests and 185 source-mutation/route tests; all six changed regressions also passed in the final focused run.
  • Workspace build, final parser and refreshed Engine builds, typechecks, lint/format, Fallow, comment checks and normal hooks.
  • Documentation reviewed. Existing deletion semantics remain unchanged; no command or flag changes.

Baseline for the measurements was 4b3f70fb1b202a1422a199e8b7440a388d0b5a20; the branch is refreshed to 7bf19ab5f9d1690d596334822f251dece3921e1a. The intervening main commit adds Engine nightly tests and changes none of the deletion-path files. No browser, native Windows or macOS validation was performed.

Verified signed head: 0192fbf3f89c06d7012cd58fb78dc536c20b1afe. The fetched complete tree matches the tested index.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 0192fbf. The deletion cascade now runs one batched removal per script instead of a parse-and-remove loop for each tween.

Strengths

  • gsapWriterAcorn.ts removeAnimationsForSelectors: the batch is equivalent to the old loop, because removeAnimationFromScript was only removeCallFromMagicString on a single located call. I walked the shapes the old loop handled one at a time, and each gives the same result:
    • a standalone tl.to/gsap.set statement is removed whole;
    • a fully selected chain (tl.to(a).to(a)) is removed whole through fullyRemovedStatement, since the root walk reaches the tl identifier;
    • a mixed chain splices only the selected links, and a surviving addLabel or unselected link is kept (pinned by the new it.each);
    • a chain rooted in gsap.timeline() still splices, because its root is an unselected CallExpression;
    • a selected call inside another selected call's callback gives two nested MagicString ranges, which is the same result as removing the outer call first.
  • The fallback is narrow and correct. The batch can't be trusted only when a removed call encloses the parser's chosen timeline declaration, because ref and the located set change after that. The code then removes one call and reparses, exactly as before. The declarator form and both assignment forms are pinned.
  • htmlParser.test.ts: spying on Parser.parse pins one parse per deletion, so a regression back to a per-tween loop fails the test.

Nits (none blocking)

  • removeAnimationsForSelectors returns current when removeAnimationFromScript(first.id) makes no change, so any other selected calls stay in the script. The old loop broke out in the same case, so this isn't a regression. A one-line comment would save the next reader from wondering.

CI: 65 checks passed, 17 skipped, none failed at 0192fbf, including Test, the Windows test shards and lint.

Verdict: APPROVE
Reasoning: the batched removal matches the per-tween loop on every removal shape it handles, the one case where the parse goes stale falls back to the old path, and a test pins the single parse.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 9, 2026
Merged via the queue into heygen-com:main with commit 6f90980 Nov 9, 2026
82 checks passed
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 9, 2026
…heygen-com#7456 and the heygen-com#7439 docs

heygen-com#7459: Studio renders take the same one-render-slot lock as the CLI and say
they are waiting; Cancel ends the wait.
heygen-com#7463: deleting an animated group parses its script once instead of once per
tween.
heygen-com#7454: seam-gate awaits the async player seek (both skill mirrors).
heygen-com#7456: the engine browser test project gets 30 s per test.
heygen-com#7439: current ChatGPT and Claude setup steps in the MCP guide.

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 9, 2026
…ve more; advance watermark

Twelve commits, PRs 7453-7468, issues 7457-7465 and eleven pin moves are
recorded with their evidence in the continued ledger, DECISIONS and UPSTREAM.
Adopted: heygen-com#7338, heygen-com#7459, heygen-com#7463, heygen-com#7454, heygen-com#7456 and the heygen-com#7439 docs; heygen-com#6732 was
already ported. Deferred: heygen-com#7455, heygen-com#7437. Not applicable: heygen-com#7453, heygen-com#7269, heygen-com#7464.
The watermark moves to 9e346f9, PR heygen-com#7468, issue heygen-com#7465; four new PRs are
pinned and the eleven moved pins re-pinned at their current heads. FORK.md
records the fork-only differences in the heygen-com#7338 port; the roadmap tracks the
standalone scripts that keep a bare mkdtemp and issue heygen-com#7458.

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deleting animated groups repeatedly reparses GSAP scripts

2 participants