Repository navigation
perf(ml): P2-2 spatial index for hit-test and paint culling - #227
Merged
Merged
Conversation
New lui_spatial library: a snapshot index over resolved node rects that both the paint pass's viewport cull and hit-testing prune with. Per node it records the shown subtree's bound [b], its clip-escaping part [eb] (promoted popups), and a zero-area flag [z]; parents with >= 32 children get a bound-top-sorted array with prefix-max bottoms so a query touches O(log n + candidates) siblings instead of every child. The env is caller-supplied so the module stays free of Lui_paint dependencies. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Lui_paint.walk takes an optional (index, query) pair: roots come from the index's snapshot (avoiding the O(n) root_ids fold per walk), wide parents enumerate candidates through the interval arrays, and small parents fall back to a per-child keep test that preserves the walk's exact clip/popup/z-order semantics. Lui_paint.paint gains ?spatial supplying a Region query over the viewport; without it the walk is byte-for-byte the old path. spatial_env publishes the paint-semantics env index builders share. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Lui_query.hit_path/hit take ?spatial and serve point queries through it — same walk, same path order. - Lui_host gains an invalidation counter (bumped by resize, set_hidden, set_ctx_anchor, invalidate_layout) plus a spatial provider thunk consulted inside repaint. - Lui_window.Assembly builds the index lazily over the populate-time hit mirror, stamps each mirror with a geometry key (store revision, host invalidations, scroll epoch, live context anchor, scale, viewport) and hands the paint pass an index only when the key proves the mirror still matches the frame being resolved; hit paths get the index whenever it matches the current mirror. Stale or absent keys fall back to the unindexed walk, so the index can never serve a wrong answer. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- lui_paint tests: indexed-vs-unindexed paint produces identical op streams (z-order, clip, promoted popup, contents, hidden, spacer), a 300-row column visits <= 12 nodes under a 100px viewport vs 302 unindexed, and zero-area children stay reachable. - lui_input tests: a point-grid sweep asserts hit_path indexed == unindexed on a tree covering clip escape, display:contents, hidden subtrees, spacers and z-overlap; plus promoted-popover and wide-list hits. - perf scenario hit-*k: hit_path p50 unindexed vs indexed, indexed static-frame paint, and index build cost. At 100k rows: hit_path 320ms -> ~0.002ms; indexed static paint 0.06ms vs ~120ms; index build ~250ms once per geometry epoch. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Picks up #224 (retained scene fragments), #217 (dead-clip ghost walk), #213, #209, #223. Resolution: - walk keeps both features: the ghost pass takes clip so the spatial keep test can prune its popup search, and both body and ghost enumerate children through the shared candidates helper. - paint stays ?spatial — the retained fragment pass is the production path and owns its own cull, so the host's spatial provider is dropped; the index still serves hit-testing via Assembly.handle and Lui_paint.paint callers. - Lui_host keeps only the invalidation counter the hit-index key needs. - Restores Lui_paint.resolve_in, lost from #224's squash merge (same fix as 21b58d7 on perf/ml-p2-3-scene-batch-cache). Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- host: keep inv_count and vport - paint walk: ?mounted + ?spatial; mounted vlist slices bypass the spatial index, other parents use candidates - window: keep spatial key/index and vport feed + drift actions + vlist hook; return spatial_hit - perf/scenarios: mount_vlist now takes unit
body: adopt frame_of; keep mounted/spatial child dispatch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
P2-2 from the ml-backend plan: a spatial index shared by the native
paint pass's viewport cull and hit-testing.
Lui_spatial(newplatform/native/lui_spatiallibrary): asnapshot index over resolved rects. Per node it records the shown
subtree's bound
b, its clip-escaping parteb(promoted popups,which paint and hit-test outside ancestor clips), and a zero-area
flag
z(degenerate nodes still run the emission path). Parentswith ≥32 children get a bound-top-sorted child array with
prefix-max bottoms — the interval index the plan calls for.
Lui_paint.walk/painttake an optional(index, query).Region(viewport) prunes subtrees that cannot intersect;Point(hit-test) prunes subtrees that cannot contain the point.Roots come from the index snapshot, avoiding the O(n)
root_idsfold per walk. Pruning is a strict filter on theunindexed visit set — same order, same clip contexts, and the
unindexed path is unchanged when no index is supplied.
Lui_query.hit_path/hittake?spatial.Lui_hosttracks a geometry invalidation counter and accepts aspatial provider consulted inside
repaint.Lui_window.Assemblylazily builds the index over thepopulate-time hit mirror and stamps each mirror with a geometry key
(store revision, host invalidations, scroll epoch, live context
anchor, scale, viewport). Paint gets the index only when the key
proves the mirror still matches the frame; hit paths get it
whenever it matches the current mirror. Any staleness falls back
to the unindexed walk — the index can never answer wrong.
Benchmarks (
platform/native/perf, 100k-row virtual list)Walk visits for a point hit at 100k rows: 3 nodes vs ~100k.
Test plan
lui_painttests: indexed == unindexed op streams over a tree withscroll clip, promoted popup, hidden subtree, display:contents,
spacer and z-order; visit bound on a 300-row column; zero-area
children stay reachable.
lui_inputtests:hit_pathindexed == unindexed across a pointgrid over clip/popup/contents/hidden/z-overlap cases; promoted
popover escape; wide-list row hits.
dune build @runtest).Generated with Devin