From ce2db4e48a7cdac44265420ef16e83f6424f4dea Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Sat, 27 Dec 2025 09:51:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8D=20World=20clean?= =?UTF-8?q?up=20=E5=9C=A8=E6=89=93=E5=8C=85=E7=8E=AF=E5=A2=83=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E5=85=BC=E5=AE=B9=E6=80=A7=E9=97=AE=E9=A2=98=20(#356)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 forEach 替代 spread + for...of 解构模式,避免某些打包工具转换后的兼容性问题 - 重构 World 和 WorldManager 类,提升代码质量 - 提取默认配置为常量 - 统一双语注释格式 --- .changeset/fix-world-cleanup.md | 9 + packages/framework/core/src/ECS/World.ts | 408 ++++++------ .../framework/core/src/ECS/WorldManager.ts | 605 ++++++++++-------- 3 files changed, 529 insertions(+), 493 deletions(-) create mode 100644 .changeset/fix-world-cleanup.md diff --git a/.changeset/fix-world-cleanup.md b/.changeset/fix-world-cleanup.md new file mode 100644 index 00000000..c9ca8f10 --- /dev/null +++ b/.changeset/fix-world-cleanup.md @@ -0,0 +1,9 @@ +--- +"@esengine/ecs-framework": patch +--- + +fix(core): 修复 World cleanup 在打包环境下的兼容性问题 + +- 使用 forEach 替代 spread + for...of 解构模式,避免某些打包工具(如 Cocos Creator)转换后的兼容性问题 +- 重构 World 和 WorldManager 类,提升代码质量 +- 提取默认配置为常量,统一双语注释格式 diff --git a/packages/framework/core/src/ECS/World.ts b/packages/framework/core/src/ECS/World.ts index 15ce23f7..36a70591 100644 --- a/packages/framework/core/src/ECS/World.ts +++ b/packages/framework/core/src/ECS/World.ts @@ -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 = { + 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 = new Map(); - private readonly _activeScenes: Set = new Set(); + + private readonly _config: Required; + private readonly _scenes = new Map(); + private readonly _activeScenes = new Set(); 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(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(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(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(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(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; - } } diff --git a/packages/framework/core/src/ECS/WorldManager.ts b/packages/framework/core/src/ECS/WorldManager.ts index df8a97c8..dd2a34de 100644 --- a/packages/framework/core/src/ECS/WorldManager.ts +++ b/packages/framework/core/src/ECS/WorldManager.ts @@ -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 = { + 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; - private readonly _worlds: Map = new Map(); - private _isRunning: boolean = false; - private _framesSinceCleanup: number = 0; + private readonly _worlds = new Map(); + 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; + }> = []; + + 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; } }