Compare commits

...

2 Commits

Author SHA1 Message Date
github-actions[bot]
044463dd5f chore: release packages (#357)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-27 09:55:30 +08:00
YHH
ce2db4e48a fix(core): 修复 World cleanup 在打包环境下的兼容性问题 (#356)
- 使用 forEach 替代 spread + for...of 解构模式,避免某些打包工具转换后的兼容性问题
- 重构 World 和 WorldManager 类,提升代码质量
- 提取默认配置为常量
- 统一双语注释格式
2025-12-27 09:51:04 +08:00
20 changed files with 606 additions and 502 deletions

View File

@@ -0,0 +1,8 @@
# @esengine/behavior-tree
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/behavior-tree",
"version": "1.0.1",
"version": "1.0.2",
"description": "ECS-based AI behavior tree system - works with any ECS framework (ESEngine, Cocos, Laya, etc.)",
"main": "dist/index.js",
"module": "dist/index.js",

View File

@@ -0,0 +1,8 @@
# @esengine/blueprint
## 1.0.1
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/blueprint",
"version": "1.0.0",
"version": "1.0.1",
"description": "Visual scripting system - works with any ECS framework (ESEngine, Cocos, Laya, etc.)",
"main": "dist/index.js",
"module": "dist/index.js",

View File

@@ -0,0 +1,10 @@
# @esengine/ecs-framework
## 2.4.3
### Patch Changes
- [#356](https://github.com/esengine/esengine/pull/356) [`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea) Thanks [@esengine](https://github.com/esengine)! - fix(core): 修复 World cleanup 在打包环境下的兼容性问题
- 使用 forEach 替代 spread + for...of 解构模式,避免某些打包工具(如 Cocos Creator转换后的兼容性问题
- 重构 World 和 WorldManager 类,提升代码质量
- 提取默认配置为常量,统一双语注释格式

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/ecs-framework",
"version": "2.4.2",
"version": "2.4.3",
"description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架",
"main": "dist/index.cjs",
"module": "dist/index.mjs",

View File

@@ -13,7 +13,7 @@ const logger = createLogger('World');
* @zh 全局系统是在World级别运行的系统不依赖特定Scene
* @en Global systems run at World level and don't depend on specific Scene
*/
export type IGlobalSystem = {
export interface IGlobalSystem {
/**
* @zh 系统名称
* @en System name
@@ -49,7 +49,7 @@ export type IGlobalSystem = {
* @zh World配置接口
* @en World configuration interface
*/
export type IWorldConfig = {
export interface IWorldConfig {
/**
* @zh World名称
* @en World name
@@ -77,12 +77,23 @@ export type IWorldConfig = {
/**
* @zh 自动清理阈值毫秒空Scene超过此时间后将被自动清理
* @en Auto cleanup threshold (ms), empty scenes exceeding this time will be auto-cleaned
*
* @default 300000 (5 minutes)
*/
cleanupThresholdMs?: number;
}
/**
* @zh World默认配置
* @en World default configuration
*/
const DEFAULT_CONFIG: Required<IWorldConfig> = {
name: 'World',
debug: false,
maxScenes: 10,
autoCleanup: true,
cleanupThresholdMs: 5 * 60 * 1000
};
/**
* @zh World类 - ECS世界管理器
* @en World class - ECS world manager
@@ -101,67 +112,66 @@ export type IWorldConfig = {
* - World.services: World-level services (independent per World)
* - Scene.services: Scene-level services (independent per Scene)
*
* @zh 这种设计允许创建独立的游戏世界,如:
* - 游戏房间每个房间一个World
* - 不同的游戏模式
* - 独立的模拟环境
* @en This design allows creating independent game worlds like:
* - Game rooms (one World per room)
* - Different game modes
* - Independent simulation environments
*
* @example
* ```typescript
* // @zh 创建游戏房间的World | @en Create World for game room
* const roomWorld = new World({ name: 'Room_001' });
*
* // @zh 注册World级别的服务 | @en Register World-level service
* roomWorld.services.registerSingleton(RoomManager);
*
* // @zh 在World中创建Scene | @en Create Scene in World
* const gameScene = roomWorld.createScene('game', new Scene());
* const uiScene = roomWorld.createScene('ui', new Scene());
*
* // @zh 在Scene中使用World级别的服务 | @en Use World-level service in Scene
* const roomManager = roomWorld.services.resolve(RoomManager);
*
* // @zh 更新整个World | @en Update entire World
* roomWorld.update(deltaTime);
* const gameScene = roomWorld.createScene('game');
* roomWorld.setSceneActive('game', true);
* roomWorld.start();
* ```
*/
export class World {
public readonly name: string;
private readonly _config: IWorldConfig;
private readonly _scenes: Map<string, IScene> = new Map();
private readonly _activeScenes: Set<string> = new Set();
private readonly _config: Required<IWorldConfig>;
private readonly _scenes = new Map<string, IScene>();
private readonly _activeScenes = new Set<string>();
private readonly _globalSystems: IGlobalSystem[] = [];
private readonly _services: ServiceContainer;
private _isActive: boolean = false;
private _createdAt: number;
private readonly _createdAt: number;
private _isActive = false;
constructor(config: IWorldConfig = {}) {
this._config = {
name: 'World',
debug: false,
maxScenes: 10,
autoCleanup: true,
cleanupThresholdMs: 5 * 60 * 1000,
...config
};
this.name = this._config.name!;
this._config = { ...DEFAULT_CONFIG, ...config };
this.name = this._config.name;
this._createdAt = Date.now();
this._services = new ServiceContainer();
}
/**
* @zh World级别的服务容器用于管理World范围内的全局服务
* @en World-level service container for managing World-scoped global services
* @zh World级别的服务容器
* @en World-level service container
*/
public get services(): ServiceContainer {
return this._services;
}
/**
* @zh 检查World是否激活
* @en Check if World is active
*/
public get isActive(): boolean {
return this._isActive;
}
/**
* @zh 获取Scene数量
* @en Get scene count
*/
public get sceneCount(): number {
return this._scenes.size;
}
/**
* @zh 获取创建时间
* @en Get creation time
*/
public get createdAt(): number {
return this._createdAt;
}
/**
* @zh 创建并添加Scene到World
* @en Create and add Scene to World
@@ -169,32 +179,21 @@ export class World {
* @param sceneName - @zh Scene名称 @en Scene name
* @param sceneInstance - @zh Scene实例可选@en Scene instance (optional)
* @returns @zh 创建的Scene实例 @en Created Scene instance
* @throws @zh 名称为空、重复或超出限制时抛出错误 @en Throws if name is empty, duplicate, or limit exceeded
*/
public createScene<T extends IScene>(sceneName: string, sceneInstance?: T): T {
if (!sceneName || typeof sceneName !== 'string' || sceneName.trim() === '') {
throw new Error('Scene name不能为空');
}
this.validateSceneName(sceneName);
if (this._scenes.has(sceneName)) {
throw new Error(`Scene name '${sceneName}' 已存在于World '${this.name}' 中`);
}
if (this._scenes.size >= this._config.maxScenes!) {
throw new Error(`World '${this.name}' 已达到最大Scene数量限制: ${this._config.maxScenes}`);
}
const scene = sceneInstance || (new Scene() as unknown as T);
const scene = sceneInstance ?? new Scene() as unknown as T;
if (this._config.debug) {
const performanceMonitor = new PerformanceMonitor();
performanceMonitor.enable();
scene.services.registerInstance(PerformanceMonitor, performanceMonitor);
const monitor = new PerformanceMonitor();
monitor.enable();
scene.services.registerInstance(PerformanceMonitor, monitor);
}
(scene as { id?: string }).id = sceneName;
if (!scene.name) {
scene.name = sceneName;
}
scene.name ||= sceneName;
this._scenes.set(sceneName, scene);
scene.initialize();
@@ -211,9 +210,7 @@ export class World {
*/
public removeScene(sceneName: string): boolean {
const scene = this._scenes.get(sceneName);
if (!scene) {
return false;
}
if (!scene) return false;
if (this._activeScenes.has(sceneName)) {
this.setSceneActive(sceneName, false);
@@ -221,11 +218,20 @@ export class World {
scene.end();
this._scenes.delete(sceneName);
logger.info(`从World '${this.name}' 中移除Scene: ${sceneName}`);
return true;
}
/**
* @zh 移除所有Scene
* @en Remove all Scenes
*/
public removeAllScenes(): void {
this._scenes.forEach((_, name) => this.removeScene(name));
logger.info(`从World '${this.name}' 中移除所有Scene`);
}
/**
* @zh 获取Scene
* @en Get Scene
@@ -234,36 +240,31 @@ export class World {
* @returns @zh Scene实例或null @en Scene instance or null
*/
public getScene<T extends IScene>(sceneName: string): T | null {
return this._scenes.get(sceneName) as T || null;
return (this._scenes.get(sceneName) as T) ?? null;
}
/**
* 获取所有Scene ID
* @zh 获取所有Scene ID
* @en Get all Scene IDs
*/
public getSceneIds(): string[] {
return Array.from(this._scenes.keys());
}
/**
* 获取所有Scene
* @zh 获取所有Scene
* @en Get all Scenes
*/
public getAllScenes(): IScene[] {
return Array.from(this._scenes.values());
}
/**
* 移除所有Scene
*/
public removeAllScenes(): void {
const sceneNames = Array.from(this._scenes.keys());
for (const sceneName of sceneNames) {
this.removeScene(sceneName);
}
logger.info(`从World '${this.name}' 中移除所有Scene`);
}
/**
* 设置Scene激活状态
* @zh 设置Scene激活状态
* @en Set Scene active state
*
* @param sceneName - @zh Scene名称 @en Scene name
* @param active - @zh 是否激活 @en Whether to activate
*/
public setSceneActive(sceneName: string, active: boolean): void {
const scene = this._scenes.get(sceneName);
@@ -283,22 +284,27 @@ export class World {
}
/**
* 检查Scene是否激活
* @zh 检查Scene是否激活
* @en Check if Scene is active
*/
public isSceneActive(sceneName: string): boolean {
return this._activeScenes.has(sceneName);
}
/**
* 获取活跃Scene数量
* @zh 获取活跃Scene数量
* @en Get active Scene count
*/
public getActiveSceneCount(): number {
return this._activeScenes.size;
}
/**
* 添加全局System
* 全局System会在所有激活Scene之前更新
* @zh 添加全局System
* @en Add global System
*
* @param system - @zh 全局System实例 @en Global System instance
* @returns @zh 添加的System实例 @en Added System instance
*/
public addGlobalSystem<T extends IGlobalSystem>(system: T): T {
if (this._globalSystems.includes(system)) {
@@ -307,132 +313,77 @@ export class World {
this._globalSystems.push(system);
system.initialize?.();
logger.debug(`在World '${this.name}' 中添加全局System: ${system.name}`);
return system;
}
/**
* 移除全局System
* @zh 移除全局System
* @en Remove global System
*
* @param system - @zh 要移除的System @en System to remove
* @returns @zh 是否成功移除 @en Whether removal was successful
*/
public removeGlobalSystem(system: IGlobalSystem): boolean {
const index = this._globalSystems.indexOf(system);
if (index === -1) {
return false;
}
if (index === -1) return false;
this._globalSystems.splice(index, 1);
system.reset?.();
logger.debug(`从World '${this.name}' 中移除全局System: ${system.name}`);
return true;
}
/**
* 获取全局System
* @zh 获取全局System
* @en Get global System
*
* @param type - @zh System类型 @en System type
* @returns @zh System实例或null @en System instance or null
*/
public getGlobalSystem<T extends IGlobalSystem>(type: new (...args: any[]) => T): T | null {
for (const system of this._globalSystems) {
if (system instanceof type) {
return system as T;
}
}
return null;
public getGlobalSystem<T extends IGlobalSystem>(type: new (...args: unknown[]) => T): T | null {
return (this._globalSystems.find(s => s instanceof type) as T) ?? null;
}
/**
* 启动World
* @zh 启动World
* @en Start World
*/
public start(): void {
if (this._isActive) {
return;
}
if (this._isActive) return;
this._isActive = true;
for (const system of this._globalSystems) {
system.initialize?.();
}
this._globalSystems.forEach(s => s.initialize?.());
logger.info(`启动World: ${this.name}`);
}
/**
* 停止World
* @zh 停止World
* @en Stop World
*/
public stop(): void {
if (!this._isActive) {
return;
}
for (const sceneName of this._activeScenes) {
this.setSceneActive(sceneName, false);
}
for (const system of this._globalSystems) {
system.reset?.();
}
if (!this._isActive) return;
this._activeScenes.forEach(name => this.setSceneActive(name, false));
this._globalSystems.forEach(s => s.reset?.());
this._isActive = false;
logger.info(`停止World: ${this.name}`);
}
/**
* @zh 更新World中的全局System
* @en Update global systems in World
*
* @internal Called by Core.update()
*/
public updateGlobalSystems(): void {
if (!this._isActive) {
return;
}
for (const system of this._globalSystems) {
system.update?.();
}
}
/**
* @zh 更新World中的所有激活Scene
* @en Update all active scenes in World
*
* @internal Called by Core.update()
*/
public updateScenes(): void {
if (!this._isActive) {
return;
}
for (const sceneName of this._activeScenes) {
const scene = this._scenes.get(sceneName);
scene?.update?.();
}
if (this._config.autoCleanup) {
this.cleanup();
}
}
/**
* 销毁World
* @zh 销毁World
* @en Destroy World
*/
public destroy(): void {
logger.info(`销毁World: ${this.name}`);
this.stop();
this.removeAllScenes();
for (const sceneName of Array.from(this._scenes.keys())) {
this.removeScene(sceneName);
}
for (const system of this._globalSystems) {
if (system.destroy) {
system.destroy();
} else {
system.reset?.();
}
}
this._globalSystems.forEach(s => s.destroy?.() ?? s.reset?.());
this._globalSystems.length = 0;
this._services.clear();
@@ -440,10 +391,49 @@ export class World {
this._activeScenes.clear();
}
/**
* 获取World状态
* @zh 更新World中的全局System
* @en Update global systems in World
* @internal
*/
public updateGlobalSystems(): void {
if (!this._isActive) return;
this._globalSystems.forEach(s => s.update?.());
}
/**
* @zh 更新World中的所有激活Scene
* @en Update all active scenes in World
* @internal
*/
public updateScenes(): void {
if (!this._isActive) return;
this._activeScenes.forEach(name => {
this._scenes.get(name)?.update?.();
});
if (this._config.autoCleanup) {
this.cleanup();
}
}
/**
* @zh 获取World状态
* @en Get World status
*/
public getStatus() {
const scenes: Array<{ id: string; name: string; isActive: boolean }> = [];
this._scenes.forEach((scene, id) => {
scenes.push({
id,
name: scene.name || id,
isActive: this._activeScenes.has(id)
});
});
return {
name: this.name,
isActive: this._isActive,
@@ -452,46 +442,61 @@ export class World {
globalSystemCount: this._globalSystems.length,
createdAt: this._createdAt,
config: { ...this._config },
scenes: Array.from(this._scenes.keys()).map((sceneName) => ({
id: sceneName,
isActive: this._activeScenes.has(sceneName),
name: this._scenes.get(sceneName)?.name || sceneName
}))
scenes
};
}
/**
* 获取World统计信息
* @zh 获取World统计信息
* @en Get World statistics
*/
public getStats() {
const stats = {
totalEntities: 0,
totalSystems: this._globalSystems.length,
let totalEntities = 0;
let totalSystems = this._globalSystems.length;
this._scenes.forEach(scene => {
totalEntities += scene.entities?.count ?? 0;
totalSystems += scene.systems?.length ?? 0;
});
return {
totalEntities,
totalSystems,
memoryUsage: 0,
performance: {
averageUpdateTime: 0,
maxUpdateTime: 0
}
};
}
for (const scene of this._scenes.values()) {
stats.totalEntities += scene.entities?.count ?? 0;
stats.totalSystems += scene.systems?.length ?? 0;
/**
* @zh 验证Scene名称
* @en Validate Scene name
*/
private validateSceneName(sceneName: string): void {
if (!sceneName?.trim()) {
throw new Error('Scene name不能为空');
}
if (this._scenes.has(sceneName)) {
throw new Error(`Scene name '${sceneName}' 已存在于World '${this.name}' 中`);
}
if (this._scenes.size >= this._config.maxScenes) {
throw new Error(`World '${this.name}' 已达到最大Scene数量限制: ${this._config.maxScenes}`);
}
return stats;
}
/**
* @zh 检查Scene是否可以被自动清理
* @en Check if a scene is eligible for auto cleanup
*/
private _isSceneCleanupCandidate(sceneName: string, scene: IScene): boolean {
private isCleanupCandidate(sceneName: string, scene: IScene): boolean {
const elapsed = Date.now() - this._createdAt;
return !this._activeScenes.has(sceneName) &&
scene.entities != null &&
scene.entities.count === 0 &&
elapsed > this._config.cleanupThresholdMs!;
elapsed > this._config.cleanupThresholdMs;
}
/**
@@ -499,33 +504,18 @@ export class World {
* @en Execute auto cleanup operation
*/
private cleanup(): void {
const candidates = [...this._scenes.entries()]
.filter(([name, scene]) => this._isSceneCleanupCandidate(name, scene));
const toRemove: string[] = [];
for (const [sceneName] of candidates) {
this.removeScene(sceneName);
logger.debug(`自动清理空Scene: ${sceneName} from World ${this.name}`);
}
this._scenes.forEach((scene, name) => {
if (this.isCleanupCandidate(name, scene)) {
toRemove.push(name);
}
});
toRemove.forEach(name => {
this.removeScene(name);
logger.debug(`自动清理空Scene: ${name} from World ${this.name}`);
});
}
/**
* 检查World是否激活
*/
public get isActive(): boolean {
return this._isActive;
}
/**
* 获取Scene数量
*/
public get sceneCount(): number {
return this._scenes.size;
}
/**
* 获取创建时间
*/
public get createdAt(): number {
return this._createdAt;
}
}

View File

@@ -5,80 +5,85 @@ import type { IService } from '../Core/ServiceContainer';
const logger = createLogger('WorldManager');
/**
* WorldManager配置接口
* @zh WorldManager配置接口
* @en WorldManager configuration interface
*/
export type IWorldManagerConfig = {
export interface IWorldManagerConfig {
/**
* 最大World数量
* @zh 最大World数量
* @en Maximum number of worlds
*/
maxWorlds?: number;
/**
* 是否自动清理空World
* @zh 是否自动清理空World
* @en Auto cleanup empty worlds
*/
autoCleanup?: boolean;
/**
* 清理间隔(帧数)
* @zh 清理间隔(帧数)
* @en Cleanup interval in frames
*/
cleanupFrameInterval?: number;
/**
* 是否启用调试模式
* @zh 是否启用调试模式
* @en Enable debug mode
*/
debug?: boolean;
}
/**
* World管理器 - 管理所有World实例
* @zh WorldManager默认配置
* @en WorldManager default configuration
*/
const DEFAULT_CONFIG: Required<IWorldManagerConfig> = {
maxWorlds: 50,
autoCleanup: true,
cleanupFrameInterval: 1800,
debug: false
};
/**
* @zh 清理阈值(毫秒)
* @en Cleanup threshold in milliseconds
*/
const CLEANUP_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes
/**
* @zh World管理器 - 管理所有World实例
* @en World Manager - Manages all World instances
*
* WorldManager负责管理多个独立的World实例。
* @zh WorldManager负责管理多个独立的World实例。
* 每个World都是独立的ECS环境可以包含多个Scene。
* @en WorldManager is responsible for managing multiple independent World instances.
* Each World is an isolated ECS environment that can contain multiple Scenes.
*
* 适用场景:
* @zh 适用场景:
* - MMO游戏的多房间管理
* - 服务器端的多游戏实例
* - 需要完全隔离的多个游戏环境
* @en Use cases:
* - Multi-room management for MMO games
* - Multiple game instances on server-side
* - Completely isolated game environments
*
* @example
* ```typescript
* // 创建WorldManager实例
* const worldManager = new WorldManager({
* maxWorlds: 100,
* autoCleanup: true
* });
*
* // 创建游戏房间World
* const room1 = worldManager.createWorld('room_001', {
* name: 'GameRoom_001',
* maxScenes: 5
* });
* room1.setActive(true);
*
* // 游戏循环
* function gameLoop(deltaTime: number) {
* Core.update(deltaTime);
* worldManager.updateAll(); // 更新所有活跃World
* }
* const worldManager = new WorldManager({ maxWorlds: 100 });
* const room = worldManager.createWorld('room_001');
* worldManager.setWorldActive('room_001', true);
* ```
*/
export class WorldManager implements IService {
private readonly _config: Required<IWorldManagerConfig>;
private readonly _worlds: Map<string, World> = new Map();
private _isRunning: boolean = false;
private _framesSinceCleanup: number = 0;
private readonly _worlds = new Map<string, World>();
private _isRunning = true;
private _framesSinceCleanup = 0;
public constructor(config: IWorldManagerConfig = {}) {
this._config = {
maxWorlds: 50,
autoCleanup: true,
cleanupFrameInterval: 1800, // 1800帧
debug: false,
...config
};
// 默认启动运行状态
this._isRunning = true;
constructor(config: IWorldManagerConfig = {}) {
this._config = { ...DEFAULT_CONFIG, ...config };
logger.info('WorldManager已初始化', {
maxWorlds: this._config.maxWorlds,
@@ -88,27 +93,57 @@ export class WorldManager implements IService {
}
/**
* 创建新World
* @zh 获取World总数
* @en Get total world count
*/
public get worldCount(): number {
return this._worlds.size;
}
/**
* @zh 获取激活World数量
* @en Get active world count
*/
public get activeWorldCount(): number {
let count = 0;
this._worlds.forEach(world => {
if (world.isActive) count++;
});
return count;
}
/**
* @zh 检查是否正在运行
* @en Check if running
*/
public get isRunning(): boolean {
return this._isRunning;
}
/**
* @zh 获取配置
* @en Get configuration
*/
public get config(): IWorldManagerConfig {
return { ...this._config };
}
/**
* @zh 创建新World
* @en Create new World
*
* @param worldName - @zh World名称 @en World name
* @param config - @zh World配置 @en World configuration
* @returns @zh 创建的World实例 @en Created World instance
* @throws @zh 名称为空、重复或超出限制时抛出错误 @en Throws if name is empty, duplicate, or limit exceeded
*/
public createWorld(worldName: string, config?: IWorldConfig): World {
if (!worldName || typeof worldName !== 'string' || worldName.trim() === '') {
throw new Error('World name不能为空');
}
this.validateWorldName(worldName);
if (this._worlds.has(worldName)) {
throw new Error(`World name '${worldName}' 已存在`);
}
if (this._worlds.size >= this._config.maxWorlds!) {
throw new Error(`已达到最大World数量限制: ${this._config.maxWorlds}`);
}
// 优先级config.debug > WorldManager.debug > 默认
const worldConfig: IWorldConfig = {
...config,
name: worldName,
debug: config?.debug ?? this._config.debug ?? false,
...(config?.maxScenes !== undefined && { maxScenes: config.maxScenes }),
...(config?.autoCleanup !== undefined && { autoCleanup: config.autoCleanup })
debug: config?.debug ?? this._config.debug
};
const world = new World(worldConfig);
@@ -118,45 +153,56 @@ export class WorldManager implements IService {
}
/**
* 移除World
* @zh 移除World
* @en Remove World
*
* @param worldName - @zh World名称 @en World name
* @returns @zh 是否成功移除 @en Whether removal was successful
*/
public removeWorld(worldName: string): boolean {
const world = this._worlds.get(worldName);
if (!world) {
return false;
}
if (!world) return false;
// 销毁World
world.destroy();
this._worlds.delete(worldName);
logger.info(`移除World: ${worldName}`);
return true;
}
/**
* 获取World
* @zh 获取World
* @en Get World
*
* @param worldName - @zh World名称 @en World name
* @returns @zh World实例或null @en World instance or null
*/
public getWorld(worldName: string): World | null {
return this._worlds.get(worldName) || null;
return this._worlds.get(worldName) ?? null;
}
/**
* 获取所有World ID
* @zh 获取所有World ID
* @en Get all World IDs
*/
public getWorldIds(): string[] {
return Array.from(this._worlds.keys());
}
/**
* 获取所有World
* @zh 获取所有World
* @en Get all Worlds
*/
public getAllWorlds(): World[] {
return Array.from(this._worlds.values());
}
/**
* 设置World激活状态
* @zh 设置World激活状态
* @en Set World active state
*
* @param worldName - @zh World名称 @en World name
* @param active - @zh 是否激活 @en Whether to activate
*/
public setWorldActive(worldName: string, active: boolean): void {
const world = this._worlds.get(worldName);
@@ -175,204 +221,84 @@ export class WorldManager implements IService {
}
/**
* 检查World是否激活
* @zh 检查World是否激活
* @en Check if World is active
*/
public isWorldActive(worldName: string): boolean {
const world = this._worlds.get(worldName);
return world?.isActive ?? false;
return this._worlds.get(worldName)?.isActive ?? false;
}
/**
* 更新所有活跃的World
*
* 应该在每帧的游戏循环中调用。
* 会自动更新所有活跃World的全局系统和场景。
*
* @example
* ```typescript
* function gameLoop(deltaTime: number) {
* Core.update(deltaTime); // 更新全局服务
* worldManager.updateAll(); // 更新所有World
* }
* ```
*/
public updateAll(): void {
if (!this._isRunning) return;
for (const world of this._worlds.values()) {
if (world.isActive) {
// 更新World的全局System
world.updateGlobalSystems();
// 更新World中的所有Scene
world.updateScenes();
}
}
// 基于帧的自动清理
if (this._config.autoCleanup) {
this._framesSinceCleanup++;
if (this._framesSinceCleanup >= this._config.cleanupFrameInterval) {
this.cleanup();
this._framesSinceCleanup = 0; // 重置计数器
if (this._config.debug) {
logger.debug(`执行定期清理World (间隔: ${this._config.cleanupFrameInterval} 帧)`);
}
}
}
}
/**
* 获取所有激活的World
* @zh 获取所有激活的World
* @en Get all active Worlds
*/
public getActiveWorlds(): World[] {
const activeWorlds: World[] = [];
for (const world of this._worlds.values()) {
if (world.isActive) {
activeWorlds.push(world);
}
}
return activeWorlds;
const result: World[] = [];
this._worlds.forEach(world => {
if (world.isActive) result.push(world);
});
return result;
}
/**
* 启动所有World
* @zh 查找满足条件的World
* @en Find Worlds matching predicate
*
* @param predicate - @zh 过滤条件 @en Filter predicate
*/
public findWorlds(predicate: (world: World) => boolean): World[] {
const result: World[] = [];
this._worlds.forEach(world => {
if (predicate(world)) result.push(world);
});
return result;
}
/**
* @zh 根据名称查找World
* @en Find World by name
*
* @param name - @zh World名称 @en World name
*/
public findWorldByName(name: string): World | null {
let found: World | null = null;
this._worlds.forEach(world => {
if (world.name === name) found = world;
});
return found;
}
/**
* @zh 启动所有World
* @en Start all Worlds
*/
public startAll(): void {
this._isRunning = true;
for (const world of this._worlds.values()) {
world.start();
}
this._worlds.forEach(world => world.start());
logger.info('启动所有World');
}
/**
* 停止所有World
* @zh 停止所有World
* @en Stop all Worlds
*/
public stopAll(): void {
this._isRunning = false;
for (const world of this._worlds.values()) {
world.stop();
}
this._worlds.forEach(world => world.stop());
logger.info('停止所有World');
}
/**
* 查找满足条件的World
*/
public findWorlds(predicate: (world: World) => boolean): World[] {
const results: World[] = [];
for (const world of this._worlds.values()) {
if (predicate(world)) {
results.push(world);
}
}
return results;
}
/**
* 根据名称查找World
*/
public findWorldByName(name: string): World | null {
for (const world of this._worlds.values()) {
if (world.name === name) {
return world;
}
}
return null;
}
/**
* 获取WorldManager统计信息
*/
public getStats() {
const stats = {
totalWorlds: this._worlds.size,
activeWorlds: this.activeWorldCount,
totalScenes: 0,
totalEntities: 0,
totalSystems: 0,
memoryUsage: 0,
isRunning: this._isRunning,
config: { ...this._config },
worlds: [] as any[]
};
for (const [worldName, world] of this._worlds) {
const worldStats = world.getStats();
stats.totalScenes += worldStats.totalSystems; // World的getStats可能需要调整
stats.totalEntities += worldStats.totalEntities;
stats.totalSystems += worldStats.totalSystems;
stats.worlds.push({
id: worldName,
name: world.name,
isActive: world.isActive,
sceneCount: world.sceneCount,
...worldStats
});
}
return stats;
}
/**
* 获取详细状态信息
*/
public getDetailedStatus() {
return {
...this.getStats(),
worlds: Array.from(this._worlds.entries()).map(([worldName, world]) => ({
id: worldName,
isActive: world.isActive,
status: world.getStatus()
}))
};
}
/**
* 清理空World
*/
public cleanup(): number {
const worldsToRemove: string[] = [];
for (const [worldName, world] of this._worlds) {
if (this.shouldCleanupWorld(world)) {
worldsToRemove.push(worldName);
}
}
for (const worldName of worldsToRemove) {
this.removeWorld(worldName);
}
if (worldsToRemove.length > 0) {
logger.debug(`清理了 ${worldsToRemove.length} 个World`);
}
return worldsToRemove.length;
}
/**
* 销毁WorldManager
* @zh 销毁WorldManager
* @en Destroy WorldManager
*/
public destroy(): void {
logger.info('正在销毁WorldManager...');
// 停止所有World
this.stopAll();
// 销毁所有World
const worldNames = Array.from(this._worlds.keys());
for (const worldName of worldNames) {
this.removeWorld(worldName);
}
worldNames.forEach(name => this.removeWorld(name));
this._worlds.clear();
this._isRunning = false;
@@ -381,67 +307,178 @@ export class WorldManager implements IService {
}
/**
* 实现 IService 接口的 dispose 方法
* 调用 destroy 方法进行清理
* @zh 实现 IService 接口的 dispose 方法
* @en Implement IService dispose method
*/
public dispose(): void {
this.destroy();
}
/**
* 判断World是否应该被清理
* 清理策略:
* 1. World未激活
* 2. 没有Scene或所有Scene都是空的
* 3. 创建时间超过10分钟
* @zh 更新所有活跃的World
* @en Update all active Worlds
*
* @zh 应该在每帧的游戏循环中调用
* @en Should be called in each frame of game loop
*/
private shouldCleanupWorld(world: World): boolean {
if (world.isActive) {
return false;
public updateAll(): void {
if (!this._isRunning) return;
this._worlds.forEach(world => {
if (world.isActive) {
world.updateGlobalSystems();
world.updateScenes();
}
});
this.processAutoCleanup();
}
/**
* @zh 获取WorldManager统计信息
* @en Get WorldManager statistics
*/
public getStats() {
let totalScenes = 0;
let totalEntities = 0;
let totalSystems = 0;
const worldsList: Array<{
id: string;
name: string;
isActive: boolean;
sceneCount: number;
totalEntities: number;
totalSystems: number;
}> = [];
this._worlds.forEach((world, worldName) => {
const worldStats = world.getStats();
totalScenes += world.sceneCount;
totalEntities += worldStats.totalEntities;
totalSystems += worldStats.totalSystems;
worldsList.push({
id: worldName,
name: world.name,
isActive: world.isActive,
sceneCount: world.sceneCount,
...worldStats
});
});
return {
totalWorlds: this._worlds.size,
activeWorlds: this.activeWorldCount,
totalScenes,
totalEntities,
totalSystems,
memoryUsage: 0,
isRunning: this._isRunning,
config: { ...this._config },
worlds: worldsList
};
}
/**
* @zh 获取详细状态信息
* @en Get detailed status information
*/
public getDetailedStatus() {
const worlds: Array<{
id: string;
isActive: boolean;
status: ReturnType<World['getStatus']>;
}> = [];
this._worlds.forEach((world, worldName) => {
worlds.push({
id: worldName,
isActive: world.isActive,
status: world.getStatus()
});
});
return { ...this.getStats(), worlds };
}
/**
* @zh 清理空World
* @en Cleanup empty Worlds
*
* @returns @zh 清理的World数量 @en Number of cleaned up Worlds
*/
public cleanup(): number {
const toRemove: string[] = [];
this._worlds.forEach((world, worldName) => {
if (this.isCleanupCandidate(world)) {
toRemove.push(worldName);
}
});
toRemove.forEach(name => this.removeWorld(name));
if (toRemove.length > 0) {
logger.debug(`清理了 ${toRemove.length} 个World`);
}
return toRemove.length;
}
/**
* @zh 验证World名称
* @en Validate World name
*/
private validateWorldName(worldName: string): void {
if (!worldName?.trim()) {
throw new Error('World name不能为空');
}
if (this._worlds.has(worldName)) {
throw new Error(`World name '${worldName}' 已存在`);
}
if (this._worlds.size >= this._config.maxWorlds) {
throw new Error(`已达到最大World数量限制: ${this._config.maxWorlds}`);
}
}
/**
* @zh 处理自动清理
* @en Process auto cleanup
*/
private processAutoCleanup(): void {
if (!this._config.autoCleanup) return;
this._framesSinceCleanup++;
if (this._framesSinceCleanup >= this._config.cleanupFrameInterval) {
this.cleanup();
this._framesSinceCleanup = 0;
if (this._config.debug) {
logger.debug(`执行定期清理World (间隔: ${this._config.cleanupFrameInterval} 帧)`);
}
}
}
/**
* @zh 判断World是否应该被清理
* @en Check if World should be cleaned up
*
* @zh 清理策略:未激活 + (无Scene或全空Scene) + 创建超过10分钟
* @en Cleanup policy: inactive + (no scenes or all empty) + created over 10 minutes ago
*/
private isCleanupCandidate(world: World): boolean {
if (world.isActive) return false;
const age = Date.now() - world.createdAt;
const isOldEnough = age > 10 * 60 * 1000; // 10分钟
if (age <= CLEANUP_THRESHOLD_MS) return false;
if (world.sceneCount === 0) {
return isOldEnough;
}
if (world.sceneCount === 0) return true;
// 检查是否所有Scene都是空的
const allScenes = world.getAllScenes();
const hasEntities = allScenes.some((scene) => scene.entities && scene.entities.count > 0);
return !hasEntities && isOldEnough;
}
const hasEntities = world.getAllScenes().some(
scene => scene.entities && scene.entities.count > 0
);
/**
* 获取World总数
*/
public get worldCount(): number {
return this._worlds.size;
}
/**
* 获取激活World数量
*/
public get activeWorldCount(): number {
let count = 0;
for (const world of this._worlds.values()) {
if (world.isActive) count++;
}
return count;
}
/**
* 检查是否正在运行
*/
public get isRunning(): boolean {
return this._isRunning;
}
/**
* 获取配置
*/
public get config(): IWorldManagerConfig {
return { ...this._config };
return !hasEntities;
}
}

View File

@@ -1,5 +1,13 @@
# @esengine/fsm
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/fsm",
"version": "1.0.1",
"version": "1.0.2",
"description": "Finite State Machine for ECS Framework / ECS 框架的有限状态机",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,13 @@
# @esengine/pathfinding
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/pathfinding",
"version": "1.0.1",
"version": "1.0.2",
"description": "寻路系统 | Pathfinding System - A*, Grid, NavMesh",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,13 @@
# @esengine/procgen
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/procgen",
"version": "1.0.1",
"version": "1.0.2",
"description": "Procedural generation tools for ECS Framework / ECS 框架的程序化生成工具",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,13 @@
# @esengine/spatial
## 1.0.3
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/spatial",
"version": "1.0.2",
"version": "1.0.3",
"description": "Spatial query and indexing system for ECS Framework / ECS 框架的空间查询和索引系统",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,13 @@
# @esengine/timer
## 1.0.2
### Patch Changes
- Updated dependencies [[`ce2db4e`](https://github.com/esengine/esengine/commit/ce2db4e48a7cdac44265420ef16e83f6424f4dea)]:
- @esengine/ecs-framework@2.4.3
- @esengine/blueprint@1.0.1
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/timer",
"version": "1.0.1",
"version": "1.0.2",
"description": "Timer and cooldown system for ECS Framework / ECS 框架的定时器和冷却系统",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,5 +1,16 @@
# @esengine/demos
## 1.0.2
### Patch Changes
- Updated dependencies []:
- @esengine/fsm@1.0.2
- @esengine/pathfinding@1.0.2
- @esengine/procgen@1.0.2
- @esengine/spatial@1.0.3
- @esengine/timer@1.0.2
## 1.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/demos",
"version": "1.0.1",
"version": "1.0.2",
"private": true,
"description": "Demo tests for ESEngine modules documentation",
"type": "module",