Replace placement calls with region nodes
This commit is contained in:
1 parent
f437495309
commit
71c9c39523
23 files changed
+374
-170
No files matched your search
+2
-3
@@ -1,6 +1,5 @@
|
||||
//! What the vertex shader's move-chain walk costs, against how deep the chain
|
||||
//! is. Every active widget owns a slot, so the depth a primitive resolves
|
||||
//! through is its depth in the widget tree.
|
||||
//! What the vertex shader's move-chain walk costs, against how many nested
|
||||
//! region nodes a primitive resolves through.
|
||||
//!
|
||||
//! cargo test --release --test chain_cost -- --ignored --nocapture
|
||||
//!
|
||||
|
||||
@@ -23,14 +23,14 @@ impl Widget for BranchesOnMeasurement {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let mut top = UiRegion::FULL;
|
||||
top.y.end = top.y.start.offset(40.0);
|
||||
let measured = painter.place(&self.probe, top).len(Axis::X);
|
||||
let measured = painter.widget_within(&self.probe, top).len(Axis::X);
|
||||
let px = measured.apply_leftover().to_px(painter.px_len(Axis::X));
|
||||
|
||||
let mut below = UiRegion::FULL;
|
||||
below.y.start = below.y.start.offset(40.0);
|
||||
match px > self.threshold {
|
||||
true => painter.place(&self.wide, below),
|
||||
false => painter.place(&self.narrow, below),
|
||||
true => painter.widget_within(&self.wide, below),
|
||||
false => painter.widget_within(&self.narrow, below),
|
||||
};
|
||||
Size::LEFTOVER
|
||||
}
|
||||
|
||||
+8
-9
@@ -1,10 +1,9 @@
|
||||
//! Random trees, checked against building the same tree cold.
|
||||
//!
|
||||
//! A frame reaches its layout by keeping most of the last one: slots
|
||||
//! rewritten, some widgets drawn again, the rest untouched. The property here
|
||||
//! is that what comes out is the tree a cold start would have produced, so
|
||||
//! anything the retained path carried over that it should not have shows up
|
||||
//! as a difference in somebody's box.
|
||||
//! A frame reaches its layout by keeping most of the last one: movable regions
|
||||
//! or primitive boxes rewritten, some widgets drawn again, the rest untouched.
|
||||
//! The result must be the tree a cold start would have produced, so anything
|
||||
//! wrongly retained shows up as a difference in somebody's box.
|
||||
//!
|
||||
//! `iris::random` grows the tree and `examples/random.rs` draws one. A seed is
|
||||
//! the whole reproduction; `a_long_run_of_seeds_agrees` is the ignored sweep
|
||||
@@ -220,8 +219,8 @@ fn describe(id: WidgetId, h: &Harness) -> String {
|
||||
}
|
||||
|
||||
/// Every widget in one tree against the matching widget in the other. A
|
||||
/// mismatch prints the widget's ancestry, marking the ones that own a slot,
|
||||
/// since where two trees disagree is rarely where the cause is.
|
||||
/// mismatch prints the widget's ancestry, marking region nodes, since where
|
||||
/// two trees disagree is rarely where the cause is.
|
||||
fn assert_same(seed: u64, what: &str, warm: (&Harness, &Tree), cold: (&Harness, &Tree)) {
|
||||
let ((wh, wt), (ch, ct)) = (warm, cold);
|
||||
assert_eq!(wt.ids.len(), ct.ids.len(), "seed {seed}: different trees");
|
||||
@@ -243,11 +242,11 @@ fn assert_same(seed: u64, what: &str, warm: (&Harness, &Tree), cold: (&Harness,
|
||||
let mut at = Some(w);
|
||||
while let Some(id) = at {
|
||||
let active = &wh.render.active[&id];
|
||||
let slot = match active.move_idx == active.parent_move {
|
||||
let node = match active.move_idx == active.parent_move {
|
||||
true => "",
|
||||
false => "*",
|
||||
};
|
||||
chain.push(format!("{}{slot}", describe(id, wh)));
|
||||
chain.push(format!("{}{node}", describe(id, wh)));
|
||||
at = active.parent;
|
||||
}
|
||||
println!(
|
||||
|
||||
+37
-11
@@ -119,8 +119,8 @@ fn a_resize_lands_where_a_cold_start_would() {
|
||||
#[test]
|
||||
fn a_fixed_box_is_drawn_again_rather_than_stretched() {
|
||||
let mut h = Harness::new((400, 400));
|
||||
// The panel fills a stack sized by its sibling, so it is drawn in the
|
||||
// whole box and then placed in the shorter one. Reusing it in that fixed
|
||||
// The panel fills a stack sized by its sibling, so it is first asked in
|
||||
// the whole box and then given the shorter one. Reusing it in that fixed
|
||||
// box afterwards would leave it whatever height it happened to have.
|
||||
let panel = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let leaf = rect(Color::RED).height(100).add(&mut h.rsc);
|
||||
@@ -142,15 +142,15 @@ fn a_moved_subtree_takes_its_children_with_it() {
|
||||
let mut h = Harness::new((400, 400));
|
||||
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
||||
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let row = inner.pad(10).height(40).add(&mut h.rsc);
|
||||
let row = inner.pad(10).height(40).region_node().add(&mut h.rsc);
|
||||
h.set_root((first, row).span(Dir::DOWN));
|
||||
assert_corners!(h, inner, (10, 50), (390, 70));
|
||||
|
||||
h.rsc[first].y = Some(Len::px(80));
|
||||
h.frame();
|
||||
|
||||
// The row is the same shape somewhere else, so one slot moved it and
|
||||
// `inner`'s own region was never rewritten.
|
||||
// The row opted into one movable region, so its descendants follow one
|
||||
// entry rather than having their primitive regions rewritten.
|
||||
assert_corners!(h, inner, (10, 90), (390, 110));
|
||||
}
|
||||
|
||||
@@ -202,21 +202,29 @@ fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_a_container_that_places_its_children_lengthens_the_chain() {
|
||||
fn only_a_region_node_lengthens_the_chain_and_it_can_be_removed() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let leaf = rect(Color::BLUE).add(&mut h.rsc);
|
||||
// Four widgets between the span and the leaf, none of which places what
|
||||
// it draws, so all of them share the span's slot.
|
||||
let buried = leaf.pad(4).pad(4).pad(4).pad(4).add(&mut h.rsc);
|
||||
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||
h.set_root((bar, buried).span(Dir::RIGHT));
|
||||
|
||||
let slot = h.render.active[&leaf.id()].parent_move;
|
||||
let move_idx = h.render.active[&leaf.id()].parent_move;
|
||||
assert_eq!(h.render.moves.depth(move_idx), 1, "only the root region");
|
||||
|
||||
h.rsc.widgets_mut().set_region_node(buried, true);
|
||||
h.frame();
|
||||
let move_idx = h.render.active[&leaf.id()].parent_move;
|
||||
assert_eq!(
|
||||
h.render.moves.depth(slot),
|
||||
h.render.moves.depth(move_idx),
|
||||
2,
|
||||
"the span above the leaf, and the root the window is held in"
|
||||
"the opted-in widget's region and the root region"
|
||||
);
|
||||
|
||||
h.rsc.widgets_mut().set_region_node(buried, false);
|
||||
h.frame();
|
||||
let move_idx = h.render.active[&leaf.id()].parent_move;
|
||||
assert_eq!(h.render.moves.depth(move_idx), 1);
|
||||
}
|
||||
|
||||
/// A span that sizes from its children passes their `leftover` weight up
|
||||
@@ -412,3 +420,21 @@ fn only_a_pure_leftover_child_disappears_when_nothing_is_left() {
|
||||
// is leftover is omitted.
|
||||
assert_corners!(h, mixed, (100, 0), (120, 20));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leftover_children_disappear_at_the_exact_fixed_content_boundary() {
|
||||
let mut h = Harness::new((100, 100));
|
||||
let first = rect(Color::RED).height(90).add(&mut h.rsc);
|
||||
let a = rect(Color::GREEN).add(&mut h.rsc);
|
||||
let b = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let inner = (a, b).span(Dir::DOWN).gap(4).add(&mut h.rsc);
|
||||
h.set_root((first, inner).span(Dir::DOWN));
|
||||
assert!(h.region(&a).is_some());
|
||||
assert!(h.region(&b).is_some());
|
||||
|
||||
h.rsc[first].y = Some(Len::px(96.0));
|
||||
h.frame();
|
||||
|
||||
assert!(h.region(&a).is_none());
|
||||
assert!(h.region(&b).is_none());
|
||||
}
|
||||
@@ -30,7 +30,7 @@ fn a_selected_widget_retains_its_layout_events() {
|
||||
diagnostics::clear_traced_widgets();
|
||||
let _ = diagnostics::take();
|
||||
let mut harness = Harness::new((400, 200));
|
||||
let leaf = rect(Color::RED).add(&mut harness.rsc);
|
||||
let leaf = rect(Color::RED).region_node().add(&mut harness.rsc);
|
||||
let other = rect(Color::BLUE).add(&mut harness.rsc);
|
||||
let root = (leaf, other).span(Dir::RIGHT).add(&mut harness.rsc);
|
||||
harness.set_root(root);
|
||||
@@ -46,7 +46,7 @@ fn a_selected_widget_retains_its_layout_events() {
|
||||
report
|
||||
.traces()
|
||||
.iter()
|
||||
.any(|event| matches!(event, TraceEvent::Placed { id, .. } if *id == leaf.id()))
|
||||
.any(|event| matches!(event, TraceEvent::RegionNode { id, .. } if *id == leaf.id()))
|
||||
);
|
||||
assert!(
|
||||
report
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! What re-placing a subtree costs per frame, as a load for a counter rather
|
||||
//! What remapping a subtree costs per frame, as a load for a counter rather
|
||||
//! than a check. A span of 200 fixed-height rows, five primitives each, with
|
||||
//! the row above them changing height every frame, so every row below is
|
||||
//! offered a box the same shape somewhere else.
|
||||
@@ -6,9 +6,7 @@
|
||||
//! cargo test --release --test replace_cost -- --ignored
|
||||
//! perf stat -e instructions:u target/release/.../replace_cost-* --ignored
|
||||
//!
|
||||
//! Wall time is the wrong number here; see `draw_cost.rs`. Measured on
|
||||
//! 2026-09-14 at 1.98M instructions per frame, against 2.38M for rewriting
|
||||
//! each row's regions instead and 7.13M for redrawing them.
|
||||
//! Wall time is the wrong number here; see `draw_cost.rs`.
|
||||
|
||||
use iris::harness::Harness;
|
||||
use iris::prelude::*;
|
||||
@@ -18,7 +16,7 @@ const FRAMES: usize = 200;
|
||||
|
||||
#[test]
|
||||
#[ignore = "measurement, not a check"]
|
||||
fn replacing_rows_every_frame() {
|
||||
fn remapping_rows_every_frame() {
|
||||
let mut h = Harness::new((1920, 1200));
|
||||
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
||||
let mut span = Span::empty(Dir::DOWN);
|
||||
|
||||
+28
-6
@@ -111,6 +111,28 @@ fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
||||
assert_corners!(h, second, (150, 0), (400, 200));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_an_ordinary_subtree_remaps_its_mask() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let (first, _) = counted(&mut h, Size::from((100, 200)), false);
|
||||
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let masked = inner.masked().add(&mut h.rsc);
|
||||
h.set_root((first, masked).span(Dir::RIGHT));
|
||||
|
||||
h.rsc[first].size = Size::from((150, 200));
|
||||
h.frame();
|
||||
|
||||
let active = &h.render.active[&masked.id()];
|
||||
assert_eq!(
|
||||
h.rsc.ui().masks[active.mask.idx()].region,
|
||||
UiRegion::new(
|
||||
UiSpan::new(UiScalar::px(150.0), UiScalar::rel_max()),
|
||||
UiSpan::FULL,
|
||||
)
|
||||
);
|
||||
assert_corners!(h, inner, (150, 0), (400, 200));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
@@ -121,7 +143,7 @@ fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
||||
h.frame();
|
||||
|
||||
// The preceding fixed child makes the remaining box this child's real
|
||||
// box, so measuring it also draws it in its final place.
|
||||
// box, so measuring it also draws it in its final box.
|
||||
assert_eq!(draws.get(), settled + 1);
|
||||
assert_corners!(h, second, (150, 0), (400, 200));
|
||||
}
|
||||
@@ -132,7 +154,7 @@ fn a_span_child_that_declares_its_length_is_drawn_once() {
|
||||
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), false);
|
||||
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), true);
|
||||
// The span takes one child's length from its hint and has to draw the
|
||||
// other to find out, so only the second is drawn before it is placed.
|
||||
// other to find out, so only the second is drawn before its final box.
|
||||
let hinted = told.width(100).add(&mut h.rsc);
|
||||
h.set_root((hinted, asked).span(Dir::RIGHT));
|
||||
|
||||
@@ -140,7 +162,7 @@ fn a_span_child_that_declares_its_length_is_drawn_once() {
|
||||
assert_eq!(
|
||||
asked_draws.get(),
|
||||
2,
|
||||
"drawn to be measured, then again to be placed"
|
||||
"drawn to be measured, then again in its final box"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,10 +195,10 @@ fn a_repaint_that_keeps_its_size_does_not_relay_out() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_placed_child_survives_the_next_frame() {
|
||||
fn a_span_child_survives_the_next_frame() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
// Both children declare a length, so the span places them from their hints
|
||||
// rather than drawing them to find out.
|
||||
// Both children declare a length, so the span chooses their boxes from
|
||||
// hints rather than drawing them to find out.
|
||||
let top = rect(Color::RED).height(80).add(&mut h.rsc);
|
||||
let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc);
|
||||
h.set_root((top, bottom).span(Dir::DOWN));
|
||||
|
||||
@@ -3,6 +3,41 @@
|
||||
use iris::harness::{Harness, assert_corners};
|
||||
use iris::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn scrollable_enables_a_region_node_but_raw_scroll_does_not() {
|
||||
let mut h = Harness::new((100, 100));
|
||||
let default_child = ().add(&mut h.rsc);
|
||||
let _default = default_child.scrollable().add(&mut h.rsc);
|
||||
assert!(h.rsc.widgets().is_region_node(default_child));
|
||||
h.rsc.widgets_mut().set_region_node(default_child, false);
|
||||
assert!(!h.rsc.widgets().is_region_node(default_child));
|
||||
|
||||
let raw_child = ().add(&mut h.rsc);
|
||||
let _raw = Scroll::new(raw_child.add_strong(&mut h.rsc), Axis::Y).add(&mut h.rsc);
|
||||
assert!(!h.rsc.widgets().is_region_node(raw_child));
|
||||
|
||||
let explicit = ().region_node().add(&mut h.rsc);
|
||||
assert!(h.rsc.widgets().is_region_node(explicit));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_scrollable_child_can_drop_its_region_node() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let top = rect(Color::RED).height(200).add(&mut h.rsc);
|
||||
let bottom = rect(Color::BLUE).height(200).add(&mut h.rsc);
|
||||
let content = (top, bottom).span(Dir::DOWN).add(&mut h.rsc);
|
||||
h.set_root(content.scrollable());
|
||||
h.rsc.widgets_mut().set_region_node(content, false);
|
||||
h.frame();
|
||||
|
||||
h.move_to((200, 100));
|
||||
h.scroll((0, 1));
|
||||
h.frame();
|
||||
|
||||
assert!(!h.rsc.widgets().is_region_node(content));
|
||||
assert_corners!(h, top, (0, -150), (400, 50));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_wheel_scrolls_the_content_and_stops_at_its_end() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
|
||||
@@ -62,8 +62,8 @@ fn dump(label: &str, report: &diag::Report, text: WidgetId) {
|
||||
TraceEvent::SizeRead { id, reader, size } if *id == text => {
|
||||
println!(" size read by {reader:?}: {size}")
|
||||
}
|
||||
TraceEvent::Placed { id, parent, region } if *id == text => {
|
||||
println!(" placed by {parent:?} at {region:?}")
|
||||
TraceEvent::RegionNode { id, parent, region } if *id == text => {
|
||||
println!(" region node under {parent:?} at {region:?}")
|
||||
}
|
||||
TraceEvent::Reuse { id, outcome } if *id == text => println!(" reuse: {outcome:?}"),
|
||||
_ => {}
|
||||
|
||||
+3
-3
@@ -216,8 +216,8 @@ fn swapping_two_children_lands_where_growing_them_that_way_does() {
|
||||
|
||||
/// Eight widgets, shrunk from 80. The scroll decides how wide to make its
|
||||
/// content from what the content says, and hands that box down through a
|
||||
/// pass-through; the span under it was placed once, in that box, so nothing
|
||||
/// at its own edge says the box was its own answer.
|
||||
/// pass-through; the span under it was given that box once, so nothing at its
|
||||
/// own edge says the box was its own answer.
|
||||
fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget<Span>; 2]) {
|
||||
let words = "Wrapping shapes one source into as many lines as the box leaves room for,";
|
||||
let text = wtext(words).size(16).wrap(true).add(&mut h.rsc);
|
||||
@@ -277,7 +277,7 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_span_placed_once_in_a_box_its_answer_decided() {
|
||||
fn a_span_given_the_box_its_answer_decided_matches_a_cold_layout() {
|
||||
let mut warm = Harness::new((640, 900));
|
||||
let (ids, spans) = plant_scrolled(&mut warm, false);
|
||||
warm.frame();
|
||||
|
||||
Reference in new issue
Block a user