@bldrs-ai/conway 1.543.1513 → 1.549.1515

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.
@@ -8,7 +8,7 @@ import { CanonicalMeshType } from "../core/canonical_mesh.js";
8
8
  import { yieldToEventLoop } from "../core/progress.js";
9
9
  import { ObjectPool } from "../core/native_pool.js";
10
10
  import { ExtractResult } from "../core/shared_constants.js";
11
- import { IfcArbitraryClosedProfileDef, IfcAxis2Placement2D, IfcAxis2Placement3D, IfcBooleanResult, IfcCartesianPointList2D, IfcCartesianPointList3D, IfcCartesianTransformationOperator3D, IfcCircleProfileDef, IfcColourRgb, IfcCompositeProfileDef, IfcDirection, IfcExtrudedAreaSolid, IfcGrid, IfcGridPlacement, IfcIndexedPolyCurve, IfcIndexedPolygonalFaceWithVoids, IfcLocalPlacement, IfcMappedItem, IfcOpeningElement, IfcOpeningStandardCase, IfcPolygonalFaceSet, IfcProduct, IfcReflectanceMethodEnum, IfcSpace, IfcSpecularExponent, IfcStyledItem, IfcSurfaceStyle, IfcSurfaceStyleRefraction, IfcSurfaceStyleRendering, IfcSurfaceStyleShading, IfcPresentationStyleAssignment, IfcSurfaceSide, IfcBooleanClippingResult, IfcCompositeCurve, IfcPolyline, IfcTrimmedCurve, IfcCartesianPoint, IfcParameterValue, IfcTrimmingPreference, IfcCircle, IfcHalfSpaceSolid, IfcPlane, IfcBoundingBox, IfcShellBasedSurfaceModel, IfcPolyLoop, IfcFacetedBrep, IfcRelAssociatesMaterial, IfcShapeRepresentation, IfcMaterialList, IfcMaterialDefinitionRepresentation, IfcMaterialLayerSetUsage, IfcMaterial, IfcRelVoidsElement, IfcRectangleProfileDef, IfcMaterialProfileSet, IfcMaterialConstituentSet, IfcMaterialConstituent, IfcMaterialProfile, IfcProject, IfcSIUnit, IfcSIPrefix, IfcUnitEnum, IfcSIUnitName, IfcConversionBasedUnit, IfcFaceBasedSurfaceModel, IfcArbitraryProfileDefWithVoids, IfcAdvancedBrep, IfcBSplineCurve, IfcBSplineSurface, IfcBSplineSurfaceWithKnots, IfcRationalBSplineSurfaceWithKnots, IfcCylindricalSurface, IfcSurfaceOfRevolution, IfcSurfaceOfLinearExtrusion, IfcRationalBSplineCurveWithKnots, IfcArbitraryOpenProfileDef, IfcBSplineCurveWithKnots, IfcEdgeLoop, IfcEdgeCurve, IfcVertexPoint, IfcRectangleHollowProfileDef, IfcEllipseProfileDef, IfcCircleHollowProfileDef, IfcIShapeProfileDef, IfcUShapeProfileDef, IfcDerivedProfileDef, IfcCartesianTransformationOperator2DnonUniform, IfcLShapeProfileDef, IfcCShapeProfileDef, IfcTShapeProfileDef, IfcZShapeProfileDef, IfcRelAggregates, IfcTriangulatedFaceSet, IfcPolygonalBoundedHalfSpace, IfcLine, IfcEllipse, IfcMaterialLayerSet, IfcSweptDiskSolid, IfcBlock, IfcRoundedRectangleProfileDef, IfcMaterialProfileSetUsage, IfcCompositeCurveSegment, IfcRevolvedAreaSolid, IfcFaceSurface, IfcVirtualGridIntersection, } from "./ifc4_gen/index.js";
11
+ import { IfcArbitraryClosedProfileDef, IfcAxis2Placement2D, IfcAxis2Placement3D, IfcBooleanResult, IfcCartesianPointList2D, IfcCartesianPointList3D, IfcCartesianTransformationOperator3D, IfcCircleProfileDef, IfcColourRgb, IfcCompositeProfileDef, IfcDirection, IfcExtrudedAreaSolid, IfcGrid, IfcGridPlacement, IfcIndexedPolyCurve, IfcIndexedPolygonalFaceWithVoids, IfcLocalPlacement, IfcMappedItem, IfcOpeningElement, IfcOpeningStandardCase, IfcPolygonalFaceSet, IfcProduct, IfcReflectanceMethodEnum, IfcSpace, IfcSpecularExponent, IfcStyledItem, IfcSurfaceStyle, IfcSurfaceStyleRefraction, IfcSurfaceStyleRendering, IfcSurfaceStyleShading, IfcPresentationStyleAssignment, IfcSurfaceSide, IfcBooleanClippingResult, IfcCompositeCurve, IfcPolyline, IfcTrimmedCurve, IfcCartesianPoint, IfcParameterValue, IfcTrimmingPreference, IfcCircle, IfcHalfSpaceSolid, IfcPlane, IfcBoundingBox, IfcShellBasedSurfaceModel, IfcPolyLoop, IfcFacetedBrep, IfcRelAssociatesMaterial, IfcShapeRepresentation, IfcMaterialList, IfcMaterialDefinitionRepresentation, IfcMaterialLayerSetUsage, IfcMaterial, IfcRelVoidsElement, IfcRectangleProfileDef, IfcMaterialProfileSet, IfcMaterialConstituentSet, IfcMaterialConstituent, IfcMaterialProfile, IfcProject, IfcSIUnit, IfcSIPrefix, IfcUnitEnum, IfcSIUnitName, IfcConversionBasedUnit, IfcFaceBasedSurfaceModel, IfcArbitraryProfileDefWithVoids, IfcAdvancedBrep, IfcBSplineCurve, IfcBSplineSurface, IfcBSplineSurfaceWithKnots, IfcRationalBSplineSurfaceWithKnots, IfcCylindricalSurface, IfcSurfaceOfRevolution, IfcSurfaceOfLinearExtrusion, IfcRationalBSplineCurveWithKnots, IfcArbitraryOpenProfileDef, IfcBSplineCurveWithKnots, IfcEdgeLoop, IfcEdgeCurve, IfcVertexPoint, IfcRectangleHollowProfileDef, IfcEllipseProfileDef, IfcCircleHollowProfileDef, IfcIShapeProfileDef, IfcUShapeProfileDef, IfcDerivedProfileDef, IfcCartesianTransformationOperator2DnonUniform, IfcLShapeProfileDef, IfcCShapeProfileDef, IfcTShapeProfileDef, IfcZShapeProfileDef, IfcRelAggregates, IfcObjectDefinition, IfcTriangulatedFaceSet, IfcPolygonalBoundedHalfSpace, IfcLine, IfcEllipse, IfcMaterialLayerSet, IfcSweptDiskSolid, IfcBlock, IfcRoundedRectangleProfileDef, IfcMaterialProfileSetUsage, IfcCompositeCurveSegment, IfcRevolvedAreaSolid, IfcFaceSurface, IfcVirtualGridIntersection, } from "./ifc4_gen/index.js";
12
12
  import EntityTypesIfc from "./ifc4_gen/entity_types_ifc.gen.js";
