@bldrs-ai/conway 1.409.1251 → 1.413.1255

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 (29) hide show
  1. package/compiled/Dist/ConwayGeomWasmNode.js +0 -0
  2. package/compiled/Dist/ConwayGeomWasmNodeMT.js +0 -0
  3. package/compiled/Dist/ConwayGeomWasmWeb.js +0 -0
  4. package/compiled/Dist/ConwayGeomWasmWebMT.js +1 -1
  5. package/compiled/Dist/ConwayGeomWasmWebMT.wasm +0 -0
  6. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmNode.js +0 -0
  7. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmNodeMT.js +0 -0
  8. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWeb.js +0 -0
  9. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWebMT.js +1 -1
  10. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWebMT.wasm +0 -0
  11. package/compiled/examples/browser-bundled.cjs +1 -1
  12. package/compiled/examples/cli-bundled.cjs +28 -28
  13. package/compiled/examples/cli-step-bundled.cjs +28 -28
  14. package/compiled/examples/validator-bundled.cjs +1 -1
  15. package/compiled/src/core/demand_residency_pump.d.ts +93 -0
  16. package/compiled/src/core/demand_residency_pump.d.ts.map +1 -0
  17. package/compiled/src/core/demand_residency_pump.js +120 -0
  18. package/compiled/src/core/demand_residency_pump.test.d.ts +2 -0
  19. package/compiled/src/core/demand_residency_pump.test.d.ts.map +1 -0
  20. package/compiled/src/core/demand_residency_pump.test.js +148 -0
  21. package/compiled/src/core/geometry_tile_bindings.d.ts +110 -0
  22. package/compiled/src/core/geometry_tile_bindings.d.ts.map +1 -0
  23. package/compiled/src/core/geometry_tile_bindings.js +76 -0
  24. package/compiled/src/core/geometry_tile_bindings.test.d.ts +2 -0
  25. package/compiled/src/core/geometry_tile_bindings.test.d.ts.map +1 -0
  26. package/compiled/src/core/geometry_tile_bindings.test.js +211 -0
  27. package/compiled/src/version/version.js +1 -1
  28. package/compiled/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +1 -1
@@ -944,7 +944,7 @@ var EntityTypesIfcCount = 909;
944
944
  var entity_types_ifc_gen_default = EntityTypesIfc;
945
945
 
946
946
  // compiled/src/version/version.js
947
- var versionString = "Conway v1.409.1251";
947
+ var versionString = "Conway v1.413.1255";
948
948
 
949
949
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
950
950
  var wasmType = "";
