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.
|
in.
|
||||||
|
|
||||||
**#18 `split/18-position-chain`** is open and finished apart from one decision:
|
**#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
|
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
|
chain, generalised to boxes. After the performance pass (`e5f8b6b` to
|
||||||
no layout: the two halves of the text fix (`e5f8b6b`, `2525637`), three
|
`3f7cd82`) came the fuzzers and what they found: the shrinking fuzzer
|
||||||
retained-layout details that claimed something they did not do (`f1a47e9`),
|
(`b7caab3`, `386a0d1`), `SetSize` measuring in its declared length
|
||||||
the many-widgets-dirty cases (`77bb75e`), what the settle order is holding up
|
(`c596bf1`), text re-breaking across the interval a break holds for
|
||||||
(`bf94380`), and carrying a widget's depth rather than walking for it
|
(`9913194`), and a dirty widget measured where its parent asked rather than
|
||||||
(`3f7cd82`). Every one of them keeps the five reference renders and the resize
|
in a box its own answer decided (`02ff8c7`). `f61e893` restores `abs()` in
|
||||||
render byte-identical, and the hundred-seed sweep passes. `/home/bob/repos/ai-app-2` is on `rustify`, worktree clean.
|
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
|
**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
|
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%.
|
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 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.
|
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;
|
coordinates, and `ActiveData::parent_move` is the slot that is in;
|
||||||
`window_region` composes the one through the other, which is the walk the
|
`window_region` composes the one through the other, which is the walk the
|
||||||
shader does.
|
shader does.
|
||||||
@@ -273,10 +278,14 @@ aligned size-changing chain.
|
|||||||
The branches are invariants rather than widget exceptions:
|
The branches are invariants rather than widget exceptions:
|
||||||
|
|
||||||
- A dirty widget draws locally first only while its retained box has the same
|
- 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
|
pixel size **and that box is a constraint rather than its own answer**. If
|
||||||
the repaint and no ancestor draws. If the size changed, the dependent path
|
its returned `Size` is unchanged, no size reader can observe the repaint and
|
||||||
lays out. A changed pixel box takes the conservative path first, which is the
|
no ancestor draws. If the size changed, the dependent path lays out. A
|
||||||
condition the discarded `/tmp/escalate.patch` missed on resize.
|
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
|
- 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.
|
reader; propagation stops as soon as a reader's own answer stays unchanged.
|
||||||
Output resize is the distinct invariant: all pixel-dependent leaves and
|
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
|
break holds. `TextBuffer::shape` answers across that interval, with a
|
||||||
sub-pixel tolerance for the edge.
|
sub-pixel tolerance for the edge.
|
||||||
|
|
||||||
**What is left is not a stale drawing -- the layout has two answers.** A span
|
**A box that is its own answer.** A span measures its children in its own
|
||||||
measures its children in its own box, and its own box is what its parent gave
|
box, and its own box is what its parent gave it, from the size it reported,
|
||||||
it, from the size it reported, from those children. Four widgets:
|
from those children. Four widgets:
|
||||||
|
|
||||||
Aligned(mid, -, Span[ Text(wrap), OneLine ])
|
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
|
318.45, and the span settles at 624.38. **Both are stable.** Which one you get
|
||||||
depends on what the tree was before.
|
depends on what the tree was before.
|
||||||
|
|
||||||
So no rule about when to keep a drawing can fix it, and several were tried.
|
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
|
(`Painter::settle` -- place a child into its own reported size without
|
||||||
it there -- passes all four cases in `tests/unsettled.rs` and fails two in
|
measuring it there -- passed the hand-written cases and failed two generated
|
||||||
`generated.rs`, in `Aligned` alone, in `Span` alone, with the child force-drawn
|
ones). The defect is in *where a dirty widget is measured*: a local redraw
|
||||||
first or not. The fix wants the constraint a container measures under to be
|
draws it in the box it was placed in, and when a reader decided that box from
|
||||||
something it is *given* rather than something it *ends up with*.
|
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
|
`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:
|
axis or report its longest child -- is written and parked for the same reason:
|
||||||
|
|||||||
Reference in new issue
Block a user