Refuse a retained answer while something the widget measured is dirty
`draw_inner` took an answer from `try_reuse`, which checks only whether the widget itself is marked, where `retained_answer` beside it also refused one while anything the widget read a size from was dirty. A widget whose drawing happened to be reusable therefore handed back the answer it gave before that descendant changed. Nothing puts that right afterwards. The comparison that tells a reader its child's answer moved is in `redraw`, and a widget settled inside its parent's own draw never goes through it -- the placing ask redraws the subtree, the descendant's mark is cleared there, and the parent keeps a number the tree no longer agrees with. So the check is not the optimization its comment claimed; it is what makes the answer an answer, and both retained routes are answers, so it is asked once in `draw_inner` rather than by one of them. Found by the generated oracle at seed 564, depth 6, `shuffle-every-other`, while reading a child's report as a fraction of the containing widget: that reading lets a span overflow itself, which makes the two asks' boxes differ far enough for the placing one to redraw. Twenty-five rig work counters are unchanged on `cold`, `repaint`, `scroll`, `resize` and `size`; `many` makes 18 fewer reuse attempts, 17 of which already reported "dirty". Both long fuzzers green.
This commit is contained in:
1 parent
ea6dbae0dc
commit
0e0d4af326
1 file changed
+19
-19
+19
-19
@@ -192,14 +192,19 @@ impl UiRenderState {
|
||||
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
||||
}
|
||||
let align = rsc.widgets().alignment(id);
|
||||
let replace_answer = self.answer_invalid.remove(&id)
|
||||
|| (self.replace_answers
|
||||
&& (rsc.widgets().needs_redraw.contains(&id)
|
||||
|| self.dirty_size_under(id, rsc.widgets())));
|
||||
let retained = match replace_answer {
|
||||
// Nothing this widget has is an answer while something it measured
|
||||
// is dirty: settling that changes what it would report, and a widget
|
||||
// settled inside its parent's draw tells nobody -- the comparison
|
||||
// that marks a reader is in `redraw`, which is not what asked here.
|
||||
// Both retained routes are an answer, so the question is asked once
|
||||
// rather than by each of them.
|
||||
let stale =
|
||||
rsc.widgets().needs_redraw.contains(&id) || self.dirty_size_under(id, rsc.widgets());
|
||||
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
||||
let retained = match replace_answer || stale {
|
||||
true => None,
|
||||
false => self
|
||||
.retained_answer(id, info, rsc.widgets())
|
||||
.retained_answer(id, info)
|
||||
.or_else(|| self.try_reuse(id, region, info, rsc)),
|
||||
};
|
||||
let answer = retained.unwrap_or_else(|| {
|
||||
@@ -489,16 +494,9 @@ impl UiRenderState {
|
||||
|
||||
/// The answer to an ask can be retained independently of where its
|
||||
/// drawing ended up. Alignment is exactly that case: the first box is the
|
||||
/// question and the smaller placed box holds the drawing.
|
||||
fn retained_answer(
|
||||
&self,
|
||||
id: WidgetId,
|
||||
info: DrawInfo,
|
||||
widgets: &Widgets,
|
||||
) -> Option<(Size, [Holds; 2])> {
|
||||
if widgets.needs_redraw.contains(&id) || self.dirty_size_under(id, widgets) {
|
||||
return None;
|
||||
}
|
||||
/// question and the smaller placed box holds the drawing. Whether the
|
||||
/// answer is stale at all is its caller's question, asked once there.
|
||||
fn retained_answer(&self, id: WidgetId, info: DrawInfo) -> Option<(Size, [Holds; 2])> {
|
||||
let active = self.active.get(&id)?;
|
||||
let has_region_node = active.move_idx != active.parent_move;
|
||||
if !active.drawn
|
||||
@@ -512,9 +510,11 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
/// Whether anything whose size this widget's own size was read from is
|
||||
/// dirty. Not needed for the answer to come right -- a changed size
|
||||
/// reaches its reader in any order -- but a reader that asks first
|
||||
/// lays out once rather than twice.
|
||||
/// dirty, which makes what it would answer not yet known. It also keeps
|
||||
/// a reader that asks first from laying out twice, which is all it was
|
||||
/// here for while a changed size was thought to reach its reader in any
|
||||
/// order; it does not, where the change settles inside the reader's own
|
||||
/// draw.
|
||||
fn dirty_size_under(&self, id: WidgetId, widgets: &Widgets) -> bool {
|
||||
self.active.get(&id).is_some_and(|active| {
|
||||
active.size_deps.iter().any(|child| {
|
||||
|
||||
Reference in new issue
Block a user