@ifc-lite/geometry 2.2.0 → 2.3.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-coordinate.d.ts.map +1 -1
- package/dist/geometry-coordinate.js +38 -1
- package/dist/geometry-coordinate.js.map +1 -1
- package/dist/geometry-native.d.ts.map +1 -1
- package/dist/geometry-native.js +53 -41
- package/dist/geometry-native.js.map +1 -1
- package/dist/geometry-parallel.d.ts +8 -0
- package/dist/geometry-parallel.d.ts.map +1 -1
- package/dist/geometry-parallel.js +271 -241
- package/dist/geometry-parallel.js.map +1 -1
- package/dist/geometry.worker.d.ts +16 -1
- package/dist/geometry.worker.d.ts.map +1 -1
- package/dist/geometry.worker.js +103 -33
- package/dist/geometry.worker.js.map +1 -1
- package/dist/ifc-lite-bridge.d.ts +25 -0
- package/dist/ifc-lite-bridge.d.ts.map +1 -1
- package/dist/ifc-lite-bridge.js +51 -0
- package/dist/ifc-lite-bridge.js.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -23
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -102,6 +102,9 @@ existingSab, options) {
|
|
|
102
102
|
// renderer (transferables; already typed arrays from the worker).
|
|
103
103
|
...(m.uvs ? { uvs: m.uvs } : {}),
|
|
104
104
|
...(m.texture ? { texture: m.texture } : {}),
|
|
105
|
+
// Carry the model-diff fingerprint through the worker boundary
|
|
106
|
+
// (issue #924); undefined when hashing is off.
|
|
107
|
+
...(m.geometryHash !== undefined ? { geometryHash: m.geometryHash } : {}),
|
|
105
108
|
}));
|
|
106
109
|
if (meshes.length > 0) {
|
|
107
110
|
// Update totalMeshes per batch so consumers see a live
|
|
@@ -187,6 +190,12 @@ existingSab, options) {
|
|
|
187
190
|
type: 'set-merge-layers',
|
|
188
191
|
enabled: options?.mergeLayers === true,
|
|
189
192
|
});
|
|
193
|
+
// Issue #924: forward the geometry-hash tolerance the same way — always
|
|
194
|
+
// sent so the controller path stays uniform; null is a cheap no-op.
|
|
195
|
+
worker.postMessage({
|
|
196
|
+
type: 'set-compute-geometry-hashes',
|
|
197
|
+
tolerance: options?.geometryHashTolerance ?? null,
|
|
198
|
+
});
|
|
190
199
|
}
|
|
191
200
|
const sendStreamEnd = () => {
|
|
192
201
|
if (endSentToWorkers)
|
|
@@ -292,285 +301,306 @@ existingSab, options) {
|
|
|
292
301
|
const elapsed = () => Math.round(performance.now() - t0);
|
|
293
302
|
console.log(`[stream] processParallel start, fileSizeMB=${fileSizeMB.toFixed(1)} workerCount=${workerCount}`);
|
|
294
303
|
const prepassWorker = makePrepassWorker();
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
|
|
311
|
-
wake();
|
|
312
|
-
return;
|
|
304
|
+
// Wrap the rest of the pipeline so worker teardown runs not only on
|
|
305
|
+
// normal completion / error / zero-jobs branches, but also when the
|
|
306
|
+
// consumer abandons the generator via `.return()` / `.throw()` while it
|
|
307
|
+
// is suspended at a `yield` or the `resolveWaiting` await. The viewer's
|
|
308
|
+
// `watchedGeometryStream` relies on this `finally` to tear down workers
|
|
309
|
+
// on break / abort / watchdog (see boundedIteratorReturn). The existing
|
|
310
|
+
// branch-local `terminate()` calls remain — `terminate()` is idempotent.
|
|
311
|
+
try {
|
|
312
|
+
// Forward the consumer-supplied wasm URL to the pre-pass worker so it
|
|
313
|
+
// doesn't fall back to wasm-bindgen's `import.meta.url` default. The
|
|
314
|
+
// pre-pass worker uses the same `geometry.worker.js` bundle and the
|
|
315
|
+
// legacy (non-threaded) wasm, so `wasmUrls.wasm` is the right key.
|
|
316
|
+
// Skipped entirely when no URL was provided — keeps Vite/webpack
|
|
317
|
+
// consumers on the bundler-native resolution path.
|
|
318
|
+
if (options?.wasmUrls?.wasm) {
|
|
319
|
+
prepassWorker.postMessage({ type: 'init', wasmUrl: options.wasmUrls.wasm });
|
|
313
320
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
buildingRotation: evt.buildingRotation ?? null,
|
|
322
|
-
};
|
|
323
|
-
console.log(`[stream] meta @ ${elapsed()}ms unitScale=${prepassMeta.unitScale} rtc=[${(prepassMeta.rtcOffset[0]).toFixed(0)},${(prepassMeta.rtcOffset[1]).toFixed(0)},${(prepassMeta.rtcOffset[2]).toFixed(0)}]`);
|
|
324
|
-
sendStreamStartIfReady();
|
|
321
|
+
let chunkArrivals = 0;
|
|
322
|
+
let totalDispatchedJobs = 0;
|
|
323
|
+
let firstChunkAt = -1;
|
|
324
|
+
prepassWorker.onmessage = (e) => {
|
|
325
|
+
const data = e.data;
|
|
326
|
+
if (data.type === 'prepass-progress') {
|
|
327
|
+
eventQueue.push({ type: 'progress', phase: 'prepass' });
|
|
325
328
|
wake();
|
|
329
|
+
return;
|
|
326
330
|
}
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
331
|
+
if (data.type === 'prepass-stream') {
|
|
332
|
+
const evt = data.event;
|
|
333
|
+
if (evt.type === 'meta') {
|
|
334
|
+
prepassMeta = {
|
|
335
|
+
unitScale: evt.unitScale,
|
|
336
|
+
rtcOffset: evt.rtcOffset,
|
|
337
|
+
needsShift: evt.needsShift,
|
|
338
|
+
buildingRotation: evt.buildingRotation ?? null,
|
|
339
|
+
};
|
|
340
|
+
console.log(`[stream] meta @ ${elapsed()}ms unitScale=${prepassMeta.unitScale} rtc=[${(prepassMeta.rtcOffset[0]).toFixed(0)},${(prepassMeta.rtcOffset[1]).toFixed(0)},${(prepassMeta.rtcOffset[2]).toFixed(0)}]`);
|
|
341
|
+
sendStreamStartIfReady();
|
|
342
|
+
wake();
|
|
338
343
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const styleColors = evt.styleColors;
|
|
348
|
-
const voidKeys = evt.voidKeys;
|
|
349
|
-
const voidCounts = evt.voidCounts;
|
|
350
|
-
const voidValues = evt.voidValues;
|
|
351
|
-
console.log(`[stream] styles @ ${elapsed()}ms (${styleIds.length} styled, ${voidKeys.length} void hosts), draining ${queuedChunks.length} queued chunks`);
|
|
352
|
-
for (const w of workers) {
|
|
353
|
-
// Slice each typed array per-worker so each can be in its own
|
|
354
|
-
// transfer list without conflict. The slice cost is bounded by
|
|
355
|
-
// `styleIds.length * 4` bytes — under 1 MB for ~250K styles.
|
|
356
|
-
try {
|
|
357
|
-
const sIds = styleIds.slice();
|
|
358
|
-
const sColors = styleColors.slice();
|
|
359
|
-
const vKeys = voidKeys.slice();
|
|
360
|
-
const vCounts = voidCounts.slice();
|
|
361
|
-
const vValues = voidValues.slice();
|
|
362
|
-
w.postMessage({
|
|
363
|
-
type: 'set-styles',
|
|
364
|
-
styleIds: sIds,
|
|
365
|
-
styleColors: sColors,
|
|
366
|
-
voidKeys: vKeys,
|
|
367
|
-
voidCounts: vCounts,
|
|
368
|
-
voidValues: vValues,
|
|
369
|
-
}, [sIds.buffer, sColors.buffer, vKeys.buffer, vCounts.buffer, vValues.buffer]);
|
|
344
|
+
else if (evt.type === 'jobs') {
|
|
345
|
+
const jobsArr = evt.jobs;
|
|
346
|
+
const jobCount = Math.floor(jobsArr.length / 3);
|
|
347
|
+
chunkArrivals++;
|
|
348
|
+
totalDispatchedJobs += jobCount;
|
|
349
|
+
if (firstChunkAt < 0) {
|
|
350
|
+
firstChunkAt = elapsed();
|
|
351
|
+
console.log(`[stream] first jobs chunk @ ${firstChunkAt}ms (${jobCount} jobs)`);
|
|
370
352
|
}
|
|
371
|
-
|
|
372
|
-
console.
|
|
353
|
+
if (chunkArrivals % 10 === 1 || jobCount < 1000) {
|
|
354
|
+
console.log(`[stream] chunk #${chunkArrivals} @ ${elapsed()}ms (+${jobCount} jobs, total ${totalDispatchedJobs})`);
|
|
373
355
|
}
|
|
356
|
+
dispatchJobsChunk(jobsArr);
|
|
374
357
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const ids = evt.ids;
|
|
387
|
-
const starts = evt.starts;
|
|
388
|
-
const lengths = evt.lengths;
|
|
389
|
-
console.log(`[stream] entity-index @ ${elapsed()}ms (${ids.length} entries)`);
|
|
390
|
-
if (typeof SharedArrayBuffer !== 'undefined') {
|
|
391
|
-
// Allocate one SAB triple, copy data once, share across all
|
|
392
|
-
// workers without postMessage clone cost.
|
|
393
|
-
const idsBytes = ids.byteLength;
|
|
394
|
-
const startsBytes = starts.byteLength;
|
|
395
|
-
const lengthsBytes = lengths.byteLength;
|
|
396
|
-
const sabIds = new SharedArrayBuffer(idsBytes);
|
|
397
|
-
const sabStarts = new SharedArrayBuffer(startsBytes);
|
|
398
|
-
const sabLengths = new SharedArrayBuffer(lengthsBytes);
|
|
399
|
-
new Uint32Array(sabIds).set(ids);
|
|
400
|
-
new Uint32Array(sabStarts).set(starts);
|
|
401
|
-
new Uint32Array(sabLengths).set(lengths);
|
|
358
|
+
else if (evt.type === 'styles') {
|
|
359
|
+
// Streaming pre-pass resolved styles + voids after its main scan.
|
|
360
|
+
// Push them into every worker, then drain any chunks that were
|
|
361
|
+
// held waiting for styles. Workers will process every chunk with
|
|
362
|
+
// resolved colors — uniform shading across the whole stream.
|
|
363
|
+
const styleIds = evt.styleIds;
|
|
364
|
+
const styleColors = evt.styleColors;
|
|
365
|
+
const voidKeys = evt.voidKeys;
|
|
366
|
+
const voidCounts = evt.voidCounts;
|
|
367
|
+
const voidValues = evt.voidValues;
|
|
368
|
+
console.log(`[stream] styles @ ${elapsed()}ms (${styleIds.length} styled, ${voidKeys.length} void hosts), draining ${queuedChunks.length} queued chunks`);
|
|
402
369
|
for (const w of workers) {
|
|
370
|
+
// Slice each typed array per-worker so each can be in its own
|
|
371
|
+
// transfer list without conflict. The slice cost is bounded by
|
|
372
|
+
// `styleIds.length * 4` bytes — under 1 MB for ~250K styles.
|
|
403
373
|
try {
|
|
374
|
+
const sIds = styleIds.slice();
|
|
375
|
+
const sColors = styleColors.slice();
|
|
376
|
+
const vKeys = voidKeys.slice();
|
|
377
|
+
const vCounts = voidCounts.slice();
|
|
378
|
+
const vValues = voidValues.slice();
|
|
404
379
|
w.postMessage({
|
|
405
|
-
type: 'set-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
console.warn('[stream] set-entity-index dispatch failed:', err);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
// Hand the same SAB triple to the parser worker (or any other
|
|
416
|
-
// listener) so it can skip its own `scanEntitiesFastBytes` call.
|
|
417
|
-
// Each consumer gets its own Uint32Array view over the shared
|
|
418
|
-
// buffers — no extra copy.
|
|
419
|
-
if (options?.onEntityIndex) {
|
|
420
|
-
try {
|
|
421
|
-
options.onEntityIndex(new Uint32Array(sabIds), new Uint32Array(sabStarts), new Uint32Array(sabLengths));
|
|
380
|
+
type: 'set-styles',
|
|
381
|
+
styleIds: sIds,
|
|
382
|
+
styleColors: sColors,
|
|
383
|
+
voidKeys: vKeys,
|
|
384
|
+
voidCounts: vCounts,
|
|
385
|
+
voidValues: vValues,
|
|
386
|
+
}, [sIds.buffer, sColors.buffer, vKeys.buffer, vCounts.buffer, vValues.buffer]);
|
|
422
387
|
}
|
|
423
388
|
catch (err) {
|
|
424
|
-
console.warn('[stream]
|
|
389
|
+
console.warn('[stream] set-styles dispatch failed:', err);
|
|
425
390
|
}
|
|
426
391
|
}
|
|
392
|
+
stylesReceived = true;
|
|
393
|
+
// Drain only when ALL gates are open (entity-index too). The
|
|
394
|
+
// worker's tail-promise serialiser ensures any set-* runs
|
|
395
|
+
// before any subsequent stream-chunk.
|
|
396
|
+
drainQueuedChunksIfReady();
|
|
427
397
|
}
|
|
428
|
-
else {
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
398
|
+
else if (evt.type === 'entity-index') {
|
|
399
|
+
// Pre-pass exported its built entity_index. Forward to every
|
|
400
|
+
// worker so they skip the ~5 s file re-scan in Rust's lazy
|
|
401
|
+
// build path. SAB sharing for zero-copy distribution to N
|
|
402
|
+
// workers — each gets a Uint32Array view over the same buffer.
|
|
403
|
+
const ids = evt.ids;
|
|
404
|
+
const starts = evt.starts;
|
|
405
|
+
const lengths = evt.lengths;
|
|
406
|
+
console.log(`[stream] entity-index @ ${elapsed()}ms (${ids.length} entries)`);
|
|
407
|
+
if (typeof SharedArrayBuffer !== 'undefined') {
|
|
408
|
+
// Allocate one SAB triple, copy data once, share across all
|
|
409
|
+
// workers without postMessage clone cost.
|
|
410
|
+
const idsBytes = ids.byteLength;
|
|
411
|
+
const startsBytes = starts.byteLength;
|
|
412
|
+
const lengthsBytes = lengths.byteLength;
|
|
413
|
+
const sabIds = new SharedArrayBuffer(idsBytes);
|
|
414
|
+
const sabStarts = new SharedArrayBuffer(startsBytes);
|
|
415
|
+
const sabLengths = new SharedArrayBuffer(lengthsBytes);
|
|
416
|
+
new Uint32Array(sabIds).set(ids);
|
|
417
|
+
new Uint32Array(sabStarts).set(starts);
|
|
418
|
+
new Uint32Array(sabLengths).set(lengths);
|
|
419
|
+
for (const w of workers) {
|
|
420
|
+
try {
|
|
421
|
+
w.postMessage({
|
|
422
|
+
type: 'set-entity-index',
|
|
423
|
+
ids: new Uint32Array(sabIds),
|
|
424
|
+
starts: new Uint32Array(sabStarts),
|
|
425
|
+
lengths: new Uint32Array(sabLengths),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
console.warn('[stream] set-entity-index dispatch failed:', err);
|
|
430
|
+
}
|
|
438
431
|
}
|
|
439
|
-
|
|
440
|
-
|
|
432
|
+
// Hand the same SAB triple to the parser worker (or any other
|
|
433
|
+
// listener) so it can skip its own `scanEntitiesFastBytes` call.
|
|
434
|
+
// Each consumer gets its own Uint32Array view over the shared
|
|
435
|
+
// buffers — no extra copy.
|
|
436
|
+
if (options?.onEntityIndex) {
|
|
437
|
+
try {
|
|
438
|
+
options.onEntityIndex(new Uint32Array(sabIds), new Uint32Array(sabStarts), new Uint32Array(sabLengths));
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
console.warn('[stream] onEntityIndex callback failed:', err);
|
|
442
|
+
}
|
|
441
443
|
}
|
|
442
444
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
445
|
+
else {
|
|
446
|
+
// SAB unavailable — clone per worker via structured clone.
|
|
447
|
+
for (const w of workers) {
|
|
448
|
+
try {
|
|
449
|
+
w.postMessage({
|
|
450
|
+
type: 'set-entity-index',
|
|
451
|
+
ids: ids.slice(),
|
|
452
|
+
starts: starts.slice(),
|
|
453
|
+
lengths: lengths.slice(),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
console.warn('[stream] set-entity-index dispatch failed:', err);
|
|
458
|
+
}
|
|
446
459
|
}
|
|
447
|
-
|
|
448
|
-
|
|
460
|
+
if (options?.onEntityIndex) {
|
|
461
|
+
try {
|
|
462
|
+
options.onEntityIndex(ids.slice(), starts.slice(), lengths.slice());
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
console.warn('[stream] onEntityIndex callback failed:', err);
|
|
466
|
+
}
|
|
449
467
|
}
|
|
450
468
|
}
|
|
469
|
+
entityIndexReceived = true;
|
|
470
|
+
drainQueuedChunksIfReady();
|
|
451
471
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
prepassCompleteSeen = true;
|
|
465
|
-
onPrepassComplete();
|
|
472
|
+
else if (evt.type === 'complete') {
|
|
473
|
+
prepassJobsTotal = evt.totalJobs;
|
|
474
|
+
console.log(`[stream] prepass complete @ ${elapsed()}ms totalJobs=${prepassJobsTotal} chunks=${chunkArrivals}`);
|
|
475
|
+
// Unconditionally drive the prepass-complete handler here.
|
|
476
|
+
// The outer loop's `prepassJobsTotal > 0` gate would skip
|
|
477
|
+
// zero-geometry files (no IFC geometry entities), causing
|
|
478
|
+
// the generator to wait forever. Calling here ensures
|
|
479
|
+
// prepassDone flips even when totalJobs === 0.
|
|
480
|
+
if (!prepassCompleteSeen) {
|
|
481
|
+
prepassCompleteSeen = true;
|
|
482
|
+
onPrepassComplete();
|
|
483
|
+
}
|
|
466
484
|
}
|
|
485
|
+
return;
|
|
467
486
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
487
|
+
if (data.type === 'error') {
|
|
488
|
+
prepassError = new Error(data.message);
|
|
489
|
+
prepassDone = true;
|
|
490
|
+
prepassWorker.terminate();
|
|
491
|
+
wake();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
// The streaming variant doesn't emit `prepass-result` — the streaming
|
|
495
|
+
// worker exits naturally after the JS callback returns from
|
|
496
|
+
// `buildPrePassStreaming`. We treat unknown messages as no-ops.
|
|
497
|
+
};
|
|
498
|
+
prepassWorker.onerror = (e) => {
|
|
499
|
+
prepassError = new Error(`Pre-pass worker failed: ${e.message}`);
|
|
472
500
|
prepassDone = true;
|
|
473
501
|
prepassWorker.terminate();
|
|
474
502
|
wake();
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
while (true) {
|
|
524
|
-
while (eventQueue.length > 0) {
|
|
525
|
-
yield eventQueue.shift();
|
|
526
|
-
}
|
|
527
|
-
if (workerError) {
|
|
528
|
-
for (const w of workers) {
|
|
503
|
+
};
|
|
504
|
+
// Track when the pre-pass worker finishes by listening for either a
|
|
505
|
+
// synthesized "complete" event from the Rust side OR a worker exit. The
|
|
506
|
+
// Rust side currently doesn't post anything after `complete` (it returns
|
|
507
|
+
// from JS), so we close the worker via terminate-on-complete in the host.
|
|
508
|
+
// After we see the Rust `complete` event we can sendStreamEnd.
|
|
509
|
+
const onPrepassComplete = () => {
|
|
510
|
+
prepassDone = true;
|
|
511
|
+
// Only signal stream-end to workers if they actually got
|
|
512
|
+
// stream-start (which gates on `meta`). Zero-geometry files
|
|
513
|
+
// never trigger meta → workers never start → no stream-end
|
|
514
|
+
// needed. The dedicated zero-jobs branch in the outer loop
|
|
515
|
+
// handles their teardown.
|
|
516
|
+
if (streamStartSentToWorkers) {
|
|
517
|
+
sendStreamEnd();
|
|
518
|
+
}
|
|
519
|
+
prepassWorker.terminate();
|
|
520
|
+
wake();
|
|
521
|
+
};
|
|
522
|
+
// Dispatch the streaming pre-pass.
|
|
523
|
+
// chunk_size = 50K is a deliberate compromise:
|
|
524
|
+
// • small enough that the FIRST chunk (always a tiny one — bounded by
|
|
525
|
+
// RTC_SAMPLE_THRESHOLD ≈ 50 jobs from the Rust side) reaches workers
|
|
526
|
+
// within ~1.5 s for fast TTFG;
|
|
527
|
+
// • large enough that subsequent chunks make few Rust→JS callbacks
|
|
528
|
+
// and few worker postMessages — each call into processGeometryBatch
|
|
529
|
+
// has fixed setup cost that compounds badly when invoked 30+ times.
|
|
530
|
+
// Per-chunk fan-out (see `dispatchJobsChunkInternal`) splits each chunk
|
|
531
|
+
// evenly across all workers so parallelism is preserved at every chunk.
|
|
532
|
+
prepassWorker.postMessage({ type: 'prepass-streaming', sharedBuffer, chunkSize: 50_000 });
|
|
533
|
+
// Drain the event queue until the pre-pass and all process workers complete.
|
|
534
|
+
// The pre-pass `complete` event is captured inside the message handler
|
|
535
|
+
// (we set prepassJobsTotal there) but the worker stays alive briefly
|
|
536
|
+
// while the JS callback returns. Detect end-of-stream by:
|
|
537
|
+
// a) `prepassJobsTotal > 0` (or zero-jobs file): pre-pass emitted complete
|
|
538
|
+
// b) all workers reported `complete`
|
|
539
|
+
let prepassCompleteSeen = false;
|
|
540
|
+
while (true) {
|
|
541
|
+
while (eventQueue.length > 0) {
|
|
542
|
+
yield eventQueue.shift();
|
|
543
|
+
}
|
|
544
|
+
if (workerError) {
|
|
545
|
+
for (const w of workers) {
|
|
546
|
+
try {
|
|
547
|
+
w.terminate();
|
|
548
|
+
}
|
|
549
|
+
catch { /* cleanup — safe to ignore */ }
|
|
550
|
+
}
|
|
529
551
|
try {
|
|
530
|
-
|
|
552
|
+
prepassWorker.terminate();
|
|
531
553
|
}
|
|
532
554
|
catch { /* cleanup — safe to ignore */ }
|
|
555
|
+
throw workerError;
|
|
533
556
|
}
|
|
534
|
-
|
|
535
|
-
|
|
557
|
+
if (prepassError) {
|
|
558
|
+
for (const w of workers) {
|
|
559
|
+
try {
|
|
560
|
+
w.terminate();
|
|
561
|
+
}
|
|
562
|
+
catch { /* cleanup — safe to ignore */ }
|
|
563
|
+
}
|
|
564
|
+
throw prepassError;
|
|
536
565
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
566
|
+
// Edge case: pre-pass for a file with zero geometry. The Rust side
|
|
567
|
+
// emits `complete { totalJobs: 0 }`; meta never fired so workers
|
|
568
|
+
// never received stream-start. Tear them down explicitly and yield
|
|
569
|
+
// `complete`. Workers were pre-spawned with `init` so they need an
|
|
570
|
+
// explicit terminate to exit.
|
|
571
|
+
if (prepassDone && !streamStartSentToWorkers && prepassJobsTotal === 0) {
|
|
572
|
+
for (const w of workers) {
|
|
573
|
+
try {
|
|
574
|
+
w.terminate();
|
|
575
|
+
}
|
|
576
|
+
catch { /* cleanup — safe to ignore */ }
|
|
544
577
|
}
|
|
545
|
-
|
|
578
|
+
const coordinateInfo = coordinator.getFinalCoordinateInfo();
|
|
579
|
+
yield { type: 'complete', totalMeshes: 0, coordinateInfo };
|
|
580
|
+
return;
|
|
546
581
|
}
|
|
547
|
-
|
|
582
|
+
if (prepassDone
|
|
583
|
+
&& streamStartSentToWorkers
|
|
584
|
+
&& workersCompleted >= workers.length
|
|
585
|
+
&& eventQueue.length === 0) {
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
await new Promise((resolve) => { resolveWaiting = resolve; });
|
|
548
589
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
try {
|
|
557
|
-
w.terminate();
|
|
558
|
-
}
|
|
559
|
-
catch { /* cleanup — safe to ignore */ }
|
|
590
|
+
const coordinateInfo = coordinator.getFinalCoordinateInfo();
|
|
591
|
+
yield { type: 'complete', totalMeshes, coordinateInfo };
|
|
592
|
+
}
|
|
593
|
+
finally {
|
|
594
|
+
for (const w of workers) {
|
|
595
|
+
try {
|
|
596
|
+
w.terminate();
|
|
560
597
|
}
|
|
561
|
-
|
|
562
|
-
yield { type: 'complete', totalMeshes: 0, coordinateInfo };
|
|
563
|
-
return;
|
|
598
|
+
catch { /* cleanup — safe to ignore */ }
|
|
564
599
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
&& workersCompleted >= workers.length
|
|
568
|
-
&& eventQueue.length === 0) {
|
|
569
|
-
break;
|
|
600
|
+
try {
|
|
601
|
+
prepassWorker.terminate();
|
|
570
602
|
}
|
|
571
|
-
|
|
603
|
+
catch { /* cleanup — safe to ignore */ }
|
|
572
604
|
}
|
|
573
|
-
const coordinateInfo = coordinator.getFinalCoordinateInfo();
|
|
574
|
-
yield { type: 'complete', totalMeshes, coordinateInfo };
|
|
575
605
|
}
|
|
576
606
|
//# sourceMappingURL=geometry-parallel.js.map
|