forked from 1b-agent/opencadstudio-upstream
perf(render): skip exploding hatch-free blocks in hatch build
Opening an xref-heavy drawing froze for ~22s before the first frame. `synced_hatch_models` walked every INSERT and called `explode_from_document()` to scan the result for hatch fills — but explode CLONES every child of the block, including 3D solids that each carry tens of KB of SAB geometry. Clinic.dwg's five xref INSERTs each reference a discipline block of thousands of solids and no hatch, so this materialized ~25000 solid clones (~1 GB) on every open just to find zero hatches. Add a memoized `block_contains_hatch`: a read-only scan of the block's entity types (recursing nested INSERTs, cycle-guarded) that skips the explode for blocks containing no hatch, transitively. Blocks that do hold a hatch still explode exactly as before, so the hatch set is unchanged. Hatch build for Clinic.dwg drops from ~21.7s to ~0.02s, cutting the total open-to-first-frame time from ~25s to ~5s. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
c91593ab9c
commit
0ce918da68
1 file changed
+69
@@ -409,6 +409,55 @@ impl Scene {
|
||||
self.bump_geometry();
|
||||
}
|
||||
|
||||
/// Whether a block definition holds at least one HATCH, directly or through
|
||||
/// a nested INSERT. Memoized by block-record handle — `None` marks a block
|
||||
/// currently being visited so a cyclic block reference resolves to `false`
|
||||
/// instead of recursing forever. Lets `synced_hatch_models` skip the costly
|
||||
/// explode of hatch-free blocks (a read-only type scan, no entity clones).
|
||||
fn block_contains_hatch(
|
||||
&self,
|
||||
block_handle: Handle,
|
||||
memo: &mut HashMap<Handle, Option<bool>>,
|
||||
) -> bool {
|
||||
if let Some(v) = memo.get(&block_handle) {
|
||||
return v.unwrap_or(false);
|
||||
}
|
||||
memo.insert(block_handle, None);
|
||||
let mut has = false;
|
||||
if let Some(br) = self
|
||||
.document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|b| b.handle == block_handle)
|
||||
{
|
||||
for &h in &br.entity_handles {
|
||||
match self.document.get_entity(h) {
|
||||
Some(EntityType::Hatch(_)) => {
|
||||
has = true;
|
||||
break;
|
||||
}
|
||||
Some(EntityType::Insert(nins)) => {
|
||||
let nbh = self
|
||||
.document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|b| b.name.eq_ignore_ascii_case(&nins.block_name))
|
||||
.map(|b| b.handle);
|
||||
if let Some(nbh) = nbh {
|
||||
if self.block_contains_hatch(nbh, memo) {
|
||||
has = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
memo.insert(block_handle, Some(has));
|
||||
has
|
||||
}
|
||||
|
||||
pub(super) fn synced_hatch_models(&self) -> Vec<HatchModel> {
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
|
||||
@@ -495,6 +544,15 @@ impl Scene {
|
||||
} else {
|
||||
self.bg_color
|
||||
};
|
||||
// Exploding an INSERT materializes (clones) every child of its block —
|
||||
// including 3D solids that each carry megabytes of SAB geometry — just
|
||||
// to scan the result for hatch fills. On xref-heavy drawings whose
|
||||
// blocks hold thousands of solids and NO hatches, that cloned ~all block
|
||||
// content on every open for nothing (~20 s on a 5-xref BIM aggregate).
|
||||
// Pre-scan each block's entity types (no clone) and skip the explode for
|
||||
// blocks that contain no hatch, transitively. Blocks that DO contain a
|
||||
// hatch still explode exactly as before, so the output is unchanged.
|
||||
let mut hatch_block_memo: HashMap<Handle, Option<bool>> = HashMap::default();
|
||||
for entity in self.document.entities() {
|
||||
let EntityType::Insert(ins) = entity else {
|
||||
continue;
|
||||
@@ -509,6 +567,17 @@ impl Scene {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if let Some(bh) = self
|
||||
.document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|b| b.name.eq_ignore_ascii_case(&ins.block_name))
|
||||
.map(|b| b.handle)
|
||||
{
|
||||
if !self.block_contains_hatch(bh, &mut hatch_block_memo) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let selected = self.selected.contains(&ins.common.handle);
|
||||
// XCLIP: clip this insert's exploded hatch fills to the boundary,
|
||||
// matching how the line geometry is clipped in expand_insert.
|
||||
|
||||
Reference in new issue
Block a user