Grow random trees, and check them against building the same tree cold
`iris::random` grows a seeded tree -- spans in every direction, stacks, rects with varying opacity, text both wrapping and overflowing, a declared size over half of it -- and `tests/generated.rs` grows each seed twice: once and then mutated, once with the mutation built in. Every widget's box has to match. `examples/random.rs` draws one, and `IRIS_SEED`/`IRIS_DEPTH` pick it. It found the defect in the commit before this one immediately: a reuse that marked a descendant for redraw escalated to that descendant's size reader, which re-placed the child, which marked it again. `try_reuse` now asks whether anything under the widget would have to be drawn again *before* keeping the drawing, and drops the whole thing if so, which terminates because it adds no marks. It also found one older and larger than this branch, which `a_wrapping_child_of_a_row_settles_somewhere_else_each_time` reproduces and documents: a wrapping text on a span's own axis is shaped twice against two different widths, so where it settles depends on how many passes it has had. 7 of 90 cases diverge on `db1751f` and 30 do here, because a placed child reaches the second shaping more often. It is the same defect either way, and it belongs where the two draws meet -- LAYOUT.md §4 -- not in the chain. The six seeds the live tests use are ones that agree. `forget_ref` goes with the subtree rewrite that used it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d98969158f
commit
86a7e8dfc3
6 files changed
+383
-29
No files matched your search
+19
-24
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, MoveIdx, Moves, OnResize, Painter, PixelRegion,
|
||||
Size, StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||
util::{HashMap, HashSet, Vec2},
|
||||
};
|
||||
|
||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||
@@ -269,40 +269,40 @@ impl UiRenderState {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Anything under it that has to be drawn again is drawn by drawing
|
||||
// this, because whatever reads that widget's size sits in between and
|
||||
// has to lay out around whatever it comes to.
|
||||
if changed.iter().any(|&c| c) && self.redraws_under(id, changed, rsc) {
|
||||
return None;
|
||||
}
|
||||
self.moves.set(slot, region);
|
||||
self.active.get_mut(&id).unwrap().region = region;
|
||||
if changed.iter().any(|&c| c) {
|
||||
self.mark_resized(id, changed, rsc);
|
||||
}
|
||||
Some(size)
|
||||
}
|
||||
|
||||
/// Marks every descendant whose drawing cannot survive the box it is a
|
||||
/// fraction of changing length, `changed` saying which axes of that box
|
||||
/// did.
|
||||
/// Whether anything under `id` would have to be drawn again for the box
|
||||
/// it is a fraction of changing length, `changed` saying which axes of
|
||||
/// that box did.
|
||||
///
|
||||
/// A part of a box with no relative extent on an axis is a fixed length,
|
||||
/// held as offsets from that box's start, and composing anything into it
|
||||
/// leaves no relative extent either. So a widget whose own box did not
|
||||
/// change length has no descendant whose box did, and the walk stops
|
||||
/// there.
|
||||
fn mark_resized(&mut self, id: WidgetId, changed: [bool; 2], rsc: &mut dyn UiRsc) {
|
||||
/// there -- an 80-wide child of a widened row is not asked at all.
|
||||
fn redraws_under(&self, id: WidgetId, changed: [bool; 2], rsc: &dyn UiRsc) -> bool {
|
||||
let Some(active) = self.active.get(&id) else {
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
// SAFETY: children cannot be recursive
|
||||
let children = unsafe { forget_ref(&active.children) };
|
||||
for &child in children {
|
||||
active.children.iter().any(|&child| {
|
||||
let Some(data) = self.active.get(&child) else {
|
||||
continue;
|
||||
return false;
|
||||
};
|
||||
let region = data.region;
|
||||
let mut own = changed;
|
||||
for (axis, c) in AXES.into_iter().zip(own.iter_mut()) {
|
||||
*c &= region.axis(axis).len().rel != 0.0;
|
||||
*c &= data.region.axis(axis).len().rel != 0.0;
|
||||
}
|
||||
if !own.iter().any(|&c| c) {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
let redraws = match rsc.widgets().get_dyn(child) {
|
||||
Some(widget) => AXES
|
||||
@@ -311,13 +311,8 @@ impl UiRenderState {
|
||||
.any(|(axis, c)| c && widget.on_resize(axis) != OnResize::Scale),
|
||||
None => true,
|
||||
};
|
||||
match redraws {
|
||||
true => {
|
||||
rsc.widgets_mut().needs_redraw.insert(child);
|
||||
}
|
||||
false => self.mark_resized(child, own, rsc),
|
||||
}
|
||||
}
|
||||
redraws || self.redraws_under(child, own, rsc)
|
||||
})
|
||||
}
|
||||
|
||||
fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool {
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub(crate) unsafe fn forget_ref<'a, T>(x: &T) -> &'a T {
|
||||
unsafe { std::mem::transmute::<&T, &T>(x) }
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_safety_doc)]
|
||||
pub(crate) unsafe fn forget_mut<'a, T>(x: &mut T) -> &'a mut T {
|
||||
unsafe { std::mem::transmute::<&mut T, &mut T>(x) }
|
||||
|
||||
Reference in new issue
Block a user