Skip to content

perf(core,viewer): stop rebuilding wall geometry every frame - #556

Open
toycenterboss-bot wants to merge 2 commits into
pascalorg:mainfrom
toycenterboss-bot:pr/wall-geometry-rebuild
Open

perf(core,viewer): stop rebuilding wall geometry every frame#556
toycenterboss-bot wants to merge 2 commits into
pascalorg:mainfrom
toycenterboss-bot:pr/wall-geometry-rebuild

Conversation

@toycenterboss-bot

@toycenterboss-bot toycenterboss-bot commented Jul 28, 2026

Copy link
Copy Markdown

What does this PR do?

Wall geometry, mitre joints and slab support were recomputed inside the render
loop, so every frame paid for work whose inputs had not changed. On an imported
floor with 1089 walls the first level build took 59 s and the editor was
unusable until it finished. This PR caches the derived results and invalidates
them only when the walls, the level or the openings actually change: mitre
solving keeps a per-level result keyed by the exact wall fields the miters
depend on, slab support holds its effective slab/wall records by identity
instead of re-deriving them once per wall, and the rendered-polygon lookup
memoises against those same arrays. The same level now builds in 0.01 s.

No behaviour changes — the geometry produced is identical, it is simply produced
once instead of once per frame.

Related to #492 — the per-frame recompute is one of the paths that makes wall
interaction stutter on large scenes. Measurements and method are in
a comment on that issue.

Scene Wall nodes First level build
imported floor plan 1089 59 s → 0.01 s

The second commit clears the new level-miter cache when WallSystem unmounts —
it lives at module scope, so without that a remount or a second project in the
same tab keeps the previous level's wall arrays reachable. Editor teardown
already resets the other shared singletons; this makes the cache follow the same
rule, and the rule is now written down in wiki/architecture/systems.md.

How to test

  1. bun dev and open a scene with a few hundred walls (any floor plan import will do).
  2. Watch the time between opening the scene and the first painted frame — it should
    be immediate rather than a multi-second freeze.
  3. Orbit the camera and confirm wall joints, openings and slab-supported wall tops
    look exactly as they did before.
  4. Move a wall with the move tool and confirm its neighbours re-mitre immediately —
    the cache must invalidate, not go stale.
  5. Navigate out of the editor and back in, then open a different project in the same
    tab — the miter cache must not carry entries from the previous level.
  6. bun testwall-mitering, slab-support and spatial-grid suites cover the
    invalidation paths.

Screenshots / screen recording

N/A — no visual change. The output geometry is identical; only the time to produce
it changes. Happy to add a before/after screen recording of the load freeze if that
would help review.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

Three separate hot paths were doing full-scene work per frame on a
1081-wall floor, adding up to 5.6 s of main-thread stalls per six
scroll ticks:

- findJunctions was O(J x N) over every wall end; a spatial-grid
  prefilter narrows it to real neighbours (level assembly 59.3 s ->
  0.01 s, calculateLevelMiters 436 ms -> 10.8 ms, identical output)
- miter data is now cached per level and keyed on the exact wall
  inputs, so draining the dirty queue no longer recomputes it
- getLevelElevations was called inside the per-wall loop; memoised
- slab support and the wall appearance key were both unstable, which
  retriggered opening cutouts on every frame for no reason

Measured on scene e5f5822f8837, six wheel ticks in split view:
long tasks 5652 ms -> 4322 ms -> 0 ms.

Co-Authored-By: Claude <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6ac6ee3. Configure here.

Comment thread packages/viewer/src/systems/wall/wall-system.tsx
The cache lives at module scope, so it outlived the mount it was created
for: every level ever visited kept its wall array reachable, and a remount
or a second project in the same tab simply added more. Editor teardown
already resets the other shared singletons; the cache now does the same
from the system's unmount effect.

Also records the rule in the systems wiki, since the same trap is waiting
for the next module-level memo.

Co-Authored-By: Claude <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.

1 participant