Delete OrthoSize, and run the seeds in parallel
A span is as long across itself as its longest child, unless a rule beside it already says how long it is -- and then reading the children answers nothing and only makes its size depend on theirs. `OrthoSize::Full` was that second case written twice, once as an enum on the span and once as the rule that actually decides; `Painter::ruled` lets the span ask which it is in. The widget under a rule still does not learn what the rule says, only that its answer for that axis is not wanted. The fuzzers grow, lay out and drop a tree within one seed, so the seeds share nothing and take a thread each, one short of every core. Measured here: the generated oracle's hundred seeds went from 68 s to 10 s, and a shrinker case at 300 seeds from 18 s to 3.5 s. A seed that fails still shrinks and panics on its own thread, and `std::thread::scope` carries that out. The shrinker now allows the two steps the oracle already did -- the deeper trees these grow reach a second composition, and a step is a thousandth of a pixel. Checked: fmt, clippy, 102 tests, all five shrinker cases at 300 seeds, 100 generated seeds, and five examples byte-identical at 1920x1200. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cb955f1023
commit
9d8415d65f
8 files changed
+94
-74
No files matched your search
+5
-14
@@ -21,27 +21,21 @@ fn a_span_gives_each_child_the_width_it_asked_for() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_full_ortho_span_reports_relative_full() {
|
||||
fn a_span_ruled_across_itself_does_not_measure_its_children_there() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let child = rect(Color::RED).height(40).add(&mut h.rsc);
|
||||
let span = (child,)
|
||||
.span(Dir::RIGHT)
|
||||
.ortho(OrthoSize::Full)
|
||||
.add(&mut h.rsc);
|
||||
let span = (child,).span(Dir::RIGHT).height(rel(1.0)).add(&mut h.rsc);
|
||||
h.set_root(span);
|
||||
|
||||
assert_eq!(h.render.active[&span.id()].size.y, Len::rel(1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_children_ortho_span_reports_its_tallest_fixed_child() {
|
||||
fn a_span_reports_its_tallest_fixed_child() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let short = rect(Color::RED).height(40).add(&mut h.rsc);
|
||||
let tall = rect(Color::BLUE).height(70).add(&mut h.rsc);
|
||||
let span = (short, tall)
|
||||
.span(Dir::RIGHT)
|
||||
.ortho(OrthoSize::Children)
|
||||
.add(&mut h.rsc);
|
||||
let span = (short, tall).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root(span);
|
||||
|
||||
assert_eq!(h.render.active[&span.id()].size.y, Len::px(70.0));
|
||||
@@ -211,10 +205,7 @@ fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() {
|
||||
let filler = rect(Color::GREEN).add(&mut h.rsc);
|
||||
// This column is an item in a row, so it takes the width left for it
|
||||
// rather than asking for a full row-width in addition to the bar.
|
||||
let column = (row, filler)
|
||||
.span(Dir::DOWN)
|
||||
.ortho(OrthoSize::Children)
|
||||
.add(&mut h.rsc);
|
||||
let column = (row, filler).span(Dir::DOWN).add(&mut h.rsc);
|
||||
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||
h.set_root((bar, column).span(Dir::RIGHT));
|
||||
assert_corners!(h, inner, (110, 10), (390, 30));
|
||||
|
||||
@@ -300,13 +300,10 @@ fn a_resize_does_not_redraw_what_the_shader_can_move() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_full_ortho_span_moves_its_child_without_redrawing_it() {
|
||||
fn a_span_ruled_across_itself_moves_its_child_without_redrawing_it() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, false);
|
||||
let span = (leaf,)
|
||||
.span(Dir::RIGHT)
|
||||
.ortho(OrthoSize::Full)
|
||||
.add(&mut h.rsc);
|
||||
let span = (leaf,).span(Dir::RIGHT).height(rel(1.0)).add(&mut h.rsc);
|
||||
h.set_root(span);
|
||||
let settled = draws.get();
|
||||
|
||||
|
||||
@@ -160,7 +160,6 @@ fn plant_pair(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, WeakWidget<Span
|
||||
children,
|
||||
dir: Dir::RIGHT,
|
||||
gap: Px::ZERO,
|
||||
ortho: OrthoSize::Children,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let span_handle = span;
|
||||
@@ -214,7 +213,6 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
||||
children: inner_children,
|
||||
dir: Dir::RIGHT,
|
||||
gap: Px::ZERO,
|
||||
ortho: OrthoSize::Children,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let block = rect(Color::RED).add(&mut h.rsc);
|
||||
@@ -228,7 +226,6 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
||||
children: outer_children,
|
||||
dir: Dir::RIGHT,
|
||||
gap: Px::ZERO,
|
||||
ortho: OrthoSize::Children,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
// Carried no rule even before rules were a property: it is here to be a
|
||||
@@ -337,7 +334,6 @@ fn plant_boundary(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
||||
children: pair,
|
||||
dir: Dir::DOWN,
|
||||
gap: Px::ZERO,
|
||||
ortho: OrthoSize::Children,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
// Takes the whole box on its own, so the span above has nothing left to
|
||||
@@ -357,7 +353,6 @@ fn plant_boundary(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
||||
children: inner_children,
|
||||
dir: Dir::DOWN,
|
||||
gap: Px::ZERO,
|
||||
ortho: OrthoSize::Children,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
h.rsc
|
||||
|
||||
+21
-4
@@ -216,10 +216,9 @@ fn describe_widget(id: WidgetId, h: &Harness) -> String {
|
||||
if let Some(w) = any.downcast_ref::<Span>() {
|
||||
let sign = if w.dir.sign == Sign::Neg { "-" } else { "+" };
|
||||
return format!(
|
||||
"Span{{dir:{:?}{sign},gap:{},ortho:{:?},n:{}}}",
|
||||
"Span{{dir:{:?}{sign},gap:{},n:{}}}",
|
||||
w.dir.axis,
|
||||
w.gap,
|
||||
w.ortho,
|
||||
w.children.len()
|
||||
);
|
||||
}
|
||||
@@ -555,7 +554,7 @@ fn a_long_run_of_seeds_agrees() {
|
||||
.and_then(|seed| seed.parse().ok())
|
||||
.map(|seed| seed..=seed)
|
||||
.unwrap_or_else(|| 1..=env("IRIS_GENERATED_SEEDS", 100));
|
||||
for seed in seeds {
|
||||
over_seeds(seeds.collect(), |seed| {
|
||||
changed_size(seed);
|
||||
changed_every_size(seed);
|
||||
repainted_together(seed);
|
||||
@@ -564,5 +563,23 @@ fn a_long_run_of_seeds_agrees() {
|
||||
for shuffle in SHUFFLES {
|
||||
reshuffled(seed, shuffle);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Every seed on its own thread's share of them. A tree is grown, laid out
|
||||
/// and dropped inside one call, so seeds share nothing, and this is most of
|
||||
/// the time a run takes. A thread that fails takes the scope down with it,
|
||||
/// which is the same panic libtest would have seen.
|
||||
///
|
||||
/// One core short of all of them, so the machine this runs on stays usable.
|
||||
pub fn over_seeds(seeds: Vec<u64>, run: impl Fn(u64) + Sync) {
|
||||
let threads =
|
||||
std::thread::available_parallelism().map_or(1, |n| n.get().saturating_sub(1).max(1));
|
||||
let chunk = seeds.len().div_ceil(threads).max(1);
|
||||
std::thread::scope(|scope| {
|
||||
for part in seeds.chunks(chunk) {
|
||||
let run = &run;
|
||||
scope.spawn(move || part.iter().for_each(|&seed| run(seed)));
|
||||
}
|
||||
});
|
||||
}
|
||||
+35
-11
@@ -51,6 +51,10 @@ const WORDS: &[&str] = &[
|
||||
const ONE_LINE: &str = "one line, overflowing whatever it is given";
|
||||
|
||||
const OUTER: (f32, f32) = (1920.0, 1200.0);
|
||||
/// Steps of the grid two ways of reaching a box may differ by. See
|
||||
/// `docs/HANDOFF.md`'s "Fixed point" in `ai-app-2` for where the last of
|
||||
/// them is.
|
||||
const AGREE_STEPS: i32 = 2;
|
||||
const INNER: (f32, f32) = (640.0, 900.0);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -120,12 +124,15 @@ impl Node {
|
||||
children,
|
||||
dir: dir(*down),
|
||||
gap: Px::from_f32(*gap),
|
||||
ortho: match down {
|
||||
true => OrthoSize::Children,
|
||||
false => OrthoSize::Full,
|
||||
},
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
// A row takes the height it is given; a column is as wide
|
||||
// as its widest child, which needs no rule.
|
||||
if !*down {
|
||||
h.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rules(handle, None, Some(Len::rel(1.0)));
|
||||
}
|
||||
spans.push(handle);
|
||||
handle.add_strong(&mut h.rsc)
|
||||
}
|
||||
@@ -515,12 +522,13 @@ fn diverges(node: &Node, case: Case) -> Option<String> {
|
||||
|
||||
for (i, (&w, &c)) in warm_ids.iter().zip(&cold_ids).enumerate() {
|
||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
||||
// To one step of the grid. A move or a resize lands on the same
|
||||
// number now; a length measured one way and composed another can
|
||||
// still be a step apart.
|
||||
// To a couple of steps of the grid, each a thousandth of a pixel: a
|
||||
// move or a resize lands on the same number now, and a length
|
||||
// measured one way against the same length composed another can
|
||||
// still be a step out per composition between them.
|
||||
let same = match (got, want) {
|
||||
(Some(g), Some(c)) => {
|
||||
let d = |a: Px, b: Px| (a - b).abs() <= Px::STEP;
|
||||
let d = |a: Px, b: Px| (a - b).abs() <= Px::STEP.mul_int(AGREE_STEPS);
|
||||
d(g.top_left.x, c.top_left.x)
|
||||
&& d(g.top_left.y, c.top_left.y)
|
||||
&& d(g.bot_right.x, c.bot_right.x)
|
||||
@@ -550,6 +558,22 @@ fn shrink(mut node: Node, case: Case) -> Node {
|
||||
}
|
||||
}
|
||||
|
||||
/// One thread per core but one, each taking a share of the seeds: a tree is
|
||||
/// grown, laid out and dropped within a seed, so nothing is shared. A seed
|
||||
/// that fails shrinks on its own thread and panics there, which brings the
|
||||
/// scope down with it.
|
||||
fn over_seeds(seeds: Vec<u64>, run: impl Fn(u64) + Sync) {
|
||||
let threads =
|
||||
std::thread::available_parallelism().map_or(1, |n| n.get().saturating_sub(1).max(1));
|
||||
let chunk = seeds.len().div_ceil(threads).max(1);
|
||||
std::thread::scope(|scope| {
|
||||
for part in seeds.chunks(chunk) {
|
||||
let run = &run;
|
||||
scope.spawn(move || part.iter().for_each(|&seed| run(seed)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn env<T: std::str::FromStr>(name: &str, fallback: T) -> T {
|
||||
std::env::var(name)
|
||||
.ok()
|
||||
@@ -570,10 +594,10 @@ fn no_grown_tree_lays_out_differently_warm_than_cold() {
|
||||
_ => Case::Resize,
|
||||
};
|
||||
|
||||
for seed in 1..=seeds {
|
||||
over_seeds((1..=seeds).collect(), |seed| {
|
||||
let node = grow(&mut Rng::new(seed), depth);
|
||||
let Some(how) = diverges(&node, case) else {
|
||||
continue;
|
||||
return;
|
||||
};
|
||||
let small = shrink(node.clone(), case);
|
||||
println!(
|
||||
@@ -582,7 +606,7 @@ fn no_grown_tree_lays_out_differently_warm_than_cold() {
|
||||
small.size()
|
||||
);
|
||||
panic!("seed {seed} lays out differently warm than cold");
|
||||
}
|
||||
});
|
||||
let sizes: Vec<usize> = (1..=seeds)
|
||||
.map(|seed| grow(&mut Rng::new(seed), depth).size())
|
||||
.collect();
|
||||
|
||||
Reference in new issue
Block a user