@bldrs-ai/conway 1.399.1223 → 1.401.1237
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 +184 -11
- package/compiled/examples/cli-bundled.cjs +213 -40
- package/compiled/examples/cli-step-bundled.cjs +105 -37
- package/compiled/examples/validator-bundled.cjs +184 -11
- package/compiled/src/ifc/ifc_step_model.d.ts +5 -2
- package/compiled/src/ifc/ifc_step_model.d.ts.map +1 -1
- package/compiled/src/ifc/ifc_step_model.js +5 -3
- package/compiled/src/ifc/ifc_step_parser.d.ts +36 -0
- package/compiled/src/ifc/ifc_step_parser.d.ts.map +1 -1
- package/compiled/src/ifc/ifc_step_parser.js +40 -0
- package/compiled/src/ifc/ifc_stream_model.test.d.ts +2 -0
- package/compiled/src/ifc/ifc_stream_model.test.d.ts.map +1 -0
- package/compiled/src/ifc/ifc_stream_model.test.js +70 -0
- package/compiled/src/parsing/parsing_buffer.d.ts +23 -0
- package/compiled/src/parsing/parsing_buffer.d.ts.map +1 -1
- package/compiled/src/parsing/parsing_buffer.js +31 -0
- package/compiled/src/step/parsing/byte_source.d.ts +55 -0
- package/compiled/src/step/parsing/byte_source.d.ts.map +1 -0
- package/compiled/src/step/parsing/byte_source.js +35 -0
- package/compiled/src/step/parsing/incremental_type_index.d.ts +64 -0
- package/compiled/src/step/parsing/incremental_type_index.d.ts.map +1 -0
- package/compiled/src/step/parsing/incremental_type_index.js +92 -0
- package/compiled/src/step/parsing/incremental_type_index.test.d.ts +2 -0
- package/compiled/src/step/parsing/incremental_type_index.test.d.ts.map +1 -0
- package/compiled/src/step/parsing/incremental_type_index.test.js +55 -0
- package/compiled/src/step/parsing/step_parser.d.ts +19 -0
- package/compiled/src/step/parsing/step_parser.d.ts.map +1 -1
- package/compiled/src/step/parsing/step_parser.js +48 -4
- package/compiled/src/step/parsing/streaming_index_builder.d.ts +70 -0
- package/compiled/src/step/parsing/streaming_index_builder.d.ts.map +1 -0
- package/compiled/src/step/parsing/streaming_index_builder.js +125 -0
- package/compiled/src/step/parsing/streaming_index_builder.test.d.ts +2 -0
- package/compiled/src/step/parsing/streaming_index_builder.test.d.ts.map +1 -0
- package/compiled/src/step/parsing/streaming_index_builder.test.js +107 -0
- package/compiled/src/step/parsing/streaming_record_dispatcher.d.ts +55 -0
- package/compiled/src/step/parsing/streaming_record_dispatcher.d.ts.map +1 -0
- package/compiled/src/step/parsing/streaming_record_dispatcher.js +67 -0
- package/compiled/src/step/parsing/streaming_record_dispatcher.test.d.ts +2 -0
- package/compiled/src/step/parsing/streaming_record_dispatcher.test.d.ts.map +1 -0
- package/compiled/src/step/parsing/streaming_record_dispatcher.test.js +66 -0
- package/compiled/src/step/step_model_base.d.ts +9 -3
- package/compiled/src/step/step_model_base.d.ts.map +1 -1
- package/compiled/src/step/step_model_base.js +9 -3
- package/compiled/src/version/version.js +1 -1
- package/compiled/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import ParsingBuffer from "../../parsing/parsing_buffer.js";
|
|
2
|
+
import { ParseResult, } from "./step_parser.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build the entity index by streaming a `ByteSource` through a fixed-size
|
|
5
|
+
* moving window, instead of parsing one resident buffer. The parse loop is
|
|
6
|
+
* byte-for-byte the resident one (`StepParser.parseDataBlockStreamed` drives
|
|
7
|
+
* the same generator); this coordinator only owns the window: it fills it,
|
|
8
|
+
* parses the header from the first fill, then slides the window forward at
|
|
9
|
+
* top-level record boundaries as the parser advances.
|
|
10
|
+
*
|
|
11
|
+
* Because the parser records file-absolute addresses and its rewind stack is
|
|
12
|
+
* empty at every top-level boundary, sliding there is transparent: the
|
|
13
|
+
* unconsumed tail is copied to the front, fresh bytes are appended, and the
|
|
14
|
+
* buffer is rebased so `address` keeps its file-absolute value. Peak JS heap
|
|
15
|
+
* for the index build is therefore `window + index columns`, independent of
|
|
16
|
+
* file size.
|
|
17
|
+
*
|
|
18
|
+
* The window slides only once the cursor passes `pool / 2`, bounding the
|
|
19
|
+
* memmove frequency; a record up to `pool / 2` bytes always fits after a
|
|
20
|
+
* slide. If a single record exceeds that (never on the current corpus, whose
|
|
21
|
+
* largest STEP record is ~25 KB), the whole parse restarts from the
|
|
22
|
+
* beginning with the window doubled, and repeats until every record fits —
|
|
23
|
+
* correctness over the pathological case, at the cost of a re-scan. (M1's
|
|
24
|
+
* production path will instead grow in place / restart from the last
|
|
25
|
+
* boundary; from-scratch keeps the spike simple.)
|
|
26
|
+
*
|
|
27
|
+
* @param source The byte source.
|
|
28
|
+
* @param parser The STEP parser (typed to the schema).
|
|
29
|
+
* @param pool Target window size in bytes.
|
|
30
|
+
* @param onRecordIndexed Optional per-record event, invoked live as each
|
|
31
|
+
* top-level record is indexed with its (localID, expressID, typeID) — the
|
|
32
|
+
* seam for incremental semantic consumers (M2). localIDs are dense and
|
|
33
|
+
* assigned in parse order from 0. On the rare grow-and-restart (a single
|
|
34
|
+
* record larger than the window — never on real files, whose largest STEP
|
|
35
|
+
* record is ~25 KB), the parse re-runs from the start and records re-fire
|
|
36
|
+
* from localID 0; consumers must therefore be idempotent by localID /
|
|
37
|
+
* expressID (the standard consumers — type index keyed by localID, roots
|
|
38
|
+
* registry keyed by expressID — are). Must be synchronous and cheap;
|
|
39
|
+
* expensive work belongs on a demand queue, not the parse path.
|
|
40
|
+
* @return {StreamingIndexResult} The index, header, result and diagnostics.
|
|
41
|
+
*/
|
|
42
|
+
export function buildIndexStreaming(source, parser, pool, onRecordIndexed) {
|
|
43
|
+
const fileSize = source.byteLength;
|
|
44
|
+
// Grow-and-restart loop: the body runs a full streamed parse at the current
|
|
45
|
+
// window size. It only re-runs if a single record couldn't fit the window
|
|
46
|
+
// (false end-of-file before true EOF), doubling the window each time.
|
|
47
|
+
let windowBytes = Math.max(pool, MIN_WINDOW);
|
|
48
|
+
for (;;) {
|
|
49
|
+
const window = new Uint8Array(windowBytes);
|
|
50
|
+
let windowStartFile = 0;
|
|
51
|
+
let windowLen = source.read(0, windowBytes, window, 0);
|
|
52
|
+
let bytesRead = windowLen;
|
|
53
|
+
const input = new ParsingBuffer(window, 0, windowLen);
|
|
54
|
+
const [header, headerResult] = parser.parseHeader(input);
|
|
55
|
+
if (headerResult !== ParseResult.COMPLETE) {
|
|
56
|
+
// Header didn't complete in the first window: either malformed, or a
|
|
57
|
+
// header larger than the pool. Report it as-is — the resident parse
|
|
58
|
+
// would surface the same header result.
|
|
59
|
+
return {
|
|
60
|
+
header,
|
|
61
|
+
elements: [],
|
|
62
|
+
result: headerResult,
|
|
63
|
+
stats: { pool, windowBytes, slides: 0, maxRecordLen: 0, bytesRead },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Slide once the cursor is past half the window; a record up to this much
|
|
67
|
+
// headroom always fits without a false EOF.
|
|
68
|
+
const slideThreshold = windowBytes >> 1;
|
|
69
|
+
let slides = 0;
|
|
70
|
+
let maxRecordLen = 0;
|
|
71
|
+
let prevBoundaryFile = windowStartFile + input.cursor;
|
|
72
|
+
const onRecordBoundary = (buffer) => {
|
|
73
|
+
const cursor = buffer.cursor;
|
|
74
|
+
const recordFileStart = windowStartFile + cursor;
|
|
75
|
+
const recordLen = recordFileStart - prevBoundaryFile;
|
|
76
|
+
if (recordLen > maxRecordLen) {
|
|
77
|
+
maxRecordLen = recordLen;
|
|
78
|
+
}
|
|
79
|
+
prevBoundaryFile = recordFileStart;
|
|
80
|
+
// Window already spans to EOF — nothing left to slide in.
|
|
81
|
+
if (windowStartFile + windowLen >= fileSize) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Enough headroom remains; defer the slide to bound memmove frequency.
|
|
85
|
+
if (cursor < slideThreshold) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Slide: move the unconsumed tail to the front, refill behind it.
|
|
89
|
+
const tail = windowLen - cursor;
|
|
90
|
+
window.copyWithin(0, cursor, windowLen);
|
|
91
|
+
const want = windowBytes - tail;
|
|
92
|
+
const got = source.read(windowStartFile + windowLen, want, window, tail);
|
|
93
|
+
bytesRead += got;
|
|
94
|
+
windowLen = tail + got;
|
|
95
|
+
windowStartFile = recordFileStart;
|
|
96
|
+
buffer.rebaseWindow(window, 0, windowLen, windowStartFile);
|
|
97
|
+
++slides;
|
|
98
|
+
};
|
|
99
|
+
const [index, result] = parser.parseDataBlockStreamed(input, onRecordBoundary, onRecordIndexed);
|
|
100
|
+
// A parse that stopped short of true EOF, with the window not spanning to
|
|
101
|
+
// EOF, means a single record overflowed the window: grow and retry.
|
|
102
|
+
const stoppedShort = result !== ParseResult.COMPLETE;
|
|
103
|
+
const notAtEof = windowStartFile + windowLen < fileSize;
|
|
104
|
+
if (stoppedShort && notAtEof) {
|
|
105
|
+
windowBytes *= 2;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Capture the final record's length (no boundary fires after the last).
|
|
109
|
+
const lastRecordLen = (windowStartFile + input.cursor) - prevBoundaryFile;
|
|
110
|
+
if (lastRecordLen > maxRecordLen) {
|
|
111
|
+
maxRecordLen = lastRecordLen;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
header,
|
|
115
|
+
elements: index.elements,
|
|
116
|
+
result,
|
|
117
|
+
stats: { pool, windowBytes, slides, maxRecordLen, bytesRead },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Floor on the window so a pathologically tiny pool still holds the header
|
|
122
|
+
// and a record with slide headroom. Tests drive pools down to this to force
|
|
123
|
+
// many slides on a small fixture; the pool sweep uses ≥ 128 KB anyway.
|
|
124
|
+
// eslint-disable-next-line no-magic-numbers
|
|
125
|
+
const MIN_WINDOW = 4 * 1024;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming_index_builder.test.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/streaming_index_builder.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
// M0 spike: streaming index build must be byte-identical to the resident
|
|
3
|
+
// parse. The parser reads file-absolute addresses and only rewinds within a
|
|
4
|
+
// single top-level record, so feeding it from a moving window that slides at
|
|
5
|
+
// record boundaries must produce the exact same index columns — at every
|
|
6
|
+
// pool size, including one small enough to force many slides + straddles.
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import { beforeAll, describe, expect, test } from "@jest/globals";
|
|
9
|
+
import ParsingBuffer from "../../parsing/parsing_buffer.js";
|
|
10
|
+
import IfcStepParser from "../../ifc/ifc_step_parser.js";
|
|
11
|
+
import { BufferByteSource } from "./byte_source.js";
|
|
12
|
+
import { buildIndexStreaming } from "./streaming_index_builder.js";
|
|
13
|
+
import { ParseResult } from "./step_parser.js";
|
|
14
|
+
/**
|
|
15
|
+
* Flatten the index (top-level records + their inline / multi-mapping
|
|
16
|
+
* children) into a stable, comparable column dump: every field the streaming
|
|
17
|
+
* parse must reproduce identically.
|
|
18
|
+
*
|
|
19
|
+
* @param elements The index elements.
|
|
20
|
+
* @return {object[]} A flat list of column tuples in traversal order.
|
|
21
|
+
*/
|
|
22
|
+
function columns(elements) {
|
|
23
|
+
const out = [];
|
|
24
|
+
const visit = (e) => {
|
|
25
|
+
out.push({
|
|
26
|
+
address: e.address,
|
|
27
|
+
length: e.length,
|
|
28
|
+
typeID: e.typeID,
|
|
29
|
+
expressID: e.expressID ?? null,
|
|
30
|
+
});
|
|
31
|
+
for (const child of e.inlineEntities ?? []) {
|
|
32
|
+
visit(child);
|
|
33
|
+
}
|
|
34
|
+
for (const child of e.multiMapping ?? []) {
|
|
35
|
+
visit(child);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
for (const e of elements) {
|
|
39
|
+
visit(e);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
let bytes;
|
|
44
|
+
let residentColumns;
|
|
45
|
+
let residentCount;
|
|
46
|
+
beforeAll(() => {
|
|
47
|
+
bytes = new Uint8Array(fs.readFileSync("data/index.ifc"));
|
|
48
|
+
const input = new ParsingBuffer(bytes);
|
|
49
|
+
IfcStepParser.Instance.parseHeader(input);
|
|
50
|
+
const [index, result] = IfcStepParser.Instance.parseDataBlock(input);
|
|
51
|
+
expect(result).toBe(ParseResult.COMPLETE);
|
|
52
|
+
residentColumns = columns(index.elements);
|
|
53
|
+
residentCount = index.elements.length;
|
|
54
|
+
});
|
|
55
|
+
describe("buildIndexStreaming", () => {
|
|
56
|
+
// A tiny pool relative to the 18 KB fixture forces repeated slides and at
|
|
57
|
+
// least one record straddling a window boundary; a large one takes the
|
|
58
|
+
// whole file in a single window (no slide). Both must match the resident
|
|
59
|
+
// parse exactly.
|
|
60
|
+
const POOLS = [4 * 1024, 8 * 1024, 16 * 1024, 128 * 1024];
|
|
61
|
+
test.each(POOLS)("produces the resident index at pool=%d bytes", (pool) => {
|
|
62
|
+
const source = new BufferByteSource(bytes);
|
|
63
|
+
const result = buildIndexStreaming(source, IfcStepParser.Instance, pool);
|
|
64
|
+
expect(result.result).toBe(ParseResult.COMPLETE);
|
|
65
|
+
expect(result.elements.length).toBe(residentCount);
|
|
66
|
+
expect(columns(result.elements)).toEqual(residentColumns);
|
|
67
|
+
});
|
|
68
|
+
test("the small pool actually slides the window (straddle exercised)", () => {
|
|
69
|
+
const source = new BufferByteSource(bytes);
|
|
70
|
+
const result = buildIndexStreaming(source, IfcStepParser.Instance, 4 * 1024);
|
|
71
|
+
// 18 KB fixture through a 4 KB window must slide several times.
|
|
72
|
+
expect(result.stats.slides).toBeGreaterThan(1);
|
|
73
|
+
// Physical window never grew past the pool (no record exceeds it).
|
|
74
|
+
expect(result.stats.windowBytes).toBe(4 * 1024);
|
|
75
|
+
// Every byte read once (no growth re-reads) — bounded to file size.
|
|
76
|
+
expect(result.stats.bytesRead).toBe(bytes.length);
|
|
77
|
+
});
|
|
78
|
+
test("the large pool takes the whole file in one window (no slide)", () => {
|
|
79
|
+
const source = new BufferByteSource(bytes);
|
|
80
|
+
const result = buildIndexStreaming(source, IfcStepParser.Instance, 128 * 1024);
|
|
81
|
+
expect(result.stats.slides).toBe(0);
|
|
82
|
+
expect(result.stats.maxRecordLen).toBeGreaterThan(0);
|
|
83
|
+
});
|
|
84
|
+
test("grows the window and restarts when a single record exceeds the pool", () => {
|
|
85
|
+
// Synthetic file with one record far larger than a tiny pool — the
|
|
86
|
+
// corpus never hits this (largest STEP record ~25 KB), so exercise the
|
|
87
|
+
// grow-and-restart safety valve directly. A long text literal makes one
|
|
88
|
+
// record ~12 KB; a 4 KB pool must grow past it and still index it.
|
|
89
|
+
const bigValue = "X".repeat(12000);
|
|
90
|
+
const text = "ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n" +
|
|
91
|
+
"#1=IFCWALL('a');\n" +
|
|
92
|
+
`#2=IFCPROPERTYSINGLEVALUE('${bigValue}');\n` +
|
|
93
|
+
"#3=IFCWALL('c');\nENDSEC;\n";
|
|
94
|
+
const synthetic = new TextEncoder().encode(text);
|
|
95
|
+
const resident = new ParsingBuffer(synthetic);
|
|
96
|
+
IfcStepParser.Instance.parseHeader(resident);
|
|
97
|
+
const [residentIndex, residentResult] = IfcStepParser.Instance.parseDataBlock(resident);
|
|
98
|
+
expect(residentResult).toBe(ParseResult.COMPLETE);
|
|
99
|
+
const streamed = buildIndexStreaming(new BufferByteSource(synthetic), IfcStepParser.Instance, 4 * 1024);
|
|
100
|
+
expect(streamed.result).toBe(ParseResult.COMPLETE);
|
|
101
|
+
// Window grew past the 4 KB pool to fit the ~12 KB record.
|
|
102
|
+
expect(streamed.stats.windowBytes).toBeGreaterThan(4 * 1024);
|
|
103
|
+
expect(streamed.stats.maxRecordLen).toBeGreaterThan(12000);
|
|
104
|
+
// ...and the index is still byte-identical to the resident parse.
|
|
105
|
+
expect(columns(streamed.elements)).toEqual(columns(residentIndex.elements));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StepEntityConstructorAbstract } from "../step_entity_constructor.js";
|
|
2
|
+
/**
|
|
3
|
+
* A per-record event handler. Receives a record's dense `localID` (assigned
|
|
4
|
+
* in parse order from 0), its `expressID`, and its `typeID` (0 for
|
|
5
|
+
* external-mapping records).
|
|
6
|
+
*/
|
|
7
|
+
export type RecordHandler<TypeIDType> = (localID: number, expressID: number, typeID: TypeIDType | undefined) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Routes the streaming parse's per-record events (M2) to consumers that
|
|
10
|
+
* subscribe by **type set**. A subscription's type set is the subtype closure
|
|
11
|
+
* of the entity constructors it names — the same `query` closure the type
|
|
12
|
+
* index and `expressIDsOfTypes` use (conway #383) — so `on([IfcRoot], …)`
|
|
13
|
+
* matches every product, relationship, property set and quantity, and any
|
|
14
|
+
* future IfcRoot subtype, with no whitelist to maintain.
|
|
15
|
+
*
|
|
16
|
+
* Feed `dispatcher.onRecordIndexed` to `buildIndexStreaming` /
|
|
17
|
+
* `parseDataBlockStreamed`. Handlers run synchronously in the parse path, so
|
|
18
|
+
* they must be cheap (copy ids into a compact structure); anything expensive
|
|
19
|
+
* belongs on a demand queue, not here.
|
|
20
|
+
*
|
|
21
|
+
* Because subscriptions match on the raw parse `typeID`, external-mapping /
|
|
22
|
+
* complex records (typeID 0) are only delivered via `onAnyRecord` — resolving
|
|
23
|
+
* their concrete type is the incremental-type-index consumer's job (it reads
|
|
24
|
+
* `multiMapping`), a follow-on. For the overwhelming majority of records
|
|
25
|
+
* (simple, one type each) type-set delivery is exact.
|
|
26
|
+
*/
|
|
27
|
+
export declare class StreamingRecordDispatcher<TypeIDType extends number> {
|
|
28
|
+
private readonly typed;
|
|
29
|
+
private readonly any;
|
|
30
|
+
/**
|
|
31
|
+
* Subscribe to records of the given entity types (including their
|
|
32
|
+
* subtypes).
|
|
33
|
+
*
|
|
34
|
+
* @param types The entity constructors whose subtype closures to match.
|
|
35
|
+
* @param handler Called for each matching record.
|
|
36
|
+
*/
|
|
37
|
+
on(types: StepEntityConstructorAbstract<TypeIDType>[], handler: RecordHandler<TypeIDType>): void;
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to every record regardless of type (the firehose — including
|
|
40
|
+
* external-mapping records).
|
|
41
|
+
*
|
|
42
|
+
* @param handler Called for every record.
|
|
43
|
+
*/
|
|
44
|
+
onAnyRecord(handler: RecordHandler<TypeIDType>): void;
|
|
45
|
+
/**
|
|
46
|
+
* The per-record callback to hand to the streaming parse. Dispatches each
|
|
47
|
+
* record to the matching subscribers. Bound so it can be passed directly.
|
|
48
|
+
*
|
|
49
|
+
* @param localID The record's dense local ID.
|
|
50
|
+
* @param expressID The record's express ID.
|
|
51
|
+
* @param typeID The record's type ID (0 for external-mapping records).
|
|
52
|
+
*/
|
|
53
|
+
readonly onRecordIndexed: RecordHandler<TypeIDType>;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=streaming_record_dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming_record_dispatcher.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/streaming_record_dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAA;AAG1E;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAC,UAAU,IAClC,CAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,KAAM,IAAI,CAAA;AAGhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,yBAAyB,CAAC,UAAU,SAAS,MAAM;IAE9D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAGf;IAEP,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkC;IAEtD;;;;;;OAMG;IACI,EAAE,CACL,KAAK,EAAE,6BAA6B,CAAC,UAAU,CAAC,EAAE,EAClD,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,GAAI,IAAI;IAQ9C;;;;;OAKG;IACI,WAAW,CAAE,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,GAAI,IAAI;IAI9D;;;;;;;OAOG;IACH,SAAgB,eAAe,EAAE,aAAa,CAAC,UAAU,CAAC,CAcvD;CACJ"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routes the streaming parse's per-record events (M2) to consumers that
|
|
3
|
+
* subscribe by **type set**. A subscription's type set is the subtype closure
|
|
4
|
+
* of the entity constructors it names — the same `query` closure the type
|
|
5
|
+
* index and `expressIDsOfTypes` use (conway #383) — so `on([IfcRoot], …)`
|
|
6
|
+
* matches every product, relationship, property set and quantity, and any
|
|
7
|
+
* future IfcRoot subtype, with no whitelist to maintain.
|
|
8
|
+
*
|
|
9
|
+
* Feed `dispatcher.onRecordIndexed` to `buildIndexStreaming` /
|
|
10
|
+
* `parseDataBlockStreamed`. Handlers run synchronously in the parse path, so
|
|
11
|
+
* they must be cheap (copy ids into a compact structure); anything expensive
|
|
12
|
+
* belongs on a demand queue, not here.
|
|
13
|
+
*
|
|
14
|
+
* Because subscriptions match on the raw parse `typeID`, external-mapping /
|
|
15
|
+
* complex records (typeID 0) are only delivered via `onAnyRecord` — resolving
|
|
16
|
+
* their concrete type is the incremental-type-index consumer's job (it reads
|
|
17
|
+
* `multiMapping`), a follow-on. For the overwhelming majority of records
|
|
18
|
+
* (simple, one type each) type-set delivery is exact.
|
|
19
|
+
*/
|
|
20
|
+
export class StreamingRecordDispatcher {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.typed = [];
|
|
23
|
+
this.any = [];
|
|
24
|
+
/**
|
|
25
|
+
* The per-record callback to hand to the streaming parse. Dispatches each
|
|
26
|
+
* record to the matching subscribers. Bound so it can be passed directly.
|
|
27
|
+
*
|
|
28
|
+
* @param localID The record's dense local ID.
|
|
29
|
+
* @param expressID The record's express ID.
|
|
30
|
+
* @param typeID The record's type ID (0 for external-mapping records).
|
|
31
|
+
*/
|
|
32
|
+
this.onRecordIndexed = (localID, expressID, typeID) => {
|
|
33
|
+
for (const handler of this.any) {
|
|
34
|
+
handler(localID, expressID, typeID);
|
|
35
|
+
}
|
|
36
|
+
if (typeID !== void 0) {
|
|
37
|
+
for (const { set, handler } of this.typed) {
|
|
38
|
+
if (set.has(typeID)) {
|
|
39
|
+
handler(localID, expressID, typeID);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Subscribe to records of the given entity types (including their
|
|
47
|
+
* subtypes).
|
|
48
|
+
*
|
|
49
|
+
* @param types The entity constructors whose subtype closures to match.
|
|
50
|
+
* @param handler Called for each matching record.
|
|
51
|
+
*/
|
|
52
|
+
on(types, handler) {
|
|
53
|
+
this.typed.push({
|
|
54
|
+
set: new Set(types.flatMap((type) => type.query)),
|
|
55
|
+
handler,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Subscribe to every record regardless of type (the firehose — including
|
|
60
|
+
* external-mapping records).
|
|
61
|
+
*
|
|
62
|
+
* @param handler Called for every record.
|
|
63
|
+
*/
|
|
64
|
+
onAnyRecord(handler) {
|
|
65
|
+
this.any.push(handler);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming_record_dispatcher.test.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/streaming_record_dispatcher.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
// M2 core: record events emitted live from the streaming parse, routed by a
|
|
3
|
+
// type-set subscription, let a consumer build a semantic structure
|
|
4
|
+
// incrementally — and it must match what the finished model's type index
|
|
5
|
+
// yields. Here: a roots registry collected from the event stream equals
|
|
6
|
+
// `expressIDsOfTypes(IfcRoot)` on the resident model.
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import { beforeAll, describe, expect, test } from "@jest/globals";
|
|
9
|
+
import ParsingBuffer from "../../parsing/parsing_buffer.js";
|
|
10
|
+
import IfcStepParser from "../../ifc/ifc_step_parser.js";
|
|
11
|
+
import { BufferByteSource } from "./byte_source.js";
|
|
12
|
+
import { buildIndexStreaming } from "./streaming_index_builder.js";
|
|
13
|
+
import { StreamingRecordDispatcher } from "./streaming_record_dispatcher.js";
|
|
14
|
+
import { ParseResult } from "./step_parser.js";
|
|
15
|
+
import { IfcRoot, IfcProduct } from "../../ifc/ifc4_gen/index.js";
|
|
16
|
+
let bytes;
|
|
17
|
+
let residentRoots;
|
|
18
|
+
let residentProducts;
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
bytes = new Uint8Array(fs.readFileSync("data/index.ifc"));
|
|
21
|
+
const input = new ParsingBuffer(bytes);
|
|
22
|
+
IfcStepParser.Instance.parseHeader(input);
|
|
23
|
+
const [, model] = IfcStepParser.Instance.parseDataToModel(input);
|
|
24
|
+
residentRoots = new Set(model.expressIDsOfTypes(IfcRoot));
|
|
25
|
+
residentProducts = new Set(model.expressIDsOfTypes(IfcProduct));
|
|
26
|
+
});
|
|
27
|
+
describe("StreamingRecordDispatcher", () => {
|
|
28
|
+
test("a roots registry built live from the event stream matches the type index", () => {
|
|
29
|
+
const roots = [];
|
|
30
|
+
const products = new Set();
|
|
31
|
+
const dispatcher = new StreamingRecordDispatcher();
|
|
32
|
+
dispatcher.on([IfcRoot], (_localID, expressID) => roots.push(expressID));
|
|
33
|
+
dispatcher.on([IfcProduct], (_localID, expressID) => products.add(expressID));
|
|
34
|
+
const result = buildIndexStreaming(new BufferByteSource(bytes), IfcStepParser.Instance, 4 * 1024, // tiny pool → the parse slides; events still fire per record
|
|
35
|
+
dispatcher.onRecordIndexed);
|
|
36
|
+
expect(result.result).toBe(ParseResult.COMPLETE);
|
|
37
|
+
// index.ifc has no external-mapping records, so raw-typeID delivery is
|
|
38
|
+
// exact vs. the type index.
|
|
39
|
+
expect(new Set(roots)).toEqual(residentRoots);
|
|
40
|
+
expect(products).toEqual(residentProducts);
|
|
41
|
+
// Products are a strict subset of roots (IfcProduct ⊂ IfcRoot).
|
|
42
|
+
expect(residentProducts.size).toBeLessThan(residentRoots.size);
|
|
43
|
+
for (const p of products) {
|
|
44
|
+
expect(residentRoots.has(p)).toBe(true);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
test("localIDs arrive dense and in ascending parse order", () => {
|
|
48
|
+
const localIDs = [];
|
|
49
|
+
const dispatcher = new StreamingRecordDispatcher();
|
|
50
|
+
dispatcher.onAnyRecord((localID) => localIDs.push(localID));
|
|
51
|
+
buildIndexStreaming(new BufferByteSource(bytes), IfcStepParser.Instance, 128 * 1024, dispatcher.onRecordIndexed);
|
|
52
|
+
expect(localIDs.length).toBeGreaterThan(10);
|
|
53
|
+
// 0,1,2,…,n-1 in order.
|
|
54
|
+
expect(localIDs).toEqual(localIDs.map((_v, i) => i));
|
|
55
|
+
});
|
|
56
|
+
test("onAnyRecord sees every record; a type filter sees a subset", () => {
|
|
57
|
+
let all = 0;
|
|
58
|
+
let rootsCount = 0;
|
|
59
|
+
const dispatcher = new StreamingRecordDispatcher();
|
|
60
|
+
dispatcher.onAnyRecord(() => ++all);
|
|
61
|
+
dispatcher.on([IfcRoot], () => ++rootsCount);
|
|
62
|
+
buildIndexStreaming(new BufferByteSource(bytes), IfcStepParser.Instance, 128 * 1024, dispatcher.onRecordIndexed);
|
|
63
|
+
expect(all).toBeGreaterThan(rootsCount);
|
|
64
|
+
expect(rootsCount).toBe(residentRoots.size);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StepIndexEntry } from "./parsing/step_parser.js";
|
|
2
|
-
import { StepExternalByteStore } from "./step_buffer_provider.js";
|
|
2
|
+
import { StepBufferProvider, StepExternalByteStore } from "./step_buffer_provider.js";
|
|
3
3
|
import StepEntityBase from "./step_entity_base.js";
|
|
4
4
|
import StepEntitySchema from "./step_entity_schema.js";
|
|
5
5
|
import StepEntityInternalReference from "./step_entity_internal_reference.js";
|
|
@@ -52,11 +52,17 @@ export default abstract class StepModelBase<EntityTypeIDs extends number, BaseEn
|
|
|
52
52
|
* Construct this step model with its matching schema, a buffer to read from and an element index.
|
|
53
53
|
*
|
|
54
54
|
* @param schema The Step schema this is based on.
|
|
55
|
-
* @param buffer_ The buffer to read this from.
|
|
55
|
+
* @param buffer_ The buffer to read this from. Pass `undefined` together
|
|
56
|
+
* with `provider` to build a model whose source is windowed from
|
|
57
|
+
* construction (e.g. a streaming open — see buildModelStreaming); the
|
|
58
|
+
* synchronous read paths (geometry extraction) then require the relevant
|
|
59
|
+
* ranges to be resident, exactly as after `spillSourceToExternalStore`.
|
|
56
60
|
* @param elementIndex The element index for this, parsed or deserialized - note this takes
|
|
57
61
|
* ownership of this array in the sense it will modify values/unfold inline elements etc.
|
|
62
|
+
* @param provider Optional pre-built buffer provider. When omitted, a
|
|
63
|
+
* resident provider over `buffer_` is used (the classic path).
|
|
58
64
|
*/
|
|
59
|
-
constructor(schema: StepEntitySchema<EntityTypeIDs, BaseEntity>, buffer_: Uint8Array | undefined, elementIndex: StepIndexEntry<EntityTypeIDs>[]);
|
|
65
|
+
constructor(schema: StepEntitySchema<EntityTypeIDs, BaseEntity>, buffer_: Uint8Array | undefined, elementIndex: StepIndexEntry<EntityTypeIDs>[], provider?: StepBufferProvider);
|
|
60
66
|
/**
|
|
61
67
|
* Materialise the descriptor object for a local ID from the columns. Only the
|
|
62
68
|
* persistent scalars are set; lazy fields (vtable, buffer, entity) are filled
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step_model_base.d.ts","sourceRoot":"","sources":["../../../src/step/step_model_base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEtD,OAAO,
|
|
1
|
+
{"version":3,"file":"step_model_base.d.ts","sourceRoot":"","sources":["../../../src/step/step_model_base.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,11 +12,17 @@ export default class StepModelBase {
|
|
|
12
12
|
* Construct this step model with its matching schema, a buffer to read from and an element index.
|
|
13
13
|
*
|
|
14
14
|
* @param schema The Step schema this is based on.
|
|
15
|
-
* @param buffer_ The buffer to read this from.
|
|
15
|
+
* @param buffer_ The buffer to read this from. Pass `undefined` together
|
|
16
|
+
* with `provider` to build a model whose source is windowed from
|
|
17
|
+
* construction (e.g. a streaming open — see buildModelStreaming); the
|
|
18
|
+
* synchronous read paths (geometry extraction) then require the relevant
|
|
19
|
+
* ranges to be resident, exactly as after `spillSourceToExternalStore`.
|
|
16
20
|
* @param elementIndex The element index for this, parsed or deserialized - note this takes
|
|
17
21
|
* ownership of this array in the sense it will modify values/unfold inline elements etc.
|
|
22
|
+
* @param provider Optional pre-built buffer provider. When omitted, a
|
|
23
|
+
* resident provider over `buffer_` is used (the classic path).
|
|
18
24
|
*/
|
|
19
|
-
constructor(schema, buffer_, elementIndex) {
|
|
25
|
+
constructor(schema, buffer_, elementIndex, provider) {
|
|
20
26
|
this.schema = schema;
|
|
21
27
|
this.buffer_ = buffer_;
|
|
22
28
|
this.vtableBuilder_ = new StepVtableBuilder();
|
|
@@ -39,7 +45,7 @@ export default class StepModelBase {
|
|
|
39
45
|
* error, and the field is optional, return null instead of throwing an exception.
|
|
40
46
|
*/
|
|
41
47
|
this.nullOnErrors = true;
|
|
42
|
-
this.bufferProvider_ = new ResidentStepBufferProvider(buffer_);
|
|
48
|
+
this.bufferProvider_ = provider ?? new ResidentStepBufferProvider(buffer_);
|
|
43
49
|
const localElementIndex = elementIndex;
|
|
44
50
|
let where = 0;
|
|
45
51
|
const firstInlineElement = localElementIndex.length;
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
// only the first segment (major) is meaningful and is the one CI carries forward.
|
|
6
6
|
// Must stay in `vN.N.N` shape: the CI stamp regex, scripts/updateVersion.mjs, and
|
|
7
7
|
// statistics.ts all match `v\d+\.\d+\.\d+`.
|
|
8
|
-
const versionString = 'Conway v1.
|
|
8
|
+
const versionString = 'Conway v1.401.1237';
|
|
9
9
|
export { versionString };
|