perf(core): guard bulk slab changes in the spatial-grid subscriber - #833
Conversation
A scene load, reload, paste or import changes tens to thousands of slabs in one store write. The spatial-grid subscriber ran markNodesOverlappingSlab for every added, removed or reshaped slab — two full node scans each — so the cost was O(slabs × nodes). When a write changes >= 32 slabs, dirty every node any slab could affect once (walls, ceilings, stairs, level-hosted floor-placed kinds — a superset of the per-slab marks) and skip the per-slab scans. setScene marks every node dirty after set() regardless, so load-time behaviour is unchanged; single-slab edits keep the targeted rules. Indicative measurement: an equivalent patch applied to the published @pascal-app/core 1.0.0-beta.5 (which predates pascalorg#805) on a 4,645-slab scene (7,143 nodes) in Chrome took the main-thread block on load from ~6.4 s to 0.4–0.9 s (real-Chrome CPU profile had 5.9 s inclusive in this scan). The mechanism here is identical; the numbers were not re-taken on this source. Tests: threshold edge (31 plates leave a non-overlapping wall clean, 32 mark it), 3,000-slab write stays linear, later single-slab edits stay targeted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
I hit an error while handling your request (Model unavailable on AI Gateway free tier: Free tier users do not have access to this model. Upgrade to paid credits at https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dtop-up for unrestricted…). Please try again, rephrase, or reach out if it keeps failing. Error id: 5cafdd00-710c-4d67-8059-613f87865866 |
Aymericr
left a comment
There was a problem hiding this comment.
The algorithmic guard is sound: the bulk path marks a true superset of every per-slab consumer, while single-slab writes retain the precise overlap/deck/covering rules. I also merged the head onto current main locally and verified the focused spatial-grid suite (24 tests / 64 assertions), the core build, and Biome. The 3,000-slab regression completed in 182 ms here.
What does this PR do?
The spatial-grid subscriber runs
markNodesOverlappingSlabfor every slab a store write adds, removes or reshapes: two full node scans per slab, so a scene load, reload, paste or import is O(slabs × nodes). On a 4,645-slab scene (7,143 nodes) that scan alone blocked the main thread for ~5 s on every load — a real-Chrome CPU profile showed 5.9 s inclusive inside this scan out of a 7 ssetSceneblock, enough for Chrome's "Page Unresponsive" dialog on a loaded machine.When one write changes 32 or more slabs, this dirties every node any slab could affect once — walls, ceilings, stairs and level-hosted floor-placed kinds, a superset of the per-slab marks — and skips the per-slab scans.
setScenemarks every node dirty right afterset()regardless, so load-time behaviour is unchanged; single-slab edits keep the targeted rules (markSlabChangeDependents, covering-below, deck stairs).Measurement caveat: the numbers were taken with an equivalent patch applied to the published
@pascal-app/core@1.0.0-beta.5(before #805), not re-taken on this source. Same mechanism. Max main-thread freeze on that scene went 6.4 s → 0.4–0.9 s; time to a ready model 31.7 s → ~21 s. Happy to re-run on a fixture you prefer.How to test
bun test --cwd packages/core src/hooks/spatial-grid/spatial-grid-sync.test.ts— newbulk slab-change guardblock: 31 added interior plates leave a non-overlapping wall clean (per-slab path); 32 mark it (superset path); a 3,000-slab write stays linear (well under 1.5 s, versus seconds unguarded); later single-slab edits stay targeted.?perfand compare thesetScenespan before and after.tsc --buildandbun checkclean.Screenshots / screen recording
No visual change. Numbers above; a before/after CPU profile of the scan is available on request.
Checklist
bun dev— verified through the package test suites,tsc --buildandbun checkinstead; no editor session was run for this changebun checkto verify)mainbranch🤖 Generated with Claude Code
Note
Medium Risk
Changes when and which nodes are marked dirty in the spatial-grid subscriber; bulk mode trades precise overlap scans for a fixed superset sweep, so correctness depends on that superset matching real slab dependents outside full-scene hydration.
Overview
Fixes main-thread freezes on scene load, paste, and import when thousands of slabs change in one store write. The spatial-grid subscriber used to run two full node scans per added, removed, or reshaped slab; this adds a bulk guard at 32+ slab changes that calls
markAllSlabDependentsonce (walls, ceilings, stairs, level-hosted floor-placed nodes) and skips the per-slab overlap andmarkSlabChangeDependentspaths.Exports
BULK_SLAB_CHANGE_THRESHOLD,countBulkSlabChanges, andmarkAllSlabDependents. Dirty marks on bulk writes can be a superset of the old per-slab marks (e.g. a non-overlapping wall may be marked at the threshold); single-slab edits still use the targeted rules. Tests cover counting, threshold behavior, a 3,000-slab performance bound, and that later one-slab edits stay precise.Reviewed by Cursor Bugbot for commit 7997a2f. Bugbot is set up for automated code reviews on this repo. Configure here.