Files
esengine/packages/framework/blueprint/src/runtime/ExecutionContext.ts

320 lines
10 KiB
TypeScript
Raw Normal View History

/**
* Execution Context - Runtime context for blueprint execution
* -
*/
feat(blueprint): refactor BlueprintComponent as proper ECS Component (#433) * feat(blueprint): refactor BlueprintComponent as proper ECS Component - Convert BlueprintComponent from interface to actual ECS Component class - Add ready-to-use BlueprintSystem that extends EntitySystem - Remove deprecated legacy APIs (createBlueprintSystem, etc.) - Update all blueprint documentation (Chinese & English) - Simplify user API: just add BlueprintSystem and BlueprintComponent BREAKING CHANGE: BlueprintComponent is now a class extending Component, not an interface. Use `new BlueprintComponent()` instead of `createBlueprintComponentData()`. * chore(blueprint): add changeset for ECS component refactor * fix(node-editor): fix connections not rendering when node is collapsed - getPinPosition now returns node header position when pin element is not found - Added collapsedNodesKey to force re-render connections after collapse/expand - Input pins connect to left side, output pins to right side of collapsed nodes * chore(node-editor): add changeset for collapse connection fix * feat(blueprint): add Add Component nodes for entity-component creation - Add type-specific Add_ComponentName nodes via ComponentNodeGenerator - Add generic ECS_AddComponent node for dynamic component creation - Add ExecutionContext.getComponentClass() for component lookup - Add registerComponentClass() helper for manual component registration - Each Add node supports initial property values from @BlueprintProperty * docs: update changeset with Add Component feature * feat(blueprint): improve event nodes with Self output and auto-create BeginPlay - Event Begin Play now outputs Self entity - Event Tick now outputs Self entity + Delta Seconds - Event End Play now outputs Self entity - createEmptyBlueprint() now includes Event Begin Play by default - Added menuPath to all event nodes for better organization
2026-01-04 11:50:16 +08:00
import type { Entity, IScene, Component } from '@esengine/ecs-framework';
import { BlueprintNode, BlueprintConnection } from '../types/nodes';
import { BlueprintAsset } from '../types/blueprint';
feat(blueprint): refactor BlueprintComponent as proper ECS Component (#433) * feat(blueprint): refactor BlueprintComponent as proper ECS Component - Convert BlueprintComponent from interface to actual ECS Component class - Add ready-to-use BlueprintSystem that extends EntitySystem - Remove deprecated legacy APIs (createBlueprintSystem, etc.) - Update all blueprint documentation (Chinese & English) - Simplify user API: just add BlueprintSystem and BlueprintComponent BREAKING CHANGE: BlueprintComponent is now a class extending Component, not an interface. Use `new BlueprintComponent()` instead of `createBlueprintComponentData()`. * chore(blueprint): add changeset for ECS component refactor * fix(node-editor): fix connections not rendering when node is collapsed - getPinPosition now returns node header position when pin element is not found - Added collapsedNodesKey to force re-render connections after collapse/expand - Input pins connect to left side, output pins to right side of collapsed nodes * chore(node-editor): add changeset for collapse connection fix * feat(blueprint): add Add Component nodes for entity-component creation - Add type-specific Add_ComponentName nodes via ComponentNodeGenerator - Add generic ECS_AddComponent node for dynamic component creation - Add ExecutionContext.getComponentClass() for component lookup - Add registerComponentClass() helper for manual component registration - Each Add node supports initial property values from @BlueprintProperty * docs: update changeset with Add Component feature * feat(blueprint): improve event nodes with Self output and auto-create BeginPlay - Event Begin Play now outputs Self entity - Event Tick now outputs Self entity + Delta Seconds - Event End Play now outputs Self entity - createEmptyBlueprint() now includes Event Begin Play by default - Added menuPath to all event nodes for better organization
2026-01-04 11:50:16 +08:00
import { getRegisteredBlueprintComponents } from '../registry/BlueprintDecorators';
/**
* Result of node execution
*
*/
export interface ExecutionResult {
/**
* Next exec pin to follow (null to stop, undefined to continue default)
* null undefined
*/
nextExec?: string | null;
/**
* Output values by pin name
*
*/
outputs?: Record<string, unknown>;
/**
* Whether to yield execution (for async operations)
*
*/
yield?: boolean;
/**
* Delay before continuing (in seconds)
*
*/
delay?: number;
/**
* Error message if execution failed
*
*/
error?: string;
}
/**
* Execution context provides access to runtime services
* 访
*/
export class ExecutionContext {
/** Current blueprint asset (当前蓝图资产) */
readonly blueprint: BlueprintAsset;
/** Owner entity (所有者实体) */
readonly entity: Entity;
/** Current scene (当前场景) */
readonly scene: IScene;
/** Frame delta time (帧增量时间) */
deltaTime: number = 0;
/** Total time since start (开始以来的总时间) */
time: number = 0;
/** Instance variables (实例变量) */
private _instanceVariables: Map<string, unknown> = new Map();
/** Local variables (per-execution) (局部变量,每次执行) */
private _localVariables: Map<string, unknown> = new Map();
/** Global variables (shared) (全局变量,共享) */
private static _globalVariables: Map<string, unknown> = new Map();
feat(blueprint): refactor BlueprintComponent as proper ECS Component (#433) * feat(blueprint): refactor BlueprintComponent as proper ECS Component - Convert BlueprintComponent from interface to actual ECS Component class - Add ready-to-use BlueprintSystem that extends EntitySystem - Remove deprecated legacy APIs (createBlueprintSystem, etc.) - Update all blueprint documentation (Chinese & English) - Simplify user API: just add BlueprintSystem and BlueprintComponent BREAKING CHANGE: BlueprintComponent is now a class extending Component, not an interface. Use `new BlueprintComponent()` instead of `createBlueprintComponentData()`. * chore(blueprint): add changeset for ECS component refactor * fix(node-editor): fix connections not rendering when node is collapsed - getPinPosition now returns node header position when pin element is not found - Added collapsedNodesKey to force re-render connections after collapse/expand - Input pins connect to left side, output pins to right side of collapsed nodes * chore(node-editor): add changeset for collapse connection fix * feat(blueprint): add Add Component nodes for entity-component creation - Add type-specific Add_ComponentName nodes via ComponentNodeGenerator - Add generic ECS_AddComponent node for dynamic component creation - Add ExecutionContext.getComponentClass() for component lookup - Add registerComponentClass() helper for manual component registration - Each Add node supports initial property values from @BlueprintProperty * docs: update changeset with Add Component feature * feat(blueprint): improve event nodes with Self output and auto-create BeginPlay - Event Begin Play now outputs Self entity - Event Tick now outputs Self entity + Delta Seconds - Event End Play now outputs Self entity - createEmptyBlueprint() now includes Event Begin Play by default - Added menuPath to all event nodes for better organization
2026-01-04 11:50:16 +08:00
/** Component class registry (组件类注册表) */
private static _componentRegistry: Map<string, new () => Component> = new Map();
/** Node output cache for current execution (当前执行的节点输出缓存) */
private _outputCache: Map<string, Record<string, unknown>> = new Map();
/** Connection lookup by target (按目标的连接查找) */
private _connectionsByTarget: Map<string, BlueprintConnection[]> = new Map();
/** Connection lookup by source (按源的连接查找) */
private _connectionsBySource: Map<string, BlueprintConnection[]> = new Map();
constructor(blueprint: BlueprintAsset, entity: Entity, scene: IScene) {
this.blueprint = blueprint;
this.entity = entity;
this.scene = scene;
// Initialize instance variables with defaults
// 使用默认值初始化实例变量
for (const variable of blueprint.variables) {
if (variable.scope === 'instance') {
this._instanceVariables.set(variable.name, variable.defaultValue);
}
}
// Build connection lookup maps
// 构建连接查找映射
this._buildConnectionMaps();
}
private _buildConnectionMaps(): void {
for (const conn of this.blueprint.connections) {
// By target
const targetKey = `${conn.toNodeId}.${conn.toPin}`;
if (!this._connectionsByTarget.has(targetKey)) {
this._connectionsByTarget.set(targetKey, []);
}
this._connectionsByTarget.get(targetKey)!.push(conn);
// By source
const sourceKey = `${conn.fromNodeId}.${conn.fromPin}`;
if (!this._connectionsBySource.has(sourceKey)) {
this._connectionsBySource.set(sourceKey, []);
}
this._connectionsBySource.get(sourceKey)!.push(conn);
}
}
/**
* Get a node by ID
* ID获取节点
*/
getNode(nodeId: string): BlueprintNode | undefined {
return this.blueprint.nodes.find(n => n.id === nodeId);
}
/**
* Get connections to a target pin
*
*/
getConnectionsToPin(nodeId: string, pinName: string): BlueprintConnection[] {
return this._connectionsByTarget.get(`${nodeId}.${pinName}`) ?? [];
}
/**
* Get connections from a source pin
*
*/
getConnectionsFromPin(nodeId: string, pinName: string): BlueprintConnection[] {
return this._connectionsBySource.get(`${nodeId}.${pinName}`) ?? [];
}
/**
* Evaluate an input pin value (follows connections or uses default)
* 使
*/
evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown {
const connections = this.getConnectionsToPin(nodeId, pinName);
if (connections.length === 0) {
// Use default from node data or provided default
// 使用节点数据的默认值或提供的默认值
const node = this.getNode(nodeId);
return node?.data[pinName] ?? defaultValue;
}
// Get value from connected output
// 从连接的输出获取值
const conn = connections[0];
const cachedOutputs = this._outputCache.get(conn.fromNodeId);
if (cachedOutputs && conn.fromPin in cachedOutputs) {
return cachedOutputs[conn.fromPin];
}
// Need to execute the source node first (lazy evaluation)
// 需要先执行源节点(延迟求值)
return defaultValue;
}
/**
* Set output values for a node (cached for current execution)
*
*/
setOutputs(nodeId: string, outputs: Record<string, unknown>): void {
this._outputCache.set(nodeId, outputs);
}
/**
* Get cached outputs for a node
*
*/
getOutputs(nodeId: string): Record<string, unknown> | undefined {
return this._outputCache.get(nodeId);
}
/**
* Clear output cache (call at start of new execution)
*
*/
clearOutputCache(): void {
this._outputCache.clear();
this._localVariables.clear();
}
/**
* Get a variable value
*
*/
getVariable(name: string): unknown {
// Check local first, then instance, then global
// 先检查局部,然后实例,然后全局
if (this._localVariables.has(name)) {
return this._localVariables.get(name);
}
if (this._instanceVariables.has(name)) {
return this._instanceVariables.get(name);
}
if (ExecutionContext._globalVariables.has(name)) {
return ExecutionContext._globalVariables.get(name);
}
// Return default from variable definition
// 返回变量定义的默认值
const varDef = this.blueprint.variables.find(v => v.name === name);
return varDef?.defaultValue;
}
/**
* Set a variable value
*
*/
setVariable(name: string, value: unknown): void {
const varDef = this.blueprint.variables.find(v => v.name === name);
if (!varDef) {
// Treat unknown variables as local
// 将未知变量视为局部变量
this._localVariables.set(name, value);
return;
}
switch (varDef.scope) {
case 'local':
this._localVariables.set(name, value);
break;
case 'instance':
this._instanceVariables.set(name, value);
break;
case 'global':
ExecutionContext._globalVariables.set(name, value);
break;
}
}
/**
* Get all instance variables (for serialization)
*
*/
getInstanceVariables(): Map<string, unknown> {
return new Map(this._instanceVariables);
}
/**
* Set instance variables (for deserialization)
*
*/
setInstanceVariables(variables: Map<string, unknown>): void {
this._instanceVariables = new Map(variables);
}
/**
* Clear global variables (for scene reset)
*
*/
static clearGlobalVariables(): void {
ExecutionContext._globalVariables.clear();
}
feat(blueprint): refactor BlueprintComponent as proper ECS Component (#433) * feat(blueprint): refactor BlueprintComponent as proper ECS Component - Convert BlueprintComponent from interface to actual ECS Component class - Add ready-to-use BlueprintSystem that extends EntitySystem - Remove deprecated legacy APIs (createBlueprintSystem, etc.) - Update all blueprint documentation (Chinese & English) - Simplify user API: just add BlueprintSystem and BlueprintComponent BREAKING CHANGE: BlueprintComponent is now a class extending Component, not an interface. Use `new BlueprintComponent()` instead of `createBlueprintComponentData()`. * chore(blueprint): add changeset for ECS component refactor * fix(node-editor): fix connections not rendering when node is collapsed - getPinPosition now returns node header position when pin element is not found - Added collapsedNodesKey to force re-render connections after collapse/expand - Input pins connect to left side, output pins to right side of collapsed nodes * chore(node-editor): add changeset for collapse connection fix * feat(blueprint): add Add Component nodes for entity-component creation - Add type-specific Add_ComponentName nodes via ComponentNodeGenerator - Add generic ECS_AddComponent node for dynamic component creation - Add ExecutionContext.getComponentClass() for component lookup - Add registerComponentClass() helper for manual component registration - Each Add node supports initial property values from @BlueprintProperty * docs: update changeset with Add Component feature * feat(blueprint): improve event nodes with Self output and auto-create BeginPlay - Event Begin Play now outputs Self entity - Event Tick now outputs Self entity + Delta Seconds - Event End Play now outputs Self entity - createEmptyBlueprint() now includes Event Begin Play by default - Added menuPath to all event nodes for better organization
2026-01-04 11:50:16 +08:00
/**
* Get a component class by name
*
*
* @zh @BlueprintExpose
* @en First checks @BlueprintExpose decorated components, then manually registered ones
*/
getComponentClass(typeName: string): (new () => Component) | undefined {
// First check registered blueprint components
const blueprintComponents = getRegisteredBlueprintComponents();
for (const [componentClass, metadata] of blueprintComponents) {
if (metadata.componentName === typeName ||
componentClass.name === typeName) {
return componentClass as new () => Component;
}
}
// Then check manual registry
return ExecutionContext._componentRegistry.get(typeName);
}
/**
* Register a component class for dynamic creation
*
*/
static registerComponentClass(typeName: string, componentClass: new () => Component): void {
ExecutionContext._componentRegistry.set(typeName, componentClass);
}
/**
* Unregister a component class
*
*/
static unregisterComponentClass(typeName: string): void {
ExecutionContext._componentRegistry.delete(typeName);
}
/**
* Get all registered component classes
*
*/
static getRegisteredComponentClasses(): Map<string, new () => Component> {
return new Map(ExecutionContext._componentRegistry);
}
}