partforge 0.130.1 → 0.130.2
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
|
@@ -867,6 +867,12 @@ The new mount claims that reply and drops it, rather than letting its own handle
|
|
|
867
867
|
the reply's `error` as a failed build or its `download` as a viewbar save. The previous
|
|
868
868
|
mount already rejected that export when it was disposed.
|
|
869
869
|
|
|
870
|
+
`captureView` replies cross a rebind the same way, and `mount()` handles them the same
|
|
871
|
+
way. Its `capture-generate` jobIds (`cap-<b>-<n>`) are unique to each mount, so a previous
|
|
872
|
+
mount's `capture-meshes` can never settle the new mount's capture with the old geometry,
|
|
873
|
+
and the new mount claims and drops that reply, including an `error`. The previous mount
|
|
874
|
+
already resolved that capture to `null` when it was disposed.
|
|
875
|
+
|
|
870
876
|
**Cancellation granularity is the sub-part.** The guard is checked only between
|
|
871
877
|
sub-parts (one macrotask yield each), so a single long WASM op — a big boolean, an OCCT
|
|
872
878
|
fillet — runs to completion no matter how stale it is. That is by design: WASM kernel
|
package/package.json
CHANGED
|
@@ -2,7 +2,23 @@
|
|
|
2
2
|
// does NOT go through the regen loop. Same shape as export-controller's pending
|
|
3
3
|
// Map (export-controller.js): allocate a jobId, resolve when the matching
|
|
4
4
|
// reply arrives. Pure — no DOM, no worker; `send` is injected.
|
|
5
|
+
|
|
6
|
+
// Job ids are unique per capture build, not just per mount: `cap-<b>-<n>`,
|
|
7
|
+
// where <b> numbers the capture builds this module has made. Each mount makes
|
|
8
|
+
// one, but a host may keep a worker across mounts (partforge-cloud's pool
|
|
9
|
+
// rebinds it with setPart), and that worker's replies then reach whichever
|
|
10
|
+
// mount is listening now. A capture-generate runs to completion, so a disposed
|
|
11
|
+
// mount's capture still answers after the remount. When every mount numbered
|
|
12
|
+
// its captures from 1, that late reply settled the next mount's same-numbered
|
|
13
|
+
// capture with pre-edit geometry. The same scheme as export-controller.js's
|
|
14
|
+
// `export-<c>-<n>`.
|
|
15
|
+
let builds = 0;
|
|
16
|
+
// Every id any capture build mints, so a reply to another build's job is
|
|
17
|
+
// recognised even though this build never sent it.
|
|
18
|
+
const CAPTURE_JOB_ID = /^cap-\d+-\d+$/;
|
|
19
|
+
|
|
5
20
|
export function createCaptureBuild({ send }) {
|
|
21
|
+
const build = ++builds;
|
|
6
22
|
let nextId = 1;
|
|
7
23
|
let disposed = false;
|
|
8
24
|
const pending = new Map(); // jobId -> resolve
|
|
@@ -12,12 +28,12 @@ export function createCaptureBuild({ send }) {
|
|
|
12
28
|
// worker (a silent no-op) and its promise would hang forever. Resolve null instead
|
|
13
29
|
// — captureView's documented "disposed runtime resolves null" contract.
|
|
14
30
|
if (disposed) return Promise.resolve(null);
|
|
15
|
-
// String-namespaced ("cap-
|
|
31
|
+
// String-namespaced ("cap-") so a capture jobId stays outside
|
|
16
32
|
// export-controller's "export-"/"warm-" ids — both share the same worker
|
|
17
33
|
// message space, and exportCtl.handleMessage claims every id in its own
|
|
18
34
|
// namespace before checking type, so a colliding id could otherwise settle
|
|
19
35
|
// the wrong promise.
|
|
20
|
-
const jobId = `cap-${nextId++}`;
|
|
36
|
+
const jobId = `cap-${build}-${nextId++}`;
|
|
21
37
|
return new Promise((resolve) => {
|
|
22
38
|
pending.set(jobId, resolve);
|
|
23
39
|
// cache:true so the worker reuses its per-sub-part geometry memo (the
|
|
@@ -26,13 +42,14 @@ export function createCaptureBuild({ send }) {
|
|
|
26
42
|
});
|
|
27
43
|
}
|
|
28
44
|
|
|
29
|
-
// Returns true iff this message
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// build (the worker's shared
|
|
34
|
-
// needs-import-mesh, jobId intact)
|
|
35
|
-
//
|
|
45
|
+
// Returns true iff this message is a reply to a capture job (so the caller —
|
|
46
|
+
// mount.js's onWorkerMessage — can skip it entirely): a pending job is
|
|
47
|
+
// settled, any other capture id is dropped. Keyed on membership in `pending`
|
|
48
|
+
// first: the namespaced jobId guarantees another channel's message never
|
|
49
|
+
// matches, so a hit here is always ours. A failed build (the worker's shared
|
|
50
|
+
// catch posts a generic error/needs-occt/needs-import-mesh, jobId intact)
|
|
51
|
+
// resolves to null rather than leaving the caller hanging forever —
|
|
52
|
+
// captureView treats null as "capture failed, skip".
|
|
36
53
|
// needs-import-mesh MUST be claimed here rather than falling through to
|
|
37
54
|
// mount's live-loop crossover case: this capture job never went through the
|
|
38
55
|
// regen loop, so treating its reply as a live crossover would call
|
|
@@ -43,7 +60,12 @@ export function createCaptureBuild({ send }) {
|
|
|
43
60
|
// gets primed by the next live build instead.
|
|
44
61
|
function handleMessage(data) {
|
|
45
62
|
const jobId = data?.jobId;
|
|
46
|
-
|
|
63
|
+
// A capture id with nothing pending here: a job this build already
|
|
64
|
+
// settled, or one a disposed build sent to a worker the host kept. Either
|
|
65
|
+
// way its promise is already settled, so claim the reply and drop it.
|
|
66
|
+
// Passing it on would let mount's switch read an "error" as a failed build
|
|
67
|
+
// and a needs-import-mesh as a live crossover.
|
|
68
|
+
if (jobId == null || !pending.has(jobId)) return typeof jobId === "string" && CAPTURE_JOB_ID.test(jobId);
|
|
47
69
|
if (data.type === "capture-meshes") {
|
|
48
70
|
pending.get(jobId)(data.meshes);
|
|
49
71
|
} else if (data.type === "error" || data.type === "needs-occt" || data.type === "needs-import-mesh") {
|
package/src/framework/mount.js
CHANGED
|
@@ -526,7 +526,7 @@ export function mount(part, { createWorker, elements = {}, onBuild, onPick, pick
|
|
|
526
526
|
// Lets the "error" case below tell a correlated tessellation failure apart
|
|
527
527
|
// from an unrelated build error sharing the same message type, so it can
|
|
528
528
|
// reset the latch instead of stranding it at "requested" forever.
|
|
529
|
-
// String-namespaced ("tess-N"), mirroring capture-build.js's "cap-
|
|
529
|
+
// String-namespaced ("tess-N"), mirroring capture-build.js's "cap-" ids —
|
|
530
530
|
// this request shares the OCCT worker's message space with
|
|
531
531
|
// export-controller's "export-"/"warm-" jobIds, and exportCtl.handleMessage
|
|
532
532
|
// (called before mount's own switch, below) claims every id in its own
|
|
@@ -1015,6 +1015,8 @@ export function mount(part, { createWorker, elements = {}, onBuild, onPick, pick
|
|
|
1015
1015
|
if (exportCtl.handleMessage(data, onDownload)) return;
|
|
1016
1016
|
// captureView's off-loop build channel: consume its replies before the
|
|
1017
1017
|
// live `meshes` case — capture-meshes must never touch live cache/display.
|
|
1018
|
+
// Like exportCtl, it also claims a disposed mount's late capture reply on
|
|
1019
|
+
// a worker the host kept, so a stale "error" never reads as a failed build.
|
|
1018
1020
|
if (captureBuild.handleMessage(data)) return;
|
|
1019
1021
|
switch (data.type) {
|
|
1020
1022
|
case "ready":
|