Carry a widget's depth down the draw instead of walking up for it

Choosing which dirty widget to settle next asked every one of them how
deep it was, and answering meant walking its ancestry to the root. At
130 of 260 widgets dirty that was 25.8% of the frame -- more than laying
out or rendering.

A widget's depth is known where it is drawn: its parent's plus one. So
`Painter` carries it and `ActiveData` keeps it, and the choice reads a
field. Being reused counts as being visited, so the two reuse paths keep
it current too; only a subtree nothing looked at can hold an old one,
and nothing under an unvisited subtree is being ordered.

The order is unchanged, so nothing about the layout is: the five
reference renders and the resize render are byte-identical. What the
carried depth might get wrong is itself, so `depth` asserts it against
the ancestry in debug builds, and the hundred-seed sweep passes with
those assertions on -- including the reshuffles, which are what move a
widget to another parent.

Same load, 1000 frames, 130 of 260 dirty: 8.16M instructions per frame
to 7.14M, median 0.813 ms to 0.639, and the choosing from 25.8% of the
frame to 4.7%. What is left of it is iterating the dirty set itself,
which a `HashSet` walks by capacity rather than by length.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-14 20:10:27 -04:00
1 parent bf9438087a
commit 3f7cd8251b
4 files changed
+40 -5

No files matched your search

+2
View File
@@ -31,6 +31,7 @@ pub struct Painter<'a> {
/// its parent placed it, otherwise the nearest ancestor that has one.
pub(super) move_idx: MoveIdx,
pub layer: usize,
pub(super) depth: usize,
pub(super) id: WidgetId,
}
@@ -134,6 +135,7 @@ impl<'a> Painter<'a> {
id.id(),
region,
Some(self.id),
self.depth + 1,
self.move_idx,
slotted,
self.mask,