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);
|
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
||||||
}
|
}
|
||||||
let align = rsc.widgets().alignment(id);
|
let align = rsc.widgets().alignment(id);
|
||||||
let replace_answer = self.answer_invalid.remove(&id)
|
// Nothing this widget has is an answer while something it measured
|
||||||
|| (self.replace_answers
|
// is dirty: settling that changes what it would report, and a widget
|
||||||
&& (rsc.widgets().needs_redraw.contains(&id)
|
// settled inside its parent's draw tells nobody -- the comparison
|
||||||
|| self.dirty_size_under(id, rsc.widgets())));
|
// that marks a reader is in `redraw`, which is not what asked here.
|
||||||
let retained = match replace_answer {
|
// 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,
|
true => None,
|
||||||
false => self
|
false => self
|
||||||
.retained_answer(id, info, rsc.widgets())
|
.retained_answer(id, info)
|
||||||
.or_else(|| self.try_reuse(id, region, info, rsc)),
|
.or_else(|| self.try_reuse(id, region, info, rsc)),
|
||||||
};
|
};
|
||||||
let answer = retained.unwrap_or_else(|| {
|
let answer = retained.unwrap_or_else(|| {
|
||||||
@@ -489,16 +494,9 @@ impl UiRenderState {
|
|||||||
|
|
||||||
/// The answer to an ask can be retained independently of where its
|
/// 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
|
/// drawing ended up. Alignment is exactly that case: the first box is the
|
||||||
/// question and the smaller placed box holds the drawing.
|
/// question and the smaller placed box holds the drawing. Whether the
|
||||||
fn retained_answer(
|
/// answer is stale at all is its caller's question, asked once there.
|
||||||
&self,
|
fn retained_answer(&self, id: WidgetId, info: DrawInfo) -> Option<(Size, [Holds; 2])> {
|
||||||
id: WidgetId,
|
|
||||||
info: DrawInfo,
|
|
||||||
widgets: &Widgets,
|
|
||||||
) -> Option<(Size, [Holds; 2])> {
|
|
||||||
if widgets.needs_redraw.contains(&id) || self.dirty_size_under(id, widgets) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let active = self.active.get(&id)?;
|
let active = self.active.get(&id)?;
|
||||||
let has_region_node = active.move_idx != active.parent_move;
|
let has_region_node = active.move_idx != active.parent_move;
|
||||||
if !active.drawn
|
if !active.drawn
|
||||||
@@ -512,9 +510,11 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether anything whose size this widget's own size was read from is
|
/// 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
|
/// dirty, which makes what it would answer not yet known. It also keeps
|
||||||
/// reaches its reader in any order -- but a reader that asks first
|
/// a reader that asks first from laying out twice, which is all it was
|
||||||
/// lays out once rather than twice.
|
/// 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 {
|
fn dirty_size_under(&self, id: WidgetId, widgets: &Widgets) -> bool {
|
||||||
self.active.get(&id).is_some_and(|active| {
|
self.active.get(&id).is_some_and(|active| {
|
||||||
active.size_deps.iter().any(|child| {
|
active.size_deps.iter().any(|child| {
|
||||||
|
|||||||
Reference in new issue
Block a user