perf(selection): cache plugin fingerprint
This commit is contained in:
parent
5c125f96be
commit
cfe1ad0006
5 changed files with 70 additions and 51 deletions
|
|
@ -679,7 +679,7 @@ pub(super) struct OpenCADStudio {
|
||||||
/// keep their manifest listed but drop their ribbon tab and command
|
/// keep their manifest listed but drop their ribbon tab and command
|
||||||
/// dispatch. Persisted via [`settings::UserSettings::disabled_plugins`].
|
/// dispatch. Persisted via [`settings::UserSettings::disabled_plugins`].
|
||||||
disabled_plugins: rustc_hash::FxHashSet<String>,
|
disabled_plugins: rustc_hash::FxHashSet<String>,
|
||||||
/// `(tab id, selection signature)` last broadcast to V4 plugins, so
|
/// `(tab id, selection fingerprint)` last broadcast to V4 plugins, so
|
||||||
/// `SelectionChangedV4` fires once per real change rather than per message.
|
/// `SelectionChangedV4` fires once per real change rather than per message.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
last_plugin_selection: Option<(u64, u64)>,
|
last_plugin_selection: Option<(u64, u64)>,
|
||||||
|
|
|
||||||
|
|
@ -233,12 +233,6 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
/// Emit `SelectionChangedV4` to V4 plugins when the active tab's selection
|
/// Emit `SelectionChangedV4` to V4 plugins when the active tab's selection
|
||||||
/// set actually changed since the last broadcast.
|
/// set actually changed since the last broadcast.
|
||||||
///
|
|
||||||
/// Checked at the message boundary rather than at the mutation sites so
|
|
||||||
/// every path is covered: picking, window select, QSELECT, SELECTALL,
|
|
||||||
/// grip edits that drop the selection, and the automation `select` op.
|
|
||||||
/// The signature comparison keeps hover and repeated no-op selects from
|
|
||||||
/// producing spurious notifications.
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(super) fn notify_plugins_selection_changed(&mut self) {
|
pub(super) fn notify_plugins_selection_changed(&mut self) {
|
||||||
if self.active_tab >= self.tabs.len() {
|
if self.active_tab >= self.tabs.len() {
|
||||||
|
|
@ -246,8 +240,8 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let tab_id = self.tabs[i].id;
|
let tab_id = self.tabs[i].id;
|
||||||
let sig = self.tabs[i].scene.selection_sig();
|
let fingerprint = self.tabs[i].scene.selection_fingerprint();
|
||||||
let key = (tab_id, sig);
|
let key = (tab_id, fingerprint);
|
||||||
if self.last_plugin_selection == Some(key) {
|
if self.last_plugin_selection == Some(key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,8 +203,11 @@ impl Scene {
|
||||||
|
|
||||||
/// If any handle belongs to a selectable group, also select every member.
|
/// If any handle belongs to a selectable group, also select every member.
|
||||||
pub fn expand_selection_for_groups(&mut self, handles: &[Handle]) {
|
pub fn expand_selection_for_groups(&mut self, handles: &[Handle]) {
|
||||||
|
let previous_len = self.selected.len();
|
||||||
self.selected
|
self.selected
|
||||||
.extend(self.handles_expanded_for_selectable_groups(handles));
|
.extend(self.handles_expanded_for_selectable_groups(handles));
|
||||||
self.bump_selection();
|
if self.selected.len() != previous_len {
|
||||||
|
self.bump_selection_set();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1486,6 +1486,9 @@ pub struct Scene {
|
||||||
/// pick only refreshes the GPU xray overlay (cheap) instead of bumping
|
/// pick only refreshes the GPU xray overlay (cheap) instead of bumping
|
||||||
/// `geometry_epoch` and re-tessellating the whole model.
|
/// `geometry_epoch` and re-tessellating the whole model.
|
||||||
pub selection_generation: u64,
|
pub selection_generation: u64,
|
||||||
|
/// Cached fingerprint of `selected`, recomputed lazily after mutations.
|
||||||
|
selection_fingerprint_cache: u64,
|
||||||
|
selection_fingerprint_dirty: bool,
|
||||||
/// Cached tessellation of all visible entity wires for the current layout.
|
/// Cached tessellation of all visible entity wires for the current layout.
|
||||||
/// Keyed by `(geometry_epoch, camera_generation)` so a camera change
|
/// Keyed by `(geometry_epoch, camera_generation)` so a camera change
|
||||||
/// invalidates the cull-dependent wire list as well as a geometry change.
|
/// invalidates the cull-dependent wire list as well as a geometry change.
|
||||||
|
|
@ -1861,6 +1864,8 @@ impl Scene {
|
||||||
projection_bounds_epoch: std::cell::Cell::new(0),
|
projection_bounds_epoch: std::cell::Cell::new(0),
|
||||||
block_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
block_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
||||||
selection_generation: 0,
|
selection_generation: 0,
|
||||||
|
selection_fingerprint_cache: 0,
|
||||||
|
selection_fingerprint_dirty: false,
|
||||||
wire_cache: RefCell::new(None),
|
wire_cache: RefCell::new(None),
|
||||||
interaction_index_cache: RefCell::new(Vec::new()),
|
interaction_index_cache: RefCell::new(Vec::new()),
|
||||||
interaction_index_pending_key: std::cell::Cell::new(None),
|
interaction_index_pending_key: std::cell::Cell::new(None),
|
||||||
|
|
@ -2610,6 +2615,11 @@ impl Scene {
|
||||||
self.selection_generation = self.selection_generation.wrapping_add(1);
|
self.selection_generation = self.selection_generation.wrapping_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bump_selection_set(&mut self) {
|
||||||
|
self.selection_fingerprint_dirty = true;
|
||||||
|
self.bump_selection();
|
||||||
|
}
|
||||||
|
|
||||||
/// Milliseconds after the last camera change during which the view counts as
|
/// Milliseconds after the last camera change during which the view counts as
|
||||||
/// "actively navigating" for interaction-LOD purposes.
|
/// "actively navigating" for interaction-LOD purposes.
|
||||||
const NAV_SETTLE_MS: u128 = 130;
|
const NAV_SETTLE_MS: u128 = 130;
|
||||||
|
|
@ -4398,6 +4408,7 @@ impl Scene {
|
||||||
.extend(self.selected.iter().copied());
|
.extend(self.selected.iter().copied());
|
||||||
self.selected.clear();
|
self.selected.clear();
|
||||||
self.selected_order.clear();
|
self.selected_order.clear();
|
||||||
|
self.bump_selection_set();
|
||||||
self.bump_entities(&changes);
|
self.bump_entities(&changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,11 @@ impl Scene {
|
||||||
}
|
}
|
||||||
pub fn select_entity(&mut self, handle: Handle, exclusive: bool) {
|
pub fn select_entity(&mut self, handle: Handle, exclusive: bool) {
|
||||||
let handles = self.handles_expanded_for_leader_annotations(&[handle]);
|
let handles = self.handles_expanded_for_leader_annotations(&[handle]);
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
if exclusive {
|
if exclusive {
|
||||||
|
changed = self.selected.len() != handles.len()
|
||||||
|
|| handles.iter().any(|handle| !self.selected.contains(handle));
|
||||||
self.selected.clear();
|
self.selected.clear();
|
||||||
self.selected_order.clear();
|
self.selected_order.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -49,30 +52,35 @@ impl Scene {
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
if self.selected.insert(handle) {
|
if self.selected.insert(handle) {
|
||||||
self.selected_order.push(handle);
|
self.selected_order.push(handle);
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.bump_selection();
|
if changed {
|
||||||
|
self.bump_selection_set();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deselect_all(&mut self) {
|
pub fn deselect_all(&mut self) {
|
||||||
|
if self.selected.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.selected.clear();
|
self.selected.clear();
|
||||||
self.selected_order.clear();
|
self.selected_order.clear();
|
||||||
self.bump_selection();
|
self.bump_selection_set();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Order-independent signature of the current selection set.
|
pub(crate) fn selection_fingerprint(&mut self) -> u64 {
|
||||||
///
|
if self.selection_fingerprint_dirty {
|
||||||
/// `selection_generation` cannot be used for change detection because it
|
let mut fingerprint = self.selected.len() as u64;
|
||||||
/// also bumps on hover. This signature combines a per-handle hash with XOR
|
for handle in &self.selected {
|
||||||
/// so it is allocation-free, O(n), and insensitive to selection order.
|
let mut hasher = DefaultHasher::new();
|
||||||
pub(crate) fn selection_sig(&self) -> u64 {
|
handle.hash(&mut hasher);
|
||||||
let mut combined = 0u64;
|
fingerprint ^= hasher.finish();
|
||||||
for handle in &self.selected {
|
}
|
||||||
let mut hasher = DefaultHasher::new();
|
self.selection_fingerprint_cache = fingerprint;
|
||||||
handle.hash(&mut hasher);
|
self.selection_fingerprint_dirty = false;
|
||||||
combined ^= hasher.finish();
|
|
||||||
}
|
}
|
||||||
combined
|
self.selection_fingerprint_cache
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn selected_handles_in_order(&self) -> Vec<Handle> {
|
pub(crate) fn selected_handles_in_order(&self) -> Vec<Handle> {
|
||||||
|
|
@ -121,7 +129,7 @@ impl Scene {
|
||||||
order.extend(added);
|
order.extend(added);
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
self.selected_order = order;
|
self.selected_order = order;
|
||||||
self.bump_selection();
|
self.bump_selection_set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +144,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
self.bump_selection();
|
self.bump_selection_set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +225,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if added > 0 {
|
if added > 0 {
|
||||||
self.bump_selection();
|
self.bump_selection_set();
|
||||||
}
|
}
|
||||||
added
|
added
|
||||||
}
|
}
|
||||||
|
|
@ -241,7 +249,9 @@ impl Scene {
|
||||||
self.selected_order.push(h);
|
self.selected_order.push(h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.bump_selection();
|
if self.selected != prev {
|
||||||
|
self.bump_selection_set();
|
||||||
|
}
|
||||||
self.selected.len()
|
self.selected.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -727,7 +737,8 @@ impl Scene {
|
||||||
|
|
||||||
let mut handle_set: HashSet<Handle> = HashSet::default();
|
let mut handle_set: HashSet<Handle> = HashSet::default();
|
||||||
let mut erased: Vec<(Handle, ChangeKind)> = Vec::new();
|
let mut erased: Vec<(Handle, ChangeKind)> = Vec::new();
|
||||||
let mut highlight_changed = false;
|
let mut selection_changed = false;
|
||||||
|
let mut hover_changed = false;
|
||||||
|
|
||||||
for &h in &erase_handles {
|
for &h in &erase_handles {
|
||||||
// Objects on a locked layer can't be erased.
|
// Objects on a locked layer can't be erased.
|
||||||
|
|
@ -742,11 +753,11 @@ impl Scene {
|
||||||
self.delete_solid_history(h);
|
self.delete_solid_history(h);
|
||||||
self.remember_removed_cache_categories(h);
|
self.remember_removed_cache_categories(h);
|
||||||
self.document.remove_entity_arc(h);
|
self.document.remove_entity_arc(h);
|
||||||
highlight_changed |= self.selected.remove(&h);
|
selection_changed |= self.selected.remove(&h);
|
||||||
self.selected_order.retain(|selected| *selected != h);
|
self.selected_order.retain(|selected| *selected != h);
|
||||||
if self.hover_highlight == Some(h) {
|
if self.hover_highlight == Some(h) {
|
||||||
self.hover_highlight = None;
|
self.hover_highlight = None;
|
||||||
highlight_changed = true;
|
hover_changed = true;
|
||||||
}
|
}
|
||||||
self.hatches.remove(&h);
|
self.hatches.remove(&h);
|
||||||
self.images.remove(&h);
|
self.images.remove(&h);
|
||||||
|
|
@ -756,7 +767,9 @@ impl Scene {
|
||||||
handle_set.insert(h);
|
handle_set.insert(h);
|
||||||
erased.push((h, ChangeKind::Removed));
|
erased.push((h, ChangeKind::Removed));
|
||||||
}
|
}
|
||||||
if highlight_changed {
|
if selection_changed {
|
||||||
|
self.bump_selection_set();
|
||||||
|
} else if hover_changed {
|
||||||
self.bump_selection();
|
self.bump_selection();
|
||||||
}
|
}
|
||||||
// Capture exactly the group objects that this erase will rewrite, plus
|
// Capture exactly the group objects that this erase will rewrite, plus
|
||||||
|
|
@ -862,25 +875,23 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn selection_sig_is_order_independent() {
|
fn selection_fingerprint_tracks_final_set_only() {
|
||||||
let mut scene_a = Scene::default();
|
|
||||||
let mut scene_b = Scene::default();
|
|
||||||
let h1 = Handle::new(1);
|
|
||||||
let h2 = Handle::new(2);
|
|
||||||
scene_a.select_entity(h1, false);
|
|
||||||
scene_a.select_entity(h2, false);
|
|
||||||
scene_b.select_entity(h2, false);
|
|
||||||
scene_b.select_entity(h1, false);
|
|
||||||
assert_eq!(scene_a.selection_sig(), scene_b.selection_sig());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn selection_sig_changes_when_selection_changes() {
|
|
||||||
let mut scene = Scene::default();
|
let mut scene = Scene::default();
|
||||||
let empty_sig = scene.selection_sig();
|
let first = Handle::new(1);
|
||||||
scene.select_entity(Handle::new(1), false);
|
let second = Handle::new(2);
|
||||||
assert_ne!(scene.selection_sig(), empty_sig);
|
scene.select_entity(first, false);
|
||||||
|
scene.select_entity(second, false);
|
||||||
|
let fingerprint = scene.selection_fingerprint();
|
||||||
|
assert!(!scene.selection_fingerprint_dirty);
|
||||||
|
|
||||||
scene.deselect_all();
|
scene.deselect_all();
|
||||||
assert_eq!(scene.selection_sig(), empty_sig);
|
scene.select_entity(second, false);
|
||||||
|
scene.select_entity(first, false);
|
||||||
|
assert!(scene.selection_fingerprint_dirty);
|
||||||
|
assert_eq!(scene.selection_fingerprint(), fingerprint);
|
||||||
|
|
||||||
|
scene.set_hover_highlight(Some(Handle::new(3)));
|
||||||
|
assert!(!scene.selection_fingerprint_dirty);
|
||||||
|
assert_eq!(scene.selection_fingerprint(), fingerprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue