forked from 1b-agent/opencadstudio-upstream
perf: cache SortEntitiesTable scan (option E)
Replace the O(objects) linear scan inside wires_for_block() with a sort_cache keyed by geometry_epoch. On a cache miss the index is built once by scanning all document objects; subsequent calls within the same epoch do an O(1) HashMap lookup (block_handle → entity→sort map). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
d1ecaf4695
commit
9c8c9a4060
2 files changed
+49
-23
No files matched your search
+9
-6
@@ -109,13 +109,16 @@ bounding boxes for all entity types.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Option E — SortEntitiesTable Scan Cache
|
## Option E — SortEntitiesTable Scan Cache ✅ Implemented
|
||||||
|
|
||||||
**Status:** Planned
|
**Status:** Done
|
||||||
|
|
||||||
Cache the result of the `SortEntitiesTable` lookup that currently performs a linear scan over all
|
Added `sort_cache: RefCell<Option<(u64, HashMap<Handle, HashMap<u64, u64>>)>>` to `Scene`.
|
||||||
objects on every `wires_for_block()` call.
|
|
||||||
|
|
||||||
**Impact:** Minor. Removes one O(objects) scan per frame. Low priority compared to A–D.
|
On the first call to `wires_for_block()` after a geometry change, the cache scans all
|
||||||
|
`document.objects` once and builds an index: `block_handle → (entity_handle.value() →
|
||||||
|
sort_handle.value())`. Subsequent calls within the same epoch do an O(1) HashMap lookup
|
||||||
|
instead of the previous O(objects) `find_map` scan.
|
||||||
|
|
||||||
**Difficulty:** Easy.
|
The cache is keyed by `geometry_epoch` and invalidated automatically alongside the wire
|
||||||
|
and GPU caches whenever geometry changes.
|
||||||
+40
-17
@@ -76,6 +76,10 @@ pub struct Scene {
|
|||||||
/// Keyed by `geometry_epoch`; invalidated automatically when the epoch changes.
|
/// Keyed by `geometry_epoch`; invalidated automatically when the epoch changes.
|
||||||
/// Uses `Arc` so `build_primitive()` avoids a full Vec clone during navigation.
|
/// Uses `Arc` so `build_primitive()` avoids a full Vec clone during navigation.
|
||||||
wire_cache: RefCell<Option<(u64, Arc<Vec<WireModel>>)>>,
|
wire_cache: RefCell<Option<(u64, Arc<Vec<WireModel>>)>>,
|
||||||
|
/// Index built from every SortEntitiesTable in the document.
|
||||||
|
/// Maps block_handle → (entity_handle.value() → sort_handle.value()).
|
||||||
|
/// Replaces the O(objects) linear scan inside `wires_for_block()` with an O(1) lookup.
|
||||||
|
sort_cache: RefCell<Option<(u64, HashMap<Handle, HashMap<u64, u64>>)>>,
|
||||||
/// Active layout name — "Model" or a paper space layout name.
|
/// Active layout name — "Model" or a paper space layout name.
|
||||||
pub current_layout: String,
|
pub current_layout: String,
|
||||||
/// GPU render data for hatch fills, keyed by the DXF entity Handle.
|
/// GPU render data for hatch fills, keyed by the DXF entity Handle.
|
||||||
@@ -107,6 +111,7 @@ impl Scene {
|
|||||||
camera_generation: 0,
|
camera_generation: 0,
|
||||||
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
||||||
wire_cache: RefCell::new(None),
|
wire_cache: RefCell::new(None),
|
||||||
|
sort_cache: RefCell::new(None),
|
||||||
current_layout: "Model".to_string(),
|
current_layout: "Model".to_string(),
|
||||||
hatches: HashMap::new(),
|
hatches: HashMap::new(),
|
||||||
meshes: HashMap::new(),
|
meshes: HashMap::new(),
|
||||||
@@ -420,14 +425,30 @@ impl Scene {
|
|||||||
fn wires_for_block(&self, block_handle: Handle) -> Vec<WireModel> {
|
fn wires_for_block(&self, block_handle: Handle) -> Vec<WireModel> {
|
||||||
use acadrust::objects::ObjectType;
|
use acadrust::objects::ObjectType;
|
||||||
|
|
||||||
// Find the SortEntitiesTable for this block (if any).
|
// ── Ensure sort-order index is current ────────────────────────────
|
||||||
let sort_table = self.document.objects.values().find_map(|obj| {
|
// Replaces the old O(objects) find_map with one rebuild per epoch,
|
||||||
if let ObjectType::SortEntitiesTable(t) = obj {
|
// after which every wires_for_block call is an O(1) HashMap lookup.
|
||||||
if t.block_owner_handle == block_handle { Some(t) } else { None }
|
{
|
||||||
} else {
|
let needs_rebuild = self.sort_cache.borrow()
|
||||||
None
|
.as_ref()
|
||||||
|
.map(|(e, _)| *e != self.geometry_epoch)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if needs_rebuild {
|
||||||
|
let mut idx: HashMap<Handle, HashMap<u64, u64>> = HashMap::new();
|
||||||
|
for obj in self.document.objects.values() {
|
||||||
|
if let ObjectType::SortEntitiesTable(t) = obj {
|
||||||
|
if !t.is_empty() {
|
||||||
|
let map = t.entries()
|
||||||
|
.map(|e| (e.entity_handle.value(), e.sort_handle.value()))
|
||||||
|
.collect();
|
||||||
|
idx.insert(t.block_owner_handle, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*self.sort_cache.borrow_mut() = Some((self.geometry_epoch, idx));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
let mut wires: Vec<WireModel> = self.document
|
let mut wires: Vec<WireModel> = self.document
|
||||||
.entities()
|
.entities()
|
||||||
@@ -450,16 +471,18 @@ impl Scene {
|
|||||||
.flat_map(|e| self.tessellate_one(e))
|
.flat_map(|e| self.tessellate_one(e))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Apply draw order if a SortEntitiesTable exists and has entries.
|
// Apply draw order via the cached index (O(1) block lookup).
|
||||||
if let Some(table) = sort_table {
|
{
|
||||||
if !table.is_empty() {
|
let cache = self.sort_cache.borrow();
|
||||||
wires.sort_by_key(|w| {
|
if let Some((_, ref idx)) = *cache {
|
||||||
let handle = Self::handle_from_wire_name(&w.name)
|
if let Some(sort_map) = idx.get(&block_handle) {
|
||||||
.unwrap_or(Handle::NULL);
|
wires.sort_by_key(|w| {
|
||||||
table.get_sort_handle(handle)
|
let key = Self::handle_from_wire_name(&w.name)
|
||||||
.map(|sh| sh.value())
|
.map(|h| h.value())
|
||||||
.unwrap_or(u64::MAX / 2) // unsorted entities draw in the middle
|
.unwrap_or(u64::MAX);
|
||||||
});
|
sort_map.get(&key).copied().unwrap_or(u64::MAX / 2)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wires
|
wires
|
||||||
|
|||||||
Reference in new issue
Block a user