Compare commits

..
Author SHA1 Message Date
iris-ai 5b181bc8af Experiment: say the room from the cursor as an inset, and read the row's length only where a slot depends on it
Measured identical to its parent at every phase of the cost rig; kept as
evidence, not proposed for landing.
2026-09-19 02:19:14 -04:00
11 changed files with 139 additions and 308 deletions

No files matched your search

-1
View File
@@ -53,7 +53,6 @@ pub struct ActiveData {
/// Its primitives, each keeping the box it was written in -- in this
/// widget's extent coordinates, which is what a move recomposes from.
pub primitives: Vec<RetainedPrimitive>,
/// An owned mask holds one reference independently of its primitives.
pub mask_region: Option<UiRegion>,
pub children: Vec<WidgetId>,
/// The children whose size this widget read while drawing.
+15
View File
@@ -73,6 +73,21 @@ impl Holds {
}
}
/// What a box has to be for a part of it, this many pixels shorter, to
/// stay in this range: the range moved by that much, an end that was
/// unbounded staying so.
pub const fn longer_by(self, px: Px) -> Self {
let lo = match self.lo.raw() == Px::MIN.raw() {
true => self.lo,
false => self.lo.add(px),
};
let hi = match self.hi.raw() == Px::MAX.raw() {
true => self.hi,
false => self.hi.add(px),
};
Self { lo, hi }
}
const fn raws(lo: i64, hi: i64) -> Self {
Self {
lo: Px::from_raw(narrow(lo)),
+53 -54
View File
@@ -35,8 +35,6 @@ pub struct Painter<'a> {
pub(super) textures: Vec<TextureHandle>,
pub(super) primitives: Vec<RetainedPrimitive>,
pub(super) mask_region: Option<UiRegion>,
/// The previous drawing's owned mask, available for this draw to reclaim.
pub(super) mask_slot: Option<MaskIdx>,
/// Only children whose answers were read constrain this widget's answer.
pub(super) answer_under: LayoutHolds,
pub(super) children: Vec<WidgetId>,
@@ -106,6 +104,7 @@ impl<'a> Painter<'a> {
fn push_primitive(&mut self, h: RetainedPrimitive) {
if self.mask != MaskIdx::NONE {
// TODO: I have no clue if this works at all :joy:
self.rsc.ui_mut().masks.push_ref(self.mask);
}
self.primitives.push(h);
@@ -130,23 +129,10 @@ impl<'a> Painter<'a> {
assert!(self.mask == MaskIdx::NONE);
let resolved = self.resolve(region);
let move_idx = self.move_idx;
let mask = Mask {
self.mask = self.rsc.ui_mut().masks.push(Mask {
region: resolved,
move_idx,
};
let masks = &mut self.rsc.ui_mut().masks;
self.mask = match self.mask_slot.take() {
Some(idx) => {
*masks.get_mut(idx) = mask;
idx
}
None => {
let idx = masks.push(mask);
// The owner keeps the slot alive even with no primitives.
masks.push_ref(idx);
idx
}
};
});
}
/// Draws a widget in the whole of this widget's own box, with the frame
@@ -281,32 +267,36 @@ impl<'a> Painter<'a> {
let widgets = self.rsc.widgets();
// A rule is the answer where there is one: it wins over whatever the
// widget would draw, so it has to win over what the widget says too.
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| {
widgets
.get_dyn(id.id())
.and_then(|widget| widget.size_hint(axis))
});
let frame = self.frame.axis(axis);
let resolved = hint.map(|hint| hint.within_len(frame));
let hint = widgets
.size_rules(id.id())
.axis(axis)
.exact()
.or_else(|| {
widgets
.get_dyn(id.id())
.and_then(|widget| widget.size_hint(axis))
})
.map(|hint| hint.within_len(self.frame.axis(axis)));
#[cfg(feature = "layout-diagnostics")]
{
diag::hint_read(id.id(), self.id, axis, resolved);
diag::bump(match resolved {
Some(_) => Counter::HintHits,
None => Counter::HintMisses,
});
}
if let Some(hint) = hint {
self.depend_on(id);
// Resolving a fraction against this frame makes this draw a
// function of the frame's length. The fraction to ask about is
// the child's own: resolved against a frame of pixels, none is
// left to see it by.
if hint.rel != Rel::ZERO {
self.frame_own_len[axis as usize] = Some(frame);
diag::hint_read(id.id(), self.id, axis, hint);
match hint {
Some(hint) => {
#[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::HintHits);
self.depend_on(id);
// A fraction was just resolved against this frame, so what
// this draw does with it is a function of the frame's length.
if hint.rel != Rel::ZERO {
self.frame_own_len[axis as usize] = Some(self.frame.axis(axis));
}
Some(hint)
}
None => {
#[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::HintMisses);
None
}
}
resolved
}
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
@@ -603,20 +593,29 @@ impl Painter<'_> {
result.extent[n] = holds.extent[n];
result.extent_len[n] = holds.extent_len[n];
}
// Its box is a part of this widget's own box, in that box's
// own lengths, so what it holds for maps back through that
// part into a range on this widget's box. A length it pinned
// is this widget's length less the part's pixels where the
// part is the whole of the box less pixels, which is the one
// shape that inverts exactly; any other part pins this
// widget's own length.
(Part::Of(span), false) => {
let part_len = span.len();
result.extent[n] = holds.extent[n].through(part_len);
result.extent_len[n] = holds.extent_len[n].map(|pinned| match part_len.rel {
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px),
_ => self.extent.axis(axis).len(),
});
// Its box is this widget's own less the inset. Where that is
// pixels, its box is exactly that many shorter in any window,
// so what it holds for is a range on this widget's box moved
// by them, and a length it pinned is this widget's length
// less them. An inset with a fraction in it is a different
// number of pixels in each window, and taking it off a length
// rounds once more than taking it off pixels does: there the
// child's box is a fixed expression of this one, so this
// widget's length is pinned and the range goes on the window
// through the child's box, the way a slot's does.
(Part::Inset { lead, trail }, false) => {
let inset = lead + trail;
match inset.rel == Rel::ZERO {
true => {
result.extent[n] = holds.extent[n].longer_by(inset.px);
result.extent_len[n] = holds.extent_len[n].map(|pinned| pinned + inset);
}
false => {
result.window[n] = result.window[n]
.and(holds.extent[n].through(extent.axis(axis).len()));
result.extent_len[n] = Some(self.extent.axis(axis).len());
}
}
}
// Its box is a length this widget decided, from its own
// frame or from a sibling's answer: no length of this
+8 -7
View File
@@ -12,12 +12,13 @@ pub enum Part {
/// here is a fraction of the window and not of the box -- the whole of a
/// box is [`Self::All`], not a `rel(1.0)` span.
From(UiSpan),
/// A part of the box in its own coordinates, which is what a container
/// that insets one speaks: taking eleven pixels off the end needs no
/// length, where saying the same thing in window lengths would make the
/// container read its own box -- and a box chosen from its own answer
/// then feeds back into the answer.
Of(UiSpan),
/// The box less a window length at each end, which is what a container
/// that insets one speaks -- padding, or a row asking a child in the
/// room left from its cursor. Neither end names the box's length, so a
/// container can say "from here to my end" without reading how long it
/// is, and a box chosen from its own answer does not feed back into the
/// answer.
Inset { lead: Len, trail: Len },
/// A box of this length, wherever in the parent's box the child's own
/// alignment puts it, and that same length as its frame. Unlike `From`,
/// it is a length decided from above rather than a place along a
@@ -32,7 +33,7 @@ impl Part {
match self {
Self::All => extent,
Self::From(span) => UiSpan::new(extent.start + span.start, extent.start + span.end),
Self::Of(span) => span.within(&extent),
Self::Inset { lead, trail } => UiSpan::new(extent.start + lead, extent.end - trail),
Self::Sized(len) => {
let start = extent.start + (extent.len() - len).scale(align.rel());
UiSpan::new(start, start + len)
+6 -22
View File
@@ -223,10 +223,6 @@ impl UiRenderState {
mut old: Option<ActiveData>,
rsc: &mut dyn UiRsc,
) -> (Size, LayoutHolds, LayoutHolds) {
let old_parent = old
.as_ref()
.or_else(|| self.active.get(&id))
.and_then(|a| a.parent);
let part = info.part;
#[cfg(feature = "layout-diagnostics")]
{
@@ -279,9 +275,12 @@ impl UiRenderState {
active.part = part;
active.placed = info.placed;
active.own_align = align;
// The previous parent must stop owning the subtree before it can
// undraw it, whether changing hands reused the drawing or replaced it.
active.parent = info.parent;
// A subtree can be reused whole under a different parent -- same box,
// same layer, same region node -- and nothing in the drawing says it
// changed hands. Two things read who its parent is: a deferral, which
// marks whoever has it to draw, and the old parent's list of children,
// which its next draw undraws whatever is missing from.
let old_parent = std::mem::replace(&mut active.parent, info.parent);
if old_parent != info.parent
&& let Some(old_parent) = old_parent
&& let Some(old_parent) = self.active.get_mut(&old_parent)
@@ -313,9 +312,6 @@ impl UiRenderState {
// Reusing its index sooner could make an old parent look current.
false => (info.parent_move, extent, self.slots.remove(&id)),
};
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);
rsc.widgets_mut().needs_redraw.remove(&id);
let px = info.px;
@@ -334,7 +330,6 @@ impl UiRenderState {
textures: Vec::new(),
primitives: Vec::new(),
mask_region: None,
mask_slot,
children: Vec::new(),
size_deps: Vec::new(),
window_own: [Holds::ANY; 2],
@@ -368,7 +363,6 @@ impl UiRenderState {
textures,
primitives,
mask_region,
mask_slot,
extent_own,
extent_len,
answer_under,
@@ -427,9 +421,6 @@ impl UiRenderState {
self.undraw_rec(*c, rsc);
}
}
if let Some(idx) = mask_slot {
rsc.ui_mut().masks.remove(idx);
}
if let Some(idx) = retired_move {
self.moves.remove(idx);
}
@@ -614,9 +605,6 @@ impl UiRenderState {
}
return None;
}
if active.parent_mask != info.mask {
return None;
}
// Drawn somewhere else in the tree: its box is in coordinates it no
// longer sits in, and its slot names the wrong parent.
if active.parent_move != info.parent_move {
@@ -838,9 +826,6 @@ impl UiRenderState {
rsc.ui_mut().masks.remove(mask);
}
}
if undraw && active.mask_region.take().is_some() {
rsc.ui_mut().masks.remove(active.mask);
}
active.primitives.clear();
active.textures.clear();
rsc.ui_mut().textures.free();
@@ -928,7 +913,6 @@ impl UiRenderState {
self.slots.clear();
self.moves.clear();
self.layers.clear();
rsc.ui_mut().masks = Default::default();
rsc.widgets_mut().needs_redraw.clear();
self.free(rsc);
}
+4 -4
View File
@@ -20,10 +20,10 @@ impl Widget for Pad {
// The two stay distinct -- the box can be narrower still, where a row
// asked this widget in the room left, and a text wraps at that.
let inset = |lead: Px, trail: Px| {
Place::Within(Part::Of(UiSpan::new(
Len::from_parts(Rel::ZERO, lead),
Len::from_parts(Rel::ONE, -trail),
)))
Place::Within(Part::Inset {
lead: Len::from_parts(Rel::ZERO, lead),
trail: Len::from_parts(Rel::ZERO, trail),
})
};
let place = [
inset(self.padding.left, self.padding.right),
+43 -18
View File
@@ -10,13 +10,18 @@ pub struct Span {
impl Widget for Span {
fn draw(&mut self, painter: &mut Painter) -> Size {
let axis = self.dir.axis;
// The row: this span's own box, as a length of the frame its children
// are laid out against. Its start is nothing's business -- a slot is
// a length from it -- so what this reads is the length alone.
let far = painter.extent_len(axis);
let along = |from: Len, to: Len| match self.dir.sign {
Sign::Pos => UiSpan::new(from, to),
Sign::Neg => UiSpan::new(far - to, far - from),
// The room left from the cursor to the row's end, said without the
// row's length: a child measured in it does not make this drawing
// depend on how long the row is.
let room_from = |cursor: Len| match self.dir.sign {
Sign::Pos => Part::Inset {
lead: cursor,
trail: Len::ZERO,
},
Sign::Neg => Part::Inset {
lead: Len::ZERO,
trail: cursor,
},
};
// Across itself the child sits where its own alignment says, in the
// whole of the row: a span is what contains its children there, and
@@ -28,24 +33,28 @@ impl Widget for Span {
// given whatever else is in it and wherever this child sits among
// them; what a drawn child is asked in is the room left from the
// cursor, because a text has to wrap at the width actually there.
// This is the one ask a drawn fixed child gets: its slot is its
// answer, and the drawing is moved there once the shares are known.
// A hinted child is asked once, in its slot.
let mut cursor = Len::rel_min();
let mut lens = Vec::with_capacity(self.children.len());
let mut drawn_across = Vec::with_capacity(self.children.len());
let mut measured = Vec::with_capacity(self.children.len());
for child in &self.children {
let len = match painter.size_hint(child, axis) {
let size = match painter.size_hint(child, axis) {
Some(len) => {
drawn_across.push(None);
measured.push(None);
len
}
None => {
let room = Place::Within(Part::From(along(cursor, far)));
let room = Place::Within(room_from(cursor));
let size = painter
.widget_at(child, [None; 2], axis.pair(room, across))
.size();
drawn_across.push(Some(size.axis(!axis)));
measured.push(Some(size));
size.axis(axis)
}
};
let len = size;
cursor.px += len.px + self.gap;
cursor.rel += len.rel;
lens.push(len);
@@ -62,9 +71,25 @@ impl Widget for Span {
|sum, len| sum + *len,
);
// The row: this span's own box as a length of the window, read only
// where a slot depends on it -- shares divide what is left of it,
// and a negative row counts from its end. Reading it pins the
// drawing to this length; a positive row of fixed children is not
// pinned and holds for any length its children do. Its start is
// nothing's business: a slot is a length from it.
let far = (total.leftover > Weight::ZERO || self.dir.sign == Sign::Neg)
.then(|| painter.extent_len(axis));
let along = |from: Len, to: Len| match self.dir.sign {
Sign::Pos => UiSpan::new(from, to),
Sign::Neg => {
let far = far.expect("a negative row reads its length");
UiSpan::new(far - to, far - from)
}
};
// What is left for the shares to divide: the row less everything
// fixed, as a length of the frame rather than a number of pixels.
let room = far - Len::from_parts(total.rel, total.px);
// Nothing where there are no shares, and nothing reads it there.
let room = far.map_or(Len::ZERO, |far| far - Len::from_parts(total.rel, total.px));
// Whether anything is left over is a question in pixels: `rel(0.5)`
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
// itself, and answered back through the same expression, so the
@@ -100,7 +125,8 @@ impl Widget for Span {
let mut taken = Weight::ZERO;
let mut start = Len::rel_min();
let mut ortho = LayoutLen::ZERO;
for ((child, &len), &across_len) in self.children.iter().zip(&lens).zip(&drawn_across) {
for ((child, len), measured) in self.children.iter().zip(&lens).zip(&measured) {
let len = *len;
// A child asking for nothing but a part of what is left over,
// when nothing is, is not drawn at all. One that also asked for
// pixels or a fraction keeps those and overflows.
@@ -108,7 +134,6 @@ impl Widget for Span {
{
painter.undraw(child);
fixed.px += self.gap;
start = shared(fixed, taken, total.leftover, room);
continue;
}
let from = start;
@@ -131,10 +156,10 @@ impl Widget for Span {
if len.leftover > Weight::ZERO && shares {
narrow[axis as usize] = Some(slot.len());
}
let used = match (across_len, narrow[axis as usize]) {
(Some(across_len), None) => {
let used = match (measured, narrow[axis as usize]) {
(Some(size), None) => {
painter.place_at(child, place);
across_len
size.axis(!axis)
}
_ => painter.widget_at(child, narrow, place).len(!axis),
};
-37
View File
@@ -821,40 +821,3 @@ fn a_root_with_a_fraction_rule_is_that_fraction_of_the_window() {
h.set_root(root);
assert_eq!(h.region(&root).unwrap().size().x, Px::from_int(450));
}
#[test]
fn a_collapsed_share_keeps_the_gaps_before_the_next_slot() {
for dir in [Dir::RIGHT, Dir::LEFT, Dir::DOWN, Dir::UP] {
for collapsed in [1, 2] {
let mut h = Harness::new((400, 400));
let head = rect(Color::RED).add(&mut h.rsc);
h.set_len(head, dir.axis, 200);
let tail = rect(Color::BLUE).add(&mut h.rsc);
let tail_len = 200 - 10 * (collapsed + 1);
h.set_len(tail, dir.axis, tail_len);
let mut children: Vec<StrongWidget> = vec![head.add_strong(&mut h.rsc)];
let mut shares = Vec::new();
for _ in 0..collapsed {
let share = rect(Color::GREEN).add(&mut h.rsc);
shares.push(share);
children.push(share.add_strong(&mut h.rsc));
}
children.push(tail.add_strong(&mut h.rsc));
h.set_root(Span {
children,
dir,
gap: Px::from_int(10),
});
for share in shares {
assert!(h.region(&share).is_none());
}
let region = h.region(&tail).unwrap();
let (from, to) = match dir.sign {
Sign::Pos => (400 - tail_len, 400),
Sign::Neg => (0, tail_len),
};
assert_eq!(region.top_left.axis(dir.axis), Px::from_int(from));
assert_eq!(region.bot_right.axis(dir.axis), Px::from_int(to));
}
}
}
-151
View File
@@ -1342,154 +1342,3 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
}
}
}
struct OptionalMask {
inner: StrongWidget,
enabled: bool,
}
impl Widget for OptionalMask {
fn draw(&mut self, painter: &mut Painter) -> Size {
if self.enabled {
painter.set_mask(UiRegion::FULL);
}
painter.widget(&self.inner);
Size::LEFTOVER
}
}
fn primitive_masks(h: &Harness, id: WidgetId) -> Vec<MaskIdx> {
h.render.active[&id]
.primitives
.iter()
.map(|primitive| {
let handle = &primitive.handle;
h.render.layers[handle.layer].primitives()[handle.kind as usize]
.as_ref()
.unwrap()
.instances()[handle.inst_idx]
.mask_idx
})
.collect()
}
#[test]
fn a_redrawn_mask_keeps_reused_primitives_clipped_when_it_moves() {
for node in [false, true] {
let mut h = Harness::new((400, 200));
let first = rect(Color::RED).height(50).add(&mut h.rsc);
let inner = rect(Color::BLUE).add(&mut h.rsc);
let draws = Rc::new(Cell::new(0));
let child = Stretchy {
inner: inner.add_strong(&mut h.rsc),
draws: draws.clone(),
}
.add(&mut h.rsc);
let masked = child.masked().add(&mut h.rsc);
h.rsc.widgets_mut().set_region_node(masked, node);
h.set_root((first, masked).span(Dir::DOWN));
let mask = h.render.active[&masked.id()].mask;
let settled = draws.get();
h.rsc.widgets_mut().get_dyn_mut(masked.id());
h.frame();
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
assert_eq!(draws.get(), settled, "a mask repaint must reuse its child");
assert_eq!(h.render.active[&masked.id()].mask, mask);
h.set_len(first, Axis::Y, 10);
h.frame();
let clip = h.rsc.ui().masks[mask.idx()];
let clip = h
.render
.moves
.resolve(clip.move_idx, clip.region)
.to_px(h.render.output_size());
assert_eq!(clip, h.region(&masked).unwrap());
assert_corners!(h, inner, (0, 10), (400, 200));
}
}
#[test]
fn adding_and_removing_a_mask_updates_existing_primitives() {
let mut h = Harness::new((400, 200));
let inner = rect(Color::BLUE).add(&mut h.rsc);
let masked = OptionalMask {
inner: inner.add_strong(&mut h.rsc),
enabled: false,
}
.add(&mut h.rsc);
h.set_root(masked);
for enabled in [true, false, true, false] {
h.rsc[masked].enabled = enabled;
h.frame();
let mask = h.render.active[&masked.id()].mask;
assert_eq!(mask == MaskIdx::NONE, !enabled);
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
}
assert_eq!(h.rsc.ui().masks.len(), 1, "retired slots must be reusable");
}
#[test]
fn an_empty_masks_slot_is_released_when_the_mask_is_removed_or_undrawn() {
let mut h = Harness::new((400, 200));
let (inner, _) = counted(&mut h, Size::LEFTOVER, false);
let masked = OptionalMask {
inner: inner.add_strong(&mut h.rsc),
enabled: true,
}
.add(&mut h.rsc);
let row = (masked,).span(Dir::DOWN).add(&mut h.rsc);
h.set_root(row);
for _ in 0..3 {
h.rsc[masked].enabled = false;
h.frame();
h.rsc[masked].enabled = true;
h.frame();
let child = h.rsc[row].pop().unwrap();
h.frame();
h.rsc[row].push(child);
h.frame();
}
assert_eq!(h.rsc.ui().masks.len(), 1);
}
struct SharedChild(Rc<StrongWidget>);
impl Widget for SharedChild {
fn draw(&mut self, painter: &mut Painter) -> Size {
painter.widget(self.0.as_ref()).size()
}
}
struct SwitchParent {
choices: [StrongWidget; 2],
choice: usize,
}
impl Widget for SwitchParent {
fn draw(&mut self, painter: &mut Painter) -> Size {
painter.widget(&self.choices[self.choice]).size()
}
}
#[test]
fn a_redrawn_subtree_is_not_undrawn_by_the_parent_it_left() {
for node in [false, true] {
let mut h = Harness::new((400, 200));
let leaf = rect(Color::RED).width(40).add(&mut h.rsc);
let held: StrongWidget = leaf.add_strong(&mut h.rsc);
let shared = Rc::new(held);
let first = SharedChild(shared.clone()).add_strong(&mut h.rsc);
let second = SharedChild(shared).add_strong(&mut h.rsc);
h.rsc.widgets_mut().set_region_node(&second, node);
let root = SwitchParent {
choices: [first, second],
choice: 0,
}
.add(&mut h.rsc);
h.set_root(root);
let before = h.region(&leaf);
h.rsc[root].choice = 1;
h.frame();
assert_eq!(h.region(&leaf), before);
}
}
+4 -8
View File
@@ -14,7 +14,7 @@
#[path = "scenario/mod.rs"]
mod scenario;
use iris::random::{Edits, Plan, plan};
use iris::random::{Edits, plan};
use scenario::{ALL, Case, diverges, env, over_seeds};
/// How deep the generator branches. The generator widens two to four ways per
@@ -32,11 +32,8 @@ fn depth() -> usize {
const SEEDS: [u64; 10] = [1, 2, 3, 5, 8, 10, 13, 20, 86, 98];
fn check(seed: u64, depth: usize, case: Case) {
check_plan(&plan(seed, depth, &Edits::default()), seed, depth, case);
}
fn check_plan(grown: &Plan, seed: u64, depth: usize, case: Case) {
if let Some(how) = diverges(grown, case, seed) {
let grown = plan(seed, depth, &Edits::default());
if let Some(how) = diverges(&grown, case, seed) {
panic!(
"seed {seed} at depth {depth} differs after {}: {how}\n\
reduce it with SHRINK_SEED={seed} SHRINK_DEPTH={depth} \
@@ -122,9 +119,8 @@ fn a_long_run_of_seeds_agrees() {
None => (1..=env("IRIS_GENERATED_SEEDS", 100_u64)).collect(),
};
over_seeds(seeds, |seed| {
let grown = plan(seed, depth, &Edits::default());
for case in ALL {
check_plan(&grown, seed, depth, case);
check(seed, depth, case);
}
});
}
+6 -6
View File
@@ -444,6 +444,12 @@ pub fn diverges(plan: &Plan, case: Case, seed: u64) -> Option<String> {
cold.state.root = Some(root);
cold.frame();
let places: HashMap<WidgetId, usize> = tree
.ids
.iter()
.enumerate()
.map(|(i, &id)| (id, i))
.collect();
let mut drawn = 0;
for (i, (&w, &c)) in tree.ids.iter().zip(&cold_tree.ids).enumerate() {
let (got, want) = (warm.region(&w), cold.region(&c));
@@ -451,12 +457,6 @@ pub fn diverges(plan: &Plan, case: Case, seed: u64) -> Option<String> {
if got == want {
continue;
}
let places: HashMap<WidgetId, usize> = tree
.ids
.iter()
.enumerate()
.map(|(i, &id)| (id, i))
.collect();
// Where two trees disagree is rarely where the cause is, so the
// ancestry comes with it, marking the widgets that own a region.
let mut chain = Vec::new();