Move a subtree by writing one slot

`try_reuse`'s pure-translation case now writes the widget's move slot
instead of remapping every primitive in its subtree. Counted on a span
of 20 rows, each five primitives deep, when the row above them changes
height:

  before   100 primitive region writes
  now        0, and 20 slot writes -- one per row the span re-placed

`window_region` walks the same chain on the CPU, so hit testing and
anyone asking in window pixels see a widget where the shader draws it.
`Moves::resolve` stops at `CHAIN_LIMIT` like the shader, and asserts in
debug that it got to the end rather than running out.

Two things fall out of it. Rewriting a region is the one thing a slot
cannot express, so `mov` zeroes the slots of everything it rewrites: a
region is what its slot was a delta from. And `try_reuse` loses its
`old == region` shortcut, which was wrong once a slot exists -- a widget
offered exactly the box it drew against has to have its delta cleared,
not skipped.

`Moves` lives on `UiRenderState` rather than `UiData`, because the draw
is what produces it and `window_region` should not need the ui's
resources to answer where something is. The renderer already takes both.

A slot is retired in `remove_rec`, after the descendants whose slots
name it as their parent. Either order is correct here -- nothing can
claim a freed index while a subtree is coming down, since `on_undraw`
cannot reach the slots -- but this way `remove`'s `undraw` flag only
notifies rather than also deciding slot lifetime, and the retirement
sits beside the recursion it follows.

Checked: fmt, clippy and 41 tests. `tabs` (with the image replay),
`view`, `minimal` and `text` all still render byte-identical, and the
live sway resize round trip -- which re-places most of the tree at the
same size, so it is the slot path throughout -- matches a cold start at
each size.
This commit is contained in:
iris-ai committed 2026-09-14 03:15:17 -04:00
1 parent f9ef7514e7
commit 8223a55cfb
4 files changed
+68 -32

No files matched your search

+8 -5
View File
@@ -25,12 +25,10 @@ pub struct UiData {
pub textures: Textures,
pub text: TextData,
pub masks: TrackedArena<Mask, u32>,
/// Where each widget's drawing sits relative to its parent's slot, so
/// moving a subtree writes one entry rather than every descendant's
/// primitives.
pub moves: Moves,
}
/// Where each widget's drawing sits relative to its parent's slot, so moving
/// a subtree writes one entry rather than every descendant's primitives.
#[derive(Default)]
pub struct Moves {
arena: Arena<MoveOffset, u32>,
@@ -64,12 +62,17 @@ impl Moves {
let mut at = idx;
for _ in 0..CHAIN_LIMIT {
if at == MoveIdx::NONE {
break;
return total;
}
let entry = self.arena[at.idx()];
total += entry.delta;
at = entry.parent;
}
debug_assert!(
at == MoveIdx::NONE,
"a move chain longer than {CHAIN_LIMIT} resolves to the wrong place, \
and the shader stops at the same depth"
);
total
}