@ifc-lite/geometry 1.16.5 → 1.17.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/README.md +43 -12
- package/dist/geometry-coordinate.d.ts +29 -0
- package/dist/geometry-coordinate.d.ts.map +1 -0
- package/dist/geometry-coordinate.js +83 -0
- package/dist/geometry-coordinate.js.map +1 -0
- package/dist/geometry-native.d.ts +51 -0
- package/dist/geometry-native.d.ts.map +1 -0
- package/dist/geometry-native.js +154 -0
- package/dist/geometry-native.js.map +1 -0
- package/dist/geometry-parallel.d.ts +23 -0
- package/dist/geometry-parallel.d.ts.map +1 -0
- package/dist/geometry-parallel.js +193 -0
- package/dist/geometry-parallel.js.map +1 -0
- package/dist/ifc-lite-mesh-collector.d.ts +1 -0
- package/dist/ifc-lite-mesh-collector.d.ts.map +1 -1
- package/dist/ifc-lite-mesh-collector.js +1 -0
- package/dist/ifc-lite-mesh-collector.js.map +1 -1
- package/dist/index.d.ts +18 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -382
- package/dist/index.js.map +1 -1
- package/dist/native-bridge-conversion.d.ts +71 -0
- package/dist/native-bridge-conversion.d.ts.map +1 -0
- package/dist/native-bridge-conversion.js +55 -0
- package/dist/native-bridge-conversion.js.map +1 -0
- package/dist/native-bridge.d.ts.map +1 -1
- package/dist/native-bridge.js +2 -139
- package/dist/native-bridge.js.map +1 -1
- package/dist/packed-geometry-decoder.d.ts +25 -0
- package/dist/packed-geometry-decoder.d.ts.map +1 -0
- package/dist/packed-geometry-decoder.js +106 -0
- package/dist/packed-geometry-decoder.js.map +1 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ifc-lite/geometry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Streaming geometry processor for IFClite. Converts IFC bytes to renderable mesh batches via Rust + WASM (or Tauri native), with first triangles in 300–500ms and progressive streaming for large models.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ Geometry processing bridge for IFClite. Connects the WASM-based geometry engine
|
|
|
8
8
|
npm install @ifc-lite/geometry
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Process geometry from IFC bytes
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { GeometryProcessor } from '@ifc-lite/geometry';
|
|
@@ -16,22 +16,53 @@ import { GeometryProcessor } from '@ifc-lite/geometry';
|
|
|
16
16
|
const processor = new GeometryProcessor();
|
|
17
17
|
await processor.init();
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
20
|
+
const result = await processor.process(buffer);
|
|
21
|
+
|
|
22
|
+
console.log(`${result.meshes.length} meshes, ${result.totalTriangles} triangles`);
|
|
23
|
+
// Each mesh: { expressId, positions: Float32Array, normals, indices: Uint32Array, color, ifcType }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Stream geometry (recommended for large models)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
for await (const event of processor.processStreaming(buffer)) {
|
|
30
|
+
if (event.type === 'batch') {
|
|
31
|
+
renderer.appendMeshes(event.meshes);
|
|
32
|
+
console.log(`Loaded ${event.totalSoFar} meshes so far`);
|
|
33
|
+
} else if (event.type === 'complete') {
|
|
34
|
+
console.log(`Done: ${event.totalMeshes} meshes`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The streaming path emits batches every ~100 meshes so the renderer can paint progressively — first triangles typically arrive 300–500ms after `processStreaming()` returns.
|
|
40
|
+
|
|
41
|
+
## Coordinate handling
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { CoordinateHandler } from '@ifc-lite/geometry';
|
|
45
|
+
|
|
46
|
+
const result = await processor.process(buffer);
|
|
47
|
+
|
|
48
|
+
// Models with large world coordinates (geo-referenced) get auto-shifted
|
|
49
|
+
// to keep float precision inside the renderer.
|
|
50
|
+
if (result.coordinateInfo?.hasLargeCoordinates) {
|
|
51
|
+
const { x, y, z } = result.coordinateInfo.originShift;
|
|
52
|
+
console.log(`Origin shifted by [${x}, ${y}, ${z}] for renderer precision`);
|
|
53
|
+
}
|
|
22
54
|
```
|
|
23
55
|
|
|
24
|
-
##
|
|
56
|
+
## Performance
|
|
25
57
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- Coordinate system handling and origin shift
|
|
58
|
+
- **First triangles:** 300–500ms (streaming path)
|
|
59
|
+
- **Throughput:** up to 5× faster than `web-ifc` on the same model
|
|
60
|
+
- **Worker support:** files > 50 MB process off-main-thread automatically
|
|
61
|
+
- **Native (Tauri):** `preferNative: true` constructor option enables the native Rust pipeline for desktop builds
|
|
31
62
|
|
|
32
63
|
## API
|
|
33
64
|
|
|
34
|
-
See the [Geometry Guide](
|
|
65
|
+
See the [Geometry Guide](https://louistrue.github.io/ifc-lite/guide/geometry/) and [API Reference](https://louistrue.github.io/ifc-lite/api/typescript/#ifc-litegeometry).
|
|
35
66
|
|
|
36
67
|
## License
|
|
37
68
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coordinate transform utilities and batch-conversion helpers.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that convert WASM mesh/instanced-geometry collections
|
|
5
|
+
* into plain MeshData arrays, compute streaming batch sizes, and merge
|
|
6
|
+
* building rotation into coordinate info.
|
|
7
|
+
*/
|
|
8
|
+
import type { MeshData, CoordinateInfo } from './types.js';
|
|
9
|
+
import type { DynamicBatchConfig } from './index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Return a fixed or heuristic batch size for streaming, given the file
|
|
12
|
+
* buffer and the caller-supplied config.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getStreamingBatchSize(buffer: Uint8Array, batchConfig: number | DynamicBatchConfig): number;
|
|
15
|
+
/**
|
|
16
|
+
* Convert a WASM MeshCollection into a plain MeshData array, freeing
|
|
17
|
+
* each mesh and the collection itself.
|
|
18
|
+
*/
|
|
19
|
+
export declare function convertMeshCollectionToBatch(collection: import('@ifc-lite/wasm').MeshCollection): MeshData[];
|
|
20
|
+
/**
|
|
21
|
+
* Convert a WASM InstancedMeshCollection into a plain array of
|
|
22
|
+
* InstancedGeometry objects, freeing the collection wrapper.
|
|
23
|
+
*/
|
|
24
|
+
export declare function convertInstancedCollectionToBatch(collection: import('@ifc-lite/wasm').InstancedMeshCollection): import('@ifc-lite/wasm').InstancedGeometry[];
|
|
25
|
+
/**
|
|
26
|
+
* Merge an optional building rotation value into a CoordinateInfo object.
|
|
27
|
+
*/
|
|
28
|
+
export declare function withBuildingRotation(coordinateInfo: CoordinateInfo, buildingRotation?: number): CoordinateInfo;
|
|
29
|
+
//# sourceMappingURL=geometry-coordinate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-coordinate.d.ts","sourceRoot":"","sources":["../src/geometry-coordinate.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,GAAG,kBAAkB,GACvC,MAAM,CAeR;AAID;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,OAAO,gBAAgB,EAAE,cAAc,GAClD,QAAQ,EAAE,CA0BZ;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,OAAO,gBAAgB,EAAE,uBAAuB,GAC3D,OAAO,gBAAgB,EAAE,iBAAiB,EAAE,CAe9C;AAID;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,cAAc,EAC9B,gBAAgB,CAAC,EAAE,MAAM,GACxB,cAAc,CAIhB"}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
// ── Batch-size heuristics ──
|
|
5
|
+
/**
|
|
6
|
+
* Return a fixed or heuristic batch size for streaming, given the file
|
|
7
|
+
* buffer and the caller-supplied config.
|
|
8
|
+
*/
|
|
9
|
+
export function getStreamingBatchSize(buffer, batchConfig) {
|
|
10
|
+
if (typeof batchConfig === 'number') {
|
|
11
|
+
return batchConfig;
|
|
12
|
+
}
|
|
13
|
+
const fileSizeMB = batchConfig.fileSizeMB
|
|
14
|
+
? batchConfig.fileSizeMB
|
|
15
|
+
: buffer.length / (1024 * 1024);
|
|
16
|
+
return fileSizeMB < 10 ? 100
|
|
17
|
+
: fileSizeMB < 50 ? 200
|
|
18
|
+
: fileSizeMB < 100 ? 300
|
|
19
|
+
: fileSizeMB < 300 ? 500
|
|
20
|
+
: fileSizeMB < 500 ? 1500
|
|
21
|
+
: 3000;
|
|
22
|
+
}
|
|
23
|
+
// ── WASM collection → MeshData[] conversion ──
|
|
24
|
+
/**
|
|
25
|
+
* Convert a WASM MeshCollection into a plain MeshData array, freeing
|
|
26
|
+
* each mesh and the collection itself.
|
|
27
|
+
*/
|
|
28
|
+
export function convertMeshCollectionToBatch(collection) {
|
|
29
|
+
const batch = [];
|
|
30
|
+
try {
|
|
31
|
+
for (let i = 0; i < collection.length; i++) {
|
|
32
|
+
const mesh = collection.get(i);
|
|
33
|
+
if (!mesh)
|
|
34
|
+
continue;
|
|
35
|
+
try {
|
|
36
|
+
batch.push({
|
|
37
|
+
expressId: mesh.expressId,
|
|
38
|
+
ifcType: mesh.ifcType,
|
|
39
|
+
positions: mesh.positions,
|
|
40
|
+
normals: mesh.normals,
|
|
41
|
+
indices: mesh.indices,
|
|
42
|
+
color: [mesh.color[0], mesh.color[1], mesh.color[2], mesh.color[3]],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
mesh.free();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
collection.free();
|
|
52
|
+
}
|
|
53
|
+
return batch;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Convert a WASM InstancedMeshCollection into a plain array of
|
|
57
|
+
* InstancedGeometry objects, freeing the collection wrapper.
|
|
58
|
+
*/
|
|
59
|
+
export function convertInstancedCollectionToBatch(collection) {
|
|
60
|
+
const batch = [];
|
|
61
|
+
try {
|
|
62
|
+
for (let i = 0; i < collection.length; i++) {
|
|
63
|
+
const geometry = collection.get(i);
|
|
64
|
+
if (geometry) {
|
|
65
|
+
batch.push(geometry);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
collection.free();
|
|
71
|
+
}
|
|
72
|
+
return batch;
|
|
73
|
+
}
|
|
74
|
+
// ── Coordinate-info helpers ──
|
|
75
|
+
/**
|
|
76
|
+
* Merge an optional building rotation value into a CoordinateInfo object.
|
|
77
|
+
*/
|
|
78
|
+
export function withBuildingRotation(coordinateInfo, buildingRotation) {
|
|
79
|
+
return buildingRotation !== undefined
|
|
80
|
+
? { ...coordinateInfo, buildingRotation }
|
|
81
|
+
: coordinateInfo;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=geometry-coordinate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-coordinate.js","sourceRoot":"","sources":["../src/geometry-coordinate.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAa/D,8BAA8B;AAE9B;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAkB,EAClB,WAAwC;IAExC,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,UAAU,GAAG,WAAW,CAAC,UAAU;QACvC,CAAC,CAAC,WAAW,CAAC,UAAU;QACxB,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAElC,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG;QAC1B,CAAC,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG;YACvB,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG;gBACxB,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG;oBACxB,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI;wBACzB,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,gDAAgD;AAEhD;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAC1C,UAAmD;IAEnD,MAAM,KAAK,GAAe,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC/B,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,IAAI,CAAC;gBACH,KAAK,CAAC,IAAI,CAAC;oBACT,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;iBACpE,CAAC,CAAC;YACL,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,UAAU,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iCAAiC,CAC/C,UAA4D;IAE5D,MAAM,KAAK,GAAiD,EAAE,CAAC;IAE/D,IAAI,CAAC;QACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,QAAQ,EAAE,CAAC;gBACb,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,UAAU,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,gCAAgC;AAEhC;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,cAA8B,EAC9B,gBAAyB;IAEzB,OAAO,gBAAgB,KAAK,SAAS;QACnC,CAAC,CAAC,EAAE,GAAG,cAAc,EAAE,gBAAgB,EAAE;QACzC,CAAC,CAAC,cAAc,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Tauri bridge streaming helpers.
|
|
3
|
+
*
|
|
4
|
+
* Handles queue coalescing, back-pressure, and event-loop yielding for
|
|
5
|
+
* the native desktop geometry streaming path. These are platform-specific
|
|
6
|
+
* utilities isolated from the main GeometryProcessor.
|
|
7
|
+
*/
|
|
8
|
+
import type { CoordinateHandler } from './coordinate-handler.js';
|
|
9
|
+
import type { MeshData } from './types.js';
|
|
10
|
+
import type { GeometryStats as PlatformGeometryStats, GeometryBatch, NativeBatchTelemetry } from './platform-bridge.js';
|
|
11
|
+
import type { StreamingGeometryEvent } from './index.js';
|
|
12
|
+
export declare const MAX_NATIVE_STREAM_QUEUE_EVENTS = 8;
|
|
13
|
+
export declare const MAX_NATIVE_STREAM_QUEUE_MESHES = 32768;
|
|
14
|
+
export declare const MAX_NATIVE_STREAM_EVENTS_PER_TURN = 4;
|
|
15
|
+
export declare const MAX_NATIVE_STREAM_MESHES_PER_TURN = 8192;
|
|
16
|
+
export declare const MAX_NATIVE_STREAM_DRAIN_MS = 10;
|
|
17
|
+
export type QueuedNativeStreamingEvent = {
|
|
18
|
+
type: 'batch';
|
|
19
|
+
meshes: MeshData[];
|
|
20
|
+
nativeTelemetry?: NativeBatchTelemetry;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'colorUpdate';
|
|
23
|
+
updates: Map<number, [number, number, number, number]>;
|
|
24
|
+
};
|
|
25
|
+
export declare function yieldToEventLoop(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Coalesce incoming native events into the queue to reduce per-yield
|
|
28
|
+
* overhead when the JS main thread cannot keep up with Rust production.
|
|
29
|
+
*/
|
|
30
|
+
export declare function enqueueNativeStreamingEvent(queuedEvents: QueuedNativeStreamingEvent[], event: QueuedNativeStreamingEvent, queueState: {
|
|
31
|
+
queuedMeshes: number;
|
|
32
|
+
coalescedBatchCount: number;
|
|
33
|
+
}): void;
|
|
34
|
+
/**
|
|
35
|
+
* Shared native streaming generator used by both buffer-based and
|
|
36
|
+
* path-based native geometry streaming.
|
|
37
|
+
*
|
|
38
|
+
* @param startStream Callback that kicks off the native stream and
|
|
39
|
+
* returns a promise resolving when it finishes.
|
|
40
|
+
* @param totalEstimate Estimated total for the 'start' event.
|
|
41
|
+
* @param coordinator CoordinateHandler for incremental bounds.
|
|
42
|
+
* @param setLastNativeStats Callback to persist the latest stats on
|
|
43
|
+
* the owning GeometryProcessor instance.
|
|
44
|
+
*/
|
|
45
|
+
export declare function streamNativeGeometry(startStream: (options: {
|
|
46
|
+
onBatch: (batch: GeometryBatch) => void;
|
|
47
|
+
onColorUpdate: (updates: Map<number, [number, number, number, number]>) => void;
|
|
48
|
+
onComplete: (stats: PlatformGeometryStats) => void;
|
|
49
|
+
onError: (error: Error) => void;
|
|
50
|
+
}) => Promise<PlatformGeometryStats>, totalEstimate: number, coordinator: CoordinateHandler, setLastNativeStats: (stats: PlatformGeometryStats) => void): AsyncGenerator<StreamingGeometryEvent>;
|
|
51
|
+
//# sourceMappingURL=geometry-native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-native.d.ts","sourceRoot":"","sources":["../src/geometry-native.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,IAAI,qBAAqB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACxH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAIzD,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAChD,eAAO,MAAM,8BAA8B,QAAQ,CAAC;AACpD,eAAO,MAAM,iCAAiC,IAAI,CAAC;AACnD,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAI7C,MAAM,MAAM,0BAA0B,GAClC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;IAAC,eAAe,CAAC,EAAE,oBAAoB,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,CAAC;AAIpF,wBAAgB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUhD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,0BAA0B,EAAE,EAC1C,KAAK,EAAE,0BAA0B,EACjC,UAAU,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,GAChE,IAAI,CA6BN;AAED;;;;;;;;;;GAUG;AACH,wBAAuB,oBAAoB,CACzC,WAAW,EAAE,CAAC,OAAO,EAAE;IACrB,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IAChF,UAAU,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACnD,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,KAAK,OAAO,CAAC,qBAAqB,CAAC,EACpC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,iBAAiB,EAC9B,kBAAkB,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,GACzD,cAAc,CAAC,sBAAsB,CAAC,CA8GxC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
// ── Queue tuning constants ──
|
|
5
|
+
export const MAX_NATIVE_STREAM_QUEUE_EVENTS = 8;
|
|
6
|
+
export const MAX_NATIVE_STREAM_QUEUE_MESHES = 32768;
|
|
7
|
+
export const MAX_NATIVE_STREAM_EVENTS_PER_TURN = 4;
|
|
8
|
+
export const MAX_NATIVE_STREAM_MESHES_PER_TURN = 8192;
|
|
9
|
+
export const MAX_NATIVE_STREAM_DRAIN_MS = 10;
|
|
10
|
+
// ── Helpers ──
|
|
11
|
+
export function yieldToEventLoop() {
|
|
12
|
+
const maybeScheduler = globalThis.scheduler;
|
|
13
|
+
if (typeof maybeScheduler?.yield === 'function') {
|
|
14
|
+
return maybeScheduler.yield();
|
|
15
|
+
}
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
globalThis.setTimeout(resolve, 0);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Coalesce incoming native events into the queue to reduce per-yield
|
|
22
|
+
* overhead when the JS main thread cannot keep up with Rust production.
|
|
23
|
+
*/
|
|
24
|
+
export function enqueueNativeStreamingEvent(queuedEvents, event, queueState) {
|
|
25
|
+
if (event.type === 'colorUpdate') {
|
|
26
|
+
const lastEvent = queuedEvents[queuedEvents.length - 1];
|
|
27
|
+
if (lastEvent?.type === 'colorUpdate') {
|
|
28
|
+
for (const [expressId, color] of event.updates) {
|
|
29
|
+
lastEvent.updates.set(expressId, color);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
queuedEvents.push(event);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const lastEvent = queuedEvents[queuedEvents.length - 1];
|
|
37
|
+
const shouldCoalesce = lastEvent?.type === 'batch' &&
|
|
38
|
+
(queuedEvents.length >= MAX_NATIVE_STREAM_QUEUE_EVENTS || queueState.queuedMeshes >= MAX_NATIVE_STREAM_QUEUE_MESHES);
|
|
39
|
+
if (shouldCoalesce) {
|
|
40
|
+
for (let i = 0; i < event.meshes.length; i++) {
|
|
41
|
+
lastEvent.meshes.push(event.meshes[i]);
|
|
42
|
+
}
|
|
43
|
+
lastEvent.nativeTelemetry = event.nativeTelemetry;
|
|
44
|
+
queueState.coalescedBatchCount += 1;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
queuedEvents.push(event);
|
|
48
|
+
}
|
|
49
|
+
queueState.queuedMeshes += event.meshes.length;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Shared native streaming generator used by both buffer-based and
|
|
53
|
+
* path-based native geometry streaming.
|
|
54
|
+
*
|
|
55
|
+
* @param startStream Callback that kicks off the native stream and
|
|
56
|
+
* returns a promise resolving when it finishes.
|
|
57
|
+
* @param totalEstimate Estimated total for the 'start' event.
|
|
58
|
+
* @param coordinator CoordinateHandler for incremental bounds.
|
|
59
|
+
* @param setLastNativeStats Callback to persist the latest stats on
|
|
60
|
+
* the owning GeometryProcessor instance.
|
|
61
|
+
*/
|
|
62
|
+
export async function* streamNativeGeometry(startStream, totalEstimate, coordinator, setLastNativeStats) {
|
|
63
|
+
coordinator.reset();
|
|
64
|
+
yield { type: 'start', totalEstimate };
|
|
65
|
+
await yieldToEventLoop();
|
|
66
|
+
yield { type: 'model-open', modelID: 0 };
|
|
67
|
+
const queuedEvents = [];
|
|
68
|
+
const queueState = { queuedMeshes: 0, coalescedBatchCount: 0 };
|
|
69
|
+
let resolvePending = null;
|
|
70
|
+
let completed = false;
|
|
71
|
+
let streamError = null;
|
|
72
|
+
let completedTotalMeshes;
|
|
73
|
+
let totalMeshes = 0;
|
|
74
|
+
const wake = () => {
|
|
75
|
+
if (resolvePending) {
|
|
76
|
+
resolvePending();
|
|
77
|
+
resolvePending = null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const streamingPromise = startStream({
|
|
81
|
+
onBatch: (batch) => {
|
|
82
|
+
enqueueNativeStreamingEvent(queuedEvents, { type: 'batch', meshes: batch.meshes, nativeTelemetry: batch.nativeTelemetry }, queueState);
|
|
83
|
+
wake();
|
|
84
|
+
},
|
|
85
|
+
onColorUpdate: (updates) => {
|
|
86
|
+
enqueueNativeStreamingEvent(queuedEvents, { type: 'colorUpdate', updates: new Map(updates) }, queueState);
|
|
87
|
+
wake();
|
|
88
|
+
},
|
|
89
|
+
onComplete: (stats) => {
|
|
90
|
+
setLastNativeStats(stats);
|
|
91
|
+
completedTotalMeshes = stats.totalMeshes;
|
|
92
|
+
completed = true;
|
|
93
|
+
wake();
|
|
94
|
+
},
|
|
95
|
+
onError: (error) => {
|
|
96
|
+
streamError = error;
|
|
97
|
+
completed = true;
|
|
98
|
+
wake();
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
while (!completed || queuedEvents.length > 0) {
|
|
102
|
+
let drainedEventCount = 0;
|
|
103
|
+
let drainedMeshCount = 0;
|
|
104
|
+
let drainStartedAt = performance.now();
|
|
105
|
+
while (queuedEvents.length > 0) {
|
|
106
|
+
const event = queuedEvents.shift();
|
|
107
|
+
if (event.type === 'colorUpdate') {
|
|
108
|
+
yield { type: 'colorUpdate', updates: event.updates };
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
queueState.queuedMeshes = Math.max(0, queueState.queuedMeshes - event.meshes.length);
|
|
112
|
+
// Native desktop streaming already produces site-local geometry, so
|
|
113
|
+
// avoid the generic JS RTC/outlier scan on every streamed batch.
|
|
114
|
+
coordinator.processTrustedMeshesIncremental(event.meshes);
|
|
115
|
+
totalMeshes += event.meshes.length;
|
|
116
|
+
const coordinateInfo = coordinator.getCurrentCoordinateInfo();
|
|
117
|
+
yield {
|
|
118
|
+
type: 'batch',
|
|
119
|
+
meshes: event.meshes,
|
|
120
|
+
totalSoFar: totalMeshes,
|
|
121
|
+
coordinateInfo: coordinateInfo || undefined,
|
|
122
|
+
nativeTelemetry: event.nativeTelemetry,
|
|
123
|
+
};
|
|
124
|
+
drainedEventCount += 1;
|
|
125
|
+
drainedMeshCount += event.meshes.length;
|
|
126
|
+
if (queuedEvents.length > 0) {
|
|
127
|
+
const shouldYield = drainedEventCount >= MAX_NATIVE_STREAM_EVENTS_PER_TURN ||
|
|
128
|
+
drainedMeshCount >= MAX_NATIVE_STREAM_MESHES_PER_TURN ||
|
|
129
|
+
performance.now() - drainStartedAt >= MAX_NATIVE_STREAM_DRAIN_MS;
|
|
130
|
+
if (shouldYield) {
|
|
131
|
+
await yieldToEventLoop();
|
|
132
|
+
drainedEventCount = 0;
|
|
133
|
+
drainedMeshCount = 0;
|
|
134
|
+
drainStartedAt = performance.now();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (streamError) {
|
|
139
|
+
throw streamError;
|
|
140
|
+
}
|
|
141
|
+
if (!completed) {
|
|
142
|
+
await new Promise((resolve) => {
|
|
143
|
+
resolvePending = resolve;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
await streamingPromise;
|
|
148
|
+
if (queueState.coalescedBatchCount > 0) {
|
|
149
|
+
console.info(`[GeometryProcessor] Coalesced ${queueState.coalescedBatchCount} native batches while JS drained the queue`);
|
|
150
|
+
}
|
|
151
|
+
const coordinateInfo = coordinator.getFinalCoordinateInfo();
|
|
152
|
+
yield { type: 'complete', totalMeshes: completedTotalMeshes ?? totalMeshes, coordinateInfo };
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=geometry-native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-native.js","sourceRoot":"","sources":["../src/geometry-native.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-worker parallel geometry processing.
|
|
3
|
+
*
|
|
4
|
+
* Spawns Web Workers that each get their own WASM instance and process
|
|
5
|
+
* disjoint slices of the geometry entity list. Batches are yielded as
|
|
6
|
+
* they arrive from any worker, enabling progressive rendering while
|
|
7
|
+
* utilizing multiple cores.
|
|
8
|
+
*/
|
|
9
|
+
import type { CoordinateHandler } from './coordinate-handler.js';
|
|
10
|
+
import type { StreamingGeometryEvent } from './index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Run the full pre-pass in a dedicated worker, then fan geometry jobs
|
|
13
|
+
* out to N workers and yield batches as they complete.
|
|
14
|
+
*
|
|
15
|
+
* @param buffer Raw IFC file bytes
|
|
16
|
+
* @param coordinator CoordinateHandler used to accumulate bounds
|
|
17
|
+
*/
|
|
18
|
+
export declare function processParallel(buffer: Uint8Array, coordinator: CoordinateHandler, sharedRtcOffset?: {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
z: number;
|
|
22
|
+
}): AsyncGenerator<StreamingGeometryEvent>;
|
|
23
|
+
//# sourceMappingURL=geometry-parallel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-parallel.d.ts","sourceRoot":"","sources":["../src/geometry-parallel.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;GAMG;AACH,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,GACpD,cAAc,CAAC,sBAAsB,CAAC,CA+MxC"}
|
|
@@ -0,0 +1,193 @@
|
|
|
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
|
+
* Run the full pre-pass in a dedicated worker, then fan geometry jobs
|
|
6
|
+
* out to N workers and yield batches as they complete.
|
|
7
|
+
*
|
|
8
|
+
* @param buffer Raw IFC file bytes
|
|
9
|
+
* @param coordinator CoordinateHandler used to accumulate bounds
|
|
10
|
+
*/
|
|
11
|
+
export async function* processParallel(buffer, coordinator, sharedRtcOffset) {
|
|
12
|
+
coordinator.reset();
|
|
13
|
+
yield { type: 'start', totalEstimate: buffer.length / 1000 };
|
|
14
|
+
yield { type: 'model-open', modelID: 0 };
|
|
15
|
+
// Copy file bytes into SharedArrayBuffer for zero-copy sharing with workers
|
|
16
|
+
const sharedBuffer = new SharedArrayBuffer(buffer.byteLength);
|
|
17
|
+
new Uint8Array(sharedBuffer).set(buffer);
|
|
18
|
+
// ── PHASE 1: Full pre-pass in worker ──
|
|
19
|
+
const makeWorker = () => new Worker(new URL('./geometry.worker.ts', import.meta.url), { type: 'module' });
|
|
20
|
+
const prePassResult = await new Promise((resolve, reject) => {
|
|
21
|
+
const w = makeWorker();
|
|
22
|
+
w.onmessage = (e) => {
|
|
23
|
+
if (e.data.type === 'prepass-result') {
|
|
24
|
+
w.terminate();
|
|
25
|
+
resolve(e.data.result);
|
|
26
|
+
}
|
|
27
|
+
else if (e.data.type === 'error') {
|
|
28
|
+
w.terminate();
|
|
29
|
+
reject(new Error(e.data.message));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
w.onerror = (e) => { w.terminate(); reject(new Error(e.message)); };
|
|
33
|
+
w.postMessage({ type: 'prepass', sharedBuffer });
|
|
34
|
+
});
|
|
35
|
+
if (!prePassResult || !prePassResult.jobs || prePassResult.totalJobs === 0) {
|
|
36
|
+
const coordinateInfo = coordinator.getFinalCoordinateInfo();
|
|
37
|
+
yield { type: 'complete', totalMeshes: 0, coordinateInfo };
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const { jobs: jobsFlat, totalJobs, unitScale, rtcOffset, needsShift, voidKeys, voidCounts, voidValues, styleIds, styleColors } = prePassResult;
|
|
41
|
+
// When a shared RTC offset is provided (2nd+ federated model), use it
|
|
42
|
+
// instead of the per-model RTC. This ensures all models share the same
|
|
43
|
+
// coordinate origin, giving pixel-perfect federation alignment.
|
|
44
|
+
const useSharedRtc = sharedRtcOffset != null;
|
|
45
|
+
const rtcX = useSharedRtc ? sharedRtcOffset.x : (rtcOffset?.[0] ?? 0);
|
|
46
|
+
const rtcY = useSharedRtc ? sharedRtcOffset.y : (rtcOffset?.[1] ?? 0);
|
|
47
|
+
const rtcZ = useSharedRtc ? sharedRtcOffset.z : (rtcOffset?.[2] ?? 0);
|
|
48
|
+
const effectiveNeedsShift = useSharedRtc ? true : needsShift;
|
|
49
|
+
yield {
|
|
50
|
+
type: 'rtcOffset',
|
|
51
|
+
rtcOffset: { x: rtcX, y: rtcY, z: rtcZ },
|
|
52
|
+
hasRtc: effectiveNeedsShift,
|
|
53
|
+
};
|
|
54
|
+
// ── PHASE 2: Dynamic worker provisioning based on device capability ──
|
|
55
|
+
const cores = typeof navigator !== 'undefined' ? (navigator.hardwareConcurrency ?? 2) : 2;
|
|
56
|
+
const deviceMemoryGB = typeof navigator !== 'undefined' ? (navigator.deviceMemory ?? 8) : 8;
|
|
57
|
+
const fileSizeGB = buffer.byteLength / (1024 * 1024 * 1024);
|
|
58
|
+
// Determine optimal workers:
|
|
59
|
+
// - Desktop (16+ cores, 16+ GB): up to 8 workers
|
|
60
|
+
// - Laptop (8 cores, 8 GB): 2-4 workers (avoid thermal throttling on fanless)
|
|
61
|
+
// - Low-end (4 cores, 4 GB): 1-2 workers
|
|
62
|
+
// - Large files need more memory per worker, so fewer workers
|
|
63
|
+
let maxWorkers;
|
|
64
|
+
if (cores >= 16 && deviceMemoryGB >= 16) {
|
|
65
|
+
maxWorkers = Math.min(8, Math.floor(cores / 2));
|
|
66
|
+
}
|
|
67
|
+
else if (cores >= 8 && deviceMemoryGB >= 8) {
|
|
68
|
+
// MacBook Air M-series: 8 cores but fanless → throttles with too many workers
|
|
69
|
+
// Use 3 workers: enough parallelism without severe throttling
|
|
70
|
+
maxWorkers = fileSizeGB > 0.5 ? 2 : 3;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
maxWorkers = Math.max(1, Math.min(2, Math.floor(cores / 2)));
|
|
74
|
+
}
|
|
75
|
+
const workerCount = Math.min(maxWorkers, totalJobs);
|
|
76
|
+
const jobsPerWorker = Math.ceil(totalJobs / workerCount);
|
|
77
|
+
const chunks = [];
|
|
78
|
+
for (let i = 0; i < workerCount; i++) {
|
|
79
|
+
const start = i * jobsPerWorker;
|
|
80
|
+
const end = Math.min(start + jobsPerWorker, totalJobs);
|
|
81
|
+
if (start < end)
|
|
82
|
+
chunks.push([start, end]);
|
|
83
|
+
}
|
|
84
|
+
// Queue-based async generator: workers push batches, generator yields them
|
|
85
|
+
const batchQueue = [];
|
|
86
|
+
let resolveWaiting = null;
|
|
87
|
+
let workersCompleted = 0;
|
|
88
|
+
let totalMeshes = 0;
|
|
89
|
+
let workerError = null;
|
|
90
|
+
const workers = [];
|
|
91
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
92
|
+
const [jobStart, jobEnd] = chunks[i];
|
|
93
|
+
if (jobStart >= jobEnd) {
|
|
94
|
+
workersCompleted++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const workerJobs = jobsFlat.slice(jobStart * 3, jobEnd * 3);
|
|
98
|
+
const worker = new Worker(new URL('./geometry.worker.ts', import.meta.url), { type: 'module' });
|
|
99
|
+
workers.push(worker);
|
|
100
|
+
worker.onmessage = (e) => {
|
|
101
|
+
const msg = e.data;
|
|
102
|
+
if (msg.type === 'batch') {
|
|
103
|
+
// Convert transferable data back to MeshData[]
|
|
104
|
+
const meshes = msg.meshes.map((m) => ({
|
|
105
|
+
expressId: m.expressId,
|
|
106
|
+
ifcType: m.ifcType,
|
|
107
|
+
positions: m.positions instanceof Float32Array ? m.positions : new Float32Array(m.positions),
|
|
108
|
+
normals: m.normals instanceof Float32Array ? m.normals : new Float32Array(m.normals),
|
|
109
|
+
indices: m.indices instanceof Uint32Array ? m.indices : new Uint32Array(m.indices),
|
|
110
|
+
color: m.color,
|
|
111
|
+
}));
|
|
112
|
+
if (meshes.length > 0) {
|
|
113
|
+
batchQueue.push(meshes);
|
|
114
|
+
if (resolveWaiting) {
|
|
115
|
+
resolveWaiting();
|
|
116
|
+
resolveWaiting = null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (msg.type === 'complete') {
|
|
121
|
+
totalMeshes += msg.totalMeshes;
|
|
122
|
+
workersCompleted++;
|
|
123
|
+
worker.terminate();
|
|
124
|
+
if (resolveWaiting) {
|
|
125
|
+
resolveWaiting();
|
|
126
|
+
resolveWaiting = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (msg.type === 'error') {
|
|
130
|
+
workerError = new Error(`Geometry worker error: ${msg.message}`);
|
|
131
|
+
workersCompleted++;
|
|
132
|
+
worker.terminate();
|
|
133
|
+
if (resolveWaiting) {
|
|
134
|
+
resolveWaiting();
|
|
135
|
+
resolveWaiting = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
worker.onerror = (e) => {
|
|
140
|
+
workerError = new Error(`Geometry worker failed: ${e.message}`);
|
|
141
|
+
workersCompleted++;
|
|
142
|
+
worker.terminate();
|
|
143
|
+
if (resolveWaiting) {
|
|
144
|
+
resolveWaiting();
|
|
145
|
+
resolveWaiting = null;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
// Send work — sharedBuffer is zero-copy, typed arrays are transferred
|
|
149
|
+
worker.postMessage({
|
|
150
|
+
type: 'process',
|
|
151
|
+
sharedBuffer,
|
|
152
|
+
jobsFlat: workerJobs,
|
|
153
|
+
unitScale,
|
|
154
|
+
rtcX, rtcY, rtcZ,
|
|
155
|
+
needsShift: effectiveNeedsShift,
|
|
156
|
+
voidKeys, voidCounts, voidValues,
|
|
157
|
+
styleIds, styleColors,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// Yield batches as they arrive from any worker
|
|
161
|
+
while (true) {
|
|
162
|
+
while (batchQueue.length > 0) {
|
|
163
|
+
const batch = batchQueue.shift();
|
|
164
|
+
coordinator.processMeshesIncremental(batch);
|
|
165
|
+
const coordinateInfo = coordinator.getCurrentCoordinateInfo();
|
|
166
|
+
yield {
|
|
167
|
+
type: 'batch',
|
|
168
|
+
meshes: batch,
|
|
169
|
+
totalSoFar: totalMeshes,
|
|
170
|
+
coordinateInfo: coordinateInfo || undefined,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (workerError) {
|
|
174
|
+
// Terminate remaining workers
|
|
175
|
+
for (const w of workers) {
|
|
176
|
+
try {
|
|
177
|
+
w.terminate();
|
|
178
|
+
}
|
|
179
|
+
catch { /* cleanup — safe to ignore */ }
|
|
180
|
+
}
|
|
181
|
+
throw workerError;
|
|
182
|
+
}
|
|
183
|
+
if (workersCompleted >= chunks.length && batchQueue.length === 0) {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
await new Promise((resolve) => {
|
|
187
|
+
resolveWaiting = resolve;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const coordinateInfo = coordinator.getFinalCoordinateInfo();
|
|
191
|
+
yield { type: 'complete', totalMeshes, coordinateInfo };
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=geometry-parallel.js.map
|