Document the causes and correction of layout invalidation cascades
This commit is contained in:
1 parent
32151f7f9f
commit
ccc42f34f8
2 files changed
+148
-192
No files matched your search
+135
-184
@@ -16,6 +16,12 @@ size rules, fixed-point layout, a box in pixels threaded down the draw, and
|
||||
a report read as a fraction of the containing widget. No PR review was
|
||||
present when checked on 2026-09-15.
|
||||
|
||||
The separate frame/extent experiment now addresses the dominant size/resize
|
||||
redraw cascade through dependency tracking and invalidation, not glyph emission.
|
||||
Its current head is `0e107f0` in `/home/bob/repos/iris-layout-experiment`.
|
||||
See **Frame and extent: retained prototype** for the mechanism, measured costs,
|
||||
and the remaining cold/many-update work. The app's framework pin is unchanged.
|
||||
|
||||
**Widen one fuzzer axis at a time, and record which.** Seeds **1121** and
|
||||
**1839** at **depth 4** failed on `ea6dbae` and on every commit before it,
|
||||
and nothing in the routine verification reached them: the fast oracle takes
|
||||
@@ -423,7 +429,7 @@ The original proposal was implemented by Claude as `5fcace1` on
|
||||
`wip/region-and-placement` in `/home/bob/repos/iris-pr18`. It kept the fraction
|
||||
reference stable but failed five layout cases and three draw-count cases.
|
||||
The first correction is `efb416b`, exact recomposition is `2ed5503`, and the
|
||||
current performance continuation is **`c44bd19`**, pushed to origin's
|
||||
current performance continuation is **`0e107f0`**, pushed to origin's
|
||||
`wip/region-and-placement`, in the isolated checkout
|
||||
`/home/bob/repos/iris-layout-experiment`; **it is not on PR #18**. The original
|
||||
checkout is unchanged. Keep the experiment's build directory separate: Cargo
|
||||
@@ -506,179 +512,131 @@ red slot. Red is centered in that slot, 5 px right of the visible black gap's
|
||||
center. Do not describe those two centers as the same thing or compensate with
|
||||
an arbitrary offset. The parked padding API change is still separate.
|
||||
|
||||
Ordinary verification passes: formatting, workspace clippy with
|
||||
`layout-diagnostics`, workspace tests (101 suite tests and 21 core tests), and
|
||||
the fast generated cases. The corrected `tabs` resize from 1920x1200 to
|
||||
900x1200 matches a cold image with zero differing pixels. The release oracle
|
||||
passes 2000 seeds at depth 4 and 1000 at depth 6; the shrinker passes 400 at
|
||||
depth 5, and the debug oracle passes 120 at depth 4 with assertions enabled.
|
||||
Every scan runs all fifteen scenarios with exact equality. The measurement
|
||||
regression fails with four draws instead of three when the optimization is
|
||||
disabled.
|
||||
**The major update gap was over-invalidation, not a required correctness cost.**
|
||||
Two different dependency graphs had been accumulated into one range. A child's
|
||||
size answer constrains its parent only when the parent reads that answer;
|
||||
the child's drawing constrains the parent's retained drawing whether its size
|
||||
was read or not. A stack sized by one child must not remeasure because an
|
||||
unmeasured overlay wrapped at another width during provisional layout.
|
||||
|
||||
**Still not ready to replace PR #18.** The prototype does substantially more
|
||||
nested layout work than `e44dea3`, even after retaining extent-relative child
|
||||
frames (seed 1, depth 8):
|
||||
`Painter` now collects these contracts separately. `DrawResult::size()` and
|
||||
`measure_len` contribute answer dependencies; every child draw contributes
|
||||
drawing dependencies. Direct numeric and raw-placement reads conservatively
|
||||
constrain both. The original offer retains the answer contract, and final
|
||||
placement retains the drawing contract. Previous attempts separated storage
|
||||
without separating which child dependencies entered each contract, so they
|
||||
continued to invalidate measurements for unrelated drawing work.
|
||||
|
||||
| phase | widget draws at e44dea3 | current | primitive writes at e44dea3 | current |
|
||||
**A valid answer does not certify a valid drawing.** Root resize checks both.
|
||||
Local updates propagate an invalidated drawing contract as well as an invalidated
|
||||
answer; otherwise an ancestor can retain a broad resize range after a descendant
|
||||
begins reading its pixel width. Focused regressions cover both paths and fail when
|
||||
those checks are removed. No geometry check or equality requirement was relaxed.
|
||||
|
||||
**A wider validity range does not invalidate an existing guarantee.** When a
|
||||
local redraw returns the same size under a contract covering the old one, the
|
||||
old answer contract remains sufficient. Keep it, and likewise keep a still-valid
|
||||
old drawing contract. Adopting the wider range without telling the parent would
|
||||
lose which guarantee the parent relies on; keeping it prevents both widening
|
||||
and narrowing back from causing layout. Scroll alternates between an exact
|
||||
width and a wider interval as it enters/leaves end anchoring. Comparing whole
|
||||
contracts by equality caused four draws and doubled scroll cycles after the
|
||||
initial dependency split (`f860f71`). Retaining the valid guarantee reduces
|
||||
scroll to one draw, below the previous baseline's two. It introduces no retained
|
||||
state or separate propagation subsystem. A focused test widens/narrows several
|
||||
times without redrawing the parent, then resizes and requires the leaf to redraw.
|
||||
|
||||
**Declared-size changes use ordinary bottom-up propagation.** The changed child
|
||||
and its direct parent remain marked until the parent resolves the new rule.
|
||||
Further propagation depends on the parent's new answer and drawing contract.
|
||||
The blanket ancestor walk, `answer_invalid` set and frame-global
|
||||
`replace_answers` flag are gone. They predated strict bottom-up settlement and
|
||||
original-offer replay; a parent's independent answer no longer invalidates the
|
||||
whole tree merely because a descendant declared a new width. A regression
|
||||
checks the changed geometry and that propagation stops at an independent parent.
|
||||
|
||||
**The `c44bd19` extent-child extension was removed.** It changed
|
||||
`widget_within` to accept `DrawRegion::Extent` and enlarged retained child records,
|
||||
but saved only 1.8% resize cycles and no primitive writes. The dependency fixes
|
||||
address the major cost with the original `widget_within(UiRegion)` API and
|
||||
widget-id-only inherited-child list. Pad and Stack again read raw placement;
|
||||
those conservative reads are not the cause of the pathological update counts.
|
||||
|
||||
Instrumented work, seed 1/depth 8, 50 frames per phase (cold once):
|
||||
|
||||
| phase | draws at c44bd19 | current draws | primitive writes at c44bd19 | current writes |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| cold | 369 | 463 | 9635 | 9179 |
|
||||
| cold | 463 | 484 | 9179 | 9179 |
|
||||
| repaint | 1 | 1 | 1 | 1 |
|
||||
| many | 157 | 263.1 | 4721 | 5873 |
|
||||
| size | 16 | 240 | 106 | 5200 |
|
||||
| scroll | 2 | 2 | 0 | 0 |
|
||||
| resize | 13 | 278 | 0 | 5730 |
|
||||
| many | 263.1 | 274 | 5873 | 5873 |
|
||||
| size | 240 | 3 | 5200 | 105 |
|
||||
| scroll | 2 | 1 | 0 | 0 |
|
||||
| resize | 278 | 27 | 5730 | 0 |
|
||||
|
||||
These are instrumented work counts, not speedups (50 frames per phase except
|
||||
cold, which is one; 578 generated widgets, 191 finally active). The
|
||||
measurement-only change at `7601aa2` removed 7 widget draws and 525
|
||||
primitive writes per size frame, and 8 draws and 525 writes per resize frame,
|
||||
relative to `2ed5503`. The extent-relative continuation below skips additional
|
||||
container evaluations but does not reduce primitive writes.
|
||||
Resize performs no text renders. Size updates render one text. Removing the
|
||||
extent-child extension increases cold/many container evaluations again. Do not
|
||||
hide those costs behind the large size/resize gains. The original `e44dea3`
|
||||
measured 16 draws/106 writes for size and 13/0 for resize, but does not implement
|
||||
the corrected frame/extent semantics, so it is context rather than a correctness
|
||||
baseline.
|
||||
|
||||
**Measure cycles as well as instructions** (Bryan, 2026-09-17). The following
|
||||
are whole-process medians from seven alternating before/after runs of direct,
|
||||
uninstrumented release test executables under `perf stat -e
|
||||
cycles:u,instructions:u`. The baseline is `39f7b08`: `2ed5503` with the same
|
||||
centering fix, so both sides draw the corrected layout. CPU-heavy scans were stopped
|
||||
during measurement. Counts include fixture setup; size, resize and many run
|
||||
5000 frames, repaint and scroll run 1,000,000. These are VM CPU measurements,
|
||||
not phone frame times.
|
||||
Nine alternating pairs of direct, uninstrumented release executables compare
|
||||
`c44bd19` with `0e107f0` under `perf stat -e cycles:u,instructions:u`. All samples
|
||||
were retained, with no scans or builds running during measurement. Counts include
|
||||
fixture setup; these are VM CPU measurements, not phone frame times.
|
||||
|
||||
| phase | cycles before → after (billions) | instructions before → after (billions) |
|
||||
| phase (frames) | median cycles before → after (billions) | median instructions before → after (billions) |
|
||||
| --- | ---: | ---: |
|
||||
| size | 12.8008 → 11.0344 (-13.8%) | 34.3660 → 29.9450 (-12.9%) |
|
||||
| resize | 14.9368 → 13.0376 (-12.7%) | 39.8487 → 35.0032 (-12.2%) |
|
||||
| many | 14.8875 → 12.9579 (-13.0%) | 39.6021 → 34.7499 (-12.3%) |
|
||||
| repaint | 2.7416 → 2.6626 (-2.9%) | 6.0266 → 6.0197 (-0.1%) |
|
||||
| scroll | 9.7848 → 8.4829 (-13.3%) | 21.7203 → 20.9527 (-3.5%) |
|
||||
| size (2,000) | 4.0774 → 0.2021 (-95.0%) | 10.4017 → 0.4993 (-95.2%) |
|
||||
| resize (2,000) | 4.7070 → 0.4930 (-89.5%) | 11.9383 → 1.3009 (-89.1%) |
|
||||
| many (2,000) | 4.7718 → 5.0276 (+5.4%) | 12.0567 → 12.4463 (+3.2%) |
|
||||
| repaint (1,000,000) | 2.6803 → 2.9086 (+8.5%) | 6.0224 → 6.4382 (+6.9%) |
|
||||
| scroll (300,000) | 2.6252 → 1.3443 (-48.8%) | 6.5802 → 3.0649 (-53.4%) |
|
||||
|
||||
The size/resize/many cycle samples have nonoverlapping before/after ranges,
|
||||
with each range less than 1.3% of its median. Additional depth-6 fixtures,
|
||||
10,000 frames and seven alternating pairs each: seed 3 improves cycles by
|
||||
7.0% for size and 8.4% for resize; seed 13 improves resize by 9.2%, while its
|
||||
size samples overlap and establish no cycle improvement.
|
||||
All five before/after cycle ranges are disjoint. The main gains coexist with
|
||||
5.4% more cycles for `many` and 8.5% more for repaint. No claim is made that the
|
||||
prototype is now uniformly faster.
|
||||
|
||||
**Glyph emission now composes its origin once per text draw.** Glyph offsets
|
||||
and bitmap sizes are pixel lengths, so composition adds them unchanged. The
|
||||
fresh draw resolves the shared frame/extent origin once, then adds each glyph's
|
||||
offset and size. Retained local geometry is unchanged; a regression compares
|
||||
fresh drawing with retained recomposition for fractional origins, negative
|
||||
pixel offsets, changing frames and extents, and region nodes. This adds no
|
||||
retained state and changes no layout decisions.
|
||||
Additional depth-6 fixtures, nine alternating pairs and 4000 frames: seed 3
|
||||
size updates use 20.2% fewer cycles and 23.0% fewer instructions, while its resize
|
||||
case costs 5.4% more cycles and 3.5% more instructions. Seed 13 resize uses 6.6%
|
||||
fewer cycles and 7.7% fewer instructions. Those cycle ranges are disjoint too.
|
||||
Seed 13's size case performs no widget draws and its cycle ranges overlap.
|
||||
|
||||
Against `7601aa2`, nine alternating pairs of direct, uninstrumented release
|
||||
executables under `perf stat -e cycles:u,instructions:u`, seed 1, depth 8,
|
||||
2000 frames per process, including setup:
|
||||
Verification: workspace formatting, clippy with `layout-diagnostics`, and tests
|
||||
(106 suite tests, 21 core tests, fast generated cases) pass. Exact release scans
|
||||
pass 2000 seeds/depth 4 and 1000/depth 6; the shrinker passes 400 trees/depth 5,
|
||||
and the debug scan passes 120 seeds/depth 4 with assertions enabled. Every scan
|
||||
runs all fifteen scenarios without a position tolerance. Each of the five new
|
||||
regressions fails when its corresponding behavior is deliberately broken.
|
||||
All five reference renders (`view`, `minimal`, `random`, `tabs`, `text`) match
|
||||
`a7307d9` pixel for pixel. Resizing `tabs` from 1920x1200 to 900x1200 matches a
|
||||
cold render there, also with zero differing pixels. The GPU probe reports Venus
|
||||
on the RX 7900 XT.
|
||||
|
||||
| phase | cycles before → after (billions) | instructions before → after (billions) |
|
||||
| --- | ---: | ---: |
|
||||
| size | 4.5164 → 4.0909 (-9.4%) | 12.1709 → 10.3993 (-14.6%) |
|
||||
| resize | 5.3025 → 4.8044 (-9.4%) | 14.2150 → 12.1999 (-14.2%) |
|
||||
| many | 5.2743 → 4.8071 (-8.9%) | 14.0989 → 12.0868 (-14.3%) |
|
||||
**Remaining work is narrower, but not proved unavoidable.** Cold layout and
|
||||
widespread content updates still run descendants while evaluating provisional
|
||||
sizes, including children whose answers will never be read. A disposable trial
|
||||
made child requests lazy during measurement and skipped primitive emission, but
|
||||
reused the same active records for measurement and painting. It failed nine
|
||||
oracle scenarios and made dirty updates much worse. Measurement cannot erase
|
||||
or replace the final drawing's dependency/lifetime state. The trial was removed;
|
||||
there is no second widget layout body or measurement phase in this change.
|
||||
|
||||
All nine samples per side were retained; before/after cycle ranges do not
|
||||
overlap. No scans or builds ran during measurement. All instrumented work
|
||||
counters match `7601aa2` on all six phases (50 frames, cold once), including
|
||||
the primitive counts in the table above: this reduces the cost of
|
||||
emitting the repeated glyphs, not the repeated layout evaluations themselves.
|
||||
The hot wrapping text really does return different heights at 68 and 89.53613 px,
|
||||
so some width-dependent evaluation is necessary. That does not justify repeating
|
||||
it after an unchanged size answer. The resize trace and reduced stack/overlay
|
||||
case distinguished those two situations and led to this fix. Future work on
|
||||
cold/many should preserve that distinction rather than weakening raw-placement
|
||||
checks or memoizing arbitrary width results without a validity model.
|
||||
|
||||
Additional depth-6 fixtures, nine alternating pairs and 4000 frames each:
|
||||
seed 3's size updates improve cycles by 5.0% and instructions by 8.0%; seed
|
||||
13's resizes improve cycles by 8.5% and instructions by 13.6%, both with
|
||||
nonoverlapping cycle ranges. Seed 3's resize and seed 13's size ranges overlap.
|
||||
On seed 1/depth 8, cold (one frame/process) and scroll (300,000 frames) also
|
||||
have overlapping cycle ranges. Repaint (1,000,000 frames) measures 1.1% more
|
||||
cycles, with nonoverlapping ranges and just 0.02% more instructions: this slice
|
||||
has a small measured repaint cost alongside the text-heavy gains. All nine
|
||||
samples per side were retained in these checks too.
|
||||
|
||||
The verification runs above pass again at this head. All five reference
|
||||
renders (`view`, `minimal`,
|
||||
`random`, `tabs`, `text`) match `7601aa2` pixel for pixel at 1920x1200; `tabs`
|
||||
resized to 900x1200 matches a cold render there. The GPU probe reported Venus
|
||||
on the RX 7900 XT in this session.
|
||||
|
||||
Two trials were removed: combining frame recomposition with extent movement
|
||||
saved under 0.8% of instructions and no cycles; special-casing equal endpoint
|
||||
fractions in `UiSpan::within` cost 11% cycles and 7% instructions despite still
|
||||
inlining. Keep that hot general composition small.
|
||||
|
||||
**Child frames can now follow the extent without evaluating their container.**
|
||||
`widget_within` accepts the same `DrawRegion::{Frame, Extent}` reference as
|
||||
primitives and masks. `Pad` retains its pixel inset in extent coordinates;
|
||||
`Stack` inherits placement for its sizing child and retains extent-relative
|
||||
frames for its other children. Moving the extent replays those requests through
|
||||
`ask_box`, preserving child alignment and region nodes. This extends the existing
|
||||
inherited-child walk rather than adding a separate layout body or a second cache.
|
||||
It stores a region-bearing relation per extent-following child, so retained
|
||||
child records are larger than the old widget-id-only list.
|
||||
|
||||
Two dependencies stay conservative. Fractional endpoints compose before pixel
|
||||
evaluation, so their bounds cannot in general be pulled back through the extent
|
||||
length alone; those frames still depend on raw placement. Reading a child's
|
||||
fractional report also depends on placement because composing that report into
|
||||
the parent's frame can change its value without changing the child's answer.
|
||||
Pixel-inset frame bounds use the existing exact `Holds::through`.
|
||||
|
||||
**Keep the measured slot when repositioning.** A first trial recomputed it from
|
||||
`active.size`, which is the final draw's report. Text measured narrow and placed
|
||||
wider may report a different height at the latter width; the slot is still the
|
||||
one chosen from measurement. Seed 20/depth 4 caught this. The retained slot is
|
||||
now preserved on axes not explicitly assigned by the parent. A small synthetic
|
||||
width-dependent widget pins the same failure without fonts.
|
||||
|
||||
Against `a7307d9`, seed 1/depth 8 skips 21 draws cold, 10.9 per `many` frame,
|
||||
7 per size frame and 14 per resize frame. Repaint, scroll, and all primitive-write
|
||||
counts are unchanged. Nine alternating pairs of direct uninstrumented release
|
||||
executables under `perf stat -e cycles:u,instructions:u`, including setup:
|
||||
|
||||
| phase | median cycles before → after (billions) | median instructions before → after (billions) |
|
||||
| --- | ---: | ---: |
|
||||
| size (2000 frames) | 4.0819 → 4.0714 | 10.4107 → 10.4017 |
|
||||
| resize (2000 frames) | 4.8108 → 4.7237 | 12.1854 → 11.9252 |
|
||||
| many (2000 frames) | 4.7918 → 4.7787 | 12.0925 → 12.0567 |
|
||||
| repaint (1,000,000 frames) | 2.6701 → 2.6851 | 6.0175 → 6.0195 |
|
||||
| scroll (300,000 frames) | 2.6296 → 2.6273 | 6.5025 → 6.5817 |
|
||||
|
||||
Only resize establishes a cycle improvement: **1.8%**, with nonoverlapping sample
|
||||
ranges, and 2.1% fewer instructions. Every other phase's cycle ranges overlap;
|
||||
scroll uses 1.2% more instructions. All samples were retained and no scans or
|
||||
builds ran during measurement. This is a small reduction in work, not closure of
|
||||
the main gap.
|
||||
|
||||
Verification: workspace formatting, clippy with `layout-diagnostics`, tests
|
||||
(104 suite tests, 21 core tests), exact release scans of 2000 seeds/depth 4 and
|
||||
1000/depth 6, the shrinker over 400 trees/depth 5, and a debug scan of 120
|
||||
seeds/depth 4 with assertions enabled.
|
||||
The new no-redraw test checks nested padding/stacks, fixed child
|
||||
alignment, primitives, masks, and region nodes; restoring the old container
|
||||
calls makes it fail with three draws instead of two. Additional tests cover
|
||||
fractional reports and numeric dependencies. All five reference renders match
|
||||
`a7307d9` pixel for pixel at 1920x1200, and the resized 900x1200 `tabs` matches a
|
||||
cold render there. The GPU probe reports Venus on the RX 7900 XT.
|
||||
|
||||
**The remaining gap has not been established as an unavoidable correctness
|
||||
cost.** Before the extent-relative change, a temporary failure trace of one
|
||||
frame on seed 1, depth 8 counted 39/108 failed reuse attempts in size, 65/151 in resize, and 45/109 in many
|
||||
where only the raw placement snapshot differed: both numeric frame and extent
|
||||
ranges still held. Containers read that snapshot to construct child positions,
|
||||
so discarding the check would be wrong. Padding and stacks now retain their
|
||||
extent-relative child frames, but `Span` still constructs its mixed-reference
|
||||
slots from a raw placement read: along the row, child fractions refer to the
|
||||
frame while cursor positions and leftover room refer to the extent. Retaining
|
||||
those expressions without changing their fixed-point evaluation order is the
|
||||
next protocol question. There is also necessary width-dependent work: the hot text at widget 199 visits 68 px and 89.53613 px
|
||||
and reports different heights (316.80078 and 228.80078). It is evaluated
|
||||
repeatedly at those widths, so that fact alone does not justify all the repeats.
|
||||
|
||||
Keeping measurement validity separate from final drawing validity and deferring
|
||||
to an already dirty parent were previously tried without improving draw counts.
|
||||
Measuring first in local redraw and then restoring its slot saved only one draw
|
||||
in `many` and added another request on simple repaints; that trial was removed.
|
||||
Retaining local coordinates still costs memory per primitive. Optimize under
|
||||
the exact oracle before adopting the prototype; the `Inset`/`Outset` changes
|
||||
remain parked.
|
||||
The earlier glyph-origin composition optimization (`a7307d9`) remains; it changes
|
||||
no layout decisions or retained state. Two removed arithmetic trials remain
|
||||
unpromising: combining frame recomposition with extent movement saved under 0.8%
|
||||
instructions and no cycles; special-casing equal endpoint fractions in
|
||||
`UiSpan::within` cost 11% cycles and 7% instructions. Retained local coordinates
|
||||
still cost memory per primitive. The prototype remains separate from PR #18;
|
||||
the `Inset`/`Outset` changes remain parked.
|
||||
|
||||
## How layout is decided
|
||||
|
||||
@@ -774,7 +732,8 @@ is a useful regression target for the experimental recomposition.
|
||||
- `Holds` is the interval of box lengths for which a widget's drawing and
|
||||
reported size stay valid. Reading `Painter::px_len` or `px_size` narrows
|
||||
it to the length read; `Painter::holds` widens it. Parent validity is the
|
||||
intersection of what its children induce. The contract is trusted: a
|
||||
intersection of what its children induce: measured children for an answer,
|
||||
all painted children for a drawing. The contract is trusted: a
|
||||
widget declaring a wrong range is a defective widget, and Iris adds no
|
||||
defensive work to recover from one.
|
||||
- A retained drawing can be reused only when its `Holds` contains the new
|
||||
@@ -791,26 +750,18 @@ is a useful regression target for the experimental recomposition.
|
||||
long as the offer; anything else is its parent's question, with the mark
|
||||
left on. Lengths and not whole boxes: what a drawing depends on is its
|
||||
lengths, so the same lengths elsewhere is the same question.
|
||||
- An answer is reusable only where both its measurement and the drawing in
|
||||
its final placed box remain valid; the drawing's `Holds` is translated
|
||||
back through `placed_lens` and intersected with the answer's.
|
||||
- `Painter::widget_decided(child, region, [bool; 2])` says the parent chose
|
||||
this box from the child's own answer along those axes, so the answer is
|
||||
not placed inside it again. A report of "half of what you give me" has no
|
||||
fixed point but zero, so the framework asks exactly twice: at the offer,
|
||||
and in the box chosen from the answer, final on the decided axes. `Span`
|
||||
decides the row axis, `Scroll` both, `Stack` both for its sizing child.
|
||||
`Pad` overrides nothing: its inset is exactly the inner where the box is
|
||||
its answer, and the slack is the inner's to sit in otherwise.
|
||||
- **Placement cannot be applied after the fact.** Three attempts at "draw
|
||||
the widget, then move its drawing to where its alignment says" failed,
|
||||
because the move is a change of frame and no split of the stored state
|
||||
carries it: moving `ActiveData::region` with the drawing made a later
|
||||
local redraw ask a differently rounded question, and leaving it made
|
||||
`placed` accumulate without bound because `try_reuse` returns a clean
|
||||
subtree's size without walking into it. Alignment is applied where the
|
||||
size is known -- `declared_box` for a rule, the placing second ask
|
||||
otherwise.
|
||||
- An answer is reusable where its measurement contract holds. Its final
|
||||
drawing is checked independently and may need redrawing even while the answer
|
||||
stands. Drawing validity is translated back through the chosen placement for
|
||||
the parent's drawing contract, not intersected into the retained answer.
|
||||
- `Painter::widget_at(child, frame, placement)` supplies an optional chosen
|
||||
extent on each axis. Unchosen axes place the measured answer by the child's
|
||||
alignment. `measure_len` leaves a fresh measurement at the offer until the
|
||||
parent assigns a slot; it does not first place an intermediate answer.
|
||||
- Placement must not replace the fraction reference. The failed earlier
|
||||
attempts moved `ActiveData::region` with the drawing and made local redraws
|
||||
ask differently rounded questions. The experiment retains `placement`
|
||||
separately and replays original local frames when recomposing geometry.
|
||||
- A widget that clips to its box reports its box: `Scroll` and `Masked`
|
||||
report `LEFTOVER` on both axes, and a `debug_assert` holds any widget
|
||||
that set a mask this draw to it. Overflowing is otherwise ordinary, which
|
||||
@@ -858,9 +809,9 @@ is a useful regression target for the experimental recomposition.
|
||||
parent (seed 10). `Painter` records size-dependency edges only when a
|
||||
parent reads a child's size or hint; an undrawn measured child stays
|
||||
recorded so a later change reaches whoever decided not to draw it.
|
||||
- Dirty widgets settle deepest-first, and that ordering is what makes an
|
||||
answer trustworthy; `dirty_size_under` is to be deleted once a resize goes
|
||||
through the same walk (see "A frame settles strictly bottom-up").
|
||||
- Dirty widgets settle deepest-first, including during resize. A deferred
|
||||
child leaves its parent marked, so no clean answer can hide an unsettled
|
||||
size dependency. `dirty_size_under` has been deleted.
|
||||
- Declared non-`leftover` lengths are resolved by the parent where the
|
||||
widget is drawn, so a declared-length change redraws the parent. A rule
|
||||
wins on the axis it names and the widget under it never learns of it.
|
||||
|
||||
+13
-8
@@ -879,14 +879,19 @@ prototype has significant container-update costs and is not yet adopted into
|
||||
the pinned framework. A container's length query can evaluate the offered extent
|
||||
without first placing the child's reported size; the parent's subsequent draw
|
||||
assigns the final slot using the same `Widget::draw` body. A parent-selected slot
|
||||
still honors a fixed child's alignment within that slot. Child frames can also
|
||||
retain an extent-relative region through `widget_within(DrawRegion::Extent)`;
|
||||
padding and stacks use this instead of baking the current placement into child
|
||||
coordinates. Pixel insets carry numeric validity through the extent. General
|
||||
fractional endpoints and fractional reports retain conservative placement
|
||||
dependencies; fixed-point composition must not be reassociated to remove them.
|
||||
Retained movement preserves the slot chosen from measurement, even if the final
|
||||
draw reports another size.
|
||||
still honors a fixed child's alignment within that slot. Answer dependencies
|
||||
are collected only from child sizes actually read; drawing dependencies
|
||||
include all painted children. Root resize and local propagation check both
|
||||
contracts. An unchanged answer with a wider validity range retains its old
|
||||
contract: the parent's guarantee is still valid, and narrowing back must not
|
||||
trigger layout. The same rule applies to retained drawing validity. A changed
|
||||
declared size defers to its direct parent, then ordinary bottom-up propagation
|
||||
stops when both contracts remain unchanged. Blanket ancestor invalidation is
|
||||
unnecessary. The extent-relative child-frame API trial was removed: the major
|
||||
size/resize cost came from conflating these dependencies, not from Pad and
|
||||
Stack's conservative raw-placement reads. Cold layout and widespread content
|
||||
changes still perform excess provisional work; that residual has not been
|
||||
established as necessary for correctness.
|
||||
|
||||
**A `Span` leaves its children in the offered orthogonal region unless
|
||||
explicitly compacted** (2026-09-12). It still reports the widest orthogonal
|
||||
|
||||
Reference in new issue
Block a user