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 b1416d86bf
commit 77b39a97e3
2 files changed
+41 -47

No files matched your search

+6 -5
View File
@@ -141,11 +141,12 @@ which is correct always, just not free.
**Size invalidation travels upward before drawing; drawing itself travels only **Size invalidation travels upward before drawing; drawing itself travels only
downward.** Every active widget retains the direct children whose size it read downward.** Every active widget retains the direct children whose size it read
through `DrawResult::size()` or `Painter::known_len`. Before a frame draws, through `DrawResult::size()` or `Painter::known_len`. `redraw_updates` takes
`redraw_updates` follows only those dependency edges from each dirty child and one id from the dirty set, follows only those dependency edges upward and marks
marks the affected ancestors dirty. It then selects the highest dirty roots and that path dirty, then redraws its highest already-dirty ancestor. Drawing that
draws them top-down. Drawing never synchronously invalidates or invokes a ancestor consumes the marks of every dirty descendant it reaches; the loop
parent, so there is no layout recursion and no provisional child draw on a then takes whatever remains. Drawing never synchronously invalidates or invokes
a parent, so there is no layout recursion and no provisional child draw on a
different layer. different layer.
An exact `size_hint` stops propagation when both axes still equal the retained An exact `size_hint` stops propagation when both axes still equal the retained
+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();
} }