@itwin/core-backend 5.3.0-dev.9 → 5.4.0-dev.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  3. package/lib/cjs/BriefcaseManager.js +14 -0
  4. package/lib/cjs/BriefcaseManager.js.map +1 -1
  5. package/lib/cjs/CloudSqlite.d.ts +11 -0
  6. package/lib/cjs/CloudSqlite.d.ts.map +1 -1
  7. package/lib/cjs/CloudSqlite.js.map +1 -1
  8. package/lib/cjs/DisplayStyle.d.ts +2 -2
  9. package/lib/cjs/DisplayStyle.d.ts.map +1 -1
  10. package/lib/cjs/DisplayStyle.js +4 -4
  11. package/lib/cjs/DisplayStyle.js.map +1 -1
  12. package/lib/cjs/ECDb.js +1 -1
  13. package/lib/cjs/ECDb.js.map +1 -1
  14. package/lib/cjs/Element.d.ts +31 -3
  15. package/lib/cjs/Element.d.ts.map +1 -1
  16. package/lib/cjs/Element.js +55 -3
  17. package/lib/cjs/Element.js.map +1 -1
  18. package/lib/cjs/ElementAspect.d.ts +24 -1
  19. package/lib/cjs/ElementAspect.d.ts.map +1 -1
  20. package/lib/cjs/ElementAspect.js +102 -1
  21. package/lib/cjs/ElementAspect.js.map +1 -1
  22. package/lib/cjs/IModelDb.d.ts +26 -3
  23. package/lib/cjs/IModelDb.d.ts.map +1 -1
  24. package/lib/cjs/IModelDb.js +60 -5
  25. package/lib/cjs/IModelDb.js.map +1 -1
  26. package/lib/cjs/IModelElementCloneContext.d.ts +1 -1
  27. package/lib/cjs/IModelElementCloneContext.d.ts.map +1 -1
  28. package/lib/cjs/IModelElementCloneContext.js +2 -2
  29. package/lib/cjs/IModelElementCloneContext.js.map +1 -1
  30. package/lib/cjs/Material.d.ts +1 -1
  31. package/lib/cjs/Material.d.ts.map +1 -1
  32. package/lib/cjs/Material.js +2 -2
  33. package/lib/cjs/Material.js.map +1 -1
  34. package/lib/cjs/NavigationRelationship.d.ts +15 -0
  35. package/lib/cjs/NavigationRelationship.d.ts.map +1 -1
  36. package/lib/cjs/NavigationRelationship.js +22 -1
  37. package/lib/cjs/NavigationRelationship.js.map +1 -1
  38. package/lib/cjs/Relationship.d.ts +5 -0
  39. package/lib/cjs/Relationship.d.ts.map +1 -1
  40. package/lib/cjs/Relationship.js +7 -0
  41. package/lib/cjs/Relationship.js.map +1 -1
  42. package/lib/cjs/TxnManager.d.ts +8 -0
  43. package/lib/cjs/TxnManager.d.ts.map +1 -1
  44. package/lib/cjs/TxnManager.js +30 -2
  45. package/lib/cjs/TxnManager.js.map +1 -1
  46. package/lib/cjs/ViewDefinition.d.ts +1 -1
  47. package/lib/cjs/ViewDefinition.d.ts.map +1 -1
  48. package/lib/cjs/ViewDefinition.js +2 -2
  49. package/lib/cjs/ViewDefinition.js.map +1 -1
  50. package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts +1 -1
  51. package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -1
  52. package/lib/cjs/annotations/ElementDrivesTextAnnotation.js +4 -4
  53. package/lib/cjs/annotations/ElementDrivesTextAnnotation.js.map +1 -1
  54. package/lib/cjs/annotations/LeaderGeometry.d.ts +13 -1
  55. package/lib/cjs/annotations/LeaderGeometry.d.ts.map +1 -1
  56. package/lib/cjs/annotations/LeaderGeometry.js +90 -10
  57. package/lib/cjs/annotations/LeaderGeometry.js.map +1 -1
  58. package/lib/cjs/annotations/TextAnnotationElement.d.ts +6 -5
  59. package/lib/cjs/annotations/TextAnnotationElement.d.ts.map +1 -1
  60. package/lib/cjs/annotations/TextAnnotationElement.js +34 -14
  61. package/lib/cjs/annotations/TextAnnotationElement.js.map +1 -1
  62. package/lib/cjs/internal/ElementLRUCache.d.ts.map +1 -1
  63. package/lib/cjs/internal/ElementLRUCache.js +2 -0
  64. package/lib/cjs/internal/ElementLRUCache.js.map +1 -1
  65. package/lib/esm/BriefcaseManager.d.ts.map +1 -1
  66. package/lib/esm/BriefcaseManager.js +14 -0
  67. package/lib/esm/BriefcaseManager.js.map +1 -1
  68. package/lib/esm/CloudSqlite.d.ts +11 -0
  69. package/lib/esm/CloudSqlite.d.ts.map +1 -1
  70. package/lib/esm/CloudSqlite.js.map +1 -1
  71. package/lib/esm/DisplayStyle.d.ts +2 -2
  72. package/lib/esm/DisplayStyle.d.ts.map +1 -1
  73. package/lib/esm/DisplayStyle.js +4 -4
  74. package/lib/esm/DisplayStyle.js.map +1 -1
  75. package/lib/esm/ECDb.js +1 -1
  76. package/lib/esm/ECDb.js.map +1 -1
  77. package/lib/esm/Element.d.ts +31 -3
  78. package/lib/esm/Element.d.ts.map +1 -1
  79. package/lib/esm/Element.js +55 -4
  80. package/lib/esm/Element.js.map +1 -1
  81. package/lib/esm/ElementAspect.d.ts +24 -1
  82. package/lib/esm/ElementAspect.d.ts.map +1 -1
  83. package/lib/esm/ElementAspect.js +101 -1
  84. package/lib/esm/ElementAspect.js.map +1 -1
  85. package/lib/esm/IModelDb.d.ts +26 -3
  86. package/lib/esm/IModelDb.d.ts.map +1 -1
  87. package/lib/esm/IModelDb.js +60 -5
  88. package/lib/esm/IModelDb.js.map +1 -1
  89. package/lib/esm/IModelElementCloneContext.d.ts +1 -1
  90. package/lib/esm/IModelElementCloneContext.d.ts.map +1 -1
  91. package/lib/esm/IModelElementCloneContext.js +2 -2
  92. package/lib/esm/IModelElementCloneContext.js.map +1 -1
  93. package/lib/esm/Material.d.ts +1 -1
  94. package/lib/esm/Material.d.ts.map +1 -1
  95. package/lib/esm/Material.js +2 -2
  96. package/lib/esm/Material.js.map +1 -1
  97. package/lib/esm/NavigationRelationship.d.ts +15 -0
  98. package/lib/esm/NavigationRelationship.d.ts.map +1 -1
  99. package/lib/esm/NavigationRelationship.js +19 -0
  100. package/lib/esm/NavigationRelationship.js.map +1 -1
  101. package/lib/esm/Relationship.d.ts +5 -0
  102. package/lib/esm/Relationship.d.ts.map +1 -1
  103. package/lib/esm/Relationship.js +7 -0
  104. package/lib/esm/Relationship.js.map +1 -1
  105. package/lib/esm/TxnManager.d.ts +8 -0
  106. package/lib/esm/TxnManager.d.ts.map +1 -1
  107. package/lib/esm/TxnManager.js +30 -2
  108. package/lib/esm/TxnManager.js.map +1 -1
  109. package/lib/esm/ViewDefinition.d.ts +1 -1
  110. package/lib/esm/ViewDefinition.d.ts.map +1 -1
  111. package/lib/esm/ViewDefinition.js +2 -2
  112. package/lib/esm/ViewDefinition.js.map +1 -1
  113. package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts +1 -1
  114. package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -1
  115. package/lib/esm/annotations/ElementDrivesTextAnnotation.js +4 -4
  116. package/lib/esm/annotations/ElementDrivesTextAnnotation.js.map +1 -1
  117. package/lib/esm/annotations/LeaderGeometry.d.ts +13 -1
  118. package/lib/esm/annotations/LeaderGeometry.d.ts.map +1 -1
  119. package/lib/esm/annotations/LeaderGeometry.js +91 -12
  120. package/lib/esm/annotations/LeaderGeometry.js.map +1 -1
  121. package/lib/esm/annotations/TextAnnotationElement.d.ts +6 -5
  122. package/lib/esm/annotations/TextAnnotationElement.d.ts.map +1 -1
  123. package/lib/esm/annotations/TextAnnotationElement.js +34 -14
  124. package/lib/esm/annotations/TextAnnotationElement.js.map +1 -1
  125. package/lib/esm/internal/ElementLRUCache.d.ts.map +1 -1
  126. package/lib/esm/internal/ElementLRUCache.js +2 -0
  127. package/lib/esm/internal/ElementLRUCache.js.map +1 -1
  128. package/lib/esm/test/ElementDrivesElement.test.d.ts +93 -0
  129. package/lib/esm/test/ElementDrivesElement.test.d.ts.map +1 -0
  130. package/lib/esm/test/ElementDrivesElement.test.js +862 -0
  131. package/lib/esm/test/ElementDrivesElement.test.js.map +1 -0
  132. package/lib/esm/test/ElementLRUCache.test.js +19 -0
  133. package/lib/esm/test/ElementLRUCache.test.js.map +1 -1
  134. package/lib/esm/test/annotations/Fields.test.js +44 -0
  135. package/lib/esm/test/annotations/Fields.test.js.map +1 -1
  136. package/lib/esm/test/annotations/LeaderGeometry.test.js +48 -2
  137. package/lib/esm/test/annotations/LeaderGeometry.test.js.map +1 -1
  138. package/lib/esm/test/annotations/TextAnnotation.test.js +73 -27
  139. package/lib/esm/test/annotations/TextAnnotation.test.js.map +1 -1
  140. package/lib/esm/test/ecdb/ConcurrentQuery.test.js +2 -2
  141. package/lib/esm/test/ecdb/ConcurrentQuery.test.js.map +1 -1
  142. package/lib/esm/test/ecdb/ECSqlStatement.test.js +33 -2
  143. package/lib/esm/test/ecdb/ECSqlStatement.test.js.map +1 -1
  144. package/lib/esm/test/element/ProjectInformationRecord.test.d.ts +2 -0
  145. package/lib/esm/test/element/ProjectInformationRecord.test.d.ts.map +1 -0
  146. package/lib/esm/test/element/ProjectInformationRecord.test.js +80 -0
  147. package/lib/esm/test/element/ProjectInformationRecord.test.js.map +1 -0
  148. package/lib/esm/test/element/SheetInformationAspect.test.d.ts +2 -0
  149. package/lib/esm/test/element/SheetInformationAspect.test.d.ts.map +1 -0
  150. package/lib/esm/test/element/SheetInformationAspect.test.js +182 -0
  151. package/lib/esm/test/element/SheetInformationAspect.test.js.map +1 -0
  152. package/lib/esm/test/hubaccess/Rebase.test.js +169 -2
  153. package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -1
  154. package/lib/esm/test/imodel/Code.test.d.ts +2 -0
  155. package/lib/esm/test/imodel/Code.test.d.ts.map +1 -0
  156. package/lib/esm/test/imodel/Code.test.js +272 -0
  157. package/lib/esm/test/imodel/Code.test.js.map +1 -0
  158. package/lib/esm/test/imodel/IModel.test.js +175 -2
  159. package/lib/esm/test/imodel/IModel.test.js.map +1 -1
  160. package/lib/esm/test/incrementalSchemaLocater/ECSqlQueries.test.js.map +1 -1
  161. package/lib/esm/test/standalone/ChangesetReader.test.js +66 -60
  162. package/lib/esm/test/standalone/ChangesetReader.test.js.map +1 -1
  163. package/lib/esm/test/standalone/DisplayStyle.test.js +6 -6
  164. package/lib/esm/test/standalone/DisplayStyle.test.js.map +1 -1
  165. package/lib/esm/test/standalone/ElementGraphics.test.js +75 -6
  166. package/lib/esm/test/standalone/ElementGraphics.test.js.map +1 -1
  167. package/lib/esm/test/standalone/RenderMaterialElement.test.js +4 -4
  168. package/lib/esm/test/standalone/RenderMaterialElement.test.js.map +1 -1
  169. package/lib/esm/test/standalone/TxnManager.test.js +33 -5
  170. package/lib/esm/test/standalone/TxnManager.test.js.map +1 -1
  171. package/package.json +14 -14
