forked from 1b-agent/opencadstudio-upstream
perf: skip the wire partition when a patch cannot change its output
`arena-post partition=55.2ms blocks=5.9ms curves=2.5ms` — the partition is almost the whole of the 73 ms an arena patch spends after patching, and it walks all 236 956 resident wires to serve a one-entity change. Its three consumers here — the instanced, circle and ellipse uploads — depend only on wires that are block references or analytical curves. A patch that touches none of those cannot change any of them, so the walk is skipped and the uploads already on the slot are kept. Two things make that sound, and both are checked rather than assumed: **Depths hold still.** The instances bake a draw depth. `inserted_draw_depth_label` allocates into a reserved gap instead of renumbering, so adding an entity leaves every other label alone — the existing doc comment says so and there is now a test. A rebuild from scratch *can* move them, so `draw_depth_generation` bumps only there, and a slot reuses its uploads only while that generation is unchanged. **Contributors are remembered.** `partition_wires` records which handles fed those three outputs, and the skip requires that no changed handle is among them and that no changed handle's new wires would join them. That catches an entity entering the set and an entity leaving it. The membership test is shared with the walk it decides to skip, so the two cannot drift, and it is deliberately conservative: a wire that looks analytical but fails extraction forces a needless re-partition, never a stale upload. `arena-post` gains `skipped=` so a run says how often this actually fires rather than leaving it to be inferred from the timing. Not verified on a device yet — the headless harness never reaches `prepare`, so this needs a GUI run on the reproducer to confirm both the saving and that nothing goes missing on screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b68989768d
commit
03c64e3d38
4 files changed
+176
-33
No files matched your search
@@ -1706,6 +1706,12 @@ pub struct Scene {
|
||||
/// constants. `[depth, half]` retains a fixed child sub-range for block
|
||||
/// composition. Full sort/layout/block changes rebuild the labels.
|
||||
draw_depth_cache: RefCell<Option<DrawDepthCache>>,
|
||||
/// Bumped only when the draw-order map is rebuilt from scratch, which is
|
||||
/// the one case that can change an existing entity's depth — an
|
||||
/// incremental insert allocates into the reserved label gap and leaves
|
||||
/// every other label alone. Consumers that cached anything carrying a baked
|
||||
/// depth compare this before reusing it.
|
||||
draw_depth_generation: std::cell::Cell<u64>,
|
||||
/// Shared empty map for 3-D wireframe, where true depth wins instead of
|
||||
/// the entity submission order used by the optimized 2-D style.
|
||||
no_draw_depths: Arc<HashMap<u64, [f32; 2]>>,
|
||||
@@ -2053,6 +2059,7 @@ impl Scene {
|
||||
interaction_handle_index_cache: RefCell::new(None),
|
||||
sort_cache: RefCell::new(None),
|
||||
draw_depth_cache: RefCell::new(None),
|
||||
draw_depth_generation: std::cell::Cell::new(0),
|
||||
no_draw_depths: Arc::new(HashMap::default()),
|
||||
hatch_cache: RefCell::new(HashMap::default()),
|
||||
wipeout_cache: RefCell::new(HashMap::default()),
|
||||
@@ -5998,6 +6005,11 @@ impl Scene {
|
||||
/// A full build assigns sparse labels in effective draw order. Incremental
|
||||
/// Add/Remove then changes only the named handle: existing siblings retain
|
||||
/// their depth, avoiding an O(all entities) map rewrite and GPU const upload.
|
||||
/// See [`Self::draw_depth_generation`].
|
||||
pub(in crate::scene) fn draw_depth_generation(&self) -> u64 {
|
||||
self.draw_depth_generation.get()
|
||||
}
|
||||
|
||||
pub(super) fn draw_depth_map(&self) -> Arc<HashMap<u64, [f32; 2]>> {
|
||||
{
|
||||
let cache = self.draw_depth_cache.borrow();
|
||||
@@ -6127,6 +6139,11 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
// From here the labels are assigned afresh, so an existing entity's
|
||||
// depth can move. Anything holding a baked depth has to rebuild.
|
||||
self.draw_depth_generation
|
||||
.set(self.draw_depth_generation.get().wrapping_add(1));
|
||||
|
||||
use acadrust::objects::ObjectType;
|
||||
// Per-block SortEntitiesTable overrides: block -> (entity_val -> sort_val).
|
||||
let mut overrides: HashMap<Handle, HashMap<u64, u64>> = HashMap::default();
|
||||
@@ -10643,6 +10660,52 @@ mod journal_tests {
|
||||
// Differential oracle: the incrementally-patched entity index must always
|
||||
// equal a from-scratch rebuild after any add / move / erase.
|
||||
#[test]
|
||||
/// The partition skip reuses uploads that bake a draw depth, and it is only
|
||||
/// sound while existing depths hold still. An incremental add must leave
|
||||
/// them alone and must not bump the generation; a rebuild from scratch may
|
||||
/// move them and must bump it.
|
||||
#[test]
|
||||
fn adding_an_entity_leaves_existing_draw_depths_alone() {
|
||||
use acadrust::entities::Line;
|
||||
use acadrust::types::Vector3;
|
||||
|
||||
fn add(scene: &mut Scene, x: f64) -> Handle {
|
||||
scene.add_entity(EntityType::Line(Line::from_points(
|
||||
Vector3::new(x, 0.0, 0.0),
|
||||
Vector3::new(x + 1.0, 0.0, 0.0),
|
||||
)))
|
||||
}
|
||||
let mut scene = Scene::new();
|
||||
let first = add(&mut scene, 0.0);
|
||||
let second = add(&mut scene, 1.0);
|
||||
let before = scene.draw_depth_map();
|
||||
let generation = scene.draw_depth_generation();
|
||||
|
||||
add(&mut scene, 2.0);
|
||||
let after = scene.draw_depth_map();
|
||||
assert_eq!(
|
||||
scene.draw_depth_generation(),
|
||||
generation,
|
||||
"an incremental add must not invalidate baked depths",
|
||||
);
|
||||
for handle in [first, second] {
|
||||
assert_eq!(
|
||||
after.get(&handle.value()),
|
||||
before.get(&handle.value()),
|
||||
"an existing entity's depth must not move when another is added",
|
||||
);
|
||||
}
|
||||
|
||||
// A rebuild from scratch reassigns labels, so it has to say so.
|
||||
scene.draw_depth_cache.borrow_mut().take();
|
||||
let _ = scene.draw_depth_map();
|
||||
assert_ne!(
|
||||
scene.draw_depth_generation(),
|
||||
generation,
|
||||
"a full rebuild must invalidate anything holding a baked depth",
|
||||
);
|
||||
}
|
||||
|
||||
/// Folding a lone addition must land the handle where the full walk would
|
||||
/// have put it — at the end of its block's list, because that list is in
|
||||
/// document order and document order is draw order.
|
||||
|
||||
Reference in new issue
Block a user