Resolve deferred size comparisons before allocating span slots

This commit is contained in:
iris-ai committed 2026-09-20 16:50:18 -04:00
1 parent de1eb7e406
commit 8780b40bb7
23 files changed
+1471 -130

No files matched your search

+100 -28
View File
@@ -94,7 +94,10 @@ pub struct UiRenderState {
deferred: crate::util::HashSet<WidgetId>,
/// What the walk has left to settle, deepest last. Ordered rather than
/// searched for, so finding the next one is not a pass over the marks.
pending: std::collections::BTreeSet<(usize, WidgetId)>,
pending: std::collections::BinaryHeap<(usize, WidgetId)>,
pub(super) requests: crate::RequestArena,
changed: Vec<WidgetId>,
request_readers: HashMap<WidgetId, crate::util::HashSet<WidgetId>>,
pub moves: Moves,
}
@@ -108,6 +111,9 @@ impl UiRenderState {
slots: Default::default(),
deferred: Default::default(),
pending: Default::default(),
requests: Default::default(),
changed: Vec::new(),
request_readers: Default::default(),
moves: Default::default(),
resized: false,
}
@@ -194,6 +200,7 @@ impl UiRenderState {
weak widgets: {all:#?}"
);
}
self.requests.reset();
let root = root.into();
if self.root_changed(root) {
self.redraw_all(root, rsc);
@@ -211,8 +218,13 @@ impl UiRenderState {
let _layout = diag::timer(TimerKind::FullLayout);
self.clear(rsc);
if let Some(id) = root {
let ask =
Placing::WINDOW.ask(rsc.widgets(), self.output_size, id.id(), PlaceDesc::WHOLE);
let ask = Placing::WINDOW.ask(
rsc.widgets(),
&mut self.requests,
self.output_size,
id.id(),
PlaceDesc::WHOLE,
);
let info = self.root_info(&ask, rsc.widgets().is_region_node(id.id()));
self.draw_inner(id.id(), info, None, rsc);
}
@@ -327,7 +339,19 @@ impl UiRenderState {
let mask_slot = old
.as_ref()
.and_then(|old| old.mask_region.map(|_| old.mask));
let old_children = old.map_or_else(Vec::new, |old| old.children);
let (mut old_children, textures, primitives, request_deps, mut scratch) = match old {
Some(old) => (
old.children,
old.textures,
old.primitives,
old.request_deps,
old.scratch,
),
None => Default::default(),
};
let children = std::mem::take(&mut scratch.children);
let size_deps = std::mem::take(&mut scratch.size_deps);
let under = std::mem::take(&mut scratch.under);
rsc.widgets_mut().needs_redraw.remove(&id);
let window = self.output_size;
let mut painter = Painter {
@@ -339,17 +363,19 @@ impl UiRenderState {
layer: info.layer,
own_layer: info.layer,
id,
textures: Vec::new(),
primitives: Vec::new(),
textures,
primitives,
mask_region: None,
mask_slot,
children: Vec::new(),
size_deps: Vec::new(),
children,
size_deps,
request_deps,
scratch,
// What the ask holds for is part of what the drawing holds for:
// a box the widget's own rule took past the offer was decided in
// this window, and at the root nobody else keeps that range.
own: info.ask_holds,
under: Vec::new(),
under,
answer_under: LayoutHolds::ANY,
depth: info.depth,
move_idx,
@@ -381,8 +407,10 @@ impl UiRenderState {
own,
answer_under,
children,
size_deps,
under,
mut size_deps,
request_deps,
mut scratch,
mut under,
move_idx,
layer,
own_layer: _,
@@ -402,14 +430,19 @@ impl UiRenderState {
// resolved into the rel base when the widget was asked, and resolving
// it again here would take the fraction of a fraction.
let rules = rsc.widgets().size_rules(id);
let ruled = |axis: Axis, reported: LayoutLen| match rules[axis].exact() {
None => reported,
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
rel: info.rel_base[axis].rel,
px: info.rel_base[axis].px,
leftover: Weight::ZERO,
},
Some(len) => len.within_len(info.rel_base[axis]),
let ruled = |axis: Axis, reported: LayoutLen| {
if matches!(rules[axis], crate::SizeRule::Request(_)) {
return info.rel_base[axis].into();
}
match rules[axis].exact() {
None => reported,
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
rel: info.rel_base[axis].rel,
px: info.rel_base[axis].px,
leftover: Weight::ZERO,
},
Some(len) => len.within_len(info.rel_base[axis]),
}
};
let mut size = Size {
x: ruled(Axis::X, size.x),
@@ -483,8 +516,8 @@ impl UiRenderState {
}
let answer_holds = own_holds.and(answer_under);
let holds = under
.into_iter()
.fold(answer_holds, |holds, (_, child)| holds.and(child));
.iter()
.fold(answer_holds, |holds, (_, child)| holds.and(*child));
debug_assert!(
holds.contains(self.output_size, info.rel_base, region),
"'{}' ({id:?}) drew in {}, outside the ranges it reported: {holds:?}",
@@ -520,6 +553,15 @@ impl UiRenderState {
}
}
for &dep in &request_deps {
self.request_readers.entry(dep).or_default().insert(id);
}
old_children.clear();
size_deps.clear();
under.clear();
scratch.children = old_children;
scratch.size_deps = size_deps;
scratch.under = under;
let active = ActiveData {
id,
placement: region,
@@ -539,6 +581,8 @@ impl UiRenderState {
primitives,
mask_region,
children,
request_deps,
scratch,
declared: info.declared,
bounds: info.bounds,
own_align: rsc.widgets().alignment(id),
@@ -585,6 +629,7 @@ impl UiRenderState {
if !active.drawn
|| active.is_region_node() != info.region_node
|| active.parent_move != info.parent_move
|| active.bounds != info.bounds
{
return None;
}
@@ -841,6 +886,11 @@ impl UiRenderState {
fn remove(&mut self, id: WidgetId, undraw: bool, rsc: &mut dyn UiRsc) -> Option<ActiveData> {
let mut active = self.active.remove(&id);
if let Some(active) = &mut active {
for dep in active.request_deps.drain(..) {
if let Some(readers) = self.request_readers.get_mut(&dep) {
readers.remove(&id);
}
}
for primitive in &active.primitives {
let mask = self.layers.free(&primitive.handle);
if mask != MaskIdx::NONE {
@@ -914,6 +964,8 @@ impl UiRenderState {
primitives: Vec::new(),
mask_region: None,
children: Vec::new(),
request_deps: Vec::new(),
scratch: Default::default(),
move_idx: info.parent_move,
declared: Declared::NONE,
bounds: Bounds::ANY,
@@ -927,6 +979,7 @@ impl UiRenderState {
}
fn clear(&mut self, rsc: &mut dyn UiRsc) {
self.request_readers.clear();
for (_, active) in self.active.drain() {
if active.drawn {
rsc.on_undraw(&active);
@@ -947,6 +1000,7 @@ impl UiRenderState {
rsc.on_remove(id);
self.remove(id, true, rsc);
self.drop_slot(id);
self.request_readers.remove(&id);
}
rsc.ui_mut().textures.free();
}
@@ -954,6 +1008,18 @@ impl UiRenderState {
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
#[cfg(feature = "layout-diagnostics")]
let _layout = diag::timer(TimerKind::IncrementalLayout);
self.changed.clear();
self.changed
.extend(rsc.widgets().needs_redraw.iter().copied());
while let Some(id) = self.changed.pop() {
if let Some(readers) = self.request_readers.get(&id) {
for &reader in readers {
if rsc.widgets_mut().needs_redraw.insert(reader) {
self.changed.push(reader);
}
}
}
}
// Deepest first, and strictly: a widget that cannot settle where it
// is defers to its parent rather than drawing the parent from
// inside itself. It marks the parent, stays marked, and waits here
@@ -965,21 +1031,21 @@ 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.
// The queue is that set, ordered: a mark made while the walk runs
// queues itself through `mark`. What ends the walk is still the set
// A mark made while the walk runs queues itself through `mark`.
// What ends the walk is still the set
// being spent, not the queue, so a mark that reached it another way
// cannot be left for the next frame.
loop {
for &id in rsc.widgets().needs_redraw.iter() {
if !self.deferred.contains(&id) {
let depth = self.depth(id);
self.pending.insert((depth, id));
self.pending.push((depth, id));
}
}
if self.pending.is_empty() {
break;
}
while let Some((depth, id)) = self.pending.pop_last() {
while let Some((depth, id)) = self.pending.pop() {
// Settled inside an ancestor's draw, or deferred to one,
// since the mark that queued it.
if self.deferred.contains(&id) || !rsc.widgets().needs_redraw.contains(&id) {
@@ -990,7 +1056,7 @@ impl UiRenderState {
// depth it had under the parent it left.
let now = self.depth(id);
if now != depth {
self.pending.insert((now, id));
self.pending.push((now, id));
continue;
}
#[cfg(feature = "layout-diagnostics")]
@@ -1007,7 +1073,7 @@ impl UiRenderState {
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));
self.pending.push((depth, id));
}
}
@@ -1114,7 +1180,13 @@ impl UiRenderState {
Some(parent) => self.placing_of(parent, self.active[&parent].region),
None => Placing::WINDOW,
};
let ask = at.ask(rsc.widgets(), self.output_size, id, active.asked);
let ask = at.ask(
rsc.widgets(),
&mut self.requests,
self.output_size,
id,
active.asked,
);
let active = &self.active[&id];
let declared_changed = ask.declared != active.declared;
let alignment_changed = rsc.widgets().alignment(id) != active.own_align;