Say what a child gets of a container's box in that box's own lengths
Three things the measurements asked for, all about how much a box that came from an answer costs. **A part in the box's own coordinates.** Saying "less eleven pixels at the end" in frame lengths from the box's start means reading how long the box is, and a container whose box is its own answer then depends on its own answer: `Pad` drew sixty-four times in one resize frame at seed 13, chasing its own width. `Part::Of` says the same thing as a part of the box, which composes without a length -- pixels are pixels wherever the box lands -- and what a child under it holds for maps back through that part onto the container's own box rather than onto the frame. **One axis of the box at a time.** `extent_len` pinned both axes, so a span dividing one of them held for one length of the other as well, and a resize broke every span whose cross-axis answer moved. **No lazy placement.** Leaving a child's answer to be placed at the end of the parent's draw, rather than as the child answers, was meant to save a recomposition. It costs one instead: the drawing is put in the part first and in the answer's box after, and where it does not hold for both that is two drawings rather than one. Seed 1 at depth 8 went from 391 widget draws on a resize to 29 with it gone. The test that pinned three draws for a numeric leaf in a span goes with it. Seed 1 at depth 8, widget draws / distinct widgets / update, against #18's head and against the commit this branch started from: | phase |e44dea3|34cafb6| here | | --- | --- | --- | --- | | cold | 369/261/10.6 | 463/274/13.3 | 516/288/12.0 | | repaint | 1 | 1 | 1 | | many | 157/95/0.33 | 263/108/0.59 | 187/119/0.52 | | size | 16/12/0.018 | 3/3 | 3/3/0.010 | | scroll | 2/0.002 | 1 | 1/0.004 | | resize | 13/13/0.019 | 22/15/0.032 | 24/76/0.090 | Seed 13 at depth 8 is where the protocol still costs: `many` 1091 draws against #18's 524, and `resize` 2215 against a frame #18 does not draw at all. Both are the same shape -- an answer measured in one box and drawn in another -- and the handoff says where that comes from. Checked: fmt, clippy with -D warnings, 108 suite tests, 20 core tests, the 11 generated cases, and the shrinker at 400 trees of depth 5, which fails seeds 2 (repaint) and 108 (reorder).
This commit is contained in:
1 parent
1956be3f3d
commit
0954770ceb
11 files changed
+134
-222
No files matched your search
+28
-111
@@ -3,7 +3,7 @@ use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::ui::painter::{declared_lens, frame_and_extent, part_of, placed_extent};
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx,
|
||||
Moves, Painter, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight,
|
||||
Moves, Painter, Part, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight,
|
||||
WidgetId, Widgets,
|
||||
util::{HashMap, Vec2},
|
||||
};
|
||||
@@ -151,8 +151,8 @@ impl UiRenderState {
|
||||
frame: region,
|
||||
frame_abs: region,
|
||||
part: UiRegion::FULL,
|
||||
place: [Place::Within(None); 2],
|
||||
offer_place: [Place::Within(None); 2],
|
||||
place: [Place::Within(Part::All); 2],
|
||||
offer_place: [Place::Within(Part::All); 2],
|
||||
offer: true,
|
||||
px,
|
||||
}
|
||||
@@ -202,7 +202,7 @@ impl UiRenderState {
|
||||
if let Some(id) = root {
|
||||
let region = Self::root_region(id.id(), rsc.widgets());
|
||||
let info = self.root_info(region);
|
||||
self.draw_inner(id.id(), info, None, true, rsc);
|
||||
self.draw_inner(id.id(), info, None, rsc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,6 @@ impl UiRenderState {
|
||||
id: WidgetId,
|
||||
info: DrawInfo,
|
||||
mut old: Option<ActiveData>,
|
||||
place_answer: bool,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> (Size, LayoutHolds, LayoutHolds) {
|
||||
let (frame, part) = (info.frame_abs, info.part);
|
||||
@@ -258,31 +257,21 @@ impl UiRenderState {
|
||||
// axis the parent left open. The frame itself does not change, so
|
||||
// nothing under it resolves a fraction a second time.
|
||||
//
|
||||
// Placing the answer waits for the end of the parent's draw, or for
|
||||
// the next ask of this child in it: a child measured in the room its
|
||||
// parent had left over and then given a slot would otherwise be put
|
||||
// in a box neither of them chose, between the two. Whoever asks with
|
||||
// nobody to finish it -- the root, a local redraw -- places here.
|
||||
let extent = match place_answer {
|
||||
// From the answer it gave when its parent first asked, and not
|
||||
// from what a placing evaluation reported: a drawing made in the
|
||||
// box that answer chose is answering a different question, and
|
||||
// placing it by that would move the box out from under itself.
|
||||
true => {
|
||||
let measured = match info.offer {
|
||||
true => answer.0,
|
||||
false => self.active[&id].answer.map_or(answer.0, |(size, _)| size),
|
||||
};
|
||||
placed_extent(
|
||||
part,
|
||||
measured,
|
||||
declared_lens(rsc.widgets(), id),
|
||||
info.fill(),
|
||||
align,
|
||||
)
|
||||
}
|
||||
false => part,
|
||||
// From the answer it gave when its parent first asked, and not from
|
||||
// what a placing evaluation reported: a drawing made in the box that
|
||||
// answer chose is answering a different question, and placing it by
|
||||
// that would move the box out from under itself.
|
||||
let measured = match info.offer {
|
||||
true => answer.0,
|
||||
false => self.active[&id].answer.map_or(answer.0, |(size, _)| size),
|
||||
};
|
||||
let extent = placed_extent(
|
||||
part,
|
||||
measured,
|
||||
declared_lens(rsc.widgets(), id),
|
||||
info.fill(),
|
||||
align,
|
||||
);
|
||||
self.place(id, extent, info, rsc);
|
||||
|
||||
// On axes the parent filled, measurement and drawing share an extent.
|
||||
@@ -468,28 +457,13 @@ impl UiRenderState {
|
||||
if let Some(idx) = retired_move {
|
||||
self.moves.remove(idx);
|
||||
}
|
||||
// The draw is over, so a child whose box was left to its own answer
|
||||
// gets that box now. Nothing it did is undone by this: a child it
|
||||
// asked twice was placed by the second ask, and one that fills what
|
||||
// it was given is already there. What those drawings need is part of
|
||||
// what this one does, so it comes before the contract is settled.
|
||||
let at = Placing {
|
||||
id,
|
||||
extent,
|
||||
local,
|
||||
px,
|
||||
depth: info.depth,
|
||||
move_idx,
|
||||
mask,
|
||||
};
|
||||
let placed = self.place_answers(&at, &children, rsc);
|
||||
let own_holds = LayoutHolds {
|
||||
frame: own,
|
||||
extent: extent_own,
|
||||
extent_len,
|
||||
};
|
||||
let answer_holds = own_holds.and(answer_under);
|
||||
let holds = answer_holds.and(under).and(placed);
|
||||
let holds = answer_holds.and(under);
|
||||
debug_assert!(
|
||||
holds.contains(px, extent),
|
||||
"'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}",
|
||||
@@ -512,8 +486,8 @@ impl UiRenderState {
|
||||
frame: UiRegion::FULL,
|
||||
frame_abs: UiRegion::FULL,
|
||||
part: UiRegion::FULL,
|
||||
place: [Place::Within(None); 2],
|
||||
offer_place: [Place::Within(None); 2],
|
||||
place: [Place::Within(Part::All); 2],
|
||||
offer_place: [Place::Within(Part::All); 2],
|
||||
offer: false,
|
||||
px,
|
||||
},
|
||||
@@ -741,61 +715,6 @@ impl UiRenderState {
|
||||
Some(answer)
|
||||
}
|
||||
|
||||
/// Puts each child of a widget that has just drawn where its answer
|
||||
/// says, for the axes the ask left open. Doing it here rather than as
|
||||
/// each child answers is what keeps a child that is asked twice -- a
|
||||
/// span measuring it and then giving it a slot -- to one drawing and one
|
||||
/// move, instead of a move to its answer in between that nobody asked
|
||||
/// for.
|
||||
/// Answers with what those drawings then depend on, as dependencies of
|
||||
/// this widget: a child placed here may have been drawn again in the box
|
||||
/// its answer chose, and what that drawing needs was not known when the
|
||||
/// child was asked.
|
||||
fn place_answers(
|
||||
&mut self,
|
||||
at: &Placing,
|
||||
children: &[WidgetId],
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> LayoutHolds {
|
||||
let mut under = LayoutHolds::ANY;
|
||||
for &child in children {
|
||||
let Some(active) = self.active.get(&child) else {
|
||||
continue;
|
||||
};
|
||||
if active.place.iter().all(|place| place.fills()) {
|
||||
continue;
|
||||
}
|
||||
self.place_child(child, at, rsc);
|
||||
under = under.and(self.placed_holds(child));
|
||||
}
|
||||
under
|
||||
}
|
||||
|
||||
/// What a child's finished drawing needs, as a range on the frame of the
|
||||
/// widget that drew it.
|
||||
///
|
||||
/// Only the frame: where the box the child ended up in is the answer it
|
||||
/// gave, that box is a length of this frame and maps back through it
|
||||
/// exactly, and where the parent chose the box instead, the ask already
|
||||
/// said what that costs on the parent's own box.
|
||||
fn placed_holds(&self, child: WidgetId) -> LayoutHolds {
|
||||
let active = &self.active[&child];
|
||||
let mut result = LayoutHolds::ANY;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let frame_len = active.frame.axis(axis).len();
|
||||
result.frame[n] = active.holds.frame[n].through(frame_len);
|
||||
if !active.place[n].fills() {
|
||||
result.frame[n] = result.frame[n].and(
|
||||
active.holds.extent[n]
|
||||
.through(active.extent.axis(axis).len())
|
||||
.through(frame_len),
|
||||
);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Places one child of `at.id` in the box that widget's own box gives
|
||||
/// it: its part of the extent, with its answer placed inside that part
|
||||
/// where the ask left the axis open.
|
||||
@@ -1025,8 +944,8 @@ impl UiRenderState {
|
||||
frame_abs: UiRegion::FULL,
|
||||
extent: UiRegion::FULL,
|
||||
frame: UiRegion::FULL,
|
||||
place: [Place::Within(None); 2],
|
||||
offer_place: [Place::Within(None); 2],
|
||||
place: [Place::Within(Part::All); 2],
|
||||
offer_place: [Place::Within(Part::All); 2],
|
||||
offer_part: UiRegion::FULL,
|
||||
answer: None,
|
||||
size,
|
||||
@@ -1256,7 +1175,7 @@ impl UiRenderState {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::LocalRedraws);
|
||||
let old = self.remove(id, false, rsc);
|
||||
self.draw_inner(id, info, old, true, rsc);
|
||||
self.draw_inner(id, info, old, rsc);
|
||||
return true;
|
||||
};
|
||||
let px = self.asked_px(id);
|
||||
@@ -1315,12 +1234,10 @@ impl UiRenderState {
|
||||
let old = self.remove(id, false, rsc);
|
||||
// The original measurement is refreshed before the assigned slot is
|
||||
// restored: its lengths may differ even though the frame is
|
||||
// unchanged. Nobody above is drawing, so this places its own answer,
|
||||
// at the slot it has.
|
||||
let places = info.place == info.offer_place && info.part == offered.part;
|
||||
let answer = self.draw_inner(id, offered, old, places, rsc);
|
||||
if !places {
|
||||
self.draw_inner(id, info, None, true, rsc);
|
||||
// unchanged.
|
||||
let answer = self.draw_inner(id, offered, old, rsc);
|
||||
if info.place != info.offer_place || info.part != offered.part {
|
||||
self.draw_inner(id, info, None, rsc);
|
||||
}
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
// A wider contract does not invalidate the guarantee the parent kept.
|
||||
|
||||
Reference in new issue
Block a user