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:
318
packages/engine/script-runtime/src/vm/CPULimiter.ts
Normal file
318
packages/engine/script-runtime/src/vm/CPULimiter.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* @zh CPU 限制器
|
||||
* @en CPU Limiter
|
||||
*
|
||||
* @zh 限制蓝图执行的 CPU 时间和执行步数
|
||||
* @en Limits CPU time and execution steps for blueprint execution
|
||||
*/
|
||||
|
||||
/**
|
||||
* @zh CPU 限制器配置
|
||||
* @en CPU limiter configuration
|
||||
*/
|
||||
export interface CPULimiterConfig {
|
||||
/**
|
||||
* @zh 每 tick 的 CPU 时间限制(毫秒)
|
||||
* @en CPU time limit per tick (milliseconds)
|
||||
*/
|
||||
cpuLimitMs: number;
|
||||
|
||||
/**
|
||||
* @zh 每 tick 的最大执行步数
|
||||
* @en Maximum execution steps per tick
|
||||
*/
|
||||
maxSteps: number;
|
||||
|
||||
/**
|
||||
* @zh CPU 桶最大值
|
||||
* @en CPU bucket maximum
|
||||
*/
|
||||
bucketMax: number;
|
||||
|
||||
/**
|
||||
* @zh 每 tick 恢复的 CPU 量
|
||||
* @en CPU amount recovered per tick
|
||||
*/
|
||||
bucketRecovery: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 默认 CPU 限制配置
|
||||
* @en Default CPU limit configuration
|
||||
*/
|
||||
export const DEFAULT_CPU_CONFIG: CPULimiterConfig = {
|
||||
cpuLimitMs: 100, // 100ms per tick
|
||||
maxSteps: 10000, // 10000 steps per tick
|
||||
bucketMax: 10000, // 10000ms max bucket
|
||||
bucketRecovery: 100 // 100ms per tick recovery
|
||||
};
|
||||
|
||||
/**
|
||||
* @zh CPU 使用统计
|
||||
* @en CPU usage statistics
|
||||
*/
|
||||
export interface CPUStats {
|
||||
/**
|
||||
* @zh 已使用的 CPU 时间(毫秒)
|
||||
* @en Used CPU time (milliseconds)
|
||||
*/
|
||||
used: number;
|
||||
|
||||
/**
|
||||
* @zh CPU 限制(毫秒)
|
||||
* @en CPU limit (milliseconds)
|
||||
*/
|
||||
limit: number;
|
||||
|
||||
/**
|
||||
* @zh 已执行的步数
|
||||
* @en Executed steps
|
||||
*/
|
||||
steps: number;
|
||||
|
||||
/**
|
||||
* @zh 最大步数
|
||||
* @en Maximum steps
|
||||
*/
|
||||
maxSteps: number;
|
||||
|
||||
/**
|
||||
* @zh 当前桶值
|
||||
* @en Current bucket value
|
||||
*/
|
||||
bucket: number;
|
||||
|
||||
/**
|
||||
* @zh 是否超出限制
|
||||
* @en Whether exceeded limit
|
||||
*/
|
||||
exceeded: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh CPU 限制器
|
||||
* @en CPU Limiter
|
||||
*
|
||||
* @zh 用于限制玩家蓝图的执行资源,类似 Screeps 的 CPU 系统
|
||||
* @en Used to limit player blueprint execution resources, similar to Screeps CPU system
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const limiter = new CPULimiter('player1', config);
|
||||
*
|
||||
* // 开始执行 | Start execution
|
||||
* limiter.start();
|
||||
*
|
||||
* // 在执行节点时检查 | Check during node execution
|
||||
* if (limiter.checkStep()) {
|
||||
* // 继续执行 | Continue execution
|
||||
* } else {
|
||||
* // 超出限制,停止执行 | Exceeded limit, stop execution
|
||||
* }
|
||||
*
|
||||
* // 结束执行 | End execution
|
||||
* limiter.end();
|
||||
* console.log(`Used: ${limiter.getUsed()}ms`);
|
||||
* ```
|
||||
*/
|
||||
export class CPULimiter {
|
||||
/**
|
||||
* @zh 玩家 ID
|
||||
* @en Player ID
|
||||
*/
|
||||
private readonly _playerId: string;
|
||||
|
||||
/**
|
||||
* @zh 配置
|
||||
* @en Configuration
|
||||
*/
|
||||
private readonly _config: CPULimiterConfig;
|
||||
|
||||
/**
|
||||
* @zh 开始时间
|
||||
* @en Start time
|
||||
*/
|
||||
private _startTime: number = 0;
|
||||
|
||||
/**
|
||||
* @zh 已使用的 CPU 时间
|
||||
* @en Used CPU time
|
||||
*/
|
||||
private _usedCpu: number = 0;
|
||||
|
||||
/**
|
||||
* @zh 已执行的步数
|
||||
* @en Executed steps
|
||||
*/
|
||||
private _steps: number = 0;
|
||||
|
||||
/**
|
||||
* @zh CPU 桶(累积的 CPU 配额)
|
||||
* @en CPU bucket (accumulated CPU quota)
|
||||
*/
|
||||
private _bucket: number;
|
||||
|
||||
/**
|
||||
* @zh 是否正在执行
|
||||
* @en Whether currently executing
|
||||
*/
|
||||
private _isRunning: boolean = false;
|
||||
|
||||
/**
|
||||
* @zh 是否超出限制
|
||||
* @en Whether exceeded limit
|
||||
*/
|
||||
private _exceeded: boolean = false;
|
||||
|
||||
constructor(playerId: string, config: Partial<CPULimiterConfig> = {}) {
|
||||
this._playerId = playerId;
|
||||
this._config = { ...DEFAULT_CPU_CONFIG, ...config };
|
||||
this._bucket = this._config.bucketMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取玩家 ID
|
||||
* @en Get player ID
|
||||
*/
|
||||
get playerId(): string {
|
||||
return this._playerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取当前 CPU 桶值
|
||||
* @en Get current CPU bucket value
|
||||
*/
|
||||
get bucket(): number {
|
||||
return this._bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取 CPU 限制
|
||||
* @en Get CPU limit
|
||||
*/
|
||||
get limit(): number {
|
||||
return this._config.cpuLimitMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 是否超出限制
|
||||
* @en Whether exceeded limit
|
||||
*/
|
||||
get exceeded(): boolean {
|
||||
return this._exceeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 开始计时
|
||||
* @en Start timing
|
||||
*/
|
||||
start(): void {
|
||||
this._startTime = performance.now();
|
||||
this._usedCpu = 0;
|
||||
this._steps = 0;
|
||||
this._exceeded = false;
|
||||
this._isRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 结束计时并更新桶
|
||||
* @en End timing and update bucket
|
||||
*/
|
||||
end(): void {
|
||||
if (!this._isRunning) return;
|
||||
|
||||
this._usedCpu = performance.now() - this._startTime;
|
||||
this._isRunning = false;
|
||||
|
||||
// 从桶中扣除使用的 CPU | Deduct used CPU from bucket
|
||||
this._bucket -= this._usedCpu;
|
||||
if (this._bucket < 0) {
|
||||
this._bucket = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取已使用的 CPU 时间
|
||||
* @en Get used CPU time
|
||||
*/
|
||||
getUsed(): number {
|
||||
if (this._isRunning) {
|
||||
return performance.now() - this._startTime;
|
||||
}
|
||||
return this._usedCpu;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取已执行的步数
|
||||
* @en Get executed steps
|
||||
*/
|
||||
getSteps(): number {
|
||||
return this._steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查是否可以继续执行一步
|
||||
* @en Check if can continue executing one step
|
||||
*
|
||||
* @returns @zh true 如果可以继续,false 如果超出限制 @en true if can continue, false if exceeded
|
||||
*/
|
||||
checkStep(): boolean {
|
||||
this._steps++;
|
||||
|
||||
// 检查步数限制 | Check step limit
|
||||
if (this._steps > this._config.maxSteps) {
|
||||
this._exceeded = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查 CPU 时间限制 | Check CPU time limit
|
||||
const currentTime = performance.now() - this._startTime;
|
||||
const effectiveLimit = Math.min(this._config.cpuLimitMs, this._bucket);
|
||||
|
||||
if (currentTime > effectiveLimit) {
|
||||
this._exceeded = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 每 tick 恢复桶
|
||||
* @en Recover bucket per tick
|
||||
*/
|
||||
recoverBucket(): void {
|
||||
this._bucket += this._config.bucketRecovery;
|
||||
if (this._bucket > this._config.bucketMax) {
|
||||
this._bucket = this._config.bucketMax;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取 CPU 统计信息
|
||||
* @en Get CPU statistics
|
||||
*/
|
||||
getStats(): CPUStats {
|
||||
return {
|
||||
used: this.getUsed(),
|
||||
limit: this._config.cpuLimitMs,
|
||||
steps: this._steps,
|
||||
maxSteps: this._config.maxSteps,
|
||||
bucket: this._bucket,
|
||||
exceeded: this._exceeded
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 重置限制器(用于测试)
|
||||
* @en Reset limiter (for testing)
|
||||
*/
|
||||
reset(): void {
|
||||
this._startTime = 0;
|
||||
this._usedCpu = 0;
|
||||
this._steps = 0;
|
||||
this._exceeded = false;
|
||||
this._isRunning = false;
|
||||
this._bucket = this._config.bucketMax;
|
||||
}
|
||||
}
|
||||
618
packages/engine/script-runtime/src/vm/ServerBlueprintVM.ts
Normal file
618
packages/engine/script-runtime/src/vm/ServerBlueprintVM.ts
Normal file
@@ -0,0 +1,618 @@
|
||||
/**
|
||||
* @zh 服务器端蓝图虚拟机
|
||||
* @en Server-side Blueprint Virtual Machine
|
||||
*
|
||||
* @zh 在服务器端执行玩家蓝图,支持 CPU 限制和意图收集
|
||||
* @en Executes player blueprints on server with CPU limiting and intent collection
|
||||
*/
|
||||
|
||||
import type {
|
||||
BlueprintAsset,
|
||||
BlueprintNode,
|
||||
BlueprintConnection
|
||||
} from '@esengine/blueprint';
|
||||
import { NodeRegistry } from '@esengine/blueprint';
|
||||
|
||||
import { ServerExecutionContext, type IGameState, type LogEntry } from './ServerExecutionContext';
|
||||
import { CPULimiter, type CPULimiterConfig, type CPUStats } from './CPULimiter';
|
||||
import { IntentCollector } from '../intent/IntentCollector';
|
||||
import type { IIntent, IntentKeyExtractor } from '../intent/IntentTypes';
|
||||
|
||||
/**
|
||||
* @zh 服务器 VM 配置
|
||||
* @en Server VM configuration
|
||||
*
|
||||
* @typeParam TIntent - @zh 意图类型 @en Intent type
|
||||
*/
|
||||
export interface ServerVMConfig<TIntent extends IIntent = IIntent> {
|
||||
/**
|
||||
* @zh CPU 限制配置
|
||||
* @en CPU limit configuration
|
||||
*/
|
||||
cpuConfig?: Partial<CPULimiterConfig>;
|
||||
|
||||
/**
|
||||
* @zh 调试模式
|
||||
* @en Debug mode
|
||||
*/
|
||||
debug?: boolean;
|
||||
|
||||
/**
|
||||
* @zh 意图键提取器
|
||||
* @en Intent key extractor
|
||||
*/
|
||||
intentKeyExtractor?: IntentKeyExtractor<TIntent>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh Tick 执行结果
|
||||
* @en Tick execution result
|
||||
*
|
||||
* @typeParam TIntent - @zh 意图类型 @en Intent type
|
||||
*/
|
||||
export interface TickResult<TIntent extends IIntent = IIntent> {
|
||||
/**
|
||||
* @zh 是否执行成功
|
||||
* @en Whether execution succeeded
|
||||
*/
|
||||
success: boolean;
|
||||
|
||||
/**
|
||||
* @zh CPU 使用统计
|
||||
* @en CPU usage statistics
|
||||
*/
|
||||
cpu: CPUStats;
|
||||
|
||||
/**
|
||||
* @zh 收集的意图列表
|
||||
* @en Collected intents list
|
||||
*/
|
||||
intents: TIntent[];
|
||||
|
||||
/**
|
||||
* @zh 日志列表
|
||||
* @en Log list
|
||||
*/
|
||||
logs: LogEntry[];
|
||||
|
||||
/**
|
||||
* @zh 错误列表
|
||||
* @en Error list
|
||||
*/
|
||||
errors: string[];
|
||||
|
||||
/**
|
||||
* @zh 更新后的 Memory
|
||||
* @en Updated Memory
|
||||
*/
|
||||
memory: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 待处理的延迟执行
|
||||
* @en Pending delayed execution
|
||||
*/
|
||||
interface PendingExecution {
|
||||
nodeId: string;
|
||||
execPin: string;
|
||||
resumeTick: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 节点执行结果
|
||||
* @en Node execution result
|
||||
*/
|
||||
interface ExecutionResult {
|
||||
nextExec?: string | null;
|
||||
outputs?: Record<string, unknown>;
|
||||
yield?: boolean;
|
||||
delay?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 服务器端蓝图虚拟机
|
||||
* @en Server-side Blueprint Virtual Machine
|
||||
*
|
||||
* @zh 专为服务器端设计的蓝图执行引擎,支持:
|
||||
* @en Blueprint execution engine designed for server-side, supporting:
|
||||
*
|
||||
* @zh - CPU 时间和步数限制
|
||||
* @en - CPU time and step limiting
|
||||
*
|
||||
* @zh - 意图收集(不直接执行操作)
|
||||
* @en - Intent collection (no direct execution)
|
||||
*
|
||||
* @zh - Memory 持久化
|
||||
* @en - Memory persistence
|
||||
*
|
||||
* @typeParam TGameState - @zh 游戏状态类型 @en Game state type
|
||||
* @typeParam TIntent - @zh 意图类型 @en Intent type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 游戏项目中定义类型 | Define types in game project
|
||||
* interface MyGameState extends IGameState {
|
||||
* units: Map<string, IUnit>;
|
||||
* }
|
||||
*
|
||||
* interface MyIntent extends IIntent {
|
||||
* readonly type: 'unit.move' | 'unit.attack';
|
||||
* unitId: string;
|
||||
* }
|
||||
*
|
||||
* // 创建 VM | Create VM
|
||||
* const vm = new ServerBlueprintVM<MyGameState, MyIntent>('player1', blueprint, {
|
||||
* intentKeyExtractor: (intent) => `${intent.type}:${intent.unitId}`
|
||||
* });
|
||||
*
|
||||
* // 每个 tick 执行 | Execute each tick
|
||||
* const result = vm.executeTick(gameState, playerMemory);
|
||||
* ```
|
||||
*/
|
||||
export class ServerBlueprintVM<
|
||||
TGameState extends IGameState = IGameState,
|
||||
TIntent extends IIntent = IIntent
|
||||
> {
|
||||
/**
|
||||
* @zh 玩家 ID
|
||||
* @en Player ID
|
||||
*/
|
||||
private readonly _playerId: string;
|
||||
|
||||
/**
|
||||
* @zh 蓝图资产
|
||||
* @en Blueprint asset
|
||||
*/
|
||||
private readonly _blueprint: BlueprintAsset;
|
||||
|
||||
/**
|
||||
* @zh 执行上下文
|
||||
* @en Execution context
|
||||
*/
|
||||
private readonly _context: ServerExecutionContext<TGameState, TIntent>;
|
||||
|
||||
/**
|
||||
* @zh CPU 限制器
|
||||
* @en CPU limiter
|
||||
*/
|
||||
private readonly _cpuLimiter: CPULimiter;
|
||||
|
||||
/**
|
||||
* @zh 意图收集器
|
||||
* @en Intent collector
|
||||
*/
|
||||
private readonly _intentCollector: IntentCollector<TIntent>;
|
||||
|
||||
/**
|
||||
* @zh 事件节点缓存
|
||||
* @en Event nodes cache
|
||||
*/
|
||||
private readonly _eventNodes: Map<string, BlueprintNode[]> = new Map();
|
||||
|
||||
/**
|
||||
* @zh 连接查找表(按源)
|
||||
* @en Connection lookup (by source)
|
||||
*/
|
||||
private readonly _connectionsBySource: Map<string, BlueprintConnection[]> = new Map();
|
||||
|
||||
/**
|
||||
* @zh 连接查找表(按目标)
|
||||
* @en Connection lookup (by target)
|
||||
*/
|
||||
private readonly _connectionsByTarget: Map<string, BlueprintConnection[]> = new Map();
|
||||
|
||||
/**
|
||||
* @zh 待处理的延迟执行
|
||||
* @en Pending delayed executions
|
||||
*/
|
||||
private _pendingExecutions: PendingExecution[] = [];
|
||||
|
||||
/**
|
||||
* @zh 当前 tick
|
||||
* @en Current tick
|
||||
*/
|
||||
private _currentTick: number = 0;
|
||||
|
||||
/**
|
||||
* @zh 调试模式
|
||||
* @en Debug mode
|
||||
*/
|
||||
private readonly _debug: boolean;
|
||||
|
||||
/**
|
||||
* @zh 错误列表
|
||||
* @en Error list
|
||||
*/
|
||||
private _errors: string[] = [];
|
||||
|
||||
constructor(
|
||||
playerId: string,
|
||||
blueprint: BlueprintAsset,
|
||||
config: ServerVMConfig<TIntent> = {}
|
||||
) {
|
||||
this._playerId = playerId;
|
||||
this._blueprint = blueprint;
|
||||
this._debug = config.debug ?? false;
|
||||
|
||||
this._context = new ServerExecutionContext<TGameState, TIntent>(blueprint, playerId);
|
||||
|
||||
this._cpuLimiter = new CPULimiter(playerId, config.cpuConfig);
|
||||
|
||||
this._intentCollector = new IntentCollector<TIntent>(playerId, {
|
||||
keyExtractor: config.intentKeyExtractor
|
||||
});
|
||||
|
||||
this._context.setCPULimiter(this._cpuLimiter);
|
||||
this._context.setIntentCollector(this._intentCollector);
|
||||
|
||||
this._buildLookupTables();
|
||||
this._cacheEventNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取玩家 ID
|
||||
* @en Get player ID
|
||||
*/
|
||||
get playerId(): string {
|
||||
return this._playerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取执行上下文
|
||||
* @en Get execution context
|
||||
*/
|
||||
get context(): ServerExecutionContext<TGameState, TIntent> {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取 CPU 限制器
|
||||
* @en Get CPU limiter
|
||||
*/
|
||||
get cpuLimiter(): CPULimiter {
|
||||
return this._cpuLimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取意图收集器
|
||||
* @en Get intent collector
|
||||
*/
|
||||
get intentCollector(): IntentCollector<TIntent> {
|
||||
return this._intentCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 构建连接查找表
|
||||
* @en Build connection lookup tables
|
||||
*/
|
||||
private _buildLookupTables(): void {
|
||||
for (const conn of this._blueprint.connections) {
|
||||
const sourceKey = `${conn.fromNodeId}.${conn.fromPin}`;
|
||||
if (!this._connectionsBySource.has(sourceKey)) {
|
||||
this._connectionsBySource.set(sourceKey, []);
|
||||
}
|
||||
this._connectionsBySource.get(sourceKey)!.push(conn);
|
||||
|
||||
const targetKey = `${conn.toNodeId}.${conn.toPin}`;
|
||||
if (!this._connectionsByTarget.has(targetKey)) {
|
||||
this._connectionsByTarget.set(targetKey, []);
|
||||
}
|
||||
this._connectionsByTarget.get(targetKey)!.push(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 缓存事件节点
|
||||
* @en Cache event nodes
|
||||
*/
|
||||
private _cacheEventNodes(): void {
|
||||
for (const node of this._blueprint.nodes) {
|
||||
if (node.type.startsWith('Event')) {
|
||||
const eventType = node.type;
|
||||
if (!this._eventNodes.has(eventType)) {
|
||||
this._eventNodes.set(eventType, []);
|
||||
}
|
||||
this._eventNodes.get(eventType)!.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 执行一个游戏 tick
|
||||
* @en Execute one game tick
|
||||
*
|
||||
* @param gameState - @zh 当前游戏状态 @en Current game state
|
||||
* @param memory - @zh 玩家 Memory @en Player Memory
|
||||
* @returns @zh Tick 执行结果 @en Tick execution result
|
||||
*/
|
||||
executeTick(gameState: TGameState, memory: Record<string, unknown> = {}): TickResult<TIntent> {
|
||||
this._currentTick = gameState.tick;
|
||||
this._errors = [];
|
||||
|
||||
this._intentCollector.clear();
|
||||
this._intentCollector.setTick(this._currentTick);
|
||||
this._context.clearLogs();
|
||||
this._context.clearOutputCache();
|
||||
|
||||
this._context.setGameState(gameState);
|
||||
this._context.setMemory(memory);
|
||||
|
||||
this._cpuLimiter.start();
|
||||
|
||||
try {
|
||||
this._processPendingExecutions();
|
||||
this._triggerEvent('EventTick');
|
||||
} catch (error) {
|
||||
this._errors.push(`Execution error: ${error}`);
|
||||
}
|
||||
|
||||
this._cpuLimiter.end();
|
||||
this._cpuLimiter.recoverBucket();
|
||||
|
||||
return {
|
||||
success: this._errors.length === 0,
|
||||
cpu: this._cpuLimiter.getStats(),
|
||||
intents: this._intentCollector.getIntents(),
|
||||
logs: this._context.getLogs(),
|
||||
errors: this._errors,
|
||||
memory: this._context.getMemory()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 触发事件
|
||||
* @en Trigger event
|
||||
*/
|
||||
private _triggerEvent(eventType: string, data?: Record<string, unknown>): void {
|
||||
const eventNodes = this._eventNodes.get(eventType);
|
||||
if (!eventNodes) return;
|
||||
|
||||
for (const node of eventNodes) {
|
||||
if (!this._context.checkCPU()) {
|
||||
this._errors.push('CPU limit exceeded');
|
||||
return;
|
||||
}
|
||||
this._executeFromNode(node, 'exec', data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 从节点开始执行
|
||||
* @en Execute from node
|
||||
*/
|
||||
private _executeFromNode(
|
||||
startNode: BlueprintNode,
|
||||
startPin: string,
|
||||
eventData?: Record<string, unknown>
|
||||
): void {
|
||||
if (eventData) {
|
||||
this._context.setOutputs(startNode.id, eventData);
|
||||
}
|
||||
|
||||
let currentNodeId: string | null = startNode.id;
|
||||
let currentPin: string = startPin;
|
||||
|
||||
while (currentNodeId) {
|
||||
if (!this._context.checkCPU()) {
|
||||
this._errors.push('CPU limit exceeded during execution');
|
||||
return;
|
||||
}
|
||||
|
||||
const connections = this._getConnectionsFromPin(currentNodeId, currentPin);
|
||||
|
||||
if (connections.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const nextConn = connections[0];
|
||||
const result = this._executeNode(nextConn.toNodeId);
|
||||
|
||||
if (result.error) {
|
||||
this._errors.push(`Node ${nextConn.toNodeId}: ${result.error}`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.delay && result.delay > 0) {
|
||||
this._pendingExecutions.push({
|
||||
nodeId: nextConn.toNodeId,
|
||||
execPin: result.nextExec ?? 'exec',
|
||||
resumeTick: this._currentTick + Math.ceil(result.delay)
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.yield) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.nextExec === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentNodeId = nextConn.toNodeId;
|
||||
currentPin = result.nextExec ?? 'exec';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 执行单个节点
|
||||
* @en Execute single node
|
||||
*/
|
||||
private _executeNode(nodeId: string): ExecutionResult {
|
||||
const node = this._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(`[ServerVM] Executing: ${node.type} (${nodeId})`);
|
||||
}
|
||||
|
||||
const compatContext = this._createCompatibleContext() as unknown as Parameters<typeof executor.execute>[1];
|
||||
|
||||
const result = executor.execute(node, compatContext);
|
||||
|
||||
if (result.outputs) {
|
||||
this._context.setOutputs(nodeId, result.outputs);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { error: `Execution error: ${error}` };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 创建与 Blueprint 兼容的执行上下文
|
||||
* @en Create Blueprint-compatible execution context
|
||||
*/
|
||||
private _createCompatibleContext(): {
|
||||
blueprint: BlueprintAsset;
|
||||
deltaTime: number;
|
||||
time: number;
|
||||
getNode: (id: string) => BlueprintNode | undefined;
|
||||
getConnectionsToPin: (nodeId: string, pinName: string) => BlueprintConnection[];
|
||||
getConnectionsFromPin: (nodeId: string, pinName: string) => BlueprintConnection[];
|
||||
evaluateInput: (nodeId: string, pinName: string, defaultValue?: unknown) => unknown;
|
||||
setOutputs: (nodeId: string, outputs: Record<string, unknown>) => void;
|
||||
getOutputs: (nodeId: string) => Record<string, unknown> | undefined;
|
||||
getVariable: (name: string) => unknown;
|
||||
setVariable: (name: string, value: unknown) => void;
|
||||
intentCollector: IntentCollector<TIntent>;
|
||||
gameState: TGameState | null;
|
||||
playerId: string;
|
||||
memory: Record<string, unknown>;
|
||||
log: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
} {
|
||||
return {
|
||||
blueprint: this._blueprint,
|
||||
deltaTime: this._context.deltaTime,
|
||||
time: this._context.time,
|
||||
getNode: (id: string) => this._getNode(id),
|
||||
getConnectionsToPin: (nodeId: string, pinName: string) =>
|
||||
this._getConnectionsToPin(nodeId, pinName),
|
||||
getConnectionsFromPin: (nodeId: string, pinName: string) =>
|
||||
this._getConnectionsFromPin(nodeId, pinName),
|
||||
evaluateInput: (nodeId: string, pinName: string, defaultValue?: unknown) =>
|
||||
this._evaluateInput(nodeId, pinName, defaultValue),
|
||||
setOutputs: (nodeId: string, outputs: Record<string, unknown>) =>
|
||||
this._context.setOutputs(nodeId, outputs),
|
||||
getOutputs: (nodeId: string) => this._context.getOutputs(nodeId),
|
||||
getVariable: (name: string) => this._context.getVariable(name),
|
||||
setVariable: (name: string, value: unknown) => this._context.setVariable(name, value),
|
||||
intentCollector: this._intentCollector,
|
||||
gameState: this._context.getGameState(),
|
||||
playerId: this._playerId,
|
||||
memory: this._context.getMemory(),
|
||||
log: (message: string) => this._context.log(message),
|
||||
warn: (message: string) => this._context.warn(message),
|
||||
error: (message: string) => this._context.error(message)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 处理待处理的延迟执行
|
||||
* @en Process pending delayed executions
|
||||
*/
|
||||
private _processPendingExecutions(): void {
|
||||
const stillPending: PendingExecution[] = [];
|
||||
|
||||
for (const pending of this._pendingExecutions) {
|
||||
if (this._currentTick >= pending.resumeTick) {
|
||||
const node = this._getNode(pending.nodeId);
|
||||
if (node) {
|
||||
this._executeFromNode(node, pending.execPin);
|
||||
}
|
||||
} else {
|
||||
stillPending.push(pending);
|
||||
}
|
||||
}
|
||||
|
||||
this._pendingExecutions = stillPending;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取节点
|
||||
* @en Get node
|
||||
*/
|
||||
private _getNode(nodeId: string): BlueprintNode | undefined {
|
||||
return this._blueprint.nodes.find(n => n.id === nodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取从源引脚的连接
|
||||
* @en Get connections from source pin
|
||||
*/
|
||||
private _getConnectionsFromPin(nodeId: string, pinName: string): BlueprintConnection[] {
|
||||
return this._connectionsBySource.get(`${nodeId}.${pinName}`) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取到目标引脚的连接
|
||||
* @en Get connections to target pin
|
||||
*/
|
||||
private _getConnectionsToPin(nodeId: string, pinName: string): BlueprintConnection[] {
|
||||
return this._connectionsByTarget.get(`${nodeId}.${pinName}`) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 计算输入引脚值
|
||||
* @en Evaluate input pin value
|
||||
*/
|
||||
private _evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown {
|
||||
const connections = this._getConnectionsToPin(nodeId, pinName);
|
||||
|
||||
if (connections.length === 0) {
|
||||
const node = this._getNode(nodeId);
|
||||
return node?.data[pinName] ?? defaultValue;
|
||||
}
|
||||
|
||||
const conn = connections[0];
|
||||
const cachedOutputs = this._context.getOutputs(conn.fromNodeId);
|
||||
|
||||
if (cachedOutputs && conn.fromPin in cachedOutputs) {
|
||||
return cachedOutputs[conn.fromPin];
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取实例变量
|
||||
* @en Get instance variables
|
||||
*/
|
||||
getInstanceVariables(): Map<string, unknown> {
|
||||
return this._context.getInstanceVariables();
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 设置实例变量
|
||||
* @en Set instance variables
|
||||
*/
|
||||
setInstanceVariables(variables: Map<string, unknown>): void {
|
||||
this._context.setInstanceVariables(variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 重置 VM
|
||||
* @en Reset VM
|
||||
*/
|
||||
reset(): void {
|
||||
this._pendingExecutions = [];
|
||||
this._currentTick = 0;
|
||||
this._errors = [];
|
||||
this._cpuLimiter.reset();
|
||||
this._intentCollector.clear();
|
||||
this._context.clearOutputCache();
|
||||
this._context.clearLogs();
|
||||
}
|
||||
}
|
||||
459
packages/engine/script-runtime/src/vm/ServerExecutionContext.ts
Normal file
459
packages/engine/script-runtime/src/vm/ServerExecutionContext.ts
Normal file
@@ -0,0 +1,459 @@
|
||||
/**
|
||||
* @zh 服务器端执行上下文
|
||||
* @en Server-side Execution Context
|
||||
*
|
||||
* @zh 扩展 Blueprint 的 ExecutionContext,添加服务器端特性
|
||||
* @en Extends Blueprint's ExecutionContext with server-side features
|
||||
*/
|
||||
|
||||
import type { BlueprintAsset } from '@esengine/blueprint';
|
||||
import type { IIntent } from '../intent/IntentTypes';
|
||||
import type { IIntentCollector } from '../intent/IntentCollector';
|
||||
import type { CPULimiter } from './CPULimiter';
|
||||
|
||||
// =============================================================================
|
||||
// 基础游戏状态接口 | Base Game State Interface
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 基础游戏状态接口(引擎级别)
|
||||
* @en Base game state interface (engine level)
|
||||
*
|
||||
* @zh 引擎只定义最基础的时间信息,具体的游戏状态由游戏项目扩展
|
||||
* @en Engine only defines basic timing info, specific game state is extended by game projects
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 游戏项目中扩展游戏状态 | Extend game state in game project
|
||||
* interface MyGameState extends IGameState {
|
||||
* units: Map<string, IUnitState>;
|
||||
* buildings: Map<string, IBuildingState>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface IGameState {
|
||||
/**
|
||||
* @zh 当前 tick
|
||||
* @en Current tick
|
||||
*/
|
||||
tick: number;
|
||||
|
||||
/**
|
||||
* @zh 自上次 tick 的时间间隔
|
||||
* @en Time interval since last tick
|
||||
*/
|
||||
deltaTime: number;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 日志接口 | Log Interface
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 日志条目
|
||||
* @en Log entry
|
||||
*/
|
||||
export interface LogEntry {
|
||||
level: 'log' | 'warn' | 'error';
|
||||
message: string;
|
||||
timestamp: number;
|
||||
tick: number;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 服务器执行上下文 | Server Execution Context
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @zh 服务器端执行上下文
|
||||
* @en Server-side Execution Context
|
||||
*
|
||||
* @zh 提供蓝图执行时访问游戏状态和收集意图的能力
|
||||
* @en Provides ability to access game state and collect intents during blueprint execution
|
||||
*
|
||||
* @typeParam TGameState - @zh 游戏状态类型,由游戏项目定义 @en Game state type, defined by game project
|
||||
* @typeParam TIntent - @zh 意图类型,由游戏项目定义 @en Intent type, defined by game project
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 游戏项目中定义具体类型 | Define specific types in game project
|
||||
* interface MyGameState extends IGameState {
|
||||
* units: Map<string, IUnit>;
|
||||
* resources: Map<string, IResource>;
|
||||
* }
|
||||
*
|
||||
* interface MyIntent extends IIntent {
|
||||
* readonly type: 'unit.move' | 'unit.attack';
|
||||
* unitId: string;
|
||||
* }
|
||||
*
|
||||
* // 创建上下文 | Create context
|
||||
* const context = new ServerExecutionContext<MyGameState, MyIntent>(blueprint, 'player1');
|
||||
* ```
|
||||
*/
|
||||
export class ServerExecutionContext<
|
||||
TGameState extends IGameState = IGameState,
|
||||
TIntent extends IIntent = IIntent
|
||||
> {
|
||||
/**
|
||||
* @zh 蓝图资产
|
||||
* @en Blueprint asset
|
||||
*/
|
||||
readonly blueprint: BlueprintAsset;
|
||||
|
||||
/**
|
||||
* @zh 玩家 ID
|
||||
* @en Player ID
|
||||
*/
|
||||
readonly playerId: string;
|
||||
|
||||
/**
|
||||
* @zh 当前游戏状态
|
||||
* @en Current game state
|
||||
*/
|
||||
private _gameState: TGameState | null = null;
|
||||
|
||||
/**
|
||||
* @zh 意图收集器
|
||||
* @en Intent collector
|
||||
*/
|
||||
private _intentCollector: IIntentCollector<TIntent> | null = null;
|
||||
|
||||
/**
|
||||
* @zh CPU 限制器
|
||||
* @en CPU limiter
|
||||
*/
|
||||
private _cpuLimiter: CPULimiter | null = null;
|
||||
|
||||
/**
|
||||
* @zh 玩家持久化数据(Memory)
|
||||
* @en Player persistent data (Memory)
|
||||
*/
|
||||
private _memory: Record<string, unknown> = {};
|
||||
|
||||
/**
|
||||
* @zh 日志列表
|
||||
* @en Log list
|
||||
*/
|
||||
private _logs: LogEntry[] = [];
|
||||
|
||||
/**
|
||||
* @zh 帧增量时间
|
||||
* @en Frame delta time
|
||||
*/
|
||||
deltaTime: number = 0;
|
||||
|
||||
/**
|
||||
* @zh 自开始以来的总时间
|
||||
* @en Total time since start
|
||||
*/
|
||||
time: number = 0;
|
||||
|
||||
/**
|
||||
* @zh 节点输出缓存
|
||||
* @en Node output cache
|
||||
*/
|
||||
private _outputCache: Map<string, Record<string, unknown>> = new Map();
|
||||
|
||||
/**
|
||||
* @zh 实例变量
|
||||
* @en Instance variables
|
||||
*/
|
||||
private _instanceVariables: Map<string, unknown> = new Map();
|
||||
|
||||
/**
|
||||
* @zh 局部变量
|
||||
* @en Local variables
|
||||
*/
|
||||
private _localVariables: Map<string, unknown> = new Map();
|
||||
|
||||
/**
|
||||
* @zh 全局变量(所有玩家共享)
|
||||
* @en Global variables (shared by all players)
|
||||
*/
|
||||
private static _globalVariables: Map<string, unknown> = new Map();
|
||||
|
||||
constructor(blueprint: BlueprintAsset, playerId: string) {
|
||||
this.blueprint = blueprint;
|
||||
this.playerId = playerId;
|
||||
|
||||
for (const variable of blueprint.variables) {
|
||||
if (variable.scope === 'instance') {
|
||||
this._instanceVariables.set(variable.name, variable.defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 游戏状态访问 | Game State Access
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @zh 设置当前游戏状态
|
||||
* @en Set current game state
|
||||
*/
|
||||
setGameState(state: TGameState): void {
|
||||
this._gameState = state;
|
||||
this.deltaTime = state.deltaTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取当前游戏状态
|
||||
* @en Get current game state
|
||||
*/
|
||||
getGameState(): TGameState | null {
|
||||
return this._gameState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取当前 tick
|
||||
* @en Get current tick
|
||||
*/
|
||||
getTick(): number {
|
||||
return this._gameState?.tick ?? 0;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 意图收集 | Intent Collection
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @zh 设置意图收集器
|
||||
* @en Set intent collector
|
||||
*/
|
||||
setIntentCollector(collector: IIntentCollector<TIntent>): void {
|
||||
this._intentCollector = collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取意图收集器
|
||||
* @en Get intent collector
|
||||
*/
|
||||
get intentCollector(): IIntentCollector<TIntent> | null {
|
||||
return this._intentCollector;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CPU 限制 | CPU Limiting
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @zh 设置 CPU 限制器
|
||||
* @en Set CPU limiter
|
||||
*/
|
||||
setCPULimiter(limiter: CPULimiter): void {
|
||||
this._cpuLimiter = limiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取 CPU 限制器
|
||||
* @en Get CPU limiter
|
||||
*/
|
||||
get cpuLimiter(): CPULimiter | null {
|
||||
return this._cpuLimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查是否可以继续执行
|
||||
* @en Check if can continue execution
|
||||
*/
|
||||
checkCPU(): boolean {
|
||||
return this._cpuLimiter?.checkStep() ?? true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Memory 访问 | Memory Access
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @zh 设置玩家 Memory
|
||||
* @en Set player Memory
|
||||
*/
|
||||
setMemory(memory: Record<string, unknown>): void {
|
||||
this._memory = memory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取玩家 Memory
|
||||
* @en Get player Memory
|
||||
*/
|
||||
getMemory(): Record<string, unknown> {
|
||||
return this._memory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取 Memory 中的值
|
||||
* @en Get value from Memory
|
||||
*/
|
||||
getMemoryValue<T>(key: string): T | undefined {
|
||||
return this._memory[key] as T | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 设置 Memory 中的值
|
||||
* @en Set value in Memory
|
||||
*/
|
||||
setMemoryValue(key: string, value: unknown): void {
|
||||
this._memory[key] = value;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 日志 | Logging
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @zh 添加日志
|
||||
* @en Add log
|
||||
*/
|
||||
log(message: string): void {
|
||||
this._logs.push({
|
||||
level: 'log',
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
tick: this.getTick()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 添加警告
|
||||
* @en Add warning
|
||||
*/
|
||||
warn(message: string): void {
|
||||
this._logs.push({
|
||||
level: 'warn',
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
tick: this.getTick()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 添加错误
|
||||
* @en Add error
|
||||
*/
|
||||
error(message: string): void {
|
||||
this._logs.push({
|
||||
level: 'error',
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
tick: this.getTick()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取日志列表
|
||||
* @en Get log list
|
||||
*/
|
||||
getLogs(): LogEntry[] {
|
||||
return [...this._logs];
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 清除日志
|
||||
* @en Clear logs
|
||||
*/
|
||||
clearLogs(): void {
|
||||
this._logs = [];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 变量管理 | Variable Management
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @zh 获取变量值
|
||||
* @en Get variable value
|
||||
*/
|
||||
getVariable(name: string): unknown {
|
||||
if (this._localVariables.has(name)) {
|
||||
return this._localVariables.get(name);
|
||||
}
|
||||
if (this._instanceVariables.has(name)) {
|
||||
return this._instanceVariables.get(name);
|
||||
}
|
||||
if (ServerExecutionContext._globalVariables.has(name)) {
|
||||
return ServerExecutionContext._globalVariables.get(name);
|
||||
}
|
||||
|
||||
const varDef = this.blueprint.variables.find(v => v.name === name);
|
||||
return varDef?.defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 设置变量值
|
||||
* @en Set variable value
|
||||
*/
|
||||
setVariable(name: string, value: unknown): void {
|
||||
const varDef = this.blueprint.variables.find(v => v.name === name);
|
||||
|
||||
if (!varDef) {
|
||||
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':
|
||||
ServerExecutionContext._globalVariables.set(name, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取实例变量
|
||||
* @en Get instance variables
|
||||
*/
|
||||
getInstanceVariables(): Map<string, unknown> {
|
||||
return new Map(this._instanceVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 设置实例变量
|
||||
* @en Set instance variables
|
||||
*/
|
||||
setInstanceVariables(variables: Map<string, unknown>): void {
|
||||
this._instanceVariables = new Map(variables);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 输出缓存 | Output Cache
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @zh 设置节点输出
|
||||
* @en Set node outputs
|
||||
*/
|
||||
setOutputs(nodeId: string, outputs: Record<string, unknown>): void {
|
||||
this._outputCache.set(nodeId, outputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取节点输出
|
||||
* @en Get node outputs
|
||||
*/
|
||||
getOutputs(nodeId: string): Record<string, unknown> | undefined {
|
||||
return this._outputCache.get(nodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 清除输出缓存
|
||||
* @en Clear output cache
|
||||
*/
|
||||
clearOutputCache(): void {
|
||||
this._outputCache.clear();
|
||||
this._localVariables.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 清除全局变量
|
||||
* @en Clear global variables
|
||||
*/
|
||||
static clearGlobalVariables(): void {
|
||||
ServerExecutionContext._globalVariables.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user