Files
esengine/packages/engine/script-runtime/src/vm/ServerExecutionContext.ts

460 lines
12 KiB
TypeScript
Raw Normal View History

feat(script-runtime): 服务器端蓝图执行模块 (#322) * feat(script-runtime): 添加服务器端蓝图执行模块 - ServerBlueprintVM: 服务器端蓝图虚拟机 - CPULimiter: CPU 时间和步数限制 - IntentCollector: 意图收集系统 - FileMemoryStore: 文件系统持久化 - ServerExecutionContext: 服务器执行上下文 * refactor(script-runtime): 分离引擎接口与游戏逻辑 - 重构 IntentTypes.ts 只保留基础 IIntent 接口和通用常量 - IntentCollector 改为泛型类,支持任意意图类型 - ServerExecutionContext 改为泛型类,支持任意游戏状态类型 - ServerBlueprintVM 改为泛型类,使用 TGameState 和 TIntent 类型参数 - 移除游戏特定类型(IUnitState, ISpawnerState 等),由游戏项目定义 - 添加 IntentKeyExtractor 机制用于防止重复意图 * feat(script-runtime): 添加服务器端游戏循环框架 - PlayerSession: 封装单个玩家的 VM、蓝图和 Memory 状态 - TickScheduler: 管理所有玩家会话,调度每 tick 的蓝图执行 - IIntentProcessor: 意图处理器接口,由游戏项目实现 - IntentProcessorBase: 意图处理器基类,提供常用处理模式 - IntentProcessorRegistry: 按类型注册意图处理器 - GameLoop: 完整的游戏主循环,协调各组件工作 * feat(script-runtime): 添加通用蓝图节点 Memory 节点: - GetMemory: 读取玩家 Memory - SetMemory: 写入玩家 Memory - HasMemoryKey: 检查键是否存在 - DeleteMemory: 删除 Memory 键 Log 节点: - Log: 记录日志 - Warn: 记录警告 - Error: 记录错误 Game 信息节点: - GetTick: 获取当前 tick - GetPlayerId: 获取玩家 ID - GetDeltaTime: 获取增量时间 - GetGameState: 获取游戏状态 提供 registerScriptRuntimeNodes() 用于批量注册节点 * fix(script-runtime): 修复 CI 构建错误 - 更新 tsconfig.json 继承 tsconfig.base.json - 添加 references 到 core 和 blueprint 包 - 更新 pnpm-lock.yaml * fix(script-runtime): 修复 DTS 构建错误 - 添加 tsconfig.build.json 用于 tsup 构建 - 更新 tsup.config.ts 使用 tsconfig.build.json - 分离构建配置和类型检查配置
2025-12-25 11:00:43 +08:00
/**
* @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();
}
}