Repository navigation
perf(ml): M5 variable-height VirtualList — measured extents + anchor drift - #216
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>
…ck positions, anchor drift (Milestone 5) - Per-row measured main extents feed the Fenwick tree already in the vlist model (stride = extent + gap): index→offset and offset→index stay O(log n) as real row heights replace the estimate; the leaf's virtual extent repatches and relayouts when measurements move the total. - Anchor correction: when freshly mounted overscan rows above the viewport top measure off-estimate, the model records the anchor item's prefix delta as drift; the driver consumes it each populate (virtual_take_drift → Ui.scroll_set) so the top row keeps its screen position instead of visibly jumping. populate now returns the correction's actions for dispatch. - Memo-hit remount runs a second mount pass after the extent patch so rows the post-measure window now reaches are covered the same frame. - Perf: vlist-var-100k scenario (every 7th row 60px vs 28px estimate) exercises the measurement path; harness applies drift like the driver. Tests: vlist mounted_children slice, anchor drift (+160 correction on a mid-list jump, consume-on-read), corrected item positions. Bench: vlist-var-100k scroll frame p50 = 3.55 ms, mounted rows = 41 — as flat as the fixed-height runs. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
# 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
- host: keep virtual_take_drift/ldrift and take_rect_changes/lchanges - main.ml: adopt frame scheduler tick; Present branch applies populate's anchor-drift actions from Assembly.repaint - tests: keep both sides' cases
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>
…-216 Brings the snapped vlist row rects and the retained vlist-scroll cross-check test onto the variable-height list branch; keeps the variable engine's virtual_take_drift surface. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
tiensonqin
changed the base branch from
perf/ml-m3-virtual-list
to
feat/ml-backend
October 13, 2026 01:04
Base tree equals the already-merged M3 tip; conflicts are squash artifacts, resolved to this branch's side.
lui_window_linux/main.ml: keep drift-action apply from Assembly.repaint, adopt renderer-ladder gl_live ().
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
Stacked on #215 (M3). Milestone 5: variable-height rows in the virtual list.
virtual_take_drift(consume-on-read) lets the driver fold it back into the scroll offset so the top row does not visibly jump.populatenow returns the correction's actions for dispatch.vlist-var-100kscenario (every 7th row 60px vs 28px estimate) exercises measurement + drift; the harness applies drift like the driver.Test plan
dune buildclean,dune runtestall greenvlist mounted childrenslice,vlist anchor drift(mid-list jump yields exactly +160 correction, consume-on-read, corrected item positions)Bench (this branch)
Mixed-height scroll is as fast as fixed-height; mounted set stays ~40 rows at 100k items.
Generated with Devin