iris: process dirty widgets one at a time

This commit is contained in:
iris committed 2026-09-12 22:05:06 -04:00
1 parent eb30a01f2d
commit 218c1cb230
1 file changed
+35 -42
+35 -42
View File
@@ -40,10 +40,10 @@ pub struct UiRenderState {
old_root: Option<WidgetId>, old_root: Option<WidgetId>,
resized: bool, resized: bool,
/// It used to only ever be inserted into, and `redraw` removed the id /// Widgets whose `draw` call is on the stack now. A reentrant draw would
/// *before* testing for it, which made the test constant `false`: the /// create two retained primitive sets with one owner, so it is rejected;
/// guard could never fire and the set grew by one entry per widget /// completed draws are removed immediately and are instead tracked by
/// ever drawn and was never emptied. /// consuming their outstanding dirty mark.
draw_started: HashSet<WidgetId>, draw_started: HashSet<WidgetId>,
/// Widgets which asked from inside `draw` to be drawn on the following /// Widgets which asked from inside `draw` to be drawn on the following
/// frame. Kept separate from `Widgets::needs_redraw` until `update_at` /// frame. Kept separate from `Widgets::needs_redraw` until `update_at`
@@ -824,49 +824,42 @@ impl UiRenderState {
} }
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) { pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
while rsc.widgets().has_updates() { while let Some(id) = rsc.widgets().needs_redraw.iter().next().copied() {
let pending: Vec<_> = rsc.widgets().needs_redraw.iter().copied().collect(); // A parent which read this child's size must lay out again before
for mut child in pending { // the changed child is drawn. Mark the whole dependent path so
for _ in 0..PARENT_CHAIN_LIMIT { // drawing its highest member reaches every dirty descendant.
if self.size_matches_hints(child, rsc) { let mut child = id;
break; for _ in 0..PARENT_CHAIN_LIMIT {
} if self.size_matches_hints(child, rsc) {
let Some(parent) = self.active.get(&child).and_then(|active| active.parent) break;
else {
break;
};
let depends = self
.active
.get(&parent)
.is_some_and(|active| active.size_dependencies.contains(&child));
if !depends {
break;
}
rsc.widgets_mut().needs_redraw.insert(parent);
child = parent;
} }
let Some(parent) = self.active.get(&child).and_then(|active| active.parent) else {
break;
};
let depends = self
.active
.get(&parent)
.is_some_and(|active| active.size_dependencies.contains(&child));
if !depends {
break;
}
rsc.widgets_mut().needs_redraw.insert(parent);
child = parent;
} }
let dirty: Vec<_> = rsc.widgets().needs_redraw.iter().copied().collect(); // Prefer an already-dirty ancestor. Its draw either consumes this
let mut roots = Vec::new(); // mark on the way down or leaves it for the next loop iteration if
for id in dirty { // a retained intermediate subtree means it never reaches here.
let mut ancestor = self.active.get(&id).and_then(|active| active.parent); let mut root = id;
let mut covered = false; let mut ancestor = self.active.get(&id).and_then(|active| active.parent);
for _ in 0..PARENT_CHAIN_LIMIT { for _ in 0..PARENT_CHAIN_LIMIT {
let Some(parent) = ancestor else { break }; let Some(parent) = ancestor else { break };
if rsc.widgets().needs_redraw.contains(&parent) { if rsc.widgets().needs_redraw.contains(&parent) {
covered = true; root = parent;
break;
}
ancestor = self.active.get(&parent).and_then(|active| active.parent);
}
if !covered {
roots.push(id);
} }
ancestor = self.active.get(&parent).and_then(|active| active.parent);
} }
for id in roots { self.redraw(root, rsc);
self.redraw(id, rsc);
}
} }
rsc.free(); rsc.free();
} }