Record that picking any dirty widget does not work, and what it costs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-14 19:37:51 -04:00
1 parent 592cc808ca
commit d0c80a2037
1 file changed
+36
+36
View File
@@ -373,6 +373,42 @@ holds a *drawing* keyed on what produced it, and the widget still draws. The
five reference renders and the resize render are byte-identical, and the
sweep passes.
**Picking any dirty widget instead of the deepest does not work, and the
generated cases say so.** Tried on 2026-09-14: take whatever the dirty set
yields first, and let `redraw`'s existing climb to the highest size reader do
the ordering -- then also climbing to an ancestor that is itself dirty, since
drawing that redraws this one anyway. Both fail
`a_changed_size_lands_where_growing_it_that_way_would` at seed 1, 24 widgets
wrong, a subtree keeping a 317 px width where a cold tree has 147.
What the trace says, and where it stops: with the deepest first, seed 1 settles
in 50 draws and the same `SetSize` is drawn three times as its box is decided
and redecided above it. Picking any settles in 12, and two of the three edited
`SetSize` widgets are never popped at all -- they are drawn inside a `Span`'s
pass, which consumes their marks. Nothing in the climb misfires; no escalation
happens in this scenario at all, so the difference is only which widgets are
drawn as the root of a pass and which inside someone else's. **Why that changes
the answer is not yet pinned down**, and it is the thing to find out before
changing the order again.
So the order is load-bearing and the cost is the scan rather than the order.
It splits: of the 24.5%, about 15 points are the depth walk (removing it
entirely takes 16.5B instructions to 14.0B) and about 9 are iterating the
`HashSet` itself, which walks by capacity rather than by length. Three ways
out, none of them tried yet:
- **Keep the depth on `ActiveData`.** `draw_inner` knows the parent, whose
depth is in its own entry, so this is O(1) to read and the order is
unchanged. The risk is a second source of truth: a widget whose ancestor is
re-placed under a different parent without the widget itself being redrawn
would hold a stale depth.
- **Bucket the dirty set by depth.** Exact, and the depth is only needed when
an id is inserted -- which inside the loop is always the parent of a widget
whose depth is already known, so no walk is needed there.
- **Process in rounds**: sort once, drain, re-sort what is left. Cheapest to
write and *not* the same order -- a widget marked during a round can be
deeper than what remains of it.
**A frame that dirties many widgets at once was not being checked, and it is
where the settle order shows.** `77bb75e` adds two generated cases -- every
declared size changing together, and a spread of widgets marked for redraw