partforge 0.70.0 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/KERNEL-CONTRACT.md
CHANGED
|
@@ -98,11 +98,25 @@ them loses sub-part caching and mesh-topology gates (`holes`, emptiness), nothin
|
|
|
98
98
|
a part (never inside a `beginSubPart`/`endSubPart` bracket), it drops cache partitions that
|
|
99
99
|
have gone unbuilt for three consecutive rebinds.
|
|
100
100
|
|
|
101
|
+
`beginSubPart`/`endSubPart` brackets MAY nest: only the outermost pair opens and
|
|
102
|
+
commits a round, and an inner pair is a balanced no-op. Nesting is real rather than
|
|
103
|
+
theoretical — `buildView` opens a round of its own, so any caller that brackets around
|
|
104
|
+
a view build contains one. A backend that keeps a single open round (rather than a
|
|
105
|
+
stack) must collapse inner pairs this way; committing on the inner `end()` would close
|
|
106
|
+
the outer round early and leave the rest of that build uncached.
|
|
107
|
+
|
|
101
108
|
Sub-part brackets bound cache RETENTION, not reuse: a solid one sub-part builds is reused
|
|
102
109
|
by any other that asks for the same content hash, so a sheet of identical cells split
|
|
103
110
|
across row sub-parts evaluates each distinct cell once rather than once per row. An adopted
|
|
104
111
|
entry is retained by both partitions and disposed only when the last one drops it.
|
|
105
112
|
|
|
113
|
+
The oracle (`buildView`, `assemblyOverlaps`) brackets under partition names of its
|
|
114
|
+
own rather than the display sub-part names, and a host adding another oracle-side build
|
|
115
|
+
should do the same. Both reuse the display build's solids through the cross-partition
|
|
116
|
+
index, so measuring a view costs almost nothing right after drawing it; keeping them in
|
|
117
|
+
separate partitions is what stops a measurement's own geometry — verify walks cases with
|
|
118
|
+
params of their own — from displacing the geometry the viewer is showing.
|
|
119
|
+
|
|
106
120
|
**Transform hoisting.** Booleans commute with rigid transforms, so a conforming backend MAY
|
|
107
121
|
lift a transform every operand shares out of the boolean and apply it to the result
|
|
108
122
|
instead — which is what lets N identically-built copies share one evaluation. Two
|
package/package.json
CHANGED
|
@@ -9,10 +9,17 @@ import { viewSubParts, resolveParams, buildPosed } from "./part-model.js";
|
|
|
9
9
|
// → [{ a, b, volume, location }] for each offending pair (empty = no collisions)
|
|
10
10
|
export function assemblyOverlaps(kernel, part, view, params = {}, { tolerance = 1 } = {}) {
|
|
11
11
|
const { p, d } = resolveParams(part, params);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
})
|
|
12
|
+
// Same posed solids buildView builds, so this round is almost entirely hits off
|
|
13
|
+
// that one; only the pairwise intersects below are new. Its own oracle partition,
|
|
14
|
+
// for the reason buildView's comment gives.
|
|
15
|
+
kernel.beginSubPart?.(`oracle:overlaps:${view}`);
|
|
16
|
+
let posed;
|
|
17
|
+
try {
|
|
18
|
+
posed = viewSubParts(part, view, p).map((name) => ({
|
|
19
|
+
name,
|
|
20
|
+
solid: buildPosed(kernel, part, name, { purpose: "display", view, p, d }),
|
|
21
|
+
}));
|
|
22
|
+
} catch (e) { kernel.endSubPart?.(); throw e; } // never strand the round on a failed build
|
|
16
23
|
|
|
17
24
|
const overlaps = [];
|
|
18
25
|
for (let i = 0; i < posed.length; i++) {
|
|
@@ -27,6 +34,7 @@ export function assemblyOverlaps(kernel, part, view, params = {}, { tolerance =
|
|
|
27
34
|
}
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
|
-
kernel.
|
|
37
|
+
kernel.endSubPart?.();
|
|
38
|
+
kernel.cleanup?.(); // free the per-check WASM objects (cached solids are pinned)
|
|
31
39
|
return overlaps;
|
|
32
40
|
}
|
|
@@ -15,6 +15,7 @@ export function createSolidCache() {
|
|
|
15
15
|
const lastBuilt = new Map(); // name -> rebind generation of the partition's last begin()
|
|
16
16
|
let generation = 0; // bumped only by sweep() (i.e. per part rebind)
|
|
17
17
|
let name = null, active = null, prev = null;
|
|
18
|
+
let depth = 0; // bracket nesting; only the OUTERMOST one is real (see begin)
|
|
18
19
|
let hits = 0, misses = 0;
|
|
19
20
|
|
|
20
21
|
// One partition stops retaining `entry`. Disposal waits for the LAST holder:
|
|
@@ -30,9 +31,19 @@ export function createSolidCache() {
|
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
return {
|
|
33
|
-
|
|
34
|
+
// Nested brackets collapse into the outermost one. There is a single open
|
|
35
|
+
// round (name/active/prev), not a stack, so an inner begin() would otherwise
|
|
36
|
+
// rebind it and the inner end() would commit-and-close the OUTER round early —
|
|
37
|
+
// evicting its entries and leaving the rest of that build uncached. Callers
|
|
38
|
+
// nest legitimately now that buildView brackets: an outer bracket is free to
|
|
39
|
+
// contain one, and the inner pair becomes a no-op.
|
|
40
|
+
begin(n) {
|
|
41
|
+
if (depth++ > 0) return;
|
|
42
|
+
name = n; lastBuilt.set(n, generation); prev = caches.get(n) ?? new Map(); active = new Map();
|
|
43
|
+
},
|
|
34
44
|
|
|
35
45
|
end() {
|
|
46
|
+
if (depth > 0 && --depth > 0) return; // inner bracket — the outer one still owns the round
|
|
36
47
|
if (name == null) return;
|
|
37
48
|
for (const [hash, entry] of prev) {
|
|
38
49
|
if (!active.has(hash)) release(hash, entry); // this partition drops it
|
|
@@ -7,8 +7,19 @@ import { viewSubParts, resolveParams, buildPosed } from "../part-model.js";
|
|
|
7
7
|
// before they free the kernel. Meshes are JS-owned arrays and survive cleanup.
|
|
8
8
|
export function buildView(kernel, part, view, params = {}) {
|
|
9
9
|
const { p, d } = resolveParams(part, params);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// Cache round for the whole view. The name is the ORACLE's, deliberately not the
|
|
11
|
+
// display sub-part names the generate path brackets under: a distinct partition
|
|
12
|
+
// still reuses those solids (the cache indexes entries by content hash across
|
|
13
|
+
// partitions), while keeping this round's own eviction away from the geometry the
|
|
14
|
+
// viewer is showing — bracketing under the display names would make running the
|
|
15
|
+
// oracle throw away the display cache. One name per view also bounds retention:
|
|
16
|
+
// verify walks its cases through here, so each case evicts the previous rather
|
|
17
|
+
// than accumulating every case's geometry at once.
|
|
18
|
+
kernel.beginSubPart?.(`oracle:view:${view}`);
|
|
19
|
+
try {
|
|
20
|
+
return viewSubParts(part, view, p).map((name) => {
|
|
21
|
+
const solid = buildPosed(kernel, part, name, { purpose: "display", view, p, d });
|
|
22
|
+
return { name, solid, mesh: solid.toMesh() };
|
|
23
|
+
});
|
|
24
|
+
} finally { kernel.endSubPart?.(); }
|
|
14
25
|
}
|