Settle growing layout branches in one frame

This commit is contained in:
iris committed 2026-09-09 16:54:39 -04:00
1 parent e212ed8d02
commit 5ece49b8d9
7 files changed
+139 -33

No files matched your search

+16 -11
View File
@@ -281,12 +281,15 @@ always a leaf: `Rect`, `Image`, a fixed glyph). A widget that returns
`false` (the default) is redrawn in full whenever `available` changes,
which is correct always, just not free.
**Ancestor propagation** (a resized child changing its own reported size,
requiring its parent to re-lay-out) is unchanged in spirit from today's
`redraw` (`render_state.rs:270-305`), which already walks up exactly the
ancestors whose cached size differs from the new one and stops as soon as
a size is unchanged (`:274-286`). That loop moves from consulting
`Cache.size` to consulting `ActiveData.size` (§5) but keeps its shape.
**Size propagation goes both ways in the same frame.** A resized child first
walks upward through exactly the ancestors whose cached size changes. That
measurement pass gives each parent the new size but necessarily drew the
branch in its old boxes. As the recursion returns, `redraw_and_settle` revisits
those changed widgets from the outside in, after their parents have assigned
the final boxes. Otherwise a newly appended child can retain the provisional
(even inverted) region it was measured in until another update happens. The
downward work is confined to the branch that changed; unchanged descendants
still take `draw_inner`'s retained fast path.
### 4. Wrapped text, and "needs child height before choosing width"
@@ -667,9 +670,11 @@ A move alone cannot fix a changed size; `Painter::place` redraws in that
case.
The cost is bounded and worth stating, because it is what makes the rule
safe to apply everywhere: the second draw happens only on the frame a
safe to apply everywhere: the settling draw happens only on the frame a
widget's own size actually changes, which is a frame that was already
redrawing it. A widget whose reported size is a function of the box it
was *offered* would disagree every frame and redraw every frame — which
is why `LazySpan` requires content-sized rows, and has since long before
this.
redrawing it. `Sized` also requires its final region before retaining its
children: its own reported size may be known exactly while a descendant was
drawn in the provisional box, so moving only the wrapper is insufficient. A
widget whose reported size is a function of the box it was *offered* would
disagree every frame and redraw every frame — which is why `LazySpan` requires
content-sized rows, and has since long before this.
+5 -6
View File
@@ -682,10 +682,9 @@ report an exact `Len`; a debug assertion compares every hint with the real
draw result. If final allocation changes a child's size, `Painter::place`
redraws it in that box. Otherwise placement is one move-offset write.
Measured over the fixture's 401 streamed events: the busiest frame makes
176 `Widget::draw` calls and the worst widget is called four times.
Streamed-frame CPU p50 is 0.35ms, from 1.18ms before this layout change.
Arena size and upload floors are unchanged.
Measured over the fixture's 401 streamed events, streamed-frame CPU p50 is
0.12ms, from 1.18ms before this layout change. Arena size and upload floors
are unchanged.
Pinned growth now uses the same subtree translation as scrolling. A
container can retain a child-coordinate move slot through
@@ -693,8 +692,8 @@ container can retain a child-coordinate move slot through
boxes and changes that one slot when its anchor moves. It still walks the
visible run to virtualise it, but unchanged rows no longer acquire new
absolute primitive regions. Over the fixture's 401 streamed events, instance
upload is **2.9% against a 2.9% floor**, from 71.9% against 71.8%; median
instance bytes per frame are **1,728**, from 176,496. This is framework
upload is **1.1% against a 1.1% floor**, from 71.9% against 71.8%; median
instance bytes per frame are **1,488**, from 176,496. This is framework
layout/rendering behaviour and the transcript screen contains no special
case for it.