fix(viewcube): cache UCS picker names behind an epoch

The two per-frame ViewCube blocks rebuilt the named-UCS name list (N String clones plus a Vec) every frame; now Scene::cached_ucs_names memoises it behind ucs_epoch and the picker takes the Arc, so frames cost a refcount bump. Bumped on UCS SAVE, UCS DELETE, RENAME UCS, record-API ucss patch, file-open, and undo Full-swap. Tests: cached_ucs_names_returns_same_arc_until_bump pins the hit path; cached_ucs_names_invalidated_by_bump pins rebuild with new content. Full suite: 1976 passed / 0 failed / 25 ignored.
This commit is contained in:
Karim Jerbi committed 2026-10-06 20:10:23 +01:00
1 parent bd37a2f969
commit 8c2cfa2c8f
9 files changed
+85 -20

No files matched your search

+1
View File
@@ -648,6 +648,7 @@ impl OpenCADStudio {
self.tabs[i].scene.document = doc;
self.tabs[i].scene.bump_layout_epoch();
self.tabs[i].scene.bump_scale_epoch();
self.tabs[i].scene.bump_ucs_epoch();
self.tabs[i].scene.load_named_parameters_from_document();
self.tabs[i]
.scene
+2
View File
@@ -247,6 +247,7 @@ impl OpenCADStudio {
.document
.ucss
.add_or_replace(ucs.clone());
self.tabs[i].scene.bump_ucs_epoch();
self.tabs[i].active_ucs = Some(ucs);
active_changed = true;
self.tabs[i].dirty = true;
@@ -261,6 +262,7 @@ impl OpenCADStudio {
} else if let Some(removed) =
self.tabs[i].scene.document.ucss.remove(&name)
{
self.tabs[i].scene.bump_ucs_epoch();
let removed_handle = removed.handle;
for entity in self.tabs[i].scene.document.entities_mut() {
if let codec::EntityType::Viewport(viewport) = entity {
+1
View File
@@ -2997,6 +2997,7 @@ impl OpenCADStudio {
self.tabs[i].scene.bump_geometry_no_blocks();
}
if type_str == "UCS" {
self.tabs[i].scene.bump_ucs_epoch();
if let Some(active) = self.tabs[i].active_ucs.as_mut() {
if active.name.eq_ignore_ascii_case(&old_name) {
active.name = new_name.clone();
+1
View File
@@ -855,6 +855,7 @@ impl OpenCADStudio {
self.tabs[i].scene.invalidate_dependency_index();
self.tabs[i].scene.bump_layout_epoch();
self.tabs[i].scene.bump_scale_epoch();
self.tabs[i].scene.bump_ucs_epoch();
}
StructureSnapshot::Layers(entries) => {
let names: Vec<String> =
+6 -1
View File
@@ -1611,7 +1611,12 @@ impl OpenCADStudio {
"app_ids" => patch_table!(app_ids),
"views" => patch_table!(views),
"vports" => patch_table!(vports),
"ucss" => patch_table!(ucss),
"ucss" => {
patch_table!(ucss);
if changed {
self.tabs[i].scene.bump_ucs_epoch();
}
}
"vx_table" => patch_table!(vx_table),
_ => unreachable!(),
}
+1
View File
@@ -1860,6 +1860,7 @@ impl OpenCADStudio {
self.tabs[i].scene.document = doc;
self.tabs[i].scene.bump_layout_epoch();
self.tabs[i].scene.bump_scale_epoch();
self.tabs[i].scene.bump_ucs_epoch();
// Load parameters first so imported dimensional constraints
// can resolve their named driving values.
self.tabs[i].scene.load_named_parameters_from_document();
+6 -16
View File
@@ -1416,14 +1416,9 @@ bg={bg_ms:.1}ms n={view_count}"
.as_ref()
.map(|u| u.name.clone())
.unwrap_or_default();
let ucs_names: Vec<String> = tab
.scene
.document
.ucss
.iter()
.map(|u| u.name.clone())
.filter(|n| !n.is_empty())
.collect();
// Epoch-memoised (#34): Arc clone per frame, rebuilt only
// after a UCS-table bump.
let ucs_names = tab.scene.cached_ucs_names();
let picker = iced::widget::pin(iced::widget::opaque(viewcube_ucs_picker(
ucs_current,
ucs_names,
@@ -1456,14 +1451,9 @@ bg={bg_ms:.1}ms n={view_count}"
.as_ref()
.map(|u| u.name.clone())
.unwrap_or_default();
let ucs_names: Vec<String> = tab
.scene
.document
.ucss
.iter()
.map(|u| u.name.clone())
.filter(|n| !n.is_empty())
.collect();
// Epoch-memoised (#34): Arc clone per frame, rebuilt only
// after a UCS-table bump.
let ucs_names = tab.scene.cached_ucs_names();
let picker = iced::widget::pin(iced::widget::opaque(viewcube_ucs_picker(
ucs_current,
ucs_names,
+8 -3
View File
@@ -150,10 +150,15 @@ pub(super) fn viewcube_nav_controls<'a>(
/// Fixed width of the UCS picker, so it can be centred under the cube.
pub(super) const UCS_PICKER_W: f32 = 84.0;
/// The WCS / named-UCS selector shown under the cube.
pub(super) fn viewcube_ucs_picker<'a>(current: String, names: Vec<String>) -> Element<'a, Message> {
/// The WCS / named-UCS selector shown under the cube. `names` is the
/// epoch-memoised `Scene::cached_ucs_names` Arc (#34) — a refcount bump per
/// frame instead of N String clones.
pub(super) fn viewcube_ucs_picker<'a>(
current: String,
names: std::sync::Arc<[String]>,
) -> Element<'a, Message> {
let mut options = vec!["WCS".to_string()];
options.extend(names);
options.extend(names.iter().cloned());
let selected = if current.is_empty() {
"WCS".to_string()
} else {