Compare commits
5
Commits
490918b789
...
394d5149a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
394d5149a5 | ||
|
|
4cbb242a5d | ||
|
|
d75a1e2129 | ||
|
|
1940e85c70 | ||
|
|
cb1bba4682 |
No files matched your search
+11
-1
@@ -138,6 +138,16 @@ impl<const SHIFT: u32> Fixed<SHIFT> {
|
|||||||
Self(narrow(shift_round(self.0 as i64 * by.0 as i64, BY)))
|
Self(narrow(shift_round(self.0 as i64 * by.0 as i64, BY)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A part of a span that is often nothing: no part of nothing is
|
||||||
|
/// nothing, for the cost of a comparison rather than a widening
|
||||||
|
/// multiply and a rounding.
|
||||||
|
pub const fn scaled<const BY: u32>(self, by: Fixed<BY>) -> Self {
|
||||||
|
match self.0 == 0 {
|
||||||
|
true => self,
|
||||||
|
false => self.mul(by),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Repeated a whole number of times, which no grid rounds.
|
/// Repeated a whole number of times, which no grid rounds.
|
||||||
pub const fn mul_int(self, by: i32) -> Self {
|
pub const fn mul_int(self, by: i32) -> Self {
|
||||||
Self(narrow(self.0 as i64 * by as i64))
|
Self(narrow(self.0 as i64 * by as i64))
|
||||||
@@ -179,7 +189,7 @@ impl<const SHIFT: u32> Fixed<SHIFT> {
|
|||||||
/// `from` and `to` a fraction of the way apart, the fraction being the
|
/// `from` and `to` a fraction of the way apart, the fraction being the
|
||||||
/// receiver -- the argument order [`crate::util::LerpUtil`] already uses.
|
/// receiver -- the argument order [`crate::util::LerpUtil`] already uses.
|
||||||
pub const fn lerp<const OF: u32>(self, from: Fixed<OF>, to: Fixed<OF>) -> Fixed<OF> {
|
pub const fn lerp<const OF: u32>(self, from: Fixed<OF>, to: Fixed<OF>) -> Fixed<OF> {
|
||||||
from.add(to.sub(from).mul(self))
|
from.add(to.sub(from).scaled(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn min(self, other: Self) -> Self {
|
pub const fn min(self, other: Self) -> Self {
|
||||||
|
|||||||
@@ -282,7 +282,26 @@ impl UiSpan {
|
|||||||
self.end += offset;
|
self.end += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The whole of the box it sits in: a span that composes to nothing and
|
||||||
|
/// a parent that changes nothing.
|
||||||
|
pub const fn is_full(&self) -> bool {
|
||||||
|
self.start.rel.raw() == Rel::ZERO.raw()
|
||||||
|
&& self.start.px.raw() == Px::ZERO.raw()
|
||||||
|
&& self.end.rel.raw() == Rel::ONE.raw()
|
||||||
|
&& self.end.px.raw() == Px::ZERO.raw()
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn within(&self, parent: &Self) -> Self {
|
pub const fn within(&self, parent: &Self) -> Self {
|
||||||
|
// A part that is the whole box is the box, and a box composed through
|
||||||
|
// the whole of its parent is itself. Both are exact -- multiplying by
|
||||||
|
// one rounds to what it started as -- and both are common enough to
|
||||||
|
// be worth four comparisons rather than four multiplies to find out.
|
||||||
|
if self.is_full() {
|
||||||
|
return *parent;
|
||||||
|
}
|
||||||
|
if parent.is_full() {
|
||||||
|
return *self;
|
||||||
|
}
|
||||||
Self {
|
Self {
|
||||||
start: self.start.within(parent),
|
start: self.start.within(parent),
|
||||||
end: self.end.within(parent),
|
end: self.end.within(parent),
|
||||||
@@ -292,6 +311,15 @@ impl UiSpan {
|
|||||||
pub const fn len(&self) -> Len {
|
pub const fn len(&self) -> Len {
|
||||||
self.end - self.start
|
self.end - self.start
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Both ends by the same amount, which is what moving a box without
|
||||||
|
/// changing its length does to every part of it.
|
||||||
|
pub const fn translated(self, by: Len) -> Self {
|
||||||
|
Self {
|
||||||
|
start: self.start + by,
|
||||||
|
end: self.end + by,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -302,6 +330,17 @@ pub struct UiRegion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UiRegion {
|
impl UiRegion {
|
||||||
|
/// Every part of the box by the same amount on each axis. Done to the
|
||||||
|
/// whole region rather than an end at a time, because that is what it is
|
||||||
|
/// -- and because four adds in a row are four adds, where four asked for
|
||||||
|
/// separately are four sequences.
|
||||||
|
pub const fn translated(self, x: Len, y: Len) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x.translated(x),
|
||||||
|
y: self.y.translated(y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const FULL: Self = Self {
|
pub const FULL: Self = Self {
|
||||||
x: UiSpan::FULL,
|
x: UiSpan::FULL,
|
||||||
y: UiSpan::FULL,
|
y: UiSpan::FULL,
|
||||||
|
|||||||
+92
-37
@@ -3,7 +3,7 @@ use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
|||||||
use crate::ui::painter::{declared_box, declared_lens, placed_box};
|
use crate::ui::painter::{declared_box, declared_lens, placed_box};
|
||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutLen, Len, MaskIdx, MoveIdx, Moves, Painter,
|
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutLen, Len, MaskIdx, MoveIdx, Moves, Painter,
|
||||||
PixelRegion, PxVec2, RegionAlign, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan, Weight,
|
PixelRegion, Px, PxVec2, RegionAlign, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan, Weight,
|
||||||
WidgetId, Widgets,
|
WidgetId, Widgets,
|
||||||
util::{HashMap, Vec2},
|
util::{HashMap, Vec2},
|
||||||
};
|
};
|
||||||
@@ -632,7 +632,7 @@ impl UiRenderState {
|
|||||||
self.moves.set(slot, region);
|
self.moves.set(slot, region);
|
||||||
} else {
|
} else {
|
||||||
let remap = RegionRemap::new(old_region, region)?;
|
let remap = RegionRemap::new(old_region, region)?;
|
||||||
self.remap_subtree(id, remap, info.parent_move, mask, rsc);
|
self.remap_subtree(id, &remap, info.parent_move, mask, rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
@@ -668,7 +668,7 @@ impl UiRenderState {
|
|||||||
fn remap_subtree(
|
fn remap_subtree(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: WidgetId,
|
id: WidgetId,
|
||||||
remap: RegionRemap,
|
remap: &RegionRemap,
|
||||||
parent_move: MoveIdx,
|
parent_move: MoveIdx,
|
||||||
inherited_mask: MaskIdx,
|
inherited_mask: MaskIdx,
|
||||||
rsc: &mut dyn UiRsc,
|
rsc: &mut dyn UiRsc,
|
||||||
@@ -1045,55 +1045,110 @@ fn same_pixel_region(a: PixelRegion, b: PixelRegion) -> bool {
|
|||||||
/// source extent can be translated but cannot recover fractions for a resize.
|
/// source extent can be translated but cannot recover fractions for a resize.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct RegionRemap {
|
struct RegionRemap {
|
||||||
from: UiRegion,
|
axes: [AxisRemap; 2],
|
||||||
to: UiRegion,
|
}
|
||||||
|
|
||||||
|
/// Moving one axis of a box into another, worked out once for the whole
|
||||||
|
/// subtree that moves with it. Every part of that subtree is divided by the
|
||||||
|
/// same extent and placed between the same two ends, so the ends and the
|
||||||
|
/// divisor belong here rather than in each part's arithmetic.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum AxisRemap {
|
||||||
|
/// A box that kept its length carries its parts by moving them, which is
|
||||||
|
/// exact. Dividing to find the fraction each sits at and multiplying to
|
||||||
|
/// place it again are two roundings, and they land a step from where
|
||||||
|
/// growing the tree that way does.
|
||||||
|
Translate(Len),
|
||||||
|
/// A box that changed length has to re-express each part as a fraction of
|
||||||
|
/// the new one, which is what a part of a box means.
|
||||||
|
Scale(AxisScale),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct AxisScale {
|
||||||
|
/// What the fraction is measured from, and what divides it. `whole` is
|
||||||
|
/// the common case of a box spanning the whole of its parent's, where
|
||||||
|
/// dividing by one is the expensive way to write a subtraction.
|
||||||
|
start_rel: Rel,
|
||||||
|
extent: Rel,
|
||||||
|
whole: bool,
|
||||||
|
/// `lerp` is `a + (b - a) * fraction`, and both ends are the same for
|
||||||
|
/// every part, so each is kept as its near end and its span.
|
||||||
|
from_px: Px,
|
||||||
|
from_px_span: Px,
|
||||||
|
to_rel: Rel,
|
||||||
|
to_rel_span: Rel,
|
||||||
|
to_px: Px,
|
||||||
|
to_px_span: Px,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegionRemap {
|
impl RegionRemap {
|
||||||
fn new(from: UiRegion, to: UiRegion) -> Option<Self> {
|
fn new(from: UiRegion, to: UiRegion) -> Option<Self> {
|
||||||
AXES.into_iter()
|
Some(Self {
|
||||||
.all(|axis| {
|
axes: [AxisRemap::new(from.x, to.x)?, AxisRemap::new(from.y, to.y)?],
|
||||||
let from = from.axis(axis);
|
|
||||||
from.start.rel != from.end.rel || from.len() == to.axis(axis).len()
|
|
||||||
})
|
})
|
||||||
.then_some(Self { from, to })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply(self, region: UiRegion) -> UiRegion {
|
fn apply(&self, region: UiRegion) -> UiRegion {
|
||||||
|
// A box that only moved carries every part of itself by the same two
|
||||||
|
// amounts, and that is the common move. Asking it once for the whole
|
||||||
|
// region is what lets it be eight adds in a row rather than four
|
||||||
|
// sequences with a branch each -- measured, it is where the time in a
|
||||||
|
// move goes.
|
||||||
|
if let [AxisRemap::Translate(x), AxisRemap::Translate(y)] = self.axes {
|
||||||
|
return region.translated(x, y);
|
||||||
|
}
|
||||||
UiRegion {
|
UiRegion {
|
||||||
x: self.apply_span(region.x, self.from.x, self.to.x),
|
x: self.axes[0].apply_span(region.x),
|
||||||
y: self.apply_span(region.y, self.from.y, self.to.y),
|
y: self.axes[1].apply_span(region.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_span(self, span: UiSpan, from: UiSpan, to: UiSpan) -> UiSpan {
|
impl AxisRemap {
|
||||||
UiSpan {
|
fn new(from: UiSpan, to: UiSpan) -> Option<Self> {
|
||||||
start: self.apply_scalar(span.start, from, to),
|
if from.len() == to.len() {
|
||||||
end: self.apply_scalar(span.end, from, to),
|
return Some(Self::Translate(to.start - from.start));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_scalar(self, scalar: Len, from: UiSpan, to: UiSpan) -> Len {
|
|
||||||
let extent = from.end.rel - from.start.rel;
|
let extent = from.end.rel - from.start.rel;
|
||||||
// A box that only moved, or that has no relative extent to divide,
|
// Without a relative extent there is no fraction to re-express: a box
|
||||||
// carries its parts by moving them, which is exact. Dividing to find
|
// of fixed length cannot say where its parts sit in a different one.
|
||||||
// the fraction each sits at and multiplying to place it again are two
|
if extent == Rel::ZERO {
|
||||||
// roundings, and they land a step from where growing the tree that
|
return None;
|
||||||
// way does. Where the box changed length there is nothing else to do,
|
|
||||||
// and the fraction is what a part means.
|
|
||||||
if from.len() == to.len() || extent == Rel::ZERO {
|
|
||||||
return scalar + to.start - from.start;
|
|
||||||
}
|
}
|
||||||
// A box that spans the whole of its parent's is the common one, and
|
Some(Self::Scale(AxisScale {
|
||||||
// dividing by one is the expensive way to write a subtraction.
|
start_rel: from.start.rel,
|
||||||
let offset = scalar.rel - from.start.rel;
|
extent,
|
||||||
let fraction = match extent == Rel::ONE {
|
whole: extent == Rel::ONE,
|
||||||
true => offset,
|
from_px: from.start.px,
|
||||||
false => offset / extent,
|
from_px_span: from.end.px - from.start.px,
|
||||||
|
to_rel: to.start.rel,
|
||||||
|
to_rel_span: to.end.rel - to.start.rel,
|
||||||
|
to_px: to.start.px,
|
||||||
|
to_px_span: to.end.px - to.start.px,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_span(&self, span: UiSpan) -> UiSpan {
|
||||||
|
UiSpan {
|
||||||
|
start: self.apply_scalar(span.start),
|
||||||
|
end: self.apply_scalar(span.end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_scalar(&self, scalar: Len) -> Len {
|
||||||
|
let scale = match self {
|
||||||
|
Self::Translate(by) => return scalar + *by,
|
||||||
|
Self::Scale(scale) => scale,
|
||||||
};
|
};
|
||||||
let from_px = fraction.lerp(from.start.px, from.end.px);
|
let offset = scalar.rel - scale.start_rel;
|
||||||
let to_rel = fraction.lerp(to.start.rel, to.end.rel);
|
let fraction = match scale.whole {
|
||||||
let to_px = fraction.lerp(to.start.px, to.end.px);
|
true => offset,
|
||||||
|
false => offset / scale.extent,
|
||||||
|
};
|
||||||
|
let from_px = scale.from_px + scale.from_px_span.scaled(fraction);
|
||||||
|
let to_rel = scale.to_rel + scale.to_rel_span.scaled(fraction);
|
||||||
|
let to_px = scale.to_px + scale.to_px_span.scaled(fraction);
|
||||||
Len::from_parts(to_rel, scalar.px - from_px + to_px)
|
Len::from_parts(to_rel, scalar.px - from_px + to_px)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-1
@@ -29,6 +29,17 @@ pub struct Edits {
|
|||||||
/// one. Region nodes change what a move writes and how deep a primitive's
|
/// one. Region nodes change what a move writes and how deep a primitive's
|
||||||
/// chain is, so a tree that never grows one leaves both untested.
|
/// chain is, so a tree that never grows one leaves both untested.
|
||||||
pub nodes: HashMap<usize, bool>,
|
pub nodes: HashMap<usize, bool>,
|
||||||
|
/// Whether a [`Branch`] takes the side it would take at any measurement,
|
||||||
|
/// rather than the side the one it made says. The oracle wants the
|
||||||
|
/// measured side -- that is the whole point of a branch, and how a widget
|
||||||
|
/// believing a measurement a cold start would not have given it becomes a
|
||||||
|
/// different tree. A rig measuring cost wants this instead: a fixture
|
||||||
|
/// whose shape moves with the thing being measured cannot be compared
|
||||||
|
/// with itself across a change to it, and seed 1 at depth 8 went from 88
|
||||||
|
/// drawn widgets and 2,298 primitive writes a frame to 115 and 8,209
|
||||||
|
/// across fixed point, which is three and a half times the work behind a
|
||||||
|
/// number read as three and a half times the cost.
|
||||||
|
pub fixed_branches: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@@ -286,7 +297,14 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
|
|||||||
let probe = self.node(depth - 1);
|
let probe = self.node(depth - 1);
|
||||||
let wide = self.node(depth - 1);
|
let wide = self.node(depth - 1);
|
||||||
let narrow = self.node(depth - 1);
|
let narrow = self.node(depth - 1);
|
||||||
let threshold = self.rng.below(500) as f32;
|
// Drawn either way, so the side a fixed branch takes is still a
|
||||||
|
// side the generator chose -- and it consumes the same randomness
|
||||||
|
// as a measured one, so the two grow the same ids.
|
||||||
|
let measured = self.rng.below(500) as f32;
|
||||||
|
let threshold = match self.edits.fixed_branches {
|
||||||
|
true => f32::MIN,
|
||||||
|
false => measured,
|
||||||
|
};
|
||||||
let id = Branch {
|
let id = Branch {
|
||||||
probe,
|
probe,
|
||||||
wide,
|
wide,
|
||||||
|
|||||||
@@ -92,9 +92,18 @@ fn trace_selected(tree: &Tree) {
|
|||||||
#[cfg(not(feature = "layout-diagnostics"))]
|
#[cfg(not(feature = "layout-diagnostics"))]
|
||||||
fn trace_selected(_: &Tree) {}
|
fn trace_selected(_: &Tree) {}
|
||||||
|
|
||||||
|
/// The shape a cost is measured on must not depend on what layout measured,
|
||||||
|
/// or two commits are compared on two different trees. See `Edits`.
|
||||||
|
fn rig_edits() -> Edits {
|
||||||
|
Edits {
|
||||||
|
fixed_branches: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn warm(seed: u64, depth: usize) -> (Harness, Tree) {
|
fn warm(seed: u64, depth: usize) -> (Harness, Tree) {
|
||||||
let mut harness = Harness::new(OUTPUT);
|
let mut harness = Harness::new(OUTPUT);
|
||||||
let (root, tree) = grow(&mut harness.rsc, seed, depth, &Edits::default());
|
let (root, tree) = grow(&mut harness.rsc, seed, depth, &rig_edits());
|
||||||
harness.state.root = Some(root);
|
harness.state.root = Some(root);
|
||||||
harness.frame();
|
harness.frame();
|
||||||
println!(
|
println!(
|
||||||
@@ -173,7 +182,7 @@ fn layout_cost() {
|
|||||||
|
|
||||||
if selected("cold") {
|
if selected("cold") {
|
||||||
let mut harness = Harness::new(OUTPUT);
|
let mut harness = Harness::new(OUTPUT);
|
||||||
let (root, tree) = grow(&mut harness.rsc, seed, depth, &Edits::default());
|
let (root, tree) = grow(&mut harness.rsc, seed, depth, &rig_edits());
|
||||||
harness.state.root = Some(root);
|
harness.state.root = Some(root);
|
||||||
println!(
|
println!(
|
||||||
"fixture: seed {seed}, depth {depth}, {} widgets",
|
"fixture: seed {seed}, depth {depth}, {} widgets",
|
||||||
|
|||||||
Reference in new issue
Block a user