@@ -0,0 +1,93 @@
1
+ import { DemandGeometryQueue } from "./demand_geometry_queue.js";
2
+ /**
3
+ * The async half of demand-driven geometry (Phase B): source-byte residency.
4
+ *
5
+ * Extraction is synchronous by design (the wasm extract reads source bytes
6
+ * through `StepBufferProvider.acquire`), but a windowed/streamed source pages
7
+ * bytes in asynchronously (`ensureResident`). This pump owns that seam: it
8
+ * admits demand, prefetches the source ranges for the most-wanted products,
9
+ * and only then forwards them to the synchronous {@link DemandGeometryQueue}
10
+ * — so a `pump()` never hits a non-resident range mid-extract.
11
+ */
12
+ export interface ResidencyPrefetcher {
13
+ /**
14
+ * Page in the source byte ranges a product's extraction will read.
15
+ * `StepModelBase.ensureResidentByLocalID` satisfies this shape.
16
+ *
17
+ * @param localID The product's local ID.
18
+ * @return {Promise<void>} Resolves when resident.
19
+ */
20
+ ensureResidentByLocalID(localID: number): Promise<void>;
21
+ }
22
+ /** Outcome of one pump cycle, for telemetry and tests. */
23
+ export interface PumpResult {
24
+ /** Tiles extracted this cycle (from the queue's pump). */
25
+ extracted: number;
26
+ /** Products whose prefetch failed this cycle (re-queued for retry). */
27
+ prefetchFailures: number;
28
+ }
29
+ /**
30
+ * Admission + prefetch orchestration in front of a {@link DemandGeometryQueue}.
31
+ *
32
+ * Consumers `request()` products as demand changes (the pump owns admission —
33
+ * don't request on the queue directly); each async `pump()` cycle:
34
+ *
35
+ * 1. selects the top-priority pending batch,
36
+ * 2. awaits source residency for the batch (failures re-queue for retry
37
+ * and are reported, never thrown — one bad range must not stall the
38
+ * viewport),
39
+ * 3. forwards the resident products to the queue and runs its synchronous
40
+ * pump under the same batch cap.
41
+ *
42
+ * Re-entrant calls coalesce: a `pump()` while one is in flight returns the
43
+ * in-flight cycle's promise rather than interleaving prefetch batches.
44
+ * Products already resident in the queue skip prefetch entirely (their
45
+ * ranking refresh is forwarded immediately).
46
+ */
47
+ export declare class DemandResidencyPump {
48
+ private readonly queue_;
49
+ private readonly prefetcher_;
50
+ private readonly batch_;
51
+ /** Pending admission: product localID → highest requested priority. */
52
+ private readonly pending_;
53
+ private inFlight_;
54
+ /**
55
+ * @param queue_ The synchronous demand queue (extraction + budget).
56
+ * @param prefetcher_ Source-byte residency (the model / buffer provider).
57
+ * @param batch_ Max products prefetched + extracted per pump cycle.
58
+ */
59
+ constructor(queue_: DemandGeometryQueue, prefetcher_: ResidencyPrefetcher, batch_?: number);
60
+ /**
61
+ * Request a product at a demand priority. Resident products forward
62
+ * immediately (ranking refresh); others queue for the next pump cycle at
63
+ * the highest priority requested so far.
64
+ *
65
+ * @param productLocalID The product to materialise.
66
+ * @param priority The demand priority (higher = more wanted).
67
+ */
68
+ request(productLocalID: number, priority: number): void;
69
+ /**
70
+ * @return {number} Products awaiting admission (not yet prefetched).
71
+ */
72
+ get pendingCount(): number;
73
+ /**
74
+ * Run one admission cycle (see class docs). Coalesces with an in-flight
75
+ * cycle.
76
+ *
77
+ * @return {Promise<PumpResult>} The cycle's outcome.
78
+ */
79
+ pump(): Promise<PumpResult>;
80
+ /**
81
+ * One cycle: select top batch → prefetch → forward + queue pump.
82
+ *
83
+ * @return {Promise<PumpResult>} The cycle's outcome.
84
+ */
85
+ private pumpCycle_;
86
+ /**
87
+ * Remove and return the top-priority pending entries, up to the batch cap.
88
+ *
89
+ * @return {[number, number][]} `[productLocalID, priority]` pairs.
90
+ */
91
+ private takeTopPending_;
92
+ }
93
+ //# sourceMappingURL=demand_residency_pump.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demand_residency_pump.d.ts","sourceRoot":"","sources":["../../../src/core/demand_residency_pump.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAG7D;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAElC;;;;;;OAMG;IACH,uBAAuB,CAAE,OAAO,EAAE,MAAM,GAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAGD,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IAEzB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAA;IAEjB,uEAAuE;IACvE,gBAAgB,EAAE,MAAM,CAAA;CACzB;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,mBAAmB;IAa5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAbzB,uEAAuE;IACvE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IAErD,OAAO,CAAC,SAAS,CAAiC;IAElD;;;;OAIG;gBAEgB,MAAM,EAAE,mBAAmB,EAC3B,WAAW,EAAE,mBAAmB,EAChC,MAAM,GAAE,MAAsB;IAOjD;;;;;;;OAOG;IACI,OAAO,CAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAI,IAAI;IAchE;;OAEG;IACH,IAAW,YAAY,IAAI,MAAM,CAEhC;IAED;;;;;OAKG;IACI,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC;IAalC;;;;OAIG;YACW,UAAU;IAkCxB;;;;OAIG;IACH,OAAO,CAAC,eAAe;CAkBxB"}
@@ -0,0 +1,120 @@
1
+ // Defaults chosen for frame-cooperative pumping: enough per cycle to fill
2
+ // quickly, small enough that a cycle stays well under a frame budget once
3
+ // extraction costs are real. Both are tunable per call site.
4
+ const DEFAULT_BATCH = 32;
5
+ /**
6
+ * Admission + prefetch orchestration in front of a {@link DemandGeometryQueue}.
7
+ *
8
+ * Consumers `request()` products as demand changes (the pump owns admission —
9
+ * don't request on the queue directly); each async `pump()` cycle:
10
+ *
11
+ * 1. selects the top-priority pending batch,
12
+ * 2. awaits source residency for the batch (failures re-queue for retry
13
+ * and are reported, never thrown — one bad range must not stall the
14
+ * viewport),
15
+ * 3. forwards the resident products to the queue and runs its synchronous
16
+ * pump under the same batch cap.
17
+ *
18
+ * Re-entrant calls coalesce: a `pump()` while one is in flight returns the
19
+ * in-flight cycle's promise rather than interleaving prefetch batches.
20
+ * Products already resident in the queue skip prefetch entirely (their
21
+ * ranking refresh is forwarded immediately).
22
+ */
23
+ export class DemandResidencyPump {
24
+ /**
25
+ * @param queue_ The synchronous demand queue (extraction + budget).
26
+ * @param prefetcher_ Source-byte residency (the model / buffer provider).
27
+ * @param batch_ Max products prefetched + extracted per pump cycle.
28
+ */
29
+ constructor(queue_, prefetcher_, batch_ = DEFAULT_BATCH) {
30
+ this.queue_ = queue_;
31
+ this.prefetcher_ = prefetcher_;
32
+ this.batch_ = batch_;
33
+ /** Pending admission: product localID → highest requested priority. */
34
+ this.pending_ = new Map();
35
+ if (batch_ < 1) {
36
+ throw new Error(`Invalid batch ${batch_}`);
37
+ }
38
+ }
39
+ /**
40
+ * Request a product at a demand priority. Resident products forward
41
+ * immediately (ranking refresh); others queue for the next pump cycle at
42
+ * the highest priority requested so far.
43
+ *
44
+ * @param productLocalID The product to materialise.
45
+ * @param priority The demand priority (higher = more wanted).
46
+ */
47
+ request(productLocalID, priority) {
48
+ if (this.queue_.isResident(productLocalID)) {
49
+ this.queue_.request(productLocalID, priority);
50
+ return;
51
+ }
52
+ const existing = this.pending_.get(productLocalID);
53
+ this.pending_.set(productLocalID, existing === void 0 ? priority : Math.max(existing, priority));
54
+ }
55
+ /**
56
+ * @return {number} Products awaiting admission (not yet prefetched).
57
+ */
58
+ get pendingCount() {
59
+ return this.pending_.size;
60
+ }
61
+ /**
62
+ * Run one admission cycle (see class docs). Coalesces with an in-flight
63
+ * cycle.
64
+ *
65
+ * @return {Promise<PumpResult>} The cycle's outcome.
66
+ */
67
+ pump() {
68
+ if (this.inFlight_ !== void 0) {
69
+ return this.inFlight_;
70
+ }
71
+ this.inFlight_ = this.pumpCycle_().finally(() => {
72
+ this.inFlight_ = void 0;
73
+ });
74
+ return this.inFlight_;
75
+ }
76
+ /**
77
+ * One cycle: select top batch → prefetch → forward + queue pump.
78
+ *
79
+ * @return {Promise<PumpResult>} The cycle's outcome.
80
+ */
81
+ async pumpCycle_() {
82
+ const batch = this.takeTopPending_();
83
+ if (batch.length === 0) {
84
+ // Nothing to admit; still give the queue a chance to fill from its own
85
+ // pending set (e.g. entries deferred by an earlier budget refusal).
86
+ return { extracted: this.queue_.pump(this.batch_), prefetchFailures: 0 };
87
+ }
88
+ const outcomes = await Promise.allSettled(batch.map(([productLocalID]) => this.prefetcher_.ensureResidentByLocalID(productLocalID)));
89
+ let prefetchFailures = 0;
90
+ for (let where = 0; where < batch.length; ++where) {
91
+ const [productLocalID, priority] = batch[where];
92
+ if (outcomes[where].status === 'fulfilled') {
93
+ this.queue_.request(productLocalID, priority);
94
+ continue;
95
+ }
96
+ // Failed prefetch: re-queue for a later cycle (transient store errors
97
+ // retry; persistent ones keep surfacing in the failure count).
98
+ ++prefetchFailures;
99
+ this.request(productLocalID, priority);
100
+ }
101
+ return { extracted: this.queue_.pump(this.batch_), prefetchFailures };
102
+ }
103
+ /**
104
+ * Remove and return the top-priority pending entries, up to the batch cap.
105
+ *
106
+ * @return {[number, number][]} `[productLocalID, priority]` pairs.
107
+ */
108
+ takeTopPending_() {
109
+ if (this.pending_.size === 0) {
110
+ return [];
111
+ }
112
+ const entries = [...this.pending_.entries()];
113
+ entries.sort((a, b) => b[1] - a[1]);
114
+ const batch = entries.slice(0, this.batch_);
115
+ for (const [productLocalID] of batch) {
116
+ this.pending_.delete(productLocalID);
117
+ }
118
+ return batch;
119
+ }
120
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=demand_residency_pump.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demand_residency_pump.test.d.ts","sourceRoot":"","sources":["../../../src/core/demand_residency_pump.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,148 @@
1
+ /* eslint-disable no-magic-numbers */
2
+ // Phase B: the pump must guarantee source residency BEFORE the synchronous
3
+ // extract runs, admit strictly by priority under the batch cap, coalesce
4
+ // re-entrant cycles, and survive prefetch failures without stalling.
5
+ import { describe, expect, test } from "@jest/globals";
6
+ import { DemandGeometryQueue } from "./demand_geometry_queue.js";
7
+ import { DemandResidencyPump } from "./demand_residency_pump.js";
8
+ /**
9
+ * A mock tiles backend that records extraction order and asserts every
10
+ * extracted product was made resident first.
11
+ *
12
+ * @param residentSource Set the prefetcher fills.
13
+ * @return {object} `{ tiles, extracted }`.
14
+ */
15
+ function residencyCheckedTiles(residentSource) {
16
+ const extracted = [];
17
+ const tiles = {
18
+ extract(id) {
19
+ if (!residentSource.has(id)) {
20
+ throw new Error(`extract(${id}) before source residency`);
21
+ }
22
+ extracted.push(id);
23
+ return 10;
24
+ },
25
+ release() { },
26
+ };
27
+ return { tiles, extracted };
28
+ }
29
+ /**
30
+ * A controllable prefetcher: records call order; optionally fails given ids.
31
+ *
32
+ * @param residentSource The set fulfilled prefetches add to.
33
+ * @param failIDs IDs whose prefetch rejects.
34
+ * @return {object} `{ prefetcher, calls }`.
35
+ */
36
+ function mockPrefetcher(residentSource, failIDs = new Set()) {
37
+ const calls = [];
38
+ const prefetcher = {
39
+ ensureResidentByLocalID: async (localID) => {
40
+ calls.push(localID);
41
+ if (failIDs.has(localID)) {
42
+ throw new Error(`range fetch failed for ${localID}`);
43
+ }
44
+ residentSource.add(localID);
45
+ },
46
+ };
47
+ return { prefetcher, calls };
48
+ }
49
+ describe("DemandResidencyPump", () => {
50
+ test("prefetches source bytes before any synchronous extract", async () => {
51
+ const residentSource = new Set();
52
+ const { tiles, extracted } = residencyCheckedTiles(residentSource);
53
+ const { prefetcher, calls } = mockPrefetcher(residentSource);
54
+ const pump = new DemandResidencyPump(new DemandGeometryQueue(tiles, 1000), prefetcher);
55
+ pump.request(1, 10);
56
+ pump.request(2, 20);
57
+ const result = await pump.pump();
58
+ // The residency-checked tiles throw if this ordering ever breaks.
59
+ expect(result.extracted).toBe(2);
60
+ expect(new Set(calls)).toEqual(new Set([1, 2]));
61
+ expect(extracted).toEqual([2, 1]); // still priority-ordered
62
+ });
63
+ test("admits strictly by priority under the batch cap", async () => {
64
+ const residentSource = new Set();
65
+ const { tiles, extracted } = residencyCheckedTiles(residentSource);
66
+ const { prefetcher, calls } = mockPrefetcher(residentSource);
67
+ const queue = new DemandGeometryQueue(tiles, 1000000);
68
+ const pump = new DemandResidencyPump(queue, prefetcher, 3);
69
+ for (let id = 0; id < 10; ++id) {
70
+ pump.request(id, id); // rising priority: 7, 8, 9 are most wanted
71
+ }
72
+ await pump.pump();
73
+ expect(new Set(calls)).toEqual(new Set([7, 8, 9]));
74
+ expect(extracted).toEqual([9, 8, 7]);
75
+ expect(pump.pendingCount).toBe(7);
76
+ await pump.pump();
77
+ expect(extracted).toEqual([9, 8, 7, 6, 5, 4]);
78
+ });
79
+ test("requesting a resident product refreshes ranking without prefetch", async () => {
80
+ const residentSource = new Set();
81
+ const { tiles } = residencyCheckedTiles(residentSource);
82
+ const { prefetcher, calls } = mockPrefetcher(residentSource);
83
+ const queue = new DemandGeometryQueue(tiles, 1000);
84
+ const pump = new DemandResidencyPump(queue, prefetcher);
85
+ pump.request(1, 10);
86
+ await pump.pump();
87
+ expect(calls).toEqual([1]);
88
+ // Re-request while resident: no new prefetch, no new pending entry.
89
+ pump.request(1, 99);
90
+ expect(pump.pendingCount).toBe(0);
91
+ await pump.pump();
92
+ expect(calls).toEqual([1]);
93
+ });
94
+ test("a failed prefetch re-queues and later retries; others proceed", async () => {
95
+ const residentSource = new Set();
96
+ const { tiles, extracted } = residencyCheckedTiles(residentSource);
97
+ const failIDs = new Set([2]);
98
+ const { prefetcher } = mockPrefetcher(residentSource, failIDs);
99
+ const pump = new DemandResidencyPump(new DemandGeometryQueue(tiles, 1000), prefetcher);
100
+ pump.request(1, 10);
101
+ pump.request(2, 20);
102
+ const first = await pump.pump();
103
+ expect(first.prefetchFailures).toBe(1);
104
+ expect(extracted).toEqual([1]);
105
+ expect(pump.pendingCount).toBe(1); // 2 re-queued
106
+ // The transient failure clears; the retry succeeds.
107
+ failIDs.clear();
108
+ const second = await pump.pump();
109
+ expect(second.prefetchFailures).toBe(0);
110
+ expect(extracted).toEqual([1, 2]);
111
+ });
112
+ test("re-entrant pump calls coalesce onto the in-flight cycle", async () => {
113
+ const residentSource = new Set();
114
+ const { tiles } = residencyCheckedTiles(residentSource);
115
+ const { prefetcher, calls } = mockPrefetcher(residentSource);
116
+ const pump = new DemandResidencyPump(new DemandGeometryQueue(tiles, 1000), prefetcher);
117
+ pump.request(1, 10);
118
+ const a = pump.pump();
119
+ const b = pump.pump();
120
+ expect(b).toBe(a); // same promise, no interleaved second cycle
121
+ await a;
122
+ expect(calls).toEqual([1]);
123
+ // After settling, pump() starts a fresh cycle.
124
+ pump.request(2, 5);
125
+ await pump.pump();
126
+ expect(calls).toEqual([1, 2]);
127
+ });
128
+ test("an empty admission cycle still drains the queue-side pending set", async () => {
129
+ const residentSource = new Set([1, 2]);
130
+ const { tiles, extracted } = residencyCheckedTiles(residentSource);
131
+ const { prefetcher } = mockPrefetcher(residentSource);
132
+ // Tiny budget: only one of the two fits per pump; the loser stays
133
+ // pending inside the QUEUE (deferred by budget), not the pump.
134
+ const queue = new DemandGeometryQueue(tiles, 10);
135
+ const pump = new DemandResidencyPump(queue, prefetcher);
136
+ pump.request(1, 10);
137
+ pump.request(2, 20);
138
+ await pump.pump();
139
+ expect(extracted).toEqual([2]);
140
+ expect(queue.stats.pendingCount).toBe(1);
141
+ // No new admissions; the cycle still gives the queue its pump call.
142
+ // (1 cannot displace 2 at this budget, so it stays pending — the point
143
+ // is the queue got the chance.)
144
+ const result = await pump.pump();
145
+ expect(result.extracted).toBe(0);
146
+ expect(queue.stats.pendingCount).toBe(1);
147
+ });
148
+ });
@@ -0,0 +1,110 @@
1
+ import { ChunkedPool } from "./mem/chunked_pool.js";
2
+ import { GeometryAsset, GeometryTilePool } from "./geometry_tile_pool.js";
3
+ /**
4
+ * The wasm module's geometry tile pool surface (conway-geom
5
+ * `tile_pool_api.h` via the embind bindings in conway-api.cpp) — Phase B of
6
+ * the demand-geometry track. Typed narrowly here rather than through the
7
+ * vendored module d.ts; asset ids and sizes cross the boundary as JS numbers
8
+ * (< 2^53 by construction).
9
+ *
10
+ * Division of labour (see "Resident memory: two regimes" in the design doc):
11
+ * the TS side (`ChunkedPool` + `SharedAssetPool` + `GeometryTilePool`) is the
12
+ * accounting/policy layer — it holds no payload bytes — while the wasm
13
+ * `TilePool` owns the physical chunks. In this composition the TS layer calls
14
+ * `materialize`/`discard` exactly once per residency, so the wasm-side
15
+ * refcount stays at 1 for TS-managed tiles; the wasm refcounting exists for
16
+ * future direct-C++ holders, not for this path.
17
+ */
18
+ export interface GeometryTilePoolBindings {
19
+ initGeometryTilePool(budgetBytes: number, chunkBytes: number): boolean;
20
+ geometryTilePoolInitialized(): boolean;
21
+ commitGeometryTileBytes(assetID: number, sourceAddress: number, byteLength: number): boolean;
22
+ retainGeometryTile(assetID: number): boolean;
23
+ releaseGeometryTile(assetID: number): boolean;
24
+ geometryTileResident(assetID: number): boolean;
25
+ geometryTileRefCount(assetID: number): number;
26
+ geometryTileByteSize(assetID: number): number;
27
+ geometryTileSegmentCount(assetID: number): number;
28
+ geometryTileSegmentAddress(assetID: number, segment: number): number;
29
+ geometryTileSegmentByteLength(assetID: number, segment: number): number;
30
+ geometryTileVertexByteLength(assetID: number): number;
31
+ geometryTileIndexByteLength(assetID: number): number;
32
+ geometryTilePoolBytesInUse(): number;
33
+ geometryTilePoolTotalBytes(): number;
34
+ geometryTilePoolFreeChunks(): number;
35
+ geometryTilePoolFailedCommits(): number;
36
+ }
37
+ /**
38
+ * The domain extractor the wasm-backed source delegates to: which assets a
39
+ * product is made of, and how to run the wasm extract → tessellate →
40
+ * `commitGeometryTile` for one asset. Implemented over the real extraction
41
+ * pipeline in the Phase B wiring of `IfcModelGeometry`; tests use recording
42
+ * fakes.
43
+ */
44
+ export interface TileAssetExtractor {
45
+ /**
46
+ * The assets (representation geometries) a product requires resident,
47
+ * with their reified byte sizes. Mapped items return the same assetID from
48
+ * many products — the sharing the pools are built around.
49
+ *
50
+ * @param productLocalID The product's local ID.
51
+ * @return {GeometryAsset<number>[]} The product's assets.
52
+ */
53
+ assetsOf(productLocalID: number): GeometryAsset<number>[];
54
+ /**
55
+ * Extract + tessellate one asset and commit its reified payload into the
56
+ * wasm tile pool (`commitGeometryTile`). Called exactly once per residency.
57
+ * Must throw if the commit fails — under the budget invariant it cannot,
58
+ * so a failure is a contract violation, not a recoverable state.
59
+ *
60
+ * @param assetID The asset to materialise.
61
+ */
62
+ extractIntoTile(assetID: number): void;
63
+ }
64
+ /**
65
+ * The Phase B backend composition: the TS accounting stack
66
+ * (`ChunkedPool` → `GeometryTilePool`) wired over the wasm tile pool, ready
67
+ * to hand to `DemandGeometryQueue`.
68
+ */
69
+ export interface WasmTileBackend {
70
+ /** The TS accounting pool (mirrors the wasm pool's budget and chunking). */
71
+ pool: ChunkedPool;
72
+ /** The `GeometryTiles` backend for the demand queue. */
73
+ tiles: GeometryTilePool<number>;
74
+ }
75
+ /**
76
+ * Initialise the wasm tile pool and build the TS accounting stack over it,
77
+ * with identical budget and chunking on both sides so the TS all-or-nothing
78
+ * admission mirrors the wasm pool's exactly (the queue-budget ≤ pool-budget
79
+ * invariant then guarantees a wasm commit can never fail mid-extract).
80
+ *
81
+ * @param bindings The wasm module's tile pool surface.
82
+ * @param extractor The domain extract/commit implementation.
83
+ * @param budgetBytes The resident geometry budget (both sides).
84
+ * @param chunkBytes The chunk size (both sides; ≥ the wasm 16-byte floor).
85
+ * @return {WasmTileBackend} The composed backend.
86
+ */
87
+ export declare function createWasmTileBackend(bindings: GeometryTilePoolBindings, extractor: TileAssetExtractor, budgetBytes: number, chunkBytes: number): WasmTileBackend;
88
+ /**
89
+ * A resident tile's reified payload, gathered from the wasm pool's segments
90
+ * into typed arrays ready for GPU upload (three.js BufferAttributes). The
91
+ * arrays are standalone copies — valid after the tile is evicted.
92
+ */
93
+ export interface GeometryTilePayload {
94
+ vertexData: Float32Array;
95
+ indexData: Uint32Array;
96
+ }
97
+ /**
98
+ * Gather a resident tile's payload out of the wasm heap by walking its
99
+ * (generally non-contiguous) segments. This is the copying consumption path;
100
+ * a zero-copy per-segment upload can walk the same segment accessors
101
+ * directly.
102
+ *
103
+ * @param bindings The wasm module's tile pool surface.
104
+ * @param heap The module's HEAPU8 view (re-read after any wasm memory
105
+ * growth; pass the module's current view, do not cache across calls).
106
+ * @param assetID The resident tile to read.
107
+ * @return {GeometryTilePayload} The gathered payload.
108
+ */
109
+ export declare function readGeometryTilePayload(bindings: GeometryTilePoolBindings, heap: Uint8Array, assetID: number): GeometryTilePayload;
110
+ //# sourceMappingURL=geometry_tile_bindings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry_tile_bindings.d.ts","sourceRoot":"","sources":["../../../src/core/geometry_tile_bindings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EACL,aAAa,EACb,gBAAgB,EAEjB,MAAM,sBAAsB,CAAA;AAG7B;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,wBAAwB;IAEvC,oBAAoB,CAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAI,OAAO,CAAA;IACxE,2BAA2B,IAAI,OAAO,CAAA;IAEtC,uBAAuB,CACrB,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAI,OAAO,CAAA;IAEvE,kBAAkB,CAAE,OAAO,EAAE,MAAM,GAAI,OAAO,CAAA;IAC9C,mBAAmB,CAAE,OAAO,EAAE,MAAM,GAAI,OAAO,CAAA;IAC/C,oBAAoB,CAAE,OAAO,EAAE,MAAM,GAAI,OAAO,CAAA;IAChD,oBAAoB,CAAE,OAAO,EAAE,MAAM,GAAI,MAAM,CAAA;IAC/C,oBAAoB,CAAE,OAAO,EAAE,MAAM,GAAI,MAAM,CAAA;IAE/C,wBAAwB,CAAE,OAAO,EAAE,MAAM,GAAI,MAAM,CAAA;IACnD,0BAA0B,CAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAI,MAAM,CAAA;IACtE,6BAA6B,CAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAI,MAAM,CAAA;IACzE,4BAA4B,CAAE,OAAO,EAAE,MAAM,GAAI,MAAM,CAAA;IACvD,2BAA2B,CAAE,OAAO,EAAE,MAAM,GAAI,MAAM,CAAA;IAEtD,0BAA0B,IAAI,MAAM,CAAA;IACpC,0BAA0B,IAAI,MAAM,CAAA;IACpC,0BAA0B,IAAI,MAAM,CAAA;IACpC,6BAA6B,IAAI,MAAM,CAAA;CACxC;AAGD;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IAEjC;;;;;;;OAOG;IACH,QAAQ,CAAE,cAAc,EAAE,MAAM,GAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAA;IAE3D;;;;;;;OAOG;IACH,eAAe,CAAE,OAAO,EAAE,MAAM,GAAI,IAAI,CAAA;CACzC;AAGD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAE9B,4EAA4E;IAC5E,IAAI,EAAE,WAAW,CAAA;IAEjB,wDAAwD;IACxD,KAAK,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;CAChC;AAGD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,wBAAwB,EAClC,SAAS,EAAE,kBAAkB,EAC7B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GAAI,eAAe,CAwBxC;AAGD;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,YAAY,CAAA;IACxB,SAAS,EAAE,WAAW,CAAA;CACvB;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACnC,QAAQ,EAAE,wBAAwB,EAClC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,GAAI,mBAAmB,CAyCzC"}
@@ -0,0 +1,76 @@
1
+ import { ChunkedPool } from "./mem/chunked_pool.js";
2
+ import { GeometryTilePool, } from "./geometry_tile_pool.js";
3
+ /**
4
+ * Initialise the wasm tile pool and build the TS accounting stack over it,
5
+ * with identical budget and chunking on both sides so the TS all-or-nothing
6
+ * admission mirrors the wasm pool's exactly (the queue-budget ≤ pool-budget
7
+ * invariant then guarantees a wasm commit can never fail mid-extract).
8
+ *
9
+ * @param bindings The wasm module's tile pool surface.
10
+ * @param extractor The domain extract/commit implementation.
11
+ * @param budgetBytes The resident geometry budget (both sides).
12
+ * @param chunkBytes The chunk size (both sides; ≥ the wasm 16-byte floor).
13
+ * @return {WasmTileBackend} The composed backend.
14
+ */
15
+ export function createWasmTileBackend(bindings, extractor, budgetBytes, chunkBytes) {
16
+ // TS pool first: if the config is invalid on the TS side (non-integer or
17
+ // degenerate chunking), fail before touching the wasm pool so no
18
+ // initialized-but-unowned wasm region is left behind.
19
+ const pool = new ChunkedPool(budgetBytes, chunkBytes);
20
+ if (!bindings.initGeometryTilePool(budgetBytes, chunkBytes)) {
21
+ throw new Error(`Wasm tile pool rejected init (budget ${budgetBytes}, ` +
22
+ `chunk ${chunkBytes})`);
23
+ }
24
+ const source = {
25
+ assetsOf: (productLocalID) => extractor.assetsOf(productLocalID),
26
+ materialize: (assetID) => {
27
+ extractor.extractIntoTile(assetID);
28
+ },
29
+ discard: (assetID) => {
30
+ bindings.releaseGeometryTile(assetID);
31
+ },
32
+ };
33
+ return { pool, tiles: new GeometryTilePool(pool, source) };
34
+ }
35
+ // Geometry tile payload layout (tile_pool_api.h): 8-byte header of two u32
36
+ // byte lengths, then float vertex data, then u32 index data.
37
+ const TILE_HEADER_BYTES = 8;
38
+ /**
39
+ * Gather a resident tile's payload out of the wasm heap by walking its
40
+ * (generally non-contiguous) segments. This is the copying consumption path;
41
+ * a zero-copy per-segment upload can walk the same segment accessors
42
+ * directly.
43
+ *
44
+ * @param bindings The wasm module's tile pool surface.
45
+ * @param heap The module's HEAPU8 view (re-read after any wasm memory
46
+ * growth; pass the module's current view, do not cache across calls).
47
+ * @param assetID The resident tile to read.
48
+ * @return {GeometryTilePayload} The gathered payload.
49
+ */
50
+ export function readGeometryTilePayload(bindings, heap, assetID) {
51
+ const byteSize = bindings.geometryTileByteSize(assetID);
52
+ if (byteSize < TILE_HEADER_BYTES) {
53
+ throw new Error(`Tile ${assetID} is not resident or has no header`);
54
+ }
55
+ const gathered = new Uint8Array(byteSize);
56
+ let copied = 0;
57
+ const segments = bindings.geometryTileSegmentCount(assetID);
58
+ for (let segment = 0; segment < segments; ++segment) {
59
+ const address = bindings.geometryTileSegmentAddress(assetID, segment);
60
+ const length = bindings.geometryTileSegmentByteLength(assetID, segment);
61
+ gathered.set(heap.subarray(address, address + length), copied);
62
+ copied += length;
63
+ }
64
+ const view = new DataView(gathered.buffer);
65
+ const vertexBytes = view.getUint32(0, true);
66
+ const indexBytes = view.getUint32(4, true);
67
+ if (TILE_HEADER_BYTES + vertexBytes + indexBytes !== byteSize) {
68
+ throw new Error(`Tile ${assetID} header inconsistent with payload size ` +
69
+ `(${vertexBytes} + ${indexBytes} + ${TILE_HEADER_BYTES} !== ${byteSize})`);
70
+ }
71
+ // Slice to aligned standalone buffers (gathered's offsets are 8/`+vertex`,
72
+ // which may misalign Float32Array/Uint32Array views over the same buffer).
73
+ const vertexData = new Float32Array(gathered.buffer.slice(TILE_HEADER_BYTES, TILE_HEADER_BYTES + vertexBytes));
74
+ const indexData = new Uint32Array(gathered.buffer.slice(TILE_HEADER_BYTES + vertexBytes, TILE_HEADER_BYTES + vertexBytes + indexBytes));
75
+ return { vertexData, indexData };
76
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=geometry_tile_bindings.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry_tile_bindings.test.d.ts","sourceRoot":"","sources":["../../../src/core/geometry_tile_bindings.test.ts"],"names":[],"mappings":""}