@bldrs-ai/conway 1.536.1507 → 1.543.1513

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.
Files changed (49) hide show
  1. package/compiled/examples/browser-bundled.cjs +39 -5
  2. package/compiled/examples/cli-bundled.cjs +197 -22
  3. package/compiled/examples/cli-step-bundled.cjs +39 -5
  4. package/compiled/examples/validator-bundled.cjs +39 -5
  5. package/compiled/src/compat/web-ifc/geometry_shard_coordination.test.d.ts +2 -0
  6. package/compiled/src/compat/web-ifc/geometry_shard_coordination.test.d.ts.map +1 -0
  7. package/compiled/src/compat/web-ifc/geometry_shard_coordination.test.js +236 -0
  8. package/compiled/src/compat/web-ifc/ifc_api.d.ts +26 -0
  9. package/compiled/src/compat/web-ifc/ifc_api.d.ts.map +1 -1
  10. package/compiled/src/compat/web-ifc/ifc_api.js +33 -0
  11. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough.d.ts +6 -0
  12. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough.d.ts.map +1 -1
  13. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.js +264 -1
  14. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts +116 -5
  15. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts.map +1 -1
  16. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.js +256 -85
  17. package/compiled/src/compat/web-ifc/store_preview_channel.d.ts +79 -0
  18. package/compiled/src/compat/web-ifc/store_preview_channel.d.ts.map +1 -1
  19. package/compiled/src/compat/web-ifc/store_preview_channel.js +237 -14
  20. package/compiled/src/compat/web-ifc/store_preview_channel.test.js +270 -1
  21. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts +106 -1
  22. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts.map +1 -1
  23. package/compiled/src/compat/web-ifc/streamed_preview_channel.js +269 -17
  24. package/compiled/src/core/progress_log.d.ts +75 -2
  25. package/compiled/src/core/progress_log.d.ts.map +1 -1
  26. package/compiled/src/core/progress_log.js +59 -2
  27. package/compiled/src/core/progress_log.test.js +75 -1
  28. package/compiled/src/ifc/dangling_placement_error.d.ts +35 -0
  29. package/compiled/src/ifc/dangling_placement_error.d.ts.map +1 -0
  30. package/compiled/src/ifc/dangling_placement_error.js +38 -0
  31. package/compiled/src/ifc/geometry_dispatch.d.ts +74 -3
  32. package/compiled/src/ifc/geometry_dispatch.d.ts.map +1 -1
  33. package/compiled/src/ifc/geometry_dispatch.js +261 -5
  34. package/compiled/src/ifc/geometry_dispatch.test.js +147 -1
  35. package/compiled/src/ifc/ifc_geometry_extraction.d.ts +54 -0
  36. package/compiled/src/ifc/ifc_geometry_extraction.d.ts.map +1 -1
  37. package/compiled/src/ifc/ifc_geometry_extraction.js +153 -20
  38. package/compiled/src/index.d.ts +1 -1
  39. package/compiled/src/index.d.ts.map +1 -1
  40. package/compiled/src/index.js +1 -1
  41. package/compiled/src/step/dangling_reference_error.d.ts +34 -0
  42. package/compiled/src/step/dangling_reference_error.d.ts.map +1 -0
  43. package/compiled/src/step/dangling_reference_error.js +36 -0
  44. package/compiled/src/step/step_entity_base.d.ts +17 -0
  45. package/compiled/src/step/step_entity_base.d.ts.map +1 -1
  46. package/compiled/src/step/step_entity_base.js +28 -4
  47. package/compiled/src/version/version.js +1 -1
  48. package/compiled/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +1 -1
@@ -13,11 +13,14 @@
13
13
  *
14
14
  * Model: Arty_Z7.stp — AP214, 38.1 MB, SolidWorks 2021 (SwSTEP 2.0)
15
15
  * Parsing [0%................100%] 3.2s, +210 MB heap
16
+ * Preview: first mesh 0.275s, 539 emitted, 12 deferred (11 on placements), 8 retried
16
17
  * Geometry [0%........56%] 41.0s, +388 MB heap
17
18
  * Total: 44.7s, 512 → 1110 MB heap
