Compare commits
4
Commits
1ebd4d3a05
...
69ba91588a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69ba91588a | ||
|
|
8088a1fa59 | ||
|
|
713e3e747b | ||
|
|
aea0387567 |
No files matched your search
@@ -23,100 +23,70 @@ use std::{
|
|||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
/// Declares a counter or timer kind beside the name its report prints. Two
|
||||||
pub(crate) enum Counter {
|
/// lists in the same order was one list too many: a variant inserted without
|
||||||
Updates,
|
/// its label moving with it renames every total after it, and nothing says
|
||||||
DrawRequests,
|
/// so.
|
||||||
WidgetDraws,
|
macro_rules! labelled {
|
||||||
RegionNodeDraws,
|
($(#[$meta:meta])* $vis:vis enum $Name:ident { $($variant:ident = $label:literal,)* }) => {
|
||||||
SizeReads,
|
$(#[$meta])*
|
||||||
HintHits,
|
#[derive(Clone, Copy)]
|
||||||
HintMisses,
|
$vis enum $Name { $($variant,)* }
|
||||||
ReuseAttempts,
|
|
||||||
ReuseExact,
|
impl $Name {
|
||||||
ReuseMoved,
|
const COUNT: usize = [$($label,)*].len();
|
||||||
ReuseDirty,
|
const NAMES: [&'static str; Self::COUNT] = [$($label,)*];
|
||||||
ReuseWrongParent,
|
}
|
||||||
ReuseRemapped,
|
};
|
||||||
ReuseOutside,
|
|
||||||
ReuseWrongLayer,
|
|
||||||
ReuseWrongNode,
|
|
||||||
QueuePops,
|
|
||||||
DepthReads,
|
|
||||||
LocalRedraws,
|
|
||||||
SizeChanges,
|
|
||||||
ReaderEdges,
|
|
||||||
PrimitiveWrites,
|
|
||||||
TextRenders,
|
|
||||||
TextShapeHits,
|
|
||||||
TextShapes,
|
|
||||||
TextBreaks,
|
|
||||||
GlyphPlacements,
|
|
||||||
OutsidePinnedLen,
|
|
||||||
OutsideRelBase,
|
|
||||||
OutsideRegion,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Counter {
|
labelled! {
|
||||||
const COUNT: usize = Self::OutsideRegion as usize + 1;
|
pub(crate) enum Counter {
|
||||||
|
Updates = "updates",
|
||||||
const NAMES: [&'static str; Self::COUNT] = [
|
DrawRequests = "draw requests",
|
||||||
"updates",
|
WidgetDraws = "widget draws",
|
||||||
"draw requests",
|
RegionNodeDraws = "region-node draws",
|
||||||
"widget draws",
|
SizeReads = "draw-result size reads",
|
||||||
"region-node draws",
|
HintHits = "hint hits",
|
||||||
"draw-result size reads",
|
HintMisses = "hint misses",
|
||||||
"hint hits",
|
ReuseAttempts = "reuse attempts",
|
||||||
"hint misses",
|
ReuseExact = "reuse exact",
|
||||||
"reuse attempts",
|
ReuseMoved = "reuse moved",
|
||||||
"reuse exact",
|
ReuseDirty = "reuse: dirty",
|
||||||
"reuse moved",
|
ReuseUndrawn = "reuse: nothing drawn to keep",
|
||||||
"reuse: dirty",
|
ReuseWrongParent = "reuse: wrong parent",
|
||||||
"reuse: wrong parent",
|
ReuseRemapped = "reuse remapped",
|
||||||
"reuse remapped",
|
ReuseOutside = "reuse: outside what it holds for",
|
||||||
"reuse: outside what it holds for",
|
ReuseWrongLayer = "reuse: another layer",
|
||||||
"reuse: another layer",
|
ReuseWrongNode = "reuse: region-node choice changed",
|
||||||
"reuse: region-node choice changed",
|
ReuseWrongMask = "reuse: a different inherited mask",
|
||||||
"redraw queue pops",
|
QueuePops = "redraw queue pops",
|
||||||
"depth reads",
|
DepthReads = "depth reads",
|
||||||
"local redraws",
|
LocalRedraws = "local redraws",
|
||||||
"size changes",
|
SizeChanges = "size changes",
|
||||||
"reader edges",
|
ReaderEdges = "reader edges",
|
||||||
"primitive writes",
|
PrimitiveWrites = "primitive writes",
|
||||||
"text renders",
|
TextRenders = "text renders",
|
||||||
"text shape hits",
|
TextShapeHits = "text shape hits",
|
||||||
"text shapes",
|
TextShapes = "text shapes",
|
||||||
"text line breaks",
|
TextBreaks = "text line breaks",
|
||||||
"glyph placements",
|
GlyphPlacements = "glyph placements",
|
||||||
"reuse outside: the length it was pinned to",
|
OutsidePinnedLen = "reuse outside: the length it was pinned to",
|
||||||
"reuse outside: a rel base",
|
OutsideRelBase = "reuse outside: a rel base",
|
||||||
"reuse outside: a region length",
|
OutsideRegion = "reuse outside: a region length",
|
||||||
];
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
labelled! {
|
||||||
pub(crate) enum TimerKind {
|
pub(crate) enum TimerKind {
|
||||||
Update,
|
Update = "update total",
|
||||||
FullLayout,
|
FullLayout = "full layout",
|
||||||
IncrementalLayout,
|
IncrementalLayout = "incremental layout",
|
||||||
TextRender,
|
TextRender = "text render",
|
||||||
TextShape,
|
TextShape = "text shape",
|
||||||
TextBreak,
|
TextBreak = "text line break",
|
||||||
GlyphPlacement,
|
GlyphPlacement = "glyph placement",
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimerKind {
|
|
||||||
const COUNT: usize = Self::GlyphPlacement as usize + 1;
|
|
||||||
|
|
||||||
const NAMES: [&'static str; Self::COUNT] = [
|
|
||||||
"update total",
|
|
||||||
"full layout",
|
|
||||||
"incremental layout",
|
|
||||||
"text render",
|
|
||||||
"text shape",
|
|
||||||
"text line break",
|
|
||||||
"glyph placement",
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -251,6 +221,8 @@ pub enum ReuseOutcome {
|
|||||||
Dirty,
|
Dirty,
|
||||||
WrongParent,
|
WrongParent,
|
||||||
WrongLayer,
|
WrongLayer,
|
||||||
|
WrongMask,
|
||||||
|
WrongNode,
|
||||||
Remapped,
|
Remapped,
|
||||||
Outside,
|
Outside,
|
||||||
Undrawn,
|
Undrawn,
|
||||||
|
|||||||
@@ -51,8 +51,6 @@ pub struct ActiveData {
|
|||||||
/// An owned mask holds one reference independently of its primitives.
|
/// An owned mask holds one reference independently of its primitives.
|
||||||
pub mask_region: Option<UiRegion>,
|
pub mask_region: Option<UiRegion>,
|
||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetId>,
|
||||||
/// The children whose size this widget read while drawing.
|
|
||||||
pub size_deps: Vec<WidgetId>,
|
|
||||||
/// The movable region its primitives are positioned through: its own when
|
/// The movable region its primitives are positioned through: its own when
|
||||||
/// opted in, otherwise the nearest ancestor's.
|
/// opted in, otherwise the nearest ancestor's.
|
||||||
pub move_idx: MoveIdx,
|
pub move_idx: MoveIdx,
|
||||||
|
|||||||
@@ -489,7 +489,6 @@ impl UiRenderState {
|
|||||||
primitives,
|
primitives,
|
||||||
mask_region,
|
mask_region,
|
||||||
children,
|
children,
|
||||||
size_deps,
|
|
||||||
declared: rsc.widgets().declared_lens(id),
|
declared: rsc.widgets().declared_lens(id),
|
||||||
own_align: rsc.widgets().alignment(id),
|
own_align: rsc.widgets().alignment(id),
|
||||||
move_idx,
|
move_idx,
|
||||||
@@ -570,12 +569,18 @@ impl UiRenderState {
|
|||||||
};
|
};
|
||||||
if !active.drawn {
|
if !active.drawn {
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::reuse(id, ReuseOutcome::Undrawn);
|
{
|
||||||
|
diag::bump(Counter::ReuseUndrawn);
|
||||||
|
diag::reuse(id, ReuseOutcome::Undrawn);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if active.is_region_node() != info.region_node {
|
if active.is_region_node() != info.region_node {
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::ReuseWrongNode);
|
{
|
||||||
|
diag::bump(Counter::ReuseWrongNode);
|
||||||
|
diag::reuse(id, ReuseOutcome::WrongNode);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Drawn on another layer: the drawing sits in that layer's list and
|
// Drawn on another layer: the drawing sits in that layer's list and
|
||||||
@@ -591,7 +596,15 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Its primitives name the mask it inherited, and a masking parent
|
||||||
|
// that redrew pushed another: keeping them would clip them by one
|
||||||
|
// nothing updates again.
|
||||||
if active.parent_mask != info.mask {
|
if active.parent_mask != info.mask {
|
||||||
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
|
{
|
||||||
|
diag::bump(Counter::ReuseWrongMask);
|
||||||
|
diag::reuse(id, ReuseOutcome::WrongMask);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Drawn somewhere else in the tree: its box is in coordinates it no
|
// Drawn somewhere else in the tree: its box is in coordinates it no
|
||||||
@@ -800,7 +813,6 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
// After the descendants, whose slots name this one as their parent.
|
// After the descendants, whose slots name this one as their parent.
|
||||||
self.drop_slot(id);
|
self.drop_slot(id);
|
||||||
active.size_deps.clear();
|
|
||||||
active.drawn = false;
|
active.drawn = false;
|
||||||
self.active.insert(id, active);
|
self.active.insert(id, active);
|
||||||
}
|
}
|
||||||
@@ -844,7 +856,6 @@ impl UiRenderState {
|
|||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
mask_region: None,
|
mask_region: None,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
size_deps: Vec::new(),
|
|
||||||
move_idx: info.parent_move,
|
move_idx: info.parent_move,
|
||||||
declared: Declared::NONE,
|
declared: Declared::NONE,
|
||||||
own_align: rsc.widgets().alignment(id),
|
own_align: rsc.widgets().alignment(id),
|
||||||
@@ -1105,8 +1116,11 @@ impl UiRenderState {
|
|||||||
{
|
{
|
||||||
active.answer = was_answer;
|
active.answer = was_answer;
|
||||||
}
|
}
|
||||||
|
// Against the box it was asked in, which is what both contracts are
|
||||||
|
// about. Where the answer put the drawing is shorter than that
|
||||||
|
// wherever the widget reported less than it was offered.
|
||||||
if active.holds.covers(was_holds)
|
if active.holds.covers(was_holds)
|
||||||
&& was_holds.contains(self.output_size, active.rel_base, active.placement)
|
&& was_holds.contains(self.output_size, active.rel_base, active.region)
|
||||||
{
|
{
|
||||||
active.holds = was_holds;
|
active.holds = was_holds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,14 +36,6 @@ impl SizeRule {
|
|||||||
Self::Exact(len) => Some(*len),
|
Self::Exact(len) => Some(*len),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The length a widget reporting `reported` ends up with.
|
|
||||||
pub fn apply(&self, reported: LayoutLen) -> LayoutLen {
|
|
||||||
match self {
|
|
||||||
Self::Free => reported,
|
|
||||||
Self::Exact(len) => *len,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LayoutLen> for SizeRule {
|
impl From<LayoutLen> for SizeRule {
|
||||||
|
|||||||
@@ -1477,3 +1477,59 @@ fn a_redrawn_subtree_is_not_undrawn_by_the_parent_it_left() {
|
|||||||
assert_eq!(h.region(&leaf), before);
|
assert_eq!(h.region(&leaf), before);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A leaf that reports less than the box it is given and states which lengths
|
||||||
|
/// of that box its drawing holds for, so a test can widen the contract
|
||||||
|
/// without changing the answer.
|
||||||
|
struct Contracted {
|
||||||
|
holds: std::ops::RangeInclusive<Px>,
|
||||||
|
size: Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Contracted {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
painter.holds(Axis::X, self.holds.clone());
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CountedParent {
|
||||||
|
inner: StrongWidget,
|
||||||
|
draws: Rc<Cell<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for CountedParent {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
self.draws.set(self.draws.get() + 1);
|
||||||
|
painter.widget(&self.inner).size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn widening_what_a_drawing_holds_for_does_not_relay_out_the_parent() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let child = Contracted {
|
||||||
|
holds: Px::from_int(300)..=Px::from_int(500),
|
||||||
|
size: Size::from((100, 200)),
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let draws = Rc::new(Cell::new(0));
|
||||||
|
let root = CountedParent {
|
||||||
|
inner: child.upgrade(&mut h.rsc),
|
||||||
|
draws: draws.clone(),
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(root);
|
||||||
|
let settled = draws.get();
|
||||||
|
|
||||||
|
// The same answer, good for more boxes than before, so the guarantee the
|
||||||
|
// parent kept still holds.
|
||||||
|
h.rsc[child].holds = Px::from_int(200)..=Px::from_int(600);
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
draws.get(),
|
||||||
|
settled,
|
||||||
|
"a wider contract for the same answer is not a change to lay out"
|
||||||
|
);
|
||||||
|
}
|
||||||
+40
-47
@@ -47,57 +47,50 @@ fn check_plan(grown: &Plan, seed: u64, depth: usize, case: Case) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! case {
|
/// A test per case, and the list of which cases have one, from the same
|
||||||
($name:ident, $case:expr) => {
|
/// place. A case the ordinary suite leaves out runs only in the long scan,
|
||||||
#[test]
|
/// which nobody runs by hand.
|
||||||
fn $name() {
|
macro_rules! cases {
|
||||||
for seed in SEEDS {
|
($($name:ident = $case:expr,)*) => {
|
||||||
check(seed, depth(), $case);
|
$(
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
for seed in SEEDS {
|
||||||
|
check(seed, depth(), $case);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)*
|
||||||
|
|
||||||
|
const NAMED: [Case; [$($case,)*].len()] = [$($case,)*];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case!(
|
cases! {
|
||||||
many_widgets_redrawing_at_once_leaves_every_box_where_it_was,
|
many_widgets_redrawing_at_once_leaves_every_box_where_it_was = Case::RepaintSome,
|
||||||
Case::RepaintSome
|
everything_redrawing_at_once_leaves_every_box_where_it_was = Case::Repaint,
|
||||||
);
|
a_resize_lands_where_starting_at_that_size_would = Case::Resize,
|
||||||
case!(
|
a_resize_and_a_repaint_land_where_starting_that_way_would = Case::ResizeRepaint,
|
||||||
everything_redrawing_at_once_leaves_every_box_where_it_was,
|
a_size_change_after_a_resize_lands_the_same_way = Case::ResizeSize,
|
||||||
Case::Repaint
|
a_resize_after_a_size_change_lands_the_same_way = Case::SizeResize,
|
||||||
);
|
a_size_change_lands_where_growing_it_that_way_would = Case::Size,
|
||||||
case!(
|
every_size_changing_at_once_lands_where_growing_it_that_way_would = Case::EverySize,
|
||||||
a_resize_lands_where_starting_at_that_size_would,
|
an_alignment_change_lands_where_growing_it_that_way_would = Case::Align,
|
||||||
Case::Resize
|
giving_and_taking_a_movable_region_rebuilds_what_resolves_it = Case::RegionNode,
|
||||||
);
|
reordering_a_span_lands_where_growing_it_that_way_would = Case::Reorder,
|
||||||
case!(
|
}
|
||||||
a_resize_and_a_repaint_land_where_starting_that_way_would,
|
|
||||||
Case::ResizeRepaint
|
/// The shuffles are one test between them, so they are the only cases `ALL`
|
||||||
);
|
/// may hold without a test of their own.
|
||||||
case!(
|
#[test]
|
||||||
a_size_change_after_a_resize_lands_the_same_way,
|
fn every_case_runs_without_the_long_scan() {
|
||||||
Case::ResizeSize
|
for case in ALL {
|
||||||
);
|
assert!(
|
||||||
case!(
|
NAMED.contains(&case) || matches!(case, Case::Shuffle(_)),
|
||||||
a_size_change_lands_where_growing_it_that_way_would,
|
"{} runs only in the long seed scan; give it a case here",
|
||||||
Case::Size
|
case.name()
|
||||||
);
|
);
|
||||||
case!(
|
}
|
||||||
every_size_changing_at_once_lands_where_growing_it_that_way_would,
|
}
|
||||||
Case::EverySize
|
|
||||||
);
|
|
||||||
case!(
|
|
||||||
an_alignment_change_lands_where_growing_it_that_way_would,
|
|
||||||
Case::Align
|
|
||||||
);
|
|
||||||
case!(
|
|
||||||
giving_and_taking_a_movable_region_rebuilds_what_resolves_it,
|
|
||||||
Case::RegionNode
|
|
||||||
);
|
|
||||||
case!(
|
|
||||||
reordering_a_span_lands_where_growing_it_that_way_would,
|
|
||||||
Case::Reorder
|
|
||||||
);
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn adding_and_removing_span_children_lands_where_growing_it_that_way_would() {
|
fn adding_and_removing_span_children_lands_where_growing_it_that_way_would() {
|
||||||
|
|||||||
Reference in new issue
Block a user