Answer "have I asked this child?" in one read
A container's draw asked it once per child by searching the list of children it had added so far, and four other per-child steps searched a list too, so one draw cost the square of its children: 70% of a 1,600-child redraw was those searches. A draw takes a DrawId and leaves it on every widget it asks about; one note per widget is enough because the handle a container holds a child by cannot be cloned. tests/children_cost.rs is the rig that shows it, and it is the only one here that varies width: 3.680 ms to 0.811 ms at 1,600 children, and flat per child. Beside it, the rest of the fourteenth sweep of #19: a mask's rectangle resolved once per fragment instead of once per instance, which takes the storage buffers out of the fragment stage and is 8.8x on a screenful of deeply nested clips; TextBuffer::shape copying its attrs before the check that would not need them, which allocated once per named-family text per frame; a should_panic test on a debug assertion that made cargo test --release fail; Fixed::div, reached only by its own test; Moves::remove re-uploading an array it cannot have changed; and two comments the branch itself falsified. docs/LAYOUT_LOG.md has all eight with their measurements, the five things looked at and left, and what was verified.
This commit is contained in:
1 parent
cbccfb600a
commit
97fca76108
17 files changed
+588
-149
No files matched your search
@@ -1,9 +1,9 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::{
|
||||
ActiveData, Answer, Axis, Bounds, Declared, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len,
|
||||
MaskIdx, MoveIdx, Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Size, StrongWidget, UiRegion,
|
||||
UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
ActiveData, Answer, Axis, Bounds, Declared, DrawId, DrawLayers, IdLike, LayoutHolds, LayoutLen,
|
||||
Len, MaskIdx, MoveIdx, Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Size, StrongWidget,
|
||||
UiRegion, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
ui::painter::Ask,
|
||||
util::{HashMap, Vec2},
|
||||
};
|
||||
@@ -97,6 +97,10 @@ pub struct UiRenderState {
|
||||
pending: std::collections::BinaryHeap<(usize, WidgetId)>,
|
||||
pub(super) requests: crate::RequestArena,
|
||||
changed: Vec<WidgetId>,
|
||||
/// The last draw id handed out. Each draw takes a fresh one and leaves it
|
||||
/// on every widget it asks about, which is how it knows in one read
|
||||
/// whether it has asked already.
|
||||
last_draw: DrawId,
|
||||
request_readers: HashMap<WidgetId, crate::util::HashSet<WidgetId>>,
|
||||
pub moves: Moves,
|
||||
}
|
||||
@@ -113,6 +117,7 @@ impl UiRenderState {
|
||||
pending: Default::default(),
|
||||
requests: Default::default(),
|
||||
changed: Vec::new(),
|
||||
last_draw: DrawId::NONE,
|
||||
request_readers: Default::default(),
|
||||
moves: Default::default(),
|
||||
resized: false,
|
||||
@@ -322,6 +327,14 @@ impl UiRenderState {
|
||||
old: Option<ActiveData>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> Answer {
|
||||
let draw = self.next_draw();
|
||||
// Whoever asked about this widget wrote this, and a draw of the widget
|
||||
// itself is not that: the record is rebuilt below, so it is carried
|
||||
// across rather than reset.
|
||||
let asked_by = old
|
||||
.as_ref()
|
||||
.or_else(|| self.active.get(&id))
|
||||
.map_or(DrawId::NONE, |active| active.asked_by);
|
||||
let rel_base = info.rel_base;
|
||||
let (move_idx, region, retired_move) = match info.region_node {
|
||||
// A node entry is only a translation. Its local box keeps the
|
||||
@@ -383,6 +396,7 @@ impl UiRenderState {
|
||||
answer_under: LayoutHolds::ANY,
|
||||
depth: info.depth,
|
||||
move_idx,
|
||||
draw,
|
||||
rsc,
|
||||
};
|
||||
|
||||
@@ -419,6 +433,7 @@ impl UiRenderState {
|
||||
layer,
|
||||
own_layer: _,
|
||||
depth: _,
|
||||
draw: _,
|
||||
id,
|
||||
} = painter;
|
||||
|
||||
@@ -507,7 +522,7 @@ impl UiRenderState {
|
||||
region.to_px(window),
|
||||
);
|
||||
for c in &old_children {
|
||||
if !children.contains(c) {
|
||||
if !self.asked_in(*c, draw) {
|
||||
self.undraw_rec(*c, rsc);
|
||||
}
|
||||
}
|
||||
@@ -531,7 +546,7 @@ impl UiRenderState {
|
||||
// and a change there has to reach it. Asking answered whatever mark
|
||||
// it had: a hint is read live, and a drawing is not kept past one.
|
||||
for &dep in &size_deps {
|
||||
if !children.contains(&dep) {
|
||||
if !self.asked_in(dep, draw) {
|
||||
self.asked(
|
||||
dep,
|
||||
DrawInfo {
|
||||
@@ -574,6 +589,7 @@ impl UiRenderState {
|
||||
region,
|
||||
// Whoever asked writes the answer.
|
||||
answer: None,
|
||||
asked_by,
|
||||
re_asked: info.re_asked,
|
||||
size,
|
||||
holds,
|
||||
@@ -603,6 +619,21 @@ impl UiRenderState {
|
||||
}
|
||||
}
|
||||
|
||||
fn next_draw(&mut self) -> DrawId {
|
||||
self.last_draw = self.last_draw.next();
|
||||
self.last_draw
|
||||
}
|
||||
|
||||
/// Whether `draw` has asked about this widget, which is what it left on
|
||||
/// the widget's own record when it did. One widget is asked about by one
|
||||
/// container, since the handle a container holds a child by cannot be
|
||||
/// cloned, so one note per widget is enough to answer this.
|
||||
pub(super) fn asked_in(&self, id: WidgetId, draw: DrawId) -> bool {
|
||||
self.active
|
||||
.get(&id)
|
||||
.is_some_and(|active| active.asked_by == draw)
|
||||
}
|
||||
|
||||
/// Keeps a region node's entry across redraws because descendants retain
|
||||
/// its index.
|
||||
fn move_slot(&mut self, id: WidgetId, parent: MoveIdx, region: UiRegion) -> MoveIdx {
|
||||
@@ -956,6 +987,7 @@ impl UiRenderState {
|
||||
asked: PlaceDesc::WHOLE,
|
||||
region: UiRegion::FULL,
|
||||
answer: None,
|
||||
asked_by: DrawId::NONE,
|
||||
re_asked: false,
|
||||
size,
|
||||
holds: LayoutHolds::ANY,
|
||||
|
||||
Reference in new issue
Block a user