@bldrs-ai/conway 1.400.1226 → 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 +13 -6
- package/compiled/examples/cli-bundled.cjs +42 -35
- package/compiled/examples/cli-step-bundled.cjs +40 -33
- package/compiled/examples/validator-bundled.cjs +13 -6
- 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 +6 -1
- package/compiled/src/step/parsing/step_parser.d.ts.map +1 -1
- package/compiled/src/step/parsing/step_parser.js +10 -3
- package/compiled/src/step/parsing/streaming_index_builder.d.ts +11 -1
- package/compiled/src/step/parsing/streaming_index_builder.d.ts.map +1 -1
- package/compiled/src/step/parsing/streaming_index_builder.js +12 -2
- 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/version/version.js +1 -1
- package/compiled/tsconfig.tsbuildinfo +1 -1
- 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.
|
|
947
|
+
var versionString = "Conway v1.401.1237";
|
|
948
948
|
|
|
949
949
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
950
950
|
var wasmType = "";
|
|
@@ -3493,11 +3493,16 @@ var StepParser = class extends StepHeaderParser {
|
|
|
3493
3493
|
* @param input The input parsing buffer, positioned at the data section.
|
|
3494
3494
|
* @param onRecordBoundary Called at each top-level record boundary with the
|
|
3495
3495
|
* buffer; the callback may rebase the buffer's window in place.
|
|
3496
|
+
* @param onRecordIndexed Called as each top-level record is indexed, with
|
|
3497
|
+
* its localID, expressID and typeID (0 for external-mapping records) — the
|
|
3498
|
+
* seam for incremental semantic consumers (type index, roots registry,
|
|
3499
|
+
* names skeleton). Must be synchronous and cheap; expensive work belongs on
|
|
3500
|
+
* a demand queue, not the parse path.
|
|
3496
3501
|
* @param onProgress Optional byte-cursor progress callback.
|
|
3497
3502
|
* @return {BlockParseResult} The parsing result, including the index and result enum.
|
|
3498
3503
|
*/
|
|
3499
|
-
parseDataBlockStreamed(input, onRecordBoundary, onProgress) {
|
|
3500
|
-
const parser211 = this.parseDataBlockIncremental(input, onRecordBoundary);
|
|
3504
|
+
parseDataBlockStreamed(input, onRecordBoundary, onRecordIndexed, onProgress) {
|
|
3505
|
+
const parser211 = this.parseDataBlockIncremental(input, onRecordBoundary, onRecordIndexed);
|
|
3501
3506
|
while (true) {
|
|
3502
3507
|
const next = parser211.next();
|
|
3503
3508
|
if (next.done === true) {
|
|
@@ -3543,7 +3548,7 @@ var StepParser = class extends StepHeaderParser {
|
|
|
3543
3548
|
* @yields {number} The current byte cursor within the input buffer.
|
|
3544
3549
|
* @return {BlockParseResult} The parsing result, including the index and result enum.
|
|
3545
3550
|
*/
|
|
3546
|
-
*parseDataBlockIncremental(input, onRecordBoundary) {
|
|
3551
|
+
*parseDataBlockIncremental(input, onRecordBoundary, onRecordIndexed) {
|
|
3547
3552
|
const indexResult = { elements: [] };
|
|
3548
3553
|
const match = input.match;
|
|
3549
3554
|
const comment = () => match(commentParser2);
|
|
@@ -3720,6 +3725,7 @@ var StepParser = class extends StepHeaderParser {
|
|
|
3720
3725
|
if (!charws(SEMICOLON)) {
|
|
3721
3726
|
return syntaxError();
|
|
3722
3727
|
}
|
|
3728
|
+
onRecordIndexed?.(indexResult.elements.length, expressID, 0);
|
|
3723
3729
|
indexResult.elements.push({
|
|
3724
3730
|
address: startElement2,
|
|
3725
3731
|
length: input.address - startElement2,
|
|
@@ -3811,6 +3817,7 @@ var StepParser = class extends StepHeaderParser {
|
|
|
3811
3817
|
if (!charws(SEMICOLON)) {
|
|
3812
3818
|
return syntaxError();
|
|
3813
3819
|
}
|
|
3820
|
+
onRecordIndexed?.(indexResult.elements.length, expressID, foundItem);
|
|
3814
3821
|
indexResult.elements.push({
|
|
3815
3822
|
address: startElement,
|
|
3816
3823
|
length: input.address - startElement,
|
|
@@ -70774,7 +70781,7 @@ var IfcStepModel = class extends StepModelBase {
|
|
|
70774
70781
|
};
|
|
70775
70782
|
|
|
70776
70783
|
// compiled/src/step/parsing/streaming_index_builder.js
|
|
70777
|
-
function buildIndexStreaming(source, parser211, pool3) {
|
|
70784
|
+
function buildIndexStreaming(source, parser211, pool3, onRecordIndexed) {
|
|
70778
70785
|
const fileSize = source.byteLength;
|
|
70779
70786
|
let windowBytes = Math.max(pool3, MIN_WINDOW);
|
|
70780
70787
|
for (; ; ) {
|
|
@@ -70820,7 +70827,7 @@ function buildIndexStreaming(source, parser211, pool3) {
|
|
|
70820
70827
|
buffer.rebaseWindow(window2, 0, windowLen, windowStartFile);
|
|
70821
70828
|
++slides;
|
|
70822
70829
|
};
|
|
70823
|
-
const [index, result] = parser211.parseDataBlockStreamed(input, onRecordBoundary);
|
|
70830
|
+
const [index, result] = parser211.parseDataBlockStreamed(input, onRecordBoundary, onRecordIndexed);
|
|
70824
70831
|
const stoppedShort = result !== ParseResult.COMPLETE;
|
|
70825
70832
|
const notAtEof = windowStartFile + windowLen < fileSize;
|
|
70826
70833
|
if (stoppedShort && notAtEof) {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { StepEntityConstructorAbstract } from "../step_entity_constructor.js";
|
|
2
|
+
import { RecordHandler } from "./streaming_record_dispatcher.js";
|
|
3
|
+
/**
|
|
4
|
+
* A type index built **incrementally** from the streaming parse's per-record
|
|
5
|
+
* events (M2), rather than from the finished element array. Feed its
|
|
6
|
+
* {@link handler} to a {@link StreamingRecordDispatcher} (or directly to
|
|
7
|
+
* `buildIndexStreaming`'s `onRecordIndexed`) and it accumulates, per concrete
|
|
8
|
+
* type, the set of express IDs seen — so the moment parsing reaches a record
|
|
9
|
+
* it is queryable, without waiting for end-of-parse.
|
|
10
|
+
*
|
|
11
|
+
* Queries take entity constructors and expand to their subtype closure via
|
|
12
|
+
* the generated `query` (conway #383), so `expressIDsOfTypes(IfcProduct)`
|
|
13
|
+
* unions every product subtype exactly as the resident model's type index
|
|
14
|
+
* does.
|
|
15
|
+
*
|
|
16
|
+
* Express IDs are held in per-type `Set`s, so the consumer is idempotent
|
|
17
|
+
* under the streaming builder's rare grow-and-restart (records re-fire from
|
|
18
|
+
* localID 0): re-adding an express ID is a no-op. Memory is O(records) — the
|
|
19
|
+
* same order as the element index it mirrors.
|
|
20
|
+
*
|
|
21
|
+
* Scope: membership is keyed on the raw parse `typeID`. External-mapping /
|
|
22
|
+
* complex records (typeID 0) are therefore not attributed to their mapped
|
|
23
|
+
* classes here — resolving those needs the record's `multiMapping`, which the
|
|
24
|
+
* event stream doesn't carry; that's the model's construction-time index. For
|
|
25
|
+
* the overwhelming majority of records (simple, one type each) this index is
|
|
26
|
+
* exact, which the parity test pins.
|
|
27
|
+
*/
|
|
28
|
+
export declare class IncrementalTypeIndex<TypeIDType extends number> {
|
|
29
|
+
/** Concrete typeID → express IDs of records of exactly that type. */
|
|
30
|
+
private readonly byType_;
|
|
31
|
+
/**
|
|
32
|
+
* Record one indexed entity. Bound as {@link handler} for direct use as a
|
|
33
|
+
* dispatcher subscription / `onRecordIndexed` callback.
|
|
34
|
+
*
|
|
35
|
+
* @param localID Unused (kept for the RecordHandler shape).
|
|
36
|
+
* @param expressID The record's express ID.
|
|
37
|
+
* @param typeID The record's concrete type ID (undefined / 0 records are
|
|
38
|
+
* ignored — they carry no concrete queryable type here).
|
|
39
|
+
*/
|
|
40
|
+
readonly handler: RecordHandler<TypeIDType>;
|
|
41
|
+
/**
|
|
42
|
+
* The distinct concrete type IDs seen so far.
|
|
43
|
+
*
|
|
44
|
+
* @return {IterableIterator<TypeIDType>} The concrete types.
|
|
45
|
+
*/
|
|
46
|
+
concreteTypes(): IterableIterator<TypeIDType>;
|
|
47
|
+
/**
|
|
48
|
+
* Lazily iterate the express IDs of all records of the given types
|
|
49
|
+
* (including subtypes).
|
|
50
|
+
*
|
|
51
|
+
* @param types The entity constructors to query (subtype closures unioned).
|
|
52
|
+
* @return {IterableIterator<number>} Express IDs of matching records.
|
|
53
|
+
* @yields {number} Each matching express ID.
|
|
54
|
+
*/
|
|
55
|
+
expressIDsOfTypes(...types: StepEntityConstructorAbstract<TypeIDType>[]): IterableIterator<number>;
|
|
56
|
+
/**
|
|
57
|
+
* Count records of the given types (including subtypes) seen so far.
|
|
58
|
+
*
|
|
59
|
+
* @param types The entity constructors to count.
|
|
60
|
+
* @return {number} The number of matching records.
|
|
61
|
+
*/
|
|
62
|
+
count(...types: StepEntityConstructorAbstract<TypeIDType>[]): number;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=incremental_type_index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental_type_index.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/incremental_type_index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAG7D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,oBAAoB,CAAC,UAAU,SAAS,MAAM;IAEzD,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAE7D;;;;;;;;OAQG;IACH,SAAgB,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAe/C;IAEH;;;;OAIG;IACI,aAAa,IAAI,gBAAgB,CAAC,UAAU,CAAC;IAIpD;;;;;;;OAOG;IACK,iBAAiB,CACrB,GAAG,KAAK,EAAE,6BAA6B,CAAC,UAAU,CAAC,EAAE,GACrD,gBAAgB,CAAC,MAAM,CAAC;IAc5B;;;;;OAKG;IACI,KAAK,CAAE,GAAG,KAAK,EAAE,6BAA6B,CAAC,UAAU,CAAC,EAAE,GAAI,MAAM;CAa9E"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type index built **incrementally** from the streaming parse's per-record
|
|
3
|
+
* events (M2), rather than from the finished element array. Feed its
|
|
4
|
+
* {@link handler} to a {@link StreamingRecordDispatcher} (or directly to
|
|
5
|
+
* `buildIndexStreaming`'s `onRecordIndexed`) and it accumulates, per concrete
|
|
6
|
+
* type, the set of express IDs seen — so the moment parsing reaches a record
|
|
7
|
+
* it is queryable, without waiting for end-of-parse.
|
|
8
|
+
*
|
|
9
|
+
* Queries take entity constructors and expand to their subtype closure via
|
|
10
|
+
* the generated `query` (conway #383), so `expressIDsOfTypes(IfcProduct)`
|
|
11
|
+
* unions every product subtype exactly as the resident model's type index
|
|
12
|
+
* does.
|
|
13
|
+
*
|
|
14
|
+
* Express IDs are held in per-type `Set`s, so the consumer is idempotent
|
|
15
|
+
* under the streaming builder's rare grow-and-restart (records re-fire from
|
|
16
|
+
* localID 0): re-adding an express ID is a no-op. Memory is O(records) — the
|
|
17
|
+
* same order as the element index it mirrors.
|
|
18
|
+
*
|
|
19
|
+
* Scope: membership is keyed on the raw parse `typeID`. External-mapping /
|
|
20
|
+
* complex records (typeID 0) are therefore not attributed to their mapped
|
|
21
|
+
* classes here — resolving those needs the record's `multiMapping`, which the
|
|
22
|
+
* event stream doesn't carry; that's the model's construction-time index. For
|
|
23
|
+
* the overwhelming majority of records (simple, one type each) this index is
|
|
24
|
+
* exact, which the parity test pins.
|
|
25
|
+
*/
|
|
26
|
+
export class IncrementalTypeIndex {
|
|
27
|
+
constructor() {
|
|
28
|
+
/** Concrete typeID → express IDs of records of exactly that type. */
|
|
29
|
+
this.byType_ = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* Record one indexed entity. Bound as {@link handler} for direct use as a
|
|
32
|
+
* dispatcher subscription / `onRecordIndexed` callback.
|
|
33
|
+
*
|
|
34
|
+
* @param localID Unused (kept for the RecordHandler shape).
|
|
35
|
+
* @param expressID The record's express ID.
|
|
36
|
+
* @param typeID The record's concrete type ID (undefined / 0 records are
|
|
37
|
+
* ignored — they carry no concrete queryable type here).
|
|
38
|
+
*/
|
|
39
|
+
this.handler = (localID, expressID, typeID) => {
|
|
40
|
+
if (typeID === void 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let ids = this.byType_.get(typeID);
|
|
44
|
+
if (ids === void 0) {
|
|
45
|
+
ids = new Set();
|
|
46
|
+
this.byType_.set(typeID, ids);
|
|
47
|
+
}
|
|
48
|
+
ids.add(expressID);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The distinct concrete type IDs seen so far.
|
|
53
|
+
*
|
|
54
|
+
* @return {IterableIterator<TypeIDType>} The concrete types.
|
|
55
|
+
*/
|
|
56
|
+
concreteTypes() {
|
|
57
|
+
return this.byType_.keys();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Lazily iterate the express IDs of all records of the given types
|
|
61
|
+
* (including subtypes).
|
|
62
|
+
*
|
|
63
|
+
* @param types The entity constructors to query (subtype closures unioned).
|
|
64
|
+
* @return {IterableIterator<number>} Express IDs of matching records.
|
|
65
|
+
* @yields {number} Each matching express ID.
|
|
66
|
+
*/
|
|
67
|
+
*expressIDsOfTypes(...types) {
|
|
68
|
+
const typeSet = types.length === 1 ? types[0].query :
|
|
69
|
+
new Set(types.flatMap((type) => type.query));
|
|
70
|
+
for (const typeID of typeSet) {
|
|
71
|
+
const ids = this.byType_.get(typeID);
|
|
72
|
+
if (ids !== void 0) {
|
|
73
|
+
yield* ids;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Count records of the given types (including subtypes) seen so far.
|
|
79
|
+
*
|
|
80
|
+
* @param types The entity constructors to count.
|
|
81
|
+
* @return {number} The number of matching records.
|
|
82
|
+
*/
|
|
83
|
+
count(...types) {
|
|
84
|
+
const typeSet = types.length === 1 ? types[0].query :
|
|
85
|
+
new Set(types.flatMap((type) => type.query));
|
|
86
|
+
let total = 0;
|
|
87
|
+
for (const typeID of typeSet) {
|
|
88
|
+
total += this.byType_.get(typeID)?.size ?? 0;
|
|
89
|
+
}
|
|
90
|
+
return total;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental_type_index.test.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/incremental_type_index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
// M2b: a type index built incrementally from the streaming parse's record
|
|
3
|
+
// events must, once parsing finishes, hold the same membership as the
|
|
4
|
+
// resident model's type index — for every queried type (and its subtypes).
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import { beforeAll, describe, expect, test } from "@jest/globals";
|
|
7
|
+
import ParsingBuffer from "../../parsing/parsing_buffer.js";
|
|
8
|
+
import IfcStepParser from "../../ifc/ifc_step_parser.js";
|
|
9
|
+
import { BufferByteSource } from "./byte_source.js";
|
|
10
|
+
import { buildIndexStreaming } from "./streaming_index_builder.js";
|
|
11
|
+
import { IncrementalTypeIndex } from "./incremental_type_index.js";
|
|
12
|
+
import { StreamingRecordDispatcher } from "./streaming_record_dispatcher.js";
|
|
13
|
+
import { ParseResult } from "./step_parser.js";
|
|
14
|
+
import { IfcRoot, IfcProduct, IfcWall, IfcPropertySet } from "../../ifc/ifc4_gen/index.js";
|
|
15
|
+
let bytes;
|
|
16
|
+
let model;
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
bytes = new Uint8Array(fs.readFileSync("data/index.ifc"));
|
|
19
|
+
const input = new ParsingBuffer(bytes);
|
|
20
|
+
IfcStepParser.Instance.parseHeader(input);
|
|
21
|
+
model = IfcStepParser.Instance.parseDataToModel(input)[1];
|
|
22
|
+
});
|
|
23
|
+
describe("IncrementalTypeIndex", () => {
|
|
24
|
+
/**
|
|
25
|
+
* Stream index.ifc, feeding an IncrementalTypeIndex, and return it.
|
|
26
|
+
*
|
|
27
|
+
* @return {IncrementalTypeIndex} The populated index.
|
|
28
|
+
*/
|
|
29
|
+
function streamed() {
|
|
30
|
+
const index = new IncrementalTypeIndex();
|
|
31
|
+
const dispatcher = new StreamingRecordDispatcher();
|
|
32
|
+
dispatcher.onAnyRecord(index.handler);
|
|
33
|
+
const r = buildIndexStreaming(new BufferByteSource(bytes), IfcStepParser.Instance, 4 * 1024, dispatcher.onRecordIndexed);
|
|
34
|
+
expect(r.result).toBe(ParseResult.COMPLETE);
|
|
35
|
+
return index;
|
|
36
|
+
}
|
|
37
|
+
test.each([
|
|
38
|
+
["IfcRoot", IfcRoot],
|
|
39
|
+
["IfcProduct", IfcProduct],
|
|
40
|
+
["IfcWall", IfcWall],
|
|
41
|
+
["IfcPropertySet", IfcPropertySet],
|
|
42
|
+
])("membership matches the resident type index for %s", (_name, ctor) => {
|
|
43
|
+
const index = streamed();
|
|
44
|
+
const incremental = new Set(index.expressIDsOfTypes(ctor));
|
|
45
|
+
const resident = new Set(model.expressIDsOfTypes(ctor));
|
|
46
|
+
expect(incremental).toEqual(resident);
|
|
47
|
+
expect(index.count(ctor)).toBe(resident.size);
|
|
48
|
+
});
|
|
49
|
+
test("a multi-type query unions the subtype closures", () => {
|
|
50
|
+
const index = streamed();
|
|
51
|
+
const incremental = new Set(index.expressIDsOfTypes(IfcWall, IfcPropertySet));
|
|
52
|
+
const resident = new Set(model.expressIDsOfTypes(IfcWall, IfcPropertySet));
|
|
53
|
+
expect(incremental).toEqual(resident);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -126,10 +126,15 @@ export default class StepParser<TypeIDType> extends StepHeaderParser {
|
|
|
126
126
|
* @param input The input parsing buffer, positioned at the data section.
|
|
127
127
|
* @param onRecordBoundary Called at each top-level record boundary with the
|
|
128
128
|
* buffer; the callback may rebase the buffer's window in place.
|
|
129
|
+
* @param onRecordIndexed Called as each top-level record is indexed, with
|
|
130
|
+
* its localID, expressID and typeID (0 for external-mapping records) — the
|
|
131
|
+
* seam for incremental semantic consumers (type index, roots registry,
|
|
132
|
+
* names skeleton). Must be synchronous and cheap; expensive work belongs on
|
|
133
|
+
* a demand queue, not the parse path.
|
|
129
134
|
* @param onProgress Optional byte-cursor progress callback.
|
|
130
135
|
* @return {BlockParseResult} The parsing result, including the index and result enum.
|
|
131
136
|
*/
|
|
132
|
-
parseDataBlockStreamed(input: ParsingBuffer, onRecordBoundary: (input: ParsingBuffer) => void, onProgress?: ParseProgressCallback): BlockParseResult<TypeIDType>;
|
|
137
|
+
parseDataBlockStreamed(input: ParsingBuffer, onRecordBoundary: (input: ParsingBuffer) => void, onRecordIndexed?: (localID: number, expressID: number, typeID: TypeIDType | undefined) => void, onProgress?: ParseProgressCallback): BlockParseResult<TypeIDType>;
|
|
133
138
|
/**
|
|
134
139
|
* Cooperative variant of parseDataBlock: identical parse (same generator
|
|
135
140
|
* body), but periodically awaits a macrotask so the event loop can run —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step_parser.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/step_parser.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,2BAA2B,CAAA;AAEjD,OAAO,aAAa,MAAM,8BAA8B,CAAA;AAcxD,OAAO,iBAAiB,EAAE,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEpE,MAAM,WAAW,kBAAkB,CAAC,UAAU;IAE5C,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAA;IAE/C,cAAc,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAA;CACpD;AAED,MAAM,WAAW,oBAAoB,CAAC,UAAU,CAAE,SAAQ,kBAAkB,CAAC,UAAU,CAAC;IAEtF,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB;AAED,MAAM,WAAW,cAAc,CAAC,UAAU,CAAE,SAAQ,kBAAkB,CAAC,UAAU,CAAC;IAEhF,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS,CAAC,UAAU;IAEnC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,CAAA;CAEvC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,GAAG,CAAA;CACX;AAID;;GAEG;AAGH,oBAAY,WAAW;IAErB,QAAQ,IAAI;IACZ,UAAU,IAAI;IACd,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB,YAAY,IAAI;CACjB;AAyBD,MAAM,MAAM,gBAAgB,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,CAAA;AAC/E,MAAM,MAAM,uBAAuB,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,CAAA;AAEtF;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAE,WAAW,EAAE,MAAM,KAAM,IAAI,CAAA;AAWnE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC7B;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;AAGzD;;GAEG;AACH,qBAAa,gBAAgB;IAE3B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAkB;IAE3C;;;;OAIG;IACH,WAAkB,QAAQ,IAAI,gBAAgB,CAQ7C;IAED;;;;;OAKG;IACI,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,iBAAiB;CA0L5D;AACD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,UAAU,CAAC,UAAU,CAAE,SAAQ,gBAAgB;IAQtD,OAAO,CAAC,QAAQ,CAAC,MAAM;IANnC;;;;;OAKG;gBAC0B,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAKpE;;;;;;;;;;;;;OAaG;IACI,gBAAgB,CACnB,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;IA+D9E;;;;;;;;;;OAUG;IACI,cAAc,CACjB,KAAK,EAAE,aAAa,EACpB,UAAU,CAAC,EAAE,qBAAqB,GAAI,gBAAgB,CAAC,UAAU,CAAC;IAgBtE
|
|
1
|
+
{"version":3,"file":"step_parser.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/step_parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -320,11 +320,16 @@ export default class StepParser extends StepHeaderParser {
|
|
|
320
320
|
* @param input The input parsing buffer, positioned at the data section.
|
|
321
321
|
* @param onRecordBoundary Called at each top-level record boundary with the
|
|
322
322
|
* buffer; the callback may rebase the buffer's window in place.
|
|
323
|
+
* @param onRecordIndexed Called as each top-level record is indexed, with
|
|
324
|
+
* its localID, expressID and typeID (0 for external-mapping records) — the
|
|
325
|
+
* seam for incremental semantic consumers (type index, roots registry,
|
|
326
|
+
* names skeleton). Must be synchronous and cheap; expensive work belongs on
|
|
327
|
+
* a demand queue, not the parse path.
|
|
323
328
|
* @param onProgress Optional byte-cursor progress callback.
|
|
324
329
|
* @return {BlockParseResult} The parsing result, including the index and result enum.
|
|
325
330
|
*/
|
|
326
|
-
parseDataBlockStreamed(input, onRecordBoundary, onProgress) {
|
|
327
|
-
const parser = this.parseDataBlockIncremental(input, onRecordBoundary);
|
|
331
|
+
parseDataBlockStreamed(input, onRecordBoundary, onRecordIndexed, onProgress) {
|
|
332
|
+
const parser = this.parseDataBlockIncremental(input, onRecordBoundary, onRecordIndexed);
|
|
328
333
|
while (true) {
|
|
329
334
|
const next = parser.next();
|
|
330
335
|
if (next.done === true) {
|
|
@@ -370,7 +375,7 @@ export default class StepParser extends StepHeaderParser {
|
|
|
370
375
|
* @yields {number} The current byte cursor within the input buffer.
|
|
371
376
|
* @return {BlockParseResult} The parsing result, including the index and result enum.
|
|
372
377
|
*/
|
|
373
|
-
*parseDataBlockIncremental(input, onRecordBoundary) {
|
|
378
|
+
*parseDataBlockIncremental(input, onRecordBoundary, onRecordIndexed) {
|
|
374
379
|
const indexResult = { elements: [] };
|
|
375
380
|
const match = input.match;
|
|
376
381
|
const comment = () => match(commentParser);
|
|
@@ -564,6 +569,7 @@ export default class StepParser extends StepHeaderParser {
|
|
|
564
569
|
if (!charws(SEMICOLON)) {
|
|
565
570
|
return syntaxError();
|
|
566
571
|
}
|
|
572
|
+
onRecordIndexed?.(indexResult.elements.length, expressID, 0);
|
|
567
573
|
indexResult.elements.push({
|
|
568
574
|
address: startElement,
|
|
569
575
|
length: input.address - startElement,
|
|
@@ -657,6 +663,7 @@ export default class StepParser extends StepHeaderParser {
|
|
|
657
663
|
if (!charws(SEMICOLON)) {
|
|
658
664
|
return syntaxError();
|
|
659
665
|
}
|
|
666
|
+
onRecordIndexed?.(indexResult.elements.length, expressID, foundItem);
|
|
660
667
|
indexResult.elements.push({
|
|
661
668
|
address: startElement,
|
|
662
669
|
length: input.address - startElement,
|
|
@@ -54,7 +54,17 @@ export interface StreamingIndexResult<TypeIDType> {
|
|
|
54
54
|
* @param source The byte source.
|
|
55
55
|
* @param parser The STEP parser (typed to the schema).
|
|
56
56
|
* @param pool Target window size in bytes.
|
|
57
|
+
* @param onRecordIndexed Optional per-record event, invoked live as each
|
|
58
|
+
* top-level record is indexed with its (localID, expressID, typeID) — the
|
|
59
|
+
* seam for incremental semantic consumers (M2). localIDs are dense and
|
|
60
|
+
* assigned in parse order from 0. On the rare grow-and-restart (a single
|
|
61
|
+
* record larger than the window — never on real files, whose largest STEP
|
|
62
|
+
* record is ~25 KB), the parse re-runs from the start and records re-fire
|
|
63
|
+
* from localID 0; consumers must therefore be idempotent by localID /
|
|
64
|
+
* expressID (the standard consumers — type index keyed by localID, roots
|
|
65
|
+
* registry keyed by expressID — are). Must be synchronous and cheap;
|
|
66
|
+
* expensive work belongs on a demand queue, not the parse path.
|
|
57
67
|
* @return {StreamingIndexResult} The index, header, result and diagnostics.
|
|
58
68
|
*/
|
|
59
|
-
export declare function buildIndexStreaming<TypeIDType>(source: ByteSource, parser: StepParser<TypeIDType>, pool: number): StreamingIndexResult<TypeIDType>;
|
|
69
|
+
export declare function buildIndexStreaming<TypeIDType>(source: ByteSource, parser: StepParser<TypeIDType>, pool: number, onRecordIndexed?: (localID: number, expressID: number, typeID: TypeIDType | undefined) => void): StreamingIndexResult<TypeIDType>;
|
|
60
70
|
//# sourceMappingURL=streaming_index_builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming_index_builder.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/streaming_index_builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,UAAU,EAAE,EACjB,WAAW,EACX,UAAU,EACV,cAAc,EACf,MAAM,eAAe,CAAA;AAGtB;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAElC,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAA;IAEZ;0CACsC;IACtC,WAAW,EAAE,MAAM,CAAA;IAEnB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAA;IAEd,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAA;IAEpB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAA;CAClB;AAGD;;;GAGG;AACH,MAAM,WAAW,oBAAoB,CAAC,UAAU;IAC9C,MAAM,EAAE,UAAU,CAAA;IAClB,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,CAAA;IACtC,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,mBAAmB,CAAA;CAC3B;AAGD
|
|
1
|
+
{"version":3,"file":"streaming_index_builder.d.ts","sourceRoot":"","sources":["../../../../src/step/parsing/streaming_index_builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,UAAU,EAAE,EACjB,WAAW,EACX,UAAU,EACV,cAAc,EACf,MAAM,eAAe,CAAA;AAGtB;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAElC,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAA;IAEZ;0CACsC;IACtC,WAAW,EAAE,MAAM,CAAA;IAEnB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAA;IAEd,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAA;IAEpB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAA;CAClB;AAGD;;;GAGG;AACH,MAAM,WAAW,oBAAoB,CAAC,UAAU;IAC9C,MAAM,EAAE,UAAU,CAAA;IAClB,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,CAAA;IACtC,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,mBAAmB,CAAA;CAC3B;AAGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAC1C,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,CAAC,UAAU,CAAC,EAC9B,IAAI,EAAE,MAAM,EACZ,eAAe,CAAC,EACd,CAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,KAAM,IAAI,GAChF,oBAAoB,CAAC,UAAU,CAAC,CA4GnC"}
|
|
@@ -27,9 +27,19 @@ import { ParseResult, } from "./step_parser.js";
|
|
|
27
27
|
* @param source The byte source.
|
|
28
28
|
* @param parser The STEP parser (typed to the schema).
|
|
29
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.
|
|
30
40
|
* @return {StreamingIndexResult} The index, header, result and diagnostics.
|
|
31
41
|
*/
|
|
32
|
-
export function buildIndexStreaming(source, parser, pool) {
|
|
42
|
+
export function buildIndexStreaming(source, parser, pool, onRecordIndexed) {
|
|
33
43
|
const fileSize = source.byteLength;
|
|
34
44
|
// Grow-and-restart loop: the body runs a full streamed parse at the current
|
|
35
45
|
// window size. It only re-runs if a single record couldn't fit the window
|
|
@@ -86,7 +96,7 @@ export function buildIndexStreaming(source, parser, pool) {
|
|
|
86
96
|
buffer.rebaseWindow(window, 0, windowLen, windowStartFile);
|
|
87
97
|
++slides;
|
|
88
98
|
};
|
|
89
|
-
const [index, result] = parser.parseDataBlockStreamed(input, onRecordBoundary);
|
|
99
|
+
const [index, result] = parser.parseDataBlockStreamed(input, onRecordBoundary, onRecordIndexed);
|
|
90
100
|
// A parse that stopped short of true EOF, with the window not spanning to
|
|
91
101
|
// EOF, means a single record overflowed the window: grow and retry.
|
|
92
102
|
const stoppedShort = result !== ParseResult.COMPLETE;
|
|
@@ -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
|
+
});
|
|
@@ -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 };
|