Redesign span layout around retained placement
This commit is contained in:
1 parent
ae0af8f5e3
commit
0aa03cf621
30 files changed
+501
-1321
No files matched your search
+8
-57
@@ -12,49 +12,6 @@ and six phone-report sections went on 2026-09-08 for that reason.
|
||||
|
||||
## Fix
|
||||
|
||||
- [ ] **`Span` draws every child twice, and making the first one a
|
||||
measurement moves the layout.** Found 2026-09-09; the safe half landed
|
||||
and this is the part that needs a decision.
|
||||
|
||||
`Span::draw`'s phase 1 draws each child at `UiRegion::FULL` purely to
|
||||
learn its length along the axis, then phase 2 draws it at its real
|
||||
share. The provisional slot is the whole span, so it is wrong by
|
||||
construction for every child, and the doubling compounds through nested
|
||||
spans: one streamed frame of the bench fixture made **1,083
|
||||
`Widget::draw` calls over 113 distinct widgets** before `ScrollArea`'s
|
||||
probe became a `Painter::measure`, and **453** after, with 102 of the
|
||||
113 still at 4 draws (2 real, 2 measurements).
|
||||
|
||||
Changing phase 1 to `painter.measure(child, UiRegion::FULL)` takes it
|
||||
to 2, and **it renders differently**: 28,771 pixels, and the headless
|
||||
phone shot shows the transcript shifted a few pixels vertically. The
|
||||
layout is intact -- panels, code fences and text all draw correctly --
|
||||
so this is a position difference, not a broken frame, but which of the
|
||||
two is *right* was not established and it must be before this lands.
|
||||
|
||||
The mechanism to check first. With phase 1 drawing, the child's
|
||||
`active.region` is the full span when phase 2 asks, so phase 2 always
|
||||
finds a different size and does a real redraw. With phase 1 measuring,
|
||||
`active.region` is still *last frame's* share, which usually matches,
|
||||
so phase 2 takes `draw_inner`'s `mov` branch -- one `move_offsets`
|
||||
write instead of a redraw, which is the intended win. But `mov`
|
||||
accumulates (`entry.delta += delta`) where a redraw recomputes from
|
||||
scratch, so the suspicion is float drift that phase 1's unconditional
|
||||
redraw was hiding. If that is it, the fix is in `mov`, not in `Span`.
|
||||
|
||||
Iris's framing, which is the target shape (2026-09-09): *"if you draw
|
||||
one child in a list of fixed sized children, then you know immediately
|
||||
where the second one must be and shouldn't need to probe its size
|
||||
again. The only time redraws should actually be needed are if you're
|
||||
using a `rest` length, where you don't know how long it's gonna be
|
||||
until you draw everything else first, and so you have to move things.
|
||||
Even then it should just be moving, not redrawing."* So the endpoint is
|
||||
no probe phase at all for `abs` children -- draw each in turn at
|
||||
`[cursor, end]`, read its length, advance the cursor -- with a `rest`
|
||||
child forcing a reposition pass over what follows it rather than a
|
||||
redraw. `Aligned` already has exactly this shape (one draw, then
|
||||
`Painter::reposition`) and is the example to copy.
|
||||
|
||||
- [ ] **A row moving because the list grew should be one `move_offsets`
|
||||
write, and today it is a redraw.** Found 2026-09-09 by
|
||||
`scripts/rigs/ui-profile`'s `arena_churn` and left for whoever picks
|
||||
@@ -62,8 +19,8 @@ and six phone-report sections went on 2026-09-08 for that reason.
|
||||
half.
|
||||
|
||||
The measurement. Over the bench fixture's 401 streamed deltas, the
|
||||
instance arena uploads **72.7%** of itself per frame, and that number
|
||||
*is* the floor -- those entries genuinely differ, so no amount of
|
||||
instance arena uploads **71.9%** of itself per frame against a
|
||||
**71.8%** floor -- those entries genuinely differ, so no amount of
|
||||
better dirty-tracking touches it. The control that says it is wrong is
|
||||
the fling phase on the same screen and the same content: it moves the
|
||||
same primitives every frame and uploads **3.3%**, because a scroll
|
||||
@@ -71,18 +28,12 @@ and six phone-report sections went on 2026-09-08 for that reason.
|
||||
the subtree (LAYOUT.md section 2) instead of rewriting every
|
||||
primitive's absolute region.
|
||||
|
||||
What is different about the streaming path. The list is pinned to the
|
||||
newest end, so a growing reply pushes every row above it up by the
|
||||
amount the last row grew. That is a translation of an already-drawn
|
||||
subtree -- exactly what `move_offsets` is for -- but it arrives as a
|
||||
new offered region per row, and `draw_inner`'s fast path only takes
|
||||
`mov` when `active.region.size() == region.size()`. Worth checking
|
||||
first: whether `LazySpan::place` is offering each row a region whose
|
||||
*size* differs (it computes `edges(height)` fresh each frame, so an
|
||||
identical height should compare equal -- unless a float differs in the
|
||||
last bit, which the `// TODO: epsilon?` beside that comparison already
|
||||
suspects), or whether something upstream marks the rows dirty so the
|
||||
fast path is skipped entirely.
|
||||
The list is pinned to the newest end, so a growing reply pushes every
|
||||
earlier row up. `draw_inner` now treats sub-pixel size differences as a
|
||||
move and the span refactor removed nearly all repeated draws, but the
|
||||
instance floor remains 71.8%. The remaining question is why those
|
||||
translations still rewrite primitive regions instead of stopping at
|
||||
the rows' move slots.
|
||||
|
||||
Done looks like: `arena_churn`'s `what_a_streamed_reply_uploads` shows
|
||||
stream instances in the same range as the fling's, and its `whole`
|
||||
|
||||
Reference in new issue
Block a user