@ifc-lite/geometry 7.6.0 → 7.8.1
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/README.md +64 -0
- package/dist/domain-export-call.d.ts +4 -0
- package/dist/domain-export-call.d.ts.map +1 -0
- package/dist/domain-export-call.js +19 -0
- package/dist/domain-export-call.js.map +1 -0
- package/dist/geometry-parallel-options.d.ts +15 -0
- package/dist/geometry-parallel-options.d.ts.map +1 -1
- package/dist/geometry-parallel.d.ts +2 -47
- package/dist/geometry-parallel.d.ts.map +1 -1
- package/dist/geometry-parallel.js +50 -146
- package/dist/geometry-parallel.js.map +1 -1
- package/dist/geometry-worker-pool.d.ts +72 -0
- package/dist/geometry-worker-pool.d.ts.map +1 -0
- package/dist/geometry-worker-pool.js +176 -0
- package/dist/geometry-worker-pool.js.map +1 -0
- package/dist/geometry.worker.d.ts.map +1 -1
- package/dist/geometry.worker.js +34 -36
- package/dist/geometry.worker.js.map +1 -1
- package/dist/huge-file-error.d.ts +1 -2
- package/dist/huge-file-error.d.ts.map +1 -1
- package/dist/huge-file-error.js +4 -5
- package/dist/huge-file-error.js.map +1 -1
- package/dist/hung-job-recovery.d.ts +26 -2
- package/dist/hung-job-recovery.d.ts.map +1 -1
- package/dist/hung-job-recovery.js +42 -7
- package/dist/hung-job-recovery.js.map +1 -1
- package/dist/ifc-lite-bridge.d.ts +2 -0
- package/dist/ifc-lite-bridge.d.ts.map +1 -1
- package/dist/ifc-lite-bridge.js +19 -35
- package/dist/ifc-lite-bridge.js.map +1 -1
- package/dist/in-call-heartbeat.d.ts +20 -0
- package/dist/in-call-heartbeat.d.ts.map +1 -0
- package/dist/in-call-heartbeat.js +39 -0
- package/dist/in-call-heartbeat.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -30
- package/dist/index.js.map +1 -1
- package/dist/main-engine-init.d.ts +5 -0
- package/dist/main-engine-init.d.ts.map +1 -0
- package/dist/main-engine-init.js +42 -0
- package/dist/main-engine-init.js.map +1 -0
- package/dist/map-normalization-capability.d.ts +7 -0
- package/dist/map-normalization-capability.d.ts.map +1 -0
- package/dist/map-normalization-capability.js +32 -0
- package/dist/map-normalization-capability.js.map +1 -0
- package/dist/native-bridge.d.ts +1 -0
- package/dist/native-bridge.d.ts.map +1 -1
- package/dist/native-bridge.js +1 -0
- package/dist/native-bridge.js.map +1 -1
- package/dist/perf-flags.d.ts +55 -0
- package/dist/perf-flags.d.ts.map +1 -0
- package/dist/perf-flags.js +66 -0
- package/dist/perf-flags.js.map +1 -0
- package/dist/platform-bridge.d.ts +2 -0
- package/dist/platform-bridge.d.ts.map +1 -1
- package/dist/platform-bridge.js.map +1 -1
- package/dist/prepass-routing.d.ts +53 -0
- package/dist/prepass-routing.d.ts.map +1 -0
- package/dist/prepass-routing.js +100 -0
- package/dist/prepass-routing.js.map +1 -0
- package/dist/remesh/remesh-client.d.ts.map +1 -1
- package/dist/remesh/remesh-client.js +5 -1
- package/dist/remesh/remesh-client.js.map +1 -1
- package/dist/remesh/remesh-core.d.ts.map +1 -1
- package/dist/remesh/remesh-core.js +29 -11
- package/dist/remesh/remesh-core.js.map +1 -1
- package/dist/remesh/remesh-protocol.d.ts +10 -4
- package/dist/remesh/remesh-protocol.d.ts.map +1 -1
- package/dist/remesh/remesh-protocol.js.map +1 -1
- package/dist/remesh/remesh.worker.js +25 -10
- package/dist/remesh/remesh.worker.js.map +1 -1
- package/dist/scan-proposals.d.ts +132 -0
- package/dist/scan-proposals.d.ts.map +1 -0
- package/dist/scan-proposals.js +12 -0
- package/dist/scan-proposals.js.map +1 -0
- package/dist/scan-segmentation.d.ts +205 -0
- package/dist/scan-segmentation.d.ts.map +1 -0
- package/dist/scan-segmentation.js +17 -0
- package/dist/scan-segmentation.js.map +1 -0
- package/dist/warm-pool.d.ts +27 -0
- package/dist/warm-pool.d.ts.map +1 -0
- package/dist/warm-pool.js +48 -0
- package/dist/warm-pool.js.map +1 -0
- package/dist/wasm-shared-module.d.ts +17 -19
- package/dist/wasm-shared-module.d.ts.map +1 -1
- package/dist/wasm-shared-module.js +90 -77
- package/dist/wasm-shared-module.js.map +1 -1
- package/dist/wasm-streaming-guard.d.ts +2 -0
- package/dist/wasm-streaming-guard.d.ts.map +1 -0
- package/dist/wasm-streaming-guard.js +22 -0
- package/dist/wasm-streaming-guard.js.map +1 -0
- package/dist/worker-count.d.ts +10 -0
- package/dist/worker-count.d.ts.map +1 -1
- package/dist/worker-count.js +19 -0
- package/dist/worker-count.js.map +1 -1
- package/dist/worker-trace.d.ts +16 -0
- package/dist/worker-trace.d.ts.map +1 -0
- package/dist/worker-trace.js +39 -0
- package/dist/worker-trace.js.map +1 -0
- package/package.json +17 -6
package/README.md
CHANGED
|
@@ -154,6 +154,70 @@ works.
|
|
|
154
154
|
|
|
155
155
|
See the [Geometry Guide](https://ifclite.dev/docs/guide/geometry/) and [API Reference](https://ifclite.dev/docs/api/typescript/#ifc-litegeometry).
|
|
156
156
|
|
|
157
|
+
`GeometryProcessor.planMapConversionNormalization(content)` returns a JSON
|
|
158
|
+
entity-patch plan for opt-in STEP coordinate compatibility export. Initialize
|
|
159
|
+
the processor first and dispose it in `finally`. Canonical Rust validates units,
|
|
160
|
+
placements and representation ownership; unsupported coordinate consumers return
|
|
161
|
+
warnings with no patches. `StepExporter.exportAsync({ normalizeMapGeometry:
|
|
162
|
+
true })` applies this plan after edits using its existing modification ledger.
|
|
163
|
+
See the [exporting guide](https://ifclite.dev/docs/guide/exporting/) for the
|
|
164
|
+
supported subset and warning handling. Third-party platform bridges may expose
|
|
165
|
+
the optional planner capability; the bundled native bridge explicitly refuses it.
|
|
166
|
+
|
|
167
|
+
## Scan plane segmentation
|
|
168
|
+
|
|
169
|
+
`@ifc-lite/geometry/scan-segmentation` types the wasm plane and cylinder
|
|
170
|
+
detector for point clouds. Pass xyz metres, either a whole buffer or a reservoir with a `count`.
|
|
171
|
+
Every option is optional; see the
|
|
172
|
+
[segmentation contract](https://ifclite.dev/docs/api/wasm/#scan-plane-segmentation)
|
|
173
|
+
for the defaults.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { segmentScan } from '@ifc-lite/geometry/scan-segmentation';
|
|
177
|
+
|
|
178
|
+
const api = new IfcAPI(); // after the wasm module is initialised
|
|
179
|
+
const positions = new Float32Array([/* x, y, z, ... in metres */]);
|
|
180
|
+
try {
|
|
181
|
+
const report = segmentScan(api, { positions }, { scannerPosition: [3, 2, 1.5] });
|
|
182
|
+
for (const plane of report.planes) {
|
|
183
|
+
console.log(plane.orientation, plane.normal, plane.d, plane.areaSquareMetres);
|
|
184
|
+
}
|
|
185
|
+
for (const cylinder of report.cylinders) {
|
|
186
|
+
// `faceted` is set for a polygonal column (radius is then the circumradius).
|
|
187
|
+
console.log(cylinder.orientation, cylinder.axisStart, cylinder.radius, cylinder.length, cylinder.faceted?.faces);
|
|
188
|
+
}
|
|
189
|
+
} finally {
|
|
190
|
+
api.free();
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Scan element proposals
|
|
195
|
+
|
|
196
|
+
`@ifc-lite/geometry/scan-proposals` turns a segmentation report into proposed
|
|
197
|
+
`IfcWall`, `IfcSlab`, `IfcColumn` and pipe elements in the IFC model frame (Z
|
|
198
|
+
up, metres): opposite wall faces pair into walls of measured thickness, floors
|
|
199
|
+
and ceilings into slabs, vertical cylinders into columns. See the
|
|
200
|
+
[proposal contract](https://ifclite.dev/docs/api/wasm/#scan-element-proposals).
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { segmentScan } from '@ifc-lite/geometry/scan-segmentation';
|
|
204
|
+
import { proposeScanElements } from '@ifc-lite/geometry/scan-proposals';
|
|
205
|
+
|
|
206
|
+
const api = new IfcAPI(); // after the wasm module is initialised
|
|
207
|
+
const positions = new Float32Array([/* x, y, z, ... Y-up metres */]);
|
|
208
|
+
try {
|
|
209
|
+
const report = segmentScan(api, { positions }, { upAxis: [0, 1, 0] });
|
|
210
|
+
// Y-up (x, y, z) -> IFC Z-up (x, -z, y), row-major.
|
|
211
|
+
const scanToModel = [1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1];
|
|
212
|
+
const { proposals } = proposeScanElements(api, report, { scanToModel, schema: 'IFC4' });
|
|
213
|
+
for (const p of proposals) {
|
|
214
|
+
console.log(p.id, p.ifcClass, p.basis, p.confidence.toFixed(2), p.geometry);
|
|
215
|
+
}
|
|
216
|
+
} finally {
|
|
217
|
+
api.free();
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
157
221
|
## License
|
|
158
222
|
|
|
159
223
|
[MPL-2.0](../../LICENSE)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IfcAPI } from '@ifc-lite/wasm';
|
|
2
|
+
/** Shared initialization/error boundary for canonical domain exports. */
|
|
3
|
+
export declare function runDomainExport<T>(api: IfcAPI | null, operation: string, content: Uint8Array, run: (api: IfcAPI) => T, onError: (error: unknown) => void): T;
|
|
4
|
+
//# sourceMappingURL=domain-export-call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-export-call.d.ts","sourceRoot":"","sources":["../src/domain-export-call.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAI7C,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACrH,CAAC,CASH"}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
import { createLogger } from '@ifc-lite/data';
|
|
5
|
+
const log = createLogger('Geometry');
|
|
6
|
+
/** Shared initialization/error boundary for canonical domain exports. */
|
|
7
|
+
export function runDomainExport(api, operation, content, run, onError) {
|
|
8
|
+
if (!api)
|
|
9
|
+
throw new Error('IFC-Lite not initialized. Call init() first.');
|
|
10
|
+
try {
|
|
11
|
+
return run(api);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
log.error(`Failed to ${operation}`, error, { operation, data: { contentLength: content.length } });
|
|
15
|
+
onError(error);
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=domain-export-call.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-export-call.js","sourceRoot":"","sources":["../src/domain-export-call.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;AAErC,yEAAyE;AACzE,MAAM,UAAU,eAAe,CAC7B,GAAkB,EAAE,SAAiB,EAAE,OAAmB,EAAE,GAAuB,EAAE,OAAiC;IAEtH,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC1E,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,KAAK,CAAC,aAAa,SAAS,EAAE,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnG,OAAO,CAAC,KAAK,CAAC,CAAC;QACf,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { TessellationQuality } from './types.js';
|
|
2
2
|
import type { BatchSizingConfig } from './batch-sizing.js';
|
|
3
3
|
import type { StallPhaseHandle } from './stall-phase.js';
|
|
4
|
+
import type { LoadTrace } from '@ifc-lite/load-trace';
|
|
5
|
+
import type { GeometryWorkerPool } from './geometry-worker-pool.js';
|
|
4
6
|
export interface ProcessParallelOptions {
|
|
5
7
|
/** Fresh per-load fingerprint cell shared only with the matching parser. */
|
|
6
8
|
sourceFingerprint?: SharedArrayBuffer;
|
|
@@ -121,5 +123,18 @@ export interface ProcessParallelOptions {
|
|
|
121
123
|
* covers, not this issue's pre-worker class.
|
|
122
124
|
*/
|
|
123
125
|
stallPhaseHandle?: StallPhaseHandle;
|
|
126
|
+
/**
|
|
127
|
+
* Issue #6956 — the caller's load trace. The pool records `geometry.pool`,
|
|
128
|
+
* `geometry.firstBatch`, the shard scan/stitch and pre-pass phases on it, and
|
|
129
|
+
* asks its workers to post their own spans back. Omitted ⇒ nothing recorded.
|
|
130
|
+
*/
|
|
131
|
+
trace?: LoadTrace;
|
|
132
|
+
/**
|
|
133
|
+
* Lease the workers a host prewarmed for this load from this pool (#7036)
|
|
134
|
+
* instead of spawning them; any it lacks are spawned. Every worker still
|
|
135
|
+
* serves only this load and is terminated by it. Omitted, every worker is
|
|
136
|
+
* spawned here, as before.
|
|
137
|
+
*/
|
|
138
|
+
workerPool?: GeometryWorkerPool;
|
|
124
139
|
}
|
|
125
140
|
//# sourceMappingURL=geometry-parallel-options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geometry-parallel-options.d.ts","sourceRoot":"","sources":["../src/geometry-parallel-options.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"geometry-parallel-options.d.ts","sourceRoot":"","sources":["../src/geometry-parallel-options.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,WAAW,sBAAsB;IACrC,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,CACd,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,WAAW,EAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,wBAAwB;IACzE,oBAAoB,CAAC,EAAE,MAAM,KAC1B,IAAI,CAAC;IACV;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACjD;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACzC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5E;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACjC"}
|
|
@@ -22,53 +22,8 @@
|
|
|
22
22
|
import type { ProcessParallelOptions } from './geometry-parallel-options.js';
|
|
23
23
|
import type { CoordinateHandler } from './coordinate-handler.js';
|
|
24
24
|
import type { StreamingGeometryEvent } from './index.js';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* `rust/processing/src/shard_classes.rs` (the source of truth — these are
|
|
28
|
-
* pinned to it by `prepass-class-spans.test.ts`).
|
|
29
|
-
*
|
|
30
|
-
* The producer packs a named code in the LOW bits and composes FLAG bits on
|
|
31
|
-
* top (`PREPASS_CLASS_FLAG_GEOMETRY_JOB` 0x80, `..._FLAG_TYPE_CANDIDATE`
|
|
32
|
-
* 0x40), so a consumer must mask before comparing — the Rust consumer does
|
|
33
|
-
* (`gpu_meshes/prepass_discovery.rs`), and so does {@link extractPrepassSpanLists}.
|
|
34
|
-
*/
|
|
35
|
-
export declare const PREPASS_CLASS_CODE_MASK = 63;
|
|
36
|
-
/** `IFCSTYLEDITEM`. */
|
|
37
|
-
export declare const PREPASS_CLASS_STYLED_ITEM = 4;
|
|
38
|
-
/** `IFCINDEXEDCOLOURMAP`. */
|
|
39
|
-
export declare const PREPASS_CLASS_INDEXED_COLOUR_MAP = 5;
|
|
40
|
-
/** `IFCMATERIALDEFINITIONREPRESENTATION`. */
|
|
41
|
-
export declare const PREPASS_CLASS_MATERIAL_DEF_REPR = 6;
|
|
42
|
-
/** `IFCRELASSOCIATESMATERIAL`. */
|
|
43
|
-
export declare const PREPASS_CLASS_REL_ASSOCIATES_MATERIAL = 7;
|
|
44
|
-
/** `IFCRELVOIDSELEMENT`. */
|
|
45
|
-
export declare const PREPASS_CLASS_REL_VOIDS = 8;
|
|
46
|
-
/** `IFCRELFILLSELEMENT`. */
|
|
47
|
-
export declare const PREPASS_CLASS_REL_FILLS = 9;
|
|
48
|
-
/** `IFCRELAGGREGATES`. */
|
|
49
|
-
export declare const PREPASS_CLASS_REL_AGGREGATES = 10;
|
|
50
|
-
/**
|
|
51
|
-
* Build one `(id, start, length)` span list per host-consumed prepass class
|
|
52
|
-
* from the stitched shard columns, in FILE ORDER. Every comparison goes
|
|
53
|
-
* through {@link PREPASS_CLASS_CODE_MASK}, so a record that carries a flag bit
|
|
54
|
-
* alongside its named code still lands in its list instead of being dropped.
|
|
55
|
-
* Returns exact-size arrays (one entry per class in `HOST_SPAN_CLASSES`,
|
|
56
|
-
* empty when the file has none).
|
|
57
|
-
*/
|
|
58
|
-
export declare function extractPrepassSpanLists(classes: Uint8Array, ids: Uint32Array, starts: Uint32Array, lengths: Uint32Array): Map<number, Uint32Array>;
|
|
59
|
-
/**
|
|
60
|
-
* Plan content-affinity routing for one chunk: assign each job (by index) to a
|
|
61
|
-
* worker bucket so that every job sharing an affinity key lands on the SAME
|
|
62
|
-
* worker — across the whole stream, since `keyToWorker` is the caller's sticky
|
|
63
|
-
* map. New keys are handed out round-robin from `startWorker`, so each worker
|
|
64
|
-
* owns roughly `1/workerCount` of the distinct keys (≈ distinct geometries, the
|
|
65
|
-
* dominant meshing cost). Pure: mutates only the passed `keyToWorker` and returns
|
|
66
|
-
* the advanced round-robin cursor. (#1130 follow-up — see `affinity_key` in Rust.)
|
|
67
|
-
*/
|
|
68
|
-
export declare function planAffinityRouting(affinity: Uint32Array, totalJobs: number, workerCount: number, keyToWorker: Map<number, number>, startWorker: number): {
|
|
69
|
-
buckets: number[][];
|
|
70
|
-
nextWorker: number;
|
|
71
|
-
};
|
|
25
|
+
export { GeometryWorkerPool, DEFAULT_POOL_LIMITS, FRESH_WORKER_HEAP_BYTES } from './geometry-worker-pool.js';
|
|
26
|
+
export { PREPASS_CLASS_CODE_MASK, PREPASS_CLASS_STYLED_ITEM, PREPASS_CLASS_INDEXED_COLOUR_MAP, PREPASS_CLASS_MATERIAL_DEF_REPR, PREPASS_CLASS_REL_ASSOCIATES_MATERIAL, PREPASS_CLASS_REL_VOIDS, PREPASS_CLASS_REL_FILLS, PREPASS_CLASS_REL_AGGREGATES, extractPrepassSpanLists, planAffinityRouting, } from './prepass-routing.js';
|
|
72
27
|
export type { ProcessParallelOptions } from './geometry-parallel-options.js';
|
|
73
28
|
export declare function processParallel(buffer: Uint8Array, coordinator: CoordinateHandler, sharedRtcOffset?: {
|
|
74
29
|
x: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geometry-parallel.d.ts","sourceRoot":"","sources":["../src/geometry-parallel.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"geometry-parallel.d.ts","sourceRoot":"","sources":["../src/geometry-parallel.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAE7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAazD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAgB7G,OAAO,EACL,uBAAuB,EAAE,yBAAyB,EAAE,gCAAgC,EAAE,+BAA+B,EACrH,qCAAqC,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,4BAA4B,EACrH,uBAAuB,EAAE,mBAAmB,GAC7C,MAAM,sBAAsB,CAAC;AAuD9B,YAAY,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAM7E,wBAAuB,eAAe,CACpC,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,iBAAiB,EAC9B,eAAe,CAAC,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;AACrD,gFAAgF;AAChF,WAAW,CAAC,EAAE,iBAAiB,EAC/B,OAAO,CAAC,EAAE,sBAAsB,GAC/B,cAAc,CAAC,sBAAsB,CAAC,CAu4CxC"}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
import { postGeometryWorkerInit } from './geometry-worker-init.js';
|
|
5
5
|
import { mergeGeometryDiagnostics } from './diagnostics.js';
|
|
6
|
-
import {
|
|
6
|
+
import { planLoadWorkerCount } from './worker-count.js';
|
|
7
|
+
import { readBatchSizingOverride, readShardScanFlag, readVisibilityFilterOverride } from './perf-flags.js';
|
|
7
8
|
import { notifyIfWasmAssetUnavailable, notifyIfWorkerScriptUnavailable } from './wasm-asset-error.js';
|
|
8
9
|
import { restashWasmPanicLocation } from './wasm-panic-forward.js';
|
|
9
10
|
import { mergeShardStyleSlices } from './shard-style-merge.js';
|
|
@@ -12,138 +13,14 @@ import { mergeShardStyleSlices } from './shard-style-merge.js';
|
|
|
12
13
|
// (and vice versa) instead of fetching the same binary a second time.
|
|
13
14
|
import { compileSharedWasmModule } from './wasm-shared-module.js';
|
|
14
15
|
import { stitchShards } from './shard-stitch.js';
|
|
16
|
+
import { spawnGeometryWorker } from './geometry-worker-pool.js';
|
|
17
|
+
export { GeometryWorkerPool, DEFAULT_POOL_LIMITS, FRESH_WORKER_HEAP_BYTES } from './geometry-worker-pool.js';
|
|
18
|
+
import { NOOP_LOAD_TRACE, accountWorkerMessages, countCopy, enableWorkerTrace, isTraceSpansMessage } from '@ifc-lite/load-trace';
|
|
15
19
|
import { resolveRtcFrame } from './rtc-frame.js';
|
|
16
20
|
import { emptyStylesPrepassEvent, frozenStallPhase, GateTracker, PhaseBoundTimers, preWorkerPhaseFailureDiagnostics, } from './stall-phase.js';
|
|
17
21
|
import { SkippedHungElementsCollector, startHungJobMonitor, WorkerJobLedger, } from './hung-job-recovery.js';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* `rust/processing/src/shard_classes.rs` (the source of truth — these are
|
|
21
|
-
* pinned to it by `prepass-class-spans.test.ts`).
|
|
22
|
-
*
|
|
23
|
-
* The producer packs a named code in the LOW bits and composes FLAG bits on
|
|
24
|
-
* top (`PREPASS_CLASS_FLAG_GEOMETRY_JOB` 0x80, `..._FLAG_TYPE_CANDIDATE`
|
|
25
|
-
* 0x40), so a consumer must mask before comparing — the Rust consumer does
|
|
26
|
-
* (`gpu_meshes/prepass_discovery.rs`), and so does {@link extractPrepassSpanLists}.
|
|
27
|
-
*/
|
|
28
|
-
export const PREPASS_CLASS_CODE_MASK = 0x3f;
|
|
29
|
-
/** `IFCSTYLEDITEM`. */
|
|
30
|
-
export const PREPASS_CLASS_STYLED_ITEM = 4;
|
|
31
|
-
/** `IFCINDEXEDCOLOURMAP`. */
|
|
32
|
-
export const PREPASS_CLASS_INDEXED_COLOUR_MAP = 5;
|
|
33
|
-
/** `IFCMATERIALDEFINITIONREPRESENTATION`. */
|
|
34
|
-
export const PREPASS_CLASS_MATERIAL_DEF_REPR = 6;
|
|
35
|
-
/** `IFCRELASSOCIATESMATERIAL`. */
|
|
36
|
-
export const PREPASS_CLASS_REL_ASSOCIATES_MATERIAL = 7;
|
|
37
|
-
/** `IFCRELVOIDSELEMENT`. */
|
|
38
|
-
export const PREPASS_CLASS_REL_VOIDS = 8;
|
|
39
|
-
/** `IFCRELFILLSELEMENT`. */
|
|
40
|
-
export const PREPASS_CLASS_REL_FILLS = 9;
|
|
41
|
-
/** `IFCRELAGGREGATES`. */
|
|
42
|
-
export const PREPASS_CLASS_REL_AGGREGATES = 10;
|
|
43
|
-
/** The classes the host builds span lists for (every other code is ignored). */
|
|
44
|
-
const HOST_SPAN_CLASSES = [
|
|
45
|
-
PREPASS_CLASS_STYLED_ITEM,
|
|
46
|
-
PREPASS_CLASS_INDEXED_COLOUR_MAP,
|
|
47
|
-
PREPASS_CLASS_MATERIAL_DEF_REPR,
|
|
48
|
-
PREPASS_CLASS_REL_ASSOCIATES_MATERIAL,
|
|
49
|
-
PREPASS_CLASS_REL_VOIDS,
|
|
50
|
-
PREPASS_CLASS_REL_FILLS,
|
|
51
|
-
PREPASS_CLASS_REL_AGGREGATES,
|
|
52
|
-
];
|
|
53
|
-
/**
|
|
54
|
-
* Build one `(id, start, length)` span list per host-consumed prepass class
|
|
55
|
-
* from the stitched shard columns, in FILE ORDER. Every comparison goes
|
|
56
|
-
* through {@link PREPASS_CLASS_CODE_MASK}, so a record that carries a flag bit
|
|
57
|
-
* alongside its named code still lands in its list instead of being dropped.
|
|
58
|
-
* Returns exact-size arrays (one entry per class in `HOST_SPAN_CLASSES`,
|
|
59
|
-
* empty when the file has none).
|
|
60
|
-
*/
|
|
61
|
-
export function extractPrepassSpanLists(classes, ids, starts, lengths) {
|
|
62
|
-
// Sized by the code mask rather than by the highest class the host consumes:
|
|
63
|
-
// a masked code is always < 64, so a class added on the Rust side cannot
|
|
64
|
-
// write out of bounds here (typed arrays discard such writes silently).
|
|
65
|
-
const counts = new Uint32Array(PREPASS_CLASS_CODE_MASK + 1);
|
|
66
|
-
for (let i = 0; i < classes.length; i++)
|
|
67
|
-
counts[classes[i] & PREPASS_CLASS_CODE_MASK]++;
|
|
68
|
-
const slots = new Map();
|
|
69
|
-
for (const k of HOST_SPAN_CLASSES)
|
|
70
|
-
slots.set(k, { arr: new Uint32Array(counts[k] * 3), w: 0 });
|
|
71
|
-
for (let i = 0; i < classes.length; i++) {
|
|
72
|
-
const slot = slots.get(classes[i] & PREPASS_CLASS_CODE_MASK);
|
|
73
|
-
if (!slot)
|
|
74
|
-
continue;
|
|
75
|
-
slot.arr[slot.w] = ids[i];
|
|
76
|
-
slot.arr[slot.w + 1] = starts[i];
|
|
77
|
-
slot.arr[slot.w + 2] = lengths[i];
|
|
78
|
-
slot.w += 3;
|
|
79
|
-
}
|
|
80
|
-
const spans = new Map();
|
|
81
|
-
for (const [k, slot] of slots)
|
|
82
|
-
spans.set(k, slot.arr);
|
|
83
|
-
return spans;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Plan content-affinity routing for one chunk: assign each job (by index) to a
|
|
87
|
-
* worker bucket so that every job sharing an affinity key lands on the SAME
|
|
88
|
-
* worker — across the whole stream, since `keyToWorker` is the caller's sticky
|
|
89
|
-
* map. New keys are handed out round-robin from `startWorker`, so each worker
|
|
90
|
-
* owns roughly `1/workerCount` of the distinct keys (≈ distinct geometries, the
|
|
91
|
-
* dominant meshing cost). Pure: mutates only the passed `keyToWorker` and returns
|
|
92
|
-
* the advanced round-robin cursor. (#1130 follow-up — see `affinity_key` in Rust.)
|
|
93
|
-
*/
|
|
94
|
-
export function planAffinityRouting(affinity, totalJobs, workerCount, keyToWorker, startWorker) {
|
|
95
|
-
const buckets = Array.from({ length: workerCount }, () => []);
|
|
96
|
-
let nextWorker = startWorker % workerCount;
|
|
97
|
-
for (let j = 0; j < totalJobs; j++) {
|
|
98
|
-
const key = affinity[j];
|
|
99
|
-
let w = keyToWorker.get(key);
|
|
100
|
-
if (w === undefined) {
|
|
101
|
-
w = nextWorker;
|
|
102
|
-
nextWorker = (nextWorker + 1) % workerCount;
|
|
103
|
-
keyToWorker.set(key, w);
|
|
104
|
-
}
|
|
105
|
-
buckets[w].push(j);
|
|
106
|
-
}
|
|
107
|
-
return { buckets, nextWorker };
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Optional runtime override for the geometry worker's adaptive batch sizing
|
|
111
|
-
* (#1097), read off `globalThis` on the host thread. A zero-cost escape hatch
|
|
112
|
-
* for hardware-specific tuning / field-debugging the watchdog↔throughput
|
|
113
|
-
* trade-off without a rebuild; unset ⇒ the worker uses DEFAULT_BATCH_SIZING.
|
|
114
|
-
* Validation/merge happens in the worker via `resolveBatchSizing`.
|
|
115
|
-
*/
|
|
116
|
-
function readBatchSizingOverride() {
|
|
117
|
-
const g = globalThis;
|
|
118
|
-
const v = g.__IFC_LITE_BATCH_SIZING;
|
|
119
|
-
return v && typeof v === 'object' ? v : undefined;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Optional load-time visibility filter (#1097), read off `globalThis` on the
|
|
123
|
-
* host thread (tuning / benchmarking escape hatch). `{ disabledTypes,
|
|
124
|
-
* skipTypeGeometry }` skip the matching geometry jobs at the prepass so they're
|
|
125
|
-
* never decoded/meshed/uploaded. Unset ⇒ load everything.
|
|
126
|
-
*/
|
|
127
|
-
function readVisibilityFilterOverride() {
|
|
128
|
-
const g = globalThis;
|
|
129
|
-
const v = g.__IFC_LITE_VISIBILITY_FILTER;
|
|
130
|
-
return v && typeof v === 'object' ? v : undefined;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* SPIKE flag: shard the entity-index scan across the idle geometry workers and
|
|
134
|
-
* deliver the stitched index early, instead of waiting for the pre-pass
|
|
135
|
-
* worker's single-threaded post-scan `entity-index` emission. Read off
|
|
136
|
-
* `globalThis.__IFC_LITE_SHARD_SCAN` (benchmark A/B knob). Truthy ⇒ on.
|
|
137
|
-
*/
|
|
138
|
-
function readShardScanFlag() {
|
|
139
|
-
const g = globalThis;
|
|
140
|
-
const v = g.__IFC_LITE_SHARD_SCAN;
|
|
141
|
-
// ON by default; 0/'0'/false is the kill switch (same convention as the
|
|
142
|
-
// other #1682 load/render knobs).
|
|
143
|
-
if (v === 0 || v === '0' || v === false)
|
|
144
|
-
return false;
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
22
|
+
export { PREPASS_CLASS_CODE_MASK, PREPASS_CLASS_STYLED_ITEM, PREPASS_CLASS_INDEXED_COLOUR_MAP, PREPASS_CLASS_MATERIAL_DEF_REPR, PREPASS_CLASS_REL_ASSOCIATES_MATERIAL, PREPASS_CLASS_REL_VOIDS, PREPASS_CLASS_REL_FILLS, PREPASS_CLASS_REL_AGGREGATES, extractPrepassSpanLists, planAffinityRouting, } from './prepass-routing.js';
|
|
23
|
+
import { PREPASS_CLASS_STYLED_ITEM, PREPASS_CLASS_INDEXED_COLOUR_MAP, PREPASS_CLASS_MATERIAL_DEF_REPR, PREPASS_CLASS_REL_ASSOCIATES_MATERIAL, PREPASS_CLASS_REL_VOIDS, PREPASS_CLASS_REL_FILLS, PREPASS_CLASS_REL_AGGREGATES, extractPrepassSpanLists, planAffinityRouting, } from './prepass-routing.js';
|
|
147
24
|
/**
|
|
148
25
|
* Terminate a pool worker on a teardown path that is already unwinding.
|
|
149
26
|
*
|
|
@@ -182,10 +59,15 @@ function entityIndexSetup(columns) {
|
|
|
182
59
|
};
|
|
183
60
|
}
|
|
184
61
|
let nextSourceSessionId = 0;
|
|
62
|
+
/** A load above this size terminates the prewarmed workers it did not lease (#7036). */
|
|
63
|
+
const LARGE_LOAD_POOL_DRAIN_MB = 64;
|
|
185
64
|
export async function* processParallel(buffer, coordinator, sharedRtcOffset,
|
|
186
65
|
/** Optional pre-allocated SAB the caller already shares with another worker. */
|
|
187
66
|
existingSab, options) {
|
|
188
67
|
const sourceSessionId = `geometry-source-${++nextSourceSessionId}`;
|
|
68
|
+
// #6956: spans for this pool; worker spans hang under `geometry.pool`.
|
|
69
|
+
const trace = options?.trace ?? NOOP_LOAD_TRACE;
|
|
70
|
+
let poolSpan = -1, tracedWorkers = 0;
|
|
189
71
|
coordinator.reset();
|
|
190
72
|
yield { type: 'start', totalEstimate: buffer.length / 1000 };
|
|
191
73
|
yield { type: 'model-open', modelID: 0 };
|
|
@@ -210,12 +92,18 @@ existingSab, options) {
|
|
|
210
92
|
}
|
|
211
93
|
else {
|
|
212
94
|
sharedBuffer = new SharedArrayBuffer(buffer.byteLength);
|
|
213
|
-
new Uint8Array(sharedBuffer).set(buffer);
|
|
95
|
+
new Uint8Array(sharedBuffer).set(countCopy('source.geometrySab', buffer));
|
|
214
96
|
}
|
|
215
97
|
// N independent WASM-instance workers, each running
|
|
216
98
|
// `geometry.worker.js` (one `@ifc-lite/wasm` instance per worker).
|
|
217
|
-
|
|
218
|
-
|
|
99
|
+
// #6957: `accountWorkerMessages` counts each pool's messages per direction (identity when counters are off).
|
|
100
|
+
// #7036: lease the workers a host prewarmed when this load was requested
|
|
101
|
+
// (spawned beside the file read); spawn the rest. Either way a worker serves
|
|
102
|
+
// this load only and is terminated by it, exactly as before.
|
|
103
|
+
const pool = options?.workerPool ?? null, poolKey = options?.wasmUrls?.wasm ?? '';
|
|
104
|
+
const acquireWorker = (role, thread) => enableWorkerTrace(pool ? pool.acquire(role, poolKey).worker : accountWorkerMessages(spawnGeometryWorker(), role), trace, thread);
|
|
105
|
+
const makeGeometryWorker = () => acquireWorker('geometry', `geom-${tracedWorkers++}`);
|
|
106
|
+
const makePrepassWorker = () => acquireWorker('prepass', 'prepass');
|
|
219
107
|
// Shared aggregator state used by every worker callback below.
|
|
220
108
|
const eventQueue = [];
|
|
221
109
|
let resolveWaiting = null;
|
|
@@ -274,7 +162,7 @@ existingSab, options) {
|
|
|
274
162
|
let entityIndexDeliveredEarly = false;
|
|
275
163
|
const shardResults = [];
|
|
276
164
|
let shardResultsRemaining = 0;
|
|
277
|
-
let shardScanDispatchedAt = -1;
|
|
165
|
+
let shardScanDispatchedAt = -1, shardScanSpan = -1;
|
|
278
166
|
// #4902: settles once, normally or via the bounded-wait timeout below.
|
|
279
167
|
let shardScanSettled = false;
|
|
280
168
|
let stylesSlicesSettled = false;
|
|
@@ -316,6 +204,10 @@ existingSab, options) {
|
|
|
316
204
|
workerSpoke = true;
|
|
317
205
|
ledger.onHeard(workerIndex, performance.now());
|
|
318
206
|
const msg = e.data;
|
|
207
|
+
if (isTraceSpansMessage(msg)) {
|
|
208
|
+
trace.merge(msg.payload, poolSpan);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
319
211
|
if (msg.type === 'ready') {
|
|
320
212
|
console.log(`[stream] worker[${workerIndex}] WASM ready @ ${elapsed()}ms`);
|
|
321
213
|
return;
|
|
@@ -394,6 +286,11 @@ existingSab, options) {
|
|
|
394
286
|
}
|
|
395
287
|
if (msg.diagnostics)
|
|
396
288
|
diagnostics = mergeGeometryDiagnostics(diagnostics, msg.diagnostics);
|
|
289
|
+
// An in-call heartbeat (no seq) past the absolute call bound no longer
|
|
290
|
+
// counts as liveness, so with recovery off the consumer watchdog still
|
|
291
|
+
// bounds a call that reports progress forever (MAX_GEOMETRY_CALL_MS).
|
|
292
|
+
if (typeof msg.seq !== 'number' && ledger.callOverCap(workerIndex, performance.now()))
|
|
293
|
+
return;
|
|
397
294
|
eventQueue.push({ type: 'progress', phase: 'workers' });
|
|
398
295
|
wake();
|
|
399
296
|
return;
|
|
@@ -405,6 +302,7 @@ existingSab, options) {
|
|
|
405
302
|
if (msg.type === 'batch') {
|
|
406
303
|
if (firstBatchByWorker[workerIndex] === undefined) {
|
|
407
304
|
firstBatchByWorker[workerIndex] = elapsed();
|
|
305
|
+
trace.milestone('geometry.firstBatch');
|
|
408
306
|
console.log(`[stream] worker[${workerIndex}] first batch @ ${elapsed()}ms (${msg.meshes?.length ?? 0} meshes)`);
|
|
409
307
|
}
|
|
410
308
|
// The worker already emits `MeshData`-shaped objects (see the worker's
|
|
@@ -521,18 +419,8 @@ existingSab, options) {
|
|
|
521
419
|
// totalJobs estimate; use file-size proxy. The memory-budget cap in
|
|
522
420
|
// `computeWorkerCount` keeps an over-estimate harmless, and still bounds an
|
|
523
421
|
// explicit `workerCountOverride` so the A/B knob can't OOM the tab.
|
|
524
|
-
const cores = typeof navigator !== 'undefined' ? (navigator.hardwareConcurrency ?? 2) : 2;
|
|
525
|
-
const deviceMemoryGB = typeof navigator !== 'undefined'
|
|
526
|
-
? (navigator.deviceMemory ?? 8) : 8;
|
|
527
422
|
const fileSizeMB = buffer.byteLength / (1024 * 1024);
|
|
528
|
-
const
|
|
529
|
-
const workerCountResult = computeWorkerCount({
|
|
530
|
-
fileSizeMB,
|
|
531
|
-
cores,
|
|
532
|
-
deviceMemoryGB,
|
|
533
|
-
totalJobs: estimatedJobs,
|
|
534
|
-
workerCountOverride: options?.workerCountOverride,
|
|
535
|
-
});
|
|
423
|
+
const { cores, ...workerCountResult } = planLoadWorkerCount(fileSizeMB, options?.workerCountOverride);
|
|
536
424
|
const workerCount = workerCountResult.count;
|
|
537
425
|
// Await the ONE shared compile (started up-front) so every worker instantiates
|
|
538
426
|
// the SAME module via `initSync` instead of recompiling the binary N+1 times.
|
|
@@ -788,6 +676,8 @@ existingSab, options) {
|
|
|
788
676
|
// Step-by-step timing so we can tell exactly where time goes.
|
|
789
677
|
const t0 = performance.now();
|
|
790
678
|
const elapsed = () => Math.round(performance.now() - t0);
|
|
679
|
+
trace.setAttrs({ workerCount });
|
|
680
|
+
poolSpan = trace.begin('geometry.pool', { workerCount });
|
|
791
681
|
const overrideNote = options?.workerCountOverride != null
|
|
792
682
|
? ` (override=${options.workerCountOverride}, bound=${workerCountResult.reason})`
|
|
793
683
|
: ` (cores=${cores}, bound=${workerCountResult.reason})`;
|
|
@@ -807,6 +697,7 @@ existingSab, options) {
|
|
|
807
697
|
// record the parser will not find" but "every record after it is missing".
|
|
808
698
|
malformedRecordCount) => {
|
|
809
699
|
console.log(`[stream] entity-index (${source}) @ ${elapsed()}ms (${ids.length} entries)`);
|
|
700
|
+
trace.milestone('geometry.entityIndex');
|
|
810
701
|
if (typeof SharedArrayBuffer !== 'undefined') {
|
|
811
702
|
// Sharded-stitch columns arrive already SAB-backed (stitchShards writes
|
|
812
703
|
// its exact-size output into SABs) — share them as-is. The serial
|
|
@@ -874,8 +765,9 @@ existingSab, options) {
|
|
|
874
765
|
return;
|
|
875
766
|
shardScanSettled = true;
|
|
876
767
|
gateTracker.markShardScanDone();
|
|
768
|
+
trace.end(shardScanSpan);
|
|
877
769
|
const shards = shardResults;
|
|
878
|
-
const stitched = stitchShards(shards);
|
|
770
|
+
const stitched = trace.span('shard.stitch', () => stitchShards(shards), undefined, poolSpan);
|
|
879
771
|
if (!stitched) {
|
|
880
772
|
console.warn('[stream][shard] stitch fallback triggered (handoff not found) — serial pre-pass');
|
|
881
773
|
startPrepass(false);
|
|
@@ -1020,6 +912,7 @@ existingSab, options) {
|
|
|
1020
912
|
shardResults.length = n;
|
|
1021
913
|
shardResultsRemaining = n;
|
|
1022
914
|
shardScanDispatchedAt = elapsed();
|
|
915
|
+
shardScanSpan = trace.begin('geometry.shardScan', { shards: n }, poolSpan);
|
|
1023
916
|
gateTracker.markShardScanStarted();
|
|
1024
917
|
console.log(`[stream][shard] dispatching ${n} shard scans over ${(len / (1024 * 1024)).toFixed(1)}MB @ ${shardScanDispatchedAt}ms`);
|
|
1025
918
|
for (let i = 0; i < n; i++) {
|
|
@@ -1039,6 +932,7 @@ existingSab, options) {
|
|
|
1039
932
|
shardScanSettled = true;
|
|
1040
933
|
gateTracker.markShardScanDone();
|
|
1041
934
|
console.warn(`[stream][shard] ${shardResultsRemaining}/${n} shard scan(s) silent — falling back to the serial pre-pass (#4902)`);
|
|
935
|
+
trace.end(shardScanSpan, { fallback: true });
|
|
1042
936
|
diagnostics = mergeGeometryDiagnostics(diagnostics, preWorkerPhaseFailureDiagnostics('shard-scan-timeout'));
|
|
1043
937
|
// Replace every worker still silent on scan-shard BEFORE continuing:
|
|
1044
938
|
// `scan-shard` is a synchronous WASM call, so a missing reply means
|
|
@@ -1052,6 +946,9 @@ existingSab, options) {
|
|
|
1052
946
|
});
|
|
1053
947
|
}
|
|
1054
948
|
const prepassWorker = makePrepassWorker();
|
|
949
|
+
// #7036: idle workers this large load did not lease would only add to its peak.
|
|
950
|
+
if (pool && fileSizeMB > LARGE_LOAD_POOL_DRAIN_MB)
|
|
951
|
+
pool.drain('large-load');
|
|
1055
952
|
// Wrap the rest of the pipeline so worker teardown runs not only on
|
|
1056
953
|
// normal completion / error / zero-jobs branches, but also when the
|
|
1057
954
|
// consumer abandons the generator via `.return()` / `.throw()` while it
|
|
@@ -1111,6 +1008,10 @@ existingSab, options) {
|
|
|
1111
1008
|
prepassWorker.onmessage = (e) => {
|
|
1112
1009
|
prepassSpoke = true;
|
|
1113
1010
|
const data = e.data;
|
|
1011
|
+
if (isTraceSpansMessage(data)) {
|
|
1012
|
+
trace.merge(data.payload, poolSpan);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1114
1015
|
if (data.type === 'prepass-progress') {
|
|
1115
1016
|
eventQueue.push({ type: 'progress', phase: 'prepass' });
|
|
1116
1017
|
wake();
|
|
@@ -1164,6 +1065,7 @@ existingSab, options) {
|
|
|
1164
1065
|
const materialColorCounts = evt.materialColorCounts ?? new Uint32Array(0);
|
|
1165
1066
|
const materialColors = evt.materialColors ?? new Uint8Array(0);
|
|
1166
1067
|
console.log(`[stream] styles @ ${elapsed()}ms (${styleIds.length} styled, ${voidKeys.length} void hosts), draining ${queuedChunks.length} queued chunks`);
|
|
1068
|
+
trace.milestone('prepass.styles');
|
|
1167
1069
|
broadcastSetup((w) => {
|
|
1168
1070
|
// Slice each typed array per-worker so each can be in its own
|
|
1169
1071
|
// transfer list without conflict. The slice cost is bounded by
|
|
@@ -1290,6 +1192,7 @@ existingSab, options) {
|
|
|
1290
1192
|
else if (evt.type === 'complete') {
|
|
1291
1193
|
prepassJobsTotal = evt.totalJobs;
|
|
1292
1194
|
console.log(`[stream] prepass complete @ ${elapsed()}ms totalJobs=${prepassJobsTotal} chunks=${chunkArrivals}`);
|
|
1195
|
+
trace.milestone('prepass.complete');
|
|
1293
1196
|
// Unconditionally drive the prepass-complete handler here.
|
|
1294
1197
|
// The outer loop's `prepassJobsTotal > 0` gate would skip
|
|
1295
1198
|
// zero-geometry files (no IFC geometry entities), causing
|
|
@@ -1501,6 +1404,7 @@ existingSab, options) {
|
|
|
1501
1404
|
};
|
|
1502
1405
|
}
|
|
1503
1406
|
finally {
|
|
1407
|
+
trace.end(poolSpan);
|
|
1504
1408
|
phaseBoundTimers.clearAll();
|
|
1505
1409
|
// Re-seat the caller's handle to a frozen snapshot (#4979 review): the
|
|
1506
1410
|
// live reader closes over `gateTracker`, and transitively this whole
|