Record the derived-box rule and what the fuzzers found in the handoff
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
98dcb31c23
commit
39409b6683
1 file changed
+61
-22
@@ -9,15 +9,20 @@ Canonical `main` is **`ca2b4b2`** (#17, the headless rig). Sixteen slices are
|
||||
in.
|
||||
|
||||
**#18 `split/18-position-chain`** is open and finished apart from one decision:
|
||||
worktree `/home/bob/repos/iris-pr18`, head `3f7cd82`, twenty-four commits,
|
||||
worktree `/home/bob/repos/iris-pr18`, head `f61e893`, thirty-four commits,
|
||||
workspace tests passing, fmt and clippy clean. It is LAYOUT.md §2's position
|
||||
chain, generalised to boxes. The last six are a performance pass that changes
|
||||
no layout: the two halves of the text fix (`e5f8b6b`, `2525637`), three
|
||||
retained-layout details that claimed something they did not do (`f1a47e9`),
|
||||
the many-widgets-dirty cases (`77bb75e`), what the settle order is holding up
|
||||
(`bf94380`), and carrying a widget's depth rather than walking for it
|
||||
(`3f7cd82`). Every one of them keeps the five reference renders and the resize
|
||||
render byte-identical, and the hundred-seed sweep passes. `/home/bob/repos/ai-app-2` is on `rustify`, worktree clean.
|
||||
chain, generalised to boxes. After the performance pass (`e5f8b6b` to
|
||||
`3f7cd82`) came the fuzzers and what they found: the shrinking fuzzer
|
||||
(`b7caab3`, `386a0d1`), `SetSize` measuring in its declared length
|
||||
(`c596bf1`), text re-breaking across the interval a break holds for
|
||||
(`9913194`), and a dirty widget measured where its parent asked rather than
|
||||
in a box its own answer decided (`02ff8c7`). `f61e893` restores `abs()` in
|
||||
the rect shader, which `7c50a3e`'s rename had taken with it so that every
|
||||
window failed shader validation while `cargo test` stayed green, and adds
|
||||
the device-free shader validation test that would have caught it. The five
|
||||
reference renders and the resize render are byte-identical across all of
|
||||
it, and the hundred-seed sweep passes. `/home/bob/repos/ai-app-2` is on
|
||||
`rustify`, worktree clean.
|
||||
|
||||
**The one thing waiting on the owner.** `tabs` at 1920x1200 is no longer
|
||||
byte-identical to `upstream/main`: 1,283 pixels of 2.3M (0.06%), two
|
||||
@@ -70,7 +75,7 @@ Invariants, not history. Everything in `core/src/ui` rests on them.
|
||||
tree depth, which is the difference between free and +42.6%.
|
||||
- **A widget's region is held in the coordinates of the slot it draws in**, so
|
||||
a placed widget draws against `UiRegion::FULL` and its box lives in its slot.
|
||||
`ActiveData::region` is the box it was *offered*, in its parent's slot
|
||||
`ActiveData::region` is the box it was *placed in*, in its parent's slot
|
||||
coordinates, and `ActiveData::parent_move` is the slot that is in;
|
||||
`window_region` composes the one through the other, which is the walk the
|
||||
shader does.
|
||||
@@ -273,10 +278,14 @@ aligned size-changing chain.
|
||||
The branches are invariants rather than widget exceptions:
|
||||
|
||||
- A dirty widget draws locally first only while its retained box has the same
|
||||
pixel size. If its returned `Size` is unchanged, no size reader can observe
|
||||
the repaint and no ancestor draws. If the size changed, the dependent path
|
||||
lays out. A changed pixel box takes the conservative path first, which is the
|
||||
condition the discarded `/tmp/escalate.patch` missed on resize.
|
||||
pixel size **and that box is a constraint rather than its own answer**. If
|
||||
its returned `Size` is unchanged, no size reader can observe the repaint and
|
||||
no ancestor draws. If the size changed, the dependent path lays out. A
|
||||
changed pixel box takes the conservative path first, which is the condition
|
||||
the discarded `/tmp/escalate.patch` missed on resize. A box decided from the
|
||||
widget's own size, on an axis that size reads, takes it too: measuring there
|
||||
can only repeat the answer, so the reader that decided it draws instead --
|
||||
see "a box that is its own answer" below.
|
||||
- Dirty widgets settle deepest-first. A changed size queues only its immediate
|
||||
reader; propagation stops as soon as a reader's own answer stays unchanged.
|
||||
Output resize is the distinct invariant: all pixel-dependent leaves and
|
||||
@@ -537,9 +546,9 @@ have taken another word, so at any width down to the longest of them the same
|
||||
break holds. `TextBuffer::shape` answers across that interval, with a
|
||||
sub-pixel tolerance for the edge.
|
||||
|
||||
**What is left is not a stale drawing -- the layout has two answers.** A span
|
||||
measures its children in its own box, and its own box is what its parent gave
|
||||
it, from the size it reported, from those children. Four widgets:
|
||||
**A box that is its own answer.** A span measures its children in its own
|
||||
box, and its own box is what its parent gave it, from the size it reported,
|
||||
from those children. Four widgets:
|
||||
|
||||
Aligned(mid, -, Span[ Text(wrap), OneLine ])
|
||||
|
||||
@@ -550,12 +559,42 @@ the span is offered the window, the text is asked for 334.06 and answers
|
||||
318.45, and the span settles at 624.38. **Both are stable.** Which one you get
|
||||
depends on what the tree was before.
|
||||
|
||||
So no rule about when to keep a drawing can fix it, and several were tried.
|
||||
`Painter::settle` -- place a child into its own reported size without measuring
|
||||
it there -- passes all four cases in `tests/unsettled.rs` and fails two in
|
||||
`generated.rs`, in `Aligned` alone, in `Span` alone, with the child force-drawn
|
||||
first or not. The fix wants the constraint a container measures under to be
|
||||
something it is *given* rather than something it *ends up with*.
|
||||
No rule about when to keep a drawing can fix that, and several were tried
|
||||
(`Painter::settle` -- place a child into its own reported size without
|
||||
measuring it there -- passed the hand-written cases and failed two generated
|
||||
ones). The defect is in *where a dirty widget is measured*: a local redraw
|
||||
draws it in the box it was placed in, and when a reader decided that box from
|
||||
the widget's answer, the old answer is a fixed point of measuring there
|
||||
whatever the content now says. So `ActiveData::offered_px` keeps the pixel
|
||||
size of the box the parent *first* asked about the child in -- `known_len` or
|
||||
the first `place` of a draw -- beside `px`, the box it drew against. A dirty
|
||||
widget whose size reads an axis on which some reader up its chain gave what
|
||||
it read a box other than the one it asked in is not drawn locally: the chain
|
||||
is marked and the parent of the highest such placement draws, since above it
|
||||
every box is a constraint. The walk has to go up the whole reader chain rather
|
||||
than one edge, because a pass-through hands a derived box down unchanged: the
|
||||
second shrunk case is a `Scroll` placing a `SetSize` in a box the content
|
||||
decided, and the span under it placed once, in that box.
|
||||
|
||||
That made `Scroll` show a second thing: it read its box's pixel length for
|
||||
the clamp through `px_len`, which records the reported size as depending on
|
||||
the box, and it does not -- a `Scroll`'s size is its content's. Every scroll
|
||||
tick was then a size question asked in a derived box, at 34x the
|
||||
instructions. `Painter::px_len_for_draw` is the read that records nothing,
|
||||
for a draw whose reported size does not follow from it. Instructions per
|
||||
frame on the depth-8 rig, 1000 frames, against the head before this:
|
||||
|
||||
| | before | after |
|
||||
| --- | --- | --- |
|
||||
| `many`, 32 dirty | 0.66M | 0.74M |
|
||||
| `many`, 130 dirty | 27.7M | 26.5M |
|
||||
| `resize` | 15.8M | 15.0M |
|
||||
| `scroll`, `repaint`, `size` | 0.36M | 0.36M |
|
||||
|
||||
The small load pays 12% for the escalations that are now required; the
|
||||
larger ones get cheaper because a `Scroll`'s retained size no longer goes
|
||||
invalid when its box changes. The reorder fuzzer passes 200 trees at depth 7
|
||||
in every case, and the sweep passes.
|
||||
|
||||
`OrthoSize::{Fill, Children}` -- a span choosing whether to fill across its
|
||||
axis or report its longest child -- is written and parked for the same reason:
|
||||
|
||||
Reference in new issue
Block a user