18
19
  *
19
20
  * Stage lines own only their deltas (duration, heap growth); Total is not
20
21
  * additive — it is a separate before/after wall-clock + heap observation.
22
+ * The Preview line is not a stage either — it reports what the parse-time
23
+ * preview delivered while Parsing ran (see {@link formatPreviewLine}).
21
24
  */
22
25
  /** What a header parse can tell us before the full file parse (issue #301). */
23
26
  export interface ModelInfo {
@@ -79,6 +82,62 @@ export declare function formatMb(megabytes: number): string;
79
82
  * SolidWorks 2021 (SwSTEP 2.0)"
80
83
  */
81
84
  export declare function formatModelLine(info: ModelInfo): string;
85
+ /**
86
+ * What a parse-time preview channel delivered — structurally typed against
87
+ * both channels' `previewYield` (compat/web-ifc/{store,streamed}_preview_
88
+ * channel.ts) so this module stays import-free.
89
+ */
90
+ export interface PreviewYieldLike {
91
+ /**
92
+ * Ms from the channel starting to the first mesh handed to the consumer;
93
+ * undefined when it never emitted one.
94
+ */
95
+ firstMeshMs?: number;
96
+ /**
97
+ * Preview meshes actually handed to the consumer. Distinct from
98
+ * {@link emitted}: one unit can yield several meshes, and a unit with no
99
+ * representation yields none at all, so reporting only units produced the
100
+ * self-contradicting "no mesh, 20 emitted" (codex round 2 on #543).
101
+ */
102
+ meshes: number;
103
+ /**
104
+ * Units the preview EXTRACTED — attempted and got a non-throwing answer
105
+ * from. Not a count of what reached the screen; that is
106
+ * {@link meshes}. This is the counter the channel's `maxUnits` cap
107
+ * measures, which is why it stays unit-shaped.
108
+ */
109
+ emitted: number;
110
+ deferred: number;
111
+ deferredOnPlacement: number;
112
+ retried: number;
113
+ }
114
+ /**
115
+ * The preview line: time-to-first-pixel, and why the preview delivered what
116
+ * it did.
117
+ *
118
+ * First pixels are the number that decides whether a first-time visitor
119
+ * sees their building or a blank canvas, and until conway#542 it was
120
+ * judged by eye — measurable only in a Node harness nobody runs in
121
+ * production. The deferral split is what makes a blank first load
122
+ * attributable rather than merely observed: a ratio near 1.0 ON PLACEMENTS
123
+ * is the file-layout case (an exporter that writes the leaf points and
124
+ * directions a product's placement chain bottoms out in toward the tail of
125
+ * the file), and anything else is a different problem wearing the same
126
+ * blank screen. `retried` is the second-look count — a retry path that has
127
+ * silently stopped firing shows up here rather than merely as a slower
128
+ * load.
129
+ *
130
+ * Meshes and units are reported separately because they diverge in both
131
+ * directions: one unit can place several meshes, and a unit with no
132
+ * representation (a site, a storey) extracts cleanly while placing none.
133
+ * Folding them into one "emitted" number let the line say "no mesh, 20
134
+ * emitted" about the same load (codex round 2 on #543).
135
+ *
136
+ * @param preview The channel's reported yield.
137
+ * @return {string} e.g. "Preview: first mesh 0.275s, 1750 meshes from 539
138
+ * units, 12 deferred (11 on placements), 8 retried"
139
+ */
140
+ export declare function formatPreviewLine(preview: PreviewYieldLike): string;
82
141
  /**
83
142
  * Accumulates progress events into the normalized text report: a live
84
143
  * current-stage line while a stage runs, a frozen line per finished stage,
@@ -96,6 +155,7 @@ export declare class LoadLogAccumulator {
96
155
  private firstHeapMb_;
97
156
  private lastHeapMb_;
98
157
  private modelLine_;
158
+ private previewLine_;
99
159
  /**
100
160
  * Record the model line (from header info) — kept with the report.
101
161
  *
@@ -103,6 +163,18 @@ export declare class LoadLogAccumulator {
103
163
  * @return {string} The formatted model line.
104
164
  */
105
165
  setModelInfo(info: ModelInfo): string;
166
+ /**
167
+ * Record what the parse-time preview delivered — kept with the report
168
+ * and rendered between the stage lines and Total.
169
+ *
170
+ * Not a stage: the preview runs *inside* Parsing, so feeding it through
171
+ * onProgress would close the Parsing stage and reopen it, splitting one
172
+ * parse across two lines.
173
+ *
174
+ * @param preview The channel's reported yield.
175
+ * @return {string} The formatted preview line.
176
+ */
177
+ setPreviewStats(preview: PreviewYieldLike): string;
106
178
  /**
107
179
  * Feed one progress event; returns the finished stage's line when this
108
180
  * event closed a stage (so callers can mirror it to a console/log once).
@@ -141,8 +213,9 @@ export declare class LoadLogAccumulator {
141
213
  */
142
214
  totalLine(): string;
143
215
  /**
144
- * The full report: model line (if known), finished stage lines, then the
145
- * Total line. Call closeCurrentStage() first at load end.
216
+ * The full report: model line (if known), finished stage lines, the
217
+ * preview line (if reported), then the Total line. Call
218
+ * closeCurrentStage() first at load end.
146
219
  *
147
220
  * @return {string[]} All report lines.
148
221
  */
@@ -1 +1 @@
1
- {"version":3,"file":"progress_log.d.ts","sourceRoot":"","sources":["../../../src/core/progress_log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAqBD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAE,KAAK,EAAE,MAAM,GAAI,MAAM,CAQlD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAE,OAAO,CAAC,EAAE,MAAM,GAAI,MAAM,CASpD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAE,YAAY,EAAE,MAAM,GAAI,MAAM,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAE,SAAS,EAAE,MAAM,GAAI,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAE,IAAI,EAAE,SAAS,GAAI,MAAM,CA0BzD;AA6DD;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAE7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,eAAe,CAAoB;IAC3C,OAAO,CAAC,cAAc,CAAY;IAClC,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,UAAU,CAAoB;IAEtC;;;;;OAKG;IACI,YAAY,CAAE,IAAI,EAAE,SAAS,GAAI,MAAM;IAM9C;;;;;;OAMG;IACI,UAAU,CAAE,KAAK,EAAE,iBAAiB,GAAI,MAAM,GAAG,SAAS;IAiDjE;;;;;;;;OAQG;IACI,iBAAiB,CAAE,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAI,MAAM,GAAG,SAAS;IAuBzF;;;;OAIG;IACI,WAAW,IAAI,MAAM,GAAG,SAAS;IAQxC;;;;OAIG;IACI,aAAa,IAAI,MAAM,EAAE;IAIhC;;;;;OAKG;IACI,SAAS,IAAI,MAAM;IAY1B;;;;;OAKG;IACI,QAAQ,IAAI,MAAM,EAAE;CAY5B"}
1
+ {"version":3,"file":"progress_log.d.ts","sourceRoot":"","sources":["../../../src/core/progress_log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAqBD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAE,KAAK,EAAE,MAAM,GAAI,MAAM,CAQlD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAE,OAAO,CAAC,EAAE,MAAM,GAAI,MAAM,CASpD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAE,YAAY,EAAE,MAAM,GAAI,MAAM,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAE,SAAS,EAAE,MAAM,GAAI,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAE,IAAI,EAAE,SAAS,GAAI,MAAM,CA0BzD;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAAE,OAAO,EAAE,gBAAgB,GAAI,MAAM,CAUrE;AA6DD;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAE7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,eAAe,CAAoB;IAC3C,OAAO,CAAC,cAAc,CAAY;IAClC,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,YAAY,CAAoB;IAExC;;;;;OAKG;IACI,YAAY,CAAE,IAAI,EAAE,SAAS,GAAI,MAAM;IAM9C;;;;;;;;;;OAUG;IACI,eAAe,CAAE,OAAO,EAAE,gBAAgB,GAAI,MAAM;IAM3D;;;;;;OAMG;IACI,UAAU,CAAE,KAAK,EAAE,iBAAiB,GAAI,MAAM,GAAG,SAAS;IAiDjE;;;;;;;;OAQG;IACI,iBAAiB,CAAE,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAI,MAAM,GAAG,SAAS;IAuBzF;;;;OAIG;IACI,WAAW,IAAI,MAAM,GAAG,SAAS;IAQxC;;;;OAIG;IACI,aAAa,IAAI,MAAM,EAAE;IAIhC;;;;;OAKG;IACI,SAAS,IAAI,MAAM;IAY1B;;;;;;OAMG;IACI,QAAQ,IAAI,MAAM,EAAE;CAiB5B"}
@@ -13,11 +13,14 @@
13
13
  *
14
14
  * Model: Arty_Z7.stp — AP214, 38.1 MB, SolidWorks 2021 (SwSTEP 2.0)
15
15
  * Parsing [0%................100%] 3.2s, +210 MB heap
16
+ * Preview: first mesh 0.275s, 539 emitted, 12 deferred (11 on placements), 8 retried
16
17
  * Geometry [0%........56%] 41.0s, +388 MB heap
17
18
  * Total: 44.7s, 512 → 1110 MB heap
18
19
  *
19
20
  * Stage lines own only their deltas (duration, heap growth); Total is not
20
21
  * additive — it is a separate before/after wall-clock + heap observation.
22
+ * The Preview line is not a stage either — it reports what the parse-time
23
+ * preview delivered while Parsing ran (see {@link formatPreviewLine}).
21
24
  */
22
25
  /** Display labels for the phase taxonomy; unknown phases title-case as-is. */
23
26
  const STAGE_LABELS = {
@@ -112,6 +115,41 @@ export function formatModelLine(info) {
112
115
  const detail = parts.length > 0 ? ` — ${parts.join(', ')}` : '';
113
116
  return `Model: ${name}${detail}`;
114
117
  }
118
+ /**
119
+ * The preview line: time-to-first-pixel, and why the preview delivered what
120
+ * it did.
121
+ *
122
+ * First pixels are the number that decides whether a first-time visitor
123
+ * sees their building or a blank canvas, and until conway#542 it was
124
+ * judged by eye — measurable only in a Node harness nobody runs in
125
+ * production. The deferral split is what makes a blank first load
126
+ * attributable rather than merely observed: a ratio near 1.0 ON PLACEMENTS
127
+ * is the file-layout case (an exporter that writes the leaf points and
128
+ * directions a product's placement chain bottoms out in toward the tail of
129
+ * the file), and anything else is a different problem wearing the same
130
+ * blank screen. `retried` is the second-look count — a retry path that has
131
+ * silently stopped firing shows up here rather than merely as a slower
132
+ * load.
133
+ *
134
+ * Meshes and units are reported separately because they diverge in both
135
+ * directions: one unit can place several meshes, and a unit with no
136
+ * representation (a site, a storey) extracts cleanly while placing none.
137
+ * Folding them into one "emitted" number let the line say "no mesh, 20
138
+ * emitted" about the same load (codex round 2 on #543).
139
+ *
140
+ * @param preview The channel's reported yield.
141
+ * @return {string} e.g. "Preview: first mesh 0.275s, 1750 meshes from 539
142
+ * units, 12 deferred (11 on placements), 8 retried"
143
+ */
144
+ export function formatPreviewLine(preview) {
145
+ const first = preview.firstMeshMs !== void 0 ?
146
+ `first mesh ${formatSeconds(preview.firstMeshMs)}` : 'no mesh';
147
+ return `Preview: ${first}, ${preview.meshes} meshes ` +
148
+ `from ${preview.emitted} units, ` +
149
+ `${preview.deferred} deferred ` +
150
+ `(${preview.deferredOnPlacement} on placements), ` +
151
+ `${preview.retried} retried`;
152
+ }
115
153
  /**
116
154
  * The signed heap-delta suffix for a stage, byte precision, e.g.
117
155
  * ", +663.000000 MB heap" — empty when memory wasn't sampled.
@@ -178,6 +216,21 @@ export class LoadLogAccumulator {
178
216
  this.modelLine_ = formatModelLine(info);
179
217
  return this.modelLine_;
180
218
  }
219
+ /**
220
+ * Record what the parse-time preview delivered — kept with the report
221
+ * and rendered between the stage lines and Total.
222
+ *
223
+ * Not a stage: the preview runs *inside* Parsing, so feeding it through
224
+ * onProgress would close the Parsing stage and reopen it, splitting one
225
+ * parse across two lines.
226
+ *
227
+ * @param preview The channel's reported yield.
228
+ * @return {string} The formatted preview line.
229
+ */
230
+ setPreviewStats(preview) {
231
+ this.previewLine_ = formatPreviewLine(preview);
232
+ return this.previewLine_;
233
+ }
181
234
  /**
182
235
  * Feed one progress event; returns the finished stage's line when this
183
236
  * event closed a stage (so callers can mirror it to a console/log once).
@@ -281,8 +334,9 @@ export class LoadLogAccumulator {
281
334
  return `Total: ${formatSeconds(duration)}${heap}`;
282
335
  }
283
336
  /**
284
- * The full report: model line (if known), finished stage lines, then the
285
- * Total line. Call closeCurrentStage() first at load end.
337
+ * The full report: model line (if known), finished stage lines, the
338
+ * preview line (if reported), then the Total line. Call
339
+ * closeCurrentStage() first at load end.
286
340
  *
287
341
  * @return {string[]} All report lines.
288
342
  */
@@ -292,6 +346,9 @@ export class LoadLogAccumulator {
292
346
  lines.push(this.modelLine_);
293
347
  }
294
348
  lines.push(...this.finished_);
349
+ if (this.previewLine_ !== void 0) {
350
+ lines.push(this.previewLine_);
351
+ }
295
352
  lines.push(this.totalLine());
296
353
  return lines;
297
354
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "@jest/globals";
2
- import { LoadLogAccumulator, formatBar, formatMb, formatModelLine, formatSeconds, stageLabel, } from "./progress_log.js";
2
+ import { LoadLogAccumulator, formatBar, formatMb, formatModelLine, formatPreviewLine, formatSeconds, stageLabel, } from "./progress_log.js";
3
3
  const HALF_PERCENT = 56;
4
4
  const HALF_DOTS = 9;
5
5
  const SAMPLE_MS = 3210;
@@ -108,3 +108,77 @@ describe("LoadLogAccumulator", () => {
108
108
  expect(log.totalLine()).toBe("Total: 12.400s");
109
109
  });
110
110
  });
111
+ describe("formatPreviewLine", () => {
112
+ test("reports time-to-first-pixel and the deferral split", () => {
113
+ expect(formatPreviewLine({
114
+ firstMeshMs: 275,
115
+ meshes: 1750,
116
+ emitted: 539,
117
+ deferred: 12,
118
+ deferredOnPlacement: 11,
119
+ retried: 8,
120
+ })).toBe("Preview: first mesh 0.275s, 1750 meshes from 539 units, " +
121
+ "12 deferred (11 on placements), 8 retried");
122
+ });
123
+ test("separates meshes delivered from units extracted", () => {
124
+ // Codex round 2 on #543. `emitted` counts units extraction ACCEPTED,
125
+ // and a unit that extracts cleanly can still place nothing: an IfcSite
126
+ // or IfcBuildingStorey has no Representation, so
127
+ // extractProductGeometry returns early while
128
+ // extractProductGeometryByLocalID still answers true. Folded into one
129
+ // number, the line contradicted itself -- "no mesh, 20 emitted" -- so
130
+ // meshes and units are reported separately and the mesh count is what
131
+ // firstMeshMs is consistent with.
132
+ expect(formatPreviewLine({
133
+ meshes: 0,
134
+ emitted: 20,
135
+ deferred: 0,
136
+ deferredOnPlacement: 0,
137
+ retried: 0,
138
+ })).toBe("Preview: no mesh, 0 meshes from 20 units, " +
139
+ "0 deferred (0 on placements), 0 retried");
140
+ });
141
+ test("says so when the preview never emitted a mesh", () => {
142
+ // The load this line exists for: 532 attempts, one mesh's worth of
143
+ // nothing, and a deferral ratio of 1.0 on placements is what names the
144
+ // cause as file layout rather than "the preview is slow" (conway#542).
145
+ expect(formatPreviewLine({
146
+ meshes: 0,
147
+ emitted: 0,
148
+ deferred: 532,
149
+ deferredOnPlacement: 531,
150
+ retried: 0,
151
+ })).toBe("Preview: no mesh, 0 meshes from 0 units, " +
152
+ "532 deferred (531 on placements), 0 retried");
153
+ });
154
+ });
155
+ describe("LoadLogAccumulator preview line", () => {
156
+ test("renders between the stage lines and Total, and only when set", () => {
157
+ const log = new LoadLogAccumulator();
158
+ log.onProgress({ phase: "dataParse", completed: 0, elapsedMs: 0 });
159
+ log.onProgress({ phase: "dataParse", completed: 1, total: 1, elapsedMs: 1000 });
160
+ log.closeCurrentStage();
161
+ expect(log.allLines()).toEqual([
162
+ "Parsing: 1.000s",
163
+ "Total: 1.000s",
164
+ ]);
165
+ log.setPreviewStats({
166
+ firstMeshMs: 275,
167
+ meshes: 6,
168
+ emitted: 4,
169
+ deferred: 0,
170
+ deferredOnPlacement: 0,
171
+ retried: 0,
172
+ });
173
+ // The preview runs INSIDE Parsing, so it is not a stage — feeding it
174
+ // through onProgress would close Parsing and reopen it, splitting one
175
+ // parse across two lines.
176
+ expect(log.allLines()).toEqual([
177
+ "Parsing: 1.000s",
178
+ "Preview: first mesh 0.275s, 6 meshes from 4 units, " +
179
+ "0 deferred (0 on placements), 0 retried",
180
+ "Total: 1.000s",
181
+ ]);
182
+ expect(log.finishedLines()).toEqual(["Parsing: 1.000s"]);
183
+ });
184
+ });
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A product whose placement chain is not in the scanned prefix yet.
3
+ *
4
+ * Thrown by `extractPlacementStrict_` under `deferDanglingPlacements`, and
5
+ * caught by the parse-time preview channels, which treat it — like any throw
6
+ * from a product extract — as "not yet extractable, leave it to the durable
7
+ * pump".
8
+ *
9
+ * Strictly "not in the prefix YET": the tag is applied only when the
10
+ * underlying throw is a `DanglingReferenceError`, i.e. the placement chain
11
+ * referenced a record the index does not hold. A placement that is present
12
+ * but broken (wrong entity type, a select that is not an axis placement)
13
+ * propagates untagged, because the channels use this tag to decide what to
14
+ * retry, and retrying a permanently broken placement also keeps the early
15
+ * generation-preemption trigger alive for nothing.
16
+ *
17
+ * It exists only so that catch can ATTRIBUTE the deferral. The preview emits
18
+ * a product once its whole closure sits inside the prefix, and Revit writes
19
+ * per-product placements toward the file tail, so on those files early ticks
20
+ * meet long runs of products that all defer and the user sees nothing until
21
+ * the parse is nearly done. Whether a given blank preview is that, or
22
+ * something else entirely, decides whether sharding the parse would help it
23
+ * at all — so the distinction has to be measurable rather than inferred from
24
+ * a comment (conway#542).
25
+ */
26
+ export declare class DanglingPlacementError extends Error {
27
+ readonly productLocalID: number;
28
+ readonly cause: unknown;
29
+ /**
30
+ * @param productLocalID The product that could not be placed.
31
+ * @param cause The underlying dangling-reference error.
32
+ */
33
+ constructor(productLocalID: number, cause: unknown);
34
+ }
35
+ //# sourceMappingURL=dangling_placement_error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dangling_placement_error.d.ts","sourceRoot":"","sources":["../../../src/ifc/dangling_placement_error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAM7B,cAAc,EAAE,MAAM;aACtB,KAAK,EAAE,OAAO;IANhC;;;OAGG;gBAEe,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,OAAO;CAQjC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A product whose placement chain is not in the scanned prefix yet.
3
+ *
4
+ * Thrown by `extractPlacementStrict_` under `deferDanglingPlacements`, and
5
+ * caught by the parse-time preview channels, which treat it — like any throw
6
+ * from a product extract — as "not yet extractable, leave it to the durable
7
+ * pump".
8
+ *
9
+ * Strictly "not in the prefix YET": the tag is applied only when the
10
+ * underlying throw is a `DanglingReferenceError`, i.e. the placement chain
11
+ * referenced a record the index does not hold. A placement that is present
12
+ * but broken (wrong entity type, a select that is not an axis placement)
13
+ * propagates untagged, because the channels use this tag to decide what to
14
+ * retry, and retrying a permanently broken placement also keeps the early
15
+ * generation-preemption trigger alive for nothing.
16
+ *
17
+ * It exists only so that catch can ATTRIBUTE the deferral. The preview emits
18
+ * a product once its whole closure sits inside the prefix, and Revit writes
19
+ * per-product placements toward the file tail, so on those files early ticks
20
+ * meet long runs of products that all defer and the user sees nothing until
21
+ * the parse is nearly done. Whether a given blank preview is that, or
22
+ * something else entirely, decides whether sharding the parse would help it
23
+ * at all — so the distinction has to be measurable rather than inferred from
24
+ * a comment (conway#542).
25
+ */
26
+ export class DanglingPlacementError extends Error {
27
+ /**
28
+ * @param productLocalID The product that could not be placed.
29
+ * @param cause The underlying dangling-reference error.
30
+ */
31
+ constructor(productLocalID, cause) {
32
+ super(`product ${productLocalID} placement is not resolvable in this ` +
33
+ `prefix: ${cause instanceof Error ? cause.message : String(cause)}`);
34
+ this.productLocalID = productLocalID;
35
+ this.cause = cause;
36
+ this.name = 'DanglingPlacementError';
37
+ }
38
+ }
@@ -44,9 +44,17 @@ import IfcStepModel from "./ifc_step_model.js";
44
44
  * then to the product's own local ID, which is unique and therefore places
45
45
  * that product positionally rather than pretending to know better.
46
46
  *
47
- * Total, and deliberately so: a product whose attributes do not resolve gets
48
- * a usable key rather than aborting a load. A wrong placement costs a
49
- * duplicated extraction; a thrown error costs the model.
47
+ * Total for attribute failures, and deliberately so: a product whose
48
+ * attributes do not resolve gets a usable key rather than aborting a load. A
49
+ * wrong placement costs a duplicated extraction; a thrown error costs the
50
+ * model. That is safe precisely because an unresolvable attribute is a
51
+ * property of the FILE — every worker sees it and takes the same fallback.
52
+ *
53
+ * A NON-RESIDENT read is not: it is a property of one worker's paging, and a
54
+ * fallback taken for it is a key another worker will not agree with. Those
55
+ * propagate. On a windowed source, call {@link computeDispatchKeys} rather
56
+ * than this — it pages the walk's closure first, so the only way to reach
57
+ * that throw is a defect in the paging itself.
50
58
  *
51
59
  * @param model The model to read attributes from.
52
60
  * @param productLocalID The product's local ID.
@@ -65,4 +73,67 @@ export declare function geometryDispatchKey(model: IfcStepModel, productLocalID:
65
73
  * @return {number} The owning shard index.
66
74
  */
67
75
  export declare function shardOfDispatchKey(key: number, shardCount: number): number;
76
+ /**
77
+ * Dispatch keys for a worklist, computed with the source bytes guaranteed
78
+ * present. Parallel to the worklist — `keys[i]` places `localIDs[i]`.
79
+ *
80
+ * **This is what makes a windowed source shardable.** Called inline,
81
+ * {@link geometryDispatchKey} reads records as it walks; on a windowed
82
+ * source a record that is not paged in throws, and the walk would fall back
83
+ * to the product's own local ID — so *which* products fell back would depend
84
+ * on which chunks that worker happened to hold. Two workers then disagree
85
+ * about a product, and both moduli select it (extracted twice) or neither
86
+ * (dropped silently). Paging the walk's own closure first removes the
87
+ * dependence: every worker reads the same bytes and computes the same key.
88
+ *
89
+ * A fully resident source takes the direct walk — there is nothing to page,
90
+ * and the keys are identical by construction rather than by agreement, which
91
+ * is what the paired windowed/resident fixtures assert.
92
+ *
93
+ * **Aligned to the worklist, not indexed by local ID.** Keys are wanted for
94
+ * products and for rel-aggregates' relating objects — tens of thousands of
95
+ * entries — and the consumer filters the worklist in order, so there is
96
+ * nothing to look up. A column over local IDs would be 37 MB on PSB's 9.4 M
97
+ * records to carry ~24 k useful ones.
98
+ *
99
+ * @param model The model to read attributes from.
100
+ * @param localIDs The worklist, in the order the consumer will filter it.
101
+ * @param waveSize Products whose closure is pinned at once.
102
+ * @return {Promise<Uint32Array>} Keys aligned to `localIDs`.
103
+ */
104
+ export declare function computeDispatchKeys(model: IfcStepModel, localIDs: readonly number[], waveSize?: number): Promise<Uint32Array>;
105
+ /**
106
+ * The local IDs an aggregate pass should be PLACED by — each
107
+ * `IfcRelAggregates`' relating object, paged first on a windowed source.
108
+ *
109
+ * A rel-aggregates entry is not an `IfcProduct`, so keying on the
110
+ * relationship record itself falls straight through
111
+ * {@link geometryDispatchKey} to its own local ID and shards by file
112
+ * position while claiming to place by representation. The assembly whose
113
+ * geometry the pass actually builds is the relating object, and that is what
114
+ * has to agree across workers.
115
+ *
116
+ * An aggregate whose relating object does not resolve keys by its own local
117
+ * ID — the same file-wide fallback the product walk takes, and the same
118
+ * reason it is safe.
119
+ *
120
+ * @param model The model to read attributes from.
121
+ * @param aggregateLocalIDs The `IfcRelAggregates` worklist, in order.
122
+ * @param waveSize Records whose bytes are pinned at once.
123
+ * @return {Promise<number[]>} Seeds aligned to `aggregateLocalIDs`.
124
+ */
125
+ export declare function computeRelatingLocalIDs(model: IfcStepModel, aggregateLocalIDs: readonly number[], waveSize?: number): Promise<number[]>;
126
+ /**
127
+ * One aggregate's relating object, or the aggregate itself when it does not
128
+ * resolve.
129
+ *
130
+ * Reads the aggregate's record, so a windowed caller must have paged it —
131
+ * {@link computeRelatingLocalIDs} is that caller. Exported for the resident
132
+ * path, which has nothing to page.
133
+ *
134
+ * @param model The model to read attributes from.
135
+ * @param aggregateLocalID The `IfcRelAggregates` record.
136
+ * @return {number} The local ID to key by.
137
+ */
138
+ export declare function relatingLocalIDOf(model: IfcStepModel, aggregateLocalID: number): number;
68
139
  //# sourceMappingURL=geometry_dispatch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"geometry_dispatch.d.ts","sourceRoot":"","sources":["../../../src/ifc/geometry_dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,kBAAkB,CAAA;AAS3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,YAAY,EACnB,cAAc,EAAE,MAAM,GAAI,MAAM,CAmCnC;AAGD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAI,MAAM,CAS5E"}
1
+ {"version":3,"file":"geometry_dispatch.d.ts","sourceRoot":"","sources":["../../../src/ifc/geometry_dispatch.ts"],"names":[],"mappings":"AACA,OAAO,YAAY,MAAM,kBAAkB,CAAA;AAU3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,YAAY,EACnB,cAAc,EAAE,MAAM,GAAI,MAAM,CAuCnC;AAGD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAI,MAAM,CAS5E;AA8BD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,mBAAmB,CACrC,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,GAAE,MAA2B,GAAI,OAAO,CAAE,WAAW,CAAE,CAyClE;AAsKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,uBAAuB,CACzC,KAAK,EAAE,YAAY,EACnB,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,QAAQ,GAAE,MAA2B,GAAI,OAAO,CAAE,MAAM,EAAE,CAAE,CAoC/D;AAGD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAE,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,GAAI,MAAM,CAUzF"}