Record why an arbitrary settle order returns a stale size
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d0c80a2037
commit
ba97265191
1 file changed
+37
-32
@@ -373,41 +373,46 @@ 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
|
five reference renders and the resize render are byte-identical, and the
|
||||||
sweep passes.
|
sweep passes.
|
||||||
|
|
||||||
**Picking any dirty widget instead of the deepest does not work, and the
|
**Why picking any dirty widget does not work, found 2026-09-14.** `try_reuse`
|
||||||
generated cases say so.** Tried on 2026-09-14: take whatever the dirty set
|
asks whether the widget in front of it is dirty and, if not, hands its parent
|
||||||
yields first, and let `redraw`'s existing climb to the highest size reader do
|
the size it last reported. It does not ask whether a dirty widget sits under it
|
||||||
the ordering -- then also climbing to an ancestor that is itself dirty, since
|
through the size dependencies -- which is the check `retained_size` makes, on
|
||||||
drawing that redraws this one anyway. Both fail
|
the path that does not draw, for exactly this reason. **The settle order is
|
||||||
`a_changed_size_lands_where_growing_it_that_way_would` at seed 1, 24 widgets
|
what covers that gap**, and it was written down nowhere: taking the deepest
|
||||||
wrong, a subtree keeping a 317 px width where a cold tree has 147.
|
first means that by the time a reader draws, what it reads has drawn and
|
||||||
|
propagated.
|
||||||
|
|
||||||
What the trace says, and where it stops: with the deepest first, seed 1 settles
|
The trace that shows it, on seed 2 of `tests/generated.rs`: with the deepest
|
||||||
in 50 draws and the same `SetSize` is drawn three times as its box is decided
|
first and with an arbitrary pick, the two are identical event for event until a
|
||||||
and redecided above it. Picking any settles in 12, and two of the three edited
|
`Span` reports its size -- 147 px one way and 317 px the other, from the same
|
||||||
`SetSize` widgets are never popped at all -- they are drawn inside a `Span`'s
|
child sizes. It had reused a subtree exactly, and inside that subtree sat a
|
||||||
pass, which consumes their marks. Nothing in the climb misfires; no escalation
|
`SetSize` whose declared width had changed and which had not been drawn yet.
|
||||||
happens in this scenario at all, so the difference is only which widgets are
|
24 widgets end up wrong.
|
||||||
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.
|
So the order is not about cost, and this is the thing to fix before changing
|
||||||
It splits: of the 24.5%, about 15 points are the depth walk (removing it
|
it. Adding the missing check to `try_reuse` does make any order correct -- all
|
||||||
entirely takes 16.5B instructions to 14.0B) and about 9 are iterating the
|
six generated cases and the hundred-seed sweep pass with the dirty set taken in
|
||||||
`HashSet` itself, which walks by capacity rather than by length. Three ways
|
whatever order it yields. But `dirty_size_under` walks the size-dependency
|
||||||
out, none of them tried yet:
|
subtree on every reuse that hands a size back, and that costs about what the
|
||||||
|
sort saved: at 130 of 260 widgets dirty, 7.83M instructions per frame against
|
||||||
|
8.23M; at 32 dirty, 3.32M against 2.84M, so *worse* where the dirty set is
|
||||||
|
small. Two other shapes measured and rejected on the way: putting the check at
|
||||||
|
the top of `try_reuse` rather than on the two paths that return a size (7% dearer
|
||||||
|
again), and phrasing it as `size_is_invalid`, which also lets a resize mark
|
||||||
|
through and takes the resize phase from 7M to 106M instructions per frame.
|
||||||
|
|
||||||
- **Keep the depth on `ActiveData`.** `draw_inner` knows the parent, whose
|
**What would actually pay is a maintained count** rather than a walk: each
|
||||||
depth is in its own entry, so this is O(1) to read and the order is
|
widget holding how many dirty widgets sit under it through size-dependency
|
||||||
unchanged. The risk is a second source of truth: a widget whose ancestor is
|
edges, incremented up the reader chain when a mark is added and decremented
|
||||||
re-placed under a different parent without the widget itself being redrawn
|
when one is consumed. That is O(depth) at a mark and O(1) at the check, where
|
||||||
would hold a stale depth.
|
today it is O(dirty x depth) per widget settled. It wants agreeing first: every
|
||||||
- **Bucket the dirty set by depth.** Exact, and the depth is only needed when
|
place that inserts into or removes from `needs_redraw` has to pair with it, and
|
||||||
an id is inserted -- which inside the loop is always the parent of a widget
|
a count that is too low is a stale size rather than a slow frame.
|
||||||
whose depth is already known, so no walk is needed there.
|
|
||||||
- **Process in rounds**: sort once, drain, re-sort what is left. Cheapest to
|
Of the sort's own 24.5%, about 15 points are the depth walk and about 9 are
|
||||||
write and *not* the same order -- a widget marked during a round can be
|
iterating the `HashSet`, which walks by capacity rather than by length. Keeping
|
||||||
deeper than what remains of it.
|
the depth on `ActiveData` would remove the first without changing the order at
|
||||||
|
all, at the price of a second source of truth for it.
|
||||||
|
|
||||||
**A frame that dirties many widgets at once was not being checked, and it is
|
**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
|
where the settle order shows.** `77bb75e` adds two generated cases -- every
|
||||||
|
|||||||
Reference in new issue
Block a user