perf: tighten viewer runtime ownership - #675
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ae68c9d. Configure here.
|
Maintainer follow-up pushed in The indexed cabinet-flame loop retained traversal-era Validated:
The remaining merge gate is the maintainer's local 2D/3D and cabinet-animation smoke. |
d455adf to
c76505d
Compare
|
Final maintainer gate is complete after rebasing onto #608 and #671. The overlap composes the two contracts rather than choosing one: FrameLimiter receives maxFps and renderPaused, retains the monotonic synthetic clock across limiter restarts, and keeps the mounted renderer warm while hidden 2D mode stops scene advancement. Validation on this exact head: Biome checked 1,670 files; 10/10 type tasks passed; 8/8 build tasks passed; 1,089 focused tests passed with one existing skip; the 1,089-wall browser fixture reached DIRTY 0 and six meshes, and switched 3D/2D/3D without runtime errors. The final diff remains entirely in the public editor packages and contains no hosted-community, auth, environment, project, or private-repository material. Remote quality, CLI smoke, and Bugbot are green. |
Lifecycle, disposal, animation indexing, and frame-loop ownership improvements, validated after composing the wall batching and configurable frame-cap contributions.

What does this PR do?
Tightens viewer lifecycle and per-frame work without changing the default animation schedule:
Maintainer follow-ups:
maxFps#671, preserving the public maxFps API and its monotonic frame clock while composing renderPaused into the same limiterValidation
bun run check: 1,670 files cleanbun run check-types: 10/10 tasksbun run build: 8/8 tasksScreenshots / screen recording
N/A — lifecycle, disposal, frame-loop, and animation-internal changes; no intentional UI change.
Checklist
Full demand rendering and shadow dirty-gating remain measurement-gated by the active runtime-performance plan.
Note
Medium Risk
Changes affect the global render loop, geometry disposal, and per-frame cabinet animation; behavior is mostly performance and leak fixes, but incorrect pause/disposal could cause stale visuals or rare GPU resource issues.
Overview
Viewer runtime gains a host-controlled
renderPausedprop wired throughFrameLimiterso the WebGL context stays mounted but scene frames stop advancing. The editor sets this when the 3D pane is hidden and loading has finished (!show3d && !showLoader), cutting per-frame work in 2D-only mode.Resource lifecycle introduces
disposeObject3DResourcesto walk a subtree, dedupe geometry/material disposal, and skip materials tagged__pascalCachedMaterial. Geometry rebuilds use it instead of inline disposal; MEP preview ghosts dispose replaced materials when ghostifying and tear down the whole ghost on unmount.Cabinet flame animation pre-collects flame objects per cabinet (rebuilt when the root or child list changes) instead of traversing every cabinet subtree each frame.
animateCabinetFlamesnow iterates that list and usescontinueinstead ofreturn, so a throttled flame-jet skip no longer stops pulse/material animation for later flames (covered by new tests).Perf overlay frame timing reads the
useFramedeltaargument instead of callingclock.getDelta(), avoiding extra mutation of the shared clock.Reviewed by Cursor Bugbot for commit c76505d. Bugbot is set up for automated code reviews on this repo. Configure here.