Describe a tree before building it, so a failing seed can be reduced
The oracle grew its trees from a seed and the shrinker grew its own, with every scenario written out on each side. So a failure the oracle found could not be handed to the shrinker: there was no tree to pass it, only a seed, and a seed cannot be made smaller. The shrinker could only grow its own trees and hope to meet the same shape, which it does not -- 20,000 of its trees never reproduced what the oracle's seed 18 shows at depth 6. `iris::random` now answers with a `Plan`: `plan(seed, depth, &edits)` draws one out of the random stream and `build(rsc, &plan)` makes the widgets, where `grow` did both at once. Every draw happens in the order it always has, so a seed still means the tree it meant -- checked by running the oracle at 1000 seeds of depth 6 before and after and getting the same three failures with the same boxes. `Plan::smaller` reduces one, `Plan::edited` applies an `Edits` to a tree that already exists, and `tests/scenario/` holds the fifteen cases both rigs now run over the same trees. A span keeps the order it holds its children in apart from the children themselves, so detaching, attaching and reordering leave the widgets made in the same order and two builds still line up index for index. `Tree::detached` is gone: `Spanned::spares` is everything made for a span that it does not hold, which is what both of those were. `tests/cases/plan.rs` pins the three properties the rest rests on: editing a plan is growing one with those edits, every simplification is smaller than what it came from, and reducing ends. The second caught this change's own defect, where dropping a side of a `Branch` duplicated another and grew the tree by four widgets. What it found, on its first run: `SHRINK_SEED=18 SHRINK_DEPTH=6 SHRINK_CASE=repaint-some` reduces 277 widgets to 5. A scroll inside a scroll, the inner one owning a movable region, and only the text at the bottom marked for redraw -- and the span lands 24px out, which is exactly the sized child's height. `git bisect` names `95fb4f9`, where `Masked` began reporting its box rather than its inner's size, so what the outer scroll is told its content measures now depends on whether the inner subtree was redrawn this frame. `tests/cases/unsettled.rs` has it written out, ignored until it is fixed. Checked: fmt, clippy over all targets with -D warnings, the workspace tests (79 + 11 + 15, one ignored for the defect above), and the 100-seed oracle over all fifteen cases at depth 4. The shrinker at 400 seeds of depth 5 now fails, which it did not before running the oracle's trees and cases: seeds 2 and 288 on region-node and 174 and 175 on repaint-some are unreduced leads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4febabfd2e
commit
98d4e98a29
7 files changed
+1469
-1290
No files matched your search
+678
-157
@@ -101,10 +101,6 @@ pub struct Tree {
|
||||
pub nodes: Vec<WidgetId>,
|
||||
pub spans: Vec<Spanned>,
|
||||
pub scrolls: Vec<WeakWidget<Scroll>>,
|
||||
/// Children a `SpanEdit` took out, held so that dropping the last share
|
||||
/// of one does not free its id for the next widget to be given -- which
|
||||
/// would put the two trees' `ids` out of step.
|
||||
pub detached: Vec<StrongWidget>,
|
||||
}
|
||||
|
||||
/// Branches on a child's measured length. Comparing boxes catches a widget
|
||||
@@ -138,15 +134,463 @@ impl Widget for Branch {
|
||||
|
||||
pub struct Spanned {
|
||||
pub id: WeakWidget<Span>,
|
||||
/// Leaves grown with the span whether or not they end up in it, so both
|
||||
/// trees make the same widgets in the same order either way. Attaching
|
||||
/// one moves it out of here: a widget belongs to one parent, and one that
|
||||
/// belongs to nobody still has to be held or it reads as a leak.
|
||||
/// Everything made for this span that it does not hold -- spares never
|
||||
/// attached and children detached alike. A widget belongs to one parent,
|
||||
/// and one that belongs to nobody still has to be held here: dropping
|
||||
/// the last share of it frees its id for the next widget to be given,
|
||||
/// which puts two trees out of step.
|
||||
pub spares: Vec<StrongWidget>,
|
||||
/// How many children it was grown with, before any edit.
|
||||
pub grown: usize,
|
||||
}
|
||||
|
||||
/// A tree described rather than built: [`plan`] turns a seed into one of
|
||||
/// these and [`build`] turns it into widgets, where growing did both at once.
|
||||
///
|
||||
/// The split is what makes a counterexample readable. A failing seed used to
|
||||
/// be the entire record of one, because a grower that makes widgets as it
|
||||
/// draws leaves nothing to take apart -- a shrinker could only grow its own
|
||||
/// trees and hope to meet the same shape, which in practice it does not. A
|
||||
/// plan is reduced by [`Plan::smaller`] and built again, so any seed that
|
||||
/// fails can be cut down until what is left is small enough to read.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Plan {
|
||||
pub kind: Kind,
|
||||
/// The declared size this widget carries. Whoever grows a widget offers
|
||||
/// it one and the offer is taken or declined; a second offer to the same
|
||||
/// widget is dropped, because two rules on one widget would settle in the
|
||||
/// order they were applied rather than in grow order.
|
||||
pub size: Option<Lens>,
|
||||
/// The alignment it carries, under the same one-offer rule.
|
||||
pub align: Option<Aligns>,
|
||||
/// Whether it was offered a movable region of its own and what it
|
||||
/// answered. `Some(false)` is an offer declined, which still uses up the
|
||||
/// one offer, where `None` is an offer never made.
|
||||
pub region_node: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Kind {
|
||||
/// Wrapped and unwrapped text, because only one of them reads the width
|
||||
/// it is given and so only one has to be drawn again for a new one.
|
||||
Wrapped,
|
||||
OneLine,
|
||||
Rect {
|
||||
color: usize,
|
||||
alpha: u8,
|
||||
},
|
||||
/// Scrolling reads the pixel length of its box, which nothing else here
|
||||
/// does, and gives its child a box longer than its own.
|
||||
Scroll {
|
||||
axis: Axis,
|
||||
inner: Box<Plan>,
|
||||
},
|
||||
/// All three sides are grown either way, so a tree that draws one has the
|
||||
/// same ids as a tree that draws another.
|
||||
Branch {
|
||||
probe: Box<Plan>,
|
||||
wide: Box<Plan>,
|
||||
narrow: Box<Plan>,
|
||||
threshold: f32,
|
||||
},
|
||||
/// Each side its own, since a padding that is the same all round hides
|
||||
/// anything that treats one edge differently from another.
|
||||
Pad {
|
||||
padding: [i32; 4],
|
||||
inner: Box<Plan>,
|
||||
},
|
||||
Stack {
|
||||
children: Vec<Plan>,
|
||||
},
|
||||
Span {
|
||||
dir: usize,
|
||||
gap: i32,
|
||||
/// Grown for this span, in the order they are made.
|
||||
children: Vec<Plan>,
|
||||
/// Grown beside it whether or not they end up in it, so the widget
|
||||
/// after them has the same id in a tree that leaves them out as in
|
||||
/// one that puts them in.
|
||||
spares: Vec<Plan>,
|
||||
/// Which of `children` then `spares` are actually in the span, and
|
||||
/// in what order -- kept apart from the two lists above so that a
|
||||
/// tree which detaches, attaches or reorders its children still
|
||||
/// makes the same widgets in the same order, and two builds line up
|
||||
/// index for index. Anything not named here is built and held
|
||||
/// rather than dropped, since freeing an id hands it to the next
|
||||
/// widget and puts two trees out of step.
|
||||
order: Vec<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Plan {
|
||||
/// A widget carrying nothing anybody has offered it yet.
|
||||
fn bare(kind: Kind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
size: None,
|
||||
align: None,
|
||||
region_node: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// How many widgets building it makes, spares and detached children
|
||||
/// included, since those are made either way.
|
||||
pub fn size(&self) -> usize {
|
||||
1 + match &self.kind {
|
||||
Kind::Scroll { inner, .. } | Kind::Pad { inner, .. } => inner.size(),
|
||||
Kind::Branch {
|
||||
probe,
|
||||
wide,
|
||||
narrow,
|
||||
..
|
||||
} => probe.size() + wide.size() + narrow.size(),
|
||||
Kind::Stack { children } => children.iter().map(Plan::size).sum(),
|
||||
Kind::Span {
|
||||
children, spares, ..
|
||||
} => children.iter().chain(spares).map(Plan::size).sum(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// The trees to try instead of this one when reducing a counterexample,
|
||||
/// biggest cut first: a shrinker takes the first that still fails, so
|
||||
/// offering "this subtree alone" before "this subtree with one child
|
||||
/// fewer" is what gets from six hundred widgets to six rather than to
|
||||
/// five hundred and ninety.
|
||||
///
|
||||
/// Every one of these is a tree the generator could have grown, so a
|
||||
/// reduced plan is a counterexample in its own right rather than a
|
||||
/// special case only the shrinker can make.
|
||||
pub fn smaller(&self) -> Vec<Plan> {
|
||||
let mut out = Vec::new();
|
||||
// Standing in for the whole of it, which is the largest cut there is.
|
||||
for kid in self.kids() {
|
||||
out.push(kid.clone());
|
||||
}
|
||||
// Then what it carries, which costs nothing to put back if it was
|
||||
// not the thing that mattered.
|
||||
for dropped in [
|
||||
self.region_node.map(|_| Plan {
|
||||
region_node: None,
|
||||
..self.clone()
|
||||
}),
|
||||
self.align.map(|_| Plan {
|
||||
align: None,
|
||||
..self.clone()
|
||||
}),
|
||||
self.size.map(|_| Plan {
|
||||
size: None,
|
||||
..self.clone()
|
||||
}),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
out.push(dropped);
|
||||
}
|
||||
out.extend(self.kind.smaller().into_iter().map(|kind| Plan {
|
||||
kind,
|
||||
..self.clone()
|
||||
}));
|
||||
out
|
||||
}
|
||||
|
||||
/// Visits every widget in the order [`build`] makes them, so a count
|
||||
/// kept by the visitor indexes the same widget as the matching [`Tree`]
|
||||
/// vector does.
|
||||
pub fn walk_mut(&mut self, at: &mut impl FnMut(&mut Plan)) {
|
||||
match &mut self.kind {
|
||||
Kind::Scroll { inner, .. } | Kind::Pad { inner, .. } => inner.walk_mut(at),
|
||||
Kind::Branch {
|
||||
probe,
|
||||
wide,
|
||||
narrow,
|
||||
..
|
||||
} => {
|
||||
probe.walk_mut(at);
|
||||
wide.walk_mut(at);
|
||||
narrow.walk_mut(at);
|
||||
}
|
||||
Kind::Stack { children } => {
|
||||
for child in children {
|
||||
child.walk_mut(at);
|
||||
}
|
||||
}
|
||||
Kind::Span {
|
||||
children, spares, ..
|
||||
} => {
|
||||
for child in children.iter_mut().chain(spares) {
|
||||
child.walk_mut(at);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
at(self);
|
||||
}
|
||||
|
||||
/// The same tree with `edits` applied, by the indices the generator would
|
||||
/// have used for them.
|
||||
///
|
||||
/// [`plan`] resolves edits while drawing, which needs a seed. A scenario
|
||||
/// needs them applied to a tree that already exists -- one it has built,
|
||||
/// and one a shrinker may already have cut down, where no seed grows it
|
||||
/// any more. Both routes take the same [`Edits`], so a case written
|
||||
/// against one reads the same against the other.
|
||||
pub fn edited(&self, edits: &Edits) -> Plan {
|
||||
let mut out = self.clone();
|
||||
let (mut sized, mut aligned, mut nodes, mut spans) = (0, 0, 0, 0);
|
||||
out.walk_mut(&mut |plan| {
|
||||
if let Kind::Span {
|
||||
children,
|
||||
spares,
|
||||
order,
|
||||
..
|
||||
} = &mut plan.kind
|
||||
{
|
||||
if let Some(edit) = edits.spans.get(&spans) {
|
||||
*order = span_edited(order, children.len(), spares.len(), edit);
|
||||
}
|
||||
spans += 1;
|
||||
}
|
||||
if let Kind::Branch { threshold, .. } = &mut plan.kind
|
||||
&& edits.fixed_branches
|
||||
{
|
||||
*threshold = f32::MIN;
|
||||
}
|
||||
if plan.size.is_some() {
|
||||
if let Some(lens) = edits.sizes.get(&sized) {
|
||||
plan.size = Some(*lens);
|
||||
}
|
||||
sized += 1;
|
||||
}
|
||||
if plan.align.is_some() {
|
||||
if let Some(align) = edits.aligns.get(&aligned) {
|
||||
plan.align = Some(*align);
|
||||
}
|
||||
aligned += 1;
|
||||
}
|
||||
if plan.region_node.is_some() {
|
||||
if let Some(take) = edits.nodes.get(&nodes) {
|
||||
plan.region_node = Some(*take);
|
||||
}
|
||||
nodes += 1;
|
||||
}
|
||||
});
|
||||
out
|
||||
}
|
||||
|
||||
fn kids(&self) -> Vec<&Plan> {
|
||||
match &self.kind {
|
||||
Kind::Scroll { inner, .. } | Kind::Pad { inner, .. } => vec![inner],
|
||||
Kind::Branch {
|
||||
probe,
|
||||
wide,
|
||||
narrow,
|
||||
..
|
||||
} => vec![probe, wide, narrow],
|
||||
Kind::Stack { children } => children.iter().collect(),
|
||||
Kind::Span { children, .. } => children.iter().collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Kind {
|
||||
/// Simplifications of the shape alone, leaving what the widget carries to
|
||||
/// [`Plan::smaller`]. Replacing a node with one of its children is there
|
||||
/// rather than here, since it answers with a whole `Plan`.
|
||||
fn smaller(&self) -> Vec<Kind> {
|
||||
let mut out = Vec::new();
|
||||
/// One child reduced at a time, rebuilt into the same shape. Every
|
||||
/// answer has the same number of children as it was given, so it is
|
||||
/// for the shapes whose child count is part of what they are.
|
||||
fn reduced(kids: &[Plan], rebuild: &dyn Fn(Vec<Plan>) -> Kind) -> Vec<Kind> {
|
||||
let mut out = Vec::new();
|
||||
for (i, kid) in kids.iter().enumerate() {
|
||||
for small in kid.smaller() {
|
||||
let mut next = kids.to_vec();
|
||||
next[i] = small;
|
||||
out.push(rebuild(next));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// One child dropped, then [`reduced`]. For the shapes that hold any
|
||||
/// number of children, where dropping one is the cut that matters.
|
||||
fn each(kids: &[Plan], rebuild: &dyn Fn(Vec<Plan>) -> Kind) -> Vec<Kind> {
|
||||
let mut out = Vec::new();
|
||||
for i in 0..kids.len() {
|
||||
if kids.len() > 1 {
|
||||
let mut less = kids.to_vec();
|
||||
less.remove(i);
|
||||
out.push(rebuild(less));
|
||||
}
|
||||
}
|
||||
out.extend(reduced(kids, rebuild));
|
||||
out
|
||||
}
|
||||
match self {
|
||||
// The one leaf that reads the width it is given, then the one
|
||||
// that does not, then the one that measures nothing at all.
|
||||
Kind::Wrapped => out.push(Kind::OneLine),
|
||||
Kind::OneLine => out.push(Kind::Rect {
|
||||
color: 0,
|
||||
alpha: 255,
|
||||
}),
|
||||
Kind::Rect { .. } => {}
|
||||
Kind::Scroll { axis, inner } => {
|
||||
let axis = *axis;
|
||||
out.extend(each(std::slice::from_ref(inner), &|mut k| Kind::Scroll {
|
||||
axis,
|
||||
inner: Box::new(k.remove(0)),
|
||||
}));
|
||||
}
|
||||
Kind::Branch {
|
||||
probe,
|
||||
wide,
|
||||
narrow,
|
||||
threshold,
|
||||
} => {
|
||||
let threshold = *threshold;
|
||||
// All three sides stay: a branch is the widget that draws
|
||||
// one of two on a measurement, and one with a side missing
|
||||
// is a different widget rather than a smaller one. Dropping
|
||||
// the branch for a side is offered by `Plan::smaller`.
|
||||
let sides = [(**probe).clone(), (**wide).clone(), (**narrow).clone()];
|
||||
out.extend(reduced(&sides, &|k| Kind::Branch {
|
||||
probe: Box::new(k[0].clone()),
|
||||
wide: Box::new(k[1].clone()),
|
||||
narrow: Box::new(k[2].clone()),
|
||||
threshold,
|
||||
}));
|
||||
}
|
||||
Kind::Pad { padding, inner } => {
|
||||
let padding = *padding;
|
||||
if padding != [0; 4] {
|
||||
out.push(Kind::Pad {
|
||||
padding: [0; 4],
|
||||
inner: inner.clone(),
|
||||
});
|
||||
}
|
||||
out.extend(each(std::slice::from_ref(inner), &|mut k| Kind::Pad {
|
||||
padding,
|
||||
inner: Box::new(k.remove(0)),
|
||||
}));
|
||||
}
|
||||
Kind::Stack { children } => {
|
||||
out.extend(each(children, &|children| Kind::Stack { children }))
|
||||
}
|
||||
Kind::Span {
|
||||
dir,
|
||||
gap,
|
||||
children,
|
||||
spares,
|
||||
order,
|
||||
} => {
|
||||
let (dir, gap, n) = (*dir, *gap, children.len());
|
||||
let span = |children: Vec<Plan>, spares: Vec<Plan>, order: Vec<usize>| Kind::Span {
|
||||
dir,
|
||||
gap,
|
||||
children,
|
||||
spares,
|
||||
order,
|
||||
};
|
||||
let identity: Vec<usize> = (0..n).collect();
|
||||
// An order the generator did not choose is part of the tree,
|
||||
// so take that off before taking the tree apart.
|
||||
if *order != identity {
|
||||
out.push(span(children.clone(), spares.clone(), identity));
|
||||
}
|
||||
// Spares exist to be attached; with none attached they are
|
||||
// widgets the span never holds.
|
||||
if !spares.is_empty() && order.iter().all(|&i| i < n) {
|
||||
out.push(span(children.clone(), Vec::new(), order.clone()));
|
||||
}
|
||||
if gap != 0 {
|
||||
out.push(Kind::Span {
|
||||
dir,
|
||||
gap: 0,
|
||||
children: children.clone(),
|
||||
spares: spares.clone(),
|
||||
order: order.clone(),
|
||||
});
|
||||
}
|
||||
for k in 0..n {
|
||||
if n > 1 {
|
||||
let mut less = children.clone();
|
||||
less.remove(k);
|
||||
// Everything after it shifts down, spares included,
|
||||
// since they are indexed past the children.
|
||||
let order = order
|
||||
.iter()
|
||||
.filter(|&&i| i != k)
|
||||
.map(|&i| if i > k { i - 1 } else { i })
|
||||
.collect();
|
||||
out.push(span(less, spares.clone(), order));
|
||||
}
|
||||
}
|
||||
for (i, kid) in children.iter().enumerate() {
|
||||
for small in kid.smaller() {
|
||||
let mut next = children.clone();
|
||||
next[i] = small;
|
||||
out.push(span(next, spares.clone(), order.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`SpanEdit`] applied to the order a span already holds its children in.
|
||||
///
|
||||
/// `detach` names positions in that order and `attach` takes from the front
|
||||
/// of what the span is not holding, both of which is what a test changing a
|
||||
/// live span does -- so an edit means the same thing said to a tree and said
|
||||
/// to the plan it was built from. On a span nobody has edited the order is
|
||||
/// the children in the order they were grown, and this is then "leave these
|
||||
/// out and put that many spares on the end".
|
||||
fn span_edited(order: &[usize], children: usize, spares: usize, edit: &SpanEdit) -> Vec<usize> {
|
||||
let mut detach = edit.detach.clone();
|
||||
detach.sort_unstable();
|
||||
detach.dedup();
|
||||
let mut next: Vec<usize> = order
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(at, _)| !detach.contains(at))
|
||||
.map(|(_, &which)| which)
|
||||
.collect();
|
||||
// What the span is not holding, in the order it hands them back: what it
|
||||
// was already not holding first, in the order the widgets were made, and
|
||||
// what this edit takes out after that, highest position first. A child
|
||||
// just detached goes to the back rather than straight back in, which is
|
||||
// what makes detaching one and attaching one a trade.
|
||||
let mut free: Vec<usize> = (0..children + spares)
|
||||
.filter(|i| !order.contains(i))
|
||||
.collect();
|
||||
free.extend(detach.iter().rev().filter_map(|&at| order.get(at).copied()));
|
||||
next.extend(free.into_iter().take(edit.attach));
|
||||
next
|
||||
}
|
||||
|
||||
/// Plans the tree `seed` describes, `edits` replacing what it would otherwise
|
||||
/// have given the widgets that carry them.
|
||||
///
|
||||
/// The edits are resolved here rather than at build time, so that a plan is
|
||||
/// the whole of what a tree is and building one has nothing left to decide.
|
||||
pub fn plan(seed: u64, depth: usize, edits: &Edits) -> Plan {
|
||||
let mut sow = Sow {
|
||||
rng: Rng::new(seed),
|
||||
edits,
|
||||
sized: 0,
|
||||
aligned: 0,
|
||||
nodes: 0,
|
||||
spans: 0,
|
||||
};
|
||||
sow.node(depth)
|
||||
}
|
||||
|
||||
/// Grows the tree `seed` describes, `edits` replacing the declared sizes it
|
||||
/// would otherwise have given those wrappers.
|
||||
pub fn grow<Rsc: UiRsc + 'static>(
|
||||
@@ -155,41 +599,32 @@ pub fn grow<Rsc: UiRsc + 'static>(
|
||||
depth: usize,
|
||||
edits: &Edits,
|
||||
) -> (StrongWidget, Tree) {
|
||||
let mut grow = Grow {
|
||||
rsc,
|
||||
rng: Rng::new(seed),
|
||||
tree: Tree::default(),
|
||||
edits,
|
||||
};
|
||||
let root = grow.node(depth);
|
||||
(root, grow.tree)
|
||||
build(rsc, &plan(seed, depth, edits))
|
||||
}
|
||||
|
||||
struct Grow<'a, Rsc> {
|
||||
rsc: &'a mut Rsc,
|
||||
/// Draws a plan out of the random stream. Every draw happens in the order it
|
||||
/// always has and before the decision it feeds, including the decisions that
|
||||
/// are then dropped, because a seed has to keep meaning the same tree.
|
||||
struct Sow<'a> {
|
||||
rng: Rng,
|
||||
tree: Tree,
|
||||
edits: &'a Edits,
|
||||
sized: usize,
|
||||
aligned: usize,
|
||||
nodes: usize,
|
||||
spans: usize,
|
||||
}
|
||||
|
||||
impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
|
||||
fn leaf(&mut self) -> StrongWidget {
|
||||
let id: StrongWidget = match self.rng.below(4) {
|
||||
// Wrapped and unwrapped, because only one of them reads the width
|
||||
// it is given and so only one has to be drawn again for a new one.
|
||||
0 => wtext(WORDS).size(16).wrap(true).add_strong(self.rsc),
|
||||
1 => wtext("one line, overflowing whatever it is given")
|
||||
.size(16)
|
||||
.wrap(false)
|
||||
.add_strong(self.rsc),
|
||||
impl Sow<'_> {
|
||||
fn leaf(&mut self) -> Plan {
|
||||
Plan::bare(match self.rng.below(4) {
|
||||
0 => Kind::Wrapped,
|
||||
1 => Kind::OneLine,
|
||||
_ => {
|
||||
let color = COLORS[self.rng.below(COLORS.len())];
|
||||
let color = self.rng.below(COLORS.len());
|
||||
let alpha = (self.rng.below(5) * 63) as u8;
|
||||
rect(color.alpha(alpha)).add_strong(self.rsc)
|
||||
Kind::Rect { color, alpha }
|
||||
}
|
||||
};
|
||||
self.tree.ids.push(id.id());
|
||||
id
|
||||
})
|
||||
}
|
||||
|
||||
fn len(&mut self) -> Option<LayoutLen> {
|
||||
@@ -200,100 +635,77 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
|
||||
}
|
||||
}
|
||||
|
||||
fn align(&mut self) -> Align {
|
||||
let mut axis = || match self.rng.below(4) {
|
||||
fn align(&mut self) -> Aligns {
|
||||
let axis = |s: &mut Self| match s.rng.below(4) {
|
||||
0 => None,
|
||||
1 => Some(AxisAlign::NEG),
|
||||
2 => Some(AxisAlign::CENTER),
|
||||
_ => Some(AxisAlign::POS),
|
||||
};
|
||||
let (mut x, y) = (axis(), axis());
|
||||
let (x, y) = (axis(self), axis(self));
|
||||
// Aligning on neither axis leaves the branch unexercised.
|
||||
if x.is_none() && y.is_none() {
|
||||
x = Some(AxisAlign::CENTER);
|
||||
match x.is_none() && y.is_none() {
|
||||
true => [Some(AxisAlign::CENTER), y],
|
||||
false => [x, y],
|
||||
}
|
||||
Align { x, y }
|
||||
}
|
||||
|
||||
/// A declared size over half the tree, kept where a test can change it.
|
||||
fn sized(&mut self, inner: StrongWidget) -> StrongWidget {
|
||||
// A rule is a property now, so a node already carrying one would take
|
||||
// a second entry in `sized` -- and two edits naming one widget settle
|
||||
// in the order they are applied, which is grow order cold and edit
|
||||
// order warm. One entry per widget instead. Both draws are taken
|
||||
// whatever is decided, and the decision is grow order alone, so the
|
||||
// two trees consume the same random stream.
|
||||
fn sized(&mut self, inner: &mut Plan) {
|
||||
let take = self.rng.chance();
|
||||
let lens = [self.len(), self.len()];
|
||||
if !take || self.tree.sized.contains(&inner.id()) {
|
||||
return inner;
|
||||
if !take || inner.size.is_some() {
|
||||
return;
|
||||
}
|
||||
let idx = self.tree.sized.len();
|
||||
let lens = self.edits.sizes.get(&idx).copied().unwrap_or(lens);
|
||||
let id = inner.id();
|
||||
self.rsc
|
||||
.ui_mut()
|
||||
.widgets
|
||||
.set_size_rules(id, lens[0], lens[1]);
|
||||
self.tree.sized.push(id);
|
||||
inner
|
||||
let idx = self.sized;
|
||||
self.sized += 1;
|
||||
inner.size = Some(self.edits.sizes.get(&idx).copied().unwrap_or(lens));
|
||||
}
|
||||
|
||||
/// An alignment over some of the tree, kept where a test can change it.
|
||||
/// One entry per widget for the reason `sized` gives.
|
||||
fn aligned(&mut self, inner: StrongWidget) -> StrongWidget {
|
||||
fn aligned(&mut self, inner: &mut Plan) {
|
||||
let align = self.align();
|
||||
let align = [align.x, align.y];
|
||||
if self.tree.aligned.contains(&inner.id()) {
|
||||
return inner;
|
||||
if inner.align.is_some() {
|
||||
return;
|
||||
}
|
||||
let idx = self.tree.aligned.len();
|
||||
let align = self.edits.aligns.get(&idx).copied().unwrap_or(align);
|
||||
let id = inner.id();
|
||||
let widgets = &mut self.rsc.ui_mut().widgets;
|
||||
for (axis, align) in [Axis::X, Axis::Y].into_iter().zip(align) {
|
||||
widgets.set_alignment(id, axis, align.unwrap_or_default());
|
||||
}
|
||||
self.tree.aligned.push(id);
|
||||
inner
|
||||
let idx = self.aligned;
|
||||
self.aligned += 1;
|
||||
inner.align = Some(self.edits.aligns.get(&idx).copied().unwrap_or(align));
|
||||
}
|
||||
|
||||
/// A movable region of its own over some of the tree. What it changes is
|
||||
/// how a move is written and how long a primitive's chain is, neither of
|
||||
/// which any other branch here varies.
|
||||
fn noded(&mut self, inner: StrongWidget) -> StrongWidget {
|
||||
fn noded(&mut self, inner: &mut Plan) {
|
||||
let take = self.rng.below(4) == 0;
|
||||
if self.tree.nodes.contains(&inner.id()) {
|
||||
return inner;
|
||||
if inner.region_node.is_some() {
|
||||
return;
|
||||
}
|
||||
let idx = self.tree.nodes.len();
|
||||
let take = self.edits.nodes.get(&idx).copied().unwrap_or(take);
|
||||
let id = inner.id();
|
||||
self.rsc.ui_mut().widgets.set_region_node(id, take);
|
||||
self.tree.nodes.push(id);
|
||||
inner
|
||||
let idx = self.nodes;
|
||||
self.nodes += 1;
|
||||
inner.region_node = Some(self.edits.nodes.get(&idx).copied().unwrap_or(take));
|
||||
}
|
||||
|
||||
fn node(&mut self, depth: usize) -> StrongWidget {
|
||||
fn offered(&mut self, inner: &mut Plan) {
|
||||
self.sized(inner);
|
||||
self.noded(inner);
|
||||
}
|
||||
|
||||
fn node(&mut self, depth: usize) -> Plan {
|
||||
if depth == 0 {
|
||||
return self.leaf();
|
||||
}
|
||||
let positioned = self.rng.below(6);
|
||||
if positioned == 0 {
|
||||
// Scrolling reads the pixel length of its box, which nothing
|
||||
// else here does, and gives its child a box longer than its own.
|
||||
let inner = self.node(depth - 1);
|
||||
let inner = self.sized(inner);
|
||||
let inner = self.noded(inner);
|
||||
let mut inner = self.node(depth - 1);
|
||||
self.offered(&mut inner);
|
||||
let axis = if self.rng.chance() { Axis::X } else { Axis::Y };
|
||||
let id = Scroll::new(inner, axis).add(self.rsc);
|
||||
self.tree.scrolls.push(id);
|
||||
self.tree.ids.push(id.id());
|
||||
return id.add_strong(self.rsc);
|
||||
return Plan::bare(Kind::Scroll {
|
||||
axis,
|
||||
inner: Box::new(inner),
|
||||
});
|
||||
}
|
||||
if positioned == 2 {
|
||||
// Both sides are grown either way, so a tree that draws one has
|
||||
// the same ids as a tree that draws the other.
|
||||
let probe = self.node(depth - 1);
|
||||
let wide = self.node(depth - 1);
|
||||
let narrow = self.node(depth - 1);
|
||||
@@ -305,90 +717,199 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
|
||||
true => f32::MIN,
|
||||
false => measured,
|
||||
};
|
||||
let id = Branch {
|
||||
probe,
|
||||
wide,
|
||||
narrow,
|
||||
return Plan::bare(Kind::Branch {
|
||||
probe: Box::new(probe),
|
||||
wide: Box::new(wide),
|
||||
narrow: Box::new(narrow),
|
||||
threshold,
|
||||
}
|
||||
.add(self.rsc);
|
||||
self.tree.ids.push(id.id());
|
||||
return id.add_strong(self.rsc);
|
||||
});
|
||||
}
|
||||
if positioned == 1 {
|
||||
let inner = self.node(depth - 1);
|
||||
let inner = self.sized(inner);
|
||||
let inner = self.noded(inner);
|
||||
return self.aligned(inner);
|
||||
// Carries an alignment and makes no widget of its own, so the
|
||||
// plan for it is the child it aligned.
|
||||
let mut inner = self.node(depth - 1);
|
||||
self.offered(&mut inner);
|
||||
self.aligned(&mut inner);
|
||||
return inner;
|
||||
}
|
||||
if self.rng.below(4) == 0 {
|
||||
let inner = self.node(depth - 1);
|
||||
let inner = self.sized(inner);
|
||||
let inner = self.noded(inner);
|
||||
// Each side its own, since a padding that is the same all round
|
||||
// hides anything that treats one edge differently from another.
|
||||
let mut side = || Px::from_int(self.rng.below(24) as i32);
|
||||
let padding = Padding {
|
||||
left: side(),
|
||||
right: side(),
|
||||
top: side(),
|
||||
bottom: side(),
|
||||
};
|
||||
let id = Pad { padding, inner }.add_strong(self.rsc);
|
||||
self.tree.ids.push(id.id());
|
||||
return id;
|
||||
let mut inner = self.node(depth - 1);
|
||||
self.offered(&mut inner);
|
||||
let side = |s: &mut Self| s.rng.below(24) as i32;
|
||||
let padding = [side(self), side(self), side(self), side(self)];
|
||||
return Plan::bare(Kind::Pad {
|
||||
padding,
|
||||
inner: Box::new(inner),
|
||||
});
|
||||
}
|
||||
let grown = 2 + self.rng.below(3);
|
||||
let mut children = Vec::with_capacity(grown);
|
||||
for _ in 0..grown {
|
||||
let child = self.node(depth - 1);
|
||||
let child = self.sized(child);
|
||||
let child = self.noded(child);
|
||||
let mut child = self.node(depth - 1);
|
||||
self.offered(&mut child);
|
||||
children.push(child);
|
||||
}
|
||||
if self.rng.chance() {
|
||||
let id = Stack {
|
||||
children,
|
||||
size: StackSize::Child(0),
|
||||
}
|
||||
.add_strong(self.rsc);
|
||||
self.tree.ids.push(id.id());
|
||||
return id;
|
||||
return Plan::bare(Kind::Stack { children });
|
||||
}
|
||||
// Grown either way, so the widget after them has the same id in a
|
||||
// tree that leaves them out as in one that puts them in.
|
||||
let mut spares: Vec<StrongWidget> = (0..SPARES).map(|_| self.leaf()).collect();
|
||||
let idx = self.tree.spans.len();
|
||||
let spares: Vec<Plan> = (0..SPARES).map(|_| self.leaf()).collect();
|
||||
let idx = self.spans;
|
||||
self.spans += 1;
|
||||
let edit = self.edits.spans.get(&idx).cloned().unwrap_or_default();
|
||||
// Highest first, so an index means the same child however many of its
|
||||
// neighbours are going too.
|
||||
let mut detach = edit.detach.clone();
|
||||
detach.sort_unstable();
|
||||
for j in detach.into_iter().rev() {
|
||||
if j < children.len() {
|
||||
self.tree.detached.push(children.remove(j));
|
||||
}
|
||||
}
|
||||
let attach = edit.attach.min(spares.len());
|
||||
children.extend(spares.drain(..attach));
|
||||
let dir = [Dir::RIGHT, Dir::DOWN, Dir::LEFT, Dir::UP][self.rng.below(4)];
|
||||
let id = Span {
|
||||
children,
|
||||
dir,
|
||||
gap: Px::from_int(self.rng.below(3) as i32 * 4),
|
||||
}
|
||||
.add(self.rsc);
|
||||
let dir = self.rng.below(4);
|
||||
// A row takes the height it is given rather than its tallest child,
|
||||
// which is a rule beside it. Derived from an existing choice and
|
||||
// consuming no randomness: a seed must keep growing the same tree
|
||||
// when the generator gains another configuration.
|
||||
if dir.axis == Axis::X {
|
||||
let gap = self.rng.below(3) as i32 * 4;
|
||||
let grown: Vec<usize> = (0..children.len()).collect();
|
||||
let order = span_edited(&grown, children.len(), spares.len(), &edit);
|
||||
Plan::bare(Kind::Span {
|
||||
dir,
|
||||
gap,
|
||||
children,
|
||||
spares,
|
||||
order,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a plan's widgets in the order it describes them, so two builds of
|
||||
/// one plan line up index for index and their boxes can be compared.
|
||||
pub fn build<Rsc: UiRsc + 'static>(rsc: &mut Rsc, plan: &Plan) -> (StrongWidget, Tree) {
|
||||
let mut build = Build {
|
||||
rsc,
|
||||
tree: Tree::default(),
|
||||
};
|
||||
let root = build.node(plan);
|
||||
(root, build.tree)
|
||||
}
|
||||
|
||||
struct Build<'a, Rsc> {
|
||||
rsc: &'a mut Rsc,
|
||||
tree: Tree,
|
||||
}
|
||||
|
||||
impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
||||
fn node(&mut self, plan: &Plan) -> StrongWidget {
|
||||
let built = self.kind(&plan.kind);
|
||||
let id = built.id();
|
||||
if let Some(lens) = plan.size {
|
||||
self.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rules(id, None, Some(LayoutLen::rel(1.0)));
|
||||
.ui_mut()
|
||||
.widgets
|
||||
.set_size_rules(id, lens[0], lens[1]);
|
||||
self.tree.sized.push(id);
|
||||
}
|
||||
if let Some(align) = plan.align {
|
||||
let widgets = &mut self.rsc.ui_mut().widgets;
|
||||
for (axis, align) in [Axis::X, Axis::Y].into_iter().zip(align) {
|
||||
widgets.set_alignment(id, axis, align.unwrap_or_default());
|
||||
}
|
||||
self.tree.aligned.push(id);
|
||||
}
|
||||
if let Some(take) = plan.region_node {
|
||||
self.rsc.ui_mut().widgets.set_region_node(id, take);
|
||||
self.tree.nodes.push(id);
|
||||
}
|
||||
built
|
||||
}
|
||||
|
||||
fn kind(&mut self, kind: &Kind) -> StrongWidget {
|
||||
let id: StrongWidget = match kind {
|
||||
Kind::Wrapped => wtext(WORDS).size(16).wrap(true).add_strong(self.rsc),
|
||||
Kind::OneLine => wtext("one line, overflowing whatever it is given")
|
||||
.size(16)
|
||||
.wrap(false)
|
||||
.add_strong(self.rsc),
|
||||
Kind::Rect { color, alpha } => rect(COLORS[*color].alpha(*alpha)).add_strong(self.rsc),
|
||||
Kind::Scroll { axis, inner } => {
|
||||
let inner = self.node(inner);
|
||||
let id = Scroll::new(inner, *axis).add(self.rsc);
|
||||
self.tree.scrolls.push(id);
|
||||
self.tree.ids.push(id.id());
|
||||
return id.add_strong(self.rsc);
|
||||
}
|
||||
Kind::Branch {
|
||||
probe,
|
||||
wide,
|
||||
narrow,
|
||||
threshold,
|
||||
} => {
|
||||
let probe = self.node(probe);
|
||||
let wide = self.node(wide);
|
||||
let narrow = self.node(narrow);
|
||||
let id = Branch {
|
||||
probe,
|
||||
wide,
|
||||
narrow,
|
||||
threshold: *threshold,
|
||||
}
|
||||
.add(self.rsc);
|
||||
self.tree.ids.push(id.id());
|
||||
return id.add_strong(self.rsc);
|
||||
}
|
||||
Kind::Pad { padding, inner } => {
|
||||
let inner = self.node(inner);
|
||||
let [left, right, top, bottom] = padding.map(Px::from_int);
|
||||
let padding = Padding {
|
||||
left,
|
||||
right,
|
||||
top,
|
||||
bottom,
|
||||
};
|
||||
Pad { padding, inner }.add_strong(self.rsc)
|
||||
}
|
||||
Kind::Stack { children } => {
|
||||
let children = children.iter().map(|c| self.node(c)).collect();
|
||||
Stack {
|
||||
children,
|
||||
size: StackSize::Child(0),
|
||||
}
|
||||
.add_strong(self.rsc)
|
||||
}
|
||||
Kind::Span {
|
||||
dir,
|
||||
gap,
|
||||
children,
|
||||
spares,
|
||||
order,
|
||||
} => {
|
||||
let grown = children.len();
|
||||
// Every one of them is made, in this order, whether or not
|
||||
// the span ends up holding it.
|
||||
let made: Vec<StrongWidget> = children
|
||||
.iter()
|
||||
.chain(spares)
|
||||
.map(|c| self.node(c))
|
||||
.collect();
|
||||
let mut left: Vec<Option<StrongWidget>> = made.into_iter().map(Some).collect();
|
||||
let children: Vec<StrongWidget> = order
|
||||
.iter()
|
||||
.filter_map(|&i| left.get_mut(i).and_then(Option::take))
|
||||
.collect();
|
||||
// What the span does not hold is still held here: dropping
|
||||
// the last share of a widget frees its id for the next one
|
||||
// to be given, which puts two trees out of step.
|
||||
let spares: Vec<StrongWidget> = left.into_iter().flatten().collect();
|
||||
let dir = [Dir::RIGHT, Dir::DOWN, Dir::LEFT, Dir::UP][*dir % 4];
|
||||
let id = Span {
|
||||
children,
|
||||
dir,
|
||||
gap: Px::from_int(*gap),
|
||||
}
|
||||
.add(self.rsc);
|
||||
if dir.axis == Axis::X {
|
||||
self.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rules(id, None, Some(LayoutLen::rel(1.0)));
|
||||
}
|
||||
self.tree.ids.push(id.id());
|
||||
self.tree.spans.push(Spanned { id, spares, grown });
|
||||
return id.add_strong(self.rsc);
|
||||
}
|
||||
};
|
||||
self.tree.ids.push(id.id());
|
||||
self.tree.spans.push(Spanned { id, spares, grown });
|
||||
id.add_strong(self.rsc)
|
||||
id
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user