UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

1,158 lines (1,152 loc) 202 kB
import { y as Logger, M as Matrix, b3 as Vector2, V as Vector3, bc as Vector4, aD as Quaternion, i as Color3, b1 as Color4, l as __runInitializers, v as RandomGUID, O as Observable, q as __esDecorate, s as serialize, cC as IsNavigatorAvailable, by as PointerEventTypes, cd as KeyboardEventTypes, aY as Tools, A as AbstractEngine, bo as unregisterGLTFExtension, bp as registerGLTFExtension } from './index-MZPybX0H.esm.js'; import { o as FlowGraphMatrix2D, m as FlowGraphMatrix3D, F as FlowGraphInteger, g as getRichTypeByFlowGraphType, r as getMappingForDeclaration, s as getMappingForFullOperationName } from './declarationMapper-CqO08-WM.esm.js'; import { G as GetPathToObjectConverter, A as AddObjectAccessorToKey } from './objectModelMapping-De5EKNEZ.esm.js'; function IsVectorClassName(className) { return (className === "Vector2" /* FlowGraphTypes.Vector2 */ || className === "Vector3" /* FlowGraphTypes.Vector3 */ || className === "Vector4" /* FlowGraphTypes.Vector4 */ || className === "Quaternion" /* FlowGraphTypes.Quaternion */ || className === "Color3" /* FlowGraphTypes.Color3 */ || className === "Color4" /* FlowGraphTypes.Color4 */); } function IsMatrixClassName(className) { return className === "Matrix" /* FlowGraphTypes.Matrix */ || className === "Matrix2D" /* FlowGraphTypes.Matrix2D */ || className === "Matrix3D" /* FlowGraphTypes.Matrix3D */; } function IsAnimationGroupClassName(className) { return className === "AnimationGroup"; } /** * Resolves a serialized node reference (`{ id, name, className, uniqueId }`) to an actual scene node. * Matching prefers `id` (falling back to `name`), then narrows by class name, then by `uniqueId`. * Because `uniqueId` is reassigned every time a scene is built, it is only used as a tie-breaker so * that references still resolve after a scene is reloaded (e.g. an editor preview). * @param serializedReference the serialized reference to resolve * @param scene the scene to resolve the reference against * @returns the matching node, or undefined when none is found */ // eslint-disable-next-line @typescript-eslint/naming-convention function GetSceneNodeFromSerializedReference(serializedReference, scene) { if (!serializedReference || (!serializedReference.id && !serializedReference.name)) { return undefined; } const nodes = scene.getNodes().filter((node) => (serializedReference.id ? node.id === serializedReference.id : node.name === serializedReference.name)); if (nodes.length === 0) { return undefined; } const className = serializedReference.type ?? serializedReference.className; const classMatches = className ? nodes.filter((node) => node.getClassName() === className) : []; const candidates = classMatches.length > 0 ? classMatches : nodes; return (serializedReference.uniqueId ? candidates.find((node) => node.uniqueId === serializedReference.uniqueId) : undefined) ?? candidates[0]; } function ParseVector(className, value, flipHandedness = false) { if (className === "Vector2" /* FlowGraphTypes.Vector2 */) { return Vector2.FromArray(value); } else if (className === "Vector3" /* FlowGraphTypes.Vector3 */) { if (flipHandedness) { value[2] *= -1; } return Vector3.FromArray(value); } else if (className === "Vector4" /* FlowGraphTypes.Vector4 */) { return Vector4.FromArray(value); } else if (className === "Quaternion" /* FlowGraphTypes.Quaternion */) { if (flipHandedness) { value[2] *= -1; value[3] *= -1; } return Quaternion.FromArray(value); } else if (className === "Color3" /* FlowGraphTypes.Color3 */) { return new Color3(value[0], value[1], value[2]); } else if (className === "Color4" /* FlowGraphTypes.Color4 */) { return new Color4(value[0], value[1], value[2], value[3]); } else { throw new Error(`Unknown vector class name ${className}`); } } /** * The default function that serializes values in a context object to a serialization object * @param key the key where the value should be stored in the serialization object * @param value the value to store * @param serializationObject the object where the value will be stored */ // eslint-disable-next-line @typescript-eslint/naming-convention function defaultValueSerializationFunction(key, value, serializationObject) { const className = value?.getClassName?.() ?? ""; if (IsVectorClassName(className) || IsMatrixClassName(className)) { serializationObject[key] = { value: value.asArray(), className, }; } else if (className === "FlowGraphInteger" /* FlowGraphTypes.Integer */) { serializationObject[key] = { value: value.value, className, }; } else { if (className && (value.id || value.name)) { serializationObject[key] = { id: value.id, name: value.name, className, uniqueId: value.uniqueId, }; } else { if (typeof value !== "object" || value === null) { serializationObject[key] = value; } else { // Skip known non-serializable keys immediately to avoid // expensive JSON.stringify attempts on large object trees // (e.g. pathConverter holds the entire glTF parse tree). if (key === "pathConverter") { return; } // Quick check: if any own property is a function, the object // is not JSON-safe and stringify would be wasteful. const hasFunction = Object.values(value).some((v) => typeof v === "function"); if (hasFunction) { return; } // Plain object (e.g. parsed event config) — store it if JSON-safe. try { serializationObject[key] = JSON.parse(JSON.stringify(value)); } catch { Logger.Warn(`FlowGraph serialization: value for key "${key}" is not JSON-serializable and was skipped.`); } } } } } /** * The default function that parses values stored in a serialization object * @param key the key to the value that will be parsed * @param serializationObject the object that will be parsed * @param assetsContainer the assets container that will be used to find the objects * @param scene * @returns */ // eslint-disable-next-line @typescript-eslint/naming-convention function defaultValueParseFunction(key, serializationObject, assetsContainer, scene) { const intermediateValue = serializationObject[key]; let finalValue; const className = intermediateValue?.type ?? intermediateValue?.className; const sceneNode = GetSceneNodeFromSerializedReference(intermediateValue, scene); if (sceneNode) { finalValue = sceneNode; } else if (IsVectorClassName(className)) { finalValue = ParseVector(className, intermediateValue.value); } else if (IsAnimationGroupClassName(className)) { // do not use the scene.getAnimationGroupByName because it is possible that two AGs will have the same name const ags = scene.animationGroups.filter((ag) => ag.name === intermediateValue.name); // uniqueId changes on each load. this is used for the glTF loader, that uses serialization after the scene was loaded. finalValue = ags.length === 1 ? ags[0] : ags.find((ag) => ag.uniqueId === intermediateValue.uniqueId); } else if (className === "Matrix" /* FlowGraphTypes.Matrix */) { finalValue = Matrix.FromArray(intermediateValue.value); } else if (className === "Matrix2D" /* FlowGraphTypes.Matrix2D */) { finalValue = new FlowGraphMatrix2D(intermediateValue.value); } else if (className === "Matrix3D" /* FlowGraphTypes.Matrix3D */) { finalValue = new FlowGraphMatrix3D(intermediateValue.value); } else if (className === "FlowGraphInteger" /* FlowGraphTypes.Integer */) { finalValue = FlowGraphInteger.FromValue(intermediateValue.value); } else if (className === "number" /* FlowGraphTypes.Number */ || className === "string" /* FlowGraphTypes.String */ || className === "boolean" /* FlowGraphTypes.Boolean */) { finalValue = intermediateValue.value[0]; } else if (intermediateValue && intermediateValue.value !== undefined) { finalValue = intermediateValue.value; } else { if (Array.isArray(intermediateValue)) { // Check if this is an event configuration array (objects with id/eventData) // versus a plain array of primitives (e.g. variable name lists) if (intermediateValue.length > 0 && typeof intermediateValue[0] === "object" && intermediateValue[0] !== null && "eventData" in intermediateValue[0]) { // configuration data of an event finalValue = intermediateValue.reduce((acc, val) => { if (!val.eventData) { return acc; } acc[val.id] = { type: getRichTypeByFlowGraphType(val.type), }; if (typeof val.value !== "undefined") { acc[val.id].value = defaultValueParseFunction("value", val, assetsContainer, scene); } return acc; }, {}); } else { // Plain array of primitives — return as-is finalValue = intermediateValue; } } else { finalValue = intermediateValue; } } return finalValue; } /** * Given a name of a flow graph block class, return if this * class needs to be created with a path converter. Used in * parsing. * @param className the name of the flow graph block class * @returns a boolean indicating if the class needs a path converter */ // eslint-disable-next-line @typescript-eslint/naming-convention function needsPathConverter(className) { // I am not using the ClassName property here because it was causing a circular dependency return className === "FlowGraphJsonPointerParserBlock" /* FlowGraphBlockNames.JsonPointerParser */; } /** * The type of the assets that flow graph supports */ var FlowGraphAssetType; (function (FlowGraphAssetType) { FlowGraphAssetType["Animation"] = "Animation"; FlowGraphAssetType["AnimationGroup"] = "AnimationGroup"; FlowGraphAssetType["Mesh"] = "Mesh"; FlowGraphAssetType["Material"] = "Material"; FlowGraphAssetType["Camera"] = "Camera"; FlowGraphAssetType["Light"] = "Light"; // Further asset types will be added here when needed. })(FlowGraphAssetType || (FlowGraphAssetType = {})); /** * Returns the asset with the given index and type from the assets context. * @param assetsContext The assets context to get the asset from * @param type The type of the asset * @param index The index of the asset * @param useIndexAsUniqueId If set to true, instead of the index in the array it will search for the unique id of the asset. * @returns The asset or null if not found */ function GetFlowGraphAssetWithType(assetsContext, type, index, useIndexAsUniqueId) { switch (type) { case "Animation" /* FlowGraphAssetType.Animation */: return useIndexAsUniqueId ? (assetsContext.animations.find((a) => a.uniqueId === index) ?? null) : (assetsContext.animations[index] ?? null); case "AnimationGroup" /* FlowGraphAssetType.AnimationGroup */: return useIndexAsUniqueId ? (assetsContext.animationGroups.find((a) => a.uniqueId === index) ?? null) : (assetsContext.animationGroups[index] ?? null); case "Mesh" /* FlowGraphAssetType.Mesh */: return useIndexAsUniqueId ? (assetsContext.meshes.find((a) => a.uniqueId === index) ?? null) : (assetsContext.meshes[index] ?? null); case "Material" /* FlowGraphAssetType.Material */: return useIndexAsUniqueId ? (assetsContext.materials.find((a) => a.uniqueId === index) ?? null) : (assetsContext.materials[index] ?? null); case "Camera" /* FlowGraphAssetType.Camera */: return useIndexAsUniqueId ? (assetsContext.cameras.find((a) => a.uniqueId === index) ?? null) : (assetsContext.cameras[index] ?? null); case "Light" /* FlowGraphAssetType.Light */: return useIndexAsUniqueId ? (assetsContext.lights.find((a) => a.uniqueId === index) ?? null) : (assetsContext.lights[index] ?? null); default: return null; } } var FlowGraphAction; (function (FlowGraphAction) { FlowGraphAction["ExecuteBlock"] = "ExecuteBlock"; FlowGraphAction["ExecuteEvent"] = "ExecuteEvent"; FlowGraphAction["TriggerConnection"] = "TriggerConnection"; FlowGraphAction["ContextVariableSet"] = "ContextVariableSet"; FlowGraphAction["GlobalVariableSet"] = "GlobalVariableSet"; FlowGraphAction["GlobalVariableDelete"] = "GlobalVariableDelete"; FlowGraphAction["GlobalVariableGet"] = "GlobalVariableGet"; FlowGraphAction["AddConnection"] = "AddConnection"; FlowGraphAction["GetConnectionValue"] = "GetConnectionValue"; FlowGraphAction["SetConnectionValue"] = "SetConnectionValue"; FlowGraphAction["ActivateSignal"] = "ActivateSignal"; FlowGraphAction["ContextVariableGet"] = "ContextVariableGet"; })(FlowGraphAction || (FlowGraphAction = {})); /** * This class will be responsible of logging the flow graph activity. * Note that using this class might reduce performance, as it will log every action, according to the configuration. * It attaches to a flow graph and uses meta-programming to replace the methods of the flow graph to add logging abilities. */ class FlowGraphLogger { constructor() { /** * Whether to log to the console. */ this.logToConsole = false; /** * The log cache of the flow graph. * Each item is a logged item, in order of execution. */ this.log = []; } addLogItem(item) { if (!item.time) { item.time = Date.now(); } this.log.push(item); if (this.logToConsole) { const value = item.payload?.value; if (typeof value === "object" && value.getClassName) { Logger.Log(`[FGLog] ${item.className}:${item.uniqueId.split("-")[0]} ${item.action} - ${JSON.stringify(value.getClassName())}: ${value.toString()}`); } else { Logger.Log(`[FGLog] ${item.className}:${item.uniqueId.split("-")[0]} ${item.action} - ${JSON.stringify(item.payload)}`); } } } getItemsOfType(action) { return this.log.filter((i) => i.action === action); } } /** * The context represents the current state and execution of the flow graph. * It contains both user-defined variables, which are derived from * a more general variable definition, and execution variables that * are set by the blocks. */ let FlowGraphContext = (() => { var _a; let _uniqueId_decorators; let _uniqueId_initializers = []; let _uniqueId_extraInitializers = []; let _name_decorators; let _name_initializers = []; let _name_extraInitializers = []; return _a = class FlowGraphContext { /** * Enable logging on this context */ get enableLogging() { return this._enableLogging; } set enableLogging(value) { if (this._enableLogging === value) { return; } this._enableLogging = value; if (this._enableLogging) { this.logger = new FlowGraphLogger(); this.logger.logToConsole = true; } else { this.logger = null; } } constructor(params) { /** * A randomly generated GUID for each context. */ this.uniqueId = __runInitializers(this, _uniqueId_initializers, RandomGUID()); /** * An optional user-facing name for the context. * Defaults to an empty string; the editor may assign a label like "Context 0". */ this.name = (__runInitializers(this, _uniqueId_extraInitializers), __runInitializers(this, _name_initializers, "")); /** * These are the variables defined by a user. */ this._userVariables = (__runInitializers(this, _name_extraInitializers), {}); /** * Optional type annotations for user variables. * Keys are variable names; values are type name strings (e.g. "number", "Vector3", "Mesh"). * This map is maintained by the editor and persisted through serialization so * that a variable's declared type survives even when its value is undefined. */ this._variableTypes = {}; /** * These are the variables set by the blocks. */ this._executionVariables = {}; /** * A context-specific global variables, available to all blocks in the context. */ this._globalContextVariables = {}; /** * These are the values for the data connection points */ this._connectionValues = {}; /** * These are blocks that have currently pending tasks/listeners that need to be cleaned up. */ this._pendingBlocks = []; /** * A monotonically increasing ID for each execution. * Incremented for every block executed. */ this._executionId = 0; /** * Observable that is triggered when a node is executed. */ this.onNodeExecutedObservable = new Observable(); /** * Observable triggered when a breakpoint is hit. * Observers receive the pending activation (block, context, signal) that was paused. */ this.onBreakpointHitObservable = new Observable(); /** * A predicate called before each execution block runs. * If it returns true, execution is paused before the block and a * pending activation is stored, which can be resumed via * {@link continueExecution} or {@link stepExecution}. * * Set to `null` to disable breakpoint checking. */ this.breakpointPredicate = null; /** * The activation that is currently paused due to a breakpoint hit. * `null` when execution is not paused on a breakpoint. */ this._pendingActivation = null; /** * When true, the next activation will pause regardless of the breakpoint predicate. * Set by {@link stepExecution}. */ this._stepMode = false; /** * When set, the breakpoint check is skipped for this specific block uniqueId * on the very next call to {@link _shouldBreak}. Used by continue/step to avoid * immediately re-hitting the breakpoint on the block being resumed. */ this._skipBreakpointForBlockId = null; /** * Whether to treat data as right-handed. * This is used when serializing data from a right-handed system, while running the context in a left-handed system, for example in glTF parsing. * Default is false. */ this.treatDataAsRightHanded = false; this._enableLogging = false; this._configuration = params; this.assetsContext = params.assetsContext ?? params.scene; } /** * Check if a user-defined variable is defined. * @param name the name of the variable * @returns true if the variable is defined */ hasVariable(name) { return name in this._userVariables; } /** * Set a user-defined variable. * @param name the name of the variable * @param value the value of the variable */ setVariable(name, value) { this._userVariables[name] = value; this.logger?.addLogItem({ time: Date.now(), className: this.getClassName(), uniqueId: this.uniqueId, action: "ContextVariableSet" /* FlowGraphAction.ContextVariableSet */, payload: { name, value, }, }); } /** * Get an assets from the assets context based on its type and index in the array * @param type The type of the asset * @param index The index of the asset * @returns The asset or null if not found */ getAsset(type, index) { return GetFlowGraphAssetWithType(this.assetsContext, type, index); } /** * Get a user-defined variable. * @param name the name of the variable * @returns the value of the variable */ getVariable(name) { this.logger?.addLogItem({ time: Date.now(), className: this.getClassName(), uniqueId: this.uniqueId, action: "ContextVariableGet" /* FlowGraphAction.ContextVariableGet */, payload: { name, value: this._userVariables[name], }, }); return this._userVariables[name]; } /** * Gets all user variables map */ get userVariables() { return this._userVariables; } /** * Set the declared type annotation for a user variable. * @param name - the variable name * @param typeName - the type name string (e.g. "number", "Vector3", "Mesh") */ setVariableType(name, typeName) { this._variableTypes[name] = typeName; } /** * Get the declared type annotation for a user variable. * @param name - the variable name * @returns the type name string, or undefined if no type was declared */ getVariableType(name) { return this._variableTypes[name]; } /** * Gets all variable type annotations. */ get variableTypes() { return this._variableTypes; } /** * Get the scene that the context belongs to. * @returns the scene */ getScene() { return this._configuration.scene; } _getUniqueIdPrefixedName(obj, name) { return `${obj.uniqueId}_${name}`; } /** * @internal * @param name name of the variable * @param defaultValue default value to return if the variable is not defined * @returns the variable value or the default value if the variable is not defined */ _getGlobalContextVariable(name, defaultValue) { this.logger?.addLogItem({ time: Date.now(), className: this.getClassName(), uniqueId: this.uniqueId, action: "GlobalVariableGet" /* FlowGraphAction.GlobalVariableGet */, payload: { name, defaultValue, possibleValue: this._globalContextVariables[name], }, }); if (this._hasGlobalContextVariable(name)) { return this._globalContextVariables[name]; } else { return defaultValue; } } /** * Set a global context variable * @internal * @param name the name of the variable * @param value the value of the variable */ _setGlobalContextVariable(name, value) { this.logger?.addLogItem({ time: Date.now(), className: this.getClassName(), uniqueId: this.uniqueId, action: "GlobalVariableSet" /* FlowGraphAction.GlobalVariableSet */, payload: { name, value }, }); this._globalContextVariables[name] = value; } /** * Delete a global context variable * @internal * @param name the name of the variable */ _deleteGlobalContextVariable(name) { this.logger?.addLogItem({ time: Date.now(), className: this.getClassName(), uniqueId: this.uniqueId, action: "GlobalVariableDelete" /* FlowGraphAction.GlobalVariableDelete */, payload: { name }, }); delete this._globalContextVariables[name]; } /** * Check if a global context variable is defined * @internal * @param name the name of the variable * @returns true if the variable is defined */ _hasGlobalContextVariable(name) { return name in this._globalContextVariables; } /** * Set an internal execution variable * @internal * @param name * @param value */ _setExecutionVariable(block, name, value) { this._executionVariables[this._getUniqueIdPrefixedName(block, name)] = value; } /** * Get an internal execution variable * @internal * @param name * @returns */ _getExecutionVariable(block, name, defaultValue) { if (this._hasExecutionVariable(block, name)) { return this._executionVariables[this._getUniqueIdPrefixedName(block, name)]; } else { return defaultValue; } } /** * Delete an internal execution variable * @internal * @param block * @param name */ _deleteExecutionVariable(block, name) { delete this._executionVariables[this._getUniqueIdPrefixedName(block, name)]; } /** * Check if an internal execution variable is defined * @internal * @param block * @param name * @returns */ _hasExecutionVariable(block, name) { return this._getUniqueIdPrefixedName(block, name) in this._executionVariables; } /** * Check if a connection value is defined * @internal * @param connectionPoint * @returns */ _hasConnectionValue(connectionPoint) { return connectionPoint.uniqueId in this._connectionValues; } /** * Set a connection value * @internal * @param connectionPoint * @param value */ _setConnectionValue(connectionPoint, value) { this._connectionValues[connectionPoint.uniqueId] = value; this.logger?.addLogItem({ time: Date.now(), className: this.getClassName(), uniqueId: this.uniqueId, action: "SetConnectionValue" /* FlowGraphAction.SetConnectionValue */, payload: { connectionPointId: connectionPoint.uniqueId, value, }, }); } /** * Set a connection value by key * @internal * @param key the key of the connection value * @param value the value of the connection */ _setConnectionValueByKey(key, value) { this._connectionValues[key] = value; } /** * Get a connection value * @internal * @param connectionPoint * @returns */ _getConnectionValue(connectionPoint) { this.logger?.addLogItem({ time: Date.now(), className: this.getClassName(), uniqueId: this.uniqueId, action: "GetConnectionValue" /* FlowGraphAction.GetConnectionValue */, payload: { connectionPointId: connectionPoint.uniqueId, value: this._connectionValues[connectionPoint.uniqueId], }, }); return this._connectionValues[connectionPoint.uniqueId]; } /** * Get the configuration * @internal * @param name * @param value */ get configuration() { return this._configuration; } /** * Check if there are any pending blocks in this context * @returns true if there are pending blocks */ get hasPendingBlocks() { return this._pendingBlocks.length > 0; } /** * Add a block to the list of blocks that have pending tasks. * @internal * @param block */ _addPendingBlock(block) { // check if block is already in the array if (this._pendingBlocks.includes(block)) { return; } this._pendingBlocks.push(block); // sort pending blocks by priority this._pendingBlocks.sort((a, b) => a.priority - b.priority); } /** * Remove a block from the list of blocks that have pending tasks. * @internal * @param block */ _removePendingBlock(block) { const index = this._pendingBlocks.indexOf(block); if (index !== -1) { this._pendingBlocks.splice(index, 1); } } /** * Clear all pending blocks. * @internal */ _clearPendingBlocks() { for (const block of this._pendingBlocks) { block._cancelPendingTasks(this); } this._pendingBlocks.length = 0; } /** * @internal * Function that notifies the node executed observable * @param node */ _notifyExecuteNode(node) { this.onNodeExecutedObservable.notifyObservers(node); this.logger?.addLogItem({ time: Date.now(), className: node.getClassName(), uniqueId: node.uniqueId, action: "ExecuteBlock" /* FlowGraphAction.ExecuteBlock */, }); } _notifyOnTick(framePayload) { // set the values as global variables this._setGlobalContextVariable("timeSinceStart", framePayload.timeSinceStart); this._setGlobalContextVariable("deltaTime", framePayload.deltaTime); // iterate the pending blocks and run each one's onFrame function for (const block of this._pendingBlocks) { block._executeOnTick?.(this); } } /** * @internal */ _increaseExecutionId() { this._executionId++; } /** * A monotonically increasing ID for each execution. * Incremented for every block executed. */ get executionId() { return this._executionId; } // ── Breakpoint API ───────────────────────────────────────────────── /** * Check whether the given block should break before executing. * Called by the signal connection infrastructure. * @internal * @param block the block about to execute * @param signal the signal that is triggering the execution * @returns true if execution should be paused (breakpoint hit) */ _shouldBreak(block, signal) { // If continue/step just resumed this specific block, let it through if (this._skipBreakpointForBlockId === block.uniqueId) { this._skipBreakpointForBlockId = null; return false; } // If already paused on a breakpoint, silently block further execution // without overwriting the pending activation or re-notifying observers. if (this._pendingActivation) { return true; } if (this._stepMode) { this._stepMode = false; this._pendingActivation = { block, context: this, signal }; this.onBreakpointHitObservable.notifyObservers(this._pendingActivation); return true; } if (this.breakpointPredicate && this.breakpointPredicate(block)) { this._pendingActivation = { block, context: this, signal }; this.onBreakpointHitObservable.notifyObservers(this._pendingActivation); return true; } return false; } /** * Returns the currently paused activation, or null if not paused. */ get pendingActivation() { return this._pendingActivation; } /** * Resume execution from a breakpoint hit. * The paused block and all downstream blocks will execute normally until * the next breakpoint (if any) is hit. */ continueExecution() { const pending = this._pendingActivation; if (!pending) { return; } this._pendingActivation = null; // Tell _shouldBreak to skip the breakpoint for this block on re-entry this._skipBreakpointForBlockId = pending.block.uniqueId; pending.signal._activateSignal(this); // Clear in case no re-entry happened (shouldn't linger) this._skipBreakpointForBlockId = null; } /** * Execute exactly the paused block and then pause again before the next * execution block fires. If no activation is pending, this is a no-op. */ stepExecution() { const pending = this._pendingActivation; if (!pending) { return; } this._pendingActivation = null; // Enable step mode so the very next input-signal activation will pause this._stepMode = true; // Tell _shouldBreak to skip the breakpoint for this block on re-entry this._skipBreakpointForBlockId = pending.block.uniqueId; pending.signal._activateSignal(this); // If nothing further executed (end of chain), clear step mode this._stepMode = false; this._skipBreakpointForBlockId = null; } /** * Discard any pending breakpoint activation without resuming. * Used when stopping or resetting the graph. * @internal */ _clearPendingActivation() { this._pendingActivation = null; this._stepMode = false; this._skipBreakpointForBlockId = null; } /** * Serializes a context * @param serializationObject the object to write the values in * @param valueSerializationFunction a function to serialize complex values */ serialize(serializationObject = {}, valueSerializationFunction = defaultValueSerializationFunction) { serializationObject.uniqueId = this.uniqueId; serializationObject.name = this.name; serializationObject._userVariables = {}; for (const key in this._userVariables) { valueSerializationFunction(key, this._userVariables[key], serializationObject._userVariables); } // Persist variable type annotations (editor metadata) if (Object.keys(this._variableTypes).length > 0) { serializationObject._variableTypes = { ...this._variableTypes }; } serializationObject._connectionValues = {}; for (const key in this._connectionValues) { valueSerializationFunction(key, this._connectionValues[key], serializationObject._connectionValues); } // serialize assets context, if not scene if (this.assetsContext !== this.getScene()) { serializationObject._assetsContext = { meshes: this.assetsContext.meshes.map((m) => m.id), materials: this.assetsContext.materials.map((m) => m.id), textures: this.assetsContext.textures.map((m) => m.name), animations: this.assetsContext.animations.map((m) => m.name), lights: this.assetsContext.lights.map((m) => m.id), cameras: this.assetsContext.cameras.map((m) => m.id), sounds: this.assetsContext.sounds?.map((m) => m.name), skeletons: this.assetsContext.skeletons.map((m) => m.id), particleSystems: this.assetsContext.particleSystems.map((m) => m.name), geometries: this.assetsContext.geometries.map((m) => m.id), multiMaterials: this.assetsContext.multiMaterials.map((m) => m.id), transformNodes: this.assetsContext.transformNodes.map((m) => m.id), }; } } /** * @returns the class name of the object. */ getClassName() { return "FlowGraphContext"; } }, (() => { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; _uniqueId_decorators = [serialize()]; _name_decorators = [serialize()]; __esDecorate(null, null, _uniqueId_decorators, { kind: "field", name: "uniqueId", static: false, private: false, access: { has: obj => "uniqueId" in obj, get: obj => obj.uniqueId, set: (obj, value) => { obj.uniqueId = value; } }, metadata: _metadata }, _uniqueId_initializers, _uniqueId_extraInitializers); __esDecorate(null, null, _name_decorators, { kind: "field", name: "name", static: false, private: false, access: { has: obj => "name" in obj, get: obj => obj.name, set: (obj, value) => { obj.name = value; } }, metadata: _metadata }, _name_initializers, _name_extraInitializers); if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); })(), _a; })(); /** * The type of a connection point - input or output. */ var FlowGraphConnectionType; (function (FlowGraphConnectionType) { FlowGraphConnectionType[FlowGraphConnectionType["Input"] = 0] = "Input"; FlowGraphConnectionType[FlowGraphConnectionType["Output"] = 1] = "Output"; })(FlowGraphConnectionType || (FlowGraphConnectionType = {})); /** * The base connection class. */ class FlowGraphConnection { constructor(name, _connectionType, /* @internal */ _ownerBlock) { this._ownerBlock = _ownerBlock; /** @internal */ this._connectedPoint = []; /** * A uniquely identifying string for the connection. */ this.uniqueId = RandomGUID(); /** * Used for parsing connections. * @internal */ // disable warning as this is used for parsing // eslint-disable-next-line @typescript-eslint/no-unused-vars this.connectedPointIds = []; this.name = name; this._connectionType = _connectionType; } /** * The type of the connection */ get connectionType() { return this._connectionType; } /** * @internal * Override this to indicate if a point can connect to more than one point. */ _isSingularConnection() { return true; } /** * Returns if a point is connected to any other point. * @returns boolean indicating if the point is connected. */ isConnected() { return this._connectedPoint.length > 0; } /** * Connects two connections together. * @param point the connection to connect to. */ connectTo(point) { if (this._connectionType === point._connectionType) { throw new Error(`Cannot connect two points of type ${this.connectionType}`); } if ((this._isSingularConnection() && this._connectedPoint.length > 0) || (point._isSingularConnection() && point._connectedPoint.length > 0)) { throw new Error("Max number of connections for point reached"); } this._connectedPoint.push(point); point._connectedPoint.push(this); } /** * Disconnects two connections. * @param point the connection to disconnect from. * @param removeFromLocal if true, the connection will be removed from the local connection list. */ disconnectFrom(point, removeFromLocal = true) { const indexLocal = this._connectedPoint.indexOf(point); const indexConnected = point._connectedPoint.indexOf(this); if (indexLocal === -1 || indexConnected === -1) { return; } if (removeFromLocal) { this._connectedPoint.splice(indexLocal, 1); } point._connectedPoint.splice(indexConnected, 1); } /** * Disconnects all connected points. */ disconnectFromAll() { for (const point of this._connectedPoint) { this.disconnectFrom(point, false); } this._connectedPoint.length = 0; } dispose() { for (const point of this._connectedPoint) { this.disconnectFrom(point); } } /** * Saves the connection to a JSON object. * @param serializationObject the object to serialize to. */ serialize(serializationObject = {}) { serializationObject.uniqueId = this.uniqueId; serializationObject.name = this.name; serializationObject._connectionType = this._connectionType; serializationObject.connectedPointIds = []; serializationObject.className = this.getClassName(); for (const point of this._connectedPoint) { serializationObject.connectedPointIds.push(point.uniqueId); } } /** * @returns class name of the connection. */ getClassName() { return "FGConnection"; } /** * Deserialize from a object into this * @param serializationObject the object to deserialize from. */ deserialize(serializationObject) { this.uniqueId = serializationObject.uniqueId; this.name = serializationObject.name; this._connectionType = serializationObject._connectionType; this.connectedPointIds = serializationObject.connectedPointIds; } } /** This file must only contain pure code and pure imports */ /** * Represents a connection point for data. * An unconnected input point can have a default value. * An output point will only have a value if it is connected to an input point. Furthermore, * if the point belongs to a "function" node, the node will run its function to update the value. */ class FlowGraphDataConnection extends FlowGraphConnection { /** * Create a new data connection point. * @param name the name of the connection * @param connectionType the type of the connection * @param ownerBlock the block that owns this connection * @param richType the type of the data in this block * @param _defaultValue the default value of the connection * @param _optional if the connection is optional */ constructor(name, connectionType, ownerBlock, /** * the type of the data in this block */ richType, /** * [any] the default value of the connection */ _defaultValue = richType.defaultValue, /** * [false] if the connection is optional */ _optional = false) { super(name, connectionType, ownerBlock); this.richType = richType; this._defaultValue = _defaultValue; this._optional = _optional; this._isDisabled = false; /** * This is used for debugging purposes! It is the last value that was set to this connection with ANY context. * Do not use this value for anything else, as it might be wrong if used in a different context. */ this._lastValue = null; /** * a data transformer function, if needed. * This can be used, for example, to force seconds into milliseconds output, if it makes sense in your case. */ this.dataTransformer = null; /** * An observable that is triggered when the value of the connection changes. */ this.onValueChangedObservable = new Observable(); } /** * Whether or not the connection is optional. * Currently only used for UI control. */ get optional() { return this._optional; } /** * is this connection disabled * If the connection is disabled you will not be able to connect anything to it. */ get isDisabled() { return this._isDisabled; } set isDisabled(value) { if (this._isDisabled === value) { return; } this._isDisabled = value; if (this._isDisabled) { this.disconnectFromAll(); } } /** * An output data block can connect to multiple input data blocks, * but an input data block can only connect to one output data block. * @returns true if the connection is singular */ _isSingularConnection() { return this.connectionType === 0 /* FlowGraphConnectionType.Input */; } /** * Set the value of the connection in a specific context. * @param value the value to set * @param context the context to which the value is set */ setValue(value, context) { // check if the value is different if (context._getConnectionValue(this) === value) { return; } context._setConnectionValue(this, value); this.onValueChangedObservable.notifyObservers(value); } /** * Reset the value of the connection to the default value. * @param context the context in which the value is reset */ resetToDefaultValue(context) { context._setConnectionValue(this, this._defaultValue); } /** * Re-resolves this input's default value against a (new) scene when the value is a node reference. * This handles two cases that occur when a graph is moved to a different scene (see * {@link FlowGraph.setScene}): * - the value is still an unresolved serialized reference (`{ id, name, className, uniqueId }`) * because the node did not yet exist in the scene at parse time, or * - the value is a node that belongs to a different (e.g. disposed) scene. * Values that are not node references