refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
116
packages/framework/blueprint/src/runtime/BlueprintComponent.ts
Normal file
116
packages/framework/blueprint/src/runtime/BlueprintComponent.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Blueprint Component - Attaches a blueprint to an entity
|
||||
* 蓝图组件 - 将蓝图附加到实体
|
||||
*/
|
||||
|
||||
import type { Entity, IScene } from '@esengine/ecs-framework';
|
||||
import { BlueprintAsset } from '../types/blueprint';
|
||||
import { BlueprintVM } from './BlueprintVM';
|
||||
|
||||
/**
|
||||
* Component interface for ECS integration
|
||||
* 用于 ECS 集成的组件接口
|
||||
*/
|
||||
export interface IBlueprintComponent {
|
||||
/** Entity ID this component belongs to (此组件所属的实体ID) */
|
||||
entityId: number | null;
|
||||
|
||||
/** Blueprint asset reference (蓝图资产引用) */
|
||||
blueprintAsset: BlueprintAsset | null;
|
||||
|
||||
/** Blueprint asset path for serialization (用于序列化的蓝图资产路径) */
|
||||
blueprintPath: string;
|
||||
|
||||
/** Auto-start execution when entity is created (实体创建时自动开始执行) */
|
||||
autoStart: boolean;
|
||||
|
||||
/** Enable debug mode for VM (启用 VM 调试模式) */
|
||||
debug: boolean;
|
||||
|
||||
/** Runtime VM instance (运行时 VM 实例) */
|
||||
vm: BlueprintVM | null;
|
||||
|
||||
/** Whether the blueprint has started (蓝图是否已启动) */
|
||||
isStarted: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a blueprint component data object
|
||||
* 创建蓝图组件数据对象
|
||||
*/
|
||||
export function createBlueprintComponentData(): IBlueprintComponent {
|
||||
return {
|
||||
entityId: null,
|
||||
blueprintAsset: null,
|
||||
blueprintPath: '',
|
||||
autoStart: true,
|
||||
debug: false,
|
||||
vm: null,
|
||||
isStarted: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the VM for a blueprint component
|
||||
* 为蓝图组件初始化 VM
|
||||
*/
|
||||
export function initializeBlueprintVM(
|
||||
component: IBlueprintComponent,
|
||||
entity: Entity,
|
||||
scene: IScene
|
||||
): void {
|
||||
if (!component.blueprintAsset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create VM instance
|
||||
// 创建 VM 实例
|
||||
component.vm = new BlueprintVM(component.blueprintAsset, entity, scene);
|
||||
component.vm.debug = component.debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start blueprint execution
|
||||
* 开始蓝图执行
|
||||
*/
|
||||
export function startBlueprint(component: IBlueprintComponent): void {
|
||||
if (component.vm && !component.isStarted) {
|
||||
component.vm.start();
|
||||
component.isStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop blueprint execution
|
||||
* 停止蓝图执行
|
||||
*/
|
||||
export function stopBlueprint(component: IBlueprintComponent): void {
|
||||
if (component.vm && component.isStarted) {
|
||||
component.vm.stop();
|
||||
component.isStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update blueprint execution
|
||||
* 更新蓝图执行
|
||||
*/
|
||||
export function tickBlueprint(component: IBlueprintComponent, deltaTime: number): void {
|
||||
if (component.vm && component.isStarted) {
|
||||
component.vm.tick(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up blueprint resources
|
||||
* 清理蓝图资源
|
||||
*/
|
||||
export function cleanupBlueprint(component: IBlueprintComponent): void {
|
||||
if (component.vm) {
|
||||
if (component.isStarted) {
|
||||
component.vm.stop();
|
||||
}
|
||||
component.vm = null;
|
||||
component.isStarted = false;
|
||||
}
|
||||
}
|
||||
121
packages/framework/blueprint/src/runtime/BlueprintSystem.ts
Normal file
121
packages/framework/blueprint/src/runtime/BlueprintSystem.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Blueprint Execution System - Manages blueprint lifecycle and execution
|
||||
* 蓝图执行系统 - 管理蓝图生命周期和执行
|
||||
*/
|
||||
|
||||
import type { Entity, IScene } from '@esengine/ecs-framework';
|
||||
import {
|
||||
IBlueprintComponent,
|
||||
initializeBlueprintVM,
|
||||
startBlueprint,
|
||||
tickBlueprint,
|
||||
cleanupBlueprint
|
||||
} from './BlueprintComponent';
|
||||
|
||||
/**
|
||||
* Blueprint system interface for engine integration
|
||||
* 用于引擎集成的蓝图系统接口
|
||||
*/
|
||||
export interface IBlueprintSystem {
|
||||
/** Process entities with blueprint components (处理带有蓝图组件的实体) */
|
||||
process(entities: IBlueprintEntity[], deltaTime: number): void;
|
||||
|
||||
/** Called when entity is added to system (实体添加到系统时调用) */
|
||||
onEntityAdded(entity: IBlueprintEntity): void;
|
||||
|
||||
/** Called when entity is removed from system (实体从系统移除时调用) */
|
||||
onEntityRemoved(entity: IBlueprintEntity): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity with blueprint component
|
||||
* 带有蓝图组件的实体
|
||||
*/
|
||||
export interface IBlueprintEntity extends Entity {
|
||||
/** Blueprint component data (蓝图组件数据) */
|
||||
blueprintComponent: IBlueprintComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a blueprint execution system
|
||||
* 创建蓝图执行系统
|
||||
*/
|
||||
export function createBlueprintSystem(scene: IScene): IBlueprintSystem {
|
||||
return {
|
||||
process(entities: IBlueprintEntity[], deltaTime: number): void {
|
||||
for (const entity of entities) {
|
||||
const component = entity.blueprintComponent;
|
||||
|
||||
// Skip if no blueprint asset loaded
|
||||
// 如果没有加载蓝图资产则跳过
|
||||
if (!component.blueprintAsset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Initialize VM if needed
|
||||
// 如果需要则初始化 VM
|
||||
if (!component.vm) {
|
||||
initializeBlueprintVM(component, entity, scene);
|
||||
}
|
||||
|
||||
// Auto-start if enabled
|
||||
// 如果启用则自动启动
|
||||
if (component.autoStart && !component.isStarted) {
|
||||
startBlueprint(component);
|
||||
}
|
||||
|
||||
// Tick the blueprint
|
||||
// 更新蓝图
|
||||
tickBlueprint(component, deltaTime);
|
||||
}
|
||||
},
|
||||
|
||||
onEntityAdded(entity: IBlueprintEntity): void {
|
||||
const component = entity.blueprintComponent;
|
||||
|
||||
if (component.blueprintAsset) {
|
||||
initializeBlueprintVM(component, entity, scene);
|
||||
|
||||
if (component.autoStart) {
|
||||
startBlueprint(component);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onEntityRemoved(entity: IBlueprintEntity): void {
|
||||
cleanupBlueprint(entity.blueprintComponent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to manually trigger blueprint events
|
||||
* 手动触发蓝图事件的工具
|
||||
*/
|
||||
export function triggerBlueprintEvent(
|
||||
entity: IBlueprintEntity,
|
||||
eventType: string,
|
||||
data?: Record<string, unknown>
|
||||
): void {
|
||||
const vm = entity.blueprintComponent.vm;
|
||||
|
||||
if (vm && entity.blueprintComponent.isStarted) {
|
||||
vm.triggerEvent(eventType, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to trigger custom events by name
|
||||
* 按名称触发自定义事件的工具
|
||||
*/
|
||||
export function triggerCustomBlueprintEvent(
|
||||
entity: IBlueprintEntity,
|
||||
eventName: string,
|
||||
data?: Record<string, unknown>
|
||||
): void {
|
||||
const vm = entity.blueprintComponent.vm;
|
||||
|
||||
if (vm && entity.blueprintComponent.isStarted) {
|
||||
vm.triggerCustomEvent(eventName, data);
|
||||
}
|
||||
}
|
||||
336
packages/framework/blueprint/src/runtime/BlueprintVM.ts
Normal file
336
packages/framework/blueprint/src/runtime/BlueprintVM.ts
Normal file
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* Blueprint Virtual Machine - Executes blueprint graphs
|
||||
* 蓝图虚拟机 - 执行蓝图图
|
||||
*/
|
||||
|
||||
import type { Entity, IScene } from '@esengine/ecs-framework';
|
||||
import { BlueprintNode } from '../types/nodes';
|
||||
import { BlueprintAsset } from '../types/blueprint';
|
||||
import { ExecutionContext, ExecutionResult } from './ExecutionContext';
|
||||
import { NodeRegistry } from './NodeRegistry';
|
||||
|
||||
/**
|
||||
* Pending execution frame (for delayed/async execution)
|
||||
* 待处理的执行帧(用于延迟/异步执行)
|
||||
*/
|
||||
interface PendingExecution {
|
||||
nodeId: string;
|
||||
execPin: string;
|
||||
resumeTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event trigger types
|
||||
* 事件触发类型
|
||||
*/
|
||||
export type EventType =
|
||||
| 'BeginPlay'
|
||||
| 'Tick'
|
||||
| 'EndPlay'
|
||||
| 'Collision'
|
||||
| 'TriggerEnter'
|
||||
| 'TriggerExit'
|
||||
| 'Custom';
|
||||
|
||||
/**
|
||||
* Blueprint Virtual Machine
|
||||
* 蓝图虚拟机
|
||||
*/
|
||||
export class BlueprintVM {
|
||||
/** Execution context (执行上下文) */
|
||||
private _context: ExecutionContext;
|
||||
|
||||
/** Pending executions (delayed nodes) (待处理的执行) */
|
||||
private _pendingExecutions: PendingExecution[] = [];
|
||||
|
||||
/** Event node cache by type (按类型缓存的事件节点) */
|
||||
private _eventNodes: Map<string, BlueprintNode[]> = new Map();
|
||||
|
||||
/** Whether the VM is running (VM 是否运行中) */
|
||||
private _isRunning: boolean = false;
|
||||
|
||||
/** Current execution time (当前执行时间) */
|
||||
private _currentTime: number = 0;
|
||||
|
||||
/** Maximum execution steps per frame (每帧最大执行步骤) */
|
||||
private _maxStepsPerFrame: number = 1000;
|
||||
|
||||
/** Debug mode (调试模式) */
|
||||
debug: boolean = false;
|
||||
|
||||
constructor(blueprint: BlueprintAsset, entity: Entity, scene: IScene) {
|
||||
this._context = new ExecutionContext(blueprint, entity, scene);
|
||||
this._cacheEventNodes();
|
||||
}
|
||||
|
||||
get context(): ExecutionContext {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
get isRunning(): boolean {
|
||||
return this._isRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache event nodes by type for quick lookup
|
||||
* 按类型缓存事件节点以便快速查找
|
||||
*/
|
||||
private _cacheEventNodes(): void {
|
||||
for (const node of this._context.blueprint.nodes) {
|
||||
// Event nodes start with "Event"
|
||||
// 事件节点以 "Event" 开头
|
||||
if (node.type.startsWith('Event')) {
|
||||
const eventType = node.type;
|
||||
if (!this._eventNodes.has(eventType)) {
|
||||
this._eventNodes.set(eventType, []);
|
||||
}
|
||||
this._eventNodes.get(eventType)!.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the VM
|
||||
* 启动 VM
|
||||
*/
|
||||
start(): void {
|
||||
this._isRunning = true;
|
||||
this._currentTime = 0;
|
||||
|
||||
// Trigger BeginPlay event
|
||||
// 触发 BeginPlay 事件
|
||||
this.triggerEvent('EventBeginPlay');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the VM
|
||||
* 停止 VM
|
||||
*/
|
||||
stop(): void {
|
||||
// Trigger EndPlay event
|
||||
// 触发 EndPlay 事件
|
||||
this.triggerEvent('EventEndPlay');
|
||||
|
||||
this._isRunning = false;
|
||||
this._pendingExecutions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the VM
|
||||
* 暂停 VM
|
||||
*/
|
||||
pause(): void {
|
||||
this._isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the VM
|
||||
* 恢复 VM
|
||||
*/
|
||||
resume(): void {
|
||||
this._isRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the VM (called every frame)
|
||||
* 更新 VM(每帧调用)
|
||||
*/
|
||||
tick(deltaTime: number): void {
|
||||
if (!this._isRunning) return;
|
||||
|
||||
this._currentTime += deltaTime;
|
||||
this._context.deltaTime = deltaTime;
|
||||
this._context.time = this._currentTime;
|
||||
|
||||
// Process pending delayed executions
|
||||
// 处理待处理的延迟执行
|
||||
this._processPendingExecutions();
|
||||
|
||||
// Trigger Tick event
|
||||
// 触发 Tick 事件
|
||||
this.triggerEvent('EventTick');
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event by type
|
||||
* 按类型触发事件
|
||||
*/
|
||||
triggerEvent(eventType: string, data?: Record<string, unknown>): void {
|
||||
const eventNodes = this._eventNodes.get(eventType);
|
||||
if (!eventNodes) return;
|
||||
|
||||
for (const node of eventNodes) {
|
||||
this._executeFromNode(node, 'exec', data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a custom event by name
|
||||
* 按名称触发自定义事件
|
||||
*/
|
||||
triggerCustomEvent(eventName: string, data?: Record<string, unknown>): void {
|
||||
const eventNodes = this._eventNodes.get('EventCustom');
|
||||
if (!eventNodes) return;
|
||||
|
||||
for (const node of eventNodes) {
|
||||
if (node.data.eventName === eventName) {
|
||||
this._executeFromNode(node, 'exec', data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute from a starting node
|
||||
* 从起始节点执行
|
||||
*/
|
||||
private _executeFromNode(
|
||||
startNode: BlueprintNode,
|
||||
startPin: string,
|
||||
eventData?: Record<string, unknown>
|
||||
): void {
|
||||
// Clear output cache for new execution
|
||||
// 为新执行清除输出缓存
|
||||
this._context.clearOutputCache();
|
||||
|
||||
// Set event data as node outputs
|
||||
// 设置事件数据为节点输出
|
||||
if (eventData) {
|
||||
this._context.setOutputs(startNode.id, eventData);
|
||||
}
|
||||
|
||||
// Follow execution chain
|
||||
// 跟随执行链
|
||||
let currentNodeId: string | null = startNode.id;
|
||||
let currentPin: string = startPin;
|
||||
let steps = 0;
|
||||
|
||||
while (currentNodeId && steps < this._maxStepsPerFrame) {
|
||||
steps++;
|
||||
|
||||
// Get connected nodes from current exec pin
|
||||
// 从当前执行引脚获取连接的节点
|
||||
const connections = this._context.getConnectionsFromPin(currentNodeId, currentPin);
|
||||
|
||||
if (connections.length === 0) {
|
||||
// No more connections, end execution
|
||||
// 没有更多连接,结束执行
|
||||
break;
|
||||
}
|
||||
|
||||
// Execute connected node
|
||||
// 执行连接的节点
|
||||
const nextConn = connections[0];
|
||||
const result = this._executeNode(nextConn.toNodeId);
|
||||
|
||||
if (result.error) {
|
||||
console.error(`Blueprint error in node ${nextConn.toNodeId}: ${result.error}`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.delay && result.delay > 0) {
|
||||
// Schedule delayed execution
|
||||
// 安排延迟执行
|
||||
this._pendingExecutions.push({
|
||||
nodeId: nextConn.toNodeId,
|
||||
execPin: result.nextExec ?? 'exec',
|
||||
resumeTime: this._currentTime + result.delay
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.yield) {
|
||||
// Yield execution until next frame
|
||||
// 暂停执行直到下一帧
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.nextExec === null) {
|
||||
// Explicitly stop execution
|
||||
// 显式停止执行
|
||||
break;
|
||||
}
|
||||
|
||||
// Continue to next node
|
||||
// 继续到下一个节点
|
||||
currentNodeId = nextConn.toNodeId;
|
||||
currentPin = result.nextExec ?? 'exec';
|
||||
}
|
||||
|
||||
if (steps >= this._maxStepsPerFrame) {
|
||||
console.warn('Blueprint execution exceeded maximum steps, possible infinite loop');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single node
|
||||
* 执行单个节点
|
||||
*/
|
||||
private _executeNode(nodeId: string): ExecutionResult {
|
||||
const node = this._context.getNode(nodeId);
|
||||
if (!node) {
|
||||
return { error: `Node not found: ${nodeId}` };
|
||||
}
|
||||
|
||||
const executor = NodeRegistry.instance.getExecutor(node.type);
|
||||
if (!executor) {
|
||||
return { error: `No executor for node type: ${node.type}` };
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.debug) {
|
||||
console.log(`[Blueprint] Executing: ${node.type} (${nodeId})`);
|
||||
}
|
||||
|
||||
const result = executor.execute(node, this._context);
|
||||
|
||||
// Cache outputs
|
||||
// 缓存输出
|
||||
if (result.outputs) {
|
||||
this._context.setOutputs(nodeId, result.outputs);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { error: `Execution error: ${error}` };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process pending delayed executions
|
||||
* 处理待处理的延迟执行
|
||||
*/
|
||||
private _processPendingExecutions(): void {
|
||||
const stillPending: PendingExecution[] = [];
|
||||
|
||||
for (const pending of this._pendingExecutions) {
|
||||
if (this._currentTime >= pending.resumeTime) {
|
||||
// Resume execution
|
||||
// 恢复执行
|
||||
const node = this._context.getNode(pending.nodeId);
|
||||
if (node) {
|
||||
this._executeFromNode(node, pending.execPin);
|
||||
}
|
||||
} else {
|
||||
stillPending.push(pending);
|
||||
}
|
||||
}
|
||||
|
||||
this._pendingExecutions = stillPending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance variables for serialization
|
||||
* 获取实例变量用于序列化
|
||||
*/
|
||||
getInstanceVariables(): Map<string, unknown> {
|
||||
return this._context.getInstanceVariables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set instance variables from serialization
|
||||
* 从序列化设置实例变量
|
||||
*/
|
||||
setInstanceVariables(variables: Map<string, unknown>): void {
|
||||
this._context.setInstanceVariables(variables);
|
||||
}
|
||||
}
|
||||
270
packages/framework/blueprint/src/runtime/ExecutionContext.ts
Normal file
270
packages/framework/blueprint/src/runtime/ExecutionContext.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Execution Context - Runtime context for blueprint execution
|
||||
* 执行上下文 - 蓝图执行的运行时上下文
|
||||
*/
|
||||
|
||||
import type { Entity, IScene } from '@esengine/ecs-framework';
|
||||
import { BlueprintNode, BlueprintConnection } from '../types/nodes';
|
||||
import { BlueprintAsset } from '../types/blueprint';
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/** 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();
|
||||
}
|
||||
}
|
||||
151
packages/framework/blueprint/src/runtime/NodeRegistry.ts
Normal file
151
packages/framework/blueprint/src/runtime/NodeRegistry.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Node Registry - Manages node templates and executors
|
||||
* 节点注册表 - 管理节点模板和执行器
|
||||
*/
|
||||
|
||||
import { BlueprintNodeTemplate, BlueprintNode } from '../types/nodes';
|
||||
import { ExecutionContext, ExecutionResult } from './ExecutionContext';
|
||||
|
||||
/**
|
||||
* Node executor interface - implements the logic for a node type
|
||||
* 节点执行器接口 - 实现节点类型的逻辑
|
||||
*/
|
||||
export interface INodeExecutor {
|
||||
/**
|
||||
* Execute the node
|
||||
* 执行节点
|
||||
*
|
||||
* @param node - Node instance (节点实例)
|
||||
* @param context - Execution context (执行上下文)
|
||||
* @returns Execution result (执行结果)
|
||||
*/
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node definition combines template with executor
|
||||
* 节点定义组合模板和执行器
|
||||
*/
|
||||
export interface NodeDefinition {
|
||||
template: BlueprintNodeTemplate;
|
||||
executor: INodeExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node Registry - singleton that holds all registered node types
|
||||
* 节点注册表 - 持有所有注册节点类型的单例
|
||||
*/
|
||||
export class NodeRegistry {
|
||||
private static _instance: NodeRegistry;
|
||||
private _nodes: Map<string, NodeDefinition> = new Map();
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static get instance(): NodeRegistry {
|
||||
if (!NodeRegistry._instance) {
|
||||
NodeRegistry._instance = new NodeRegistry();
|
||||
}
|
||||
return NodeRegistry._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a node type
|
||||
* 注册节点类型
|
||||
*/
|
||||
register(template: BlueprintNodeTemplate, executor: INodeExecutor): void {
|
||||
if (this._nodes.has(template.type)) {
|
||||
console.warn(`Node type "${template.type}" is already registered, overwriting`);
|
||||
}
|
||||
this._nodes.set(template.type, { template, executor });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a node definition by type
|
||||
* 通过类型获取节点定义
|
||||
*/
|
||||
get(type: string): NodeDefinition | undefined {
|
||||
return this._nodes.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get node template by type
|
||||
* 通过类型获取节点模板
|
||||
*/
|
||||
getTemplate(type: string): BlueprintNodeTemplate | undefined {
|
||||
return this._nodes.get(type)?.template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get node executor by type
|
||||
* 通过类型获取节点执行器
|
||||
*/
|
||||
getExecutor(type: string): INodeExecutor | undefined {
|
||||
return this._nodes.get(type)?.executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node type is registered
|
||||
* 检查节点类型是否已注册
|
||||
*/
|
||||
has(type: string): boolean {
|
||||
return this._nodes.has(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered templates
|
||||
* 获取所有注册的模板
|
||||
*/
|
||||
getAllTemplates(): BlueprintNodeTemplate[] {
|
||||
return Array.from(this._nodes.values()).map(d => d.template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get templates by category
|
||||
* 按类别获取模板
|
||||
*/
|
||||
getTemplatesByCategory(category: string): BlueprintNodeTemplate[] {
|
||||
return this.getAllTemplates().filter(t => t.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search templates by keyword
|
||||
* 按关键词搜索模板
|
||||
*/
|
||||
searchTemplates(keyword: string): BlueprintNodeTemplate[] {
|
||||
const lower = keyword.toLowerCase();
|
||||
return this.getAllTemplates().filter(t =>
|
||||
t.title.toLowerCase().includes(lower) ||
|
||||
t.type.toLowerCase().includes(lower) ||
|
||||
t.keywords?.some(k => k.toLowerCase().includes(lower)) ||
|
||||
t.description?.toLowerCase().includes(lower)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all registrations (for testing)
|
||||
* 清除所有注册(用于测试)
|
||||
*/
|
||||
clear(): void {
|
||||
this._nodes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator for registering node executors
|
||||
* 用于注册节点执行器的装饰器
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @RegisterNode(EventTickTemplate)
|
||||
* class EventTickExecutor implements INodeExecutor {
|
||||
* execute(node, context) { ... }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function RegisterNode(template: BlueprintNodeTemplate) {
|
||||
return function<T extends new () => INodeExecutor>(constructor: T) {
|
||||
const executor = new constructor();
|
||||
NodeRegistry.instance.register(template, executor);
|
||||
return constructor;
|
||||
};
|
||||
}
|
||||
10
packages/framework/blueprint/src/runtime/index.ts
Normal file
10
packages/framework/blueprint/src/runtime/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Blueprint Runtime - Execution engine for blueprints
|
||||
* 蓝图运行时 - 蓝图执行引擎
|
||||
*/
|
||||
|
||||
export * from './ExecutionContext';
|
||||
export * from './NodeRegistry';
|
||||
export * from './BlueprintVM';
|
||||
export * from './BlueprintComponent';
|
||||
export * from './BlueprintSystem';
|
||||
Reference in New Issue
Block a user