forked from 1b-agent/opencadstudio-upstream
perf: stop scanning every entity for associative centers on each edit
The spans put it exactly: 63.5 ms of a 63.8 ms pointer-up is `bump_entities`, and inside it two drawing-wide scans. `refresh_associative_centerlines` and `refresh_associative_center_marks` each walk all 1 166 357 entities, filter to Lines and parse every one's extended data looking for an association that references a changed handle. About 30 ms each, run on every edit -- to find, on this drawing, none at all. Track whether the document holds any such association. The question is settled once, with a single pass, and after that each edit only examines the handles it actually changed: an entity that carries an association flips the answer to yes, and nothing else can. Deleting the last one leaves the answer at yes, which costs a scan that finds nothing rather than skipping one it should have run. A coarse `bump_geometry` re-opens the question, since it can carry a new document. Where an association really exists the scans run exactly as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
13afc1479f
commit
69bd3c40a9
3 files changed
+75
-7
No files matched your search
@@ -435,3 +435,15 @@ pub(crate) fn center_measure_is_relative(specification: &str) -> bool {
|
|||||||
let text = specification.trim();
|
let text = specification.trim();
|
||||||
text.eq_ignore_ascii_case("ByLineType") || text.ends_with(['x', 'X'])
|
text.eq_ignore_ascii_case("ByLineType") || text.ends_with(['x', 'X'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether `entity` carries a centerline association.
|
||||||
|
///
|
||||||
|
/// Used to keep the drawing-wide scan off the edit path: only an entity that
|
||||||
|
/// actually carries one can make the scan necessary.
|
||||||
|
pub(crate) fn entity_has_center_association(entity: &EntityType) -> bool {
|
||||||
|
let EntityType::Line(line) = entity else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
CenterLineAssociation::read(&line.common.extended_data)
|
||||||
|
.is_some_and(|association| association.associated)
|
||||||
|
}
|
||||||
@@ -473,3 +473,15 @@ impl Scene {
|
|||||||
candidates.len()
|
candidates.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether `entity` carries a center mark association.
|
||||||
|
///
|
||||||
|
/// Used to keep the drawing-wide scan off the edit path: only an entity that
|
||||||
|
/// actually carries one can make the scan necessary.
|
||||||
|
pub(crate) fn entity_has_center_association(entity: &EntityType) -> bool {
|
||||||
|
let EntityType::Line(line) = entity else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
CenterMarkAssociation::read(&line.common.extended_data)
|
||||||
|
.is_some_and(|association| association.associated)
|
||||||
|
}
|
||||||
+51
-7
@@ -1827,6 +1827,15 @@ pub struct Scene {
|
|||||||
/// Boundary source → associative hatch handles. Source edits reuse this
|
/// Boundary source → associative hatch handles. Source edits reuse this
|
||||||
/// index instead of scanning the document during every drag step.
|
/// index instead of scanning the document during every drag step.
|
||||||
associative_hatch_source_cache: RefCell<Option<HashMap<Handle, Vec<Handle>>>>,
|
associative_hatch_source_cache: RefCell<Option<HashMap<Handle, Vec<Handle>>>>,
|
||||||
|
/// Whether the drawing contains any associative centerline or center mark.
|
||||||
|
///
|
||||||
|
/// Refreshing those means scanning every entity and parsing its extended
|
||||||
|
/// data — 1.17 M entities and ~30 ms each on the reproducer, run twice on
|
||||||
|
/// every edit, to find none at all. `None` means not yet determined; the
|
||||||
|
/// first scan records the answer, and later edits only re-examine the
|
||||||
|
/// handles they actually changed. Staying `true` after the last one is
|
||||||
|
/// deleted is merely slower, never wrong.
|
||||||
|
has_associative_centers: std::cell::Cell<Option<bool>>,
|
||||||
/// Tessellated block definitions in block-local coords, keyed by render
|
/// Tessellated block definitions in block-local coords, keyed by render
|
||||||
/// background and block epoch. Model and Paper adapt black/white colours
|
/// background and block epoch. Model and Paper adapt black/white colours
|
||||||
/// differently; retaining both variants prevents a full block rebuild on
|
/// differently; retaining both variants prevents a full block rebuild on
|
||||||
@@ -2057,6 +2066,7 @@ impl Scene {
|
|||||||
layout_type_names_cache: RefCell::new(None),
|
layout_type_names_cache: RefCell::new(None),
|
||||||
dependency_index_cache: RefCell::new(None),
|
dependency_index_cache: RefCell::new(None),
|
||||||
associative_hatch_source_cache: RefCell::new(None),
|
associative_hatch_source_cache: RefCell::new(None),
|
||||||
|
has_associative_centers: std::cell::Cell::new(None),
|
||||||
block_defn_cache: RefCell::new(HashMap::default()),
|
block_defn_cache: RefCell::new(HashMap::default()),
|
||||||
entity_index_cache: RefCell::new(None),
|
entity_index_cache: RefCell::new(None),
|
||||||
last_render_aspect: std::cell::Cell::new(16.0 / 9.0),
|
last_render_aspect: std::cell::Cell::new(16.0 / 9.0),
|
||||||
@@ -2531,6 +2541,35 @@ impl Scene {
|
|||||||
changes
|
changes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the associative centerline / center-mark scans can be skipped.
|
||||||
|
///
|
||||||
|
/// Answers from the cached hint when it is known, and otherwise from the
|
||||||
|
/// changed entities alone: if any of them carries an association the hint
|
||||||
|
/// flips to `true` and the scans run from then on. Only an undetermined
|
||||||
|
/// hint costs a full pass, and that happens once.
|
||||||
|
fn associative_centers_possible(&self, changes: &[(Handle, ChangeKind)]) -> bool {
|
||||||
|
if changes.iter().any(|(handle, _)| {
|
||||||
|
self.document.get_entity(*handle).is_some_and(|entity| {
|
||||||
|
crate::scene::centerline::entity_has_center_association(entity)
|
||||||
|
|| crate::scene::centermark::entity_has_center_association(entity)
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
self.has_associative_centers.set(Some(true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if let Some(known) = self.has_associative_centers.get() {
|
||||||
|
return known;
|
||||||
|
}
|
||||||
|
// Undetermined: settle it with one pass over the drawing, then never
|
||||||
|
// again for this document.
|
||||||
|
let any = self.document.entities().any(|entity| {
|
||||||
|
crate::scene::centerline::entity_has_center_association(entity)
|
||||||
|
|| crate::scene::centermark::entity_has_center_association(entity)
|
||||||
|
});
|
||||||
|
self.has_associative_centers.set(Some(any));
|
||||||
|
any
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bump_entities(&mut self, changes: &[(Handle, ChangeKind)]) {
|
pub fn bump_entities(&mut self, changes: &[(Handle, ChangeKind)]) {
|
||||||
if changes.iter().any(|(handle, kind)| {
|
if changes.iter().any(|(handle, kind)| {
|
||||||
matches!(kind, ChangeKind::Removed)
|
matches!(kind, ChangeKind::Removed)
|
||||||
@@ -2542,14 +2581,16 @@ impl Scene {
|
|||||||
self.associative_hatch_source_cache.borrow_mut().take();
|
self.associative_hatch_source_cache.borrow_mut().take();
|
||||||
}
|
}
|
||||||
let mut changes = changes.to_vec();
|
let mut changes = changes.to_vec();
|
||||||
for change in self.refresh_associative_centerlines(&changes) {
|
if self.associative_centers_possible(&changes) {
|
||||||
if !changes.iter().any(|(handle, _)| *handle == change.0) {
|
for change in self.refresh_associative_centerlines(&changes) {
|
||||||
changes.push(change);
|
if !changes.iter().any(|(handle, _)| *handle == change.0) {
|
||||||
|
changes.push(change);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
for change in self.refresh_associative_center_marks(&changes) {
|
||||||
for change in self.refresh_associative_center_marks(&changes) {
|
if !changes.iter().any(|(handle, _)| *handle == change.0) {
|
||||||
if !changes.iter().any(|(handle, _)| *handle == change.0) {
|
changes.push(change);
|
||||||
changes.push(change);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for change in self.refresh_associative_dimensions(&changes) {
|
for change in self.refresh_associative_dimensions(&changes) {
|
||||||
@@ -2662,6 +2703,9 @@ impl Scene {
|
|||||||
// Default: also invalidate block definitions. Safe for every caller;
|
// Default: also invalidate block definitions. Safe for every caller;
|
||||||
// operations that know blocks are untouched use `bump_geometry_no_blocks`.
|
// operations that know blocks are untouched use `bump_geometry_no_blocks`.
|
||||||
self.block_epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
self.block_epoch = GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed);
|
||||||
|
// A change this coarse can carry a new document; re-determine whether
|
||||||
|
// it holds associative centerlines or center marks.
|
||||||
|
self.has_associative_centers.set(None);
|
||||||
// Structural change — drop both per-entity tessellation memos.
|
// Structural change — drop both per-entity tessellation memos.
|
||||||
self.tess_memo.borrow_mut().clear();
|
self.tess_memo.borrow_mut().clear();
|
||||||
self.resident_tess_memo.borrow_mut().clear();
|
self.resident_tess_memo.borrow_mut().clear();
|
||||||
|
|||||||
Reference in new issue
Block a user