diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 4d169c2..b62d68b 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -69,6 +69,10 @@ pub struct UiRenderState { /// Widgets waiting for an ancestor to draw them, so the walk down the /// depths does not pick one up again at its own depth. deferred: crate::util::HashSet, + /// What the walk has left to settle, deepest last. Ordered rather than + /// searched: the set is scanned once a frame, and every mark made while + /// the walk runs puts itself in place. + pending: std::collections::BTreeSet<(usize, WidgetId)>, pub moves: Moves, } @@ -81,6 +85,7 @@ impl UiRenderState { old_root: None, slots: Default::default(), deferred: Default::default(), + pending: Default::default(), moves: Default::default(), resized: false, } @@ -994,15 +999,25 @@ impl UiRenderState { // something below is about to change it -- which is the whole class // of defect where a widget settles inside its parent's draw, clears // its mark there, and tells nobody its answer moved. - loop { - let next = rsc - .widgets() - .needs_redraw - .iter() - .copied() - .filter(|id| !self.deferred.contains(id)) - .max_by_key(|&id| self.depth(id)); - let Some(id) = next else { break }; + let marked: Vec = rsc.widgets().needs_redraw.iter().copied().collect(); + for id in marked { + let depth = self.depth(id); + self.pending.insert((depth, id)); + } + while let Some(&(depth, id)) = self.pending.last() { + self.pending.remove(&(depth, id)); + // A widget settled inside an ancestor's draw, or deferred to one, + // is left here by the mark that queued it. + if self.deferred.contains(&id) || !rsc.widgets().needs_redraw.contains(&id) { + continue; + } + // A subtree that changed hands takes its descendants' depths with + // it, so an entry queued before that move names the wrong one. + let now = self.depth(id); + if now != depth { + self.pending.insert((now, id)); + continue; + } #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::QueuePops); if !self.redraw(id, rsc) { @@ -1012,6 +1027,16 @@ impl UiRenderState { self.deferred.clear(); } + /// Marks a widget for the walk to settle. Every mark made while a frame + /// is being laid out goes through here, so the queue holds what the set + /// holds without being searched again. + fn mark(&mut self, id: WidgetId, widgets: &mut Widgets) { + if widgets.needs_redraw.insert(id) && !self.deferred.contains(&id) { + let depth = self.depth(id); + self.pending.insert((depth, id)); + } + } + fn depth(&self, id: WidgetId) -> usize { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::DepthReads); @@ -1115,8 +1140,8 @@ impl UiRenderState { // Both stay marked: the parent because it has this to draw, and // this because the parent must draw it rather than keep what it // has. The mark comes off in `draw_at`, where the parent draws. - rsc.widgets_mut().needs_redraw.insert(id); - rsc.widgets_mut().needs_redraw.insert(parent); + self.mark(id, rsc.widgets_mut()); + self.mark(parent, rsc.widgets_mut()); return false; } if !active.drawn { @@ -1144,8 +1169,8 @@ impl UiRenderState { // mark left on. Lengths and not whole boxes: what a drawing depends // on is its lengths, so the same lengths elsewhere is one question. if given_px != offered_px { - rsc.widgets_mut().needs_redraw.insert(id); - rsc.widgets_mut().needs_redraw.insert(parent); + self.mark(id, rsc.widgets_mut()); + self.mark(parent, rsc.widgets_mut()); return false; } let info = DrawInfo { @@ -1200,7 +1225,7 @@ impl UiRenderState { diag::bump(Counter::SizeChanges); diag::bump(Counter::ReaderEdges); } - rsc.widgets_mut().needs_redraw.insert(parent); + self.mark(parent, rsc.widgets_mut()); } true } diff --git a/core/src/util/slot.rs b/core/src/util/slot.rs index 94fd2a1..707b0bf 100644 --- a/core/src/util/slot.rs +++ b/core/src/util/slot.rs @@ -1,4 +1,4 @@ -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct SlotId { idx: u32, genr: u32,