refactor(selection): migrate Scene.selection to Arc<RefCell> to avoid per-frame deep clone

Scene.selection was Rc<RefCell<SelectionState>> (31 fields, ~1.6KB
with poly_points) cloned deep per view_main per pane via
(*sel_ref).clone() at view/mod.rs:760. Replace with
Arc<RefCell<SelectionState>> (Arc::new(RefCell::new(...)) at
scene/mod.rs:1436,1868), SelectionCanvas holds Arc<RefCell> and
selection_overlay takes Arc<RefCell> (overlay.rs:365,415),
view_main does Arc::clone(&tab.scene.selection) (atomic bump)
instead of deep clone. sel_ref stays borrowed for field reads
(vp_size etc.).

Bench (release, n=5000): SelectionState::clone 39-68ns (64pts) /
11-22ns (0pts) vs Arc::clone 7ns — 5-9x on the hot path.

Tests:
- selection_is_arc_not_refcell: asserts type_name contains Arc
- bench_selection_state_clone / bench_selection_arc_clone / bench_selection_state_clone_empty (ignored, release)
This commit is contained in:
Karim Jerbi committed 2026-09-02 22:14:32 +01:00
1 parent d2338fa10f
commit 46190ccba1
4 files changed
+129 -29

No files matched your search

+5 -9
View File
@@ -435,14 +435,10 @@ impl OpenCADStudio {
// Hold a single `Ref<'_, SelectionState>` for the whole overlay
// block. The `vp_size` and `last_move_pos` reads below become
// field accesses on `sel_ref` (no redundant `borrow()`s), and
// the final widget call takes `(*sel_ref).clone()` — one deep
// clone per frame, at the only site that needs an owned
// `SelectionState`. The `Ref` is dropped at the end of this
// block. **Invariant:** no `selection.borrow_mut()` exists
// inside this block (the only two `borrow_mut` sites in
// `src/scene` are in `preview.rs:282` and `render.rs:3443`,
// neither reachable from here). Holding the `Ref` is therefore
// safe.
// the final widget call takes `Arc::clone(&selection)` — atomic
// bump per frame instead of a deep `SelectionState` clone.
// `sel_ref` is a borrowed view for field reads; the widget owns
// the Arc clone. No `selection` mutation occurs inside this block.
let sel_ref = tab.scene.selection.borrow();
let snap_info = tab.snap_result.map(|s| (s.screen, s.snap_type));
let snap_ext_base = tab.snap_result.and_then(|s| s.extension_base);
@@ -757,7 +753,7 @@ impl OpenCADStudio {
.map(|h| tab.scene.is_layer_locked(h))
.unwrap_or(false);
crate::ui::overlay::selection_overlay(
(*sel_ref).clone(),
std::sync::Arc::clone(&tab.scene.selection),
snap_info,
snap_ext_base,
snap_ext_base2,