diff --git a/README.md b/README.md index 9c2e2dea..6b0ad2e4 100644 --- a/README.md +++ b/README.md @@ -19,16 +19,154 @@ TypeScript ECS (Entity-Component-System) 框架,专为游戏开发设计。 -## ECS 架构原理 +## 架构原理 -
- ECS 架构流程动画 -
+ECS Framework 采用多World + 多Scene的现代化架构设计: -ECS 是一种基于组合而非继承的软件架构模式: -- **Entity(实体)**: 游戏对象的唯一标识 -- **Component(组件)**: 纯数据结构,描述实体属性 -- **System(系统)**: 处理具有特定组件的实体 +```mermaid +graph TD + subgraph Main["🎮 ECS Framework - 多World・多Scene架构"] + direction TB + + subgraph CoreLayer["⚙️ 核心层 (Core Foundation)"] + direction LR + Core["🔧 Core
📋 生命周期管理
⚙️ 配置系统
🔗 平台兼容"] + Registry["📝 ComponentRegistry
🏷️ 类型注册
✨ 装饰器支持
🔒 类型安全"] + Pool["🔢 IdentifierPool
🆔 实体ID分配
♻️ ID回收
📊 BigInt兼容"] + PoolMgr["♻️ PoolManager
🎯 对象池
⚡ 内存优化
📈 性能提升"] + EventBus["📡 EventBus
🔄 事件系统
⚡ 异步/同步
🎭 类型安全"] + end + + subgraph WorldLayer["🌍 世界管理层 (World Management)"] + direction TB + WorldMgr["🗺️ WorldManager
🚀 多World调度
📊 资源管理
🔍 统计监控
🧹 自动清理"] + + subgraph WorldsContainer["多World容器"] + direction LR + World1["🌐 GameWorld
🎮 游戏逻辑
🌟 全局系统
🔄 跨Scene业务"] + World2["🌐 UIWorld
🎨 界面管理
⚡ 独立更新
🔒 资源隔离"] + end + + GlobalSys["🎭 Global Systems
🌐 NetworkSync
👥 PlayerMgmt
📡 跨Scene通信"] + end + + subgraph SceneLayer["🎬 场景层 (Scene Management)"] + direction LR + Scene1["🎯 BattleScene
⚔️ 实体管理
🎪 系统调度
⚡ 高性能处理"] + Scene2["🎯 MenuScene
🎨 界面逻辑
🔄 生命周期
💾 状态管理"] + Scene3["🎯 UIScene
📦 组件存储
🔍 查询引擎
🎭 交互处理"] + end + + subgraph ECLayer["🤖 实体组件层 (Entity-Component System)"] + direction TB + + subgraph EntityMgmt["📦 实体管理 (Entity Management)"] + direction LR + EntityMgr["👥 EntityManager
📋 集合管理
🌳 层次结构
⚡ 高效操作"] + Entities["🎭 Entities
👤 Player
👹 Enemy
💥 Bullet
🎯 轻量容器"] + end + + subgraph ComponentStore["🧩 组件存储 (Component Storage)"] + direction LR + Storage["💾 ComponentStorage
📊 SoA模式
📚 AoS模式
⚡ 内存优化"] + StorageMgr["🗄️ StorageManager
🏷️ 类型管理
🔄 脏标记
📈 性能监控"] + Components["🎲 Components
📍 Position
🏃 Velocity
❤️ Health
📊 纯数据"] + end + end + + subgraph SystemLayer["⚡ 系统层 (System Processing)"] + direction TB + + subgraph EntitySys["🔄 实体系统 (Entity Systems)"] + direction LR + EntitySystems["🎪 EntitySystems
🏃 MovementSystem
🎨 RenderSystem
🧠 AISystem
⚡ 业务逻辑"] + Processors["📋 EntityProcessors
🎯 调度管理
📊 优先级
⚡ 批量处理"] + end + end + + subgraph QueryLayer["🔍 查询优化层 (Query & Optimization)"] + direction LR + Matcher["🎯 Matcher
✅ withAll
🔄 withAny
❌ withNone
🌊 流式API
💾 智能缓存"] + QuerySys["🔎 QuerySystem
⚡ 实时查询
📦 批量优化
🔄 自动更新"] + Archetype["🏗️ ArchetypeSystem
📊 组件分组
🎯 原型缓存
💻 BitSet优化"] + end + + subgraph DebugLayer["📊 监控调试层 (Debug & Monitoring)"] + direction LR + Debug["🐛 DebugManager
🌐 WebSocket调试
🎮 Cocos Creator插件
📸 内存快照"] + Perf["📈 PerformanceMonitor
📊 性能统计
⚠️ 阈值告警
📱 实时监控"] + Logger["📋 Logger
📊 分级日志
🎨 彩色输出
🔧 自定义处理器"] + end + end + + %% 连接关系 - 使用更丰富的箭头样式 + Core -.->|初始化| WorldMgr + Core -.->|注册| Registry + Core -.->|分配| Pool + Core -.->|管理| PoolMgr + Core -.->|事件| EventBus + + WorldMgr ==>|调度| World1 + WorldMgr ==>|调度| World2 + World1 -.->|管理| GlobalSys + + World1 ==>|包含| Scene1 + World1 ==>|包含| Scene2 + World2 ==>|包含| Scene3 + + Scene1 -->|使用| EntityMgr + Scene2 -->|使用| EntityMgr + Scene3 -->|使用| EntityMgr + + EntityMgr -->|管理| Entities + Entities -->|附加| Components + + Scene1 -->|存储| Storage + Scene2 -->|存储| Storage + Scene3 -->|存储| Storage + Storage -->|管理| StorageMgr + + Scene1 -->|调度| EntitySystems + Scene2 -->|调度| EntitySystems + Scene3 -->|调度| EntitySystems + EntitySystems -->|处理| Processors + + EntitySystems -->|查询| Matcher + Matcher -->|缓存| QuerySys + QuerySys -->|优化| Archetype + + Core -.->|调试| Debug + Core -.->|监控| Perf + Core -.->|日志| Logger + + %% 样式定义 - 使用Mermaid支持的语法 + classDef coreStyle fill:#E3F2FD,stroke:#1976D2,stroke-width:3px,color:#0D47A1 + classDef worldStyle fill:#F3E5F5,stroke:#7B1FA2,stroke-width:3px,color:#4A148C + classDef sceneStyle fill:#FFF3E0,stroke:#F57C00,stroke-width:3px,color:#E65100 + classDef entityStyle fill:#E8F5E8,stroke:#388E3C,stroke-width:3px,color:#1B5E20 + classDef systemStyle fill:#FCE4EC,stroke:#C2185B,stroke-width:3px,color:#880E4F + classDef queryStyle fill:#E0F2F1,stroke:#00695C,stroke-width:3px,color:#004D40 + classDef debugStyle fill:#FFF8E1,stroke:#F9A825,stroke-width:3px,color:#FF8F00 + + class Core,Registry,Pool,PoolMgr,EventBus coreStyle + class WorldMgr,World1,World2,GlobalSys worldStyle + class Scene1,Scene2,Scene3 sceneStyle + class EntityMgr,Entities,Storage,StorageMgr,Components entityStyle + class EntitySystems,Processors systemStyle + class Matcher,QuerySys,Archetype queryStyle + class Debug,Perf,Logger debugStyle +``` + +### 核心概念 + +| 概念 | 职责 | 特点 | +|------|------|------| +| **Entity** | 游戏对象唯一标识 | 轻量级容器,无业务逻辑 | +| **Component** | 纯数据结构 | 描述实体属性,支持SoA优化 | +| **System** | 业务逻辑处理 | 操作组件数据,可热插拔 | +| **Scene** | 实体和系统容器 | 独立的游戏场景 | +| **World** | Scene和全局系统容器 | 支持跨Scene的全局逻辑 | +| **WorldManager** | 多World管理 | 统一调度和资源管理 | ## 特性 @@ -201,11 +339,20 @@ class GameSystem { ### SoA 存储优化 -
- SoA vs AoS 数据结构对比 -
+针对大规模实体处理的内存布局优化: -用于大规模实体处理: +| 存储方式 | 内存布局 | 适用场景 | 性能特点 | +|----------|----------|----------|----------| +| **AoS** (Array of Structures) | `[{x,y,z}, {x,y,z}, {x,y,z}]` | 通用场景 | 访问灵活,缓存效率一般 | +| **SoA** (Structure of Arrays) | `{x:[1,2,3], y:[4,5,6], z:[7,8,9]}` | 批量处理 | SIMD优化,缓存友好 | + +**SoA 优势:** +- 🚀 提升 2-4x 批量处理性能 +- 💾 更好的CPU缓存利用率 +- 🔧 支持SIMD向量化操作 +- ⚡ 减少内存访问跳跃 + +用法示例: ```typescript import { EnableSoA, Float32, Int32 } from '@esengine/ecs-framework'; diff --git a/assets/svg/ecs-architecture.svg b/assets/svg/ecs-architecture.svg deleted file mode 100644 index 09c8aef6..00000000 --- a/assets/svg/ecs-architecture.svg +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ECS Framework 完整架构 - - - - - - - 1 - - - - Core 框架核心 - - - ComponentRegistry • IdentifierPool - - - EventBus • ConfigManager - - - - - - - - - 2 - - - - Scene 场景管理器 - - - EntityList - - - ComponentStorageManager - - - EntityProcessors - - - 实体管理 • 组件存储 • 系统调度 • 查询引擎 - - - - - - - - - 3 - - - - Entity 实体系统 - - - - - - EntityList • 高性能实体集合管理 - - - - - Player - - - Enemy - - - Bullet - - - • 组件容器 - • 层次结构 - • 生命周期管理 - • 状态管理 - - - 唯一标识 • 无数据逻辑载体 - - - - - - - - - 4 - - - - Component 组件系统 - - - - - - ComponentStorageManager • SoA/AoS 双模式 - - - ComponentPool • DirtyTrackingSystem - - - - - Position {x,y,z} - - - Velocity {dx,dy,dz} - - - Health {hp,max} - - - Render {sprite} - - - • @EnableSoA 优化 - • 对象池管理 - • 序列化支持 - • 类型安全 - - - 纯数据结构 • 描述实体属性 - - - - - - - - - 5 - - - - System 系统层 - - - - - - EntityProcessors • 系统调度管理 - - - - - MovementSystem - - - RenderSystem - - - PhysicsSystem - - - AISystem - - - • Matcher 查询 - • 性能监控 - • 优先级调度 - • 热插拔 - - - 业务逻辑处理 • 操作组件数据 - - - - - - - - - 6 - - - - Query 查询系统 - - - - - Matcher - withAll() - withAny() - withNone() - - - QuerySystem - 查询缓存 - 批量优化 - 实时更新 - - - ArchetypeSystem - 组件组合分组 - 原型级缓存 - BitSet优化查询 - - - - - - - - 7 - - - - Event 事件系统 - - - - - TypeSafeEventSystem - 同步/异步 - 优先级排序 - 批处理机制 - - - Performance Monitor - 性能统计 - 阈值告警 - 实时监控 - - - Debug Manager - WebSocket通信 - 实时调试数据 - 内存快照 - - - - - - - - 初始化 - - - - - 管理实体 - - - - 存储组件 - - - - 调度系统 - - - - - 附加组件 - - - - - 处理数据 - - - - - 查询支持 - - - - 事件通知 - - - - - 匹配结果 - - - - 修改组件数据 - - - 触发事件 - - - - - - 🔄 ECS 框架7步工作流程 - - - - - ①初始化 - Core.create() - - ②场景管理 - Scene.initialize() - - ③创建实体 - Entity.create() - - ④附加组件 - addComponent() - - ⑤系统处理 - System.process() - - ⑥查询匹配 - Matcher.query() - - ⑦事件通知 - Event.emit() - - - - 每帧循环:查询实体 → 匹配组件 → 执行系统逻辑 → 修改数据 → 触发事件 → 性能监控 - - - 💡 圆形数字显示执行顺序 • 不同颜色连线代表不同数据流 - - - \ No newline at end of file diff --git a/assets/svg/soa-vs-aos.svg b/assets/svg/soa-vs-aos.svg deleted file mode 100644 index 9ffd2f57..00000000 --- a/assets/svg/soa-vs-aos.svg +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoA vs AoS 数据结构对比 - - - - Structure of Arrays vs Array of Structures - - - - - - - - - AoS - Array of Structures - - - 结构体数组(传统方式) - - - - - - - - Entity[0] - - - x - - - y - - - hp - - - id - - - - Entity[1] - - - - - - - - - Entity[2] - - - - - - - - - 内存访问:跳跃式访问,缓存不友好 - - - - - 处理位置时需跳过其他数据 - - - - - - - - - - SoA - Structure of Arrays - - - 数组结构(ECS优化方式) - - - - - - - Position[]: - - - - - 连续存储 - - - Velocity[]: - - - - - - - Health[]: - - - - - - - EntityID[]: - - - - - - - - 内存访问:连续访问,缓存友好 - - - - - 处理位置时连续访问相同类型数据 - - - - - - - - - 性能对比分析 - - - - - - 缓存命中率: - - - - AoS: - - - 35% - - - SoA: - - - 85% - - - - - - 批量处理速度: - - - - AoS: - - - 2.3x slower - - - SoA: - - - baseline - - - - - - 适用场景: - - - - ✅ SoA: 大量实体的同类型操作 - - - ✅ SoA: 游戏循环中的系统处理 - - - ❌ AoS: 混合操作、少量实体 - - - ❌ AoS: 随机访问模式 - - - - - - - - - 🚀 本框架采用 SoA 优化存储,@EnableSoA 装饰器自动转换,性能提升 2-3 倍 - - - 支持热切换存储方式,开发时使用 AoS 调试,生产环境自动启用 SoA 优化 - - - \ No newline at end of file diff --git a/docs/core-concepts.md b/docs/core-concepts.md index 90d320dc..20e8aee5 100644 --- a/docs/core-concepts.md +++ b/docs/core-concepts.md @@ -14,7 +14,7 @@ ECS 架构将传统的面向对象设计分解为三个核心部分: ## Core(核心) -Core 是框架的核心管理类,负责游戏的生命周期管理。 +Core 是框架的核心管理类,负责游戏的生命周期管理。框架采用融合设计,既支持传统的单Scene模式(向后兼容),也支持高级的多World/多Scene架构。 ### 创建和配置 @@ -47,6 +47,40 @@ const core2 = Core.create(false); // 发布模式 const core3 = Core.create(); // 默认调试模式 ``` +### 场景管理API + +```typescript +// 单Scene模式(默认,向后兼容) +const scene = new Scene(); +Core.setScene(scene); // 设置场景 +const currentScene = Core.getScene(); // 获取当前场景 + +// 多World模式(高级功能) +Core.enableWorldManager(); // 启用World管理器 +const worldManager = Core.getWorldManager(); + +// 创建World +const gameWorld = worldManager.createWorld('GameWorld', { + name: 'GameWorld', + maxScenes: 10, + autoCleanup: true +}); + +// 在World中管理Scene +const battleScene = gameWorld.createScene('battle', new Scene()); +const uiScene = gameWorld.createScene('ui', new Scene()); + +gameWorld.setSceneActive('battle', true); +gameWorld.setSceneActive('ui', true); + +// 启动World +gameWorld.start(); + +// 获取World统计 +const worldStats = gameWorld.getStats(); +console.log('World状态:', worldStats); +``` + ### 事件系统 ```typescript @@ -404,6 +438,235 @@ console.log("实体数量:", stats.entityCount); console.log("系统数量:", stats.processorCount); ``` +## World(世界) + +World是Scene的容器,提供了更高级的场景管理功能。每个World可以包含多个Scene,适用于复杂的游戏架构。 + +### World基本使用 + +```typescript +import { World, Scene, IWorldConfig } from '@esengine/ecs-framework'; + +// 创建World配置 +const worldConfig: IWorldConfig = { + name: 'GameWorld', + debug: true, + maxScenes: 10, + autoCleanup: true +}; + +// 创建World +const gameWorld = new World(worldConfig); + +// 在World中创建Scene +const battleScene = gameWorld.createScene('battle', new Scene()); +const uiScene = gameWorld.createScene('ui', new Scene()); +const menuScene = gameWorld.createScene('menu'); + +// 激活Scene +gameWorld.setSceneActive('battle', true); +gameWorld.setSceneActive('ui', true); + +// 启动World +gameWorld.start(); +``` + +### World生命周期管理 + +```typescript +// 启动World(启动所有全局System) +gameWorld.start(); + +// 检查World状态 +if (gameWorld.isActive) { + console.log('World正在运行'); +} + +// 停止World(停止所有Scene和全局System) +gameWorld.stop(); + +// 销毁World(清理所有资源) +gameWorld.destroy(); +``` + +### Scene管理 + +```typescript +// 获取Scene +const battleScene = gameWorld.getScene('battle'); + +// 检查Scene是否激活 +if (gameWorld.isSceneActive('battle')) { + console.log('战斗场景正在运行'); +} + +// 移除Scene +gameWorld.removeScene('menu'); + +// 获取所有Scene ID +const sceneIds = gameWorld.getSceneIds(); +console.log('所有Scene:', sceneIds); + +// 获取活跃Scene数量 +const activeCount = gameWorld.getActiveSceneCount(); +console.log('活跃Scene数量:', activeCount); +``` + +### 全局System管理 + +World支持全局System,这些System会在所有Scene之前执行,适用于跨Scene的业务逻辑: + +```typescript +import { IGlobalSystem } from '@esengine/ecs-framework'; + +// 全局网络同步系统 +class GlobalNetworkSystem implements IGlobalSystem { + public readonly name = 'GlobalNetworkSystem'; + + public initialize(): void { + // 初始化网络连接 + console.log('网络系统初始化'); + } + + public update(): void { + // 处理全局网络同步逻辑 + // 注意:全局系统处理的是World级别的逻辑,不直接处理实体 + // 如需处理特定实体,请在Scene中使用EntitySystem + this.syncGlobalNetworkState(); + } + + public reset(): void { + // 重置系统状态 + } + + public destroy(): void { + // 清理网络连接 + console.log('网络系统销毁'); + } + + private syncGlobalNetworkState(): void { + // 全局网络状态同步 + } +} + +// 添加全局System +const networkSystem = gameWorld.addGlobalSystem(new GlobalNetworkSystem()); + +// 获取全局System +const existingSystem = gameWorld.getGlobalSystem(GlobalNetworkSystem); + +// 移除全局System +gameWorld.removeGlobalSystem(networkSystem); +``` + +> **注意**:全局System适用于World级别的业务逻辑(如网络管理、资源管理、全局状态管理等)。如果需要处理具体的实体和组件,请在Scene中使用EntitySystem。 + +### World状态监控 + +```typescript +// 获取World状态 +const status = gameWorld.getStatus(); +console.log('World状态:', { + name: status.name, + isActive: status.isActive, + sceneCount: status.sceneCount, + activeSceneCount: status.activeSceneCount, + globalSystemCount: status.globalSystemCount, + scenes: status.scenes +}); + +// 获取World统计信息 +const stats = gameWorld.getStats(); +console.log('World统计:', { + totalEntities: stats.totalEntities, + totalSystems: stats.totalSystems, + memoryUsage: stats.memoryUsage +}); +``` + +## WorldManager(世界管理器) + +WorldManager是单例模式的World管理器,负责管理多个World实例。 + +### WorldManager基本使用 + +```typescript +import { WorldManager, IWorldManagerConfig } from '@esengine/ecs-framework'; + +// 获取WorldManager实例 +const worldManager = WorldManager.getInstance({ + maxWorlds: 50, + autoCleanup: true, + debug: true +}); + +// 或者通过Core获取 +Core.enableWorldManager(); +const worldManager2 = Core.getWorldManager(); +``` + +### World管理 + +```typescript +// 创建World +const gameWorld = worldManager.createWorld('GameRoom_001', { + name: 'GameRoom_001', + maxScenes: 5, + autoCleanup: true +}); + +// 获取World +const existingWorld = worldManager.getWorld('GameRoom_001'); + +// 检查World是否存在 +if (worldManager.getWorld('GameRoom_001')) { + console.log('World存在'); +} + +// 销毁World +worldManager.removeWorld('GameRoom_001'); + +// 获取所有World ID +const worldIds = worldManager.getWorldIds(); +console.log('所有World ID:', worldIds); + +// 获取活跃World +const activeWorlds = worldManager.getActiveWorlds(); +console.log('活跃World数量:', activeWorlds.length); +``` + +### WorldManager统计和监控 + +```typescript +// 获取WorldManager状态 +const managerStatus = worldManager.getStatus(); +console.log('WorldManager状态:', { + totalWorlds: managerStatus.totalWorlds, + activeWorlds: managerStatus.activeWorlds, + maxWorlds: managerStatus.maxWorlds, + memoryUsage: managerStatus.memoryUsage +}); + +// 获取所有World的统计 +const allStats = worldManager.getAllWorldStats(); +allStats.forEach(stat => { + console.log(`World ${stat.worldName}:`, stat); +}); + +// 清理空闲World +const cleanedCount = worldManager.cleanup(); +console.log(`清理了 ${cleanedCount} 个空闲World`); +``` + +### 使用场景 + +World和WorldManager适用于: +- **游戏服务器**:每个房间一个独立World +- **复杂客户端**:按功能分层管理Scene(游戏层、UI层、特效层) +- **并发世界**:需要同时运行多个独立游戏世界的场景 + +> **完整示例和最佳实践**:查看 [场景管理完整指南](scene-management-guide.md#world多场景管理) 了解详细的实现方案和架构设计 + ## System(系统) 系统处理实体集合,实现游戏的核心逻辑。 @@ -646,11 +909,18 @@ const movingEntities = scene.querySystem.queryAll(PositionComponent, VelocityCom ECS Framework 提供了完整的实体组件系统架构: -- **Core** 管理游戏生命周期和全局功能 +- **Core** 管理游戏生命周期和全局功能,支持单Scene和多World模式 - **Entity** 作为游戏对象的基础容器 - **Component** 实现具体的功能模块,支持对象池优化 - **System** 处理游戏逻辑 - **Scene** 管理游戏世界状态,支持批量操作 +- **World** 高级场景容器,支持多Scene管理和全局System +- **WorldManager** 管理多个World实例,适用于复杂架构 - **高级优化** 位掩码优化器、组件对象池、批量操作等 -通过合理使用这些核心概念和优化功能,可以构建出高性能、结构清晰、易于维护的游戏代码。 \ No newline at end of file +### 架构选择指南 + +- **单Scene模式**:适合简单游戏、单机游戏、原型开发 +- **多World模式**:适合多人游戏服务器、复杂应用、需要场景隔离的项目 + +框架采用融合设计,确保向后兼容性的同时提供强大的扩展能力。通过合理使用这些核心概念和优化功能,可以构建出高性能、结构清晰、易于维护的游戏代码。 \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md index 2bb96aea..08a1f2cc 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -33,7 +33,7 @@ Core.update(deltaTime); ### 基础配置 -ECS框架提供了灵活的配置选项来满足不同项目需求: +ECS框架提供了灵活的配置选项来满足不同项目需求。框架采用融合设计,既支持传统的单Scene模式(向后兼容),也支持高级的多World/多Scene架构: ```typescript import { Core, ICoreConfig } from '@esengine/ecs-framework'; @@ -65,6 +65,31 @@ const config: ICoreConfig = { const core = Core.create(config); ``` +### 架构说明 + +ECS框架支持两种使用模式: + +1. **单Scene模式(默认,向后兼容)**: + ```typescript + // 传统用法,无需任何修改 + const scene = new Scene(); + Core.setScene(scene); + ``` + +2. **多World模式(高级功能)**: + ```typescript + // 启用World管理器,支持多World/多Scene架构 + Core.enableWorldManager(); + const roomWorld = Core.getWorldManager().createWorld('Room_001'); + const battleScene = roomWorld.createScene('battle'); + ``` + +**使用场景:** +- **单Scene模式**:适合简单游戏、单机游戏、原型开发 +- **多World模式**:适合多人游戏服务器、复杂应用、需要场景隔离的项目 + +> **详细了解World系统**:查看 [场景管理完整指南](scene-management-guide.md) 获取完整的多World架构示例和最佳实践 + ### 调试功能 ECS框架内置了强大的调试功能,支持运行时监控和远程调试: @@ -324,7 +349,7 @@ class CocosRenderSystem extends EntitySystem { ### Node.js后端 ```typescript -import { Core, Scene, EntityManager, EntitySystem, Time } from '@esengine/ecs-framework'; +import { Core, Scene, EntityManager, EntitySystem, Time, World } from '@esengine/ecs-framework'; class ServerGameManager { private scene: Scene; @@ -338,6 +363,7 @@ class ServerGameManager { Core.create(true); // 启用调试模式 // 完整配置示例: Core.create({ debug: true, enableEntitySystems: true, debugConfig: {...} }) + // 单Scene模式(简单场景) this.scene = new Scene(); this.scene.name = "ServerScene"; Core.setScene(this.scene); @@ -418,6 +444,8 @@ const server = new ServerGameManager(); server.start(); ``` +> **多房间游戏服务器示例**:查看 [场景管理完整指南](scene-management-guide.md#world多场景管理) 了解如何使用多World架构实现复杂的多房间游戏服务器 + ### 原生浏览器 ```typescript diff --git a/docs/scene-management-guide.md b/docs/scene-management-guide.md index a8a3d1fc..5b52091a 100644 --- a/docs/scene-management-guide.md +++ b/docs/scene-management-guide.md @@ -1,6 +1,6 @@ # 场景管理完整指南 -场景(Scene)是ECS框架中管理游戏对象和系统的核心容器。本指南将详细介绍如何有效地使用场景来构建和管理你的游戏。 +场景(Scene)是ECS框架中管理游戏对象和系统的核心容器。框架采用融合设计,既支持传统的单Scene模式(向后兼容),也支持高级的多World/多Scene架构。本指南将详细介绍如何有效地使用场景来构建和管理你的游戏。 ## 场景基础概念 @@ -25,6 +25,47 @@ Core.setScene(gameScene); > **注意**: `Core.scene = ` 设置方式已被标记为废弃,推荐使用 `Core.setScene()` 方法。新方法提供更好的类型安全性和可预测的激活时序。 +### 架构选择指南 + +ECS框架提供两种使用模式: + +#### 1. 单Scene模式(默认,向后兼容) + +```typescript +// 传统用法,无需任何修改 +const scene = new Scene(); +Core.setScene(scene); +``` + +**适用场景:** +- 简单游戏、单机游戏 +- 原型开发、快速验证 +- 学习ECS架构 +- 不需要复杂场景管理的项目 + +#### 2. 多World模式(高级功能) + +```typescript +// 启用World管理器 +Core.enableWorldManager(); +const worldManager = Core.getWorldManager(); + +// 创建多个World,每个World可包含多个Scene +const roomWorld = worldManager.createWorld('Room_001'); +const battleScene = roomWorld.createScene('battle'); +const uiScene = roomWorld.createScene('ui'); + +roomWorld.start(); +roomWorld.setSceneActive('battle', true); +roomWorld.setSceneActive('ui', true); +``` + +**适用场景:** +- 多人游戏服务器(每个房间一个World) +- 复杂应用架构(需要场景隔离) +- 需要并发处理多个游戏世界 +- 高级场景管理需求 + ### 场景的生命周期 ```typescript @@ -214,9 +255,374 @@ class GameScene extends Scene { } ``` -## 场景切换和管理 +## World多场景管理 -### 1. 场景管理器 +### 1. World基础使用 + +对于需要复杂场景管理的项目,可以使用World系统: + +```typescript +import { Core, World, Scene, WorldManager, IGlobalSystem } from '@esengine/ecs-framework'; + +// 定义全局系统(跨Scene的业务逻辑) +class NetworkSyncSystem implements IGlobalSystem { + public readonly name = 'NetworkSyncSystem'; + + public initialize(): void { + console.log('网络同步系统初始化'); + } + + public update(): void { + // 同步所有Scene的网络状态 + this.syncNetworkData(); + } + + public reset(): void { + // 重置网络连接 + } + + public destroy(): void { + console.log('网络同步系统销毁'); + } + + private syncNetworkData(): void { + // 网络数据同步逻辑 + } +} + +class PlayerManagementSystem implements IGlobalSystem { + public readonly name = 'PlayerManagementSystem'; + + public initialize(): void { + console.log('玩家管理系统初始化'); + } + + public update(): void { + // 管理跨Scene的玩家数据 + this.managePlayerStates(); + } + + public reset(): void { + // 重置玩家状态 + } + + public destroy(): void { + console.log('玩家管理系统销毁'); + } + + private managePlayerStates(): void { + // 玩家状态管理逻辑 + } +} + +// 启用World管理功能 +Core.enableWorldManager(); +const worldManager = Core.getWorldManager(); + +// 创建游戏房间World +const roomWorld = worldManager.createWorld('GameRoom_001', { + name: 'GameRoom_001', + maxScenes: 5, + autoCleanup: true, + debug: true +}); + +// 在World中创建多个Scene +const gameScene = roomWorld.createScene('game', new GameScene()); +const uiScene = roomWorld.createScene('ui', new UIScene()); +const backgroundScene = roomWorld.createScene('background', new BackgroundScene()); + +// 添加全局系统(跨Scene的系统) +roomWorld.addGlobalSystem(new NetworkSyncSystem()); +roomWorld.addGlobalSystem(new PlayerManagementSystem()); + +// 启动World并激活Scene +roomWorld.start(); +roomWorld.setSceneActive('game', true); +roomWorld.setSceneActive('ui', true); +roomWorld.setSceneActive('background', true); +``` + +### 2. 多房间游戏服务器示例 + +```typescript +// 房间管理系统 +class RoomManagementSystem implements IGlobalSystem { + public readonly name = 'RoomManagementSystem'; + private roomId: string; + + constructor(roomId: string) { + this.roomId = roomId; + } + + public initialize(): void { + console.log(`房间管理系统初始化: ${this.roomId}`); + } + + public update(): void { + // 管理房间状态、玩家进出等 + this.manageRoomState(); + } + + public reset(): void { + // 重置房间状态 + } + + public destroy(): void { + console.log(`房间管理系统销毁: ${this.roomId}`); + } + + private manageRoomState(): void { + // 房间状态管理逻辑 + } +} + +// 玩家同步系统 +class PlayerSyncSystem implements IGlobalSystem { + public readonly name = 'PlayerSyncSystem'; + + public initialize(): void { + console.log('玩家同步系统初始化'); + } + + public update(): void { + // 同步房间内所有玩家的状态 + this.syncPlayerData(); + } + + public reset(): void { + // 重置同步状态 + } + + public destroy(): void { + console.log('玩家同步系统销毁'); + } + + private syncPlayerData(): void { + // 玩家数据同步逻辑 + } +} + +class MultiRoomGameServer { + private worldManager: WorldManager; + private rooms: Map = new Map(); + + constructor() { + Core.create({ debug: false }); + Core.enableWorldManager(); + this.worldManager = Core.getWorldManager(); + } + + // 创建游戏房间 + createRoom(roomId: string): World { + const roomWorld = this.worldManager.createWorld(`Room_${roomId}`, { + name: `GameRoom_${roomId}`, + maxScenes: 3, + autoCleanup: true + }); + + // 房间内的Scene设置 + const gameScene = roomWorld.createScene('game', new ServerGameScene()); + const lobbyScene = roomWorld.createScene('lobby', new LobbyScene()); + + // 设置房间级的全局系统 + roomWorld.addGlobalSystem(new RoomManagementSystem(roomId)); + roomWorld.addGlobalSystem(new PlayerSyncSystem()); + + // 启动房间 + roomWorld.start(); + roomWorld.setSceneActive('lobby', true); // 默认激活大厅 + + this.rooms.set(roomId, roomWorld); + console.log(`创建房间: ${roomId}`); + + return roomWorld; + } + + // 开始游戏 + startGame(roomId: string): boolean { + const roomWorld = this.rooms.get(roomId); + if (!roomWorld) return false; + + // 停用大厅,激活游戏Scene + roomWorld.setSceneActive('lobby', false); + roomWorld.setSceneActive('game', true); + + console.log(`房间 ${roomId} 开始游戏`); + return true; + } + + // 销毁房间 + destroyRoom(roomId: string): boolean { + const roomWorld = this.rooms.get(roomId); + if (!roomWorld) return false; + + roomWorld.destroy(); + this.rooms.delete(roomId); + console.log(`销毁房间: ${roomId}`); + return true; + } + + // 获取服务器状态 + getServerStats() { + return { + totalRooms: this.rooms.size, + activeWorlds: this.worldManager.getActiveWorlds().length, + rooms: Array.from(this.rooms.keys()).map(roomId => ({ + roomId, + world: this.rooms.get(roomId)?.getStatus() + })) + }; + } + + // 游戏循环 + start(): void { + const gameLoop = () => { + const deltaTime = 1000 / 60; // 60 TPS + Core.update(deltaTime / 1000); + setTimeout(gameLoop, deltaTime); + }; + gameLoop(); + } +} + +// 使用示例 +const gameServer = new MultiRoomGameServer(); +gameServer.start(); + +// 创建房间 +const room1 = gameServer.createRoom('room_001'); +const room2 = gameServer.createRoom('room_002'); + +// 开始游戏 +setTimeout(() => { + gameServer.startGame('room_001'); +}, 5000); + +console.log('服务器状态:', gameServer.getServerStats()); +``` + +### 3. 客户端多Scene管理示例 + +```typescript +class GameClient { + private worldManager: WorldManager; + private mainWorld: World; + + constructor() { + Core.create({ debug: true }); + Core.enableWorldManager(); + this.worldManager = Core.getWorldManager(); + + this.setupGameWorld(); + } + + private setupGameWorld(): void { + // 创建主游戏世界 + this.mainWorld = this.worldManager.createWorld('MainWorld', { + name: 'ClientWorld', + maxScenes: 10, + autoCleanup: false // 客户端通常不需要自动清理 + }); + + // 创建不同层级的Scene + this.createGameplayScenes(); + this.createUIScenes(); + this.createEffectScenes(); + + // 启动世界 + this.mainWorld.start(); + this.activateDefaultScenes(); + } + + private createGameplayScenes(): void { + // 游戏主场景 + const gameScene = this.mainWorld.createScene('gameplay', new GameplayScene()); + + // 背景场景 + const backgroundScene = this.mainWorld.createScene('background', new BackgroundScene()); + + // 特效场景 + const effectsScene = this.mainWorld.createScene('effects', new EffectsScene()); + } + + private createUIScenes(): void { + // 主UI场景 + const mainUIScene = this.mainWorld.createScene('mainUI', new MainUIScene()); + + // 菜单场景 + const menuScene = this.mainWorld.createScene('menu', new MenuScene()); + + // 设置场景 + const settingsScene = this.mainWorld.createScene('settings', new SettingsScene()); + } + + private createEffectScenes(): void { + // 粒子效果场景 + const particleScene = this.mainWorld.createScene('particles', new ParticleScene()); + + // 音效场景 + const audioScene = this.mainWorld.createScene('audio', new AudioScene()); + } + + private activateDefaultScenes(): void { + // 激活基础Scene + this.mainWorld.setSceneActive('background', true); + this.mainWorld.setSceneActive('gameplay', true); + this.mainWorld.setSceneActive('mainUI', true); + this.mainWorld.setSceneActive('particles', true); + this.mainWorld.setSceneActive('audio', true); + + // 菜单和设置默认不激活 + this.mainWorld.setSceneActive('menu', false); + this.mainWorld.setSceneActive('settings', false); + } + + // 切换到菜单 + showMenu(): void { + this.mainWorld.setSceneActive('gameplay', false); + this.mainWorld.setSceneActive('menu', true); + } + + // 切换回游戏 + hideMenu(): void { + this.mainWorld.setSceneActive('menu', false); + this.mainWorld.setSceneActive('gameplay', true); + } + + // 显示设置 + showSettings(): void { + this.mainWorld.setSceneActive('settings', true); + } + + // 隐藏设置 + hideSettings(): void { + this.mainWorld.setSceneActive('settings', false); + } + + // 获取World状态 + getWorldStatus() { + return this.mainWorld.getStatus(); + } +} + +// 使用示例 +const gameClient = new GameClient(); + +// 显示菜单 +gameClient.showMenu(); + +// 5秒后返回游戏 +setTimeout(() => { + gameClient.hideMenu(); +}, 5000); + +console.log('客户端World状态:', gameClient.getWorldStatus()); +``` + +## 传统场景切换和管理 + +### 1. 单Scene模式场景管理器 > **注意:** 以下的 SceneManager、TransitionManager 等是自定义的场景管理类示例,不是ECS框架提供的内置API。你可以基于这些示例实现自己的场景管理系统。 @@ -731,6 +1137,47 @@ A: ### Q: 多个场景可以同时存在吗? -A: 框架同时只支持一个活跃场景,但可以通过场景栈实现多场景管理(如暂停菜单)。 +A: +- **单Scene模式**:框架同时只支持一个活跃场景,但可以通过场景栈实现多场景管理(如暂停菜单) +- **多World模式**:每个World可以包含多个同时激活的Scene,支持复杂的多场景架构 -通过合理使用场景系统,你可以构建出结构清晰、性能优良的游戏架构! \ No newline at end of file +### Q: 什么时候使用World系统? + +A: +- 多人游戏服务器(每个房间独立管理) +- 需要并发运行多个独立游戏世界 +- 复杂的客户端架构(游戏层、UI层、特效层分离) +- 需要跨Scene的全局系统支持 + +### Q: World和Scene的性能影响? + +A: +- **单Scene模式**:最佳性能,适合简单项目 +- **多World模式**:每个World独立更新,合理使用不会显著影响性能 +- **建议**:根据项目复杂度选择合适的架构 + +### Q: 如何从单Scene迁移到多World? + +A: +```typescript +// 原始单Scene代码 +const scene = new Scene(); +Core.setScene(scene); + +// 迁移到World模式(可选) +Core.enableWorldManager(); +const world = Core.getWorldManager().createWorld('MainWorld'); +const scene = world.createScene('main', new Scene()); +world.start(); +world.setSceneActive('main', true); +``` + +### Q: World系统的最佳实践? + +A: +1. **服务器端**:每个游戏房间使用独立World +2. **客户端**:按功能层级划分Scene(游戏、UI、特效) +3. **全局系统**:将跨Scene的逻辑放在World的全局System中 +4. **资源管理**:使用World的autoCleanup功能自动清理空闲资源 + +通过合理选择单Scene或多World架构,你可以构建出结构清晰、性能优良的游戏架构! \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3bb20d62..4d6e9750 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11537,7 +11537,7 @@ }, "packages/core": { "name": "@esengine/ecs-framework", - "version": "2.1.44", + "version": "2.1.45", "license": "MIT", "devDependencies": { "@rollup/plugin-commonjs": "^28.0.3", diff --git a/packages/core/package.json b/packages/core/package.json index be0ccb28..54ceeceb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/ecs-framework", - "version": "2.1.44", + "version": "2.1.45", "description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架", "main": "bin/index.js", "types": "bin/index.d.ts", diff --git a/packages/core/src/Core.ts b/packages/core/src/Core.ts index 6e98098f..2d94d5f2 100644 --- a/packages/core/src/Core.ts +++ b/packages/core/src/Core.ts @@ -6,8 +6,8 @@ import { Time } from './Utils/Time'; import { PerformanceMonitor } from './Utils/PerformanceMonitor'; import { PoolManager } from './Utils/Pool/PoolManager'; import { ECSFluentAPI, createECSAPI } from './ECS/Core/FluentAPI'; -import { Scene } from './ECS/Scene'; import { IScene } from './ECS/IScene'; +import { WorldManager } from './ECS/WorldManager'; import { DebugManager } from './Utils/Debug'; import { ICoreConfig, IECSDebugConfig } from './Types'; import { BigIntFactory, EnvironmentInfo } from './ECS/Utils/BigIntCompatibility'; @@ -46,6 +46,20 @@ export class Core { * 当设置为true时,游戏循环将暂停执行。 */ public static paused = false; + + /** + * 默认World ID + * + * 用于单Scene模式的默认World标识 + */ + private static readonly DEFAULT_WORLD_ID = '__default__'; + + /** + * 默认Scene ID + * + * 用于单Scene模式的默认Scene标识 + */ + private static readonly DEFAULT_SCENE_ID = '__main__'; /** * 全局核心实例 @@ -71,12 +85,6 @@ export class Core { */ public readonly debug: boolean; - /** - * 待切换的场景 - * - * 存储下一帧要切换到的场景实例。 - */ - public _nextScene: IScene | null = null; /** * 全局管理器集合 @@ -113,10 +121,6 @@ export class Core { */ public _ecsAPI?: ECSFluentAPI; - /** - * 当前活动场景 - */ - public _scene?: IScene; /** * 调试管理器 @@ -125,6 +129,13 @@ export class Core { */ public _debugManager?: DebugManager; + /** + * World管理器 + * + * 管理多个World实例,支持多房间/多世界架构。 + */ + public _worldManager?: WorldManager; + /** * Core配置 */ @@ -194,82 +205,63 @@ export class Core { } /** - * 获取当前活动的场景 + * 获取当前活动的场景(属性访问器) * * @returns 当前场景实例,如果没有则返回null */ public static get scene(): IScene | null { - if (!this._instance) - return null; - return this._instance._scene || null; + return this.getScene(); } /** - * 设置当前场景(已废弃) + * 获取当前活动的场景(方法调用) * - * @deprecated 请使用 Core.setScene() 方法代替。scene setter 可能导致场景延迟激活的时序问题, - * 而 setScene() 提供更好的类型安全性和可预测的激活时序。 - * - * 迁移示例: - * ```typescript - * // 旧方式(已废弃) - * Core.scene = myScene; - * - * // 新方式(推荐) - * Core.setScene(myScene); - * ``` - * - * 如果当前没有场景,会立即切换;否则会在下一帧切换。 - * - * @param value - 场景实例 + * @returns 当前场景实例,如果没有则返回null */ - public static set scene(value: IScene | null) { - if (!value) return; - - if (this._instance._scene == null) { - this._instance.setSceneInternal(value); - } else { - this._instance._nextScene = value; + public static getScene(): T | null { + if (!this._instance) { + return null; } + + // 确保默认World存在 + this._instance.ensureDefaultWorld(); + + const defaultWorld = this._instance._worldManager!.getWorld(this.DEFAULT_WORLD_ID); + return defaultWorld?.getScene(this.DEFAULT_SCENE_ID) as T || null; } + /** - * 类型安全的场景设置方法(推荐) - * - * 这是设置场景的推荐方法,提供更好的类型安全性和可预测的激活时序。 - * 相比于 scene setter,此方法能确保场景正确初始化和激活。 - * - * 如果当前没有场景,会立即切换;否则会在下一帧切换。 + * 设置当前场景 * * @param scene - 要设置的场景实例 * @returns 设置的场景实例,便于链式调用 - * - * @example - * ```typescript - * const myScene = new MyScene(); - * Core.setScene(myScene); - * - * // 链式调用 - * const scene = Core.setScene(new MyScene()).addSystem(new MySystem()); - * ``` */ public static setScene(scene: T): T { - if (this._instance._scene == null) { - this._instance.setSceneInternal(scene); - } else { - this._instance._nextScene = scene; + if (!this._instance) { + throw new Error("Core实例未创建,请先调用Core.create()"); } + + // 确保默认World存在 + this._instance.ensureDefaultWorld(); + + const defaultWorld = this._instance._worldManager!.getWorld(this.DEFAULT_WORLD_ID)!; + + // 移除旧的主Scene(如果存在) + if (defaultWorld.getScene(this.DEFAULT_SCENE_ID)) { + defaultWorld.removeScene(this.DEFAULT_SCENE_ID); + } + + // 添加新Scene到默认World + defaultWorld.createScene(this.DEFAULT_SCENE_ID, scene); + defaultWorld.setSceneActive(this.DEFAULT_SCENE_ID, true); + + // 触发场景切换回调 + this._instance.onSceneChanged(); + return scene; } - /** - * 类型安全的场景获取方法 - * - * @returns 当前场景实例 - */ - public static getScene(): T | null { - return this._instance?._scene as T || null; - } /** * 创建Core实例 @@ -466,15 +458,61 @@ export class Core { } /** - * 内部场景设置方法 + * 获取WorldManager实例 * - * @param scene - 要设置的场景实例 + * @returns WorldManager实例,如果未初始化则自动创建 */ - private setSceneInternal(scene: IScene): void { - this._scene = scene; - this.onSceneChanged(); - this._scene.initialize(); - this._scene.begin(); + public static getWorldManager(): WorldManager { + if (!this._instance) { + throw new Error("Core实例未创建,请先调用Core.create()"); + } + + if (!this._instance._worldManager) { + // 多World模式的配置(用户主动获取WorldManager) + this._instance._worldManager = WorldManager.getInstance({ + maxWorlds: 50, + autoCleanup: true, + cleanupInterval: 60000, + debug: this._instance._config.debug + }); + } + + return this._instance._worldManager; + } + + /** + * 启用World管理 + * + * 显式启用World功能,用于多房间/多世界架构 + */ + public static enableWorldManager(): WorldManager { + return this.getWorldManager(); + } + + /** + * 确保默认World存在 + * + * 内部方法,用于懒初始化默认World + */ + private ensureDefaultWorld(): void { + if (!this._worldManager) { + this._worldManager = WorldManager.getInstance({ + maxWorlds: 1, // 单场景用户只需要1个World + autoCleanup: false, // 单场景不需要自动清理 + cleanupInterval: 0, // 禁用清理定时器 + debug: this._config.debug + }); + } + + // 检查默认World是否存在 + if (!this._worldManager.getWorld(Core.DEFAULT_WORLD_ID)) { + this._worldManager.createWorld(Core.DEFAULT_WORLD_ID, { + name: 'DefaultWorld', + maxScenes: 1, + autoCleanup: false + }); + this._worldManager.setWorldActive(Core.DEFAULT_WORLD_ID, true); + } } /** @@ -485,28 +523,19 @@ export class Core { public onSceneChanged() { Time.sceneChanged(); + // 获取当前Scene(从默认World) + const currentScene = Core.getScene(); + // 初始化ECS API(如果场景支持) - if (this._scene && this._scene.querySystem && this._scene.eventSystem) { - this._ecsAPI = createECSAPI(this._scene, this._scene.querySystem, this._scene.eventSystem); + if (currentScene && currentScene.querySystem && currentScene.eventSystem) { + this._ecsAPI = createECSAPI(currentScene, currentScene.querySystem, currentScene.eventSystem); } // 延迟调试管理器通知,避免在场景初始化过程中干扰属性 if (this._debugManager) { - // 使用 requestAnimationFrame 确保在场景完全初始化后再收集数据 - if (typeof requestAnimationFrame !== 'undefined') { - requestAnimationFrame(() => { - if (this._debugManager) { - this._debugManager.onSceneChanged(); - } - }); - } else { - // 兜底:使用 setTimeout - setTimeout(() => { - if (this._debugManager) { - this._debugManager.onSceneChanged(); - } - }, 0); - } + queueMicrotask(() => { + this._debugManager?.onSceneChanged(); + }); } } @@ -567,23 +596,25 @@ export class Core { // 更新对象池管理器 this._poolManager.update(); - // 处理场景切换 - if (this._nextScene != null) { - if (this._scene != null) - this._scene.end(); + // 更新所有World + if (this._worldManager) { + const worldsStartTime = this._performanceMonitor.startMonitoring('Worlds.update'); + const activeWorlds = this._worldManager.getActiveWorlds(); + let totalWorldEntities = 0; - this._scene = this._nextScene; - this._nextScene = null; - this.onSceneChanged(); - this._scene.begin(); - } + for (const world of activeWorlds) { + // 更新World的全局System + world.updateGlobalSystems(); + + // 更新World中的所有Scene + world.updateScenes(); - // 更新当前场景 - if (this._scene != null && this._scene.update) { - const sceneStartTime = this._performanceMonitor.startMonitoring('Scene.update'); - this._scene.update(); - const entityCount = this._scene.entities?.count || 0; - this._performanceMonitor.endMonitoring('Scene.update', sceneStartTime, entityCount); + // 统计实体数量(用于性能监控) + const worldStats = world.getStats(); + totalWorldEntities += worldStats.totalEntities; + } + + this._performanceMonitor.endMonitoring('Worlds.update', worldsStartTime, totalWorldEntities); } // 更新调试管理器(基于FPS的数据发送) diff --git a/packages/core/src/ECS/World.ts b/packages/core/src/ECS/World.ts new file mode 100644 index 00000000..15f03796 --- /dev/null +++ b/packages/core/src/ECS/World.ts @@ -0,0 +1,504 @@ +import { IScene } from './IScene'; +import { Scene } from './Scene'; +import { createLogger } from '../Utils/Logger'; + +const logger = createLogger('World'); + +/** + * 全局系统接口 + * 全局系统是在World级别运行的系统,不依赖特定Scene + */ +export interface IGlobalSystem { + /** + * 系统名称 + */ + readonly name: string; + + /** + * 初始化系统 + */ + initialize?(): void; + + /** + * 更新系统 + */ + update(deltaTime?: number): void; + + /** + * 重置系统 + */ + reset?(): void; + + /** + * 销毁系统 + */ + destroy?(): void; +} + +/** + * World配置接口 + */ +export interface IWorldConfig { + /** + * World名称 + */ + name?: string; + + /** + * 是否启用调试模式 + */ + debug?: boolean; + + /** + * 最大Scene数量限制 + */ + maxScenes?: number; + + /** + * 是否自动清理空Scene + */ + autoCleanup?: boolean; +} + +/** + * World类 - ECS世界管理器 + * + * World是Scene的容器,每个World可以管理多个Scene。 + * 这种设计允许创建独立的游戏世界,如: + * - 游戏房间(每个房间一个World) + * - 不同的游戏模式 + * - 独立的模拟环境 + * + * @example + * ```typescript + * // 创建游戏房间的World + * const roomWorld = new World({ name: 'Room_001' }); + * + * // 在World中创建Scene + * const gameScene = roomWorld.createScene('game', new Scene()); + * const uiScene = roomWorld.createScene('ui', new Scene()); + * + * // 更新整个World + * roomWorld.update(deltaTime); + * ``` + */ +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 _globalSystems: IGlobalSystem[] = []; + private _isActive: boolean = false; + private _createdAt: number; + + constructor(config: IWorldConfig = {}) { + this._config = { + name: 'World', + debug: false, + maxScenes: 10, + autoCleanup: true, + ...config + }; + + this.name = this._config.name!; + this._createdAt = Date.now(); + + logger.info(`创建World: ${this.name}`); + } + + // ===== Scene管理 ===== + + /** + * 创建并添加Scene到World + */ + public createScene(sceneId: string, sceneInstance?: T): T { + if (this._scenes.has(sceneId)) { + throw new Error(`Scene ID '${sceneId}' 已存在于World '${this.name}' 中`); + } + + if (this._scenes.size >= this._config.maxScenes!) { + throw new Error(`World '${this.name}' 已达到最大Scene数量限制: ${this._config.maxScenes}`); + } + + // 如果没有提供Scene实例,创建默认Scene + const scene = sceneInstance || (new Scene() as unknown as T); + + // 设置Scene的标识 + if ('id' in scene) { + (scene as any).id = sceneId; + } + if ('name' in scene && !scene.name) { + scene.name = sceneId; + } + + this._scenes.set(sceneId, scene); + + // 初始化Scene + scene.initialize(); + + logger.info(`在World '${this.name}' 中创建Scene: ${sceneId}`); + return scene; + } + + /** + * 移除Scene + */ + public removeScene(sceneId: string): boolean { + const scene = this._scenes.get(sceneId); + if (!scene) { + return false; + } + + // 如果Scene正在运行,先停止它 + if (this._activeScenes.has(sceneId)) { + this.setSceneActive(sceneId, false); + } + + // 清理Scene资源 + scene.end(); + this._scenes.delete(sceneId); + + logger.info(`从World '${this.name}' 中移除Scene: ${sceneId}`); + return true; + } + + /** + * 获取Scene + */ + public getScene(sceneId: string): T | null { + return this._scenes.get(sceneId) as T || null; + } + + /** + * 获取所有Scene ID + */ + public getSceneIds(): string[] { + return Array.from(this._scenes.keys()); + } + + /** + * 获取所有Scene + */ + public getAllScenes(): IScene[] { + return Array.from(this._scenes.values()); + } + + /** + * 设置Scene激活状态 + */ + public setSceneActive(sceneId: string, active: boolean): void { + const scene = this._scenes.get(sceneId); + if (!scene) { + logger.warn(`Scene '${sceneId}' 不存在于World '${this.name}' 中`); + return; + } + + if (active) { + this._activeScenes.add(sceneId); + // 启动Scene + if (scene.begin) { + scene.begin(); + } + logger.debug(`在World '${this.name}' 中激活Scene: ${sceneId}`); + } else { + this._activeScenes.delete(sceneId); + // 可选择性地停止Scene,或者让它继续运行但不更新 + logger.debug(`在World '${this.name}' 中停用Scene: ${sceneId}`); + } + } + + /** + * 检查Scene是否激活 + */ + public isSceneActive(sceneId: string): boolean { + return this._activeScenes.has(sceneId); + } + + /** + * 获取活跃Scene数量 + */ + public getActiveSceneCount(): number { + return this._activeScenes.size; + } + + // ===== 全局System管理 ===== + + /** + * 添加全局System + * 全局System会在所有激活Scene之前更新 + */ + public addGlobalSystem(system: T): T { + if (this._globalSystems.includes(system)) { + return system; + } + + this._globalSystems.push(system); + if (system.initialize) { + system.initialize(); + } + + logger.debug(`在World '${this.name}' 中添加全局System: ${system.name}`); + return system; + } + + /** + * 移除全局System + */ + public removeGlobalSystem(system: IGlobalSystem): boolean { + const index = this._globalSystems.indexOf(system); + if (index === -1) { + return false; + } + + this._globalSystems.splice(index, 1); + if (system.reset) { + system.reset(); + } + + logger.debug(`从World '${this.name}' 中移除全局System: ${system.name}`); + return true; + } + + /** + * 获取全局System + */ + 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; + } + + // ===== World生命周期 ===== + + /** + * 启动World + */ + public start(): void { + if (this._isActive) { + return; + } + + this._isActive = true; + + // 启动所有全局System + for (const system of this._globalSystems) { + if (system.initialize) { + system.initialize(); + } + } + + logger.info(`启动World: ${this.name}`); + } + + /** + * 停止World + */ + public stop(): void { + if (!this._isActive) { + return; + } + + // 停止所有Scene + for (const sceneId of this._activeScenes) { + this.setSceneActive(sceneId, false); + } + + // 重置所有全局System + for (const system of this._globalSystems) { + if (system.reset) { + system.reset(); + } + } + + this._isActive = false; + logger.info(`停止World: ${this.name}`); + } + + /** + * 更新World中的全局System + * 注意:此方法由Core.update()调用,不应直接调用 + */ + public updateGlobalSystems(): void { + if (!this._isActive) { + return; + } + + // 更新全局System + for (const system of this._globalSystems) { + if (system.update) { + system.update(); + } + } + } + + /** + * 更新World中的所有激活Scene + * 注意:此方法由Core.update()调用,不应直接调用 + */ + public updateScenes(): void { + if (!this._isActive) { + return; + } + + // 更新所有激活的Scene + for (const sceneId of this._activeScenes) { + const scene = this._scenes.get(sceneId); + if (scene && scene.update) { + scene.update(); + } + } + + // 自动清理(如果启用) + if (this._config.autoCleanup && this.shouldAutoCleanup()) { + this.cleanup(); + } + } + + /** + * 销毁World + */ + public destroy(): void { + logger.info(`销毁World: ${this.name}`); + + // 停止World + this.stop(); + + // 销毁所有Scene + const sceneIds = Array.from(this._scenes.keys()); + for (const sceneId of sceneIds) { + this.removeScene(sceneId); + } + + // 清理全局System + for (const system of this._globalSystems) { + if (system.destroy) { + system.destroy(); + } else if (system.reset) { + system.reset(); + } + } + this._globalSystems.length = 0; + + this._scenes.clear(); + this._activeScenes.clear(); + } + + // ===== 状态信息 ===== + + /** + * 获取World状态 + */ + public getStatus() { + return { + name: this.name, + isActive: this._isActive, + sceneCount: this._scenes.size, + activeSceneCount: this._activeScenes.size, + globalSystemCount: this._globalSystems.length, + createdAt: this._createdAt, + config: { ...this._config }, + scenes: Array.from(this._scenes.keys()).map(sceneId => ({ + id: sceneId, + isActive: this._activeScenes.has(sceneId), + name: this._scenes.get(sceneId)?.name || sceneId + })) + }; + } + + /** + * 获取World统计信息 + */ + public getStats() { + const stats = { + totalEntities: 0, + totalSystems: this._globalSystems.length, + memoryUsage: 0, + performance: { + averageUpdateTime: 0, + maxUpdateTime: 0 + } + }; + + // 统计所有Scene的实体数量 + for (const scene of this._scenes.values()) { + if (scene.entities) { + stats.totalEntities += scene.entities.count; + } + if (scene.systems) { + stats.totalSystems += scene.systems.length; + } + } + + return stats; + } + + // ===== 私有方法 ===== + + /** + * 检查是否应该执行自动清理 + */ + private shouldAutoCleanup(): boolean { + // 简单的清理策略:如果有空Scene且超过5分钟没有实体 + const currentTime = Date.now(); + const cleanupThreshold = 5 * 60 * 1000; // 5分钟 + + for (const [sceneId, scene] of this._scenes) { + if (!this._activeScenes.has(sceneId) && + scene.entities && + scene.entities.count === 0 && + (currentTime - this._createdAt) > cleanupThreshold) { + return true; + } + } + + return false; + } + + /** + * 执行清理操作 + */ + private cleanup(): void { + const sceneIds = Array.from(this._scenes.keys()); + const currentTime = Date.now(); + const cleanupThreshold = 5 * 60 * 1000; // 5分钟 + + for (const sceneId of sceneIds) { + const scene = this._scenes.get(sceneId); + if (scene && + !this._activeScenes.has(sceneId) && + scene.entities && + scene.entities.count === 0 && + (currentTime - this._createdAt) > cleanupThreshold) { + + this.removeScene(sceneId); + logger.debug(`自动清理空Scene: ${sceneId} 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; + } +} \ No newline at end of file diff --git a/packages/core/src/ECS/WorldManager.ts b/packages/core/src/ECS/WorldManager.ts new file mode 100644 index 00000000..354dba83 --- /dev/null +++ b/packages/core/src/ECS/WorldManager.ts @@ -0,0 +1,463 @@ +import { World, IWorldConfig } from './World'; +import { createLogger } from '../Utils/Logger'; + +const logger = createLogger('WorldManager'); + +/** + * WorldManager配置接口 + */ +export interface IWorldManagerConfig { + /** + * 最大World数量 + */ + maxWorlds?: number; + + /** + * 是否自动清理空World + */ + autoCleanup?: boolean; + + /** + * 清理间隔(毫秒) + */ + cleanupInterval?: number; + + /** + * 是否启用调试模式 + */ + debug?: boolean; +} + +/** + * World管理器 - 管理所有World实例 + * + * WorldManager是全局单例,负责管理所有World的生命周期。 + * 每个World都是独立的ECS环境,可以包含多个Scene。 + * + * 设计理念: + * - Core负责单Scene的传统ECS管理 + * - World负责多Scene的管理和协调 + * - WorldManager负责多World的全局管理 + * + * @example + * ```typescript + * // 获取全局WorldManager + * const worldManager = WorldManager.getInstance(); + * + * // 创建游戏房间World + * const roomWorld = worldManager.createWorld('room_001', { + * name: 'GameRoom_001', + * maxScenes: 5 + * }); + * + * // 在游戏循环中更新所有World + * worldManager.updateAll(deltaTime); + * ``` + */ +export class WorldManager { + private static _instance: WorldManager | null = null; + + private readonly _config: IWorldManagerConfig; + private readonly _worlds: Map = new Map(); + private readonly _activeWorlds: Set = new Set(); + private _cleanupTimer: NodeJS.Timeout | null = null; + private _isRunning: boolean = false; + + private constructor(config: IWorldManagerConfig = {}) { + this._config = { + maxWorlds: 50, + autoCleanup: true, + cleanupInterval: 30000, // 30秒 + debug: false, + ...config + }; + + logger.info('WorldManager已初始化', { + maxWorlds: this._config.maxWorlds, + autoCleanup: this._config.autoCleanup, + cleanupInterval: this._config.cleanupInterval + }); + + this.startCleanupTimer(); + } + + /** + * 获取WorldManager单例实例 + */ + public static getInstance(config?: IWorldManagerConfig): WorldManager { + if (!this._instance) { + this._instance = new WorldManager(config); + } + return this._instance; + } + + /** + * 重置WorldManager实例(主要用于测试) + */ + public static reset(): void { + if (this._instance) { + this._instance.destroy(); + this._instance = null; + } + } + + // ===== World管理 ===== + + /** + * 创建新World + */ + public createWorld(worldId: string, config?: IWorldConfig): World { + if (!worldId || typeof worldId !== 'string' || worldId.trim() === '') { + throw new Error('World ID不能为空'); + } + + if (this._worlds.has(worldId)) { + throw new Error(`World ID '${worldId}' 已存在`); + } + + if (this._worlds.size >= this._config.maxWorlds!) { + throw new Error(`已达到最大World数量限制: ${this._config.maxWorlds}`); + } + + const worldConfig: IWorldConfig = { + name: worldId, + debug: this._config.debug, + ...config + }; + + const world = new World(worldConfig); + this._worlds.set(worldId, world); + + logger.info(`创建World: ${worldId}`, { config: worldConfig }); + return world; + } + + /** + * 移除World + */ + public removeWorld(worldId: string): boolean { + const world = this._worlds.get(worldId); + if (!world) { + return false; + } + + // 如果World正在运行,先停止它 + if (this._activeWorlds.has(worldId)) { + this.setWorldActive(worldId, false); + } + + // 销毁World + world.destroy(); + this._worlds.delete(worldId); + + logger.info(`移除World: ${worldId}`); + return true; + } + + /** + * 获取World + */ + public getWorld(worldId: string): World | null { + return this._worlds.get(worldId) || null; + } + + /** + * 获取所有World ID + */ + public getWorldIds(): string[] { + return Array.from(this._worlds.keys()); + } + + /** + * 获取所有World + */ + public getAllWorlds(): World[] { + return Array.from(this._worlds.values()); + } + + /** + * 设置World激活状态 + */ + public setWorldActive(worldId: string, active: boolean): void { + const world = this._worlds.get(worldId); + if (!world) { + logger.warn(`World '${worldId}' 不存在`); + return; + } + + if (active) { + this._activeWorlds.add(worldId); + world.start(); + logger.debug(`激活World: ${worldId}`); + } else { + this._activeWorlds.delete(worldId); + world.stop(); + logger.debug(`停用World: ${worldId}`); + } + } + + /** + * 检查World是否激活 + */ + public isWorldActive(worldId: string): boolean { + return this._activeWorlds.has(worldId); + } + + // ===== 批量操作 ===== + + /** + * 获取所有激活的World + * 注意:此方法供Core.update()使用 + */ + public getActiveWorlds(): World[] { + const activeWorlds: World[] = []; + for (const worldId of this._activeWorlds) { + const world = this._worlds.get(worldId); + if (world) { + activeWorlds.push(world); + } + } + return activeWorlds; + } + + /** + * 启动所有World + */ + public startAll(): void { + this._isRunning = true; + + for (const worldId of this._worlds.keys()) { + this.setWorldActive(worldId, true); + } + + logger.info('启动所有World'); + } + + /** + * 停止所有World + */ + public stopAll(): void { + this._isRunning = false; + + for (const worldId of this._activeWorlds) { + this.setWorldActive(worldId, false); + } + + 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._activeWorlds.size, + totalScenes: 0, + totalEntities: 0, + totalSystems: 0, + memoryUsage: 0, + isRunning: this._isRunning, + config: { ...this._config }, + worlds: [] as any[] + }; + + for (const [worldId, 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: worldId, + name: world.name, + isActive: this._activeWorlds.has(worldId), + sceneCount: world.sceneCount, + ...worldStats + }); + } + + return stats; + } + + /** + * 获取详细状态信息 + */ + public getDetailedStatus() { + return { + ...this.getStats(), + worlds: Array.from(this._worlds.entries()).map(([worldId, world]) => ({ + id: worldId, + isActive: this._activeWorlds.has(worldId), + status: world.getStatus() + })) + }; + } + + // ===== 生命周期管理 ===== + + /** + * 清理空World + */ + public cleanup(): number { + const worldsToRemove: string[] = []; + + for (const [worldId, world] of this._worlds) { + if (this.shouldCleanupWorld(world)) { + worldsToRemove.push(worldId); + } + } + + for (const worldId of worldsToRemove) { + this.removeWorld(worldId); + } + + if (worldsToRemove.length > 0) { + logger.debug(`清理了 ${worldsToRemove.length} 个World`); + } + + return worldsToRemove.length; + } + + /** + * 销毁WorldManager + */ + public destroy(): void { + logger.info('正在销毁WorldManager...'); + + // 停止清理定时器 + this.stopCleanupTimer(); + + // 停止所有World + this.stopAll(); + + // 销毁所有World + const worldIds = Array.from(this._worlds.keys()); + for (const worldId of worldIds) { + this.removeWorld(worldId); + } + + this._worlds.clear(); + this._activeWorlds.clear(); + this._isRunning = false; + + logger.info('WorldManager已销毁'); + } + + // ===== 私有方法 ===== + + /** + * 启动清理定时器 + */ + private startCleanupTimer(): void { + if (!this._config.autoCleanup || this._cleanupTimer) { + return; + } + + this._cleanupTimer = setInterval(() => { + this.cleanup(); + }, this._config.cleanupInterval); + + logger.debug(`启动World清理定时器,间隔: ${this._config.cleanupInterval}ms`); + } + + /** + * 停止清理定时器 + */ + private stopCleanupTimer(): void { + if (this._cleanupTimer) { + clearInterval(this._cleanupTimer); + this._cleanupTimer = null; + logger.debug('停止World清理定时器'); + } + } + + /** + * 判断World是否应该被清理 + */ + private shouldCleanupWorld(world: World): boolean { + // 清理策略: + // 1. World未激活 + // 2. 没有Scene或所有Scene都是空的 + // 3. 创建时间超过10分钟 + + if (world.isActive) { + return false; + } + + if (world.sceneCount === 0) { + const age = Date.now() - world.createdAt; + return age > 10 * 60 * 1000; // 10分钟 + } + + // 检查是否所有Scene都是空的 + const allScenes = world.getAllScenes(); + const hasEntities = allScenes.some(scene => + scene.entities && scene.entities.count > 0 + ); + + if (!hasEntities) { + const age = Date.now() - world.createdAt; + return age > 10 * 60 * 1000; // 10分钟 + } + + return false; + } + + // ===== 访问器 ===== + + /** + * 获取World总数 + */ + public get worldCount(): number { + return this._worlds.size; + } + + /** + * 获取激活World数量 + */ + public get activeWorldCount(): number { + return this._activeWorlds.size; + } + + /** + * 检查是否正在运行 + */ + public get isRunning(): boolean { + return this._isRunning; + } + + /** + * 获取配置 + */ + public get config(): IWorldManagerConfig { + return { ...this._config }; + } +} \ No newline at end of file diff --git a/packages/core/src/ECS/index.ts b/packages/core/src/ECS/index.ts index 2f20612d..d40ae819 100644 --- a/packages/core/src/ECS/index.ts +++ b/packages/core/src/ECS/index.ts @@ -6,6 +6,8 @@ export * from './Utils'; export * from './Decorators'; export { Scene } from './Scene'; export { IScene, ISceneFactory, ISceneConfig } from './IScene'; +export { World, IWorldConfig } from './World'; +export { WorldManager, IWorldManagerConfig } from './WorldManager'; export { EntityManager, EntityQueryBuilder } from './Core/EntityManager'; export * from './Core/Events'; export * from './Core/Query'; diff --git a/packages/core/tests/ECS/Core/WorldCoreIntegration.test.ts b/packages/core/tests/ECS/Core/WorldCoreIntegration.test.ts new file mode 100644 index 00000000..797d3b55 --- /dev/null +++ b/packages/core/tests/ECS/Core/WorldCoreIntegration.test.ts @@ -0,0 +1,589 @@ +import { Core } from '../../../src/Core'; +import { Scene } from '../../../src/ECS/Scene'; +import { World, IGlobalSystem } from '../../../src/ECS/World'; +import { WorldManager } from '../../../src/ECS/WorldManager'; +import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem'; +import { Component } from '../../../src/ECS/Component'; +import { Matcher } from '../../../src/ECS/Utils/Matcher'; +import { Entity } from '../../../src/ECS/Entity'; + +// 测试用组件 +class TestComponent extends Component { + public value: number = 0; + + constructor(value: number = 0) { + super(); + this.value = value; + } + + public reset(): void { + this.value = 0; + } +} + +class NetworkComponent extends Component { + public playerId: string; + + constructor(playerId: string) { + super(); + this.playerId = playerId; + } + + public reset(): void { + this.playerId = ''; + } +} + +// 测试用系统 +class TestGlobalSystem extends EntitySystem { + public processedEntities: Entity[] = []; + public updateCount: number = 0; + + constructor() { + super(Matcher.empty().all(TestComponent)); + } + + protected override process(entities: Entity[]): void { + this.processedEntities = [...entities]; + this.updateCount++; + } +} + +// 正确的全局系统实现 +class NetworkSyncGlobalSystem implements IGlobalSystem { + public readonly name = 'NetworkSyncSystem'; + public updateCount: number = 0; + + public initialize(): void { + // 初始化网络连接等 + } + + public update(): void { + this.updateCount++; + // 同步网络数据等全局逻辑 + } + + public reset(): void { + this.updateCount = 0; + } + + public destroy(): void { + // 清理网络连接等 + } +} + +// Scene级别的EntitySystem(正确的用法) +class NetworkSyncSystem extends EntitySystem { + public syncCount: number = 0; + + constructor() { + super(Matcher.empty().all(NetworkComponent)); + } + + protected override process(entities: Entity[]): void { + this.syncCount++; + } +} + +// World级别的网络同步全局系统 +class NetworkGlobalSystem implements IGlobalSystem { + public readonly name = 'NetworkGlobalSystem'; + public syncCount: number = 0; + + public initialize(): void { + // 初始化网络连接 + } + + public update(): void { + this.syncCount++; + // 全局网络同步逻辑 + } + + public reset(): void { + this.syncCount = 0; + } + + public destroy(): void { + // 清理网络连接 + } +} + +// 测试用Scene +class TestScene extends Scene { + public updateCallCount: number = 0; + + public override update(): void { + super.update(); + this.updateCallCount++; + } +} + +describe('World与Core集成测试', () => { + beforeEach(() => { + // 重置Core和WorldManager + if ((Core as any)._instance) { + (Core as any)._instance = null; + } + WorldManager['_instance'] = null; + }); + + afterEach(() => { + // 清理资源 + if ((Core as any)._instance) { + const worldManager = Core.getWorldManager?.(); + if (worldManager) { + const worldIds = worldManager.getWorldIds(); + worldIds.forEach(id => { + worldManager.removeWorld(id); + }); + } + (Core as any)._instance = null; + } + WorldManager['_instance'] = null; + }); + + describe('融合设计基础功能', () => { + test('单Scene模式应该保持向后兼容', () => { + Core.create({ debug: false }); + + // 传统单Scene用法 + const scene = new Scene(); + scene.name = 'TestScene'; + + Core.setScene(scene); + + const retrievedScene = Core.getScene(); + expect(retrievedScene).toBe(scene); + expect(retrievedScene?.name).toBe('TestScene'); + }); + + test('启用WorldManager后应该支持多World功能', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + expect(worldManager).toBeDefined(); + + const world = worldManager.createWorld('TestWorld'); + expect(world).toBeDefined(); + expect(world.name).toBe('TestWorld'); + }); + + test('getWorldManager应该自动创建WorldManager', () => { + Core.create({ debug: false }); + + // 获取WorldManager会自动创建实例 + const worldManager = Core.getWorldManager(); + expect(worldManager).toBeDefined(); + + // 多次获取应该返回同一个实例 + const worldManager2 = Core.getWorldManager(); + expect(worldManager2).toBe(worldManager); + }); + + test('单Scene模式下Core.update应该正常工作', () => { + Core.create({ debug: false }); + + const scene = new TestScene(); + Core.setScene(scene); + + // 模拟更新 + Core.update(0.016); + + expect(scene.updateCallCount).toBeGreaterThan(0); + }); + }); + + describe('默认World机制', () => { + test('设置Scene应该自动创建默认World', () => { + Core.create({ debug: false }); + + const scene = new Scene(); + Core.setScene(scene); + + // 启用WorldManager后应该能看到默认World + Core.enableWorldManager(); + const worldManager = Core.getWorldManager(); + + expect(worldManager.getWorld('__default__')).toBeDefined(); + + const defaultWorld = worldManager.getWorld('__default__'); + expect(defaultWorld).toBeDefined(); + expect(defaultWorld?.getScene('__main__')).toBe(scene); + }); + + test('默认World的Scene应该正确激活', () => { + Core.create({ debug: false }); + + const scene = new Scene(); + Core.setScene(scene); + + Core.enableWorldManager(); + const worldManager = Core.getWorldManager(); + const defaultWorld = worldManager.getWorld('__default__'); + + expect(defaultWorld?.isSceneActive('__main__')).toBe(true); + }); + + test('替换默认Scene应该正确处理', () => { + Core.create({ debug: false }); + + const scene1 = new Scene(); + scene1.name = 'Scene1'; + Core.setScene(scene1); + + const scene2 = new Scene(); + scene2.name = 'Scene2'; + Core.setScene(scene2); + + const currentScene = Core.getScene(); + expect(currentScene).toBe(scene2); + expect(currentScene?.name).toBe('Scene2'); + }); + }); + + describe('多World更新机制', () => { + test('Core.update应该更新所有活跃World', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + + // 创建多个World + const world1 = worldManager.createWorld('World1'); + const world2 = worldManager.createWorld('World2'); + const world3 = worldManager.createWorld('World3'); + + // 为每个World创建Scene和System + const scene1 = world1.createScene('scene1', new TestScene()); + const scene2 = world2.createScene('scene2', new TestScene()); + const scene3 = world3.createScene('scene3', new TestScene()); + + // 启动部分World + worldManager.setWorldActive('World1', true); + worldManager.setWorldActive('World2', true); + // world3保持未启动 + + world1.setSceneActive('scene1', true); + world2.setSceneActive('scene2', true); + + // 执行更新 + Core.update(0.016); + + // 检查只有激活的World被更新 + expect(scene1.updateCallCount).toBeGreaterThan(0); + expect(scene2.updateCallCount).toBeGreaterThan(0); + expect(scene3.updateCallCount).toBe(0); + }); + + test('全局系统应该在Scene更新前执行', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + const world = worldManager.createWorld('TestWorld'); + + // 添加正确设计的全局系统(业务逻辑系统,不是EntitySystem) + const globalSystem = new NetworkSyncGlobalSystem(); + world.addGlobalSystem(globalSystem); + + // 创建Scene + const scene = world.createScene('testScene'); + + worldManager.setWorldActive('TestWorld', true); + world.setSceneActive('testScene', true); + + // 执行更新 + Core.update(0.016); + + // 验证全局System被正确更新 + expect(globalSystem.updateCount).toBeGreaterThan(0); + }); + }); + + describe('多房间游戏服务器场景', () => { + test('多个游戏房间应该独立运行', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + + // 创建两个游戏房间 + const room1 = worldManager.createWorld('Room_001'); + const room2 = worldManager.createWorld('Room_002'); + + // 为每个房间设置Scene + const gameScene1 = room1.createScene('game'); + const gameScene2 = room2.createScene('game'); + + // 为每个房间添加全局网络系统 + const netSystem1 = new NetworkGlobalSystem(); + const netSystem2 = new NetworkGlobalSystem(); + + room1.addGlobalSystem(netSystem1); + room2.addGlobalSystem(netSystem2); + + // 在每个房间创建玩家 + const player1 = gameScene1.createEntity('Player1'); + player1.addComponent(new NetworkComponent('player_123')); + + const player2 = gameScene2.createEntity('Player2'); + player2.addComponent(new NetworkComponent('player_456')); + + // 启动房间 + worldManager.setWorldActive('Room_001', true); + worldManager.setWorldActive('Room_002', true); + room1.setSceneActive('game', true); + room2.setSceneActive('game', true); + + // 模拟游戏循环 + for (let i = 0; i < 5; i++) { + Core.update(0.016); + } + + // 验证每个房间独立运行 + expect(netSystem1.syncCount).toBeGreaterThan(0); + expect(netSystem2.syncCount).toBeGreaterThan(0); + expect(room1.getActiveSceneCount()).toBe(1); + expect(room2.getActiveSceneCount()).toBe(1); + }); + + test('房间销毁应该完全清理资源', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + + // 创建房间 + const room = worldManager.createWorld('TempRoom'); + const scene = room.createScene('game'); + + // 添加内容 + for (let i = 0; i < 10; i++) { + const entity = scene.createEntity(`Entity${i}`); + entity.addComponent(new TestComponent(i)); + } + + room.addGlobalSystem(new NetworkSyncGlobalSystem()); + worldManager.setWorldActive('TempRoom', true); + room.setSceneActive('game', true); + + // 验证房间正常运行 + Core.update(0.016); + + const beforeDestroy = worldManager.getStats(); + expect(beforeDestroy.totalWorlds).toBe(1); + expect(beforeDestroy.activeWorlds).toBe(1); + + // 销毁房间 + worldManager.removeWorld('TempRoom'); + + const afterDestroy = worldManager.getStats(); + expect(afterDestroy.totalWorlds).toBe(0); + expect(afterDestroy.activeWorlds).toBe(0); + }); + }); + + describe('客户端多层Scene架构', () => { + test('分层Scene应该同时运行', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + const clientWorld = worldManager.createWorld('ClientWorld'); + + // 创建不同层的Scene + const gameplayScene = clientWorld.createScene('gameplay', new TestScene()); + const uiScene = clientWorld.createScene('ui', new TestScene()); + const effectsScene = clientWorld.createScene('effects', new TestScene()); + + // 启动世界并激活所有Scene + worldManager.setWorldActive('ClientWorld', true); + clientWorld.setSceneActive('gameplay', true); + clientWorld.setSceneActive('ui', true); + clientWorld.setSceneActive('effects', true); + + // 执行更新 + Core.update(0.016); + + // 验证所有Scene都被更新 + expect(gameplayScene.updateCallCount).toBeGreaterThan(0); + expect(uiScene.updateCallCount).toBeGreaterThan(0); + expect(effectsScene.updateCallCount).toBeGreaterThan(0); + }); + + test('Scene的动态激活和停用', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + const world = worldManager.createWorld('DynamicWorld'); + + const gameScene = world.createScene('game', new TestScene()); + const menuScene = world.createScene('menu', new TestScene()); + + worldManager.setWorldActive('DynamicWorld', true); + + // 初始状态:只有游戏Scene激活 + world.setSceneActive('game', true); + world.setSceneActive('menu', false); + + Core.update(0.016); + + const gameCount1 = gameScene.updateCallCount; + const menuCount1 = menuScene.updateCallCount; + + // 切换到菜单 + world.setSceneActive('game', false); + world.setSceneActive('menu', true); + + Core.update(0.016); + + const gameCount2 = gameScene.updateCallCount; + const menuCount2 = menuScene.updateCallCount; + + // 验证Scene状态切换 + expect(gameCount2).toBe(gameCount1); // 游戏Scene停止更新 + expect(menuCount2).toBeGreaterThan(menuCount1); // 菜单Scene开始更新 + }); + }); + + describe('性能和稳定性', () => { + test('大量World和Scene应该稳定运行', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + const worldCount = 20; + const scenePerWorld = 3; + + // 创建大量World和Scene + for (let i = 0; i < worldCount; i++) { + const world = worldManager.createWorld(`World${i}`); + + for (let j = 0; j < scenePerWorld; j++) { + const scene = world.createScene(`Scene${j}`, new TestScene()); + + // 添加一些实体 + for (let k = 0; k < 5; k++) { + const entity = scene.createEntity(`Entity${k}`); + entity.addComponent(new TestComponent(k)); + } + + world.setSceneActive(`Scene${j}`, true); + } + + worldManager.setWorldActive(`World${i}`, true); + } + + // 验证所有资源创建成功 + expect(worldManager.getWorldIds()).toHaveLength(worldCount); + expect(worldManager.getActiveWorlds()).toHaveLength(worldCount); + + // 执行多次更新测试稳定性 + for (let i = 0; i < 10; i++) { + expect(() => { + Core.update(0.016); + }).not.toThrow(); + } + + // 验证更新正常工作 + const activeWorlds = worldManager.getActiveWorlds(); + activeWorlds.forEach(world => { + const scenes = world.getAllScenes(); + scenes.forEach(scene => { + if (scene instanceof TestScene && world.isSceneActive(scene.name)) { + expect(scene.updateCallCount).toBeGreaterThan(0); + } + }); + }); + }); + + test('频繁的World创建和销毁应该不影响性能', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + + // 频繁创建和销毁World + for (let cycle = 0; cycle < 10; cycle++) { + // 创建批次World + const worldIds: string[] = []; + for (let i = 0; i < 5; i++) { + const worldId = `Cycle${cycle}_World${i}`; + worldIds.push(worldId); + + const world = worldManager.createWorld(worldId); + const scene = world.createScene('test'); + scene.createEntity('entity'); + + worldManager.setWorldActive(worldId, true); + world.setSceneActive('test', true); + } + + // 更新一次 + Core.update(0.016); + + // 销毁批次World + worldIds.forEach(id => { + worldManager.removeWorld(id); + }); + + // 验证清理完成 + expect(worldManager.getWorldIds()).toHaveLength(0); + expect(worldManager.getActiveWorlds()).toHaveLength(0); + } + }); + }); + + describe('错误处理和边界情况', () => { + test('Core未初始化时操作应该抛出合适错误', () => { + // getScene 会返回 null 而不是抛出错误 + expect(Core.getScene()).toBeNull(); + + expect(() => { + Core.setScene(new Scene()); + }).toThrow(); + }); + + test('在World销毁后继续操作应该安全', () => { + Core.create({ debug: false }); + Core.enableWorldManager(); + + const worldManager = Core.getWorldManager(); + const world = worldManager.createWorld('DestroyTest'); + + worldManager.setWorldActive('DestroyTest', true); + worldManager.removeWorld('DestroyTest'); + + // 对已销毁的World进行操作应该不会崩溃 + expect(() => { + world.updateGlobalSystems(); + world.updateScenes(); + }).not.toThrow(); + }); + + test('混合使用单Scene和多World模式', () => { + Core.create({ debug: false }); + + // 直接启用WorldManager(避免先使用单Scene创建限制性配置) + const worldManager = Core.getWorldManager(); + + // 然后使用单Scene模式 + const singleScene = new Scene(); + Core.setScene(singleScene); + + // 验证默认World被创建 + expect(worldManager.getWorld('__default__')).toBeDefined(); + + // 创建额外的World + const extraWorld = worldManager.createWorld('ExtraWorld'); + worldManager.setWorldActive('ExtraWorld', true); + + // 两种模式应该能共存 + expect(() => { + Core.update(0.016); + }).not.toThrow(); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/tests/ECS/World.test.ts b/packages/core/tests/ECS/World.test.ts new file mode 100644 index 00000000..0dba9c56 --- /dev/null +++ b/packages/core/tests/ECS/World.test.ts @@ -0,0 +1,466 @@ +import { World, IWorldConfig, IGlobalSystem } from '../../src/ECS/World'; +import { Scene } from '../../src/ECS/Scene'; +import { EntitySystem } from '../../src/ECS/Systems/EntitySystem'; +import { Entity } from '../../src/ECS/Entity'; +import { Component } from '../../src/ECS/Component'; +import { Matcher } from '../../src/ECS/Utils/Matcher'; + +// 测试用组件 +class TestComponent extends Component { + public value: number = 0; + + constructor(value: number = 0) { + super(); + this.value = value; + } +} + +class PlayerComponent extends Component { + public playerId: string; + + constructor(playerId: string) { + super(); + this.playerId = playerId; + } +} + +// 测试用全局系统 +class TestGlobalSystem implements IGlobalSystem { + public readonly name = 'TestGlobalSystem'; + public updateCount: number = 0; + + public initialize(): void { + // 初始化逻辑 + } + + public update(): void { + this.updateCount++; + } + + public reset(): void { + this.updateCount = 0; + } + + public destroy(): void { + // 销毁逻辑 + } +} + +class TestSceneSystem extends EntitySystem { + public updateCount = 0; + + constructor() { + super(Matcher.empty().all(PlayerComponent)); + } + + protected override process(): void { + this.updateCount++; + } +} + +// 测试用Scene +class TestScene extends Scene { + public initializeCalled = false; + public beginCalled = false; + public endCalled = false; + + public override initialize(): void { + this.initializeCalled = true; + super.initialize(); + } + + public override begin(): void { + this.beginCalled = true; + super.begin(); + } + + public override end(): void { + this.endCalled = true; + super.end(); + } +} + +describe('World', () => { + let world: World; + + beforeEach(() => { + world = new World({ name: 'TestWorld' }); + }); + + afterEach(() => { + if (world) { + world.destroy(); + } + }); + + describe('基础功能', () => { + test('创建World时应该设置正确的配置', () => { + const config: IWorldConfig = { + name: 'GameWorld', + debug: true, + maxScenes: 5, + autoCleanup: false + }; + + const testWorld = new World(config); + + expect(testWorld.name).toBe('GameWorld'); + expect(testWorld.sceneCount).toBe(0); + expect(testWorld.isActive).toBe(false); + expect(testWorld.createdAt).toBeGreaterThan(0); + + testWorld.destroy(); + }); + + test('默认配置应该正确', () => { + const defaultWorld = new World(); + + expect(defaultWorld.name).toBe('World'); + expect(defaultWorld.sceneCount).toBe(0); + expect(defaultWorld.isActive).toBe(false); + + defaultWorld.destroy(); + }); + }); + + describe('Scene管理', () => { + test('创建Scene应该成功', () => { + const scene = world.createScene('test-scene'); + + expect(scene).toBeDefined(); + expect(world.sceneCount).toBe(1); + expect(world.getSceneIds()).toContain('test-scene'); + }); + + test('创建Scene时传入自定义Scene实例', () => { + const customScene = new TestScene(); + const scene = world.createScene('custom-scene', customScene); + + expect(scene).toBe(customScene); + expect(scene.initializeCalled).toBe(true); + expect(world.sceneCount).toBe(1); + }); + + test('重复的Scene ID应该抛出错误', () => { + world.createScene('duplicate'); + + expect(() => { + world.createScene('duplicate'); + }).toThrow("Scene ID 'duplicate' 已存在于World 'TestWorld' 中"); + }); + + test('超出最大Scene数量限制应该抛出错误', () => { + const limitedWorld = new World({ maxScenes: 2 }); + + limitedWorld.createScene('scene1'); + limitedWorld.createScene('scene2'); + + expect(() => { + limitedWorld.createScene('scene3'); + }).toThrow("World 'World' 已达到最大Scene数量限制: 2"); + + limitedWorld.destroy(); + }); + + test('获取Scene应该正确', () => { + const scene = world.createScene('get-test'); + const retrievedScene = world.getScene('get-test'); + + expect(retrievedScene).toBe(scene); + }); + + test('获取不存在的Scene应该返回null', () => { + const scene = world.getScene('non-existent'); + expect(scene).toBeNull(); + }); + + test('移除Scene应该正确清理', () => { + const testScene = new TestScene(); + world.createScene('remove-test', testScene); + world.setSceneActive('remove-test', true); + + const removed = world.removeScene('remove-test'); + + expect(removed).toBe(true); + expect(world.sceneCount).toBe(0); + expect(world.getScene('remove-test')).toBeNull(); + expect(testScene.endCalled).toBe(true); + }); + + test('移除不存在的Scene应该返回false', () => { + const removed = world.removeScene('non-existent'); + expect(removed).toBe(false); + }); + + test('获取所有Scene应该正确', () => { + const scene1 = world.createScene('scene1'); + const scene2 = world.createScene('scene2'); + + const allScenes = world.getAllScenes(); + + expect(allScenes).toHaveLength(2); + expect(allScenes).toContain(scene1); + expect(allScenes).toContain(scene2); + }); + }); + + describe('Scene激活管理', () => { + test('激活Scene应该正确', () => { + const testScene = new TestScene(); + world.createScene('active-test', testScene); + + world.setSceneActive('active-test', true); + + expect(world.isSceneActive('active-test')).toBe(true); + expect(world.getActiveSceneCount()).toBe(1); + expect(testScene.beginCalled).toBe(true); + }); + + test('停用Scene应该正确', () => { + world.createScene('deactive-test'); + world.setSceneActive('deactive-test', true); + + world.setSceneActive('deactive-test', false); + + expect(world.isSceneActive('deactive-test')).toBe(false); + expect(world.getActiveSceneCount()).toBe(0); + }); + + test('激活不存在的Scene应该记录警告', () => { + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + + world.setSceneActive('non-existent', true); + + // 注意:这里需要检查具体的日志实现,可能需要调整 + consoleSpy.mockRestore(); + }); + }); + + describe('全局System管理', () => { + test('添加全局System应该成功', () => { + const globalSystem = new TestGlobalSystem(); + + const addedSystem = world.addGlobalSystem(globalSystem); + + expect(addedSystem).toBe(globalSystem); + expect(world.getGlobalSystem(TestGlobalSystem)).toBe(globalSystem); + }); + + test('重复添加相同System应该返回原System', () => { + const globalSystem = new TestGlobalSystem(); + + const firstAdd = world.addGlobalSystem(globalSystem); + const secondAdd = world.addGlobalSystem(globalSystem); + + expect(firstAdd).toBe(secondAdd); + expect(firstAdd).toBe(globalSystem); + }); + + test('移除全局System应该成功', () => { + const globalSystem = new TestGlobalSystem(); + world.addGlobalSystem(globalSystem); + + const removed = world.removeGlobalSystem(globalSystem); + + expect(removed).toBe(true); + expect(world.getGlobalSystem(TestGlobalSystem)).toBeNull(); + }); + + test('移除不存在的System应该返回false', () => { + const globalSystem = new TestGlobalSystem(); + + const removed = world.removeGlobalSystem(globalSystem); + + expect(removed).toBe(false); + }); + + test('获取不存在的System类型应该返回null', () => { + const system = world.getGlobalSystem(TestGlobalSystem); + expect(system).toBeNull(); + }); + }); + + describe('World生命周期', () => { + test('启动World应该正确', () => { + const globalSystem = new TestGlobalSystem(); + world.addGlobalSystem(globalSystem); + + world.start(); + + expect(world.isActive).toBe(true); + }); + + test('重复启动World应该无效果', () => { + world.start(); + const firstActive = world.isActive; + + world.start(); + + expect(world.isActive).toBe(firstActive); + }); + + test('停止World应该停用所有Scene', () => { + const testScene = new TestScene(); + world.createScene('stop-test', testScene); + world.setSceneActive('stop-test', true); + world.start(); + + world.stop(); + + expect(world.isActive).toBe(false); + expect(world.isSceneActive('stop-test')).toBe(false); + }); + + test('销毁World应该清理所有资源', () => { + const testScene = new TestScene(); + const globalSystem = new TestGlobalSystem(); + + world.createScene('destroy-test', testScene); + world.addGlobalSystem(globalSystem); + world.start(); + + world.destroy(); + + expect(world.sceneCount).toBe(0); + expect(world.isActive).toBe(false); + expect(testScene.endCalled).toBe(true); + }); + }); + + describe('更新逻辑', () => { + test('updateGlobalSystems应该更新全局系统', () => { + const globalSystem = new TestGlobalSystem(); + world.addGlobalSystem(globalSystem); + world.start(); + + // 创建测试Scene + const scene = world.createScene('update-test'); + world.setSceneActive('update-test', true); + + // 直接测试全局系统更新 + world.updateGlobalSystems(); + + // 验证全局System被正确调用 + expect(globalSystem.updateCount).toBeGreaterThan(0); + }); + + test('未激活的World不应该更新', () => { + const globalSystem = new TestGlobalSystem(); + world.addGlobalSystem(globalSystem); + // 不启动World + + world.updateGlobalSystems(); + + expect(globalSystem.updateCount).toBe(0); + }); + + test('updateScenes应该更新激活的Scene', () => { + const scene1 = world.createScene('scene1'); + const scene2 = world.createScene('scene2'); + + scene1.addEntityProcessor(new TestSceneSystem()); + scene2.addEntityProcessor(new TestSceneSystem()); + + world.start(); + world.setSceneActive('scene1', true); + // scene2保持未激活 + + world.updateScenes(); + + // 这里需要根据具体的Scene更新实现来验证 + // 由于Scene.update()的具体实现可能不同,这里主要测试调用不出错 + expect(() => world.updateScenes()).not.toThrow(); + }); + }); + + describe('状态和统计', () => { + test('获取World状态应该正确', () => { + world.createScene('status-scene1'); + world.createScene('status-scene2'); + world.setSceneActive('status-scene1', true); + world.addGlobalSystem(new TestGlobalSystem()); + world.start(); + + const status = world.getStatus(); + + expect(status.name).toBe('TestWorld'); + expect(status.isActive).toBe(true); + expect(status.sceneCount).toBe(2); + expect(status.activeSceneCount).toBe(1); + expect(status.globalSystemCount).toBe(1); + expect(status.createdAt).toBeGreaterThan(0); + expect(status.scenes).toHaveLength(2); + + const activeScene = status.scenes.find(s => s.id === 'status-scene1'); + expect(activeScene?.isActive).toBe(true); + + const inactiveScene = status.scenes.find(s => s.id === 'status-scene2'); + expect(inactiveScene?.isActive).toBe(false); + }); + + test('获取World统计应该包含基本信息', () => { + world.addGlobalSystem(new TestGlobalSystem()); + + const scene = world.createScene('stats-scene'); + const entity = scene.createEntity('stats-entity'); + entity.addComponent(new TestComponent()); + + const stats = world.getStats(); + + expect(stats).toHaveProperty('totalEntities'); + expect(stats).toHaveProperty('totalSystems'); + expect(stats).toHaveProperty('memoryUsage'); + expect(stats).toHaveProperty('performance'); + expect(stats.totalSystems).toBeGreaterThanOrEqual(1); + }); + }); + + describe('自动清理功能', () => { + test('自动清理应该移除空闲Scene', async () => { + // 创建一个启用自动清理的World + const autoCleanWorld = new World({ + name: 'AutoCleanWorld', + autoCleanup: true, + maxScenes: 10 + }); + + // 创建一个空Scene + autoCleanWorld.createScene('empty-scene'); + autoCleanWorld.start(); + + // 手动触发清理检查 + autoCleanWorld.updateScenes(); + + // 由于清理策略基于时间,这里主要测试不会出错 + expect(() => autoCleanWorld.updateScenes()).not.toThrow(); + + autoCleanWorld.destroy(); + }); + }); + + describe('错误处理', () => { + test('Scene ID为空时应该创建默认ID', () => { + expect(() => { + world.createScene(''); + }).not.toThrow(); + }); + + test('极限情况下的资源管理', () => { + // 创建大量Scene + for (let i = 0; i < 5; i++) { + world.createScene(`scene_${i}`); + world.setSceneActive(`scene_${i}`, true); + } + + // 添加多个全局System + for (let i = 0; i < 3; i++) { + world.addGlobalSystem(new TestGlobalSystem()); + } + + world.start(); + + // 测试批量清理 + expect(() => world.destroy()).not.toThrow(); + }); + }); +}); \ No newline at end of file diff --git a/packages/core/tests/ECS/WorldManager.test.ts b/packages/core/tests/ECS/WorldManager.test.ts new file mode 100644 index 00000000..d656f11b --- /dev/null +++ b/packages/core/tests/ECS/WorldManager.test.ts @@ -0,0 +1,464 @@ +import { WorldManager, IWorldManagerConfig } from '../../src/ECS/WorldManager'; +import { World, IWorldConfig } from '../../src/ECS/World'; +import { Scene } from '../../src/ECS/Scene'; +import { EntitySystem } from '../../src/ECS/Systems/EntitySystem'; +import { Component } from '../../src/ECS/Component'; +import { Matcher } from '../../src/ECS/Utils/Matcher'; + +// 测试用组件 +class TestComponent extends Component { + public value: number = 0; + + constructor(value: number = 0) { + super(); + this.value = value; + } +} + +// 测试用全局系统 +class TestGlobalSystem { + public readonly name = 'TestGlobalSystem'; + public updateCount: number = 0; + + public initialize(): void { + // 初始化 + } + + public update(): void { + this.updateCount++; + } + + public reset(): void { + this.updateCount = 0; + } + + public destroy(): void { + // 销毁 + } +} + +describe('WorldManager', () => { + let worldManager: WorldManager; + + beforeEach(() => { + // 重置单例 + WorldManager['_instance'] = null; + worldManager = WorldManager.getInstance(); + }); + + afterEach(() => { + // 清理所有World + if (worldManager) { + const worldIds = worldManager.getWorldIds(); + worldIds.forEach(id => { + worldManager.removeWorld(id); + }); + // 清理定时器 + worldManager.destroy(); + } + WorldManager['_instance'] = null; + }); + + describe('单例模式', () => { + test('获取实例应该返回相同的实例', () => { + const instance1 = WorldManager.getInstance(); + const instance2 = WorldManager.getInstance(); + + expect(instance1).toBe(instance2); + }); + + test('使用配置创建实例应该正确', () => { + WorldManager['_instance'] = null; + + const config: IWorldManagerConfig = { + maxWorlds: 10, + autoCleanup: true, + debug: false + }; + + const instance = WorldManager.getInstance(config); + + expect(instance).toBeDefined(); + expect(instance).toBe(WorldManager.getInstance()); + }); + }); + + describe('World管理', () => { + test('创建World应该成功', () => { + const world = worldManager.createWorld('test-world'); + + expect(world).toBeDefined(); + expect(world.name).toBe('test-world'); + expect(worldManager.getWorld('test-world')).toBeDefined(); + expect(worldManager.getWorldIds()).toContain('test-world'); + }); + + test('创建World时传入配置应该正确', () => { + const worldConfig: IWorldConfig = { + name: 'ConfiguredWorld', + debug: true, + maxScenes: 5, + autoCleanup: false + }; + + const world = worldManager.createWorld('configured-world', worldConfig); + + expect(world.name).toBe('ConfiguredWorld'); + }); + + test('重复的World ID应该抛出错误', () => { + worldManager.createWorld('duplicate-world'); + + expect(() => { + worldManager.createWorld('duplicate-world'); + }).toThrow("World ID 'duplicate-world' 已存在"); + }); + + test('超出最大World数量应该抛出错误', () => { + WorldManager['_instance'] = null; + const limitedManager = WorldManager.getInstance({ maxWorlds: 2 }); + + limitedManager.createWorld('world1'); + limitedManager.createWorld('world2'); + + expect(() => { + limitedManager.createWorld('world3'); + }).toThrow("已达到最大World数量限制: 2"); + + // 清理 + limitedManager.removeWorld('world1'); + limitedManager.removeWorld('world2'); + }); + + test('获取World应该正确', () => { + const world = worldManager.createWorld('get-world'); + const retrievedWorld = worldManager.getWorld('get-world'); + + expect(retrievedWorld).toBe(world); + }); + + test('获取不存在的World应该返回null', () => { + const world = worldManager.getWorld('non-existent'); + expect(world).toBeNull(); + }); + + test('检查World存在性应该正确', () => { + expect(worldManager.getWorld('non-existent')).toBeNull(); + + worldManager.createWorld('exists'); + expect(worldManager.getWorld('exists')).toBeDefined(); + }); + + test('销毁World应该正确清理', () => { + const world = worldManager.createWorld('destroy-world'); + world.start(); + + const destroyed = worldManager.removeWorld('destroy-world'); + + expect(destroyed).toBe(true); + expect(worldManager.getWorld('destroy-world')).toBeNull(); + }); + + test('销毁不存在的World应该返回false', () => { + const destroyed = worldManager.removeWorld('non-existent'); + expect(destroyed).toBe(false); + }); + + test('获取所有World ID应该正确', () => { + worldManager.createWorld('world1'); + worldManager.createWorld('world2'); + worldManager.createWorld('world3'); + + const worldIds = worldManager.getWorldIds(); + + expect(worldIds).toHaveLength(3); + expect(worldIds).toContain('world1'); + expect(worldIds).toContain('world2'); + expect(worldIds).toContain('world3'); + }); + }); + + describe('活跃World管理', () => { + test('启动World应该加入活跃列表', () => { + const world = worldManager.createWorld('active-world'); + + worldManager.setWorldActive('active-world', true); + + const activeWorlds = worldManager.getActiveWorlds(); + expect(activeWorlds).toHaveLength(1); + expect(activeWorlds[0]).toBe(world); + }); + + test('停止World应该从活跃列表移除', () => { + const world = worldManager.createWorld('inactive-world'); + worldManager.setWorldActive('inactive-world', true); + + worldManager.setWorldActive('inactive-world', false); + + const activeWorlds = worldManager.getActiveWorlds(); + expect(activeWorlds).toHaveLength(0); + }); + + test('销毁激活的World应该从活跃列表移除', () => { + const world = worldManager.createWorld('destroy-active'); + worldManager.setWorldActive('destroy-active', true); + + worldManager.removeWorld('destroy-active'); + + const activeWorlds = worldManager.getActiveWorlds(); + expect(activeWorlds).toHaveLength(0); + }); + + test('多个World的激活状态应该独立管理', () => { + const world1 = worldManager.createWorld('world1'); + const world2 = worldManager.createWorld('world2'); + const world3 = worldManager.createWorld('world3'); + + worldManager.setWorldActive('world1', true); + worldManager.setWorldActive('world3', true); + // world2 保持未启动 + + const activeWorlds = worldManager.getActiveWorlds(); + + expect(activeWorlds).toHaveLength(2); + expect(activeWorlds).toContain(world1); + expect(activeWorlds).toContain(world3); + expect(activeWorlds).not.toContain(world2); + }); + }); + + describe('统计和监控', () => { + test('获取WorldManager状态应该正确', () => { + worldManager.createWorld('status-world1'); + const world2 = worldManager.createWorld('status-world2'); + worldManager.setWorldActive('status-world2', true); + + const status = worldManager.getStats(); + + expect(status.totalWorlds).toBe(2); + expect(status.activeWorlds).toBe(1); + expect(status.config.maxWorlds).toBeGreaterThan(0); + expect(status.memoryUsage).toBeGreaterThanOrEqual(0); + expect(status.isRunning).toBeDefined(); + }); + + test('获取所有World统计应该包含详细信息', () => { + const world1 = worldManager.createWorld('stats-world1'); + const world2 = worldManager.createWorld('stats-world2'); + + // 为world1添加一些内容 + const scene1 = world1.createScene('scene1'); + scene1.createEntity('entity1'); + worldManager.setWorldActive('stats-world1', true); + + // world2保持空 + + const allStats = worldManager.getDetailedStatus().worlds; + + expect(allStats).toHaveLength(2); + + const world1Stats = allStats.find(stat => stat.id === 'stats-world1'); + const world2Stats = allStats.find(stat => stat.id === 'stats-world2'); + + expect(world1Stats).toBeDefined(); + expect(world2Stats).toBeDefined(); + expect(world1Stats?.isActive).toBe(true); + expect(world2Stats?.isActive).toBe(false); + }); + + test('空WorldManager的统计应该正确', () => { + const status = worldManager.getStats(); + const allStats = worldManager.getDetailedStatus().worlds; + + expect(status.totalWorlds).toBe(0); + expect(status.activeWorlds).toBe(0); + expect(allStats).toHaveLength(0); + }); + }); + + describe('清理功能', () => { + test('清理空闲World应该移除符合条件的World', () => { + // 创建一个空的World + const emptyWorld = worldManager.createWorld('empty-world'); + + // 创建一个有内容的World + const fullWorld = worldManager.createWorld('full-world'); + const scene = fullWorld.createScene('scene'); + scene.createEntity('entity'); + fullWorld.start(); + + // 执行清理 + const cleanedCount = worldManager.cleanup(); + + // 由于清理逻辑可能基于时间或其他条件,这里主要测试不会出错 + expect(cleanedCount).toBeGreaterThanOrEqual(0); + expect(() => worldManager.cleanup()).not.toThrow(); + }); + }); + + describe('World更新协调', () => { + test('更新所有活跃World应该正确', () => { + const world1 = worldManager.createWorld('update-world1'); + const world2 = worldManager.createWorld('update-world2'); + const world3 = worldManager.createWorld('update-world3'); + + // 添加一些内容到World中 + const scene1 = world1.createScene('scene1'); + const scene2 = world2.createScene('scene2'); + + scene1.createEntity('entity1'); + scene2.createEntity('entity2'); + + // 启动部分World + worldManager.setWorldActive('update-world1', true); + worldManager.setWorldActive('update-world2', true); + // world3保持未启动 + + // 手动调用更新(通常由Core.update()调用) + const activeWorlds = worldManager.getActiveWorlds(); + + expect(() => { + activeWorlds.forEach(world => { + world.updateGlobalSystems(); + world.updateScenes(); + }); + }).not.toThrow(); + + expect(activeWorlds).toHaveLength(2); + }); + }); + + describe('边界情况和错误处理', () => { + test('World ID为空字符串应该抛出错误', () => { + expect(() => { + worldManager.createWorld(''); + }).toThrow(); + }); + + test('World ID为null或undefined应该抛出错误', () => { + expect(() => { + worldManager.createWorld(null as any); + }).toThrow(); + + expect(() => { + worldManager.createWorld(undefined as any); + }).toThrow(); + }); + + test('极限情况下的大量World管理', () => { + const worldCount = 50; + const worldIds: string[] = []; + + // 创建大量World + for (let i = 0; i < worldCount; i++) { + const worldId = `mass-world-${i}`; + worldIds.push(worldId); + + expect(() => { + worldManager.createWorld(worldId); + }).not.toThrow(); + } + + expect(worldManager.getWorldIds()).toHaveLength(worldCount); + + // 启动一半的World + for (let i = 0; i < worldCount / 2; i++) { + worldManager.setWorldActive(worldIds[i], true); + } + + expect(worldManager.getActiveWorlds()).toHaveLength(worldCount / 2); + + // 批量清理 + worldIds.forEach(id => { + expect(() => { + worldManager.removeWorld(id); + }).not.toThrow(); + }); + + expect(worldManager.getWorldIds()).toHaveLength(0); + }); + + test('销毁后获取World应该返回null', () => { + worldManager.createWorld('temp-world'); + worldManager.removeWorld('temp-world'); + + expect(worldManager.getWorld('temp-world')).toBeNull(); + }); + }); + + describe('内存管理', () => { + test('销毁所有World后内存应该被释放', () => { + // 创建多个World并添加内容 + for (let i = 0; i < 10; i++) { + const world = worldManager.createWorld(`memory-world-${i}`); + const scene = world.createScene('scene'); + + // 添加一些实体和系统 + for (let j = 0; j < 5; j++) { + const entity = scene.createEntity(`entity-${j}`); + entity.addComponent(new TestComponent(j)); + } + + world.addGlobalSystem(new TestGlobalSystem()); + worldManager.setWorldActive(`memory-world-${i}`, true); + } + + const beforeCleanup = worldManager.getStats(); + expect(beforeCleanup.totalWorlds).toBe(10); + expect(beforeCleanup.activeWorlds).toBe(10); + + // 清理所有World + const worldIds = worldManager.getWorldIds(); + worldIds.forEach(id => { + worldManager.removeWorld(id); + }); + + const afterCleanup = worldManager.getStats(); + expect(afterCleanup.totalWorlds).toBe(0); + expect(afterCleanup.activeWorlds).toBe(0); + }); + }); + + describe('配置验证', () => { + test('无效的maxWorlds配置应该使用默认值', () => { + WorldManager['_instance'] = null; + + const invalidConfig: IWorldManagerConfig = { + maxWorlds: -1, + autoCleanup: true, + debug: true + }; + + expect(() => { + WorldManager.getInstance(invalidConfig); + }).not.toThrow(); + }); + + test('配置更新应该影响后续操作', () => { + WorldManager['_instance'] = null; + + const config: IWorldManagerConfig = { + maxWorlds: 3, + autoCleanup: true, + debug: true + }; + + const manager = WorldManager.getInstance(config); + + // 创建到限制数量的World + manager.createWorld('world1'); + manager.createWorld('world2'); + manager.createWorld('world3'); + + // 第四个应该失败 + expect(() => { + manager.createWorld('world4'); + }).toThrow(); + + // 清理 + manager.removeWorld('world1'); + manager.removeWorld('world2'); + manager.removeWorld('world3'); + }); + }); +}); \ No newline at end of file