Creating or freeing a frame or region re-derives only what it touches (#915)

Any write that changed the layout graph's roster (a frame or region created, freed, re-owned or re-pointed) made the next resolve derive the whole graph: about 1.35 ms for one `CreateTexture` or `CreateFrame` on the stock UI, against 0.05 ms for a no-op.

A write now names the nodes it touched, and the resolve re-derives just those in the cached graph: a joining node is added with its edges, a leaving one is removed and its dependents seeded, a staying one has its edges and inputs re-synced. The full derivation stays the fallback. A region's owner and an art change are now layout inputs that name their region. Verify now also compares the graph itself.

A randomized test checks rects and graph against a scratch solve after every batch of creates, frees, re-owns and re-anchors. `CreateTexture` now costs 0.14 ms, `CreateFrame` 0.15 ms, a backdrop rebuild 0.22 ms.

Fixes #903

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sam Sung
2026-10-12 01:05:23 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 55729baff9
commit fa5568dcdc
32 changed files with 1318 additions and 376 deletions
+6 -7
View File
@@ -331,16 +331,13 @@ impl Installed {
/// the next resolve runs them. Each piece is a fresh Texture linked at the tail of the region
/// list, so a second backdrop's pieces come after any region made in between.
pub(crate) fn install(model: &mut Model, fh: FrameHandle, spec: Option<Backdrop>) {
// One layout touch for the whole swap, the pieces it frees and the ones it builds: the next
// resolve re-derives the graph once either way.
// Each freed piece names its node, and each built one below, so the next resolve takes the
// swap through the cached graph.
let old = model.backdrops.remove(&fh);
for rh in old.iter().flat_map(|o| o.pieces).flatten() {
super::region::release_region(model, rh);
super::region::free_region(model, rh);
}
let Some(spec) = spec.filter(|_| model.arena.frame(fh).is_some()) else {
if old.is_some() {
model.touch_layout();
}
return;
};
let owner = model.frame_id(fh);
@@ -399,6 +396,9 @@ pub(crate) fn install(model: &mut Model, fh: FrameHandle, spec: Option<Backdrop>
);
}
}
for rh in pieces.iter().flatten() {
model.touch_layout_region(*rh);
}
model.backdrops.insert(
fh,
Installed {
@@ -407,7 +407,6 @@ pub(crate) fn install(model: &mut Model, fh: FrameHandle, spec: Option<Backdrop>
ran_for: None,
},
);
model.touch_layout();
if let Some(rect) = model.resolved.get(&fh).copied() {
run_texcoords(model, fh, rect);
}