Say what the settle order is holding up
`try_reuse` asks whether the widget in front of it is dirty and, if not, hands its parent the size it last reported. Nothing asks whether a dirty widget sits under it through the size dependencies -- which is the check `retained_size` makes, for exactly this reason, on the path that does not draw. What covers the gap is the order `redraw_updates` settles in: taking the deepest dirty widget first means that by the time a reader draws, what it reads has already drawn and propagated. Drawing in any other order returns a stale size. Measured rather than reasoned: picking whatever the dirty set yields first fails seed 2 of `tests/generated.rs` with 24 widgets wrong, a subtree keeping a 317 px width where a cold tree has 147, and the traces are identical until a `Span` reports 317 against 147 from the same child sizes -- it had reused a subtree holding a `SetSize` whose declared width had changed. So the coupling is real and was written down nowhere. Say it in both places, since a reader of either would otherwise conclude the order is about cost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
77bb75e5de
commit
bf9438087a
1 file changed
+11
-2
@@ -388,6 +388,12 @@ impl UiRenderState {
|
||||
) -> Option<Size> {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseAttempts);
|
||||
// Only its own dirtiness, not anything dirty under it that could
|
||||
// change the size this hands back. What makes that safe is the order
|
||||
// `redraw_updates` settles in, and nothing else: by the time a reader
|
||||
// draws, everything dirty below it has been drawn and has propagated.
|
||||
// Draw in another order and this returns a stale size -- measured, on
|
||||
// seed 2 of `tests/generated.rs`.
|
||||
if rsc.widgets().needs_redraw.contains(&id) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
@@ -587,8 +593,11 @@ impl UiRenderState {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _layout = diag::timer(TimerKind::IncrementalLayout);
|
||||
// A reader's answer is only valid after every dirty size it reads has
|
||||
// settled. Equal-depth widgets are independent, so their order does
|
||||
// not matter. Resize dirtiness already marks whole reader chains, so
|
||||
// settled, and taking the deepest first is what arranges that --
|
||||
// `try_reuse` hands back a retained size without asking whether
|
||||
// anything dirty sits under it, so this order is load-bearing for the
|
||||
// answer and not only for the cost. Equal-depth widgets are
|
||||
// independent, so their order does not matter. Resize dirtiness already marks whole reader chains, so
|
||||
// choosing their shallowest roots coalesces descendants that share a
|
||||
// reader and gives each changing box its final constraints first.
|
||||
while let Some(id) = {
|
||||
|
||||
Reference in new issue
Block a user