Repository navigation
perf(ml): M3 real VirtualList — mounted window drives layout/paint/hit-test - #215
Merged
Merged
Conversation
- root_ids now reads an incrementally maintained roots set instead of folding every node + probing each parent per call — a paint over a 100k-node store no longer pays O(n) just to enumerate roots. - child_count / child_id_at / child_index / children_range expose indexed slices of the child vector so virtualized containers can materialize a mounted window without building the full child list. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…paint and hit-test (Milestone 3) A virtual-list's store children stay the logical model; the layout engine materializes flex trees only for the mounted window of item indices (visible range + overscan): - Lui_layout: per-list extent model (Fenwick tree over stride = measured extent + gap) giving O(log n) index<->offset; the list is a leaf in the main mirror whose intrinsic main size is the virtual content extent; mounted row subtrees live in a side forest and get composed absolute rects in t.rects. Sync feeds a ?vport accessor (content-space visible range per list); on a memo hit only the rows that slid are remounted — scroll frames pay no mirror rebuild. - Config rides data-attrs (no schema change): data-item-extent, data-overscan, data-track-visible-range, data-scroll-target/ token/anchor. - Lui_paint: hooks.mounted_range slices the child walk to the mounted window; the paint pass no longer touches unmounted rows. - Lui_host: forwards the vport feed into every engine sync and exposes the virtual-list query surface (mounted range, virtual extent, item rects incl. unmounted indices, key/id -> index). - Ui/main: hit-test mirror and scroll caps slice to the mounted window and span the virtual extent; scroll-target resolves keys through the model so unmounted rows are reachable; visible-range reports answer via the fed viewport + binary search; the scrollable-id list is memoized on store revision so populate does not re-walk all rows per scroll frame. - Lui_elements.virtual_list gains ?overscan / ?item_extent. Tests: layout window mount/move/measured/gap/unwindowed, paint mounted-range slicing, Ui vlist scroll-request. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Harness feeds each virtual-list's content-space visible range into layout sync and wires mounted_range into paint hooks — the same composition the window driver makes. - vlist scenarios report "mounted rows"; the perf test asserts the milestone bound (10k rows mount <= 200). Results: vlist-100k scroll frame p50 = 7.3ms (paint 0.13ms), mounted rows = 38 at 10k/50k/100k alike. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Conflicts resolved keeping both sides: - lui_store: upstream intrusive child list + typed dirty masks + change journal, plus the incremental roots set; indexed child access reimplemented on the linked list (nearer-end walk). - lui_layout: sync memo keys on layout_rev (upstream) with ?vport feed preserved; the vlist keeps its own kid-id array snapshot so per-row window work stays O(1) despite the linked child list. - paint/hit-test slicing switched from a (first,last) range + store-level indexed access to engine-provided mounted_children id lists (O(window), no linked-list walk). - lui_window/main: upstream caret_ops + vlist hook and unmounted-row content_rect fallback both kept. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2 tasks done
# Conflicts: # platform/native/lui_layout/lui_layout.ml # platform/native/lui_layout/test/lui_layout_test.ml
Resolve conflicts with M4 retained paint / rect-change tracking: - host: keep both virtual-list surface and take_rect_changes - paint: adopt shared stacking walk; walk takes ?mounted so virtual lists only walk the mounted window; restore resolve_in - retained: kids lists honour mounted_children and rebuild when a list's mounted window slides (host passes vlists) - layout: vlist row rects and purged rows feed rect_changed - store: keep incrementally maintained root set - window: hit-test visit without anc_clip, over mounted children
Resolve conflicts keeping both features — virtual lists on top of retained scene fragments, frame scheduler, subtree culling, text input session and pixel snapping: - host: keep both the virtual-list engine surface and take_rect_changes; repaint hands the vlist ids to paint_retained - paint: adopt the shared stacking walk; walk takes ?mounted so virtual lists enumerate only the mounted window slice; restore resolve_in - retained: kids lists honour mounted_children and rebuild when a list's mounted window slides (vwin snapshot + ?vlists channel) - layout: vlist row rects go through the snapped scene_rect and feed rect_changed; purged rows mark rect_changed too - store: keep the incrementally maintained root set - window: hit-test visits the mounted children without anc_clip Adds a retained test that scrolls a virtual list and cross-checks the painted scene against a fresh full paint on every frame. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…-215 Same-parent merge: the remote tip already carries the feat/ml-backend merge; this keeps our resolutions on top — vlist row rects snapped through scene_rect and the retained vlist-scroll cross-check test. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
tiensonqin
added a commit
that referenced
this pull request
Oct 13, 2026
Base tree equals the already-merged M3 tip; conflicts are squash artifacts, resolved to this branch's side.
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
Plan P0-2 / Milestone 3: a
virtual-listkeeps all children in the store as the logical model, but the backend materializes flex trees only for the mounted window (visible range + overscan).rootsset makesroot_idsO(#roots) instead of a full-table fold per paint;child_id_atindexed lookup (nearer-end walk).stride = measured extent + gapgives O(log n) index↔offset; the list is a leaf in the main mirror sized by the virtual content extent; mounted row subtrees live in a side forest and get composed absolute rects.sync ~vportfeeds each list's content-space visible range; on a memo hit only rows that slid are remounted — a scroll frame pays no mirror rebuild. Config ridesdata-attrs(data-item-extent,data-overscan,data-track-visible-range,data-scroll-target/token/anchor) — no schema change.hooks.mounted_childrenslices the child walk to the mounted window; unmounted rows are never touched.vportfeed into every engine sync and exposes the virtual-list query surface (mounted range/children, virtual extent, item rects incl. unmounted indices, key/id → index).?overscan/?item_extentparams.Test plan
dune buildclean,dune runtestall green (incl. merged M1 dirty-flags + shared text layout work)Bench (post-merge, this branch)
Milestone targets met: scroll p50 < 8ms at 100k, mounted render rows < 200, paint cost independent of item count.
Base:
feat/ml-backend(first milestone in the vlist stack; includes merge of feat/ml-backend @ cc6ac2a + later).Generated with Devin