variable-engine.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import { Subject } from 'rxjs';
  6. import { inject, injectable, interfaces, preDestroy } from 'inversify';
  7. import { Disposable, DisposableCollection } from '@flowgram.ai/utils';
  8. import { Emitter } from '@flowgram.ai/utils';
  9. import { subsToDisposable } from './utils/toDisposable';
  10. import { createMemo } from './utils/memo';
  11. import { VariableTable } from './scope/variable-table';
  12. import { ScopeChangeAction } from './scope/types';
  13. import { IScopeConstructor } from './scope/scope';
  14. import { Scope, ScopeChain, type IVariableTable } from './scope';
  15. import { ContainerProvider } from './providers';
  16. import { ASTRegisters, type GlobalEventActionType } from './ast';
  17. /**
  18. * The core of the variable engine system.
  19. * It manages scopes, variables, and events within the system.
  20. */
  21. @injectable()
  22. export class VariableEngine implements Disposable {
  23. protected toDispose = new DisposableCollection();
  24. protected memo = createMemo();
  25. protected scopeMap = new Map<string | symbol, Scope>();
  26. /**
  27. * A rxjs subject that emits global events occurring within the variable engine.
  28. */
  29. globalEvent$: Subject<GlobalEventActionType> = new Subject<GlobalEventActionType>();
  30. protected onScopeChangeEmitter = new Emitter<ScopeChangeAction>();
  31. /**
  32. * A table containing all global variables.
  33. */
  34. public globalVariableTable: IVariableTable = new VariableTable();
  35. /**
  36. * An event that fires whenever a scope is added, updated, or deleted.
  37. */
  38. public onScopeChange = this.onScopeChangeEmitter.event;
  39. @inject(ContainerProvider) private readonly containerProvider: ContainerProvider;
  40. /**
  41. * The Inversify container instance.
  42. */
  43. get container(): interfaces.Container {
  44. return this.containerProvider();
  45. }
  46. constructor(
  47. /**
  48. * The scope chain, which manages the dependency relationships between scopes.
  49. */
  50. @inject(ScopeChain)
  51. public readonly chain: ScopeChain,
  52. /**
  53. * The registry for all AST node types.
  54. */
  55. @inject(ASTRegisters)
  56. public readonly astRegisters: ASTRegisters
  57. ) {
  58. this.toDispose.pushAll([
  59. chain,
  60. Disposable.create(() => {
  61. // Dispose all scopes
  62. this.getAllScopes().forEach((scope) => scope.dispose());
  63. this.globalVariableTable.dispose();
  64. }),
  65. ]);
  66. }
  67. /**
  68. * Disposes of all resources used by the variable engine.
  69. */
  70. @preDestroy()
  71. dispose(): void {
  72. this.toDispose.dispose();
  73. }
  74. /**
  75. * Retrieves a scope by its unique identifier.
  76. * @param scopeId The ID of the scope to retrieve.
  77. * @returns The scope if found, otherwise undefined.
  78. */
  79. getScopeById(scopeId: string | symbol): Scope | undefined {
  80. return this.scopeMap.get(scopeId);
  81. }
  82. /**
  83. * Removes a scope by its unique identifier and disposes of it.
  84. * @param scopeId The ID of the scope to remove.
  85. */
  86. removeScopeById(scopeId: string | symbol): void {
  87. this.getScopeById(scopeId)?.dispose();
  88. }
  89. /**
  90. * Creates a new scope or retrieves an existing one if the ID and type match.
  91. * @param id The unique identifier for the scope.
  92. * @param meta Optional metadata for the scope, defined by the user.
  93. * @param options Options for creating the scope.
  94. * @param options.ScopeConstructor The constructor to use for creating the scope. Defaults to `Scope`.
  95. * @returns The created or existing scope.
  96. */
  97. createScope(
  98. id: string | symbol,
  99. meta?: Record<string, any>,
  100. options: {
  101. ScopeConstructor?: IScopeConstructor;
  102. } = {}
  103. ): Scope {
  104. const { ScopeConstructor = Scope } = options;
  105. let scope = this.getScopeById(id);
  106. if (!scope) {
  107. scope = new ScopeConstructor({ variableEngine: this, meta, id });
  108. this.scopeMap.set(id, scope);
  109. this.onScopeChangeEmitter.fire({ type: 'add', scope: scope! });
  110. scope.toDispose.pushAll([
  111. scope.ast.subscribe(() => {
  112. this.onScopeChangeEmitter.fire({ type: 'update', scope: scope! });
  113. }),
  114. // Fires when available variables change
  115. scope.available.onDataChange(() => {
  116. this.onScopeChangeEmitter.fire({ type: 'available', scope: scope! });
  117. }),
  118. ]);
  119. scope.onDispose(() => {
  120. this.scopeMap.delete(id);
  121. this.onScopeChangeEmitter.fire({ type: 'delete', scope: scope! });
  122. });
  123. }
  124. return scope;
  125. }
  126. /**
  127. * Retrieves all scopes currently managed by the engine.
  128. * @param options Options for retrieving the scopes.
  129. * @param options.sort Whether to sort the scopes based on their dependency chain.
  130. * @returns An array of all scopes.
  131. */
  132. getAllScopes({
  133. sort,
  134. }: {
  135. sort?: boolean;
  136. } = {}): Scope[] {
  137. const allScopes = Array.from(this.scopeMap.values());
  138. if (sort) {
  139. const sortScopes = this.chain.sortAll();
  140. const remainScopes = new Set(allScopes);
  141. sortScopes.forEach((_scope) => remainScopes.delete(_scope));
  142. return [...sortScopes, ...Array.from(remainScopes)];
  143. }
  144. return [...allScopes];
  145. }
  146. /**
  147. * Fires a global event to be broadcast to all listeners.
  148. * @param event The global event to fire.
  149. */
  150. fireGlobalEvent(event: GlobalEventActionType) {
  151. this.globalEvent$.next(event);
  152. }
  153. /**
  154. * Subscribes to a specific type of global event.
  155. * @param type The type of the event to listen for.
  156. * @param observer A function to be called when the event is observed.
  157. * @returns A disposable object to unsubscribe from the event.
  158. */
  159. onGlobalEvent<ActionType extends GlobalEventActionType = GlobalEventActionType>(
  160. type: ActionType['type'],
  161. observer: (action: ActionType) => void
  162. ): Disposable {
  163. return subsToDisposable(
  164. this.globalEvent$.subscribe((_action) => {
  165. if (_action.type === type) {
  166. observer(_action as ActionType);
  167. }
  168. })
  169. );
  170. }
  171. }