13
13
  import { IfcSceneBuilder } from "./ifc_scene_builder.js";
14
14
  import Logger from "../logging/logger.js";
@@ -5462,10 +5462,28 @@ export class IfcGeometryExtraction {
5462
5462
  if (relating !== null && relating !== void 0) {
5463
5463
  await this.ensureResidentForProductExtract(relating.localID, pinned, leafSpans);
5464
5464
  }
5465
- const related = relAggregate.RelatedObjects;
5466
- if (related !== null && related !== void 0) {
5467
- for (const product of related) {
5468
- await this.ensureResidentForProductExtract(product.localID, pinned, leafSpans);
5465
+ // By express ID rather than through `relAggregate.RelatedObjects`, for
5466
+ // the reason relatedObjectExpressIDs_ documents: the getter memoizes
5467
+ // the whole related-product array onto the relationship, and this
5468
+ // prefetch runs immediately before the extract that must not retain
5469
+ // it (conway#549 §4). Falls back to the getter for a list holding a
5470
+ // non-reference entry, so the two paths agree on what is in the list.
5471
+ const relatedExpressIDs = this.relatedObjectExpressIDs_(relAggregate);
5472
+ if (relatedExpressIDs === void 0) {
5473
+ const related = relAggregate.RelatedObjects;
5474
+ if (related !== null && related !== void 0) {
5475
+ for (const product of related) {
5476
+ await this.ensureResidentForProductExtract(product.localID, pinned, leafSpans);
5477
+ }
5478
+ }
5479
+ }
5480
+ else {
5481
+ for (const relatedExpressID of relatedExpressIDs) {
5482
+ const relatedLocalID = this.model.resolveExpressID(relatedExpressID);
5483
+ if (relatedLocalID === void 0) {
5484
+ continue;
5485
+ }
5486
+ await this.ensureResidentForProductExtract(relatedLocalID, pinned, leafSpans);
5469
5487
  }
5470
5488
  }
5471
5489
  return pinned;
@@ -5494,6 +5512,46 @@ export class IfcGeometryExtraction {
5494
5512
  this.extractRelAggregateGeometry(relAggregate);
5495
5513
  }
5496
5514
  }
5515
+ /**
5516
+ * Express IDs of one IfcRelAggregates' RelatedObjects, read straight off
5517
+ * the relationship's own record.
5518
+ *
5519
+ * Deliberately NOT `relAggregate.RelatedObjects`: that generated getter
5520
+ * memoizes the materialised entity array onto the relationship as
5521
+ * `RelatedObjects_` (IfcRelAggregates.gen.ts), so every related product
5522
+ * stays reachable for as long as the relationship does — and each product
5523
+ * in turn memoizes the tessellation subtree its own getters walk
5524
+ * (IfcPolyLoop.Polygon_, IfcCartesianPoint.Coordinates_, ...). The
5525
+ * per-product graphs therefore SUM instead of overlapping. On SKYLARK250,
5526
+ * whose 1,992 products are all aggregate targets, that is 2.7 GB of a
5527
+ * 5.4 GB peak (conway#549 §4). Resolving one product at a time from IDs
5528
+ * lets each graph die with its iteration, exactly as the ordinary product
5529
+ * loop's iterator already does.
5530
+ *
5531
+ * The scan mirrors the getter's own parse — `forEachReferenceInField`
5532
+ * runs the same optional/array-token walk over the same field
5533
+ * coordinates — so it sees the same entries in the same order.
5534
+ *
5535
+ * @param relAggregate The relationship to read.
5536
+ * @return {number[] | undefined} The related objects' express IDs in
5537
+ * record order, or undefined when the list holds an entry that is not a
5538
+ * `#N` reference — an inline entity, `$`, or junk. Only the generated
5539
+ * getter can resolve (or reject) those, so the caller falls back to it
5540
+ * rather than guessing.
5541
+ */
5542
+ relatedObjectExpressIDs_(relAggregate) {
5543
+ const expressIDs = [];
5544
+ let allReferences = true;
5545
+ relAggregate.forEachReferenceInField(REL_AGGREGATES_RELATED_OFFSET, REL_AGGREGATES_RELATED_BASE, REL_AGGREGATES_RELATED_DEPTH, (expressID) => {
5546
+ if (expressID === void 0) {
5547
+ allReferences = false;
5548
+ return false;
5549
+ }
5550
+ expressIDs.push(expressID);
5551
+ return true;
5552
+ });
5553
+ return allReferences ? expressIDs : void 0;
5554
+ }
5497
5555
  /**
5498
5556
  * Extract one IfcRelAggregates' related products with the relating
5499
5557
  * object's ("master") rel-voids — the shared per-item body of the
@@ -5505,23 +5563,87 @@ export class IfcGeometryExtraction {
5505
5563
  * @param relAggregate The rel-aggregates relationship to process.
5506
5564
  */
5507
5565
  extractRelAggregateGeometry(relAggregate) {
5566
+ const stepper = this.extractRelAggregateGeometryIncremental(relAggregate);
5567
+ let step = stepper.next();
5568
+ while (step.done !== true) {
5569
+ step = stepper.next();
5570
+ }
5571
+ }
5572
+ /**
5573
+ * Incremental twin of {@link extractRelAggregateGeometry}: identical
5574
+ * extraction, suspending after each related product so a driver can
5575
+ * report progress and yield to the event loop *inside* the relationship.
5576
+ *
5577
+ * Without this the whole rel-aggregates pass is a single unyielding call.
5578
+ * On SKYLARK250 all 2,002 of the cooperative walk's suspension points
5579
+ * fire in 16 ms — every product is an aggregate target, so the product
5580
+ * loop skips them all — and the remaining ~60 s of real work runs inside
5581
+ * two `extractRelAggregateGeometry` calls with no suspension point at
5582
+ * all. That is the browser-tab hang in conway#549 §7, and it is
5583
+ * independent of the memory fix: a small model with one big aggregate
5584
+ * freezes the same way.
5585
+ *
5586
+ * Suspending here cannot show a caller a half-built product: the split is
5587
+ * BETWEEN products, and each product's scene content is complete before
5588
+ * the yield. Nor does it reorder emission — the products are visited in
5589
+ * record order either way. Aggregate targets are skipped by the product
5590
+ * pass (see aggregateTargetLocalIDs) and by the pump's product worklist,
5591
+ * so a consumer that streams meshes mid-relationship sees the
5592
+ * not-yet-reached products as absent rather than as uncut placeholders.
5593
+ *
5594
+ * @param relAggregate The rel-aggregates relationship to process.
5595
+ * @yields {number} The number of related products extracted so far from
5596
+ * THIS relationship, after each one.
5597
+ */
5598
+ *extractRelAggregateGeometryIncremental(relAggregate) {
5599
+ // Held outside the try so the finally can free it on the abandonment
5600
+ // path too: a driver that stops consuming (stepper.return()) unwinds
5601
+ // through here, and the vector is native memory the GC cannot reclaim.
5602
+ let masterRelVoids;
5508
5603
  try {
5509
5604
  // Note, this is required because the relating object
5510
5605
  // can be where the rel-void is applied, so we
5511
5606
  // used this as a top level over-ride for a rel-void.
5512
5607
  // for all objects in the aggregate - CS
5513
5608
  const relatingObject = relAggregate.RelatingObject;
5514
- const masterRelVoids = relatingObject instanceof IfcProduct ?
5515
- this.extractRelVoids(relatingObject) :
5516
- void 0;
5517
- const relatedObjects = relAggregate.RelatedObjects;
5518
- for (const productRepresentation of relatedObjects) {
5519
- if (productRepresentation instanceof IfcProduct) {
5520
- this.extractProductGeometry(productRepresentation, masterRelVoids);
5609
+ masterRelVoids =
5610
+ relatingObject instanceof IfcProduct ?
5611
+ this.extractRelVoids(relatingObject) :
5612
+ void 0;
5613
+ // Read before the first suspension point, deliberately: the scan
5614
+ // reads the relationship's own record bytes, and a windowed source
5615
+ // can page that record out while a later product is extracted.
5616
+ const relatedExpressIDs = this.relatedObjectExpressIDs_(relAggregate);
5617
+ let extractedCount = 0;
5618
+ if (relatedExpressIDs === void 0) {
5619
+ // Non-reference entry in the list — fall back to the generated
5620
+ // getter, the only thing that can resolve an inline entity (and the
5621
+ // only thing that decides an entry is unresolvable). Rare enough
5622
+ // that keeping its retention is the right trade against duplicating
5623
+ // inline resolution here.
5624
+ for (const productRepresentation of relAggregate.RelatedObjects) {
5625
+ if (productRepresentation instanceof IfcProduct) {
5626
+ this.extractProductGeometry(productRepresentation, masterRelVoids);
5627
+ yield ++extractedCount;
5628
+ }
5521
5629
  }
5522
5630
  }
5523
- if (masterRelVoids !== void 0) {
5524
- masterRelVoids[0].delete();
5631
+ else {
5632
+ for (const relatedExpressID of relatedExpressIDs) {
5633
+ const relatedObject = this.model.getTypedElementByExpressID(relatedExpressID, IfcObjectDefinition);
5634
+ if (relatedObject === void 0) {
5635
+ // What the getter does with the same entry: extractBufferElement
5636
+ // returns undefined for a reference that resolves to nothing (or
5637
+ // to something that is not an IfcObjectDefinition) and the getter
5638
+ // throws this exact message. The catch below then skips the whole
5639
+ // relationship, permissively, as it always has.
5640
+ throw new Error("Value in STEP was incorrectly typed");
5641
+ }
5642
+ if (relatedObject instanceof IfcProduct) {
5643
+ this.extractProductGeometry(relatedObject, masterRelVoids);
5644
+ yield ++extractedCount;
5645
+ }
5646
+ }
5525
5647
  }
5526
5648
  }
5527
5649
  catch (ex) {
@@ -5537,6 +5659,11 @@ export class IfcGeometryExtraction {
5537
5659
  Logger.error("Unknown exception processing IfcRelAssociateMaterials.");
5538
5660
  }
5539
5661
  }
5662
+ finally {
5663
+ if (masterRelVoids !== void 0) {
5664
+ masterRelVoids[0].delete();
5665
+ }
5666
+ }
5540
5667
  }
5541
5668
  /**
5542
5669
  * Extract one product's geometry into the scene: placement, material /
@@ -5781,16 +5908,24 @@ export class IfcGeometryExtraction {
5781
5908
  }
5782
5909
  this.prepareExtractionMaps_();
5783
5910
  const products = this.model.types(IfcProduct);
5784
- // Prefix-sum counts (no iteration/materialization) — see typeCount.
5785
- // Slight over-count from multi-mapped elements just leaves the bar
5786
- // shy of 100% until endPhase; fine for progress.
5787
- const totalItems = this.model.typeCount(IfcProduct) + this.model.typeCount(IfcRelAggregates);
5788
- let completedItems = 0;
5789
5911
  // Products the aggregates pass re-extracts are SKIPPED here and
5790
5912
  // extracted exactly once, below, with the master rel-voids — one
5791
5913
  // scene instance per placement and no content replacement after
5792
5914
  // emission (see aggregateTargetLocalIDs).
5793
5915
  const aggregateTargets = this.aggregateTargetLocalIDs();
5916
+ // Prefix-sum counts (no iteration/materialization) — see typeCount.
5917
+ // Slight over-count from multi-mapped elements just leaves the bar
5918
+ // shy of 100% until endPhase; fine for progress.
5919
+ //
5920
+ // Aggregate targets are counted TWICE on purpose — once for the tick
5921
+ // the product loop spends skipping each of them, once for the tick
5922
+ // the rel-aggregates pass spends extracting it. On a model whose
5923
+ // products are all aggregate targets that is the difference between
5924
+ // a bar that reaches 100% in the first 16 ms and then sits there for
5925
+ // the entire extraction, and one that tracks the work.
5926
+ let totalItems = this.model.typeCount(IfcProduct) + this.model.typeCount(IfcRelAggregates) +
5927
+ aggregateTargets.size;
5928
+ let completedItems = 0;
5794
5929
  for (const product of products) {
5795
5930
  yield [++completedItems, totalItems];
5796
5931
  if (aggregateTargets.has(product.localID)) {
@@ -5825,7 +5960,22 @@ export class IfcGeometryExtraction {
5825
5960
  const relAggregates = this.model.types(IfcRelAggregates);
5826
5961
  for (const relAggregate of relAggregates) {
5827
5962
  yield [++completedItems, totalItems];
5828
- this.extractRelAggregateGeometry(relAggregate);
5963
+ // Step the relationship rather than running it in one call: on an
5964
+ // aggregate-structured model this pass IS the extraction, and
5965
+ // without a suspension point inside it the cooperative driver
5966
+ // awaits nothing for the whole of it (conway#549 §7). for-of, not a
5967
+ // manual next() loop, so abandoning this generator closes the inner
5968
+ // one and frees its master rel-voids vector.
5969
+ const aggregateBase = completedItems;
5970
+ for (const extractedInAggregate of this.extractRelAggregateGeometryIncremental(relAggregate)) {
5971
+ completedItems = aggregateBase + extractedInAggregate;
5972
+ // The total counts one item per product and one per relationship,
5973
+ // so a product reached through more than one relationship can
5974
+ // push completed past it. Widen rather than let the bar exceed
5975
+ // 100% or go backwards.
5976
+ totalItems = Math.max(totalItems, completedItems);
5977
+ yield [completedItems, totalItems];
5978
+ }
5829
5979
  }
5830
5980
  if (RegressionCaptureState.memoization !== MemoizationCapture.FULL) {
5831
5981
  this.model.geometry.deleteTemporaries();
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ifc_rel_aggregate_extraction.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ifc_rel_aggregate_extraction.test.d.ts","sourceRoot":"","sources":["../../../src/ifc/ifc_rel_aggregate_extraction.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,115 @@
1
+ import fs from "fs";
2
+ import { beforeAll, describe, expect, test } from "@jest/globals";
3
+ import { ConwayGeometry } from "../../dependencies/conway-geom/index.js";
4
+ import ParsingBuffer from "../parsing/parsing_buffer.js";
5
+ import { IfcGeometryExtraction } from "./ifc_geometry_extraction.js";
6
+ import IfcStepParser from "./ifc_step_parser.js";
7
+ import { IfcProduct, IfcRelAggregates } from "./ifc4_gen/index.js";
8
+ const conwayGeometry = new ConwayGeometry();
9
+ /** Wasm init plus two full extractions per test; CI runners are slow. */
10
+ // eslint-disable-next-line no-magic-numbers
11
+ const TEST_TIMEOUT_MS = 120 * 1000;
12
+ /** Fixtures that carry at least one IfcRelAggregates. */
13
+ const AGGREGATE_FIXTURES = [
14
+ "data/index.ifc",
15
+ "data/aggregate_master_voids.ifc",
16
+ ];
17
+ /**
18
+ * Parse a fixture into a fresh model.
19
+ *
20
+ * @param fixturePath The IFC file to parse.
21
+ * @return {IfcStepModel} The parsed model.
22
+ */
23
+ function parseFixtureModel(fixturePath) {
24
+ const parser = IfcStepParser.Instance;
25
+ const bufferInput = new ParsingBuffer(fs.readFileSync(fixturePath));
26
+ parser.parseHeader(bufferInput);
27
+ const model = parser.parseDataToModel(bufferInput)[1];
28
+ expect(model).toBeDefined();
29
+ return model;
30
+ }
31
+ /**
32
+ * Count the IfcProducts the rel-aggregates pass will extract — the related
33
+ * objects of every IfcRelAggregates, which is one extraction each.
34
+ *
35
+ * @param model The model to count over.
36
+ * @return {number} The number of related products.
37
+ */
38
+ function relatedProductCount(model) {
39
+ let count = 0;
40
+ for (const relAggregate of model.types(IfcRelAggregates)) {
41
+ for (const relatedObject of relAggregate.RelatedObjects) {
42
+ if (relatedObject instanceof IfcProduct) {
43
+ ++count;
44
+ }
45
+ }
46
+ }
47
+ return count;
48
+ }
49
+ beforeAll(async () => {
50
+ await conwayGeometry.initialize();
51
+ });
52
+ describe("rel-aggregates extraction (conway#549)", () => {
53
+ test("suspends once per related product, not once per relationship", () => {
54
+ // The tab-hang half of #549. The whole-model walk skips every aggregate
55
+ // target in its product loop and does the real work in the
56
+ // rel-aggregates pass, so a pass that suspends only between
57
+ // RELATIONSHIPS gives a cooperative driver nothing to yield on: on
58
+ // SKYLARK250 all 2,002 suspension points fire in 16 ms and the
59
+ // remaining ~60 s runs inside two calls with none at all.
60
+ //
61
+ // Counted rather than merely "more than before", so this fails on a
62
+ // pass that suspends per relationship AND on one that suspends at some
63
+ // other granularity.
64
+ for (const fixturePath of AGGREGATE_FIXTURES) {
65
+ const model = parseFixtureModel(fixturePath);
66
+ const products = model.typeCount(IfcProduct);
67
+ const relationships = model.typeCount(IfcRelAggregates);
68
+ const relatedProducts = relatedProductCount(model);
69
+ expect(relationships).toBeGreaterThan(0);
70
+ expect(relatedProducts).toBeGreaterThan(0);
71
+ const extraction = new IfcGeometryExtraction(conwayGeometry, model);
72
+ const ticks = [];
73
+ extraction.extractIFCGeometryData((completed, total) => ticks.push([completed, total]));
74
+ expect(ticks.length)
75
+ .toBe(products + relationships + relatedProducts);
76
+ // Still monotonic, still bounded by the total it reports.
77
+ for (let where = 1; where < ticks.length; ++where) {
78
+ expect(ticks[where][0]).toBeGreaterThan(ticks[where - 1][0]);
79
+ expect(ticks[where][0]).toBeLessThanOrEqual(ticks[where][1]);
80
+ }
81
+ }
82
+ }, TEST_TIMEOUT_MS);
83
+ test("does not leave the related-product array memoized on the relationship", () => {
84
+ // The memory half of #549. `RelatedObjects` memoizes the
85
+ // materialised entity array onto the relationship as
86
+ // `RelatedObjects_`, and each of those products in turn memoizes the
87
+ // tessellation subtree its getters walk — so a pass that reads the
88
+ // getter keeps the whole model's face/loop/point graph alive for the
89
+ // length of the relationship instead of one product at a time. On
90
+ // SKYLARK250 that is 2.7 GB of a 5.4 GB peak.
91
+ //
92
+ // White-box on the generated field name deliberately: the retention
93
+ // IS the field, and there is no black-box way to observe reachability
94
+ // from a test. If codegen renames it, this test stops testing
95
+ // anything, so it also asserts the field exists to be set.
96
+ for (const fixturePath of AGGREGATE_FIXTURES) {
97
+ const model = parseFixtureModel(fixturePath);
98
+ const extraction = new IfcGeometryExtraction(conwayGeometry, model);
99
+ extraction.extractIFCGeometryData();
100
+ let checked = 0;
101
+ for (const relAggregate of model.types(IfcRelAggregates)) {
102
+ const memo = relAggregate.RelatedObjects_;
103
+ expect(memo).toBeUndefined();
104
+ // Reading the getter must still populate it — i.e. the field is
105
+ // the one the getter memoizes into, so the assertion above is
106
+ // meaningful rather than vacuous.
107
+ expect(relAggregate.RelatedObjects).toBeDefined();
108
+ expect(relAggregate.RelatedObjects_)
109
+ .toBeDefined();
110
+ ++checked;
111
+ }
112
+ expect(checked).toBeGreaterThan(0);
113
+ }
114
+ }, TEST_TIMEOUT_MS);
115
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bless_perf_snapshot.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bless_perf_snapshot.test.d.ts","sourceRoot":"","sources":["../../../src/scripts/bless_perf_snapshot.test.ts"],"names":[],"mappings":""}