@bldrs-ai/conway 1.408.1248 → 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.
- package/compiled/Dist/ConwayGeomWasmNode.js +0 -0
- package/compiled/Dist/ConwayGeomWasmNodeMT.js +0 -0
- package/compiled/Dist/ConwayGeomWasmWeb.js +0 -0
- package/compiled/Dist/ConwayGeomWasmWebMT.js +1 -1
- package/compiled/Dist/ConwayGeomWasmWebMT.wasm +0 -0
- package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmNode.js +0 -0
- package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmNodeMT.js +0 -0
- package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWeb.js +0 -0
- package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWebMT.js +1 -1
- package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWebMT.wasm +0 -0
- package/compiled/examples/browser-bundled.cjs +238 -13
- package/compiled/examples/cli-bundled.cjs +265 -40
- package/compiled/examples/cli-step-bundled.cjs +131 -36
- package/compiled/examples/validator-bundled.cjs +238 -13
- package/compiled/src/AP214E3_2010/ap214_step_model.d.ts +2 -1
- package/compiled/src/AP214E3_2010/ap214_step_model.d.ts.map +1 -1
- package/compiled/src/AP214E3_2010/ap214_step_model.js +1 -1
- package/compiled/src/core/demand_residency_pump.d.ts +93 -0
- package/compiled/src/core/demand_residency_pump.d.ts.map +1 -0
- package/compiled/src/core/demand_residency_pump.js +120 -0
- package/compiled/src/core/demand_residency_pump.test.d.ts +2 -0
- package/compiled/src/core/demand_residency_pump.test.d.ts.map +1 -0
- package/compiled/src/core/demand_residency_pump.test.js +148 -0
- package/compiled/src/core/geometry_tile_bindings.d.ts +110 -0
- package/compiled/src/core/geometry_tile_bindings.d.ts.map +1 -0
- package/compiled/src/core/geometry_tile_bindings.js +76 -0
- package/compiled/src/core/geometry_tile_bindings.test.d.ts +2 -0
- package/compiled/src/core/geometry_tile_bindings.test.d.ts.map +1 -0
- package/compiled/src/core/geometry_tile_bindings.test.js +211 -0
- package/compiled/src/ifc/ifc_step_model.d.ts +2 -1
- package/compiled/src/ifc/ifc_step_model.d.ts.map +1 -1
- package/compiled/src/ifc/ifc_step_model.js +1 -1
- package/compiled/src/ifc/ifc_step_parser.d.ts.map +1 -1
- package/compiled/src/ifc/ifc_step_parser.js +5 -3
- package/compiled/src/step/indexing/step_type_indexer.d.ts +20 -0
- package/compiled/src/step/indexing/step_type_indexer.d.ts.map +1 -1
- package/compiled/src/step/indexing/step_type_indexer.js +67 -0
- package/compiled/src/step/parsing/columnar_index.d.ts +81 -0
- package/compiled/src/step/parsing/columnar_index.d.ts.map +1 -0
- package/compiled/src/step/parsing/columnar_index.js +159 -0
- package/compiled/src/step/parsing/columnar_index.test.d.ts +2 -0
- package/compiled/src/step/parsing/columnar_index.test.d.ts.map +1 -0
- package/compiled/src/step/parsing/columnar_index.test.js +155 -0
- package/compiled/src/step/parsing/index_sidecar.d.ts +28 -0
- package/compiled/src/step/parsing/index_sidecar.d.ts.map +1 -1
- package/compiled/src/step/parsing/index_sidecar.js +117 -0
- package/compiled/src/step/parsing/index_sidecar.test.js +31 -1
- package/compiled/src/step/parsing/step_parser.d.ts +22 -1
- package/compiled/src/step/parsing/step_parser.d.ts.map +1 -1
- package/compiled/src/step/parsing/step_parser.js +19 -7
- package/compiled/src/step/parsing/streaming_index_builder.d.ts +29 -2
- package/compiled/src/step/parsing/streaming_index_builder.d.ts.map +1 -1
- package/compiled/src/step/parsing/streaming_index_builder.js +23 -2
- package/compiled/src/step/step_model_base.d.ts +2 -1
- package/compiled/src/step/step_model_base.d.ts.map +1 -1
- package/compiled/src/step/step_model_base.js +31 -0
- package/compiled/src/version/version.js +1 -1
- package/compiled/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"geometry_tile_bindings.test.d.ts","sourceRoot":"","sources":["../../../src/core/geometry_tile_bindings.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
// Phase B: the wasm-backed tile composition — TS accounting over the module's
|
|
3
|
+
// physical pool — and the segment-walk payload reader, verified against a
|
|
4
|
+
// faithful in-memory fake of the embind surface (same layout, same chunking,
|
|
5
|
+
// same non-contiguous segments a fragmented pool produces).
|
|
6
|
+
import { describe, expect, test } from "@jest/globals";
|
|
7
|
+
import { DemandGeometryQueue } from "./demand_geometry_queue.js";
|
|
8
|
+
import { createWasmTileBackend, readGeometryTilePayload, } from "./geometry_tile_bindings.js";
|
|
9
|
+
const HEADER_BYTES = 8;
|
|
10
|
+
/**
|
|
11
|
+
* A faithful in-memory fake of the wasm tile pool bindings: a byte "heap"
|
|
12
|
+
* carved into chunks with a LIFO freelist, tiles committed as (possibly
|
|
13
|
+
* non-contiguous) chunk lists — the same observable behaviour as the C++
|
|
14
|
+
* TilePool through the embind surface.
|
|
15
|
+
*
|
|
16
|
+
* @return {object} `{ bindings, heap, commitPayload }` where commitPayload
|
|
17
|
+
* scatter-commits a payload built from header+floats+indices.
|
|
18
|
+
*/
|
|
19
|
+
function fakeWasmPool() {
|
|
20
|
+
const HEAP_BYTES = 64 * 1024;
|
|
21
|
+
const heap = new Uint8Array(HEAP_BYTES);
|
|
22
|
+
let chunkBytes = 0;
|
|
23
|
+
let regionBase = 0;
|
|
24
|
+
let freeList = [];
|
|
25
|
+
const tiles = new Map();
|
|
26
|
+
let failedCommits = 0;
|
|
27
|
+
let totalChunks = 0;
|
|
28
|
+
const bindings = {
|
|
29
|
+
initGeometryTilePool: (budget, chunk) => {
|
|
30
|
+
if (chunk < 16 || budget < chunk) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
chunkBytes = chunk;
|
|
34
|
+
totalChunks = Math.floor(budget / chunk);
|
|
35
|
+
regionBase = 1024; // nonzero base, like a real heap allocation
|
|
36
|
+
freeList = [];
|
|
37
|
+
for (let c = totalChunks - 1; c >= 0; --c) {
|
|
38
|
+
freeList.push(c);
|
|
39
|
+
}
|
|
40
|
+
tiles.clear();
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
geometryTilePoolInitialized: () => chunkBytes > 0,
|
|
44
|
+
commitGeometryTileBytes: (assetID, sourceAddress, byteLength) => {
|
|
45
|
+
const needed = Math.ceil(byteLength / chunkBytes);
|
|
46
|
+
if (tiles.has(assetID) || needed > freeList.length) {
|
|
47
|
+
++failedCommits;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const chunks = [];
|
|
51
|
+
let copied = 0;
|
|
52
|
+
for (let where = 0; where < needed; ++where) {
|
|
53
|
+
const chunk = freeList.pop();
|
|
54
|
+
chunks.push(chunk);
|
|
55
|
+
const span = Math.min(byteLength - copied, chunkBytes);
|
|
56
|
+
heap.set(heap.subarray(sourceAddress + copied, sourceAddress + copied + span), regionBase + chunk * chunkBytes);
|
|
57
|
+
copied += span;
|
|
58
|
+
}
|
|
59
|
+
tiles.set(assetID, { chunks, byteSize: byteLength, refCount: 1 });
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
retainGeometryTile: (assetID) => {
|
|
63
|
+
const tile = tiles.get(assetID);
|
|
64
|
+
if (tile === void 0) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
++tile.refCount;
|
|
68
|
+
return true;
|
|
69
|
+
},
|
|
70
|
+
releaseGeometryTile: (assetID) => {
|
|
71
|
+
const tile = tiles.get(assetID);
|
|
72
|
+
if (tile === void 0) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (--tile.refCount > 0) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
freeList.push(...tile.chunks);
|
|
79
|
+
tiles.delete(assetID);
|
|
80
|
+
return true;
|
|
81
|
+
},
|
|
82
|
+
geometryTileResident: (assetID) => tiles.has(assetID),
|
|
83
|
+
geometryTileRefCount: (assetID) => tiles.get(assetID)?.refCount ?? 0,
|
|
84
|
+
geometryTileByteSize: (assetID) => tiles.get(assetID)?.byteSize ?? 0,
|
|
85
|
+
geometryTileSegmentCount: (assetID) => tiles.get(assetID)?.chunks.length ?? 0,
|
|
86
|
+
geometryTileSegmentAddress: (assetID, segment) => {
|
|
87
|
+
const tile = tiles.get(assetID);
|
|
88
|
+
if (tile === void 0 || segment >= tile.chunks.length) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
return regionBase + tile.chunks[segment] * chunkBytes;
|
|
92
|
+
},
|
|
93
|
+
geometryTileSegmentByteLength: (assetID, segment) => {
|
|
94
|
+
const tile = tiles.get(assetID);
|
|
95
|
+
if (tile === void 0 || segment >= tile.chunks.length) {
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
return Math.min(tile.byteSize - segment * chunkBytes, chunkBytes);
|
|
99
|
+
},
|
|
100
|
+
geometryTileVertexByteLength: (assetID) => {
|
|
101
|
+
const address = bindings.geometryTileSegmentAddress(assetID, 0);
|
|
102
|
+
return address === 0 ?
|
|
103
|
+
0 : new DataView(heap.buffer).getUint32(address, true);
|
|
104
|
+
},
|
|
105
|
+
geometryTileIndexByteLength: (assetID) => {
|
|
106
|
+
const address = bindings.geometryTileSegmentAddress(assetID, 0);
|
|
107
|
+
return address === 0 ?
|
|
108
|
+
0 : new DataView(heap.buffer).getUint32(address + 4, true);
|
|
109
|
+
},
|
|
110
|
+
geometryTilePoolBytesInUse: () => (totalChunks - freeList.length) * chunkBytes,
|
|
111
|
+
geometryTilePoolTotalBytes: () => totalChunks * chunkBytes,
|
|
112
|
+
geometryTilePoolFreeChunks: () => freeList.length,
|
|
113
|
+
geometryTilePoolFailedCommits: () => failedCommits,
|
|
114
|
+
};
|
|
115
|
+
// Stage a payload high in the fake heap and scatter-commit it.
|
|
116
|
+
const commitPayload = (assetID, floats, indices) => {
|
|
117
|
+
const staging = HEAP_BYTES - 8192;
|
|
118
|
+
const view = new DataView(heap.buffer, staging);
|
|
119
|
+
view.setUint32(0, floats.length * 4, true);
|
|
120
|
+
view.setUint32(4, indices.length * 4, true);
|
|
121
|
+
floats.forEach((value, i) => view.setFloat32(HEADER_BYTES + i * 4, value, true));
|
|
122
|
+
indices.forEach((value, i) => view.setUint32(HEADER_BYTES + floats.length * 4 + i * 4, value, true));
|
|
123
|
+
return bindings.commitGeometryTileBytes(assetID, staging, HEADER_BYTES + floats.length * 4 + indices.length * 4);
|
|
124
|
+
};
|
|
125
|
+
return { bindings, heap, commitPayload };
|
|
126
|
+
}
|
|
127
|
+
describe("createWasmTileBackend", () => {
|
|
128
|
+
test("composes TS accounting over the wasm pool end to end with the queue", () => {
|
|
129
|
+
const { bindings, commitPayload } = fakeWasmPool();
|
|
130
|
+
// Two products share one representation; extraction commits real bytes.
|
|
131
|
+
const composition = {
|
|
132
|
+
1: [{ assetID: 100, byteSize: 200 }],
|
|
133
|
+
2: [{ assetID: 100, byteSize: 200 }, { assetID: 200, byteSize: 100 }],
|
|
134
|
+
};
|
|
135
|
+
const extracted = [];
|
|
136
|
+
const extractor = {
|
|
137
|
+
assetsOf: (id) => composition[id] ?? [],
|
|
138
|
+
extractIntoTile: (assetID) => {
|
|
139
|
+
extracted.push(assetID);
|
|
140
|
+
if (!commitPayload(assetID, [1, 2, 3], [0, 1, 2])) {
|
|
141
|
+
throw new Error("commit failed");
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
const backend = createWasmTileBackend(bindings, extractor, 4096, 64);
|
|
146
|
+
const queue = new DemandGeometryQueue(backend.tiles, 4096);
|
|
147
|
+
queue.request(1, 10);
|
|
148
|
+
queue.request(2, 20);
|
|
149
|
+
queue.pump();
|
|
150
|
+
// Shared asset extracted once; both tiles resident wasm-side.
|
|
151
|
+
expect(extracted).toEqual([100, 200]);
|
|
152
|
+
expect(bindings.geometryTileResident(100)).toBe(true);
|
|
153
|
+
expect(bindings.geometryTileResident(200)).toBe(true);
|
|
154
|
+
// Evicting product 2 releases only its unique asset (100 is shared).
|
|
155
|
+
queue.evictAll();
|
|
156
|
+
expect(bindings.geometryTileResident(100)).toBe(false);
|
|
157
|
+
expect(bindings.geometryTileResident(200)).toBe(false);
|
|
158
|
+
expect(bindings.geometryTilePoolBytesInUse()).toBe(0);
|
|
159
|
+
});
|
|
160
|
+
test("rejects a config the wasm pool refuses", () => {
|
|
161
|
+
const { bindings } = fakeWasmPool();
|
|
162
|
+
const extractor = {
|
|
163
|
+
assetsOf: () => [],
|
|
164
|
+
extractIntoTile: () => { },
|
|
165
|
+
};
|
|
166
|
+
expect(() => createWasmTileBackend(bindings, extractor, 4096, 8))
|
|
167
|
+
.toThrow(/rejected init/);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe("readGeometryTilePayload", () => {
|
|
171
|
+
test("gathers a multi-segment payload back to exact typed arrays", () => {
|
|
172
|
+
const { bindings, heap, commitPayload } = fakeWasmPool();
|
|
173
|
+
bindings.initGeometryTilePool(4096, 64);
|
|
174
|
+
// 40 floats + 20 indices + header = 248 bytes over 64-byte chunks →
|
|
175
|
+
// 4 non-contiguous segments with a partial tail.
|
|
176
|
+
const floats = Array.from({ length: 40 }, (_v, i) => i * 0.5);
|
|
177
|
+
const indices = Array.from({ length: 20 }, (_v, i) => i * 3);
|
|
178
|
+
expect(commitPayload(7, floats, indices)).toBe(true);
|
|
179
|
+
expect(bindings.geometryTileSegmentCount(7)).toBe(4);
|
|
180
|
+
const payload = readGeometryTilePayload(bindings, heap, 7);
|
|
181
|
+
expect(Array.from(payload.vertexData)).toEqual(floats);
|
|
182
|
+
expect(Array.from(payload.indexData)).toEqual(indices);
|
|
183
|
+
expect(bindings.geometryTileVertexByteLength(7)).toBe(160);
|
|
184
|
+
expect(bindings.geometryTileIndexByteLength(7)).toBe(80);
|
|
185
|
+
});
|
|
186
|
+
test("survives freelist fragmentation (interleaved tiles)", () => {
|
|
187
|
+
const { bindings, heap, commitPayload } = fakeWasmPool();
|
|
188
|
+
bindings.initGeometryTilePool(4096, 64);
|
|
189
|
+
commitPayload(1, [1, 1, 1, 1], [1]);
|
|
190
|
+
commitPayload(2, Array(30).fill(2), [2, 2]);
|
|
191
|
+
bindings.releaseGeometryTile(1);
|
|
192
|
+
// Tile 3's chunks interleave with tile 2's.
|
|
193
|
+
const floats = Array.from({ length: 25 }, (_v, i) => i + 0.25);
|
|
194
|
+
commitPayload(3, floats, [9, 8, 7]);
|
|
195
|
+
const payload = readGeometryTilePayload(bindings, heap, 3);
|
|
196
|
+
expect(Array.from(payload.vertexData)).toEqual(floats);
|
|
197
|
+
expect(Array.from(payload.indexData)).toEqual([9, 8, 7]);
|
|
198
|
+
});
|
|
199
|
+
test("rejects a non-resident tile and a corrupt header", () => {
|
|
200
|
+
const { bindings, heap, commitPayload } = fakeWasmPool();
|
|
201
|
+
bindings.initGeometryTilePool(4096, 64);
|
|
202
|
+
expect(() => readGeometryTilePayload(bindings, heap, 42))
|
|
203
|
+
.toThrow(/not resident/);
|
|
204
|
+
commitPayload(5, [1], [2]);
|
|
205
|
+
// Corrupt the header's vertex byte length in place.
|
|
206
|
+
const address = bindings.geometryTileSegmentAddress(5, 0);
|
|
207
|
+
new DataView(heap.buffer).setUint32(address, 9999, true);
|
|
208
|
+
expect(() => readGeometryTilePayload(bindings, heap, 5))
|
|
209
|
+
.toThrow(/inconsistent/);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import EntityTypesIfc from "./ifc4_gen/entity_types_ifc.gen.js";
|
|
2
2
|
import StepModelBase from "../step/step_model_base.js";
|
|
3
3
|
import { StepIndexEntry } from "../step/parsing/step_parser.js";
|
|
4
|
+
import { StepIndexColumns } from "../step/parsing/columnar_index.js";
|
|
4
5
|
import { MultiIndexSet } from "../indexing/multi_index_set.js";
|
|
5
6
|
import { IfcModelGeometry } from "./ifc_model_geometry.js";
|
|
6
7
|
import { IfcModelProfile } from "./ifc_model_profile.js";
|
|
@@ -32,6 +33,6 @@ export default class IfcStepModel extends StepModelBase<EntityTypesIfc> {
|
|
|
32
33
|
* @param elementIndex The parsed index to elements in the STEP.
|
|
33
34
|
* @param provider Optional pre-built buffer provider (windowed source).
|
|
34
35
|
*/
|
|
35
|
-
constructor(buffer: Uint8Array | undefined, elementIndex: StepIndexEntry<EntityTypesIfc>[]
|
|
36
|
+
constructor(buffer: Uint8Array | undefined, elementIndex: StepIndexEntry<EntityTypesIfc>[] | StepIndexColumns<EntityTypesIfc>, provider?: StepBufferProvider);
|
|
36
37
|
}
|
|
37
38
|
//# sourceMappingURL=ifc_step_model.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ifc_step_model.d.ts","sourceRoot":"","sources":["../../../src/ifc/ifc_step_model.ts"],"names":[],"mappings":"AAAA,OAAO,cAAqC,MAAM,iCAAiC,CAAA;AACnF,OAAO,aAAa,MAAM,yBAAyB,CAAA;AAEnD,OAAO,EAAC,cAAc,EAAC,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"ifc_step_model.d.ts","sourceRoot":"","sources":["../../../src/ifc/ifc_step_model.ts"],"names":[],"mappings":"AAAA,OAAO,cAAqC,MAAM,iCAAiC,CAAA;AACnF,OAAO,aAAa,MAAM,yBAAyB,CAAA;AAEnD,OAAO,EAAC,cAAc,EAAC,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAC,gBAAgB,EAAC,MAAM,gCAAgC,CAAA;AAE/D,OAAO,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,sBAAsB,MAAM,6BAA6B,CAAA;AAChE,OAAO,cAAc,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AAKjE;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,aAAa,CAAE,cAAc,CAAE;IAEvE,SAAgB,SAAS,EAAE,aAAa,CAAE,cAAc,CAAE,CAAA;IAC1D,SAAgB,mBAAmB,gCAAyB;IAC5D,SAAgB,QAAQ,mBAA+B;IACvD,SAAgB,YAAY,mBAAqC;IACjE,SAAgB,QAAQ,kBAA4B;IACpD,SAAgB,MAAM,iBAA2B;IACjD,SAAgB,aAAa,iBAAuB;IACpD,SAAgB,SAAS,mBAA+B;IACxD,SAAgB,aAAa,mBAAqC;IAClE,SAAgB,cAAc,wBAAiB;IAE/C;;;;;;;;OAQG;gBAEC,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,YAAY,EAAE,cAAc,CAAE,cAAc,CAAE,EAAE,GAAG,gBAAgB,CAAE,cAAc,CAAE,EACrF,QAAQ,CAAC,EAAE,kBAAkB;CAKlC"}
|
|
@@ -33,6 +33,6 @@ export default class IfcStepModel extends StepModelBase {
|
|
|
33
33
|
this.materials = new IfcMaterialCache(this);
|
|
34
34
|
this.voidMaterials = new IfcMaterialCache(this, true);
|
|
35
35
|
this.elementTypeIDs = EntityTypesIfc;
|
|
36
|
-
this.typeIndex = indexerInstance.
|
|
36
|
+
this.typeIndex = indexerInstance.createFor(elementIndex);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ifc_step_parser.d.ts","sourceRoot":"","sources":["../../../src/ifc/ifc_step_parser.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,2BAA2B,CAAA;AACrD,OAAO,UAAU,EAAE,EAAC,qBAAqB,EAAE,WAAW,EAAC,MAAM,6BAA6B,CAAA;AAC1F,OAAO,cAAc,MAAM,iCAAiC,CAAA;AAE5D,OAAO,YAAY,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAExD,OAAO,EACL,qBAAqB,EAEtB,MAAM,8BAA8B,CAAA;AAOrC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,UAAU,CAAE,cAAc,CAAE;IACrE;;OAEG;;IAKH;;;;;OAKG;IACH,gBAAuB,QAAQ,gBAAsB;IAErD;;;;;;;OAOG;IACI,gBAAgB,CACnB,KAAK,EAAE,aAAa,EACpB,UAAU,CAAC,EAAE,qBAAqB,GAAI,CAAC,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC;IAMjF;;;;;;;;OAQG;IACU,qBAAqB,CAC9B,KAAK,EAAE,aAAa,EACpB,UAAU,CAAC,EAAE,qBAAqB,GAClC,OAAO,CAAC,CAAC,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC,CAAC;IAMpD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACI,kBAAkB,CACrB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,qBAAqB,EAC5B,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,GACzE;QAAC,WAAW;QAAE,YAAY,GAAG,SAAS;KAAC;
|
|
1
|
+
{"version":3,"file":"ifc_step_parser.d.ts","sourceRoot":"","sources":["../../../src/ifc/ifc_step_parser.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,2BAA2B,CAAA;AACrD,OAAO,UAAU,EAAE,EAAC,qBAAqB,EAAE,WAAW,EAAC,MAAM,6BAA6B,CAAA;AAC1F,OAAO,cAAc,MAAM,iCAAiC,CAAA;AAE5D,OAAO,YAAY,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAExD,OAAO,EACL,qBAAqB,EAEtB,MAAM,8BAA8B,CAAA;AAOrC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,UAAU,CAAE,cAAc,CAAE;IACrE;;OAEG;;IAKH;;;;;OAKG;IACH,gBAAuB,QAAQ,gBAAsB;IAErD;;;;;;;OAOG;IACI,gBAAgB,CACnB,KAAK,EAAE,aAAa,EACpB,UAAU,CAAC,EAAE,qBAAqB,GAAI,CAAC,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC;IAMjF;;;;;;;;OAQG;IACU,qBAAqB,CAC9B,KAAK,EAAE,aAAa,EACpB,UAAU,CAAC,EAAE,qBAAqB,GAClC,OAAO,CAAC,CAAC,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC,CAAC;IAMpD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACI,kBAAkB,CACrB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,qBAAqB,EAC5B,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,GACzE;QAAC,WAAW;QAAE,YAAY,GAAG,SAAS;KAAC;CAkB5C"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import StepParser from "../step/parsing/step_parser.js";
|
|
2
2
|
import EntitTypesIfcSearch from "./ifc4_gen/entity_types_search.gen.js";
|
|
3
3
|
import IfcStepModel from "./ifc_step_model.js";
|
|
4
|
-
import {
|
|
4
|
+
import { buildColumnarIndexStreaming } from "../step/parsing/streaming_index_builder.js";
|
|
5
5
|
import { WindowedStepBufferProvider, } from "../step/step_buffer_provider.js";
|
|
6
6
|
/** Default moving-window size for the streaming index build (1 MiB). */
|
|
7
7
|
// eslint-disable-next-line no-magic-numbers
|
|
@@ -72,9 +72,11 @@ class IfcStepParser extends StepParser {
|
|
|
72
72
|
throw new Error(`Streaming store byteLength ${store.byteLength} does not match ` +
|
|
73
73
|
`source byteLength ${source.byteLength}`);
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
// Columnar build (M7): the index goes straight into SoA columns — the
|
|
76
|
+
// per-record object phase never exists, so peak heap is window + columns.
|
|
77
|
+
const { columns, result } = buildColumnarIndexStreaming(source, this, opts?.pool ?? DEFAULT_STREAM_POOL_BYTES);
|
|
76
78
|
const provider = new WindowedStepBufferProvider(store, opts?.chunkBytes, opts?.maxResidentChunks);
|
|
77
|
-
return [result, new IfcStepModel(void 0,
|
|
79
|
+
return [result, new IfcStepModel(void 0, columns, provider)];
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MultiIndexSet } from "../../indexing/multi_index_set.js";
|
|
2
|
+
import { StepIndexColumns } from "../parsing/columnar_index.js";
|
|
2
3
|
import { StepIndexEntry } from "../parsing/step_parser.js";
|
|
3
4
|
/**
|
|
4
5
|
* Indexes STEP by type so that it can be searched by type.
|
|
@@ -11,6 +12,25 @@ export declare class StepTypeIndexer<TypeIDType extends number> {
|
|
|
11
12
|
* @param typeCount The number of types that will be indexed.
|
|
12
13
|
*/
|
|
13
14
|
constructor(typeCount: number);
|
|
15
|
+
/**
|
|
16
|
+
* Create a type index from either index form — the parsed object array or
|
|
17
|
+
* the columnar index (M7).
|
|
18
|
+
*
|
|
19
|
+
* @param index The element index, object or columnar.
|
|
20
|
+
* @return {MultiIndexSet} The created multi-set index.
|
|
21
|
+
*/
|
|
22
|
+
createFor(index: StepIndexEntry<TypeIDType>[] | StepIndexColumns<TypeIDType>): MultiIndexSet<TypeIDType>;
|
|
23
|
+
/**
|
|
24
|
+
* Create a type index straight from the columnar index — the same
|
|
25
|
+
* membership `create` produces from the unfolded object array: every row's
|
|
26
|
+
* concrete type (top-level + inline; −1 rows skipped) plus the
|
|
27
|
+
* multi-mapping subtypes of retained complex entries at their parent's
|
|
28
|
+
* dense index.
|
|
29
|
+
*
|
|
30
|
+
* @param columns The columnar index.
|
|
31
|
+
* @return {MultiIndexSet} The created multi-set index.
|
|
32
|
+
*/
|
|
33
|
+
createFromColumns(columns: StepIndexColumns<TypeIDType>): MultiIndexSet<TypeIDType>;
|
|
14
34
|
/**
|
|
15
35
|
* Create a type index from a set of parsed STEP elements.
|
|
16
36
|
*
|