Files
esengine/packages/engine/script-runtime/src/server/PlayerSession.ts
YHH 155411e743 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
2025-12-26 14:50:35 +08:00

295 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @zh 玩家会话
* @en Player Session
*
* @zh 封装单个玩家的 VM 实例、蓝图和 Memory 状态
* @en Encapsulates a single player's VM instance, blueprint and Memory state
*/
import type { BlueprintAsset } from '@esengine/blueprint';
import type { IIntent, IntentKeyExtractor } from '../intent/IntentTypes';
import type { IGameState } from '../vm/ServerExecutionContext';
import type { CPULimiterConfig } from '../vm/CPULimiter';
import { ServerBlueprintVM } from '../vm/ServerBlueprintVM';
import type { PlayerTickResult } from './types';
/**
* @zh 玩家会话配置
* @en Player session configuration
*
* @typeParam TIntent - @zh 意图类型 @en Intent type
*/
export interface PlayerSessionConfig<TIntent extends IIntent = IIntent> {
/**
* @zh CPU 限制配置
* @en CPU limit configuration
*/
readonly cpuConfig?: Partial<CPULimiterConfig>;
/**
* @zh 意图键提取器
* @en Intent key extractor
*/
readonly intentKeyExtractor?: IntentKeyExtractor<TIntent>;
/**
* @zh 调试模式
* @en Debug mode
*/
readonly debug?: boolean;
}
/**
* @zh 玩家会话状态
* @en Player session state
*/
export type PlayerSessionState = 'active' | 'suspended' | 'error';
/**
* @zh 玩家会话
* @en Player Session
*
* @zh 管理单个玩家的蓝图执行环境
* @en Manages a single player's blueprint execution environment
*
* @typeParam TGameState - @zh 游戏状态类型 @en Game state type
* @typeParam TIntent - @zh 意图类型 @en Intent type
*
* @example
* ```typescript
* const session = new PlayerSession<MyGameState, MyIntent>(
* 'player1',
* playerBlueprint,
* { cpuConfig: { maxCpuTime: 50 } }
* );
*
* const result = session.executeTick(gameState);
* ```
*/
export class PlayerSession<
TGameState extends IGameState = IGameState,
TIntent extends IIntent = IIntent
> {
private readonly _playerId: string;
private readonly _vm: ServerBlueprintVM<TGameState, TIntent>;
private _memory: Record<string, unknown>;
private _state: PlayerSessionState = 'active';
private _lastError: string | null = null;
private _totalCpuUsed: number = 0;
private _ticksExecuted: number = 0;
/**
* @param playerId - @zh 玩家 ID @en Player ID
* @param blueprint - @zh 蓝图资产 @en Blueprint asset
* @param config - @zh 配置选项 @en Configuration options
*/
constructor(
playerId: string,
blueprint: BlueprintAsset,
config: PlayerSessionConfig<TIntent> = {}
) {
this._playerId = playerId;
this._memory = {};
this._vm = new ServerBlueprintVM<TGameState, TIntent>(playerId, blueprint, {
cpuConfig: config.cpuConfig,
intentKeyExtractor: config.intentKeyExtractor,
debug: config.debug
});
}
// =========================================================================
// 属性 | Properties
// =========================================================================
/**
* @zh 获取玩家 ID
* @en Get player ID
*/
get playerId(): string {
return this._playerId;
}
/**
* @zh 获取会话状态
* @en Get session state
*/
get state(): PlayerSessionState {
return this._state;
}
/**
* @zh 获取最后一次错误
* @en Get last error
*/
get lastError(): string | null {
return this._lastError;
}
/**
* @zh 获取累计 CPU 使用时间
* @en Get total CPU time used
*/
get totalCpuUsed(): number {
return this._totalCpuUsed;
}
/**
* @zh 获取已执行的 tick 数
* @en Get number of ticks executed
*/
get ticksExecuted(): number {
return this._ticksExecuted;
}
/**
* @zh 获取当前 Memory
* @en Get current Memory
*/
get memory(): Readonly<Record<string, unknown>> {
return this._memory;
}
/**
* @zh 获取底层 VM 实例
* @en Get underlying VM instance
*/
get vm(): ServerBlueprintVM<TGameState, TIntent> {
return this._vm;
}
// =========================================================================
// Memory 管理 | Memory Management
// =========================================================================
/**
* @zh 设置 Memory
* @en Set Memory
*/
setMemory(memory: Record<string, unknown>): void {
this._memory = { ...memory };
}
/**
* @zh 更新 Memory合并
* @en Update Memory (merge)
*/
updateMemory(updates: Record<string, unknown>): void {
this._memory = { ...this._memory, ...updates };
}
/**
* @zh 清空 Memory
* @en Clear Memory
*/
clearMemory(): void {
this._memory = {};
}
// =========================================================================
// 执行 | Execution
// =========================================================================
/**
* @zh 执行一个 tick
* @en Execute one tick
*
* @param gameState - @zh 当前游戏状态 @en Current game state
* @returns @zh 执行结果 @en Execution result
*/
executeTick(gameState: TGameState): PlayerTickResult<TIntent> {
if (this._state === 'suspended') {
return this._createSkippedResult('Session is suspended');
}
try {
const result = this._vm.executeTick(gameState, this._memory);
this._memory = result.memory;
this._totalCpuUsed += result.cpu.used;
this._ticksExecuted++;
if (!result.success && result.errors.length > 0) {
this._lastError = result.errors[0];
}
return {
playerId: this._playerId,
success: result.success,
cpu: result.cpu,
intents: result.intents,
logs: result.logs,
errors: result.errors,
memory: result.memory
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this._state = 'error';
this._lastError = errorMessage;
return this._createSkippedResult(errorMessage);
}
}
// =========================================================================
// 状态管理 | State Management
// =========================================================================
/**
* @zh 暂停会话
* @en Suspend session
*/
suspend(): void {
this._state = 'suspended';
}
/**
* @zh 恢复会话
* @en Resume session
*/
resume(): void {
if (this._state === 'suspended') {
this._state = 'active';
}
}
/**
* @zh 重置会话
* @en Reset session
*/
reset(): void {
this._vm.reset();
this._state = 'active';
this._lastError = null;
this._totalCpuUsed = 0;
this._ticksExecuted = 0;
}
/**
* @zh 从错误状态恢复
* @en Recover from error state
*/
recover(): void {
if (this._state === 'error') {
this._vm.reset();
this._state = 'active';
this._lastError = null;
}
}
// =========================================================================
// 私有方法 | Private Methods
// =========================================================================
private _createSkippedResult(error: string): PlayerTickResult<TIntent> {
return {
playerId: this._playerId,
success: false,
cpu: { used: 0, limit: 0, bucket: 0, steps: 0, maxSteps: 0, exceeded: false },
intents: [],
logs: [],
errors: [error],
memory: this._memory
};
}
}