@ifc-lite/geometry 7.3.0 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,233 @@
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
+ const ZERO_OFFSET = { x: 0, y: 0, z: 0 };
5
+ const EPSILON = 1e-12;
6
+ function finite(values) {
7
+ return values.every(Number.isFinite);
8
+ }
9
+ function usableFrame(frame) {
10
+ const horizontal = new Set(frame.axes.filter((axis) => axis !== 'up' && axis !== 'down'));
11
+ const vertical = frame.axes.filter((axis) => axis === 'up' || axis === 'down');
12
+ const hasEastWest = [...horizontal].some((axis) => axis === 'east' || axis === 'west');
13
+ const hasNorthSouth = [...horizontal].some((axis) => axis === 'north' || axis === 'south');
14
+ return horizontal.size === 2 && hasEastWest && hasNorthSouth && vertical.length === 1
15
+ && finite([frame.horizontalUnitToMetres, frame.verticalUnitToMetres])
16
+ // Direction belongs exclusively in `axes`; a unit-to-metre factor cannot
17
+ // be negative without silently reflecting a source a second time.
18
+ && frame.horizontalUnitToMetres >= EPSILON
19
+ && frame.verticalUnitToMetres >= EPSILON;
20
+ }
21
+ /** Convert a source-native point to the renderer's East/Up/South metre frame. */
22
+ function sourceToViewer(frame, point) {
23
+ if (!usableFrame(frame) || !finite(point))
24
+ return null;
25
+ let east;
26
+ let up;
27
+ let south;
28
+ for (let index = 0; index < 3; index++) {
29
+ const axis = frame.axes[index];
30
+ const value = point[index];
31
+ switch (axis) {
32
+ case 'east':
33
+ east = value * frame.horizontalUnitToMetres;
34
+ break;
35
+ case 'west':
36
+ east = -value * frame.horizontalUnitToMetres;
37
+ break;
38
+ case 'up':
39
+ up = value * frame.verticalUnitToMetres;
40
+ break;
41
+ case 'down':
42
+ up = -value * frame.verticalUnitToMetres;
43
+ break;
44
+ case 'south':
45
+ south = value * frame.horizontalUnitToMetres;
46
+ break;
47
+ case 'north':
48
+ south = -value * frame.horizontalUnitToMetres;
49
+ break;
50
+ }
51
+ }
52
+ return east === undefined || up === undefined || south === undefined ? null : [east, up, south];
53
+ }
54
+ /** Convert a renderer East/Up/South metre point to source-native coordinates. */
55
+ function viewerToSource(frame, point) {
56
+ if (!usableFrame(frame) || !finite(point))
57
+ return null;
58
+ const values = frame.axes.map((axis) => {
59
+ switch (axis) {
60
+ case 'east': return point[0] / frame.horizontalUnitToMetres;
61
+ case 'west': return -point[0] / frame.horizontalUnitToMetres;
62
+ case 'up': return point[1] / frame.verticalUnitToMetres;
63
+ case 'down': return -point[1] / frame.verticalUnitToMetres;
64
+ case 'south': return point[2] / frame.horizontalUnitToMetres;
65
+ case 'north': return -point[2] / frame.horizontalUnitToMetres;
66
+ }
67
+ });
68
+ return finite(values) ? [values[0], values[1], values[2]] : null;
69
+ }
70
+ function normalizedAxis(operation) {
71
+ const length = Math.hypot(operation.xAxisAbscissa, operation.xAxisOrdinate);
72
+ if (!(Number.isFinite(length) && length >= EPSILON))
73
+ return null;
74
+ return { a: operation.xAxisAbscissa / length, b: operation.xAxisOrdinate / length };
75
+ }
76
+ function usableOperation(operation) {
77
+ return operation !== undefined
78
+ && operation.kind === 'local-projected-affine'
79
+ && finite([
80
+ operation.eastings, operation.northings, operation.orthogonalHeight,
81
+ operation.xAxisAbscissa, operation.xAxisOrdinate,
82
+ operation.scaleX, operation.scaleY, operation.scaleZ,
83
+ ])
84
+ && Math.abs(operation.scaleX) >= EPSILON
85
+ && Math.abs(operation.scaleY) >= EPSILON
86
+ && Math.abs(operation.scaleZ) >= EPSILON
87
+ && normalizedAxis(operation) !== null;
88
+ }
89
+ function sameCrs(a, b) {
90
+ return a !== undefined && b !== undefined && a.id === b.id;
91
+ }
92
+ function verticalCompatible(source, target, policy) {
93
+ if (source.vertical && target.vertical)
94
+ return source.vertical.id === target.vertical.id ? null : 'vertical-crs-mismatch';
95
+ if (policy === 'assume-compatible')
96
+ return null;
97
+ return 'vertical-crs-unknown';
98
+ }
99
+ /** Apply a spatial affine transform without narrowing the result. */
100
+ export function applySpatialPlacement(transform, x, y, z) {
101
+ return [
102
+ transform.m00 * x + transform.m01 * y + transform.m02 * z + transform.tx,
103
+ transform.m10 * x + transform.m11 * y + transform.m12 * z + transform.ty,
104
+ transform.m20 * x + transform.m21 * y + transform.m22 * z + transform.tz,
105
+ ];
106
+ }
107
+ /** Map a source-native point to projected East/North/height in f64. */
108
+ export function localViewerToProjected(reference, point, frameOffset = ZERO_OFFSET) {
109
+ if (!usableFrame(reference.source) || !usableOperation(reference.localToProjected)
110
+ || !finite([point[0], point[1], point[2], frameOffset.x, frameOffset.y, frameOffset.z]))
111
+ return null;
112
+ const operation = reference.localToProjected;
113
+ const axis = normalizedAxis(operation);
114
+ if (!axis)
115
+ return null;
116
+ // Renderer coordinates are East, Up, South. IFC/Y-up geometry's Z is the
117
+ // negated northing, hence the signs are intentionally asymmetric.
118
+ const viewer = sourceToViewer(reference.source, point);
119
+ const viewerOffset = sourceToViewer(reference.source, [frameOffset.x, frameOffset.y, frameOffset.z]);
120
+ if (!viewer || !viewerOffset)
121
+ return null;
122
+ const x = viewer[0] + viewerOffset[0];
123
+ const y = viewer[1] + viewerOffset[1];
124
+ const south = viewer[2] + viewerOffset[2];
125
+ return [
126
+ operation.eastings + operation.scaleX * axis.a * x + operation.scaleY * axis.b * south,
127
+ operation.northings + operation.scaleX * axis.b * x - operation.scaleY * axis.a * south,
128
+ operation.orthogonalHeight + operation.scaleZ * y,
129
+ ];
130
+ }
131
+ /** Map projected East/North/height to a source-native point in f64. */
132
+ export function projectedToLocalViewer(reference, point, frameOffset = ZERO_OFFSET) {
133
+ if (!usableFrame(reference.source) || !usableOperation(reference.localToProjected)
134
+ || !finite([point[0], point[1], point[2], frameOffset.x, frameOffset.y, frameOffset.z]))
135
+ return null;
136
+ const operation = reference.localToProjected;
137
+ const axis = normalizedAxis(operation);
138
+ if (!axis)
139
+ return null;
140
+ const de = point[0] - operation.eastings;
141
+ const dn = point[1] - operation.northings;
142
+ const x = (axis.a * de + axis.b * dn) / operation.scaleX;
143
+ const south = (axis.b * de - axis.a * dn) / operation.scaleY;
144
+ const y = (point[2] - operation.orthogonalHeight) / operation.scaleZ;
145
+ const source = viewerToSource(reference.source, [x, y, south]);
146
+ if (!source)
147
+ return null;
148
+ // Frame offsets live in the reference's NATIVE source coordinates, just as
149
+ // the point this function returns does. Treating the offset as viewer axes
150
+ // and converting it a second time happened to work for IFC's viewer-shaped
151
+ // metre frame, but corrupts a feet/North-East-Down adapter's inverse.
152
+ const result = [
153
+ source[0] - frameOffset.x, source[1] - frameOffset.y, source[2] - frameOffset.z,
154
+ ];
155
+ return finite(result) ? result : null;
156
+ }
157
+ /**
158
+ * Resolve a same-horizontal-CRS source placement into a target's renderer
159
+ * frame. Cross-CRS transforms require an explicit operation provider and are
160
+ * intentionally refused here rather than guessed from matching display names.
161
+ */
162
+ export function resolveSpatialPlacement(source, target, options = {}) {
163
+ if (!usableFrame(source.source) || !usableFrame(target.source)
164
+ || !usableOperation(source.localToProjected) || !usableOperation(target.localToProjected)) {
165
+ return { ok: false, refusal: 'invalid-axis-or-unit' };
166
+ }
167
+ if (!source.horizontal || !target.horizontal)
168
+ return { ok: false, refusal: 'missing-horizontal-crs' };
169
+ if (!sameCrs(source.horizontal, target.horizontal))
170
+ return { ok: false, refusal: 'crs-mismatch' };
171
+ const vertical = verticalCompatible(source, target, options.unknownVertical ?? 'refuse');
172
+ if (vertical)
173
+ return { ok: false, refusal: vertical };
174
+ const sourceOperation = source.localToProjected;
175
+ const targetOperation = target.localToProjected;
176
+ const sourceAxis = normalizedAxis(sourceOperation);
177
+ const targetAxis = normalizedAxis(targetOperation);
178
+ const targetOffset = options.targetFrameOffset ?? ZERO_OFFSET;
179
+ const sourceOffset = options.sourceFrameOffset ?? ZERO_OFFSET;
180
+ if (!finite([
181
+ sourceOffset.x, sourceOffset.y, sourceOffset.z,
182
+ targetOffset.x, targetOffset.y, targetOffset.z,
183
+ ]))
184
+ return { ok: false, refusal: 'invalid-axis-or-unit' };
185
+ // The origin goes through the public f64 map/inverse functions because it
186
+ // contains both map and frame offsets. The linear columns must NOT be
187
+ // derived by subtracting two ~million-metre projected points: that loses
188
+ // meaningful sub-millimetre precision before the target inverse sees them.
189
+ // Compose just the linear map terms below, then convert the target viewer
190
+ // deltas back to its native axes/units. This retains the deliberately
191
+ // asymmetric South axis without finite-differencing large coordinates.
192
+ const origin = localViewerToProjected(source, [0, 0, 0], sourceOffset);
193
+ if (!origin)
194
+ return { ok: false, refusal: 'non-invertible-transform' };
195
+ const p0 = projectedToLocalViewer(target, origin, targetOffset);
196
+ if (!p0)
197
+ return { ok: false, refusal: 'non-invertible-transform' };
198
+ const sourceToTargetDelta = (sourceNative) => {
199
+ const sourceViewer = sourceToViewer(source.source, sourceNative);
200
+ if (!sourceViewer)
201
+ return null;
202
+ const [east, up, south] = sourceViewer;
203
+ const deltaEast = sourceOperation.scaleX * sourceAxis.a * east + sourceOperation.scaleY * sourceAxis.b * south;
204
+ const deltaNorth = sourceOperation.scaleX * sourceAxis.b * east - sourceOperation.scaleY * sourceAxis.a * south;
205
+ const deltaHeight = sourceOperation.scaleZ * up;
206
+ const targetViewer = [
207
+ (targetAxis.a * deltaEast + targetAxis.b * deltaNorth) / targetOperation.scaleX,
208
+ deltaHeight / targetOperation.scaleZ,
209
+ (targetAxis.b * deltaEast - targetAxis.a * deltaNorth) / targetOperation.scaleY,
210
+ ];
211
+ return viewerToSource(target.source, targetViewer);
212
+ };
213
+ const px = sourceToTargetDelta([1, 0, 0]);
214
+ const py = sourceToTargetDelta([0, 1, 0]);
215
+ const pz = sourceToTargetDelta([0, 0, 1]);
216
+ if (!px || !py || !pz)
217
+ return { ok: false, refusal: 'non-invertible-transform' };
218
+ const transform = {
219
+ m00: px[0], m01: py[0], m02: pz[0], tx: p0[0],
220
+ m10: px[1], m11: py[1], m12: pz[1], ty: p0[1],
221
+ m20: px[2], m21: py[2], m22: pz[2], tz: p0[2],
222
+ };
223
+ if (!finite(Object.values(transform)))
224
+ return { ok: false, refusal: 'non-invertible-transform' };
225
+ const identity = Math.abs(transform.m00 - 1) < 1e-12 && Math.abs(transform.m11 - 1) < 1e-12
226
+ && Math.abs(transform.m22 - 1) < 1e-12 && Math.abs(transform.m01) < 1e-12
227
+ && Math.abs(transform.m02) < 1e-12 && Math.abs(transform.m10) < 1e-12
228
+ && Math.abs(transform.m12) < 1e-12 && Math.abs(transform.m20) < 1e-12
229
+ && Math.abs(transform.m21) < 1e-12 && Math.abs(transform.tx) < 1e-12
230
+ && Math.abs(transform.ty) < 1e-12 && Math.abs(transform.tz) < 1e-12;
231
+ return { ok: true, placement: { status: identity ? 'identity' : 'same-crs', sourceToFederation: transform, source, target } };
232
+ }
233
+ //# sourceMappingURL=spatial-reference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatial-reference.js","sourceRoot":"","sources":["../src/spatial-reference.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import type { GeometryDiagnostics } from './diagnostics.js';
2
+ /**
3
+ * Which pre-worker pipeline phase (#4902) is still outstanding. Production
4
+ * "Geometry stream stalled... Last rendered meshes: 0" reports (#4884
5
+ * follow-up) cannot currently be told apart by phase — this is what makes
6
+ * that possible: read with {@link GateTracker.getStallPhase} at the instant
7
+ * the consumer's watchdog fires, rather than inferred from elapsed time.
8
+ */
9
+ export type StallPhase = 'prepass' | 'shard-scan' | 'styles-gate' | 'entity-index-gate' | 'workers';
10
+ /**
11
+ * Handed in via `ProcessParallelOptions.stallPhaseHandle` (#4902) and filled
12
+ * in synchronously by the pool with a live `getStallPhase()` reader. Absent
13
+ * `getStallPhase` (before the pool runs, or on a code path that doesn't wire
14
+ * one) is the caller's signal that no phase is known yet.
15
+ */
16
+ export interface StallPhaseHandle {
17
+ getStallPhase?: () => StallPhase;
18
+ }
19
+ /**
20
+ * Tracks which pre-worker gate(s) `geometry-parallel.ts` is still waiting on:
21
+ * the streaming pre-pass itself, the SPIKE sharded entity-index scan
22
+ * (`scan-shard` / `shardResultsRemaining`), and the two chunk-dispatch gates
23
+ * (`styles` and `entity-index` — see `dispatchJobsChunk`). A new sibling
24
+ * module (not `geometry-parallel.ts`, which is size-allowlisted and must not
25
+ * grow) so the gate state has one place a consumer can read it from.
26
+ *
27
+ * Precedence when more than one gate is open at once: the EARLIEST phase in
28
+ * the pipeline wins, because it is the one actually responsible for every
29
+ * later phase staying open too (a stuck shard scan means neither the prepass
30
+ * nor either dispatch gate can ever resolve).
31
+ */
32
+ export declare class GateTracker {
33
+ private shardScanOpen;
34
+ private prepassOpen;
35
+ private stylesOpen;
36
+ private entityIndexOpen;
37
+ /** The SPIKE sharded scan dispatched shard-scan to the workers. */
38
+ markShardScanStarted(): void;
39
+ /** Every shard result is in (stitched or fell back to the serial pre-pass). */
40
+ markShardScanDone(): void;
41
+ /** The pre-pass worker's `complete` event arrived. */
42
+ markPrepassDone(): void;
43
+ /** The `styles` event (serial or the sharded finalize's synthesized one)
44
+ * reached `dispatchJobsChunk`'s gate. */
45
+ markStylesReceived(): void;
46
+ /** The entity index (prepass or sharded-early) reached the same gate. */
47
+ markEntityIndexReceived(): void;
48
+ /** Which phase a consumer's watchdog should attribute a stall to right now. */
49
+ getStallPhase(): StallPhase;
50
+ }
51
+ /**
52
+ * A `getStallPhase` reader that returns a fixed `phase` and closes over
53
+ * NOTHING else (#4979 review). `processParallel`'s `finally` re-seats the
54
+ * caller-owned `stallPhaseHandle` to one of these on teardown: the LIVE
55
+ * reader it installed at the start (`() => gateTracker.getStallPhase()`)
56
+ * closes over `gateTracker`, which — through the rest of that generator's
57
+ * scope — keeps the per-load `sharedBuffer` reachable for as long as the
58
+ * caller holds the handle, even after every worker is gone. A snapshot taken
59
+ * at teardown is exactly as useful for a watchdog that reads it afterwards
60
+ * (the phase cannot change once nothing is left running) without the retention.
61
+ */
62
+ export declare function frozenStallPhase(phase: StallPhase): () => StallPhase;
63
+ /** Bound (ms) for one pre-worker single-reply phase, scaled by file size. */
64
+ export declare function preWorkerPhaseBoundMs(fileSizeMB: number): number;
65
+ /**
66
+ * Registry for the pre-worker phase-bound timers (#4979 review). A plain
67
+ * `setTimeout` that is never cancelled keeps its own closure — and whatever
68
+ * it closes over, including the per-load `sharedBuffer` — alive for the FULL
69
+ * bound even after the load completes, fails, or is superseded; on a large
70
+ * file that is real memory held for up to 15s + 30ms/MB for no reason. `arm`
71
+ * tracks the handle and self-removes it once it actually fires; `clearAll` is
72
+ * for the generator's own teardown (`finally`, which also runs on `.return()`
73
+ * / abort), which normally fires first.
74
+ */
75
+ export declare class PhaseBoundTimers {
76
+ private readonly handles;
77
+ /**
78
+ * Arm a bounded wait for a pre-worker phase that depends on a single
79
+ * worker's reply — shard scan, a style slice, styles finalize (#4902).
80
+ * Fires `onTimeout` once, only if `isSettled()` is still false when the
81
+ * bound elapses, so a genuine reply arriving right at the boundary (which
82
+ * flips the call site's own settled flag before this checks it) always
83
+ * wins.
84
+ */
85
+ arm(fileSizeMB: number, isSettled: () => boolean, onTimeout: () => void): void;
86
+ /** Cancel every timer still pending — the load exited before its bound. */
87
+ clearAll(): void;
88
+ }
89
+ /**
90
+ * The synthetic prepass-stream `styles` event an older engine binary that
91
+ * never resolves any styles already produces byte-identically (default
92
+ * per-type colours, no voids). Reused as the #4902 finalize-timeout
93
+ * fallback: worker[0]'s `styles-final` reply feeds the exact same path
94
+ * (see `geometry-parallel.ts`'s `msg.type === 'styles-final'` handler), so a
95
+ * degraded stream degrades to a shape the pool already handles rather than a
96
+ * new one.
97
+ */
98
+ export declare function emptyStylesPrepassEvent(): {
99
+ type: 'prepass-stream';
100
+ event: Record<string, unknown>;
101
+ };
102
+ /**
103
+ * A `GeometryDiagnostics` fragment carrying exactly one typed pre-worker-phase
104
+ * failure (#4902), for `mergeGeometryDiagnostics`. Every counter besides
105
+ * `failuresByReason` is zero, so merging this in never fabricates CSG
106
+ * activity that did not happen. `schemaVersion: 0` — this is a host-
107
+ * synthesized fragment, not a producer-versioned wasm payload, and
108
+ * `mergeGeometryDiagnostics` takes the max, so it never lowers a real one.
109
+ */
110
+ export declare function preWorkerPhaseFailureDiagnostics(reason: string): GeometryDiagnostics;
111
+ //# sourceMappingURL=stall-phase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stall-phase.d.ts","sourceRoot":"","sources":["../src/stall-phase.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,YAAY,GACZ,aAAa,GACb,mBAAmB,GACnB,SAAS,CAAC;AAEd;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,EAAE,MAAM,UAAU,CAAC;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,eAAe,CAAQ;IAE/B,mEAAmE;IACnE,oBAAoB,IAAI,IAAI;IAI5B,+EAA+E;IAC/E,iBAAiB,IAAI,IAAI;IAIzB,sDAAsD;IACtD,eAAe,IAAI,IAAI;IAIvB;8CAC0C;IAC1C,kBAAkB,IAAI,IAAI;IAI1B,yEAAyE;IACzE,uBAAuB,IAAI,IAAI;IAI/B,+EAA+E;IAC/E,aAAa,IAAI,UAAU;CAO5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,UAAU,CAEpE;AAUD,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4C;IAEpE;;;;;;;OAOG;IACH,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,GAAG,IAAI;IAQ9E,2EAA2E;IAC3E,QAAQ,IAAI,IAAI;CAIjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,IAAI;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CASpG;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CAoBpF"}
@@ -0,0 +1,164 @@
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
+ * Tracks which pre-worker gate(s) `geometry-parallel.ts` is still waiting on:
6
+ * the streaming pre-pass itself, the SPIKE sharded entity-index scan
7
+ * (`scan-shard` / `shardResultsRemaining`), and the two chunk-dispatch gates
8
+ * (`styles` and `entity-index` — see `dispatchJobsChunk`). A new sibling
9
+ * module (not `geometry-parallel.ts`, which is size-allowlisted and must not
10
+ * grow) so the gate state has one place a consumer can read it from.
11
+ *
12
+ * Precedence when more than one gate is open at once: the EARLIEST phase in
13
+ * the pipeline wins, because it is the one actually responsible for every
14
+ * later phase staying open too (a stuck shard scan means neither the prepass
15
+ * nor either dispatch gate can ever resolve).
16
+ */
17
+ export class GateTracker {
18
+ shardScanOpen = false;
19
+ prepassOpen = true;
20
+ stylesOpen = true;
21
+ entityIndexOpen = true;
22
+ /** The SPIKE sharded scan dispatched shard-scan to the workers. */
23
+ markShardScanStarted() {
24
+ this.shardScanOpen = true;
25
+ }
26
+ /** Every shard result is in (stitched or fell back to the serial pre-pass). */
27
+ markShardScanDone() {
28
+ this.shardScanOpen = false;
29
+ }
30
+ /** The pre-pass worker's `complete` event arrived. */
31
+ markPrepassDone() {
32
+ this.prepassOpen = false;
33
+ }
34
+ /** The `styles` event (serial or the sharded finalize's synthesized one)
35
+ * reached `dispatchJobsChunk`'s gate. */
36
+ markStylesReceived() {
37
+ this.stylesOpen = false;
38
+ }
39
+ /** The entity index (prepass or sharded-early) reached the same gate. */
40
+ markEntityIndexReceived() {
41
+ this.entityIndexOpen = false;
42
+ }
43
+ /** Which phase a consumer's watchdog should attribute a stall to right now. */
44
+ getStallPhase() {
45
+ if (this.shardScanOpen)
46
+ return 'shard-scan';
47
+ if (this.prepassOpen)
48
+ return 'prepass';
49
+ if (this.stylesOpen)
50
+ return 'styles-gate';
51
+ if (this.entityIndexOpen)
52
+ return 'entity-index-gate';
53
+ return 'workers';
54
+ }
55
+ }
56
+ /**
57
+ * A `getStallPhase` reader that returns a fixed `phase` and closes over
58
+ * NOTHING else (#4979 review). `processParallel`'s `finally` re-seats the
59
+ * caller-owned `stallPhaseHandle` to one of these on teardown: the LIVE
60
+ * reader it installed at the start (`() => gateTracker.getStallPhase()`)
61
+ * closes over `gateTracker`, which — through the rest of that generator's
62
+ * scope — keeps the per-load `sharedBuffer` reachable for as long as the
63
+ * caller holds the handle, even after every worker is gone. A snapshot taken
64
+ * at teardown is exactly as useful for a watchdog that reads it afterwards
65
+ * (the phase cannot change once nothing is left running) without the retention.
66
+ */
67
+ export function frozenStallPhase(phase) {
68
+ return () => phase;
69
+ }
70
+ // Bound for a pre-worker phase that waits on a single worker's reply (a shard
71
+ // scan, one style slice, styles finalize). Mirrors the desktop first-batch
72
+ // floor/ramp in watchdog.ts (15_000 + MB*30) rather than the browser one
73
+ // (30_000 + MB*60): several of these bounds can be outstanding in the same
74
+ // load, so each gets the smaller half.
75
+ const PRE_WORKER_PHASE_FLOOR_MS = 15_000;
76
+ const PRE_WORKER_PHASE_PER_MB_MS = 30;
77
+ /** Bound (ms) for one pre-worker single-reply phase, scaled by file size. */
78
+ export function preWorkerPhaseBoundMs(fileSizeMB) {
79
+ return Math.max(PRE_WORKER_PHASE_FLOOR_MS, Math.round(PRE_WORKER_PHASE_FLOOR_MS + Math.max(0, fileSizeMB) * PRE_WORKER_PHASE_PER_MB_MS));
80
+ }
81
+ /**
82
+ * Registry for the pre-worker phase-bound timers (#4979 review). A plain
83
+ * `setTimeout` that is never cancelled keeps its own closure — and whatever
84
+ * it closes over, including the per-load `sharedBuffer` — alive for the FULL
85
+ * bound even after the load completes, fails, or is superseded; on a large
86
+ * file that is real memory held for up to 15s + 30ms/MB for no reason. `arm`
87
+ * tracks the handle and self-removes it once it actually fires; `clearAll` is
88
+ * for the generator's own teardown (`finally`, which also runs on `.return()`
89
+ * / abort), which normally fires first.
90
+ */
91
+ export class PhaseBoundTimers {
92
+ handles = new Set();
93
+ /**
94
+ * Arm a bounded wait for a pre-worker phase that depends on a single
95
+ * worker's reply — shard scan, a style slice, styles finalize (#4902).
96
+ * Fires `onTimeout` once, only if `isSettled()` is still false when the
97
+ * bound elapses, so a genuine reply arriving right at the boundary (which
98
+ * flips the call site's own settled flag before this checks it) always
99
+ * wins.
100
+ */
101
+ arm(fileSizeMB, isSettled, onTimeout) {
102
+ const handle = setTimeout(() => {
103
+ this.handles.delete(handle);
104
+ if (!isSettled())
105
+ onTimeout();
106
+ }, preWorkerPhaseBoundMs(fileSizeMB));
107
+ this.handles.add(handle);
108
+ }
109
+ /** Cancel every timer still pending — the load exited before its bound. */
110
+ clearAll() {
111
+ for (const handle of this.handles)
112
+ clearTimeout(handle);
113
+ this.handles.clear();
114
+ }
115
+ }
116
+ /**
117
+ * The synthetic prepass-stream `styles` event an older engine binary that
118
+ * never resolves any styles already produces byte-identically (default
119
+ * per-type colours, no voids). Reused as the #4902 finalize-timeout
120
+ * fallback: worker[0]'s `styles-final` reply feeds the exact same path
121
+ * (see `geometry-parallel.ts`'s `msg.type === 'styles-final'` handler), so a
122
+ * degraded stream degrades to a shape the pool already handles rather than a
123
+ * new one.
124
+ */
125
+ export function emptyStylesPrepassEvent() {
126
+ return {
127
+ type: 'prepass-stream',
128
+ event: {
129
+ type: 'styles',
130
+ styleIds: new Uint32Array(0), styleColors: new Uint8Array(0),
131
+ voidKeys: new Uint32Array(0), voidCounts: new Uint32Array(0), voidValues: new Uint32Array(0),
132
+ },
133
+ };
134
+ }
135
+ /**
136
+ * A `GeometryDiagnostics` fragment carrying exactly one typed pre-worker-phase
137
+ * failure (#4902), for `mergeGeometryDiagnostics`. Every counter besides
138
+ * `failuresByReason` is zero, so merging this in never fabricates CSG
139
+ * activity that did not happen. `schemaVersion: 0` — this is a host-
140
+ * synthesized fragment, not a producer-versioned wasm payload, and
141
+ * `mergeGeometryDiagnostics` takes the max, so it never lowers a real one.
142
+ */
143
+ export function preWorkerPhaseFailureDiagnostics(reason) {
144
+ return {
145
+ schemaVersion: 0,
146
+ totalCsgFailures: 0,
147
+ productsWithFailures: 0,
148
+ hostsWithOpenings: 0,
149
+ classification: { rectangular: 0, diagonal: 0, nonRectangular: 0, total: 0 },
150
+ failuresByReason: [{ reason, count: 1 }],
151
+ silentNoOps: 0,
152
+ rectFast: {
153
+ fired: 0,
154
+ openingsCut: 0,
155
+ deferHostNotBox: 0,
156
+ deferNotThrough: 0,
157
+ deferOffFace: 0,
158
+ deferNearEdge: 0,
159
+ deferNoOpenings: 0,
160
+ },
161
+ worstHosts: [],
162
+ };
163
+ }
164
+ //# sourceMappingURL=stall-phase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stall-phase.js","sourceRoot":"","sources":["../src/stall-phase.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ifc-lite/geometry",
3
- "version": "7.3.0",
3
+ "version": "7.5.0",
4
4
  "description": "Geometry processing bridge for IFC-Lite - exact-arithmetic CSG, streamed across workers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,8 +28,8 @@
28
28
  }
29
29
  },
30
30
  "dependencies": {
31
- "@ifc-lite/data": "^4.5.0",
32
- "@ifc-lite/wasm": "^9.1.0",
31
+ "@ifc-lite/data": "^5.0.0",
32
+ "@ifc-lite/wasm": "^9.3.0",
33
33
  "@ifc-lite/wasm-lifecycle": "^0.2.0"
34
34
  },
35
35
  "optionalDependencies": {
@@ -38,7 +38,7 @@
38
38
  "devDependencies": {
39
39
  "@tauri-apps/api": "^2.11.1",
40
40
  "typescript": "^6.0.3",
41
- "vitest": "^4.1.11"
41
+ "vitest": "^5.0.1"
42
42
  },
43
43
  "license": "MPL-2.0",
44
44
  "author": "Louis True",