@@ -0,0 +1,862 @@
1
+ import { BeEvent, DbResult, IModelStatus, StopWatch } from "@itwin/core-bentley";
2
+ import { Code, GeometryStreamBuilder, IModel, IModelError, RelatedElement } from "@itwin/core-common";
3
+ import { LineSegment3d, Point3d, YawPitchRollAngles } from "@itwin/core-geometry";
4
+ import * as chai from "chai";
5
+ import * as chaiAsPromised from "chai-as-promised";
6
+ import { SpatialCategory } from "../Category";
7
+ import { ChannelControl } from "../ChannelControl";
8
+ import { ClassRegistry } from "../ClassRegistry";
9
+ import { GeometricElement3d, PhysicalPartition } from "../Element";
10
+ import { IModelDb } from "../IModelDb";
11
+ import { HubMock } from "../internal/HubMock";
12
+ import { PhysicalModel } from "../Model";
13
+ import { SubjectOwnsPartitionElements } from "../NavigationRelationship";
14
+ import { ElementDrivesElement } from "../Relationship";
15
+ import { Schema, Schemas } from "../Schema";
16
+ import { HubWrappers } from "./IModelTestUtils";
17
+ import { KnownTestLocations } from "./KnownTestLocations";
18
+ chai.use(chaiAsPromised);
19
+ /**
20
+ 1. What is Change Propagation?**
21
+ In engineering, models often consist of many interdependent components (e.g., parts, assemblies, constraints). When you modify one component (say, changing a dimension), that change can affect other components.
22
+ **Change propagation** is the process of updating all dependent components so the design remains consistent.
23
+
24
+ 2. Why Use Topological Sort?**
25
+ The dependencies between components can be represented as a **Directed Acyclic Graph (DAG)**:
26
+ - **Nodes** = components or features.
27
+ - **Edges** = dependency relationships (e.g., "Feature B depends on Feature A").
28
+
29
+ To propagate changes correctly:
30
+ - You must update components **in dependency order** (parents before children).
31
+ - This is where **topological sorting** comes in—it gives a linear order of nodes such that every dependency comes before the dependent.
32
+
33
+ 3. How It Works**
34
+ **Steps:**
35
+ 1. **Build the dependency graph**:
36
+ - For each feature/component, list what it depends on.
37
+ 2. **Perform topological sort**:
38
+ - Use algorithms like **Kahn’s Algorithm** or **DFS-based sort**.
39
+ 3. **Propagate changes in sorted order**:
40
+ - Start from nodes with no dependencies (roots).
41
+ - Update each node, then move to its dependents.
42
+
43
+
44
+ 4. Example**
45
+ Imagine a CAD model:
46
+ - **Sketch → Extrude → Fillet → Hole**
47
+ - If you change the **Sketch**, the **Extrude**, **Fillet**, and **Hole** must update in that order.
48
+
49
+ Graph:
50
+ Sketch → Extrude → Fillet → Hole
51
+ Topological sort result:
52
+ [Sketch, Extrude, Fillet, Hole]
53
+ Update in this order to maintain consistency.
54
+
55
+ 5. Benefits**
56
+ - Prevents circular updates (since DAG ensures no cycles).
57
+ - Ensures deterministic and efficient update propagation.
58
+ - Scales well for complex assemblies.
59
+ */
60
+ var Color;
61
+ (function (Color) {
62
+ Color[Color["White"] = 0] = "White";
63
+ Color[Color["Gray"] = 1] = "Gray";
64
+ Color[Color["Black"] = 2] = "Black";
65
+ })(Color || (Color = {}));
66
+ export class Graph {
67
+ _nodes = [];
68
+ _edges = new Map();
69
+ constructor() {
70
+ }
71
+ addNode(node) {
72
+ if (!this._nodes.includes(node))
73
+ this._nodes.push(node);
74
+ }
75
+ *nodes() {
76
+ yield* this._nodes;
77
+ }
78
+ *edges() {
79
+ for (const [from, toList] of this._edges.entries()) {
80
+ for (const to of toList) {
81
+ yield { from, to };
82
+ }
83
+ }
84
+ }
85
+ addEdge(from, to) {
86
+ this.addNode(from);
87
+ if (!this._edges.has(from)) {
88
+ this._edges.set(from, []);
89
+ }
90
+ if (Array.isArray(to)) {
91
+ to.forEach(t => this.addNode(t));
92
+ this._edges.get(from).push(...to);
93
+ }
94
+ else {
95
+ this.addNode(to);
96
+ this._edges.get(from).push(to);
97
+ }
98
+ }
99
+ getEdges(node) {
100
+ if (!this._edges.has(node))
101
+ return [];
102
+ return this._edges.get(node);
103
+ }
104
+ clone() {
105
+ const newGraph = new Graph();
106
+ for (const node of this._nodes) {
107
+ newGraph.addNode(node);
108
+ }
109
+ for (const [from, toList] of this._edges.entries()) {
110
+ newGraph.addEdge(from, toList);
111
+ }
112
+ return newGraph;
113
+ }
114
+ toGraphvis(accessor) {
115
+ // Implementation for converting the graph to Graphviz DOT format
116
+ let dot = "digraph G {\n";
117
+ for (const node of this._nodes) {
118
+ dot += ` "${accessor.getId(node)}" [label="${accessor.getLabel(node)}"];\n`;
119
+ }
120
+ for (const [from, toList] of this._edges.entries()) {
121
+ for (const to of toList) {
122
+ dot += ` "${accessor.getId(from)}" -> "${accessor.getId(to)}";\n`;
123
+ }
124
+ }
125
+ dot += "}\n";
126
+ return dot;
127
+ }
128
+ }
129
+ export class TopologicalSorter {
130
+ static visit(graph, node, colors, sorted, failOnCycles) {
131
+ if (colors.get(node) === Color.Gray) {
132
+ if (failOnCycles)
133
+ throw new Error("Graph has a cycle");
134
+ else {
135
+ return;
136
+ }
137
+ }
138
+ if (colors.get(node) === Color.White) {
139
+ colors.set(node, Color.Gray);
140
+ const neighbors = graph.getEdges(node);
141
+ for (const neighbor of neighbors) {
142
+ this.visit(graph, neighbor, colors, sorted, failOnCycles);
143
+ }
144
+ colors.set(node, Color.Black);
145
+ sorted.push(node);
146
+ }
147
+ }
148
+ static sortDepthFirst(graph, updated, failOnCycles = true) {
149
+ const sorted = [];
150
+ const colors = new Map(Array.from(graph.nodes()).map((node) => [node, Color.White]));
151
+ if (updated) {
152
+ // remove duplicate
153
+ let filteredUpdated = Array.from(new Set(updated));
154
+ filteredUpdated = filteredUpdated.filter(node => colors.get(node) === Color.White);
155
+ if (filteredUpdated.length !== updated.length) {
156
+ throw new Error("Updated list contains nodes that are not in the graph or have duplicates");
157
+ }
158
+ if (filteredUpdated.length === 0)
159
+ updated = undefined;
160
+ else
161
+ updated = filteredUpdated;
162
+ }
163
+ for (const node of updated ?? Array.from(graph.nodes())) {
164
+ if (colors.get(node) === Color.White) {
165
+ this.visit(graph, node, colors, sorted, failOnCycles);
166
+ }
167
+ }
168
+ return sorted.reverse();
169
+ }
170
+ static sortBreadthFirst(graph, updated, failOnCycles = true) {
171
+ const sorted = [];
172
+ const queue = [];
173
+ // Vector to store indegree of each vertex
174
+ const inDegree = new Map();
175
+ for (const node of graph.nodes()) {
176
+ inDegree.set(node, 0);
177
+ }
178
+ for (const edge of graph.edges()) {
179
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
180
+ }
181
+ if (updated) {
182
+ // remove duplicate
183
+ const filteredUpdated = Array.from(new Set(updated));
184
+ if (filteredUpdated.length !== updated.length) {
185
+ throw new Error("Updated list contains nodes that are not in the graph or have duplicates");
186
+ }
187
+ if (filteredUpdated.length === 0)
188
+ updated = undefined;
189
+ else
190
+ updated = filteredUpdated;
191
+ }
192
+ const startNodes = updated ?? Array.from(graph.nodes());
193
+ for (const node of startNodes) {
194
+ if (inDegree.get(node) === 0) {
195
+ queue.push(node);
196
+ }
197
+ }
198
+ if (startNodes.length === 0) {
199
+ throw new Error("Graph has at least one cycle");
200
+ }
201
+ if (startNodes)
202
+ while (queue.length > 0) {
203
+ const current = queue.shift();
204
+ sorted.push(current);
205
+ for (const neighbor of graph.getEdges(current)) {
206
+ inDegree.set(neighbor, (inDegree.get(neighbor) ?? 0) - 1);
207
+ if (inDegree.get(neighbor) === 0) {
208
+ queue.push(neighbor);
209
+ }
210
+ }
211
+ }
212
+ if (failOnCycles && sorted.length !== Array.from(graph.nodes()).length)
213
+ throw new Error("Graph has at least one cycle");
214
+ return sorted;
215
+ }
216
+ static validate(graph, sorted) {
217
+ if (sorted.length !== Array.from(graph.nodes()).length) {
218
+ return false;
219
+ }
220
+ const position = new Map();
221
+ for (let i = 0; i < sorted.length; i++) {
222
+ position.set(sorted[i], i);
223
+ }
224
+ for (const { from, to } of graph.edges()) {
225
+ if (position.get(from) > position.get(to)) {
226
+ return false;
227
+ }
228
+ }
229
+ return true;
230
+ }
231
+ }
232
+ class ElementDrivesElementEventMonitor {
233
+ iModelDb;
234
+ onRootChanged = [];
235
+ onAllInputsHandled = [];
236
+ onBeforeOutputsHandled = [];
237
+ onDeletedDependency = [];
238
+ constructor(iModelDb) {
239
+ this.iModelDb = iModelDb;
240
+ InputDrivesOutput.events.onDeletedDependency.addListener((props) => this.onDeletedDependency.push([this.iModelDb.elements.tryGetElement(props.sourceId)?.userLabel, this.iModelDb.elements.tryGetElement(props.targetId)?.userLabel]));
241
+ InputDrivesOutput.events.onRootChanged.addListener((props) => this.onRootChanged.push([this.iModelDb.elements.tryGetElement(props.sourceId)?.userLabel, this.iModelDb.elements.tryGetElement(props.targetId)?.userLabel]));
242
+ NodeElement.events.onAllInputsHandled.addListener((id) => this.onAllInputsHandled.push(this.iModelDb.elements.tryGetElement(id)?.userLabel));
243
+ NodeElement.events.onBeforeOutputsHandled.addListener((id) => this.onBeforeOutputsHandled.push(this.iModelDb.elements.tryGetElement(id)?.userLabel));
244
+ }
245
+ clear() {
246
+ this.onRootChanged.length = 0;
247
+ this.onAllInputsHandled.length = 0;
248
+ this.onBeforeOutputsHandled.length = 0;
249
+ this.onDeletedDependency.length = 0;
250
+ }
251
+ }
252
+ export class InputDrivesOutput extends ElementDrivesElement {
253
+ static events = {
254
+ onRootChanged: new BeEvent(),
255
+ onDeletedDependency: new BeEvent(),
256
+ };
257
+ static get className() { return "InputDrivesOutput"; }
258
+ constructor(props, iModel) {
259
+ super(props, iModel);
260
+ }
261
+ static onRootChanged(props, iModel) {
262
+ this.events.onRootChanged.raiseEvent(props, iModel);
263
+ }
264
+ static onDeletedDependency(props, iModel) {
265
+ this.events.onDeletedDependency.raiseEvent(props, iModel);
266
+ }
267
+ }
268
+ export class NodeElement extends GeometricElement3d {
269
+ op;
270
+ val;
271
+ static events = {
272
+ onAllInputsHandled: new BeEvent(),
273
+ onBeforeOutputsHandled: new BeEvent(),
274
+ };
275
+ static get className() { return "Node"; }
276
+ constructor(props, iModel) {
277
+ super(props, iModel);
278
+ this.op = props.op;
279
+ this.val = props.val;
280
+ }
281
+ toJSON() {
282
+ const val = super.toJSON();
283
+ val.op = this.op;
284
+ val.val = this.val;
285
+ return val;
286
+ }
287
+ static onAllInputsHandled(id, iModel) {
288
+ this.events.onAllInputsHandled.raiseEvent(id, iModel);
289
+ }
290
+ static onBeforeOutputsHandled(id, iModel) {
291
+ this.events.onBeforeOutputsHandled.raiseEvent(id, iModel);
292
+ }
293
+ static generateGeometry(radius) {
294
+ const builder = new GeometryStreamBuilder();
295
+ const p1 = Point3d.createZero();
296
+ const p2 = Point3d.createFrom({ x: radius, y: 0.0, z: 0.0 });
297
+ const circle = LineSegment3d.create(p1, p2);
298
+ builder.appendGeometry(circle);
299
+ return builder.geometryStream;
300
+ }
301
+ static getCategory(iModelDb) {
302
+ const categoryId = SpatialCategory.queryCategoryIdByName(iModelDb, IModelDb.dictionaryId, this.classFullName);
303
+ if (categoryId === undefined)
304
+ throw new IModelError(IModelStatus.NotFound, "Category not found");
305
+ return iModelDb.elements.getElement(categoryId);
306
+ }
307
+ }
308
+ export class NetworkSchema extends Schema {
309
+ static get schemaName() { return "Network"; }
310
+ static registerSchema() {
311
+ if (this !== Schemas.getRegisteredSchema(NetworkSchema.schemaName)) {
312
+ Schemas.registerSchema(this);
313
+ ClassRegistry.register(NodeElement, this);
314
+ ClassRegistry.register(InputDrivesOutput, this);
315
+ }
316
+ }
317
+ static async importSchema(iModel) {
318
+ if (iModel.querySchemaVersion("Network"))
319
+ return;
320
+ const schema1 = `<?xml version="1.0" encoding="UTF-8"?>
321
+ <ECSchema schemaName="Network" alias="net" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
322
+ <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
323
+ <ECEntityClass typeName="Node">
324
+ <BaseClass>bis:GraphicalElement3d</BaseClass>
325
+ <ECProperty propertyName="op" typeName="string" />
326
+ <ECProperty propertyName="val" typeName="double" />
327
+ </ECEntityClass>
328
+ <ECRelationshipClass typeName="InputDrivesOutput" modifier="None" strength="referencing">
329
+ <BaseClass>bis:ElementDrivesElement</BaseClass>
330
+ <Source multiplicity="(0..1)" roleLabel="drives" polymorphic="true">
331
+ <Class class="Node"/>
332
+ </Source>
333
+ <Target multiplicity="(0..*)" roleLabel="is driven by" polymorphic="false">
334
+ <Class class="Node"/>
335
+ </Target>
336
+ <ECProperty propertyName="prop" typeName="double" />
337
+ </ECRelationshipClass>
338
+ </ECSchema>`;
339
+ await iModel.importSchemaStrings([schema1]);
340
+ }
341
+ }
342
+ export class Engine {
343
+ static async createGraph(iModelDb, modelId, graph) {
344
+ const nodes = new Map();
345
+ const outGraph = new Graph();
346
+ for (const node of graph.nodes()) {
347
+ const id = await this.insertNode(iModelDb, modelId, node, "", 0, new Point3d(0, 0, 0));
348
+ nodes.set(node, { id, name: node });
349
+ }
350
+ for (const edge of graph.edges()) {
351
+ const fromId = nodes.get(edge.from).id;
352
+ const toId = nodes.get(edge.to).id;
353
+ await this.insertEdge(iModelDb, fromId, toId, 0);
354
+ outGraph.addEdge(nodes.get(edge.from), nodes.get(edge.to));
355
+ }
356
+ return outGraph;
357
+ }
358
+ static countNodes(iModelDb) {
359
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
360
+ return iModelDb.withPreparedStatement("SELECT COUNT(*) FROM Network.Node", (stmt) => {
361
+ if (stmt.step() === DbResult.BE_SQLITE_ROW) {
362
+ return stmt.getValue(0).getInteger();
363
+ }
364
+ return 0;
365
+ });
366
+ }
367
+ static countEdges(iModelDb) {
368
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
369
+ return iModelDb.withPreparedStatement("SELECT COUNT(*) FROM [Network].[InputDrivesOutput]", (stmt) => {
370
+ if (stmt.step() === DbResult.BE_SQLITE_ROW) {
371
+ return stmt.getValue(0).getInteger();
372
+ }
373
+ return 0;
374
+ });
375
+ }
376
+ static queryEdgesForSource(iModelDb, sourceId) {
377
+ const edges = [];
378
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
379
+ iModelDb.withPreparedStatement("SELECT [ECInstanceId], [SourceECInstanceId], [TargetECInstanceId], [prop], [Status], [Priority] FROM [Network].[InputDrivesOutput] WHERE [SourceECInstanceId] = ?", (stmt) => {
380
+ stmt.bindId(1, sourceId);
381
+ while (stmt.step() === DbResult.BE_SQLITE_ROW) {
382
+ edges.push({
383
+ id: stmt.getValue(0).getId(),
384
+ classFullName: InputDrivesOutput.classFullName,
385
+ sourceId: stmt.getValue(1).getId(),
386
+ targetId: stmt.getValue(2).getId(),
387
+ prop: stmt.getValue(3).getDouble(),
388
+ status: stmt.getValue(4).getInteger(),
389
+ priority: stmt.getValue(5).getInteger(),
390
+ });
391
+ }
392
+ });
393
+ return edges;
394
+ }
395
+ static queryEdgesForTarget(iModelDb, targetId) {
396
+ const edges = [];
397
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
398
+ iModelDb.withPreparedStatement("SELECT [ECInstanceId], [SourceECInstanceId], [TargetECInstanceId], [prop], [Status], [Priority] FROM [Network].[InputDrivesOutput] WHERE [TargetECInstanceId] = ?", (stmt) => {
399
+ stmt.bindId(1, targetId);
400
+ while (stmt.step() === DbResult.BE_SQLITE_ROW) {
401
+ edges.push({
402
+ id: stmt.getValue(0).getId(),
403
+ classFullName: InputDrivesOutput.classFullName,
404
+ sourceId: stmt.getValue(1).getId(),
405
+ targetId: stmt.getValue(2).getId(),
406
+ prop: stmt.getValue(3).getDouble(),
407
+ status: stmt.getValue(4).getInteger(),
408
+ priority: stmt.getValue(5).getInteger(),
409
+ });
410
+ }
411
+ });
412
+ return edges;
413
+ }
414
+ static async createPartition(iModelDb) {
415
+ const parentId = new SubjectOwnsPartitionElements(IModel.rootSubjectId);
416
+ const modelId = IModel.repositoryModelId;
417
+ const modeledElementProps = {
418
+ classFullName: PhysicalPartition.classFullName,
419
+ parent: parentId,
420
+ model: modelId,
421
+ code: Code.createEmpty(),
422
+ userLabel: "NetworkPhysicalPartition"
423
+ };
424
+ const modeledElement = iModelDb.elements.createElement(modeledElementProps);
425
+ await iModelDb.locks.acquireLocks({ shared: modelId });
426
+ return iModelDb.elements.insertElement(modeledElement.toJSON());
427
+ }
428
+ static async createModel(iModelDb) {
429
+ const partitionId = await this.createPartition(iModelDb);
430
+ const modeledElementRef = new RelatedElement({ id: partitionId });
431
+ const newModel = iModelDb.models.createModel({ modeledElement: modeledElementRef, classFullName: PhysicalModel.classFullName });
432
+ const newModelId = newModel.insert();
433
+ return newModelId;
434
+ }
435
+ static async createNodeCategory(iModelDb) {
436
+ const category = SpatialCategory.create(iModelDb, IModelDb.dictionaryId, NodeElement.classFullName);
437
+ return category.insert();
438
+ }
439
+ static async initialize(iModelDb) {
440
+ await NetworkSchema.importSchema(iModelDb);
441
+ NetworkSchema.registerSchema();
442
+ const modelId = await this.createModel(iModelDb);
443
+ const categoryId = await this.createNodeCategory(iModelDb);
444
+ return {
445
+ modelId,
446
+ categoryId,
447
+ };
448
+ }
449
+ static async insertNode(iModelDb, modelId, name, op, val, location, radius = 0.1) {
450
+ const props = {
451
+ classFullName: NodeElement.classFullName,
452
+ model: modelId,
453
+ code: Code.createEmpty(),
454
+ userLabel: name,
455
+ category: NodeElement.getCategory(iModelDb).id,
456
+ placement: { origin: location, angles: new YawPitchRollAngles() },
457
+ geom: NodeElement.generateGeometry(radius),
458
+ op,
459
+ val,
460
+ };
461
+ await iModelDb.locks.acquireLocks({ shared: modelId });
462
+ return iModelDb.elements.insertElement(props);
463
+ }
464
+ static async deleteNode(iModelDb, nodeId) {
465
+ await iModelDb.locks.acquireLocks({ exclusive: nodeId });
466
+ return iModelDb.elements.deleteElement(nodeId);
467
+ }
468
+ static async updateNodeProps(iModelDb, props) {
469
+ await iModelDb.locks.acquireLocks({ exclusive: props.id });
470
+ return iModelDb.elements.updateElement(props);
471
+ }
472
+ static async updateNode(iModelDb, userLabel) {
473
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
474
+ const id = iModelDb.withPreparedStatement("SELECT [ECInstanceId] FROM [Network].[Node] WHERE [UserLabel] = ?", (stmt) => {
475
+ stmt.bindString(1, userLabel);
476
+ if (stmt.step() === DbResult.BE_SQLITE_ROW)
477
+ return stmt.getValue(0).getId();
478
+ return undefined;
479
+ });
480
+ if (!id) {
481
+ throw new Error(`Node with userLabel ${userLabel} not found`);
482
+ }
483
+ await this.updateNodeProps(iModelDb, { id });
484
+ }
485
+ static async deleteEdge(iModelDb, from, to) {
486
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
487
+ const edge = iModelDb.withPreparedStatement(`
488
+ SELECT [IDo].[ECInstanceId], [IDo].[SourceECInstanceId], [IDo].[TargetECInstanceId]
489
+ FROM [Network].[InputDrivesOutput] [IDo]
490
+ JOIN [Network].[Node] [Src] ON [Src].[ECInstanceId] = [IDo].[SourceECInstanceId]
491
+ JOIN [Network].[Node] [Tgt] ON [Tgt].[ECInstanceId] = [IDo].[TargetECInstanceId]
492
+ WHERE [Src].[UserLabel] = ? AND [Tgt].[UserLabel] = ?`, (stmt) => {
493
+ stmt.bindString(1, from);
494
+ stmt.bindString(2, to);
495
+ if (stmt.step() === DbResult.BE_SQLITE_ROW)
496
+ return {
497
+ id: stmt.getValue(0).getId(),
498
+ classFullName: InputDrivesOutput.classFullName,
499
+ sourceId: stmt.getValue(1).getId(),
500
+ targetId: stmt.getValue(2).getId(),
501
+ };
502
+ return undefined;
503
+ });
504
+ if (!edge) {
505
+ throw new Error(`Edge from ${from} to ${to} not found`);
506
+ }
507
+ iModelDb.relationships.deleteInstance(edge);
508
+ }
509
+ static async insertEdge(iModelDb, sourceId, targetId, prop) {
510
+ const props = {
511
+ classFullName: InputDrivesOutput.classFullName,
512
+ sourceId,
513
+ targetId,
514
+ prop,
515
+ status: 0,
516
+ priority: 0
517
+ };
518
+ return iModelDb.relationships.insertInstance(props);
519
+ }
520
+ }
521
+ describe("ElementDrivesElement Tests", () => {
522
+ const briefcases = [];
523
+ let iModelId;
524
+ async function openBriefcase() {
525
+ const iModelDb = await HubWrappers.downloadAndOpenBriefcase({ iTwinId: HubMock.iTwinId, iModelId });
526
+ iModelDb.channels.addAllowedChannel(ChannelControl.sharedChannelName);
527
+ iModelDb.saveChanges();
528
+ briefcases.push(iModelDb);
529
+ return iModelDb;
530
+ }
531
+ beforeEach(async () => {
532
+ HubMock.startup("TestIModel", KnownTestLocations.outputDir);
533
+ iModelId = await HubMock.createNewIModel({ iTwinId: HubMock.iTwinId, iModelName: "Test", description: "TestSubject" });
534
+ });
535
+ afterEach(async () => {
536
+ NodeElement.events.onAllInputsHandled.clear();
537
+ NodeElement.events.onBeforeOutputsHandled.clear();
538
+ InputDrivesOutput.events.onRootChanged.clear();
539
+ InputDrivesOutput.events.onDeletedDependency.clear();
540
+ for (const briefcase of briefcases) {
541
+ briefcase.close();
542
+ }
543
+ HubMock.shutdown();
544
+ });
545
+ it("local: topological sort", async () => {
546
+ const graph = new Graph();
547
+ graph.addEdge("1", ["2", "3"]);
548
+ graph.addEdge("2", ["5", "4"]);
549
+ graph.addEdge("3", ["4"]);
550
+ graph.addEdge("4", ["5"]);
551
+ const df = TopologicalSorter.sortDepthFirst(graph);
552
+ chai.expect(TopologicalSorter.validate(graph, df)).to.be.true;
553
+ chai.expect(df).to.deep.equal(["1", "3", "2", "4", "5"]);
554
+ const bf = TopologicalSorter.sortBreadthFirst(graph);
555
+ chai.expect(TopologicalSorter.validate(graph, bf)).to.be.true;
556
+ chai.expect(bf).to.deep.equal(["1", "2", "3", "4", "5"]);
557
+ });
558
+ it("local: cycle detection (suppress cycles)", async () => {
559
+ const graph = new Graph();
560
+ // Graph structure:
561
+ // 1 --> 2 <-- 3
562
+ // ^ |
563
+ // |-----------|
564
+ graph.addEdge("1", ["2"]);
565
+ graph.addEdge("2", ["3"]);
566
+ graph.addEdge("3", ["1"]);
567
+ const df = TopologicalSorter.sortDepthFirst(graph, [], false);
568
+ chai.expect(TopologicalSorter.validate(graph, df)).to.be.false;
569
+ chai.expect(df).to.deep.equal(["1", "2", "3"]);
570
+ });
571
+ it("local: cycle detection (throw)", async () => {
572
+ const graph = new Graph();
573
+ // Graph structure:
574
+ // 1 --> 2 --> 3
575
+ // ^ |
576
+ // |-----------|
577
+ graph.addEdge("1", ["2"]);
578
+ graph.addEdge("2", ["3"]);
579
+ graph.addEdge("3", ["1"]);
580
+ chai.expect(() => TopologicalSorter.sortDepthFirst(graph)).to.throw("Graph has a cycle");
581
+ });
582
+ it("EDE/local: build system dependencies", async () => {
583
+ const graph = new Graph();
584
+ /*
585
+ Example: Build system dependencies
586
+
587
+ - Compile main.c and util.c to main.o and util.o
588
+ - Link main.o and util.o to produce app.exe
589
+ - app.exe depends on config.json
590
+ - test.exe depends on main.o, util.o, and test.c
591
+
592
+ Graph:
593
+ main.c util.c test.c config.json
594
+ | | | |
595
+ v v | |
596
+ main.o util.o | |
597
+ \ / | |
598
+ \ / | |
599
+ app.exe test.exe |
600
+ | | |
601
+ +----------------+---------+
602
+ */
603
+ graph.addEdge("main.c", ["main.o"]);
604
+ graph.addEdge("util.c", ["util.o"]);
605
+ graph.addEdge("test.c", ["test.exe"]);
606
+ graph.addEdge("main.o", ["app.exe", "test.exe"]);
607
+ graph.addEdge("util.o", ["app.exe", "test.exe"]);
608
+ graph.addEdge("config.json", ["app.exe", "test.exe"]);
609
+ // create graph
610
+ const b1 = await openBriefcase();
611
+ const { modelId, } = await Engine.initialize(b1);
612
+ const monitor = new ElementDrivesElementEventMonitor(b1);
613
+ await Engine.createGraph(b1, modelId, graph);
614
+ b1.saveChanges();
615
+ b1.saveChanges();
616
+ chai.expect(monitor.onRootChanged).to.deep.equal([
617
+ ["main.c", "main.o"],
618
+ ["main.o", "test.exe"],
619
+ ["main.o", "app.exe"],
620
+ ["util.c", "util.o"],
621
+ ["util.o", "test.exe"],
622
+ ["util.o", "app.exe"],
623
+ ["test.c", "test.exe"],
624
+ ["config.json", "test.exe"],
625
+ ["config.json", "app.exe"],
626
+ ]);
627
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal(["main.o", "util.o", "test.exe", "app.exe"]);
628
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal(["main.c", "util.c", "test.c", "config.json"]);
629
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([]);
630
+ // update main.c
631
+ monitor.clear();
632
+ await Engine.updateNode(b1, "main.c");
633
+ b1.saveChanges();
634
+ chai.expect(monitor.onRootChanged).to.deep.equal([
635
+ ["main.c", "main.o"],
636
+ ["main.o", "test.exe"],
637
+ ["main.o", "app.exe"],
638
+ ]);
639
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal(["main.o", "test.exe", "app.exe"]);
640
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal(["main.c"]);
641
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([]);
642
+ // Topological sort (depth-first)
643
+ const df = TopologicalSorter.sortDepthFirst(graph);
644
+ chai.expect(TopologicalSorter.validate(graph, df)).to.be.true;
645
+ // Topological sort (breadth-first)
646
+ const bf = TopologicalSorter.sortBreadthFirst(graph);
647
+ chai.expect(TopologicalSorter.validate(graph, bf)).to.be.true;
648
+ chai.expect(df).to.deep.equal(["config.json", "test.c", "util.c", "util.o", "main.c", "main.o", "test.exe", "app.exe"]);
649
+ chai.expect(bf).to.deep.equal(["main.c", "util.c", "test.c", "config.json", "main.o", "util.o", "app.exe", "test.exe"]);
650
+ });
651
+ it("EDE/local: complex, subset", async () => {
652
+ const graph = new Graph();
653
+ /*
654
+ Graph shows what must be put on before other items:
655
+ - Socks before Shoes
656
+ - Underwear before Shoes and Pants
657
+ - Pants before Belt and Shoes
658
+ - Shirt before Belt and Tie
659
+ - Tie before Jacket
660
+ - Belt before Jacket
661
+ - Watch has no dependencies
662
+ */
663
+ graph.addEdge("Socks", ["Shoes"]);
664
+ graph.addEdge("Underwear", ["Shoes", "Pants"]);
665
+ graph.addEdge("Pants", ["Belt", "Shoes"]);
666
+ graph.addEdge("Shirt", ["Belt", "Tie"]);
667
+ graph.addEdge("Tie", ["Jacket"]);
668
+ graph.addEdge("Belt", ["Jacket"]);
669
+ graph.addNode("Watch");
670
+ // Test using EDE
671
+ const b1 = await openBriefcase();
672
+ const { modelId, } = await Engine.initialize(b1);
673
+ const monitor = new ElementDrivesElementEventMonitor(b1);
674
+ await Engine.createGraph(b1, modelId, graph);
675
+ b1.saveChanges();
676
+ chai.expect(monitor.onRootChanged).to.deep.equal([
677
+ ["Socks", "Shoes"],
678
+ ["Underwear", "Shoes"],
679
+ ["Underwear", "Pants"],
680
+ ["Pants", "Shoes"],
681
+ ["Pants", "Belt"],
682
+ ["Shirt", "Belt"],
683
+ ["Belt", "Jacket"],
684
+ ["Shirt", "Tie"],
685
+ ["Tie", "Jacket"]
686
+ ]);
687
+ // Watch is missing as it is not connected to any other node.
688
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal(["Pants", "Shoes", "Belt", "Tie", "Jacket"]);
689
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal(["Socks", "Underwear", "Shirt"]);
690
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([]);
691
+ monitor.clear();
692
+ await Engine.updateNode(b1, "Socks");
693
+ b1.saveChanges();
694
+ chai.expect(monitor.onRootChanged).to.deep.equal([["Socks", "Shoes"]]);
695
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal(["Shoes"]);
696
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal(["Socks"]);
697
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([]);
698
+ const sorted = TopologicalSorter.sortDepthFirst(graph);
699
+ chai.expect(TopologicalSorter.validate(graph, sorted)).to.be.true;
700
+ chai.expect(sorted).to.deep.equal(["Watch", "Shirt", "Tie", "Underwear", "Pants", "Belt", "Jacket", "Socks", "Shoes"]);
701
+ // Test sorting with a subset of nodes
702
+ const sorted1 = TopologicalSorter.sortDepthFirst(graph, ["Underwear"]);
703
+ chai.expect(sorted1).to.deep.equal(["Underwear", "Pants", "Belt", "Jacket", "Shoes"]);
704
+ const sorted2 = TopologicalSorter.sortDepthFirst(graph, ["Belt"]);
705
+ chai.expect(sorted2).to.deep.equal(["Belt", "Jacket"]);
706
+ const sorted3 = TopologicalSorter.sortDepthFirst(graph, ["Shoes"]);
707
+ chai.expect(sorted3).to.deep.equal(["Shoes"]);
708
+ const sorted4 = TopologicalSorter.sortDepthFirst(graph, ["Socks"]);
709
+ chai.expect(sorted4).to.deep.equal(["Socks", "Shoes"]);
710
+ const sorted5 = TopologicalSorter.sortDepthFirst(graph, ["Tie"]);
711
+ chai.expect(sorted5).to.deep.equal(["Tie", "Jacket"]);
712
+ const sorted6 = TopologicalSorter.sortDepthFirst(graph, ["Jacket"]);
713
+ chai.expect(sorted6).to.deep.equal(["Jacket"]);
714
+ const sorted7 = TopologicalSorter.sortDepthFirst(graph, ["Shirt"]);
715
+ chai.expect(sorted7).to.deep.equal(["Shirt", "Tie", "Belt", "Jacket"]);
716
+ const sorted8 = TopologicalSorter.sortDepthFirst(graph, ["Watch"]);
717
+ chai.expect(sorted8).to.deep.equal(["Watch"]);
718
+ });
719
+ it("EDE: basic graph operations", async () => {
720
+ const b1 = await openBriefcase();
721
+ const { modelId, } = await Engine.initialize(b1);
722
+ const graph = new Graph();
723
+ // Graph structure:
724
+ // A
725
+ // / \
726
+ // B C
727
+ // |\ /
728
+ // | \/
729
+ // E--D
730
+ graph.addEdge("A", ["B", "C"]);
731
+ graph.addEdge("B", ["E", "D"]);
732
+ graph.addEdge("C", ["D"]);
733
+ graph.addEdge("D", ["E"]);
734
+ const monitor = new ElementDrivesElementEventMonitor(b1);
735
+ // create a network
736
+ await Engine.createGraph(b1, modelId, graph);
737
+ b1.saveChanges();
738
+ chai.expect(monitor.onRootChanged).to.deep.equal([
739
+ ["A", "B"],
740
+ ["A", "C"],
741
+ ["B", "E"],
742
+ ["B", "D"],
743
+ ["C", "D"],
744
+ ["D", "E"]
745
+ ]);
746
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal(["B", "C", "D", "E"]);
747
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal(["A"]);
748
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([]);
749
+ monitor.clear();
750
+ // update a node in network
751
+ await Engine.updateNode(b1, "B");
752
+ b1.saveChanges();
753
+ chai.expect(monitor.onRootChanged).to.deep.equal([
754
+ ["B", "E"],
755
+ ["B", "D"],
756
+ ["D", "E"]
757
+ ]);
758
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal(["D", "E"]);
759
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal(["B"]);
760
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([]);
761
+ monitor.clear();
762
+ // delete edge in network
763
+ await Engine.deleteEdge(b1, "B", "E");
764
+ b1.saveChanges();
765
+ chai.expect(monitor.onRootChanged).to.deep.equal([]);
766
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal([]);
767
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal([]);
768
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([["B", "E"]]);
769
+ });
770
+ it("EDE: cyclical throw exception", async () => {
771
+ const b1 = await openBriefcase();
772
+ const { modelId, } = await Engine.initialize(b1);
773
+ const graph = new Graph();
774
+ // Graph structure with a cycle:
775
+ // A
776
+ // / \
777
+ // B - C
778
+ graph.addEdge("A", ["B"]);
779
+ graph.addEdge("B", ["C"]);
780
+ graph.addEdge("C", ["A"]);
781
+ const monitor = new ElementDrivesElementEventMonitor(b1);
782
+ // create a network
783
+ await Engine.createGraph(b1, modelId, graph);
784
+ chai.expect(() => b1.saveChanges()).to.throw("Could not save changes due to propagation failure.");
785
+ b1.abandonChanges();
786
+ chai.expect(monitor.onRootChanged).to.deep.equal([["B", "C"], ["C", "A"], ["A", "B"]]);
787
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal(["C", "A", "B"]);
788
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal([]);
789
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([]);
790
+ monitor.clear();
791
+ });
792
+ it("EDE: cyclical graph can start propagation with no clear starting element", async () => {
793
+ const b1 = await openBriefcase();
794
+ const { modelId, } = await Engine.initialize(b1);
795
+ const graph = new Graph();
796
+ // Graph structure with a cycle:
797
+ // A
798
+ // / \
799
+ // B - C
800
+ // order of insertion effect graph with cycles.
801
+ graph.addNode("B");
802
+ graph.addNode("A");
803
+ graph.addNode("C");
804
+ graph.addEdge("A", ["B"]);
805
+ graph.addEdge("B", ["C"]);
806
+ graph.addEdge("C", ["A"]);
807
+ const monitor = new ElementDrivesElementEventMonitor(b1);
808
+ // create a network
809
+ await Engine.createGraph(b1, modelId, graph);
810
+ chai.expect(() => b1.saveChanges()).to.throw("Could not save changes due to propagation failure.");
811
+ b1.abandonChanges();
812
+ chai.expect(monitor.onRootChanged).to.deep.equal([["C", "A"], ["A", "B"], ["B", "C"]]);
813
+ chai.expect(monitor.onAllInputsHandled).to.deep.equal(["A", "B", "C"]);
814
+ chai.expect(monitor.onBeforeOutputsHandled).to.deep.equal([]);
815
+ chai.expect(monitor.onDeletedDependency).to.deep.equal([]);
816
+ monitor.clear();
817
+ });
818
+ it.skip("EDE: performance", async () => {
819
+ const b1 = await openBriefcase();
820
+ const { modelId, } = await Engine.initialize(b1);
821
+ const graph = new Graph();
822
+ const createTree = (depth, breadth, prefix) => {
823
+ if (depth === 0)
824
+ return;
825
+ for (let i = 0; i < breadth; i++) {
826
+ const node = `${prefix}${i}`;
827
+ graph.addNode(node);
828
+ if (depth > 1) {
829
+ for (let j = 0; j < breadth; j++) {
830
+ const child = `${prefix}${i}${j}`;
831
+ graph.addEdge(node, [child]);
832
+ createTree(depth - 1, breadth, `${prefix}${i}${j}`);
833
+ }
834
+ }
835
+ }
836
+ };
837
+ const stopWatch0 = new StopWatch("create graph", true);
838
+ createTree(5, 3, "N");
839
+ await Engine.createGraph(b1, modelId, graph);
840
+ stopWatch0.stop();
841
+ const createGraphTime = stopWatch0.elapsed.seconds;
842
+ let onRootChangedCount = 0;
843
+ let onDeletedDependencyCount = 0;
844
+ let onAllInputsHandledCount = 0;
845
+ let onBeforeOutputsHandledCount = 0;
846
+ InputDrivesOutput.events.onRootChanged.addListener(() => { onRootChangedCount++; });
847
+ InputDrivesOutput.events.onDeletedDependency.addListener(() => { onDeletedDependencyCount++; });
848
+ NodeElement.events.onAllInputsHandled.addListener((_id) => { onAllInputsHandledCount++; });
849
+ NodeElement.events.onBeforeOutputsHandled.addListener(() => { onBeforeOutputsHandledCount++; });
850
+ const stopWatch1 = new StopWatch("save changes", true);
851
+ b1.saveChanges();
852
+ stopWatch1.stop();
853
+ const saveChangesTime = stopWatch1.elapsed.seconds;
854
+ chai.expect(onRootChangedCount).to.be.equals(7380);
855
+ chai.expect(onDeletedDependencyCount).to.equal(0);
856
+ chai.expect(onAllInputsHandledCount).to.be.equals(7380);
857
+ chai.expect(onBeforeOutputsHandledCount).to.equal(2460);
858
+ chai.expect(createGraphTime).to.be.lessThan(3);
859
+ chai.expect(saveChangesTime).to.be.lessThan(3);
860
+ });
861
+ });
862
+ //# sourceMappingURL=ElementDrivesElement.test.js.map