@ifc-lite/geometry 3.3.1 → 3.5.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/dist/geometry-parallel.d.ts.map +1 -1
- package/dist/geometry-parallel.js +4 -84
- package/dist/geometry-parallel.js.map +1 -1
- package/dist/ifc-lite-bridge.d.ts +17 -1
- package/dist/ifc-lite-bridge.d.ts.map +1 -1
- package/dist/ifc-lite-bridge.js +72 -26
- package/dist/ifc-lite-bridge.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/wasm-init-retry.d.ts +2 -1
- package/dist/wasm-init-retry.d.ts.map +1 -1
- package/dist/wasm-init-retry.js +48 -2
- package/dist/wasm-init-retry.js.map +1 -1
- package/dist/wasm-runtime-trap.d.ts +90 -0
- package/dist/wasm-runtime-trap.d.ts.map +1 -0
- package/dist/wasm-runtime-trap.js +134 -0
- package/dist/wasm-runtime-trap.js.map +1 -0
- package/dist/wasm-shared-module.d.ts +45 -0
- package/dist/wasm-shared-module.d.ts.map +1 -0
- package/dist/wasm-shared-module.js +155 -0
- package/dist/wasm-shared-module.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Classify WebAssembly runtime traps and, for the one case that genuinely
|
|
6
|
+
* cannot be recovered in-document, broadcast it so the host can offer a reload
|
|
7
|
+
* (#1898).
|
|
8
|
+
*
|
|
9
|
+
* A trap is what reaches JS when the engine executes `unreachable`: with
|
|
10
|
+
* `panic = "abort"` (rust/Cargo.toml) a Rust panic, a failed `assert!` and an
|
|
11
|
+
* allocator abort are ALL indistinguishable from here — see the same caveat in
|
|
12
|
+
* `huge-file-error.ts`. What a trap can damage is bounded: the wasm instance's
|
|
13
|
+
* linear memory keeps whatever the aborted call leaked, and any Rust state the
|
|
14
|
+
* call was mutating is left mid-flight. It does NOT invalidate the module, and
|
|
15
|
+
* it says nothing about other engine handles in the realm, let alone about
|
|
16
|
+
* other realms (each Worker instantiates its own `@ifc-lite/wasm`).
|
|
17
|
+
*
|
|
18
|
+
* So the recovery contract is:
|
|
19
|
+
*
|
|
20
|
+
* - a trap taken inside an *operation* is the caller's failure. The bridge
|
|
21
|
+
* that took it drops (and frees) its `IfcAPI` handle — that is where the
|
|
22
|
+
* wedgeable per-load caches live — and the original trap propagates
|
|
23
|
+
* unchanged. A later `init()`, on that bridge or any other, builds a fresh
|
|
24
|
+
* handle and works.
|
|
25
|
+
* - a trap taken while *initializing* means this realm could not produce a
|
|
26
|
+
* working engine at all, and neither half of that is retryable in practice:
|
|
27
|
+
* if the trap came from `new IfcAPI()` the module singleton is already built
|
|
28
|
+
* and wasm-bindgen's `init()` will keep returning it, and if it came from
|
|
29
|
+
* instantiation itself it is a deterministic module-level failure that
|
|
30
|
+
* recurs. That is the only case that earns
|
|
31
|
+
* {@link WASM_RUNTIME_UNRECOVERABLE_EVENT} — the user is told to reload,
|
|
32
|
+
* which is the one thing that really does get a new instance.
|
|
33
|
+
*
|
|
34
|
+
* Even then nothing is latched: a later `init()` still tries, so the
|
|
35
|
+
* "unrecoverable" verdict is a diagnosis for the user, never a lock that could
|
|
36
|
+
* disable an unrelated consumer. Recovery is lazy — it happens on the next
|
|
37
|
+
* `init()` a consumer asks for — so there is no retry loop to bound either.
|
|
38
|
+
*
|
|
39
|
+
* Detection lives here as a pure, unit-testable predicate; the host app owns
|
|
40
|
+
* the reload policy and subscribes to the event. This library never reloads
|
|
41
|
+
* the page on its own — same division of labour as `wasm-asset-error.ts`.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Stable marker in the message of the error thrown when the engine cannot be
|
|
45
|
+
* initialized after a trap. Kept in the message (not only in a field) so it
|
|
46
|
+
* survives the string-only round trip through error tracking, and so the host
|
|
47
|
+
* app can classify it without importing this package.
|
|
48
|
+
*/
|
|
49
|
+
export const WASM_RUNTIME_UNRECOVERABLE_CODE = 'WASM_RUNTIME_UNRECOVERABLE';
|
|
50
|
+
/**
|
|
51
|
+
* Dispatched on `globalThis` when the WebAssembly geometry engine trapped
|
|
52
|
+
* while initializing and therefore cannot be rebuilt in this document.
|
|
53
|
+
* `detail.message` carries the originating trap text. Hosts that opt in tell
|
|
54
|
+
* the user to reload; unlike the version-skew event this must NOT trigger an
|
|
55
|
+
* automatic reload — a crash mid-session would discard the user's work.
|
|
56
|
+
*/
|
|
57
|
+
export const WASM_RUNTIME_UNRECOVERABLE_EVENT = 'ifclite:wasm-runtime-unrecoverable';
|
|
58
|
+
function messageOf(err) {
|
|
59
|
+
if (err == null)
|
|
60
|
+
return '';
|
|
61
|
+
if (typeof err === 'string')
|
|
62
|
+
return err;
|
|
63
|
+
if (typeof err === 'object' && 'message' in err) {
|
|
64
|
+
const m = err.message;
|
|
65
|
+
if (typeof m === 'string')
|
|
66
|
+
return m;
|
|
67
|
+
}
|
|
68
|
+
return String(err);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* True when `err` is a WebAssembly runtime trap.
|
|
72
|
+
*
|
|
73
|
+
* `instanceof WebAssembly.RuntimeError` alone is not enough: an error thrown
|
|
74
|
+
* inside a Worker/iframe realm and re-raised here fails the identity check
|
|
75
|
+
* because each realm has its own `WebAssembly.RuntimeError` constructor. The
|
|
76
|
+
* `.name` fallback is the cross-realm-safe half — `RuntimeError` is the class
|
|
77
|
+
* name the spec fixes for wasm traps, and structured-cloned errors keep it.
|
|
78
|
+
*/
|
|
79
|
+
export function isWasmRuntimeTrap(err) {
|
|
80
|
+
if (typeof WebAssembly !== 'undefined' && err instanceof WebAssembly.RuntimeError)
|
|
81
|
+
return true;
|
|
82
|
+
if (typeof err !== 'object' || err === null)
|
|
83
|
+
return false;
|
|
84
|
+
return err.name === 'RuntimeError';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* True for the typed error {@link wasmRuntimeUnrecoverableError} produces —
|
|
88
|
+
* matched on the message marker so it also works on an error that only
|
|
89
|
+
* survived as text (an analytics payload, a `postMessage` hop).
|
|
90
|
+
*/
|
|
91
|
+
export function isWasmRuntimeUnrecoverableError(err) {
|
|
92
|
+
return messageOf(err).includes(WASM_RUNTIME_UNRECOVERABLE_CODE);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the error thrown when the engine trapped while initializing.
|
|
96
|
+
*
|
|
97
|
+
* Constructed FRESH at every throw site on purpose. The bug this replaces
|
|
98
|
+
* stored one Error object in a module global and rethrew it forever, so the
|
|
99
|
+
* stack shipped to error tracking pointed at whichever call first trapped —
|
|
100
|
+
* the production report for #1898 showed a `exportGlb` stack captured under a
|
|
101
|
+
* model-load context, which made it undiagnosable. The underlying trap is kept
|
|
102
|
+
* verbatim in the message tail (for triage from text alone) and as `.cause`
|
|
103
|
+
* (for programmatic inspection), mirroring `largeFilePrepassError`.
|
|
104
|
+
*/
|
|
105
|
+
export function wasmRuntimeUnrecoverableError(cause, operation) {
|
|
106
|
+
const raw = messageOf(cause) || String(cause);
|
|
107
|
+
return new Error(`${WASM_RUNTIME_UNRECOVERABLE_CODE}: the IFC-Lite WebAssembly geometry engine trapped during ` +
|
|
108
|
+
`${operation}, so this document has no working engine instance. Reload the page to get a ` +
|
|
109
|
+
`fresh one. (underlying wasm trap: ${raw})`, { cause });
|
|
110
|
+
}
|
|
111
|
+
function domDispatcher() {
|
|
112
|
+
const g = globalThis;
|
|
113
|
+
return typeof g.dispatchEvent === 'function' && typeof g.CustomEvent === 'function'
|
|
114
|
+
? g
|
|
115
|
+
: null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Broadcast {@link WASM_RUNTIME_UNRECOVERABLE_EVENT} on `globalThis` so an
|
|
119
|
+
* opted-in host can tell the user the engine is gone and offer a reload.
|
|
120
|
+
* No-op in non-DOM hosts such as Node (CLI/MCP), where the thrown error is the
|
|
121
|
+
* whole story.
|
|
122
|
+
*/
|
|
123
|
+
export function notifyWasmRuntimeUnrecoverable(err) {
|
|
124
|
+
const target = domDispatcher();
|
|
125
|
+
if (!target)
|
|
126
|
+
return;
|
|
127
|
+
try {
|
|
128
|
+
target.dispatchEvent(new CustomEvent(WASM_RUNTIME_UNRECOVERABLE_EVENT, { detail: { message: messageOf(err) } }));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
/* CustomEvent unavailable — best effort, nothing more to do */
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=wasm-runtime-trap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm-runtime-trap.js","sourceRoot":"","sources":["../src/wasm-runtime-trap.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,4BAA4B,CAAC;AAE5E;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,oCAAoC,CAAC;AAErF,SAAS,SAAS,CAAC,GAAY;IAC7B,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACxC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;QAChD,MAAM,CAAC,GAAI,GAA6B,CAAC,OAAO,CAAC;QACjD,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,IAAI,OAAO,WAAW,KAAK,WAAW,IAAI,GAAG,YAAY,WAAW,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAC/F,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,OAAQ,GAA0B,CAAC,IAAI,KAAK,cAAc,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,+BAA+B,CAAC,GAAY;IAC1D,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,+BAA+B,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,6BAA6B,CAAC,KAAc,EAAE,SAAiB;IAC7E,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,IAAI,KAAK,CACd,GAAG,+BAA+B,4DAA4D;QAC5F,GAAG,SAAS,8EAA8E;QAC1F,qCAAqC,GAAG,GAAG,EAC7C,EAAE,KAAK,EAAE,CACV,CAAC;AACJ,CAAC;AAMD,SAAS,aAAa;IACpB,MAAM,CAAC,GAAG,UAA2E,CAAC;IACtF,OAAO,OAAO,CAAC,CAAC,aAAa,KAAK,UAAU,IAAI,OAAO,CAAC,CAAC,WAAW,KAAK,UAAU;QACjF,CAAC,CAAE,CAAmB;QACtB,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAAC,GAAY;IACzD,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,IAAI,CAAC;QACH,MAAM,CAAC,aAAa,CAClB,IAAI,WAAW,CAAC,gCAAgC,EAAE,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAC3F,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile (or join the in-flight compile of) the engine binary for `explicitUrl`
|
|
3
|
+
* — or the default resolution when omitted.
|
|
4
|
+
*
|
|
5
|
+
* Resolves to `null` — the caller then leaves the consumer on its own `init()` —
|
|
6
|
+
* when the URL can't be resolved or compilation fails, so non-Vite consumers and
|
|
7
|
+
* offline/edge failures degrade to the previous behaviour rather than breaking.
|
|
8
|
+
*/
|
|
9
|
+
export declare function compileSharedWasmModule(explicitUrl?: string): Promise<WebAssembly.Module | null>;
|
|
10
|
+
/**
|
|
11
|
+
* The compiled module for `explicitUrl` **only if a compile was already started**
|
|
12
|
+
* for it — otherwise `null`, without starting one.
|
|
13
|
+
*
|
|
14
|
+
* This is the "join, don't initiate" accessor for consumers that have their own
|
|
15
|
+
* working init path (`IfcLiteBridge.init()`): when a prewarm or a parallel load
|
|
16
|
+
* has already fetched the binary, reuse it; when nothing has, do exactly what
|
|
17
|
+
* this consumer did before rather than speculatively pulling 1.3 MB on a path
|
|
18
|
+
* that may not need it.
|
|
19
|
+
*
|
|
20
|
+
* Awaits an in-flight compile rather than racing it with a second fetch — the
|
|
21
|
+
* bytes are the same and already on the wire.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getStartedSharedWasmModule(explicitUrl?: string): Promise<WebAssembly.Module | null> | null;
|
|
24
|
+
/**
|
|
25
|
+
* Start the shared fetch+compile BEFORE a file is opened, so the binary is
|
|
26
|
+
* already downloaded and compiled by the time a load wants it.
|
|
27
|
+
*
|
|
28
|
+
* Without this the binary (~1.3 MB over the wire) is fetched lazily, on the
|
|
29
|
+
* click that opens a model: nothing overlaps the user's think time, and on a
|
|
30
|
+
* slow link the whole download sits in front of first geometry (measured 2.5 s
|
|
31
|
+
* on a ~4 Mbit connection, for a 225 KB model).
|
|
32
|
+
*
|
|
33
|
+
* Fire-and-forget by design: a prewarm failure must never surface to the user or
|
|
34
|
+
* poison the load path. `compileSharedWasmModule` already evicts a failed URL so
|
|
35
|
+
* the real load retries, and every consumer falls back to its own `init()` when
|
|
36
|
+
* no shared module is available. Callers decide *when* to call this (idle,
|
|
37
|
+
* intent) and whether the connection can afford it.
|
|
38
|
+
*
|
|
39
|
+
* `wasmUrl` MUST match the URL the subsequent load resolves — the memo is keyed
|
|
40
|
+
* on it, so prewarming one binary and loading another downloads both.
|
|
41
|
+
* Vite/webpack consumers (the viewer included) pass none and share the default
|
|
42
|
+
* resolution, which is the intended usage.
|
|
43
|
+
*/
|
|
44
|
+
export declare function prewarmSharedWasmModule(wasmUrl?: string): void;
|
|
45
|
+
//# sourceMappingURL=wasm-shared-module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm-shared-module.d.ts","sourceRoot":"","sources":["../src/wasm-shared-module.ts"],"names":[],"mappings":"AAoDA;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,CA0CpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAK3C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAM9D"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* ONE compiled `WebAssembly.Module` per binary per session, shared by every
|
|
6
|
+
* consumer of the geometry engine in this realm.
|
|
7
|
+
*
|
|
8
|
+
* Two independent code paths need the ~3.9 MB engine binary and used to fetch
|
|
9
|
+
* and compile it separately:
|
|
10
|
+
* - the N-worker pool (`geometry-parallel.ts`), which structured-clones a
|
|
11
|
+
* compiled module to each worker (#1818 — before that, N parallel compiles
|
|
12
|
+
* of a multi-MB module contended on the CPU and produced a multi-second
|
|
13
|
+
* "WASM ready" stagger before any geometry was meshed);
|
|
14
|
+
* - the main-thread `IfcLiteBridge.init()` path, used by the adaptive sync
|
|
15
|
+
* route for small files and by the export/bridge APIs, which calls
|
|
16
|
+
* wasm-bindgen `init()` and lets IT fetch + compile.
|
|
17
|
+
*
|
|
18
|
+
* Keeping the memo here — rather than private to the worker pool — means the
|
|
19
|
+
* second path can reuse whatever the first already compiled, so the binary is
|
|
20
|
+
* fetched at most once per URL per realm no matter which path runs first. That
|
|
21
|
+
* matters most when the two overlap: a prewarm started at page load and a small
|
|
22
|
+
* file opened seconds later would otherwise be two concurrent downloads of the
|
|
23
|
+
* same 1.3 MB (browsers are not required to coalesce in-flight requests for the
|
|
24
|
+
* same URL), on exactly the slow connections the prewarm exists to help.
|
|
25
|
+
*/
|
|
26
|
+
// Keyed by the RESOLVED wasm URL, not a single global slot: federation / version
|
|
27
|
+
// skew can load a DIFFERENT binary in the same session, and returning the first
|
|
28
|
+
// compiled module for a later, incompatible URL would initialize the consumer's
|
|
29
|
+
// wasm-bindgen glue against the wrong module. One promise per distinct binary.
|
|
30
|
+
const sharedWasmModulePromises = new Map();
|
|
31
|
+
function resolveWasmUrl(explicitUrl) {
|
|
32
|
+
if (explicitUrl)
|
|
33
|
+
return explicitUrl;
|
|
34
|
+
try {
|
|
35
|
+
// Source-aliased (Vite) build: the sibling `@ifc-lite/wasm` package's
|
|
36
|
+
// binary, resolved relative to THIS module. Vite statically rewrites this
|
|
37
|
+
// `new URL(..., import.meta.url)` to the emitted, content-hashed asset URL
|
|
38
|
+
// — the same asset the worker's wasm-bindgen glue resolves. In a plain
|
|
39
|
+
// tsc/npm build it resolves against dist/ and may 404, which the caller
|
|
40
|
+
// treats as "no shared module" and falls back to per-consumer init.
|
|
41
|
+
//
|
|
42
|
+
// NOTE: this specifier is relative to THIS FILE. It must stay in
|
|
43
|
+
// packages/geometry/src/ alongside its original home in
|
|
44
|
+
// geometry-parallel.ts, or Vite rewrites it to a different (404) asset.
|
|
45
|
+
return new URL('../../wasm/pkg/ifc-lite_bg.wasm', import.meta.url);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Compile (or join the in-flight compile of) the engine binary for `explicitUrl`
|
|
53
|
+
* — or the default resolution when omitted.
|
|
54
|
+
*
|
|
55
|
+
* Resolves to `null` — the caller then leaves the consumer on its own `init()` —
|
|
56
|
+
* when the URL can't be resolved or compilation fails, so non-Vite consumers and
|
|
57
|
+
* offline/edge failures degrade to the previous behaviour rather than breaking.
|
|
58
|
+
*/
|
|
59
|
+
export async function compileSharedWasmModule(explicitUrl) {
|
|
60
|
+
if (typeof WebAssembly === 'undefined')
|
|
61
|
+
return null;
|
|
62
|
+
const url = resolveWasmUrl(explicitUrl);
|
|
63
|
+
if (!url)
|
|
64
|
+
return null;
|
|
65
|
+
const cacheKey = url instanceof URL ? url.href : url;
|
|
66
|
+
const cached = sharedWasmModulePromises.get(cacheKey);
|
|
67
|
+
if (cached)
|
|
68
|
+
return cached;
|
|
69
|
+
const p = (async () => {
|
|
70
|
+
try {
|
|
71
|
+
if (typeof WebAssembly.compileStreaming === 'function') {
|
|
72
|
+
try {
|
|
73
|
+
// Compile WHILE the binary downloads (one streaming fetch + compile
|
|
74
|
+
// for every consumer).
|
|
75
|
+
return await WebAssembly.compileStreaming(fetch(url));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Some static hosts serve `.wasm` with the wrong MIME type, which
|
|
79
|
+
// rejects compileStreaming — fall through to the buffer path.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const resp = await fetch(url);
|
|
83
|
+
if (!resp.ok)
|
|
84
|
+
return null;
|
|
85
|
+
return await WebAssembly.compile(await resp.arrayBuffer());
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.warn('[stream] shared wasm compile failed; consumers will self-init:', err);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
})();
|
|
92
|
+
sharedWasmModulePromises.set(cacheKey, p);
|
|
93
|
+
const result = await p;
|
|
94
|
+
// Don't cache a failure — evict ONLY this URL's entry so the next load retries
|
|
95
|
+
// (a transient fetch error shouldn't permanently disable the shared-module fast
|
|
96
|
+
// path for the session), while other URLs' successful modules stay cached.
|
|
97
|
+
//
|
|
98
|
+
// Evict only if THIS promise is still the installed one. A failed compile can
|
|
99
|
+
// have several awaiters; the first to resume evicts and a retry may install a
|
|
100
|
+
// fresh promise before the rest resume. Without the identity check those late
|
|
101
|
+
// awaiters would delete the *replacement*, breaking single-flight and letting
|
|
102
|
+
// a second compile of the same binary start.
|
|
103
|
+
if (result === null && sharedWasmModulePromises.get(cacheKey) === p) {
|
|
104
|
+
sharedWasmModulePromises.delete(cacheKey);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The compiled module for `explicitUrl` **only if a compile was already started**
|
|
110
|
+
* for it — otherwise `null`, without starting one.
|
|
111
|
+
*
|
|
112
|
+
* This is the "join, don't initiate" accessor for consumers that have their own
|
|
113
|
+
* working init path (`IfcLiteBridge.init()`): when a prewarm or a parallel load
|
|
114
|
+
* has already fetched the binary, reuse it; when nothing has, do exactly what
|
|
115
|
+
* this consumer did before rather than speculatively pulling 1.3 MB on a path
|
|
116
|
+
* that may not need it.
|
|
117
|
+
*
|
|
118
|
+
* Awaits an in-flight compile rather than racing it with a second fetch — the
|
|
119
|
+
* bytes are the same and already on the wire.
|
|
120
|
+
*/
|
|
121
|
+
export function getStartedSharedWasmModule(explicitUrl) {
|
|
122
|
+
const url = resolveWasmUrl(explicitUrl);
|
|
123
|
+
if (!url)
|
|
124
|
+
return null;
|
|
125
|
+
const cacheKey = url instanceof URL ? url.href : url;
|
|
126
|
+
return sharedWasmModulePromises.get(cacheKey) ?? null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Start the shared fetch+compile BEFORE a file is opened, so the binary is
|
|
130
|
+
* already downloaded and compiled by the time a load wants it.
|
|
131
|
+
*
|
|
132
|
+
* Without this the binary (~1.3 MB over the wire) is fetched lazily, on the
|
|
133
|
+
* click that opens a model: nothing overlaps the user's think time, and on a
|
|
134
|
+
* slow link the whole download sits in front of first geometry (measured 2.5 s
|
|
135
|
+
* on a ~4 Mbit connection, for a 225 KB model).
|
|
136
|
+
*
|
|
137
|
+
* Fire-and-forget by design: a prewarm failure must never surface to the user or
|
|
138
|
+
* poison the load path. `compileSharedWasmModule` already evicts a failed URL so
|
|
139
|
+
* the real load retries, and every consumer falls back to its own `init()` when
|
|
140
|
+
* no shared module is available. Callers decide *when* to call this (idle,
|
|
141
|
+
* intent) and whether the connection can afford it.
|
|
142
|
+
*
|
|
143
|
+
* `wasmUrl` MUST match the URL the subsequent load resolves — the memo is keyed
|
|
144
|
+
* on it, so prewarming one binary and loading another downloads both.
|
|
145
|
+
* Vite/webpack consumers (the viewer included) pass none and share the default
|
|
146
|
+
* resolution, which is the intended usage.
|
|
147
|
+
*/
|
|
148
|
+
export function prewarmSharedWasmModule(wasmUrl) {
|
|
149
|
+
void compileSharedWasmModule(wasmUrl).catch((err) => {
|
|
150
|
+
// Unreachable in practice — compileSharedWasmModule swallows its own
|
|
151
|
+
// failures and resolves null — but never let a prewarm reject unhandled.
|
|
152
|
+
console.warn('[stream] wasm prewarm failed; load path will retry:', err);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=wasm-shared-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm-shared-module.js","sourceRoot":"","sources":["../src/wasm-shared-module.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifc-lite/geometry",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Geometry processing bridge for IFC-Lite - exact-arithmetic CSG, streamed across workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@ifc-lite/data": "^
|
|
17
|
-
"@ifc-lite/wasm": "^4.
|
|
16
|
+
"@ifc-lite/data": "^3.0.0",
|
|
17
|
+
"@ifc-lite/wasm": "^4.2.0"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
20
|
"@tauri-apps/api": "^2.11.1"
|