Document independent widget bounds and measured layout costs

This commit is contained in:
iris-ai committed 2026-09-20 19:48:28 -04:00
1 parent ddaf9b37d6
commit 17423a57b8
3 files changed
+96 -5

No files matched your search

+39 -4
View File
@@ -4,10 +4,42 @@ Where the work in flight stands. The settled layout design, the vocabulary
and the measurement method are in `docs/LAYOUT.md`; what the review of #19 and the measurement method are in `docs/LAYOUT.md`; what the review of #19
found is in `docs/LAYOUT_LOG.md`. found is in `docs/LAYOUT_LOG.md`.
## Built-in bounds replace `MaxSize` on PR #19
Bryan clarified that the built-in attribute should replace the wrapper.
Work is in `/home/bob/repos/iris-deferred`, on `layout/one-ask`, at
`2ac0843` (pushed). Bounds now constrain the offer in `Placing::ask`; declarations
are retained as resolved window lengths, so movement does not decide the
bound again. `.max_width`/`.max_height` now edit the same widget, like the
minimum helpers, and `MaxSize` is removed. Preferred lengths and bounds
are independent, so either order of `.width` and `.max_width` works. The
app pin is unchanged.
The review fixed three cases: `window_holds` erasing an ask's bound crossing
(seed 104, depth 5), inactive bound edits failing to update the parent's offer
dependencies (seed 144, depth 6, reduced further to three widgets), and a
fractional intrinsic cap being read against its already-allocated slot.
The latter has an absolute 75-in-300 geometry check; warm/cold agreement
alone cannot catch it.
207 ordinary and 211 diagnostic tests pass, with warning-clean Clippy both
ways. All three layout scans pass (400 depth 5, 1,000 depth 6, 2,000 depth 4),
as do 400 depth-5 trees in each of three deferred-request corpora. All 34,986
unbounded cold boxes match `4cb6f68`; bounded offers intentionally change.
The final unbounded-path optimization preserves every bounded cold box too.
Five-run release instruction medians show capped rows using 21.7729.64%
less CPU work than the wrapper. Plain/fixed-width retained resizes differ by
under 0.05%; forced redraws improve about 0.3%. Text workloads differ by at
most 0.13%. Independent preferences and bounds cost 32 extra inline bytes per
widget; all three allocation fixtures remain allocation-free after warm-up.
`docs/LAYOUT.md` records the contract and measurements. Artifacts are in
`/tmp/attribute-bounds/`.
## A quality sweep of the deferred system is submitted on PR #19 ## A quality sweep of the deferred system is submitted on PR #19
`layout/one-ask` is at `4cb6f68` in `/home/bob/repos/iris-deferred`. The tenth The tenth sweep landed at `4cb6f68` in `/home/bob/repos/iris-deferred`. It is
sweep is the first review of `76aaf06` through `0e838e9` -- the bound rules, the first review of `76aaf06` through `0e838e9` -- the bound rules,
the deferred request path and the invalidation fix. Ten findings, the largest the deferred request path and the invalidation fix. Ten findings, the largest
two being a bound's held length looked up a second time through a value that two being a bound's held length looked up a second time through a value that
could not promise the end it named (`Bound::at` `expect`ed it), and a bound in could not promise the end it named (`Bound::at` `expect`ed it), and a bound in
@@ -176,7 +208,9 @@ repeated 34,492, which is a `wc -l` of the whole run rather than of its box
lines, so count the lines that are a box (`grep -cE '^[0-9]+ [0-9]+ '`). It is the only thing that catches two lines, so count the lines that are a box (`grep -cE '^[0-9]+ [0-9]+ '`). It is the only thing that catches two
same-typed values being swapped, which is the failure mode of a rename or a same-typed values being swapped, which is the failure mode of a rename or a
move. The repair moved 650 of those boxes, all from the collapsed-share move. The repair moved 650 of those boxes, all from the collapsed-share
correction; every commit since has been byte-identical to `84dad21`. correction. Subsequent review-only changes preserved cold geometry through
`4cb6f68`; the offered-bound change in `2ac0843` intentionally changes bounded
geometry while preserving all unbounded boxes.
**Only when the change can alter what layout computes**: the three seed scans **Only when the change can alter what layout computes**: the three seed scans
-- 400 at depth 5, 1,000 at depth 6, 2,000 at depth 4. They cost about a -- 400 at depth 5, 1,000 at depth 6, 2,000 at depth 4. They cost about a
@@ -257,7 +291,8 @@ glyphs do not follow a shortened entry.
(`SizeRule::{Min, Max, Clamp}`); a widget holds the box (`MaxSize`, which (`SizeRule::{Min, Max, Clamp}`); a widget holds the box (`MaxSize`, which
`.max_width`/`.max_height` build). Bryan settled the split on 2026-09-20 `.max_width`/`.max_height` build). Bryan settled the split on 2026-09-20
after four readings were measured; the reasoning is in `docs/LAYOUT_LOG.md`. after four readings were measured; the reasoning is in `docs/LAYOUT_LOG.md`.
The deferred-request work fixes stale relative-bound reuse by tracking the Bryan superseded that split with the built-in bounds work above. The
deferred-request work fixes stale relative-bound reuse by tracking the
input base and comparing resolved bounds, with a reduced natural-size-hint input base and comparing resolved bounds, with a reduced natural-size-hint
regression and 400 depth-5 relative-bound trees. Intrinsic bounds remain regression and 400 depth-5 relative-bound trees. Intrinsic bounds remain
`Len`s; comparisons involving leftover use `SizeRequest`. Ordinary generated `Len`s; comparisons involving leftover use `SizeRequest`. Ordinary generated
+51
View File
@@ -12,6 +12,57 @@ in flight stands. The sections from "Three names, and the one argument that
says them" onwards are the settled design, the findings that outlived the says them" onwards are the settled design, the findings that outlived the
working log, and the measurement method. working log, and the measurement method.
## Bounds on the offered box
`SizeRule` carries an optional preferred request and independent min/max bounds.
The bounds constrain both the box offered to a widget and its reported size.
`.min_width`, `.max_width` and their height variants edit that widget's rules
and preserve its identity and type; `MaxSize` is removed. Setting `.width` or `.height` preserves the bounds; setting a bound
preserves the preferred request and the other bound, in either call order.
`Widgets::set_len` makes the same edit at runtime; `set_size_rule` replaces
the complete axis. This replaces the rule/widget split recorded in
`LAYOUT_LOG.md` (Bryan, 2026-09-20).
The shared ask resolves declarations and bounds against the incoming rel
base, compares the offer, and retains the chosen box length in `Declared`.
These are window lengths. Placement aligns a retained length in its destination
without resolving it again or comparing the destination against the bound.
The comparison's window range and incoming-region dependency invalidate the
ask when its inputs change. Ask constraints are combined after the draw, so a
widget widening its own `window_holds` cannot erase a bound's crossing.
Fractional rules also track their incoming base. Allocated slots use the
allocator's base for bounds; applying a 25% cap to its own 25% slot would
shrink it twice. Changing an inactive bound also reaches the parent, because
it changes which offers the parent can safely reuse.
A cap constrains wrapping and scroll viewports, but short intrinsic content
can still report less than the cap. Shares remain deferred requests for the
parent to allocate; the offered box does not replace their reported weight.
Measured against `4cb6f68`, medians of five release-process instruction counts:
| Fixture | Before | After | Change |
| --- | ---: | ---: | ---: |
| 128 capped rows, 2,000 resizes | 7.387B | 5.779B | -21.77% |
| Same, every widget redrawn | 13.864B | 9.755B | -29.64% |
| 128 plain rows, every widget redrawn | 10.041B | 10.010B | -0.31% |
| 128 fixed-width rows, every widget redrawn | 10.085B | 10.056B | -0.29% |
| 40 text rows, 1,000 resizes | 9.460B | 9.472B | +0.13% |
Plain and fixed-width retained resizes differ by under 0.05%; text repaint,
edit and scroll differ by -0.05%, +0.07% and +0.06%. `tests/bounds_cost.rs`
asserts the same geometry for the old builders and new attributes, with 513
active widgets before and 385 after in the capped fixture. The text runs use
`tests/revision_cost.rs`. These include cold setup and do not measure GPU or
phone frame times. The unbounded cold dump remains identical at 34,986 boxes;
bounded geometry changes intentionally because the offer is now constrained.
The storage tradeoff is explicit: a `SizeRule` is now 40 bytes rather than 24,
so independent preferences and bounds add 32 inline bytes per widget across
both axes. Plain, expression-bounded and attribute-bounded fixtures each
perform zero allocations over 100 forced-redraw resize frames after warm-up.
Bounds introduce no heap allocation; an expression also loses
its former extra outer `Arc`. Artifacts are in `/tmp/attribute-bounds/`.
## Deferred size requests on PR #19 ## Deferred size requests on PR #19
`Widget::draw` still returns the small, copyable `Size` of two `LayoutLen`s. `Widget::draw` still returns the small, copyable `Size` of two `LayoutLen`s.
+6 -1
View File
@@ -872,7 +872,12 @@ is its *placement*, and the length a fraction resolves against is its *rel
base*. A container says all three in one `PlaceDesc` argument. `Widget::draw` base*. A container says all three in one `PlaceDesc` argument. `Widget::draw`
remains the only layout body. PR #19 also has optional size-request discovery remains the only layout body. PR #19 also has optional size-request discovery
for deferred min/max/clamp expressions (2026-09-20); ordinary widgets still for deferred min/max/clamp expressions (2026-09-20); ordinary widgets still
return `Size`, and plain requests retain symbolic allocation. The invariants, return `Size`, and plain requests retain symbolic allocation. Built-in
min/max/clamp rules also constrain the offered box; the dimension helpers
edit the widget's own attributes, replacing the `MaxSize` wrapper. Preferred
lengths and bounds are independent, so setting `.width` preserves `.max_width`
and vice versa. Bound choices are retained as resolved lengths so moving a
drawing cannot resolve them against a different box. The invariants,
the measured costs, the fuzzing method and the failed hypotheses are all in the measured costs, the fuzzing method and the failed hypotheses are all in
`docs/LAYOUT.md`; the app's pin at `docs/LAYOUT.md`; the app's pin at
`32f6ad8` predates every part of it, so nothing here depends on it until the `32f6ad8` predates every part of it, so nothing here depends on it until the