Skip to content

stack: #1037 (share edge arenas with the classification snapshot) merged with main, Send-safe - #1090

Merged
bherbruck merged 4 commits into
tangency-labs:mainfrom
brett19:stack/brep-snapshot-share-edges-on-main
Oct 26, 2026
Merged

bherbruck merged 4 commits into
tangency-labs:mainfrom
brett19:stack/brep-snapshot-share-edges-on-main

Conversation

@brett19

@brett19 brett19 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

Stacks @ablasik's #1037 (commit 4a66dda56, unchanged) on current main, plus one commit that keeps Brep: Send. Closes if #1037 lands instead; credit for the sharing goes to #1037.

Why #1037 stopped compiling

#992 added curves_and_breps_stay_send (assert_send::<Brep>()). #1037 holds the edge and edgeuse arenas in an Arc, and Arc<T> is Send only when T: Send + Sync. Four interior caches that an edge's curve can reach were Cell/RefCell. These are not #992's memo, which is already an OnceLock<Arc<Mutex<…>>>:

field was now
CrvOnSurf::break_cache (m_bNeedBreaks, m_vBreaks) RefCell<BreakCache> Mutex<BreakCache>, locked only for each access
OffsetSurface::offset_distance Cell<f64> F64Cell (an AtomicU64 holding the bits, relaxed)
OffsetSurface pending extension Cell<Option<Box<Surface>>> Mutex<…> with the same take/set
BSplineSurface::out_of_bounds_enabled Cell<bool> AtomicBool (relaxed)

Design and why

I considered three options:

Rc is not an option, because it is not Send.

The reads and writes stay the same and in the same order, and the bits round-trip exactly. No lock is held while geometry runs, which the existing BreakCache and pending-extension invariants already require. A poisoned lock is recovered, since every value is written whole. Nothing panics and there is no unsafe. Atomics and Mutex compile for wasm32-unknown-unknown, as #992's memo does. Uncontended, they cost one load or one CAS.

Sharing these caches between the source and the snapshot is the same behaviour #1037 already had on its base with the Cells. The break cache is a pure function of the curve's value. The out-of-bounds flag is set and restored within one scope. The offset distance and extension are only written through &self by the fillet solvers, never by classification.

Intent preserved

#1037:

  • The edge and edgeuse arenas are shared with the classification snapshot through Arc (Shared::share, clone_sharing_surface_caches -> copy(true)). The commit is unchanged.
  • Copy-on-write: DerefMut is Arc::make_mut.
  • Plain Brep::clone stays deep (copy(false), Shared::clone deep-copies the arena).

#992:

  • The memo (CurveMemo, the key, memo_pure, the off switch) is untouched. curve_cache_memo passes: memo on = memo off, and second run = first.
  • curves_and_breps_stay_send keeps both Send assertions and now also asserts Curve: Sync, which the shared arena relies on. Its comment is updated.

Gate

fmt, clippy -D warnings, cargo test -p solidcore and the wasm check all pass. Difftest ran on the 62 green/partial units that go through curve_on_classify, the Booleans or the changed types: boolean.* (api_brep, merge_p1–p5, topology_intersector_p1–p3), intersect.curve_class_p1–p9, topology.brep_p1–p10, query.brep, curve.crv_on_surf, nurbs.b_spline_surface_p1/p2, surface.offset_surface, surface.surface_point_solve_p1–p3, intersect.surface_intersector_p1–p5 and blend.fillet_* (21 units).

Every unit's total/passed/failed/deferred/rejected/vacuous counts and status are identical to main's CI reports at the merge base f58ad1525: 0 of 62 differ.

Totals: 63954 cases, 63822 passed, 0 failed, 0 deferred, 132 rejected, max_rel_err 0. Sample lines:

PASS boolean.api_brep cases=287 passed=279 failed=0 deferred=0 max_rel_err=0e0 with_values=279 no_values=0 vacuous=0 rejected=8
PASS boolean.merge_p4 cases=311 passed=308 failed=0 deferred=0 max_rel_err=0e0 with_values=308 no_values=0 vacuous=0 rejected=3

Timing (sharing still avoids the deep copy)

The test case is a 100x100x10 box cut by 25 cylinders in turn, then 200 clone_sharing_surface_caches of the result. It ran in release on Linux x86_64 under load (loadavg ~33), so the totals are noisy. I compared it against the same head with copy(true) -> copy(false) (a local experiment, not committed):

sharing (this PR) deep copy
200 snapshots 10.8–16.0 ms 29.6–47.9 ms
25 cuts 178–185 ms 168–336 ms
volume 87433.695421502 identical

The snapshots are about 3x cheaper. This model's edges are only lines and circles. #1037's 269-face battery model measured about 2x for the whole cut.

🤖 Generated with Claude Code

https://claude.ai/code/session_011gJAwcLRBPQXYLmvXd3pWf

ablasik and others added 4 commits October 25, 2026 23:02
…ication snapshot

curve_on_classify clones the whole B-rep (clone_sharing_surface_caches) for
every curve it classifies and drops it afterwards; on a 269-face boolean this
was about half of the cut, and the edge and edgeuse arenas were 72% of each
copy. The snapshot now shares those two arenas through an Arc and copies one
only when something writes to it (Arc::make_mut in DerefMut), so every writer
sees exactly what the deep copy gave it. Plain Brep::clone stays deep.
… Send

The classification snapshot shares the edge and edgeuse arenas through an
`Arc`, which is `Send` only when the arenas are `Sync`. Four interior caches
reachable from an edge's curve were `Cell`/`RefCell`:

- `CrvOnSurf::break_cache`: `RefCell` -> `Mutex` (locked only per access)
- `OffsetSurface::offset_distance`: `Cell<f64>` -> `AtomicU64` of the bits
- `OffsetSurface` pending extension: `Cell<Option<Box<Surface>>>` -> `Mutex`
- `BSplineSurface::out_of_bounds_enabled`: `Cell<bool>` -> `AtomicBool`

Same reads and writes in the same order, relaxed atomics, poisoned locks
recovered (values are written whole), so no result changes. The memo test
now also asserts `Curve: Sync`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011gJAwcLRBPQXYLmvXd3pWf

@bherbruck bherbruck left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved by the automated first-pass review: CI green, mergeable, no CI/build/dependency file changes, no red-flag patterns in added code (process/network/env/unsafe outside FFI/blobs/hidden unicode), 0 known-mismatch entries added. Logic is gated by CI and the difftest merge queue.

@bherbruck
bherbruck added this pull request to the merge queue Oct 26, 2026
Merged via the queue into tangency-labs:main with commit 98c9420 Oct 26, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants