diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 06bc266..376216c 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -388,6 +388,12 @@ impl UiRenderState { ) -> Option { #[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) = {