Skip to content

Give cached bodies fresh originalIDs per call site - #84

Merged
revarbat merged 1 commit into
mainfrom
restamp-cached-ids
Aug 10, 2026
Merged

Give cached bodies fresh originalIDs per call site#84
revarbat merged 1 commit into
mainfrom
restamp-cached-ids

Conversation

@revarbat

Copy link
Copy Markdown
Member

Two identical shapes at two call sites came back sharing one originalID, so a UI keying off it treated them as one thing — selecting either picked both.

translate([60,60,0]) cylinder(h=20,r=4,$fn=32);
translate([85,60,0]) cylinder(h=20,r=4,$fn=32);

without ManifoldCache → ids [1], [2]   distinct
with    ManifoldCache → ids [3], [3]   same        ← the bug
after this change     → ids [3], [4]   distinct

A cache hit handed back the geometry and the IDs of whichever call site first produced it. Those IDs are provenance — which node made this — not content, so they cannot be cached along with the shape.

Reported against two identical cylinders. It affects any array of identical parts, which in CAD is most of them.

Per run, not per body

Each distinct run gets a fresh ID on a hit. That distinction matters: Manifold's own AsOriginal() collapses a multi-run body into a single ID —

union of two cubes: runOriginalID [1, 2]  →  as_original()  →  [5]

— which would have made a cached subtree of several parts selectable only as a whole. Each new ID inherits the old one's node and colour, so parts keep pointing at the AST nodes that actually produced them. A cached two-part module used twice yields four distinct IDs, and that is asserted.

Cost

Measured on 200 identical spheres, where every one after the first is a cache hit:

cold   170 ms
warm   128 ms

The re-stamp rebuilds each cached body from its mesh, and the cache still pays for itself comfortably.

Verification

Two new tests: distinct IDs across call sites (with the node mapping intact for both), and one-ID-per-part for a reused multi-part subtree.

The first hung on its first version — GetMeshGL() returns by value, and taking begin() and end() from two separate calls walks between unrelated buffers. Held in locals now.

803 C++ tests pass. Against a local build, BelfrySCAD's 653 tests and its GUI verifiers pass, and the two cylinders now report original_ids=[1] and [2] in the live renderer.

🤖 Generated with Claude Code

Two identical shapes at two call sites came back sharing one originalID,
so a UI keying off it treated them as one thing: selecting either picked
both. Reported against two identical cylinders; it affects any array of
identical parts, which in CAD is most of them.

A cache hit handed back the geometry AND the IDs of whichever call site
first produced it. Those IDs are provenance -- which node made this -- not
content, so they cannot be cached along with the shape.

Each distinct run now gets a fresh ID on a hit, one per run rather than
one per body. That matters: Manifold's own AsOriginal() collapses a
multi-run body to a single ID, which would have made a cached subtree of
several parts selectable only as a whole. Each new ID inherits the old
one's node and colour, so parts keep pointing at the AST nodes that
actually produced them.

Measured on 200 identical spheres, where every one after the first is a
hit: 170 ms cold, 128 ms warm. The re-stamp rebuilds each cached body from
its mesh, and the cache still pays for itself comfortably.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@revarbat
revarbat merged commit 5f4778e into main Aug 10, 2026
3 checks passed
@revarbat
revarbat deleted the restamp-cached-ids branch August 10, 2026 02:17
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.

1 participant