flowGraph.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import { FlowGraphEventBlock } from "./flowGraphEventBlock.js";
  2. import { FlowGraphContext } from "./flowGraphContext.js";
  3. import { FlowGraphBlock } from "./flowGraphBlock.js";
  4. import { FlowGraphExecutionBlock } from "./flowGraphExecutionBlock.js";
  5. import { FlowGraphMeshPickEventBlock } from "./Blocks/Event/flowGraphMeshPickEventBlock.js";
  6. import { _isADescendantOf } from "./utils.js";
  7. import { defaultValueParseFunction } from "./serialization.js";
  8. export var FlowGraphState;
  9. (function (FlowGraphState) {
  10. /**
  11. * The graph is stopped
  12. */
  13. FlowGraphState[FlowGraphState["Stopped"] = 0] = "Stopped";
  14. /**
  15. * The graph is running
  16. */
  17. FlowGraphState[FlowGraphState["Started"] = 1] = "Started";
  18. })(FlowGraphState || (FlowGraphState = {}));
  19. /**
  20. * @experimental
  21. * Class used to represent a flow graph.
  22. * A flow graph is a graph of blocks that can be used to create complex logic.
  23. * Blocks can be added to the graph and connected to each other.
  24. * The graph can then be started, which will init and start all of its event blocks.
  25. */
  26. export class FlowGraph {
  27. /**
  28. * Construct a Flow Graph
  29. * @param params construction parameters. currently only the scene
  30. */
  31. constructor(params) {
  32. /** @internal */
  33. this._eventBlocks = [];
  34. this._executionContexts = [];
  35. /**
  36. * The state of the graph
  37. */
  38. this.state = FlowGraphState.Stopped;
  39. this._scene = params.scene;
  40. this._coordinator = params.coordinator;
  41. this._sceneDisposeObserver = this._scene.onDisposeObservable.add(() => this.dispose());
  42. }
  43. /**
  44. * Create a context. A context represents one self contained execution for the graph, with its own variables.
  45. * @returns the context, where you can get and set variables
  46. */
  47. createContext() {
  48. const context = new FlowGraphContext({ scene: this._scene, coordinator: this._coordinator });
  49. this._executionContexts.push(context);
  50. return context;
  51. }
  52. /**
  53. * Returns the execution context at a given index
  54. * @param index the index of the context
  55. * @returns the execution context at that index
  56. */
  57. getContext(index) {
  58. return this._executionContexts[index];
  59. }
  60. /**
  61. * Add an event block. When the graph is started, it will start listening to events
  62. * from the block and execute the graph when they are triggered.
  63. * @param block the event block to be added
  64. */
  65. addEventBlock(block) {
  66. this._eventBlocks.push(block);
  67. }
  68. /**
  69. * Starts the flow graph. Initializes the event blocks and starts listening to events.
  70. */
  71. start() {
  72. if (this.state === FlowGraphState.Started) {
  73. return;
  74. }
  75. this.state = FlowGraphState.Started;
  76. if (this._executionContexts.length === 0) {
  77. this.createContext();
  78. }
  79. for (const context of this._executionContexts) {
  80. const contextualOrder = this._getContextualOrder();
  81. for (const block of contextualOrder) {
  82. block._startPendingTasks(context);
  83. }
  84. }
  85. }
  86. _getContextualOrder() {
  87. const order = [];
  88. for (const block1 of this._eventBlocks) {
  89. // If the block is a mesh pick, guarantee that picks of children meshes come before picks of parent meshes
  90. if (block1.getClassName() === FlowGraphMeshPickEventBlock.ClassName) {
  91. const mesh1 = block1._getReferencedMesh();
  92. let i = 0;
  93. for (; i < order.length; i++) {
  94. const block2 = order[i];
  95. const mesh2 = block2._getReferencedMesh();
  96. if (mesh1 && mesh2 && _isADescendantOf(mesh1, mesh2)) {
  97. break;
  98. }
  99. }
  100. order.splice(i, 0, block1);
  101. }
  102. else {
  103. order.push(block1);
  104. }
  105. }
  106. return order;
  107. }
  108. /**
  109. * Disposes of the flow graph. Cancels any pending tasks and removes all event listeners.
  110. */
  111. dispose() {
  112. if (this.state === FlowGraphState.Stopped) {
  113. return;
  114. }
  115. this.state = FlowGraphState.Stopped;
  116. for (const context of this._executionContexts) {
  117. context._clearPendingBlocks();
  118. }
  119. this._executionContexts.length = 0;
  120. this._eventBlocks.length = 0;
  121. this._scene.onDisposeObservable.remove(this._sceneDisposeObserver);
  122. this._sceneDisposeObserver = null;
  123. }
  124. /**
  125. * Executes a function in all blocks of a flow graph, starting with the event blocks.
  126. * @param visitor the function to execute.
  127. */
  128. visitAllBlocks(visitor) {
  129. const visitList = [];
  130. const idsAddedToVisitList = new Set();
  131. for (const block of this._eventBlocks) {
  132. visitList.push(block);
  133. idsAddedToVisitList.add(block.uniqueId);
  134. }
  135. while (visitList.length > 0) {
  136. const block = visitList.pop();
  137. visitor(block);
  138. for (const dataIn of block.dataInputs) {
  139. for (const connection of dataIn._connectedPoint) {
  140. if (!idsAddedToVisitList.has(connection._ownerBlock.uniqueId)) {
  141. visitList.push(connection._ownerBlock);
  142. idsAddedToVisitList.add(connection._ownerBlock.uniqueId);
  143. }
  144. }
  145. }
  146. if (block instanceof FlowGraphExecutionBlock) {
  147. for (const signalOut of block.signalOutputs) {
  148. for (const connection of signalOut._connectedPoint) {
  149. if (!idsAddedToVisitList.has(connection._ownerBlock.uniqueId)) {
  150. visitList.push(connection._ownerBlock);
  151. idsAddedToVisitList.add(connection._ownerBlock.uniqueId);
  152. }
  153. }
  154. }
  155. }
  156. }
  157. }
  158. /**
  159. * Serializes a graph
  160. * @param serializationObject the object to write the values in
  161. * @param valueSerializeFunction a function to serialize complex values
  162. */
  163. serialize(serializationObject = {}, valueSerializeFunction) {
  164. serializationObject.allBlocks = [];
  165. this.visitAllBlocks((block) => {
  166. const serializedBlock = {};
  167. block.serialize(serializedBlock);
  168. serializationObject.allBlocks.push(serializedBlock);
  169. });
  170. serializationObject.executionContexts = [];
  171. for (const context of this._executionContexts) {
  172. const serializedContext = {};
  173. context.serialize(serializedContext, valueSerializeFunction);
  174. serializationObject.executionContexts.push(serializedContext);
  175. }
  176. }
  177. /**
  178. * Given a list of blocks, find an output data connection that has a specific unique id
  179. * @param blocks a list of flow graph blocks
  180. * @param uniqueId the unique id of a connection
  181. * @returns the connection that has this unique id. throws an error if none was found
  182. */
  183. static GetDataOutConnectionByUniqueId(blocks, uniqueId) {
  184. for (const block of blocks) {
  185. for (const dataOut of block.dataOutputs) {
  186. if (dataOut.uniqueId === uniqueId) {
  187. return dataOut;
  188. }
  189. }
  190. }
  191. throw new Error("Could not find data out connection with unique id " + uniqueId);
  192. }
  193. /**
  194. * Given a list of blocks, find an input signal connection that has a specific unique id
  195. * @param blocks a list of flow graph blocks
  196. * @param uniqueId the unique id of a connection
  197. * @returns the connection that has this unique id. throws an error if none was found
  198. */
  199. static GetSignalInConnectionByUniqueId(blocks, uniqueId) {
  200. for (const block of blocks) {
  201. if (block instanceof FlowGraphExecutionBlock) {
  202. for (const signalIn of block.signalInputs) {
  203. if (signalIn.uniqueId === uniqueId) {
  204. return signalIn;
  205. }
  206. }
  207. }
  208. }
  209. throw new Error("Could not find signal in connection with unique id " + uniqueId);
  210. }
  211. /**
  212. * Parses a graph from a given serialization object
  213. * @param serializationObject the object where the values are written
  214. * @param options options for parsing the graph
  215. * @returns the parsed graph
  216. */
  217. static Parse(serializationObject, options) {
  218. const graph = options.coordinator.createGraph();
  219. const blocks = [];
  220. const valueParseFunction = options.valueParseFunction ?? defaultValueParseFunction;
  221. // Parse all blocks
  222. for (const serializedBlock of serializationObject.allBlocks) {
  223. const block = FlowGraphBlock.Parse(serializedBlock, { scene: options.coordinator.config.scene, pathConverter: options.pathConverter, valueParseFunction });
  224. blocks.push(block);
  225. if (block instanceof FlowGraphEventBlock) {
  226. graph.addEventBlock(block);
  227. }
  228. }
  229. // After parsing all blocks, connect them
  230. for (const block of blocks) {
  231. for (const dataIn of block.dataInputs) {
  232. for (const serializedConnection of dataIn.connectedPointIds) {
  233. const connection = FlowGraph.GetDataOutConnectionByUniqueId(blocks, serializedConnection);
  234. dataIn.connectTo(connection);
  235. }
  236. }
  237. if (block instanceof FlowGraphExecutionBlock) {
  238. for (const signalOut of block.signalOutputs) {
  239. for (const serializedConnection of signalOut.connectedPointIds) {
  240. const connection = FlowGraph.GetSignalInConnectionByUniqueId(blocks, serializedConnection);
  241. signalOut.connectTo(connection);
  242. }
  243. }
  244. }
  245. }
  246. for (const serializedContext of serializationObject.executionContexts) {
  247. FlowGraphContext.Parse(serializedContext, { graph, valueParseFunction });
  248. }
  249. return graph;
  250. }
  251. }
  252. //# sourceMappingURL=flowGraph.js.map