Compare commits

...

34 Commits

Author SHA1 Message Date
YHH
d798995876 Merge pull request #111 from esengine/issue-110-_Inject_装饰器在_Cocos_Creator_环境中不执行
扩展 InjectableMetadata 接口支持属性注入,实现 @InjectProperty 装饰器
2025-10-12 23:44:22 +08:00
YHH
43e6b7bf88 扩展 InjectableMetadata 接口支持属性注入,实现 @InjectProperty 装饰器 2025-10-12 23:39:32 +08:00
YHH
9253686de1 v2.2.3 2025-10-12 21:41:09 +08:00
YHH
7e7eae2d1a Merge pull request #109 from esengine/issue-108-world和scene进行了多次更新
Revert "Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰"
2025-10-12 21:39:35 +08:00
YHH
1924d979d6 Revert "Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰"
This reverts commit f2b9c5cc5a, reversing
changes made to 5f507532ed.
2025-10-12 21:38:53 +08:00
YHH
ed84394301 更新序列化文档 2025-10-12 19:02:17 +08:00
YHH
bb99cf5389 v2.2.2 2025-10-12 18:56:39 +08:00
YHH
2d0700f441 Merge pull request #107 from esengine/issue-106-类型定义声明返回_Buffer导致浏览器兼容问题
修复buffer再浏览器环境不兼容的问题
2025-10-12 18:53:53 +08:00
YHH
e3ead8a695 修复buffer再浏览器环境不兼容的问题 2025-10-12 18:49:20 +08:00
YHH
701f538e57 Merge pull request #104 from esengine/issue-103-WorldManager统一管理所有World实例_含默认World
Issue 103 world manager统一管理所有world实例 含默认world
2025-10-11 15:26:27 +08:00
YHH
bb3017ffc2 Merge remote-tracking branch 'origin/master' into issue-103-WorldManager统一管理所有World实例_含默认World
# Conflicts:
#	packages/core/src/Core.ts
#	packages/core/src/ECS/SceneManager.ts
2025-10-11 15:21:43 +08:00
YHH
f2b9c5cc5a Merge pull request #102 from esengine/issue-74-World与Scene关系不清晰
统一World与Scene架构,SceneManager内部使用DefaultWorld
2025-10-11 15:16:52 +08:00
YHH
532a52acfc 统一的World管理路径 2025-10-11 15:14:37 +08:00
YHH
c19b5ae9a7 统一World与Scene架构,SceneManager内部使用DefaultWorld 2025-10-11 14:44:21 +08:00
YHH
5f507532ed 统一World与Scene架构,SceneManager内部使用DefaultWorld 2025-10-11 14:27:09 +08:00
YHH
6e48f22540 更新v2.2.1文档 2025-10-11 11:33:07 +08:00
YHH
66aa9f4f20 更新文档 2025-10-11 10:48:24 +08:00
YHH
62f895efe0 v2.2.1 2025-10-11 10:46:46 +08:00
YHH
4a060e1ce3 WorldManager 现在由 ServiceContainer 统一管理 2025-10-11 10:40:10 +08:00
YHH
a0177c9163 从 tslib 导入辅助函数 2025-10-11 10:36:59 +08:00
YHH
f45af34614 更新v2.2.0文档 2025-10-11 10:16:52 +08:00
YHH
14a8d755f0 PoolManager 现在由 ServiceContainer 统一管理 2025-10-11 09:38:16 +08:00
YHH
b67ab80c75 Merge pull request #93 from esengine/issue-80-插件系统
插件系统
2025-10-11 09:32:12 +08:00
YHH
ae71af856b 插件系统 2025-10-11 09:26:36 +08:00
YHH
279c1d9bc9 Merge pull request #92 from esengine/issue-82-组件引用完整性
组件引用完整性,升级到es2021使用weakref
2025-10-11 00:30:36 +08:00
YHH
9068a109b0 降级es2020,实现 WeakRef Polyfill 2025-10-11 00:25:10 +08:00
YHH
7850fc610c 组件引用完整性,升级到es2021使用weakref 2025-10-10 23:38:48 +08:00
YHH
536871d09b Merge pull request #88 from esengine/issue-76-依赖注入
依赖注入引入DI容器
2025-10-10 22:15:24 +08:00
YHH
1af2cf5f99 Scene 构造函数注入 PerformanceMonitor 2025-10-10 22:08:10 +08:00
YHH
b13132b259 依赖注入引入DI容器 2025-10-10 21:52:43 +08:00
YHH
a1a6970ea4 Merge pull request #87 from esengine/issue-73-Core类职责过重需要进行拆分
新增ServiceContainer服务容器, 所有服务统一实现 IService 接口
2025-10-10 18:17:55 +08:00
YHH
41bbe23404 新增ServiceContainer服务容器, 所有服务统一实现 IService 接口 2025-10-10 18:13:28 +08:00
YHH
1d2a3e283e Merge pull request #86 from esengine/issue-75-组件存储策略不统一
组件存储策略不统一
2025-10-10 16:38:06 +08:00
YHH
62d7521384 移除 Entity._localComponents/强制Entity必须属于Scene/简化组件操作逻辑 2025-10-10 16:31:43 +08:00
62 changed files with 8158 additions and 729 deletions

View File

@@ -73,13 +73,28 @@ export default defineConfig({
{ text: 'Worker系统 (多线程)', link: '/guide/worker-system' }
]
},
{ text: '场景管理 (Scene)', link: '/guide/scene' },
{
text: '场景管理 (Scene)',
link: '/guide/scene',
items: [
{ text: 'SceneManager', link: '/guide/scene-manager' },
{ text: 'WorldManager', link: '/guide/world-manager' }
]
},
{ text: '序列化系统 (Serialization)', link: '/guide/serialization' },
{ text: '事件系统 (Event)', link: '/guide/event-system' },
{ text: '时间和定时器 (Time)', link: '/guide/time-and-timers' },
{ text: '日志系统 (Logger)', link: '/guide/logging' }
]
},
{
text: '高级特性',
collapsed: false,
items: [
{ text: '服务容器 (Service Container)', link: '/guide/service-container' },
{ text: '插件系统 (Plugin System)', link: '/guide/plugin-system' }
]
},
{
text: '平台适配器',
link: '/guide/platform-adapter',

View File

@@ -63,14 +63,14 @@ class Health extends Component {
- 框架能正确管理组件注册
```typescript
// 正确的用法
// 正确的用法
@ECSComponent('Velocity')
class Velocity extends Component {
dx: number = 0;
dy: number = 0;
}
// 错误的用法 - 没有装饰器
// 错误的用法 - 没有装饰器
class BadComponent extends Component {
// 这样定义的组件可能在生产环境出现问题
}
@@ -90,7 +90,7 @@ class ExampleComponent extends Component {
* 用于初始化资源、建立引用等
*/
onAddedToEntity(): void {
console.log(`组件 ${this.constructor.name} 添加实体 ${this.entity.name}`);
console.log(`组件 ${this.constructor.name} 添加实体ID: ${this.entityId}`);
this.resource = new SomeResource();
}
@@ -99,7 +99,7 @@ class ExampleComponent extends Component {
* 用于清理资源、断开引用等
*/
onRemovedFromEntity(): void {
console.log(`组件 ${this.constructor.name} 从实体 ${this.entity.name} 移除`);
console.log(`组件 ${this.constructor.name} 移除`);
if (this.resource) {
this.resource.cleanup();
this.resource = null;
@@ -108,30 +108,58 @@ class ExampleComponent extends Component {
}
```
## 访问实体
## 组件与实体的关系
组件可以通过 `this.entity` 访问其所属的实体:
组件存储了所属实体的ID (`entityId`)而不是直接引用实体对象。这是ECS数据导向设计的体现避免了循环引用。
在实际使用中,**应该在 System 中处理实体和组件的交互**,而不是在组件内部:
```typescript
@ECSComponent('Damage')
class Damage extends Component {
damage: number;
@ECSComponent('Health')
class Health extends Component {
current: number;
max: number;
constructor(damage: number) {
constructor(max: number = 100) {
super();
this.damage = damage;
this.max = max;
this.current = max;
}
// 在组件方法中访问实体和其他组件
applyDamage(): void {
const health = this.entity.getComponent(Health);
if (health) {
health.takeDamage(this.damage);
isDead(): boolean {
return this.current <= 0;
}
}
@ECSComponent('Damage')
class Damage extends Component {
value: number;
constructor(value: number) {
super();
this.value = value;
}
}
// 推荐:在 System 中处理逻辑
class DamageSystem extends EntitySystem {
constructor() {
super(new Matcher().all(Health, Damage));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health)!;
const damage = entity.getComponent(Damage)!;
health.current -= damage.value;
// 如果生命值为0销毁实体
if (health.isDead()) {
this.entity.destroy();
entity.destroy();
}
// 应用伤害后移除 Damage 组件
entity.removeComponent(damage);
}
}
}
@@ -146,9 +174,27 @@ class Damage extends Component {
class ExampleComponent extends Component {
someData: string = "example";
showComponentInfo(): void {
console.log(`组件ID: ${this.id}`); // 唯一的组件ID
console.log(`所属实体: ${this.entity.name}`); // 所属实体引用
onAddedToEntity(): void {
console.log(`组件ID: ${this.id}`); // 唯一的组件ID
console.log(`所属实体ID: ${this.entityId}`); // 所属实体的ID
}
}
```
如果需要访问实体对象,应该在 System 中进行:
```typescript
class ExampleSystem extends EntitySystem {
constructor() {
super(new Matcher().all(ExampleComponent));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const comp = entity.getComponent(ExampleComponent)!;
console.log(`实体名称: ${entity.name}`);
console.log(`组件数据: ${comp.someData}`);
}
}
}
```
@@ -245,7 +291,7 @@ class WeaponConfig extends Component {
### 1. 保持组件简单
```typescript
// 好的组件设计 - 单一职责
// 好的组件设计 - 单一职责
@ECSComponent('Position')
class Position extends Component {
x: number = 0;
@@ -258,7 +304,7 @@ class Velocity extends Component {
dy: number = 0;
}
// 避免的组件设计 - 职责过多
// 避免的组件设计 - 职责过多
@ECSComponent('GameObject')
class GameObject extends Component {
x: number;
@@ -330,16 +376,11 @@ class Inventory extends Component {
}
```
### 4. 避免在组件中存储实体引用
### 4. 引用其他实体
当组件需要关联其他实体时(如父子关系、跟随目标等),**推荐方式是存储实体ID**,然后在 System 中查找:
```typescript
// ❌ 避免:在组件中存储其他实体的引用
@ECSComponent('BadFollower')
class BadFollower extends Component {
target: Entity; // 直接引用可能导致内存泄漏
}
// ✅ 推荐存储实体ID通过场景查找
@ECSComponent('Follower')
class Follower extends Component {
targetId: number;
@@ -349,11 +390,269 @@ class Follower extends Component {
super();
this.targetId = targetId;
}
}
getTarget(): Entity | null {
return this.entity.scene?.findEntityById(this.targetId) || null;
// 在 System 中查找目标实体并处理逻辑
class FollowerSystem extends EntitySystem {
constructor() {
super(new Matcher().all(Follower, Position));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const follower = entity.getComponent(Follower)!;
const position = entity.getComponent(Position)!;
// 通过场景查找目标实体
const target = entity.scene?.findEntityById(follower.targetId);
if (target) {
const targetPos = target.getComponent(Position);
if (targetPos) {
// 跟随逻辑
const dx = targetPos.x - position.x;
const dy = targetPos.y - position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > follower.followDistance) {
// 移动靠近目标
}
}
}
}
}
}
```
这种方式的优势:
- 组件保持简单,只存储基本数据类型
- 符合数据导向设计
- 在 System 中统一处理查找和逻辑
- 易于理解和维护
**避免在组件中直接存储实体引用**
```typescript
// 错误示范:直接存储实体引用
@ECSComponent('BadFollower')
class BadFollower extends Component {
target: Entity; // 实体销毁后仍持有引用,可能导致内存泄漏
}
```
## 高级特性
### EntityRef 装饰器 - 自动引用追踪
框架提供了 `@EntityRef` 装饰器用于**特殊场景**下安全地存储实体引用。这是一个高级特性,一般情况下推荐使用存储ID的方式。
#### 什么时候需要 EntityRef
在以下场景中,`@EntityRef` 可以简化代码:
1. **父子关系**: 需要在组件中直接访问父实体或子实体
2. **复杂关联**: 实体之间有多个引用关系
3. **频繁访问**: 需要在多处访问引用的实体,使用ID查找会有性能开销
#### 核心特性
`@EntityRef` 装饰器通过 **ReferenceTracker** 自动追踪引用关系:
- 当被引用的实体销毁时,所有指向它的 `@EntityRef` 属性自动设为 `null`
- 防止跨场景引用(会输出警告并拒绝设置)
- 防止引用已销毁的实体(会输出警告并设为 `null`)
- 使用 WeakRef 避免内存泄漏(自动GC支持)
- 组件移除时自动清理引用注册
#### 基本用法
```typescript
import { Component, ECSComponent, EntityRef, Entity } from '@esengine/ecs-framework';
@ECSComponent('Parent')
class ParentComponent extends Component {
@EntityRef()
parent: Entity | null = null;
}
// 使用示例
const scene = new Scene();
const parent = scene.createEntity('Parent');
const child = scene.createEntity('Child');
const comp = child.addComponent(new ParentComponent());
comp.parent = parent;
console.log(comp.parent); // Entity { name: 'Parent' }
// 当 parent 被销毁时comp.parent 自动变为 null
parent.destroy();
console.log(comp.parent); // null
```
#### 多个引用属性
一个组件可以有多个 `@EntityRef` 属性:
```typescript
@ECSComponent('Combat')
class CombatComponent extends Component {
@EntityRef()
target: Entity | null = null;
@EntityRef()
ally: Entity | null = null;
@EntityRef()
lastAttacker: Entity | null = null;
}
// 使用示例
const player = scene.createEntity('Player');
const enemy = scene.createEntity('Enemy');
const npc = scene.createEntity('NPC');
const combat = player.addComponent(new CombatComponent());
combat.target = enemy;
combat.ally = npc;
// enemy 销毁后,只有 target 变为 nullally 仍然有效
enemy.destroy();
console.log(combat.target); // null
console.log(combat.ally); // Entity { name: 'NPC' }
```
#### 安全检查
`@EntityRef` 提供了多重安全检查:
```typescript
const scene1 = new Scene();
const scene2 = new Scene();
const entity1 = scene1.createEntity('Entity1');
const entity2 = scene2.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
// 跨场景引用会失败
comp.parent = entity2; // 输出错误日志comp.parent 为 null
console.log(comp.parent); // null
// 引用已销毁的实体会失败
const entity3 = scene1.createEntity('Entity3');
entity3.destroy();
comp.parent = entity3; // 输出警告日志comp.parent 为 null
console.log(comp.parent); // null
```
#### 实现原理
`@EntityRef` 使用以下机制实现自动引用追踪:
1. **ReferenceTracker**: Scene 持有一个引用追踪器,记录所有实体引用关系
2. **WeakRef**: 使用弱引用存储组件,避免循环引用导致内存泄漏
3. **属性拦截**: 通过 `Object.defineProperty` 拦截 getter/setter
4. **自动清理**: 实体销毁时,ReferenceTracker 遍历所有引用并设为 null
```typescript
// 简化的实现原理
class ReferenceTracker {
// entityId -> 引用该实体的所有组件记录
private _references: Map<number, Set<{ component: WeakRef<Component>, propertyKey: string }>>;
// 实体销毁时调用
clearReferencesTo(entityId: number): void {
const records = this._references.get(entityId);
if (records) {
for (const record of records) {
const component = record.component.deref();
if (component) {
// 将组件的引用属性设为 null
(component as any)[record.propertyKey] = null;
}
}
this._references.delete(entityId);
}
}
}
```
#### 性能考虑
`@EntityRef` 会带来一些性能开销:
- **写入开销**: 每次设置引用时需要更新 ReferenceTracker
- **内存开销**: ReferenceTracker 需要维护引用映射表
- **销毁开销**: 实体销毁时需要遍历所有引用并清理
对于大多数场景,这些开销是可以接受的。但如果有**大量实体和频繁的引用变更**,存储ID可能更高效。
#### 最佳实践
```typescript
// 推荐:适合使用 @EntityRef 的场景 - 父子关系
@ECSComponent('Transform')
class Transform extends Component {
@EntityRef()
parent: Entity | null = null;
position: { x: number, y: number } = { x: 0, y: 0 };
// 可以直接访问父实体的组件
getWorldPosition(): { x: number, y: number } {
if (!this.parent) {
return { ...this.position };
}
const parentTransform = this.parent.getComponent(Transform);
if (parentTransform) {
const parentPos = parentTransform.getWorldPosition();
return {
x: parentPos.x + this.position.x,
y: parentPos.y + this.position.y
};
}
return { ...this.position };
}
}
// 不推荐:不适合使用 @EntityRef 的场景 - 大量动态目标
@ECSComponent('AITarget')
class AITarget extends Component {
@EntityRef()
target: Entity | null = null; // 如果目标频繁变化用ID更好
updateCooldown: number = 0;
}
// 推荐这种场景用ID更好
@ECSComponent('AITarget')
class AITargetBetter extends Component {
targetId: number | null = null; // 存储ID
updateCooldown: number = 0;
}
```
#### 调试支持
ReferenceTracker 提供了调试接口:
```typescript
// 查看某个实体被哪些组件引用
const references = scene.referenceTracker.getReferencesTo(entity.id);
console.log(`实体 ${entity.name}${references.length} 个组件引用`);
// 获取完整的调试信息
const debugInfo = scene.referenceTracker.getDebugInfo();
console.log(debugInfo);
```
#### 总结
- **推荐做法**: 大部分情况使用存储ID + System查找的方式
- **EntityRef 适用场景**: 父子关系、复杂关联、组件内需要直接访问引用实体的场景
- **核心优势**: 自动清理、防止悬空引用、代码更简洁
- **注意事项**: 有性能开销,不适合大量动态引用的场景
组件是 ECS 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。

View File

@@ -314,8 +314,8 @@ import { Core, WorldManager } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 创建世界管理器(手动管理
const worldManager = new WorldManager();
// 从服务容器获取 WorldManagerCore 已自动创建并注册
const worldManager = Core.services.resolve(WorldManager);
// 创建多个独立的游戏世界
const room1 = worldManager.createWorld('room_001');

View File

@@ -29,4 +29,12 @@
掌握分级日志系统,用于调试、监控和错误追踪。
### [平台适配器 (Platform Adapter)](./platform-adapter.md)
了解如何为不同平台实现和注册平台适配器支持浏览器、小游戏、Node.js等环境。
了解如何为不同平台实现和注册平台适配器支持浏览器、小游戏、Node.js等环境。
## 高级特性
### [服务容器 (Service Container)](./service-container.md)
掌握依赖注入和服务管理,实现松耦合的架构设计。
### [插件系统 (Plugin System)](./plugin-system.md)
学习如何开发和使用插件,扩展框架功能,实现功能模块化。

643
docs/guide/plugin-system.md Normal file
View File

@@ -0,0 +1,643 @@
# 插件系统
插件系统允许你以模块化的方式扩展 ECS Framework 的功能。通过插件,你可以封装特定功能(如网络同步、物理引擎、调试工具等),并在多个项目中复用。
## 概述
### 什么是插件
插件是实现了 `IPlugin` 接口的类,可以在运行时动态安装到框架中。插件可以:
- 注册自定义服务到服务容器
- 添加系统到场景
- 注册自定义组件
- 扩展框架功能
### 插件的优势
- **模块化**: 将功能封装为独立模块,提高代码可维护性
- **可复用**: 同一个插件可以在多个项目中使用
- **解耦**: 核心框架与扩展功能分离
- **热插拔**: 运行时动态安装和卸载插件
## 快速开始
### 创建第一个插件
创建一个简单的调试插件:
```typescript
import { IPlugin, Core, ServiceContainer } from '@esengine/ecs-framework';
class DebugPlugin implements IPlugin {
readonly name = 'debug-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
console.log('Debug plugin installed');
// 可以在这里注册服务、添加系统等
}
uninstall(): void {
console.log('Debug plugin uninstalled');
// 清理资源
}
}
```
### 安装插件
使用 `Core.installPlugin()` 安装插件:
```typescript
import { Core } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 安装插件
await Core.installPlugin(new DebugPlugin());
// 检查插件是否已安装
if (Core.isPluginInstalled('debug-plugin')) {
console.log('Debug plugin is running');
}
```
### 卸载插件
```typescript
// 卸载插件
await Core.uninstallPlugin('debug-plugin');
```
### 获取插件实例
```typescript
// 获取已安装的插件
const plugin = Core.getPlugin('debug-plugin');
if (plugin) {
console.log(`Plugin version: ${plugin.version}`);
}
```
## 插件开发
### IPlugin 接口
所有插件必须实现 `IPlugin` 接口:
```typescript
export interface IPlugin {
// 插件唯一名称
readonly name: string;
// 插件版本建议遵循semver规范
readonly version: string;
// 依赖的其他插件(可选)
readonly dependencies?: readonly string[];
// 安装插件时调用
install(core: Core, services: ServiceContainer): void | Promise<void>;
// 卸载插件时调用
uninstall(): void | Promise<void>;
}
```
### 插件生命周期
#### install 方法
在插件安装时调用,用于初始化插件:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 1. 注册服务
services.registerSingleton(MyService);
// 2. 访问当前场景
const scene = core.scene;
if (scene) {
// 3. 添加系统
scene.addSystem(new MySystem());
}
// 4. 其他初始化逻辑
console.log('Plugin initialized');
}
uninstall(): void {
// 清理逻辑
}
}
```
#### uninstall 方法
在插件卸载时调用,用于清理资源:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private myService?: MyService;
install(core: Core, services: ServiceContainer): void {
this.myService = new MyService();
services.registerInstance(MyService, this.myService);
}
uninstall(): void {
// 清理服务
if (this.myService) {
this.myService.dispose();
this.myService = undefined;
}
// 移除事件监听器
// 释放其他资源
}
}
```
### 异步插件
插件的 `install``uninstall` 方法都支持异步:
```typescript
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
// 异步加载资源
const config = await fetch('/plugin-config.json').then(r => r.json());
// 使用加载的配置初始化服务
const service = new MyService(config);
services.registerInstance(MyService, service);
}
async uninstall(): Promise<void> {
// 异步清理
await this.saveState();
}
private async saveState() {
// 保存插件状态
}
}
// 使用
await Core.installPlugin(new AsyncPlugin());
```
### 注册服务
插件可以向服务容器注册自己的服务:
```typescript
import { IService } from '@esengine/ecs-framework';
class NetworkService implements IService {
connect(url: string) {
console.log(`Connecting to ${url}`);
}
dispose(): void {
console.log('Network service disposed');
}
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 注册网络服务
services.registerSingleton(NetworkService);
// 解析并使用服务
const network = services.resolve(NetworkService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// 服务容器会自动调用服务的dispose方法
}
}
```
### 添加系统
插件可以向场景添加自定义系统:
```typescript
import { EntitySystem, Matcher } from '@esengine/ecs-framework';
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PhysicsBody));
}
protected process(entities: readonly Entity[]): void {
// 物理模拟逻辑
}
}
class PhysicsPlugin implements IPlugin {
readonly name = 'physics-plugin';
readonly version = '1.0.0';
private physicsSystem?: PhysicsSystem;
install(core: Core, services: ServiceContainer): void {
const scene = core.scene;
if (scene) {
this.physicsSystem = new PhysicsSystem();
scene.addSystem(this.physicsSystem);
}
}
uninstall(): void {
// 移除系统
if (this.physicsSystem) {
const scene = Core.scene;
if (scene) {
scene.removeSystem(this.physicsSystem);
}
this.physicsSystem = undefined;
}
}
}
```
## 依赖管理
### 声明依赖
插件可以声明对其他插件的依赖:
```typescript
class AdvancedPhysicsPlugin implements IPlugin {
readonly name = 'advanced-physics';
readonly version = '2.0.0';
// 声明依赖基础物理插件
readonly dependencies = ['physics-plugin'] as const;
install(core: Core, services: ServiceContainer): void {
// 可以安全地使用physics-plugin提供的服务
const physicsService = services.resolve(PhysicsService);
// ...
}
uninstall(): void {
// 清理
}
}
```
### 依赖检查
框架会自动检查依赖关系,如果依赖未满足会抛出错误:
```typescript
// 错误physics-plugin 未安装
try {
await Core.installPlugin(new AdvancedPhysicsPlugin());
} catch (error) {
console.error(error); // Plugin advanced-physics has unmet dependencies: physics-plugin
}
// 正确:先安装依赖
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
```
### 卸载顺序
框架会检查依赖关系,防止卸载被其他插件依赖的插件:
```typescript
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
// 错误physics-plugin 被 advanced-physics 依赖
try {
await Core.uninstallPlugin('physics-plugin');
} catch (error) {
console.error(error); // Cannot uninstall plugin physics-plugin: it is required by advanced-physics
}
// 正确:先卸载依赖它的插件
await Core.uninstallPlugin('advanced-physics');
await Core.uninstallPlugin('physics-plugin');
```
## 插件管理
### 通过 Core 管理
Core 类提供了便捷的插件管理方法:
```typescript
// 安装插件
await Core.installPlugin(myPlugin);
// 卸载插件
await Core.uninstallPlugin('plugin-name');
// 检查插件是否已安装
if (Core.isPluginInstalled('plugin-name')) {
// ...
}
// 获取插件实例
const plugin = Core.getPlugin('plugin-name');
```
### 通过 PluginManager 管理
也可以直接使用 PluginManager 服务:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// 获取所有插件
const allPlugins = pluginManager.getAllPlugins();
console.log(`Total plugins: ${allPlugins.length}`);
// 获取插件元数据
const metadata = pluginManager.getMetadata('my-plugin');
if (metadata) {
console.log(`State: ${metadata.state}`);
console.log(`Installed at: ${new Date(metadata.installedAt!)}`);
}
// 获取所有插件元数据
const allMetadata = pluginManager.getAllMetadata();
for (const meta of allMetadata) {
console.log(`${meta.name} v${meta.version} - ${meta.state}`);
}
```
## 实用插件示例
### 网络同步插件
```typescript
import { IPlugin, IService, Core, ServiceContainer } from '@esengine/ecs-framework';
class NetworkSyncService implements IService {
private ws?: WebSocket;
connect(url: string) {
this.ws = new WebSocket(url);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
}
private handleMessage(data: any) {
// 处理网络消息
}
dispose(): void {
if (this.ws) {
this.ws.close();
this.ws = undefined;
}
}
}
class NetworkSyncPlugin implements IPlugin {
readonly name = 'network-sync';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 注册网络服务
services.registerSingleton(NetworkSyncService);
// 自动连接
const network = services.resolve(NetworkSyncService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// 服务会自动dispose
}
}
```
### 性能分析插件
```typescript
class PerformanceAnalysisPlugin implements IPlugin {
readonly name = 'performance-analysis';
readonly version = '1.0.0';
private frameCount = 0;
private totalTime = 0;
install(core: Core, services: ServiceContainer): void {
const monitor = services.resolve(PerformanceMonitor);
monitor.enable();
// 定期输出性能报告
const timer = services.resolve(TimerManager);
timer.schedule(5.0, true, null, () => {
this.printReport(monitor);
});
}
uninstall(): void {
// 清理
}
private printReport(monitor: PerformanceMonitor) {
console.log('=== Performance Report ===');
console.log(`FPS: ${monitor.getFPS()}`);
console.log(`Memory: ${monitor.getMemoryUsage()} MB`);
}
}
```
## 最佳实践
### 命名规范
- 插件名称使用小写字母和连字符:`my-awesome-plugin`
- 版本号遵循语义化版本规范:`1.0.0`
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-awesome-plugin'; // 好
readonly version = '1.0.0'; // 好
}
```
### 清理资源
始终在 `uninstall` 中清理插件创建的所有资源:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private timerId?: number;
private listener?: () => void;
install(core: Core, services: ServiceContainer): void {
// 添加定时器
this.timerId = setInterval(() => {
// ...
}, 1000);
// 添加事件监听
this.listener = () => {};
window.addEventListener('resize', this.listener);
}
uninstall(): void {
// 清理定时器
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = undefined;
}
// 移除事件监听
if (this.listener) {
window.removeEventListener('resize', this.listener);
this.listener = undefined;
}
}
}
```
### 错误处理
在插件中妥善处理错误,避免影响整个应用:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
try {
// 可能失败的操作
await this.loadConfig();
} catch (error) {
console.error('Failed to load plugin config:', error);
throw error; // 重新抛出,让框架知道安装失败
}
}
async uninstall(): Promise<void> {
try {
await this.cleanup();
} catch (error) {
console.error('Failed to cleanup plugin:', error);
// 即使清理失败也不应该阻止卸载
}
}
private async loadConfig() {
// 加载配置
}
private async cleanup() {
// 清理
}
}
```
### 配置化
允许用户配置插件行为:
```typescript
interface NetworkPluginConfig {
serverUrl: string;
autoReconnect: boolean;
timeout: number;
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
constructor(private config: NetworkPluginConfig) {}
install(core: Core, services: ServiceContainer): void {
const network = new NetworkService(this.config);
services.registerInstance(NetworkService, network);
}
uninstall(): void {
// 清理
}
}
// 使用
const plugin = new NetworkPlugin({
serverUrl: 'ws://localhost:8080',
autoReconnect: true,
timeout: 5000
});
await Core.installPlugin(plugin);
```
## 常见问题
### 插件安装失败
**问题**: 插件安装时抛出错误
**原因**:
- 依赖未满足
- install 方法中有异常
- 服务注册冲突
**解决**:
1. 检查依赖是否已安装
2. 查看错误日志
3. 确保服务名称不冲突
### 插件卸载后仍有副作用
**问题**: 卸载插件后,插件的功能仍在运行
**原因**: uninstall 方法中未正确清理资源
**解决**: 确保在 uninstall 中清理:
- 定时器
- 事件监听器
- WebSocket连接
- 系统引用
### 何时使用插件
**适合使用插件**:
- 可选功能(调试工具、性能分析)
- 第三方集成(网络库、物理引擎)
- 跨项目复用的功能模块
**不适合使用插件**:
- 核心游戏逻辑
- 简单的工具类
- 项目特定的功能
## 相关链接
- [服务容器](./service-container.md) - 在插件中使用服务容器
- [系统架构](./system.md) - 在插件中添加系统
- [快速开始](./getting-started.md) - Core 初始化和基础使用

675
docs/guide/scene-manager.md Normal file
View File

@@ -0,0 +1,675 @@
# SceneManager
SceneManager 是 ECS Framework 提供的轻量级场景管理器,适用于 95% 的游戏应用。它提供简单直观的 API支持场景切换和延迟加载。
## 适用场景
SceneManager 适合以下场景:
- 单人游戏
- 简单多人游戏
- 移动游戏
- 需要场景切换的游戏(菜单、游戏、暂停等)
- 不需要多 World 隔离的项目
## 特点
- 轻量级,零额外开销
- 简单直观的 API
- 支持延迟场景切换(避免在当前帧中途切换)
- 自动管理 ECS 流式 API
- 自动处理场景生命周期
- 集成在 Core 中,自动更新
## 基本使用
### 推荐方式:使用 Core 的静态方法
这是最简单和推荐的方式,适合大多数应用:
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
// 1. 初始化 Core
Core.create({ debug: true });
// 2. 创建并设置场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// 创建初始实体
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Health(100));
}
public onStart(): void {
console.log("游戏场景已启动");
}
}
// 3. 设置场景
Core.setScene(new GameScene());
// 4. 游戏循环Core.update 会自动更新场景)
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新所有服务和场景
}
// Laya 引擎集成
Laya.timer.frameLoop(1, this, () => {
const deltaTime = Laya.timer.delta / 1000;
Core.update(deltaTime);
});
// Cocos Creator 集成
update(deltaTime: number) {
Core.update(deltaTime);
}
```
### 高级方式:直接使用 SceneManager
如果需要更多控制,可以直接使用 SceneManager
```typescript
import { Core, SceneManager, Scene } from '@esengine/ecs-framework';
// 初始化 Core
Core.create({ debug: true });
// 获取 SceneManagerCore 已自动创建并注册)
const sceneManager = Core.services.resolve(SceneManager);
// 设置场景
const gameScene = new GameScene();
sceneManager.setScene(gameScene);
// 游戏循环(仍然使用 Core.update
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // Core会自动调用sceneManager.update()
}
```
**重要**:无论使用哪种方式,游戏循环中都应该只调用 `Core.update()`,它会自动更新 SceneManager 和场景。不需要手动调用 `sceneManager.update()`
## 场景切换
### 立即切换
使用 `Core.setScene()``sceneManager.setScene()` 立即切换场景:
```typescript
// 方式1使用 Core推荐
Core.setScene(new MenuScene());
// 方式2使用 SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new MenuScene());
```
### 延迟切换
使用 `Core.loadScene()``sceneManager.loadScene()` 延迟切换场景,场景会在下一帧切换:
```typescript
// 方式1使用 Core推荐
Core.loadScene(new GameOverScene());
// 方式2使用 SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.loadScene(new GameOverScene());
```
在 System 中切换场景时,应该使用延迟切换:
```typescript
class GameOverSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
const player = entities.find(e => e.name === 'Player');
const health = player?.getComponent(Health);
if (health && health.value <= 0) {
// 延迟切换到游戏结束场景(下一帧生效)
Core.loadScene(new GameOverScene());
// 当前帧继续执行,不会中断当前系统的处理
}
}
}
```
### 完整的场景切换示例
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
// 初始化
Core.create({ debug: true });
// 菜单场景
class MenuScene extends Scene {
protected initialize(): void {
this.name = "MenuScene";
// 监听开始游戏事件
this.eventSystem.on('start_game', () => {
Core.loadScene(new GameScene());
});
}
public onStart(): void {
console.log("显示菜单界面");
}
public unload(): void {
console.log("菜单场景卸载");
}
}
// 游戏场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 创建游戏实体
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Health(100));
// 监听游戏结束事件
this.eventSystem.on('game_over', () => {
Core.loadScene(new GameOverScene());
});
}
public onStart(): void {
console.log("游戏开始");
}
public unload(): void {
console.log("游戏场景卸载");
}
}
// 游戏结束场景
class GameOverScene extends Scene {
protected initialize(): void {
this.name = "GameOverScene";
// 监听返回菜单事件
this.eventSystem.on('back_to_menu', () => {
Core.loadScene(new MenuScene());
});
}
public onStart(): void {
console.log("显示游戏结束界面");
}
}
// 开始游戏
Core.setScene(new MenuScene());
// 游戏循环
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新场景
}
```
## API 参考
### Core 静态方法(推荐)
#### Core.setScene()
立即切换场景。
```typescript
public static setScene<T extends IScene>(scene: T): T
```
**参数**
- `scene` - 要设置的场景实例
**返回**
- 返回设置的场景实例
**示例**
```typescript
const gameScene = Core.setScene(new GameScene());
console.log(gameScene.name);
```
#### Core.loadScene()
延迟加载场景(下一帧切换)。
```typescript
public static loadScene<T extends IScene>(scene: T): void
```
**参数**
- `scene` - 要加载的场景实例
**示例**
```typescript
Core.loadScene(new GameOverScene());
```
#### Core.scene
获取当前活跃的场景。
```typescript
public static get scene(): IScene | null
```
**返回**
- 当前场景实例,如果没有场景则返回 null
**示例**
```typescript
const currentScene = Core.scene;
if (currentScene) {
console.log(`当前场景: ${currentScene.name}`);
}
```
#### Core.ecsAPI
获取 ECS 流式 API。
```typescript
public static get ecsAPI(): ECSFluentAPI | null
```
**返回**
- ECS API 实例,如果当前没有场景则返回 null
**示例**
```typescript
const api = Core.ecsAPI;
if (api) {
// 查询实体
const enemies = api.find(Enemy, Transform);
// 发射事件
api.emit('game:start', { level: 1 });
}
```
### SceneManager 方法(高级)
如果需要直接使用 SceneManager可以通过服务容器获取
```typescript
const sceneManager = Core.services.resolve(SceneManager);
```
#### setScene()
立即切换场景。
```typescript
public setScene<T extends IScene>(scene: T): T
```
#### loadScene()
延迟加载场景。
```typescript
public loadScene<T extends IScene>(scene: T): void
```
#### currentScene
获取当前场景。
```typescript
public get currentScene(): IScene | null
```
#### api
获取 ECS 流式 API。
```typescript
public get api(): ECSFluentAPI | null
```
#### hasScene
检查是否有活跃场景。
```typescript
public get hasScene(): boolean
```
#### hasPendingScene
检查是否有待切换的场景。
```typescript
public get hasPendingScene(): boolean
```
## 使用 ECS 流式 API
通过 `Core.ecsAPI` 可以方便地访问场景的 ECS 功能:
```typescript
const api = Core.ecsAPI;
if (!api) {
console.error('没有活跃场景');
return;
}
// 查询实体
const players = api.find(Player, Transform);
const enemies = api.find(Enemy, Health, Transform);
// 发射事件
api.emit('player:scored', { points: 100 });
// 监听事件
api.on('enemy:died', (data) => {
console.log('敌人死亡:', data);
});
```
## 最佳实践
### 1. 使用 Core 的静态方法
```typescript
// 推荐:使用 Core 的静态方法
Core.setScene(new GameScene());
Core.loadScene(new MenuScene());
const currentScene = Core.scene;
// 不推荐:除非有特殊需求,否则不需要直接使用 SceneManager
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
### 2. 只调用 Core.update()
```typescript
// 正确:只调用 Core.update()
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新所有服务和场景
}
// 错误:不要手动调用 sceneManager.update()
function gameLoop(deltaTime: number) {
Core.update(deltaTime);
sceneManager.update(); // 重复更新,会导致问题!
}
```
### 3. 使用延迟切换避免问题
在 System 中切换场景时,应该使用 `loadScene()` 而不是 `setScene()`
```typescript
// 推荐:延迟切换
class HealthSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health);
if (health.value <= 0) {
Core.loadScene(new GameOverScene());
// 当前帧继续处理其他实体
}
}
}
}
// 不推荐:立即切换可能导致问题
class HealthSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const health = entity.getComponent(Health);
if (health.value <= 0) {
Core.setScene(new GameOverScene());
// 场景立即切换,当前帧的其他实体可能无法正常处理
}
}
}
}
```
### 4. 场景职责分离
每个场景应该只负责一个特定的游戏状态:
```typescript
// 好的设计 - 职责清晰
class MenuScene extends Scene {
// 只处理菜单相关逻辑
}
class GameScene extends Scene {
// 只处理游戏玩法逻辑
}
class PauseScene extends Scene {
// 只处理暂停界面逻辑
}
// 避免的设计 - 职责混乱
class MegaScene extends Scene {
// 包含菜单、游戏、暂停等所有逻辑
}
```
### 5. 资源管理
在场景的 `unload()` 方法中清理资源:
```typescript
class GameScene extends Scene {
private textures: Map<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
this.textures.set('player', loadTexture('player.png'));
this.sounds.set('bgm', loadSound('bgm.mp3'));
}
public unload(): void {
// 清理资源
this.textures.clear();
this.sounds.clear();
console.log('场景资源已清理');
}
}
```
### 6. 事件驱动的场景切换
使用事件系统来触发场景切换,保持代码解耦:
```typescript
class GameScene extends Scene {
protected initialize(): void {
// 监听场景切换事件
this.eventSystem.on('goto:menu', () => {
Core.loadScene(new MenuScene());
});
this.eventSystem.on('goto:gameover', (data) => {
Core.loadScene(new GameOverScene());
});
}
}
// 在 System 中触发事件
class GameLogicSystem extends EntitySystem {
process(entities: readonly Entity[]): void {
if (levelComplete) {
this.scene.eventSystem.emitSync('goto:gameover', {
score: 1000,
level: 5
});
}
}
}
```
## 架构层次
SceneManager 在 ECS Framework 中的位置:
```
Core (全局服务)
└── SceneManager (场景管理,自动更新)
└── Scene (当前场景)
├── EntitySystem (系统)
├── Entity (实体)
└── Component (组件)
```
## 与 WorldManager 的对比
| 特性 | SceneManager | WorldManager |
|------|--------------|--------------|
| 适用场景 | 95% 的游戏应用 | 高级多世界隔离场景 |
| 复杂度 | 简单 | 复杂 |
| 场景数量 | 单场景(可切换) | 多 World每个 World 多场景 |
| 性能开销 | 最小 | 较高 |
| 使用方式 | `Core.setScene()` | `worldManager.createWorld()` |
**何时使用 SceneManager**
- 单人游戏
- 简单的多人游戏
- 移动游戏
- 场景之间需要切换但不需要同时运行
**何时使用 WorldManager**
- MMO 游戏服务器(每个房间一个 World
- 游戏大厅系统(每个游戏房间完全隔离)
- 需要运行多个完全独立的游戏实例
## 完整示例
```typescript
import { Core, Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// 定义组件
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// 定义系统
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (transform && velocity) {
transform.x += velocity.vx;
transform.y += velocity.vy;
}
}
}
}
// 定义场景
class MenuScene extends Scene {
protected initialize(): void {
this.name = "MenuScene";
console.log("菜单场景初始化");
}
public onStart(): void {
console.log("菜单场景启动");
}
}
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Transform(
Math.random() * 800,
Math.random() * 600
));
enemy.addComponent(new Velocity(
Math.random() * 100 - 50,
Math.random() * 100 - 50
));
enemy.addComponent(new Health(50));
}
}
public onStart(): void {
console.log('游戏场景启动');
}
public unload(): void {
console.log('游戏场景卸载');
}
}
// 初始化
Core.create({ debug: true });
// 设置初始场景
Core.setScene(new MenuScene());
// 游戏循环
let lastTime = 0;
function gameLoop(currentTime: number) {
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
// 只需要调用 Core.update它会自动更新场景
Core.update(deltaTime);
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
// 切换到游戏场景
setTimeout(() => {
Core.loadScene(new GameScene());
}, 3000);
```
SceneManager 为大多数游戏提供了简单而强大的场景管理能力。通过 Core 的静态方法,你可以轻松地管理场景切换。如果你需要更高级的多世界隔离功能,请参考 [WorldManager](./world-manager.md) 文档。

View File

@@ -11,6 +11,22 @@
- 事件系统支持
- 性能监控和调试信息
## 场景管理方式
ECS Framework 提供了两种场景管理方式:
1. **[SceneManager](./scene-manager.md)** - 适用于 95% 的游戏应用
- 单人游戏、简单多人游戏、移动游戏
- 轻量级,简单直观的 API
- 支持场景切换
2. **[WorldManager](./world-manager.md)** - 适用于高级多世界隔离场景
- MMO 游戏服务器、游戏房间系统
- 多 World 管理,每个 World 可包含多个场景
- 完全隔离的独立环境
本文档重点介绍 Scene 类本身的使用方法。关于场景管理器的详细信息,请查看对应的文档。
## 创建场景
### 继承 Scene 类
@@ -106,6 +122,13 @@ const scene = new ExampleScene();
// 场景的 initialize(), begin(), update(), end() 由框架自动调用
```
**生命周期方法**
1. `initialize()` - 场景初始化,设置系统和初始实体
2. `begin()` / `onStart()` - 场景开始运行
3. `update()` - 每帧更新(由场景管理器调用)
4. `end()` / `unload()` - 场景卸载,清理资源
## 实体管理
### 创建实体
@@ -247,15 +270,42 @@ class EventScene extends Scene {
}
public triggerGameEvent(): void {
// 发送事件
// 发送事件(同步)
this.eventSystem.emitSync('custom_event', {
message: "这是自定义事件",
timestamp: Date.now()
});
// 发送事件(异步)
this.eventSystem.emit('async_event', {
data: "异步事件数据"
});
}
}
```
### 事件系统 API
```typescript
// 监听事件
this.eventSystem.on('event_name', callback);
// 监听一次(自动取消订阅)
this.eventSystem.once('event_name', callback);
// 取消监听
this.eventSystem.off('event_name', callback);
// 同步发送事件
this.eventSystem.emitSync('event_name', data);
// 异步发送事件
this.eventSystem.emit('event_name', data);
// 清除所有事件监听
this.eventSystem.clear();
```
## 场景统计和调试
### 获取场景统计
@@ -287,176 +337,58 @@ class StatsScene extends Scene {
}
```
## 场景集成到框架
## 组件查询
ECS Framework 提供了灵活的场景管理架构,适用于不同规模的应用
### 1. 使用 SceneManager推荐大多数应用
适用于 95% 的游戏应用(单人游戏、简单多人游戏、移动游戏等):
Scene 提供了强大的组件查询系统
```typescript
import { Core, SceneManager } from '@esengine/ecs-framework';
// 初始化Core全局服务
Core.create({ debug: true });
// 创建场景管理器
const sceneManager = new SceneManager();
// 创建游戏场景
class GameScene extends Scene {
class QueryScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
}
}
// 设置场景
const gameScene = new GameScene();
sceneManager.setScene(gameScene);
// 游戏循环
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 更新全局服务
sceneManager.update(); // 更新当前场景
}
```
### 2. 场景切换
SceneManager 支持流畅的场景切换:
```typescript
// 立即切换场景
const menuScene = new MenuScene();
sceneManager.setScene(menuScene);
// 延迟切换场景(在下一帧切换)
const gameScene = new GameScene();
sceneManager.startSceneTransition(gameScene, false);
// 访问当前场景
const currentScene = sceneManager.currentScene;
// 访问 ECS API
const ecsAPI = sceneManager.ecsAPI;
const entity = ecsAPI?.createEntity('player');
```
### 3. 使用 WorldManager高级用例
适用于需要完全隔离的多世界应用MMO服务器、游戏房间系统等
```typescript
import { Core, WorldManager } from '@esengine/ecs-framework';
// 初始化Core全局服务
Core.create({ debug: true });
// 创建世界管理器
const worldManager = new WorldManager();
// 创建多个独立的游戏世界
const gameWorld = worldManager.createWorld('game', {
name: 'MainGame',
maxScenes: 5
});
// 在World中创建场景
const menuScene = gameWorld.createScene('menu', new MenuScene());
const gameScene = gameWorld.createScene('game', new GameScene());
// 激活场景
gameWorld.setSceneActive('menu', true);
// 游戏循环
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 更新全局服务
worldManager.updateAll(); // 更新所有世界
}
```
## 多场景管理
在World中可以管理多个场景通过激活/停用来切换:
```typescript
class GameWorld extends World {
private menuScene: Scene;
private gameScene: Scene;
private gameOverScene: Scene;
public initialize(): void {
// 创建多个场景
this.menuScene = this.createScene('menu', new MenuScene());
this.gameScene = this.createScene('game', new GameScene());
this.gameOverScene = this.createScene('gameover', new GameOverScene());
// 设置初始场景
this.showMenu();
// 创建一些实体
for (let i = 0; i < 10; i++) {
const entity = this.createEntity(`Entity_${i}`);
entity.addComponent(new Transform(i * 10, 0));
entity.addComponent(new Velocity(1, 0));
if (i % 2 === 0) {
entity.addComponent(new Renderer());
}
}
}
public showMenu(): void {
this.deactivateAllScenes();
this.setSceneActive('menu', true);
}
public queryEntities(): void {
// 通过 QuerySystem 查询
const entities = this.querySystem.query([Transform, Velocity]);
console.log(`找到 ${entities.length} 个有 Transform 和 Velocity 的实体`);
public startGame(): void {
this.deactivateAllScenes();
this.setSceneActive('game', true);
}
public showGameOver(): void {
this.deactivateAllScenes();
this.setSceneActive('gameover', true);
}
private deactivateAllScenes(): void {
this.setSceneActive('menu', false);
this.setSceneActive('game', false);
this.setSceneActive('gameover', false);
// 使用 ECS 流式 API如果通过 SceneManager
// const api = sceneManager.api;
// const entities = api?.find(Transform, Velocity);
}
}
```
## 架构层次
## 性能监控
ECS Framework 的架构层次清晰,职责分明
Scene 内置了性能监控功能
```typescript
// 架构层次:
// Core (全局服务) → SceneManager (场景管理) → Scene → EntitySystem → Entity → Component
// 或
// Core (全局服务) → WorldManager (世界管理) → World → Scene → EntitySystem → Entity → Component
class PerformanceScene extends Scene {
public showPerformance(): void {
// 获取性能数据
const perfData = this.performanceMonitor?.getPerformanceData();
if (perfData) {
console.log('FPS:', perfData.fps);
console.log('帧时间:', perfData.frameTime);
console.log('实体更新时间:', perfData.entityUpdateTime);
console.log('系统更新时间:', perfData.systemUpdateTime);
}
// 1. 推荐:使用 SceneManager 管理单场景/场景切换
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = new SceneManager();
sceneManager.setScene(new GameScene());
// 游戏循环
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 全局服务
sceneManager.update(); // 场景更新
}
// 2. 高级:使用 WorldManager 管理多世界
import { Core, WorldManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const worldManager = new WorldManager();
const world = worldManager.createWorld('gameWorld');
const scene = world.createScene('mainScene', new GameScene());
world.setSceneActive('mainScene', true);
// 游戏循环
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 全局服务
worldManager.updateAll(); // 所有世界更新
// 获取性能报告
const report = this.performanceMonitor?.generateReport();
if (report) {
console.log('性能报告:', report);
}
}
}
```
@@ -465,7 +397,7 @@ function gameLoop(deltaTime: number) {
### 1. 场景职责分离
```typescript
// 好的场景设计 - 职责清晰
// 好的场景设计 - 职责清晰
class MenuScene extends Scene {
// 只处理菜单相关逻辑
}
@@ -478,7 +410,7 @@ class InventoryScene extends Scene {
// 只处理物品栏逻辑
}
// 避免的场景设计 - 职责混乱
// 避免的场景设计 - 职责混乱
class MegaScene extends Scene {
// 包含菜单、游戏、物品栏等所有逻辑
}
@@ -525,12 +457,25 @@ class ResourceScene extends Scene {
private loadResources(): void {
// 加载场景所需资源
this.textures.set('player', this.loadTexture('player.png'));
this.sounds.set('bgm', this.loadSound('bgm.mp3'));
}
public unload(): void {
// 清理资源
this.textures.clear();
this.sounds.clear();
console.log('场景资源已清理');
}
private loadTexture(path: string): any {
// 加载纹理
return null;
}
private loadSound(path: string): any {
// 加载音效
return null;
}
}
```
@@ -571,7 +516,146 @@ class EventHandlingScene extends Scene {
private onPlayerInput(data: any): void {
// 处理玩家输入
}
public unload(): void {
// 清理事件监听
this.eventSystem.clear();
}
}
```
场景是 ECS 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。
### 5. 初始化顺序
```typescript
class ProperInitScene extends Scene {
protected initialize(): void {
// 1. 首先设置场景配置
this.name = "GameScene";
// 2. 然后添加系统(按依赖顺序)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
// 3. 最后创建实体
this.createEntities();
// 4. 设置事件监听
this.setupEvents();
}
private createEntities(): void {
// 创建实体
}
private setupEvents(): void {
// 设置事件监听
}
}
```
## 完整示例
```typescript
import { Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// 定义组件
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// 定义系统
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (transform && velocity) {
transform.x += velocity.vx;
transform.y += velocity.vy;
}
}
}
}
// 定义场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Transform(
Math.random() * 800,
Math.random() * 600
));
enemy.addComponent(new Velocity(
Math.random() * 100 - 50,
Math.random() * 100 - 50
));
enemy.addComponent(new Health(50));
}
// 设置事件监听
this.eventSystem.on('player_died', () => {
console.log('玩家死亡!');
});
}
public onStart(): void {
console.log('游戏场景启动');
}
public unload(): void {
console.log('游戏场景卸载');
this.eventSystem.clear();
}
}
// 使用场景
// 方式1通过 SceneManager推荐
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
// 方式2通过 WorldManager高级用例
import { WorldManager } from '@esengine/ecs-framework';
const worldManager = Core.services.resolve(WorldManager);
const world = worldManager.createWorld('game');
world.createScene('main', new GameScene());
world.setSceneActive('main', true);
```
## 下一步
- 了解 [SceneManager](./scene-manager.md) - 适用于大多数游戏的简单场景管理
- 了解 [WorldManager](./world-manager.md) - 适用于需要多世界隔离的高级场景
场景是 ECS 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。

View File

@@ -14,6 +14,18 @@
- **JSON格式**:人类可读,便于调试和编辑
- **Binary格式**使用MessagePack体积更小性能更高
> **📢 v2.2.2 重要变更**
>
> 从 v2.2.2 开始,二进制序列化格式返回 `Uint8Array` 而非 Node.js 的 `Buffer`,以确保浏览器兼容性:
> - `serialize({ format: 'binary' })` 返回 `string | Uint8Array`(原为 `string | Buffer`
> - `deserialize(data)` 接收 `string | Uint8Array`(原为 `string | Buffer`
> - `applyIncremental(data)` 接收 `IncrementalSnapshot | string | Uint8Array`(原为包含 `Buffer`
>
> **迁移影响**
> - ✅ **运行时兼容**Node.js 的 `Buffer` 继承自 `Uint8Array`,现有代码可直接运行
> - ⚠️ **类型检查**:如果你的 TypeScript 代码中显式使用了 `Buffer` 类型,需要改为 `Uint8Array`
> - ✅ **浏览器支持**`Uint8Array` 是标准 JavaScript 类型,所有现代浏览器都支持
## 全量序列化
### 基础用法
@@ -63,6 +75,7 @@ const binaryData = scene.serialize({
});
// 保存为文件Node.js环境
// 注意binaryData 是 Uint8Array 类型Node.js 的 fs 可以直接写入
fs.writeFileSync('save.bin', binaryData);
```
@@ -285,7 +298,7 @@ otherScene.applyIncremental(incremental);
const jsonData = IncrementalSerializer.serializeIncremental(incremental, { format: 'json' });
otherScene.applyIncremental(jsonData);
// 从二进制Buffer应用
// 从二进制Uint8Array应用
const binaryData = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
otherScene.applyIncremental(binaryData);
```
@@ -552,9 +565,9 @@ class NetworkSync {
}
private receiveIncremental(data: ArrayBuffer): void {
// 直接应用二进制数据
const buffer = Buffer.from(data);
this.scene.applyIncremental(buffer);
// 直接应用二进制数据ArrayBuffer 转 Uint8Array
const uint8Array = new Uint8Array(data);
this.scene.applyIncremental(uint8Array);
}
}
```
@@ -790,7 +803,7 @@ class LargeDataComponent extends Component {
- [`Scene.createIncrementalSnapshot()`](/api/classes/Scene#createincrementalsnapshot) - 创建基础快照
- [`Scene.serializeIncremental()`](/api/classes/Scene#serializeincremental) - 获取增量变更
- [`Scene.applyIncremental()`](/api/classes/Scene#applyincremental) - 应用增量变更支持IncrementalSnapshot对象、JSON字符串或二进制Buffer
- [`Scene.applyIncremental()`](/api/classes/Scene#applyincremental) - 应用增量变更支持IncrementalSnapshot对象、JSON字符串或二进制Uint8Array
- [`Scene.updateIncrementalSnapshot()`](/api/classes/Scene#updateincrementalsnapshot) - 更新快照基准
- [`Scene.clearIncrementalSnapshot()`](/api/classes/Scene#clearincrementalsnapshot) - 清除快照
- [`Scene.hasIncrementalSnapshot()`](/api/classes/Scene#hasincrementalsnapshot) - 检查是否有快照

View File

@@ -0,0 +1,589 @@
# 服务容器
服务容器ServiceContainer是 ECS Framework 的依赖注入容器,负责管理框架中所有服务的注册、解析和生命周期。通过服务容器,你可以实现松耦合的架构设计,提高代码的可测试性和可维护性。
## 概述
### 什么是服务容器
服务容器是一个轻量级的依赖注入DI容器它提供了
- **服务注册**: 将服务类型注册到容器中
- **服务解析**: 从容器中获取服务实例
- **生命周期管理**: 自动管理服务实例的创建和销毁
- **依赖注入**: 自动解析服务之间的依赖关系
### 核心概念
#### 服务Service
服务是实现了 `IService` 接口的类,必须提供 `dispose()` 方法用于资源清理:
```typescript
import { IService } from '@esengine/ecs-framework';
class MyService implements IService {
constructor() {
// 初始化逻辑
}
dispose(): void {
// 清理资源
}
}
```
#### 生命周期
服务容器支持两种生命周期:
- **Singleton单例**: 整个应用程序生命周期内只有一个实例,所有解析请求返回同一个实例
- **Transient瞬时**: 每次解析都创建新的实例
## 基础使用
### 访问服务容器
Core 类内置了服务容器,可以通过 `Core.services` 访问:
```typescript
import { Core } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 访问服务容器
const container = Core.services;
```
### 注册服务
#### 注册单例服务
单例服务在首次解析时创建,之后所有解析请求都返回同一个实例:
```typescript
class DataService implements IService {
private data: Map<string, any> = new Map();
getData(key: string) {
return this.data.get(key);
}
setData(key: string, value: any) {
this.data.set(key, value);
}
dispose(): void {
this.data.clear();
}
}
// 注册单例服务
Core.services.registerSingleton(DataService);
```
#### 注册瞬时服务
瞬时服务每次解析都创建新实例,适用于无状态或短生命周期的服务:
```typescript
class CommandService implements IService {
execute(command: string) {
console.log(`Executing: ${command}`);
}
dispose(): void {
// 清理资源
}
}
// 注册瞬时服务
Core.services.registerTransient(CommandService);
```
#### 注册服务实例
直接注册已创建的实例,自动视为单例:
```typescript
const config = new ConfigService();
config.load('./config.json');
// 注册实例
Core.services.registerInstance(ConfigService, config);
```
#### 使用工厂函数注册
工厂函数允许你在创建服务时执行自定义逻辑:
```typescript
Core.services.registerSingleton(LoggerService, (container) => {
const logger = new LoggerService();
logger.setLevel('debug');
return logger;
});
```
### 解析服务
#### resolve 方法
解析服务实例,如果服务未注册会抛出异常:
```typescript
// 解析服务
const dataService = Core.services.resolve(DataService);
dataService.setData('player', { name: 'Alice', score: 100 });
// 单例服务,多次解析返回同一个实例
const same = Core.services.resolve(DataService);
console.log(same === dataService); // true
```
#### tryResolve 方法
尝试解析服务,如果未注册返回 null 而不抛出异常:
```typescript
const optional = Core.services.tryResolve(OptionalService);
if (optional) {
optional.doSomething();
}
```
#### isRegistered 方法
检查服务是否已注册:
```typescript
if (Core.services.isRegistered(DataService)) {
const service = Core.services.resolve(DataService);
}
```
## 内置服务
Core 在初始化时自动注册了以下内置服务:
### TimerManager
定时器管理器,负责管理所有游戏定时器:
```typescript
const timerManager = Core.services.resolve(TimerManager);
// 创建定时器
timerManager.schedule(1.0, false, null, (timer) => {
console.log('1秒后执行');
});
```
### PerformanceMonitor
性能监控器,监控游戏性能并提供优化建议:
```typescript
const monitor = Core.services.resolve(PerformanceMonitor);
// 启用性能监控
monitor.enable();
// 获取性能数据
const fps = monitor.getFPS();
```
### SceneManager
场景管理器,管理单场景应用的场景生命周期:
```typescript
const sceneManager = Core.services.resolve(SceneManager);
// 设置当前场景
sceneManager.setScene(new GameScene());
// 获取当前场景
const currentScene = sceneManager.currentScene;
// 延迟切换场景
sceneManager.loadScene(new MenuScene());
// 更新场景
sceneManager.update();
```
### WorldManager
世界管理器,管理多个独立的 World 实例(高级用例):
```typescript
const worldManager = Core.services.resolve(WorldManager);
// 创建独立的游戏世界
const gameWorld = worldManager.createWorld('game_room_001', {
name: 'GameRoom',
maxScenes: 5
});
// 在World中创建场景
const scene = gameWorld.createScene('battle', new BattleScene());
gameWorld.setSceneActive('battle', true);
// 更新所有World
worldManager.updateAll();
```
**适用场景**:
- SceneManager: 适用于 95% 的游戏(单人游戏、简单多人游戏)
- WorldManager: 适用于 MMO 服务器、游戏房间系统等需要完全隔离的多世界应用
### PoolManager
对象池管理器,管理所有对象池:
```typescript
const poolManager = Core.services.resolve(PoolManager);
// 创建对象池
const bulletPool = poolManager.createPool('bullets', () => new Bullet(), 100);
```
### PluginManager
插件管理器,管理插件的安装和卸载:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// 获取所有已安装的插件
const plugins = pluginManager.getAllPlugins();
```
## 依赖注入
ECS Framework 提供了装饰器来简化依赖注入。
### @Injectable 装饰器
标记类为可注入的服务:
```typescript
import { Injectable, IService } from '@esengine/ecs-framework';
@Injectable()
class GameService implements IService {
constructor() {
console.log('GameService created');
}
dispose(): void {
console.log('GameService disposed');
}
}
```
### @Inject 装饰器
在构造函数中注入依赖:
```typescript
import { Injectable, Inject, IService } from '@esengine/ecs-framework';
@Injectable()
class PlayerService implements IService {
constructor(
@Inject(DataService) private data: DataService,
@Inject(GameService) private game: GameService
) {
// data 和 game 会自动从容器中解析
}
dispose(): void {
// 清理资源
}
}
```
### 注册可注入服务
使用 `registerInjectable` 自动处理依赖注入:
```typescript
import { registerInjectable } from '@esengine/ecs-framework';
// 注册服务(会自动解析@Inject依赖
registerInjectable(Core.services, PlayerService);
// 解析时会自动注入依赖
const player = Core.services.resolve(PlayerService);
```
### @Updatable 装饰器
标记服务为可更新的,使其在每帧自动被调用:
```typescript
import { Injectable, Updatable, IService, IUpdatable } from '@esengine/ecs-framework';
@Injectable()
@Updatable() // 默认优先级为0
class PhysicsService implements IService, IUpdatable {
update(deltaTime?: number): void {
// 每帧更新物理模拟
}
dispose(): void {
// 清理资源
}
}
// 指定更新优先级(数值越小越先执行)
@Injectable()
@Updatable(10)
class RenderService implements IService, IUpdatable {
update(deltaTime?: number): void {
// 每帧渲染
}
dispose(): void {
// 清理资源
}
}
```
使用 `@Updatable` 装饰器的服务会被 Core 自动调用,无需手动管理:
```typescript
// Core.update() 会自动调用所有@Updatable服务的update方法
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 自动更新所有可更新服务
}
```
## 自定义服务
### 创建自定义服务
实现 `IService` 接口并注册到容器:
```typescript
import { IService } from '@esengine/ecs-framework';
class AudioService implements IService {
private sounds: Map<string, HTMLAudioElement> = new Map();
play(soundId: string) {
const sound = this.sounds.get(soundId);
if (sound) {
sound.play();
}
}
load(soundId: string, url: string) {
const audio = new Audio(url);
this.sounds.set(soundId, audio);
}
dispose(): void {
// 停止所有音效并清理
for (const sound of this.sounds.values()) {
sound.pause();
sound.src = '';
}
this.sounds.clear();
}
}
// 注册自定义服务
Core.services.registerSingleton(AudioService);
// 使用服务
const audio = Core.services.resolve(AudioService);
audio.load('jump', '/sounds/jump.mp3');
audio.play('jump');
```
### 服务间依赖
服务可以依赖其他服务:
```typescript
@Injectable()
class ConfigService implements IService {
private config: any = {};
get(key: string) {
return this.config[key];
}
dispose(): void {
this.config = {};
}
}
@Injectable()
class NetworkService implements IService {
constructor(
@Inject(ConfigService) private config: ConfigService
) {
// 使用配置服务
const apiUrl = this.config.get('apiUrl');
}
dispose(): void {
// 清理网络连接
}
}
// 注册服务(按依赖顺序)
registerInjectable(Core.services, ConfigService);
registerInjectable(Core.services, NetworkService);
```
## 高级用法
### 服务替换(测试)
在测试中替换真实服务为模拟服务:
```typescript
// 测试代码
class MockDataService implements IService {
getData(key: string) {
return 'mock data';
}
dispose(): void {}
}
// 注册模拟服务(用于测试)
Core.services.registerInstance(DataService, new MockDataService());
```
### 循环依赖检测
服务容器会自动检测循环依赖:
```typescript
// A 依赖 BB 依赖 A
@Injectable()
class ServiceA implements IService {
constructor(@Inject(ServiceB) b: ServiceB) {}
dispose(): void {}
}
@Injectable()
class ServiceB implements IService {
constructor(@Inject(ServiceA) a: ServiceA) {}
dispose(): void {}
}
// 解析时会抛出错误: Circular dependency detected: ServiceA -> ServiceB -> ServiceA
```
### 获取所有服务
```typescript
// 获取所有已注册的服务类型
const types = Core.services.getRegisteredServices();
// 获取所有已实例化的服务实例
const instances = Core.services.getAll();
```
### 服务清理
```typescript
// 注销单个服务
Core.services.unregister(MyService);
// 清空所有服务会调用每个服务的dispose方法
Core.services.clear();
```
## 最佳实践
### 服务命名
服务类名应该以 `Service``Manager` 结尾,清晰表达其职责:
```typescript
class PlayerService implements IService {}
class AudioManager implements IService {}
class NetworkService implements IService {}
```
### 资源清理
始终在 `dispose()` 方法中清理资源:
```typescript
class ResourceService implements IService {
private resources: Map<string, Resource> = new Map();
dispose(): void {
// 释放所有资源
for (const resource of this.resources.values()) {
resource.release();
}
this.resources.clear();
}
}
```
### 避免过度使用
不要把所有类都注册为服务,服务应该是:
- 全局单例或需要共享状态
- 需要在多处使用
- 生命周期需要管理
- 需要依赖注入
对于简单的工具类或数据类,直接创建实例即可。
### 依赖方向
保持清晰的依赖方向,避免循环依赖:
```
高层服务 -> 中层服务 -> 底层服务
GameLogic -> DataService -> ConfigService
```
## 常见问题
### 服务未注册错误
**问题**: `Error: Service MyService is not registered`
**解决**:
```typescript
// 确保服务已注册
Core.services.registerSingleton(MyService);
// 或者使用tryResolve
const service = Core.services.tryResolve(MyService);
if (!service) {
console.log('Service not found');
}
```
### 循环依赖错误
**问题**: `Circular dependency detected`
**解决**: 重新设计服务依赖关系,引入中间服务或使用事件系统解耦。
### 何时使用单例 vs 瞬时
- **单例**: 管理器类、配置、缓存、状态管理
- **瞬时**: 命令对象、请求处理器、临时任务
## 相关链接
- [插件系统](./plugin-system.md) - 使用服务容器注册插件服务
- [快速开始](./getting-started.md) - Core 初始化和基础使用
- [系统架构](./system.md) - 在系统中使用服务

View File

@@ -354,14 +354,18 @@ class PerformanceSystem extends EntitySystem {
### 添加系统到场景
框架提供了两种方式添加系统:传入实例或传入类型(自动依赖注入)。
```typescript
// 在场景子类中添加系统
class GameScene extends Scene {
protected initialize(): void {
// 添加系统
// 方式1传入实例
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
this.addSystem(new PhysicsSystem());
// 方式2传入类型自动依赖注入
this.addEntityProcessor(PhysicsSystem);
// 设置系统更新顺序
const movementSystem = this.getSystem(MovementSystem);
@@ -372,6 +376,48 @@ class GameScene extends Scene {
}
```
### 系统依赖注入
系统实现了 `IService` 接口,支持通过依赖注入获取其他服务或系统:
```typescript
import { ECSSystem, Injectable, Inject } from '@esengine/ecs-framework';
@Injectable()
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem {
constructor(
@Inject(CollisionService) private collision: CollisionService
) {
super(Matcher.all(Transform, RigidBody));
}
protected process(entities: readonly Entity[]): void {
// 使用注入的服务
this.collision.detectCollisions(entities);
}
// 实现 IService 接口的 dispose 方法
public dispose(): void {
// 清理资源
}
}
// 使用时传入类型即可,框架会自动注入依赖
class GameScene extends Scene {
protected initialize(): void {
// 自动依赖注入
this.addEntityProcessor(PhysicsSystem);
}
}
```
注意事项:
- 使用 `@Injectable()` 装饰器标记需要依赖注入的系统
- 在构造函数参数中使用 `@Inject()` 装饰器声明依赖
- 系统必须实现 `dispose()` 方法IService 接口要求)
- 使用 `addEntityProcessor(类型)` 而不是 `addSystem(new 类型())` 来启用依赖注入
### 系统更新顺序
```typescript

761
docs/guide/world-manager.md Normal file
View File

@@ -0,0 +1,761 @@
# WorldManager
WorldManager 是 ECS Framework 提供的高级世界管理器用于管理多个完全隔离的游戏世界World。每个 World 都是独立的 ECS 环境,可以包含多个场景。
## 适用场景
WorldManager 适合以下高级场景:
- MMO 游戏服务器的多房间管理
- 游戏大厅系统(每个游戏房间完全隔离)
- 服务器端的多游戏实例
- 需要完全隔离的多个游戏环境
- 需要同时运行多个独立世界的应用
## 特点
- 多 World 管理,每个 World 完全独立
- 每个 World 可以包含多个 Scene
- 支持 World 的激活/停用
- 自动清理空 World
- World 级别的全局系统
- 批量操作和查询
## 基本使用
### 初始化
WorldManager 是 Core 的内置服务,通过服务容器获取:
```typescript
import { Core, WorldManager } from '@esengine/ecs-framework';
// 初始化 Core
Core.create({ debug: true });
// 从服务容器获取 WorldManagerCore 已自动创建并注册)
const worldManager = Core.services.resolve(WorldManager);
```
### 创建 World
```typescript
// 创建游戏房间 World
const room1 = worldManager.createWorld('room_001', {
name: 'GameRoom_001',
maxScenes: 5,
debug: true
});
// 激活 World
worldManager.setWorldActive('room_001', true);
// 创建更多房间
const room2 = worldManager.createWorld('room_002', {
name: 'GameRoom_002',
maxScenes: 5
});
worldManager.setWorldActive('room_002', true);
```
### 游戏循环
在游戏循环中更新所有活跃的 World
```typescript
function gameLoop(deltaTime: number) {
Core.update(deltaTime); // 更新全局服务
worldManager.updateAll(); // 更新所有活跃的 World
}
// 启动游戏循环
let lastTime = 0;
setInterval(() => {
const currentTime = Date.now();
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
gameLoop(deltaTime);
}, 16); // 60 FPS
```
## World 管理
### 创建 World
```typescript
// 基本创建
const world = worldManager.createWorld('worldId');
// 带配置创建
const world = worldManager.createWorld('worldId', {
name: 'MyWorld',
maxScenes: 10,
autoCleanup: true,
debug: true
});
```
**配置选项IWorldConfig**
- `name?: string` - World 名称
- `maxScenes?: number` - 最大场景数量限制(默认 10
- `autoCleanup?: boolean` - 是否自动清理空场景(默认 true
- `debug?: boolean` - 是否启用调试模式(默认 false
### 获取 World
```typescript
// 通过 ID 获取
const world = worldManager.getWorld('room_001');
if (world) {
console.log(`World: ${world.name}`);
}
// 获取所有 World
const allWorlds = worldManager.getAllWorlds();
console.log(`共有 ${allWorlds.length} 个 World`);
// 获取所有 World ID
const worldIds = worldManager.getWorldIds();
console.log('World 列表:', worldIds);
// 通过名称查找
const world = worldManager.findWorldByName('GameRoom_001');
```
### 激活和停用 World
```typescript
// 激活 World开始运行和更新
worldManager.setWorldActive('room_001', true);
// 停用 World停止更新但保留数据
worldManager.setWorldActive('room_001', false);
// 检查 World 是否激活
if (worldManager.isWorldActive('room_001')) {
console.log('房间正在运行');
}
// 获取所有活跃的 World
const activeWorlds = worldManager.getActiveWorlds();
console.log(`当前有 ${activeWorlds.length} 个活跃 World`);
```
### 移除 World
```typescript
// 移除 World会自动停用并销毁
const removed = worldManager.removeWorld('room_001');
if (removed) {
console.log('World 已移除');
}
```
## World 中的场景管理
每个 World 可以包含多个 Scene 并独立管理它们的生命周期。
### 创建场景
```typescript
const world = worldManager.getWorld('room_001');
if (!world) return;
// 创建场景
const mainScene = world.createScene('main', new MainScene());
const uiScene = world.createScene('ui', new UIScene());
const hudScene = world.createScene('hud', new HUDScene());
// 激活场景
world.setSceneActive('main', true);
world.setSceneActive('ui', true);
world.setSceneActive('hud', false);
```
### 查询场景
```typescript
// 获取特定场景
const mainScene = world.getScene<MainScene>('main');
if (mainScene) {
console.log(`场景名称: ${mainScene.name}`);
}
// 获取所有场景
const allScenes = world.getAllScenes();
console.log(`World 中共有 ${allScenes.length} 个场景`);
// 获取所有场景 ID
const sceneIds = world.getSceneIds();
console.log('场景列表:', sceneIds);
// 获取活跃场景数量
const activeCount = world.getActiveSceneCount();
console.log(`当前有 ${activeCount} 个活跃场景`);
// 检查场景是否激活
if (world.isSceneActive('main')) {
console.log('主场景正在运行');
}
```
### 场景切换
World 支持多场景同时运行,也支持场景切换:
```typescript
class GameWorld {
private world: World;
constructor(worldManager: WorldManager) {
this.world = worldManager.createWorld('game', {
name: 'GameWorld',
maxScenes: 5
});
// 创建所有场景
this.world.createScene('menu', new MenuScene());
this.world.createScene('game', new GameScene());
this.world.createScene('pause', new PauseScene());
this.world.createScene('gameover', new GameOverScene());
// 激活 World
worldManager.setWorldActive('game', true);
}
public showMenu(): void {
this.deactivateAllScenes();
this.world.setSceneActive('menu', true);
}
public startGame(): void {
this.deactivateAllScenes();
this.world.setSceneActive('game', true);
}
public pauseGame(): void {
// 游戏场景继续存在但停止更新
this.world.setSceneActive('game', false);
// 显示暂停界面
this.world.setSceneActive('pause', true);
}
public resumeGame(): void {
this.world.setSceneActive('pause', false);
this.world.setSceneActive('game', true);
}
public showGameOver(): void {
this.deactivateAllScenes();
this.world.setSceneActive('gameover', true);
}
private deactivateAllScenes(): void {
const sceneIds = this.world.getSceneIds();
sceneIds.forEach(id => this.world.setSceneActive(id, false));
}
}
```
### 移除场景
```typescript
// 移除不再需要的场景
const removed = world.removeScene('oldScene');
if (removed) {
console.log('场景已移除');
}
// 场景会自动调用 end() 方法进行清理
```
## 全局系统
World 支持全局系统,这些系统在 World 级别运行,不依赖特定 Scene。
### 定义全局系统
```typescript
import { IGlobalSystem } from '@esengine/ecs-framework';
// 网络系统World 级别)
class NetworkSystem implements IGlobalSystem {
readonly name = 'NetworkSystem';
private connectionId: string;
constructor(connectionId: string) {
this.connectionId = connectionId;
}
initialize(): void {
console.log(`网络系统初始化: ${this.connectionId}`);
// 建立网络连接
}
update(deltaTime?: number): void {
// 处理网络消息,不依赖任何 Scene
// 接收和发送网络包
}
destroy(): void {
console.log(`网络系统销毁: ${this.connectionId}`);
// 关闭网络连接
}
}
// 物理系统World 级别)
class PhysicsSystem implements IGlobalSystem {
readonly name = 'PhysicsSystem';
initialize(): void {
console.log('物理系统初始化');
}
update(deltaTime?: number): void {
// 物理模拟,作用于 World 中所有场景
}
destroy(): void {
console.log('物理系统销毁');
}
}
```
### 使用全局系统
```typescript
const world = worldManager.getWorld('room_001');
if (!world) return;
// 添加全局系统
const networkSystem = world.addGlobalSystem(new NetworkSystem('conn_001'));
const physicsSystem = world.addGlobalSystem(new PhysicsSystem());
// 获取全局系统
const network = world.getGlobalSystem(NetworkSystem);
if (network) {
console.log('找到网络系统');
}
// 移除全局系统
world.removeGlobalSystem(networkSystem);
```
## 批量操作
### 更新所有 World
```typescript
// 更新所有活跃的 World应该在游戏循环中调用
worldManager.updateAll();
// 这会自动更新每个 World 的:
// 1. 全局系统
// 2. 所有活跃场景
```
### 启动和停止
```typescript
// 启动所有 World
worldManager.startAll();
// 停止所有 World
worldManager.stopAll();
// 检查是否正在运行
if (worldManager.isRunning) {
console.log('WorldManager 正在运行');
}
```
### 查找 World
```typescript
// 使用条件查找
const emptyWorlds = worldManager.findWorlds(world => {
return world.sceneCount === 0;
});
// 查找活跃的 World
const activeWorlds = worldManager.findWorlds(world => {
return world.isActive;
});
// 查找特定名称的 World
const world = worldManager.findWorldByName('GameRoom_001');
```
## 统计和监控
### 获取统计信息
```typescript
const stats = worldManager.getStats();
console.log(`总 World 数: ${stats.totalWorlds}`);
console.log(`活跃 World 数: ${stats.activeWorlds}`);
console.log(`总场景数: ${stats.totalScenes}`);
console.log(`总实体数: ${stats.totalEntities}`);
console.log(`总系统数: ${stats.totalSystems}`);
// 查看每个 World 的详细信息
stats.worlds.forEach(worldInfo => {
console.log(`World: ${worldInfo.name}`);
console.log(` 场景数: ${worldInfo.sceneCount}`);
console.log(` 是否活跃: ${worldInfo.isActive}`);
});
```
### 获取详细状态
```typescript
const status = worldManager.getDetailedStatus();
// 包含所有 World 的详细状态
status.worlds.forEach(worldStatus => {
console.log(`World ID: ${worldStatus.id}`);
console.log(`状态:`, worldStatus.status);
});
```
## 自动清理
WorldManager 支持自动清理空的 World。
### 配置清理
```typescript
// 创建带清理配置的 WorldManager
const worldManager = Core.services.resolve(WorldManager);
// WorldManager 的配置在 Core 中设置:
// {
// maxWorlds: 50,
// autoCleanup: true,
// cleanupInterval: 30000 // 30 秒
// }
```
### 手动清理
```typescript
// 手动触发清理
const cleanedCount = worldManager.cleanup();
console.log(`清理了 ${cleanedCount} 个 World`);
```
**清理条件**
- World 未激活
- 没有 Scene 或所有 Scene 都是空的
- 创建时间超过 10 分钟
## API 参考
### WorldManager API
| 方法 | 说明 |
|------|------|
| `createWorld(worldId, config?)` | 创建新 World |
| `removeWorld(worldId)` | 移除 World |
| `getWorld(worldId)` | 获取 World |
| `getAllWorlds()` | 获取所有 World |
| `getWorldIds()` | 获取所有 World ID |
| `setWorldActive(worldId, active)` | 设置 World 激活状态 |
| `isWorldActive(worldId)` | 检查 World 是否激活 |
| `getActiveWorlds()` | 获取所有活跃的 World |
| `updateAll()` | 更新所有活跃 World |
| `startAll()` | 启动所有 World |
| `stopAll()` | 停止所有 World |
| `findWorlds(predicate)` | 查找满足条件的 World |
| `findWorldByName(name)` | 根据名称查找 World |
| `getStats()` | 获取统计信息 |
| `getDetailedStatus()` | 获取详细状态信息 |
| `cleanup()` | 清理空 World |
| `destroy()` | 销毁 WorldManager |
### World API
| 方法 | 说明 |
|------|------|
| `createScene(sceneId, sceneInstance?)` | 创建并添加 Scene |
| `removeScene(sceneId)` | 移除 Scene |
| `getScene(sceneId)` | 获取 Scene |
| `getAllScenes()` | 获取所有 Scene |
| `getSceneIds()` | 获取所有 Scene ID |
| `setSceneActive(sceneId, active)` | 设置 Scene 激活状态 |
| `isSceneActive(sceneId)` | 检查 Scene 是否激活 |
| `getActiveSceneCount()` | 获取活跃 Scene 数量 |
| `addGlobalSystem(system)` | 添加全局系统 |
| `removeGlobalSystem(system)` | 移除全局系统 |
| `getGlobalSystem(type)` | 获取全局系统 |
| `start()` | 启动 World |
| `stop()` | 停止 World |
| `updateGlobalSystems()` | 更新全局系统 |
| `updateScenes()` | 更新所有激活 Scene |
| `destroy()` | 销毁 World |
| `getStatus()` | 获取 World 状态 |
| `getStats()` | 获取统计信息 |
### 属性
| 属性 | 说明 |
|------|------|
| `worldCount` | World 总数 |
| `activeWorldCount` | 活跃 World 数量 |
| `isRunning` | 是否正在运行 |
| `config` | 配置信息 |
## 完整示例
### MMO 游戏房间系统
```typescript
import { Core, WorldManager, Scene, World } from '@esengine/ecs-framework';
// 初始化
Core.create({ debug: true });
const worldManager = Core.services.resolve(WorldManager);
// 房间管理器
class RoomManager {
private worldManager: WorldManager;
private rooms: Map<string, World> = new Map();
constructor(worldManager: WorldManager) {
this.worldManager = worldManager;
}
// 创建游戏房间
public createRoom(roomId: string, maxPlayers: number): World {
const world = this.worldManager.createWorld(roomId, {
name: `Room_${roomId}`,
maxScenes: 3,
debug: true
});
// 创建房间场景
world.createScene('lobby', new LobbyScene());
world.createScene('game', new GameScene());
world.createScene('result', new ResultScene());
// 添加房间级别的系统
world.addGlobalSystem(new NetworkSystem(roomId));
world.addGlobalSystem(new RoomLogicSystem(maxPlayers));
// 激活 World 和初始场景
this.worldManager.setWorldActive(roomId, true);
world.setSceneActive('lobby', true);
this.rooms.set(roomId, world);
console.log(`房间 ${roomId} 已创建`);
return world;
}
// 玩家加入房间
public joinRoom(roomId: string, playerId: string): boolean {
const world = this.rooms.get(roomId);
if (!world) {
console.log(`房间 ${roomId} 不存在`);
return false;
}
// 在大厅场景中创建玩家实体
const lobbyScene = world.getScene('lobby');
if (lobbyScene) {
const player = lobbyScene.createEntity(`Player_${playerId}`);
// 添加玩家组件...
console.log(`玩家 ${playerId} 加入房间 ${roomId}`);
return true;
}
return false;
}
// 开始游戏
public startGame(roomId: string): void {
const world = this.rooms.get(roomId);
if (!world) return;
// 切换到游戏场景
world.setSceneActive('lobby', false);
world.setSceneActive('game', true);
console.log(`房间 ${roomId} 游戏开始`);
}
// 结束游戏
public endGame(roomId: string): void {
const world = this.rooms.get(roomId);
if (!world) return;
// 切换到结果场景
world.setSceneActive('game', false);
world.setSceneActive('result', true);
console.log(`房间 ${roomId} 游戏结束`);
}
// 关闭房间
public closeRoom(roomId: string): void {
this.worldManager.removeWorld(roomId);
this.rooms.delete(roomId);
console.log(`房间 ${roomId} 已关闭`);
}
// 获取房间列表
public getRoomList(): string[] {
return Array.from(this.rooms.keys());
}
// 获取房间统计
public getRoomStats(roomId: string) {
const world = this.rooms.get(roomId);
return world?.getStats();
}
}
// 使用房间管理器
const roomManager = new RoomManager(worldManager);
// 创建多个游戏房间
roomManager.createRoom('room_001', 4);
roomManager.createRoom('room_002', 4);
roomManager.createRoom('room_003', 2);
// 玩家加入
roomManager.joinRoom('room_001', 'player_1');
roomManager.joinRoom('room_001', 'player_2');
// 开始游戏
roomManager.startGame('room_001');
// 游戏循环
function gameLoop(deltaTime: number) {
Core.update(deltaTime);
worldManager.updateAll(); // 更新所有房间
}
// 定期清理空房间
setInterval(() => {
const stats = worldManager.getStats();
console.log(`当前房间数: ${stats.totalWorlds}`);
console.log(`活跃房间数: ${stats.activeWorlds}`);
worldManager.cleanup();
}, 60000); // 每分钟清理一次
```
## 最佳实践
### 1. 合理的 World 粒度
```typescript
// 推荐:每个独立环境一个 World
const room1 = worldManager.createWorld('room_1'); // 游戏房间1
const room2 = worldManager.createWorld('room_2'); // 游戏房间2
// 不推荐:过度使用 World
const world1 = worldManager.createWorld('ui'); // UI 不需要独立 World
const world2 = worldManager.createWorld('menu'); // 菜单不需要独立 World
```
### 2. 使用全局系统处理跨场景逻辑
```typescript
// 推荐World 级别的系统
class NetworkSystem implements IGlobalSystem {
update() {
// 网络处理不依赖场景
}
}
// 不推荐:在每个场景中重复创建
class GameScene extends Scene {
initialize() {
this.addSystem(new NetworkSystem()); // 不应该在场景级别
}
}
```
### 3. 及时清理不用的 World
```typescript
// 推荐:玩家离开时清理房间
function onPlayerLeave(roomId: string) {
const world = worldManager.getWorld(roomId);
if (world && world.sceneCount === 0) {
worldManager.removeWorld(roomId);
}
}
// 或使用自动清理
worldManager.cleanup();
```
### 4. 监控资源使用
```typescript
// 定期检查资源使用情况
setInterval(() => {
const stats = worldManager.getStats();
if (stats.totalWorlds > 100) {
console.warn('World 数量过多,考虑清理');
worldManager.cleanup();
}
if (stats.totalEntities > 10000) {
console.warn('实体数量过多,检查是否有泄漏');
}
}, 30000);
```
## 与 SceneManager 的对比
| 特性 | SceneManager | WorldManager |
|------|--------------|--------------|
| 适用场景 | 95% 的游戏应用 | 高级多世界隔离场景 |
| 复杂度 | 简单 | 复杂 |
| 场景数量 | 单场景(可切换) | 多 World每个 World 多场景 |
| 场景隔离 | 无(场景切换) | 完全隔离(每个 World 独立) |
| 性能开销 | 最小 | 较高 |
| 全局系统 | 无 | 支持World 级别) |
| 使用示例 | 单人游戏、移动游戏 | MMO 服务器、游戏房间系统 |
**何时使用 WorldManager**
- MMO 游戏服务器(每个房间一个 World
- 游戏大厅系统(每个游戏房间完全隔离)
- 需要运行多个完全独立的游戏实例
- 服务器端模拟多个游戏世界
**何时使用 SceneManager**
- 单人游戏
- 简单的多人游戏
- 移动游戏
- 场景之间需要切换但不需要同时运行
## 架构层次
WorldManager 在 ECS Framework 中的位置:
```
Core (全局服务)
└── WorldManager (世界管理)
├── World 1 (游戏房间1)
│ ├── GlobalSystem (全局系统)
│ ├── Scene 1 (场景1)
│ │ ├── EntitySystem
│ │ ├── Entity
│ │ └── Component
│ └── Scene 2 (场景2)
├── World 2 (游戏房间2)
│ ├── GlobalSystem
│ └── Scene 1
└── World 3 (游戏房间3)
```
WorldManager 为需要多世界隔离的高级应用提供了强大的管理能力。如果你的应用不需要多世界隔离,建议使用更简单的 [SceneManager](./scene-manager.md)。

55
package-lock.json generated
View File

@@ -3559,6 +3559,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@msgpack/msgpack": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.2.tgz",
"integrity": "sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==",
"license": "ISC",
"engines": {
"node": ">= 18"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz",
@@ -5220,16 +5229,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/msgpack-lite": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/@types/msgpack-lite/-/msgpack-lite-0.1.11.tgz",
"integrity": "sha512-cdCZS/gw+jIN22I4SUZUFf1ZZfVv5JM1//Br/MuZcI373sxiy3eSSoiyLu0oz+BPatTbGGGBO5jrcvd0siCdTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/multer": {
"version": "1.4.13",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
@@ -7715,12 +7714,6 @@
"node": ">=0.10.0"
}
},
"node_modules/event-lite": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/event-lite/-/event-lite-0.1.3.tgz",
"integrity": "sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==",
"license": "MIT"
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -8672,6 +8665,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -8861,12 +8855,6 @@
"node": ">=8"
}
},
"node_modules/int64-buffer": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.10.tgz",
"integrity": "sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==",
"license": "MIT"
},
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
@@ -9138,6 +9126,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true,
"license": "MIT"
},
"node_modules/isexe": {
@@ -11543,21 +11532,6 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/msgpack-lite": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/msgpack-lite/-/msgpack-lite-0.1.26.tgz",
"integrity": "sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==",
"license": "MIT",
"dependencies": {
"event-lite": "^0.1.1",
"ieee754": "^1.1.8",
"int64-buffer": "^0.1.9",
"isarray": "^1.0.0"
},
"bin": {
"msgpack": "bin/msgpack"
}
},
"node_modules/multimatch": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz",
@@ -14515,7 +14489,6 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/tuf-js": {
@@ -15557,10 +15530,11 @@
},
"packages/core": {
"name": "@esengine/ecs-framework",
"version": "2.1.52",
"version": "2.2.3",
"license": "MIT",
"dependencies": {
"msgpack-lite": "^0.1.26"
"@msgpack/msgpack": "^3.0.0",
"tslib": "^2.8.1"
},
"devDependencies": {
"@babel/core": "^7.28.3",
@@ -15572,7 +15546,6 @@
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.14",
"@types/msgpack-lite": "^0.1.11",
"@types/node": "^20.19.17",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@esengine/ecs-framework",
"version": "2.1.52",
"version": "2.2.3",
"description": "用于Laya、Cocos Creator等JavaScript游戏引擎的高性能ECS框架",
"main": "bin/index.js",
"types": "bin/index.d.ts",
@@ -58,7 +58,6 @@
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4",
"@types/jest": "^29.5.14",
"@types/msgpack-lite": "^0.1.11",
"@types/node": "^20.19.17",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@@ -78,6 +77,7 @@
"directory": "packages/core"
},
"dependencies": {
"msgpack-lite": "^0.1.26"
"@msgpack/msgpack": "^3.0.0",
"tslib": "^2.8.1"
}
}

View File

@@ -52,6 +52,13 @@ module.exports = [
})
],
external,
onwarn(warning, warn) {
// 忽略 msgpack-lite 的循环依赖警告
if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids && warning.ids.some(id => id.includes('msgpack-lite'))) {
return;
}
warn(warning);
},
treeshake: {
moduleSideEffects: false,
propertyReadSideEffects: false,
@@ -78,6 +85,12 @@ module.exports = [
})
],
external,
onwarn(warning, warn) {
if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids && warning.ids.some(id => id.includes('msgpack-lite'))) {
return;
}
warn(warning);
},
treeshake: {
moduleSideEffects: false
}
@@ -103,6 +116,12 @@ module.exports = [
})
],
external: [],
onwarn(warning, warn) {
if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids && warning.ids.some(id => id.includes('msgpack-lite'))) {
return;
}
warn(warning);
},
treeshake: {
moduleSideEffects: false
}
@@ -157,6 +176,12 @@ module.exports = [
})
],
external: [],
onwarn(warning, warn) {
if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.ids && warning.ids.some(id => id.includes('msgpack-lite'))) {
return;
}
warn(warning);
},
treeshake: {
moduleSideEffects: false
}

View File

@@ -1,4 +1,3 @@
import { GlobalManager } from './Utils/GlobalManager';
import { TimerManager } from './Utils/Timers/TimerManager';
import { ITimer } from './Utils/Timers/ITimer';
import { Timer } from './Utils/Timers/Timer';
@@ -6,10 +5,14 @@ import { Time } from './Utils/Time';
import { PerformanceMonitor } from './Utils/PerformanceMonitor';
import { PoolManager } from './Utils/Pool/PoolManager';
import { DebugManager } from './Utils/Debug';
import { ICoreConfig, IECSDebugConfig } from './Types';
import { ICoreConfig, IECSDebugConfig, IUpdatable, isUpdatable } from './Types';
import { createLogger } from './Utils/Logger';
import { SceneManager } from './ECS/SceneManager';
import { IScene } from './ECS/IScene';
import { ServiceContainer } from './Core/ServiceContainer';
import { PluginManager } from './Core/PluginManager';
import { IPlugin } from './Core/Plugin';
import { WorldManager } from './ECS/WorldManager';
/**
* 游戏引擎核心类
@@ -54,8 +57,10 @@ export class Core {
/**
* 全局核心实例
*
* 可能为null表示Core尚未初始化或已被销毁
*/
private static _instance: Core;
private static _instance: Core | null = null;
/**
* Core专用日志器
@@ -77,11 +82,11 @@ export class Core {
public readonly debug: boolean;
/**
* 全局管理器集合
* 服务容器
*
* 存储所有注册的全局管理器实例
* 管理所有服务的注册、解析和生命周期
*/
public _globalManagers: GlobalManager[] = [];
private _serviceContainer: ServiceContainer;
/**
* 定时器管理器
@@ -118,6 +123,20 @@ export class Core {
*/
private _sceneManager: SceneManager;
/**
* World管理器
*
* 管理多个独立的World实例可选
*/
private _worldManager: WorldManager;
/**
* 插件管理器
*
* 管理所有插件的生命周期。
*/
private _pluginManager: PluginManager;
/**
* Core配置
*/
@@ -138,12 +157,16 @@ export class Core {
...config
};
// 初始化服务容器
this._serviceContainer = new ServiceContainer();
// 初始化定时器管理器
this._timerManager = new TimerManager();
Core.registerGlobalManager(this._timerManager);
this._serviceContainer.registerInstance(TimerManager, this._timerManager);
// 初始化性能监控器
this._performanceMonitor = PerformanceMonitor.instance;
this._performanceMonitor = new PerformanceMonitor();
this._serviceContainer.registerInstance(PerformanceMonitor, this._performanceMonitor);
// 在调试模式下启用性能监控
if (this._config.debug) {
@@ -151,17 +174,42 @@ export class Core {
}
// 初始化对象池管理器
this._poolManager = PoolManager.getInstance();
this._poolManager = new PoolManager();
this._serviceContainer.registerInstance(PoolManager, this._poolManager);
// 初始化场景管理器
this._sceneManager = new SceneManager();
this._serviceContainer.registerInstance(SceneManager, this._sceneManager);
// 设置场景切换回调,通知调试管理器
this._sceneManager.setSceneChangedCallback(() => {
if (this._debugManager) {
this._debugManager.onSceneChanged();
}
});
// 初始化World管理器
this._worldManager = new WorldManager();
this._serviceContainer.registerInstance(WorldManager, this._worldManager);
// 初始化插件管理器
this._pluginManager = new PluginManager();
this._pluginManager.initialize(this, this._serviceContainer);
this._serviceContainer.registerInstance(PluginManager, this._pluginManager);
Core.entitySystemsEnabled = this._config.enableEntitySystems ?? true;
this.debug = this._config.debug ?? true;
// 初始化调试管理器
if (this._config.debugConfig?.enabled) {
this._debugManager = new DebugManager(this, this._config.debugConfig);
// 使用DI容器创建DebugManager前两个参数从容器解析config手动传入
const config = this._config.debugConfig;
this._debugManager = new DebugManager(
this._serviceContainer.resolve(SceneManager),
this._serviceContainer.resolve(PerformanceMonitor),
config
);
this._serviceContainer.registerInstance(DebugManager, this._debugManager);
}
this.initialize();
@@ -176,6 +224,54 @@ export class Core {
return this._instance;
}
/**
* 获取服务容器
*
* 用于注册和解析自定义服务。
*
* @returns 服务容器实例
* @throws 如果Core实例未创建
*
* @example
* ```typescript
* // 注册自定义服务
* Core.services.registerSingleton(MyService);
*
* // 解析服务
* const myService = Core.services.resolve(MyService);
* ```
*/
public static get services(): ServiceContainer {
if (!this._instance) {
throw new Error('Core实例未创建请先调用Core.create()');
}
return this._instance._serviceContainer;
}
/**
* 获取World管理器
*
* 用于管理多个独立的World实例高级用户
*
* @returns WorldManager实例
* @throws 如果Core实例未创建
*
* @example
* ```typescript
* // 创建多个游戏房间
* const wm = Core.worldManager;
* const room1 = wm.createWorld('room_001');
* room1.createScene('game', new GameScene());
* room1.start();
* ```
*/
public static get worldManager(): WorldManager {
if (!this._instance) {
throw new Error('Core实例未创建请先调用Core.create()');
}
return this._instance._worldManager;
}
/**
* 创建Core实例
*
@@ -330,43 +426,6 @@ export class Core {
this._instance.updateInternal(deltaTime);
}
/**
* 注册全局管理器
*
* 将管理器添加到全局管理器列表中,并启用它。
*
* @param manager - 要注册的全局管理器
*/
public static registerGlobalManager(manager: GlobalManager) {
this._instance._globalManagers.push(manager);
manager.enabled = true;
}
/**
* 注销全局管理器
*
* 从全局管理器列表中移除管理器,并禁用它。
*
* @param manager - 要注销的全局管理器
*/
public static unregisterGlobalManager(manager: GlobalManager) {
this._instance._globalManagers.splice(this._instance._globalManagers.indexOf(manager), 1);
manager.enabled = false;
}
/**
* 获取指定类型的全局管理器
*
* @param type - 管理器类型构造函数
* @returns 管理器实例如果未找到则返回null
*/
public static getGlobalManager<T extends GlobalManager>(type: new (...args: unknown[]) => T): T | null {
for (const manager of this._instance._globalManagers) {
if (manager instanceof type)
return manager as T;
}
return null;
}
/**
* 调度定时器
@@ -378,6 +437,7 @@ export class Core {
* @param context - 回调函数的上下文默认为null
* @param onTime - 定时器触发时的回调函数
* @returns 创建的定时器实例
* @throws 如果Core实例未创建或onTime回调未提供
*
* @example
* ```typescript
@@ -393,6 +453,9 @@ export class Core {
* ```
*/
public static schedule<TContext = unknown>(timeInSeconds: number, repeats: boolean = false, context?: TContext, onTime?: (timer: ITimer<TContext>) => void): Timer<TContext> {
if (!this._instance) {
throw new Error('Core实例未创建请先调用Core.create()');
}
if (!onTime) {
throw new Error('onTime callback is required');
}
@@ -413,7 +476,13 @@ export class Core {
if (this._instance._debugManager) {
this._instance._debugManager.updateConfig(config);
} else {
this._instance._debugManager = new DebugManager(this._instance, config);
// 使用DI容器创建DebugManager
this._instance._debugManager = new DebugManager(
this._instance._serviceContainer.resolve(SceneManager),
this._instance._serviceContainer.resolve(PerformanceMonitor),
config
);
this._instance._serviceContainer.registerInstance(DebugManager, this._instance._debugManager);
}
// 更新Core配置
@@ -459,6 +528,90 @@ export class Core {
return this._instance?._config.debugConfig?.enabled || false;
}
/**
* 安装插件
*
* @param plugin - 插件实例
* @throws 如果Core实例未创建或插件安装失败
*
* @example
* ```typescript
* Core.create({ debug: true });
*
* // 安装插件
* await Core.installPlugin(new MyPlugin());
* ```
*/
public static async installPlugin(plugin: IPlugin): Promise<void> {
if (!this._instance) {
throw new Error('Core实例未创建请先调用Core.create()');
}
await this._instance._pluginManager.install(plugin);
}
/**
* 卸载插件
*
* @param name - 插件名称
* @throws 如果Core实例未创建或插件卸载失败
*
* @example
* ```typescript
* await Core.uninstallPlugin('my-plugin');
* ```
*/
public static async uninstallPlugin(name: string): Promise<void> {
if (!this._instance) {
throw new Error('Core实例未创建请先调用Core.create()');
}
await this._instance._pluginManager.uninstall(name);
}
/**
* 获取插件实例
*
* @param name - 插件名称
* @returns 插件实例如果未安装则返回undefined
*
* @example
* ```typescript
* const myPlugin = Core.getPlugin('my-plugin');
* if (myPlugin) {
* console.log(myPlugin.version);
* }
* ```
*/
public static getPlugin(name: string): IPlugin | undefined {
if (!this._instance) {
return undefined;
}
return this._instance._pluginManager.getPlugin(name);
}
/**
* 检查插件是否已安装
*
* @param name - 插件名称
* @returns 是否已安装
*
* @example
* ```typescript
* if (Core.isPluginInstalled('my-plugin')) {
* console.log('Plugin is installed');
* }
* ```
*/
public static isPluginInstalled(name: string): boolean {
if (!this._instance) {
return false;
}
return this._instance._pluginManager.isInstalled(name);
}
/**
* 初始化核心系统
*
@@ -492,20 +645,20 @@ export class Core {
this._performanceMonitor.updateFPS(Time.deltaTime);
}
// 更新全局管理器
const managersStartTime = this._performanceMonitor.startMonitoring('GlobalManagers.update');
for (const globalManager of this._globalManagers) {
if (globalManager.enabled)
globalManager.update();
}
this._performanceMonitor.endMonitoring('GlobalManagers.update', managersStartTime, this._globalManagers.length);
// 更新所有可更新的服务
const servicesStartTime = this._performanceMonitor.startMonitoring('Services.update');
this._serviceContainer.updateAll(deltaTime);
this._performanceMonitor.endMonitoring('Services.update', servicesStartTime, this._serviceContainer.getUpdatableCount());
// 更新对象池管理器
this._poolManager.update();
// 更新场景
// 更新默认场景(通过 SceneManager
this._sceneManager.update();
// 更新额外的 WorldManager
this._worldManager.updateAll();
// 更新调试管理器基于FPS的数据发送
if (this._debugManager) {
this._debugManager.onFrameUpdate(deltaTime);
@@ -528,15 +681,12 @@ export class Core {
this._instance._debugManager.stop();
}
// 清理全局管理器
for (const manager of this._instance._globalManagers) {
manager.enabled = false;
}
this._instance._globalManagers = [];
// 清理所有服务
this._instance._serviceContainer.clear();
Core._logger.info('Core destroyed');
// @ts-ignore - 清空实例引用
// 清空实例引用允许重新创建Core实例
this._instance = null;
}
}

View File

@@ -0,0 +1,364 @@
/**
* 依赖注入装饰器
*
* 提供 @Injectable、@Inject 和 @Updatable 装饰器,用于标记可注入的类和依赖注入点
*/
import type { ServiceContainer } from '../ServiceContainer';
import type { IService, ServiceType } from '../ServiceContainer';
/**
* 依赖注入元数据键
*/
const INJECTABLE_METADATA_KEY = Symbol('injectable');
const INJECT_METADATA_KEY = Symbol('inject');
const INJECT_PARAMS_METADATA_KEY = Symbol('inject:params');
const UPDATABLE_METADATA_KEY = Symbol('updatable');
/**
* 依赖注入元数据存储
*/
const injectableMetadata = new WeakMap<any, InjectableMetadata>();
const injectMetadata = new WeakMap<any, Map<number, ServiceType<any> | string | symbol>>();
const updatableMetadata = new WeakMap<any, UpdatableMetadata>();
/**
* 可注入元数据接口
*/
export interface InjectableMetadata {
/**
* 是否可注入
*/
injectable: boolean;
/**
* 依赖列表
*/
dependencies: Array<ServiceType<any> | string | symbol>;
/**
* 属性注入映射
* key: 属性名, value: 服务类型
*/
properties?: Map<string | symbol, ServiceType<any>>;
}
/**
* 可更新元数据接口
*/
export interface UpdatableMetadata {
/**
* 是否可更新
*/
updatable: boolean;
/**
* 更新优先级数值越小越先执行默认0
*/
priority: number;
}
/**
* @Injectable() 装饰器
*
* 标记类为可注入的服务使其可以通过ServiceContainer进行依赖注入
*
* @example
* ```typescript
* @Injectable()
* class TimeService implements IService {
* constructor() {}
* dispose() {}
* }
*
* @Injectable()
* class PhysicsSystem extends EntitySystem {
* constructor(
* @Inject(TimeService) private timeService: TimeService
* ) {
* super();
* }
* }
* ```
*/
export function Injectable(): ClassDecorator {
return function <T extends Function>(target: T): T {
const existing = injectableMetadata.get(target);
injectableMetadata.set(target, {
injectable: true,
dependencies: [],
properties: existing?.properties
});
return target;
};
}
/**
* @Updatable() 装饰器
*
* 标记服务类为可更新的使其在每帧自动被ServiceContainer调用update方法。
* 使用此装饰器的类必须实现IUpdatable接口包含update方法
*
* @param priority - 更新优先级数值越小越先执行默认0
* @throws 如果类没有实现update方法将在运行时抛出错误
*
* @example
* ```typescript
* @Injectable()
* @Updatable()
* class TimerManager implements IService, IUpdatable {
* update(deltaTime?: number) {
* // 每帧更新逻辑
* }
* dispose() {}
* }
*
* // 指定优先级
* @Injectable()
* @Updatable(10)
* class PhysicsManager implements IService, IUpdatable {
* update() { }
* dispose() {}
* }
* ```
*/
export function Updatable(priority: number = 0): ClassDecorator {
return function <T extends Function>(target: T): T {
// 验证类原型上是否有update方法
const prototype = (target as any).prototype;
if (!prototype || typeof prototype.update !== 'function') {
throw new Error(
`@Updatable() decorator requires class ${target.name} to implement IUpdatable interface with update() method. ` +
`Please add 'implements IUpdatable' and define update(deltaTime?: number): void method.`
);
}
// 标记为可更新
updatableMetadata.set(target, {
updatable: true,
priority
});
return target;
};
}
/**
* @Inject() 装饰器
*
* 标记构造函数参数需要注入的服务类型
*
* @param serviceType 服务类型标识符
*/
export function Inject(serviceType: ServiceType<any> | string | symbol): ParameterDecorator {
return function (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) {
// 获取或创建注入元数据
let params = injectMetadata.get(target);
if (!params) {
params = new Map();
injectMetadata.set(target, params);
}
// 记录参数索引和服务类型的映射
params.set(parameterIndex, serviceType);
};
}
/**
* @InjectProperty() 装饰器
*
* 通过属性装饰器注入依赖
*
* 注入时机在构造函数执行后、onInitialize() 调用前完成
*
* @param serviceType 服务类型
*/
export function InjectProperty(serviceType: ServiceType<any>): PropertyDecorator {
return function (target: any, propertyKey: string | symbol) {
let metadata = injectableMetadata.get(target.constructor);
if (!metadata) {
metadata = {
injectable: true,
dependencies: []
};
injectableMetadata.set(target.constructor, metadata);
}
if (!metadata.properties) {
metadata.properties = new Map();
}
metadata.properties.set(propertyKey, serviceType);
};
}
/**
* 检查类是否标记为可注入
*
* @param target 目标类
* @returns 是否可注入
*/
export function isInjectable(target: any): boolean {
const metadata = injectableMetadata.get(target);
return metadata?.injectable ?? false;
}
/**
* 获取类的依赖注入元数据
*
* @param target 目标类
* @returns 依赖注入元数据
*/
export function getInjectableMetadata(target: any): InjectableMetadata | undefined {
return injectableMetadata.get(target);
}
/**
* 获取构造函数参数的注入元数据
*
* @param target 目标类
* @returns 参数索引到服务类型的映射
*/
export function getInjectMetadata(target: any): Map<number, ServiceType<any> | string | symbol> {
return injectMetadata.get(target) || new Map();
}
/**
* 创建实例并自动注入依赖
*
* @param constructor 构造函数
* @param container 服务容器
* @returns 创建的实例
*
* @example
* ```typescript
* const instance = createInstance(MySystem, container);
* ```
*/
export function createInstance<T>(
constructor: new (...args: any[]) => T,
container: ServiceContainer
): T {
// 获取参数注入元数据
const injectParams = getInjectMetadata(constructor);
// 解析依赖
const dependencies: any[] = [];
// 获取构造函数参数数量
const paramCount = constructor.length;
for (let i = 0; i < paramCount; i++) {
const serviceType = injectParams.get(i);
if (serviceType) {
// 如果有显式的@Inject标记使用标记的类型
if (typeof serviceType === 'string' || typeof serviceType === 'symbol') {
// 字符串或Symbol类型的服务标识
throw new Error(
`String and Symbol service identifiers are not yet supported in constructor injection. ` +
`Please use class types for ${constructor.name} parameter ${i}`
);
} else {
// 类类型
dependencies.push(container.resolve(serviceType as ServiceType<any>));
}
} else {
// 没有@Inject标记传入undefined
dependencies.push(undefined);
}
}
// 创建实例
return new constructor(...dependencies);
}
/**
* 为实例注入属性依赖
*
* @param instance 目标实例
* @param container 服务容器
*/
export function injectProperties<T>(instance: T, container: ServiceContainer): void {
const constructor = (instance as any).constructor;
const metadata = getInjectableMetadata(constructor);
if (!metadata?.properties || metadata.properties.size === 0) {
return;
}
for (const [propertyKey, serviceType] of metadata.properties) {
const service = container.resolve(serviceType);
if (service !== null) {
(instance as any)[propertyKey] = service;
}
}
}
/**
* 检查类是否标记为可更新
*
* @param target 目标类
* @returns 是否可更新
*/
export function isUpdatable(target: any): boolean {
const metadata = updatableMetadata.get(target);
return metadata?.updatable ?? false;
}
/**
* 获取类的可更新元数据
*
* @param target 目标类
* @returns 可更新元数据
*/
export function getUpdatableMetadata(target: any): UpdatableMetadata | undefined {
return updatableMetadata.get(target);
}
/**
* 注册可注入的服务到容器
*
* 自动检测@Injectable装饰器并注册服务
*
* @param container 服务容器
* @param serviceType 服务类型
* @param singleton 是否注册为单例默认true
*
* @example
* ```typescript
* @Injectable()
* class MyService implements IService {
* dispose() {}
* }
*
* // 自动注册
* registerInjectable(Core.services, MyService);
* ```
*/
export function registerInjectable<T extends IService>(
container: ServiceContainer,
serviceType: ServiceType<T>,
singleton: boolean = true
): void {
if (!isInjectable(serviceType)) {
throw new Error(
`${serviceType.name} is not marked as @Injectable(). ` +
`Please add @Injectable() decorator to the class.`
);
}
// 创建工厂函数使用createInstance自动解析依赖
const factory = (c: ServiceContainer) => createInstance(serviceType, c);
// 注册到容器
if (singleton) {
container.registerSingleton(serviceType, factory);
} else {
container.registerTransient(serviceType, factory);
}
}

View File

@@ -0,0 +1,22 @@
/**
* 依赖注入模块
*
* 提供装饰器和工具函数,用于实现依赖注入模式
*/
export {
Injectable,
Inject,
InjectProperty,
Updatable,
isInjectable,
getInjectableMetadata,
getInjectMetadata,
isUpdatable,
getUpdatableMetadata,
createInstance,
injectProperties,
registerInjectable
} from './Decorators';
export type { InjectableMetadata, UpdatableMetadata } from './Decorators';

View File

@@ -0,0 +1,124 @@
import type { Core } from '../Core';
import type { ServiceContainer } from './ServiceContainer';
/**
* 插件状态
*/
export enum PluginState {
/**
* 未安装
*/
NotInstalled = 'not_installed',
/**
* 已安装
*/
Installed = 'installed',
/**
* 安装失败
*/
Failed = 'failed'
}
/**
* 插件接口
*
* 所有插件都必须实现此接口。
* 插件提供了一种扩展框架功能的标准方式。
*
* @example
* ```typescript
* class MyPlugin implements IPlugin {
* readonly name = 'my-plugin';
* readonly version = '1.0.0';
* readonly dependencies = ['other-plugin'];
*
* async install(core: Core, services: ServiceContainer) {
* // 注册服务
* services.registerSingleton(MyService);
*
* // 添加系统
* const world = core.getWorld();
* if (world) {
* world.addSystem(new MySystem());
* }
* }
*
* async uninstall() {
* // 清理资源
* }
* }
* ```
*/
export interface IPlugin {
/**
* 插件唯一名称
*
* 用于依赖解析和插件管理。
*/
readonly name: string;
/**
* 插件版本
*
* 遵循语义化版本规范 (semver)。
*/
readonly version: string;
/**
* 依赖的其他插件名称列表
*
* 这些插件必须在当前插件之前安装。
*/
readonly dependencies?: readonly string[];
/**
* 安装插件
*
* 在此方法中初始化插件,注册服务、系统等。
* 可以是同步或异步的。
*
* @param core - Core实例用于访问World等
* @param services - 服务容器,用于注册服务
*/
install(core: Core, services: ServiceContainer): void | Promise<void>;
/**
* 卸载插件
*
* 清理插件占用的资源。
* 可以是同步或异步的。
*/
uninstall(): void | Promise<void>;
}
/**
* 插件元数据
*/
export interface IPluginMetadata {
/**
* 插件名称
*/
name: string;
/**
* 插件版本
*/
version: string;
/**
* 插件状态
*/
state: PluginState;
/**
* 安装时间戳
*/
installedAt?: number;
/**
* 错误信息(如果安装失败)
*/
error?: string;
}

View File

@@ -0,0 +1,266 @@
import { IPlugin, IPluginMetadata, PluginState } from './Plugin';
import type { IService } from './ServiceContainer';
import type { Core } from '../Core';
import type { ServiceContainer } from './ServiceContainer';
import { createLogger } from '../Utils/Logger';
const logger = createLogger('PluginManager');
/**
* 插件管理器
*
* 负责插件的注册、安装、卸载和生命周期管理。
* 支持依赖检查和异步加载。
*
* @example
* ```typescript
* const core = Core.create();
* const pluginManager = core.getService(PluginManager);
*
* // 注册插件
* await pluginManager.install(new MyPlugin());
*
* // 查询插件
* const plugin = pluginManager.getPlugin('my-plugin');
*
* // 卸载插件
* await pluginManager.uninstall('my-plugin');
* ```
*/
export class PluginManager implements IService {
/**
* 已安装的插件
*/
private _plugins: Map<string, IPlugin> = new Map();
/**
* 插件元数据
*/
private _metadata: Map<string, IPluginMetadata> = new Map();
/**
* Core实例引用
*/
private _core: Core | null = null;
/**
* 服务容器引用
*/
private _services: ServiceContainer | null = null;
/**
* 初始化插件管理器
*
* @param core - Core实例
* @param services - 服务容器
*/
public initialize(core: Core, services: ServiceContainer): void {
this._core = core;
this._services = services;
logger.info('PluginManager initialized');
}
/**
* 安装插件
*
* 会自动检查依赖并按正确顺序安装。
*
* @param plugin - 插件实例
* @throws 如果依赖检查失败或安装失败
*/
public async install(plugin: IPlugin): Promise<void> {
if (!this._core || !this._services) {
throw new Error('PluginManager not initialized. Call initialize() first.');
}
// 检查是否已安装
if (this._plugins.has(plugin.name)) {
logger.warn(`Plugin ${plugin.name} is already installed`);
return;
}
// 检查依赖
if (plugin.dependencies && plugin.dependencies.length > 0) {
this._checkDependencies(plugin);
}
// 创建元数据
const metadata: IPluginMetadata = {
name: plugin.name,
version: plugin.version,
state: PluginState.NotInstalled,
installedAt: Date.now()
};
this._metadata.set(plugin.name, metadata);
try {
// 调用插件的安装方法
logger.info(`Installing plugin: ${plugin.name} v${plugin.version}`);
await plugin.install(this._core, this._services);
// 标记为已安装
this._plugins.set(plugin.name, plugin);
metadata.state = PluginState.Installed;
logger.info(`Plugin ${plugin.name} installed successfully`);
} catch (error) {
// 安装失败
metadata.state = PluginState.Failed;
metadata.error = error instanceof Error ? error.message : String(error);
logger.error(`Failed to install plugin ${plugin.name}:`, error);
throw error;
}
}
/**
* 卸载插件
*
* @param name - 插件名称
* @throws 如果插件未安装或卸载失败
*/
public async uninstall(name: string): Promise<void> {
const plugin = this._plugins.get(name);
if (!plugin) {
throw new Error(`Plugin ${name} is not installed`);
}
// 检查是否有其他插件依赖此插件
this._checkDependents(name);
try {
logger.info(`Uninstalling plugin: ${name}`);
await plugin.uninstall();
// 从注册表中移除
this._plugins.delete(name);
this._metadata.delete(name);
logger.info(`Plugin ${name} uninstalled successfully`);
} catch (error) {
logger.error(`Failed to uninstall plugin ${name}:`, error);
throw error;
}
}
/**
* 获取插件实例
*
* @param name - 插件名称
* @returns 插件实例如果未安装则返回undefined
*/
public getPlugin(name: string): IPlugin | undefined {
return this._plugins.get(name);
}
/**
* 获取插件元数据
*
* @param name - 插件名称
* @returns 插件元数据如果未安装则返回undefined
*/
public getMetadata(name: string): IPluginMetadata | undefined {
return this._metadata.get(name);
}
/**
* 获取所有已安装的插件
*
* @returns 插件列表
*/
public getAllPlugins(): IPlugin[] {
return Array.from(this._plugins.values());
}
/**
* 获取所有插件元数据
*
* @returns 元数据列表
*/
public getAllMetadata(): IPluginMetadata[] {
return Array.from(this._metadata.values());
}
/**
* 检查插件是否已安装
*
* @param name - 插件名称
* @returns 是否已安装
*/
public isInstalled(name: string): boolean {
return this._plugins.has(name);
}
/**
* 检查插件依赖
*
* @param plugin - 插件实例
* @throws 如果依赖未满足
*/
private _checkDependencies(plugin: IPlugin): void {
if (!plugin.dependencies) {
return;
}
const missingDeps: string[] = [];
for (const dep of plugin.dependencies) {
if (!this._plugins.has(dep)) {
missingDeps.push(dep);
}
}
if (missingDeps.length > 0) {
throw new Error(
`Plugin ${plugin.name} has unmet dependencies: ${missingDeps.join(', ')}`
);
}
}
/**
* 检查是否有其他插件依赖指定插件
*
* @param name - 插件名称
* @throws 如果有其他插件依赖此插件
*/
private _checkDependents(name: string): void {
const dependents: string[] = [];
for (const plugin of this._plugins.values()) {
if (plugin.dependencies && plugin.dependencies.includes(name)) {
dependents.push(plugin.name);
}
}
if (dependents.length > 0) {
throw new Error(
`Cannot uninstall plugin ${name}: it is required by ${dependents.join(', ')}`
);
}
}
/**
* 释放资源
*/
public dispose(): void {
// 卸载所有插件(逆序,先卸载依赖项)
const plugins = Array.from(this._plugins.values()).reverse();
for (const plugin of plugins) {
try {
logger.info(`Disposing plugin: ${plugin.name}`);
plugin.uninstall();
} catch (error) {
logger.error(`Error disposing plugin ${plugin.name}:`, error);
}
}
this._plugins.clear();
this._metadata.clear();
this._core = null;
this._services = null;
logger.info('PluginManager disposed');
}
}

View File

@@ -0,0 +1,423 @@
import { createLogger } from '../Utils/Logger';
import { isUpdatable as checkUpdatable, getUpdatableMetadata } from './DI';
const logger = createLogger('ServiceContainer');
/**
* 服务基础接口
* 所有通过 ServiceContainer 管理的服务都应该实现此接口
*/
export interface IService {
/**
* 释放服务占用的资源
* 当服务被注销或容器被清空时调用
*/
dispose(): void;
}
/**
* 服务类型
*
* 支持任意构造函数签名,以便与依赖注入装饰器配合使用
*/
export type ServiceType<T extends IService> = new (...args: any[]) => T;
/**
* 服务生命周期
*/
export enum ServiceLifetime {
/**
* 单例模式 - 整个应用生命周期内只有一个实例
*/
Singleton = 'singleton',
/**
* 瞬时模式 - 每次请求都创建新实例
*/
Transient = 'transient'
}
/**
* 服务注册信息
*/
interface ServiceRegistration<T extends IService> {
/**
* 服务类型
*/
type: ServiceType<T>;
/**
* 服务实例(单例模式)
*/
instance?: T;
/**
* 工厂函数
*/
factory?: (container: ServiceContainer) => T;
/**
* 生命周期
*/
lifetime: ServiceLifetime;
}
/**
* 服务容器
*
* 负责管理所有服务的注册、解析和生命周期。
* 支持依赖注入和服务定位模式。
*
* 特点:
* - 单例和瞬时两种生命周期
* - 支持工厂函数注册
* - 支持实例注册
* - 类型安全的服务解析
*
* @example
* ```typescript
* const container = new ServiceContainer();
*
* // 注册单例服务
* container.registerSingleton(TimerManager);
*
* // 注册工厂函数
* container.registerSingleton(Logger, (c) => createLogger('App'));
*
* // 注册实例
* container.registerInstance(Config, new Config());
*
* // 解析服务
* const timer = container.resolve(TimerManager);
* ```
*/
export class ServiceContainer {
/**
* 服务注册表
*/
private _services: Map<ServiceType<IService>, ServiceRegistration<IService>> = new Map();
/**
* 正在解析的服务栈(用于循环依赖检测)
*/
private _resolving: Set<ServiceType<IService>> = new Set();
/**
* 可更新的服务列表
*
* 自动收集所有使用@Updatable装饰器标记的服务供Core统一更新
* 按优先级排序(数值越小越先执行)
*/
private _updatableServices: Array<{ instance: any; priority: number }> = [];
/**
* 注册单例服务
*
* @param type - 服务类型
* @param factory - 可选的工厂函数
*
* @example
* ```typescript
* // 直接注册类型
* container.registerSingleton(TimerManager);
*
* // 使用工厂函数
* container.registerSingleton(Logger, (c) => {
* return createLogger('App');
* });
* ```
*/
public registerSingleton<T extends IService>(
type: ServiceType<T>,
factory?: (container: ServiceContainer) => T
): void {
if (this._services.has(type as ServiceType<IService>)) {
logger.warn(`Service ${type.name} is already registered`);
return;
}
this._services.set(type as ServiceType<IService>, {
type: type as ServiceType<IService>,
factory: factory as ((container: ServiceContainer) => IService) | undefined,
lifetime: ServiceLifetime.Singleton
});
logger.debug(`Registered singleton service: ${type.name}`);
}
/**
* 注册瞬时服务
*
* 每次解析都会创建新实例。
*
* @param type - 服务类型
* @param factory - 可选的工厂函数
*
* @example
* ```typescript
* // 每次解析都创建新实例
* container.registerTransient(Command);
* ```
*/
public registerTransient<T extends IService>(
type: ServiceType<T>,
factory?: (container: ServiceContainer) => T
): void {
if (this._services.has(type as ServiceType<IService>)) {
logger.warn(`Service ${type.name} is already registered`);
return;
}
this._services.set(type as ServiceType<IService>, {
type: type as ServiceType<IService>,
factory: factory as ((container: ServiceContainer) => IService) | undefined,
lifetime: ServiceLifetime.Transient
});
logger.debug(`Registered transient service: ${type.name}`);
}
/**
* 注册服务实例
*
* 直接注册已创建的实例,自动视为单例。
*
* @param type - 服务类型(构造函数,仅用作标识)
* @param instance - 服务实例
*
* @example
* ```typescript
* const config = new Config();
* container.registerInstance(Config, config);
* ```
*/
public registerInstance<T extends IService>(type: new (...args: any[]) => T, instance: T): void {
if (this._services.has(type as ServiceType<IService>)) {
logger.warn(`Service ${type.name} is already registered`);
return;
}
this._services.set(type as ServiceType<IService>, {
type: type as ServiceType<IService>,
instance: instance as IService,
lifetime: ServiceLifetime.Singleton
});
// 如果使用了@Updatable装饰器添加到可更新列表
if (checkUpdatable(type)) {
const metadata = getUpdatableMetadata(type);
const priority = metadata?.priority ?? 0;
this._updatableServices.push({ instance, priority });
// 按优先级排序(数值越小越先执行)
this._updatableServices.sort((a, b) => a.priority - b.priority);
logger.debug(`Service ${type.name} is updatable (priority: ${priority}), added to update list`);
}
logger.debug(`Registered service instance: ${type.name}`);
}
/**
* 解析服务
*
* @param type - 服务类型
* @returns 服务实例
* @throws 如果服务未注册或存在循环依赖
*
* @example
* ```typescript
* const timer = container.resolve(TimerManager);
* ```
*/
public resolve<T extends IService>(type: ServiceType<T>): T {
const registration = this._services.get(type as ServiceType<IService>);
if (!registration) {
throw new Error(`Service ${type.name} is not registered`);
}
// 检测循环依赖
if (this._resolving.has(type as ServiceType<IService>)) {
const chain = Array.from(this._resolving).map(t => t.name).join(' -> ');
throw new Error(`Circular dependency detected: ${chain} -> ${type.name}`);
}
// 如果是单例且已经有实例,直接返回
if (registration.lifetime === ServiceLifetime.Singleton && registration.instance) {
return registration.instance as T;
}
// 添加到解析栈
this._resolving.add(type as ServiceType<IService>);
try {
// 创建实例
let instance: IService;
if (registration.factory) {
// 使用工厂函数
instance = registration.factory(this);
} else {
// 直接构造
instance = new (registration.type)();
}
// 如果是单例,缓存实例
if (registration.lifetime === ServiceLifetime.Singleton) {
registration.instance = instance;
// 如果使用了@Updatable装饰器添加到可更新列表
if (checkUpdatable(registration.type)) {
const metadata = getUpdatableMetadata(registration.type);
const priority = metadata?.priority ?? 0;
this._updatableServices.push({ instance, priority });
// 按优先级排序(数值越小越先执行)
this._updatableServices.sort((a, b) => a.priority - b.priority);
logger.debug(`Service ${type.name} is updatable (priority: ${priority}), added to update list`);
}
}
return instance as T;
} finally {
// 从解析栈移除
this._resolving.delete(type as ServiceType<IService>);
}
}
/**
* 尝试解析服务
*
* 如果服务未注册返回null而不是抛出异常。
*
* @param type - 服务类型
* @returns 服务实例或null
*
* @example
* ```typescript
* const timer = container.tryResolve(TimerManager);
* if (timer) {
* timer.schedule(...);
* }
* ```
*/
public tryResolve<T extends IService>(type: ServiceType<T>): T | null {
try {
return this.resolve(type);
} catch {
return null;
}
}
/**
* 检查服务是否已注册
*
* @param type - 服务类型
* @returns 是否已注册
*/
public isRegistered<T extends IService>(type: ServiceType<T>): boolean {
return this._services.has(type as ServiceType<IService>);
}
/**
* 注销服务
*
* @param type - 服务类型
* @returns 是否成功注销
*/
public unregister<T extends IService>(type: ServiceType<T>): boolean {
const registration = this._services.get(type as ServiceType<IService>);
if (!registration) {
return false;
}
// 如果有单例实例,调用 dispose
if (registration.instance) {
// 从可更新列表中移除
const index = this._updatableServices.findIndex(item => item.instance === registration.instance);
if (index !== -1) {
this._updatableServices.splice(index, 1);
}
registration.instance.dispose();
}
this._services.delete(type as ServiceType<IService>);
logger.debug(`Unregistered service: ${type.name}`);
return true;
}
/**
* 清空所有服务
*/
public clear(): void {
// 清理所有单例实例
for (const [, registration] of this._services) {
if (registration.instance) {
registration.instance.dispose();
}
}
this._services.clear();
this._updatableServices = [];
logger.debug('Cleared all services');
}
/**
* 获取所有已注册的服务类型
*
* @returns 服务类型数组
*/
public getRegisteredServices(): ServiceType<IService>[] {
return Array.from(this._services.keys());
}
/**
* 更新所有使用@Updatable装饰器标记的服务
*
* 此方法会按优先级顺序遍历所有可更新的服务并调用它们的update方法。
* 所有服务在注册时已经由@Updatable装饰器验证过必须实现IUpdatable接口。
* 通常在Core的主更新循环中调用。
*
* @param deltaTime - 可选的帧时间间隔(秒)
*
* @example
* ```typescript
* // 在Core的update方法中
* this._serviceContainer.updateAll(deltaTime);
* ```
*/
public updateAll(deltaTime?: number): void {
for (const { instance } of this._updatableServices) {
instance.update(deltaTime);
}
}
/**
* 获取所有可更新的服务数量
*
* @returns 可更新服务的数量
*/
public getUpdatableCount(): number {
return this._updatableServices.length;
}
/**
* 获取所有已实例化的服务实例
*
* @returns 所有服务实例的数组
*/
public getAll(): IService[] {
const instances: IService[] = [];
for (const descriptor of this._services.values()) {
if (descriptor.instance) {
instances.push(descriptor.instance);
}
}
return instances;
}
}

View File

@@ -45,6 +45,13 @@ export abstract class Component implements IComponent {
*/
public readonly id: number;
/**
* 所属实体ID
*
* 存储实体ID而非引用避免循环引用符合ECS数据导向设计。
*/
public entityId: number | null = null;
/**
* 创建组件实例
*

View File

@@ -14,7 +14,9 @@ export class EntityBuilder {
constructor(scene: IScene, storageManager: ComponentStorageManager) {
this.scene = scene;
this.storageManager = storageManager;
this.entity = new Entity("", scene.identifierPool.checkOut());
const id = scene.identifierPool.checkOut();
this.entity = new Entity("", id);
this.entity.scene = this.scene as any;
}
/**

View File

@@ -0,0 +1,345 @@
import { Component } from '../Component';
import type { Entity } from '../Entity';
import type { IScene } from '../IScene';
import { createLogger } from '../../Utils/Logger';
const logger = createLogger('ReferenceTracker');
/**
* WeakRef 接口定义
*
* 用于 ES2020 环境下的类型定义
*/
interface IWeakRef<T extends object> {
deref(): T | undefined;
}
/**
* WeakRef Polyfill for ES2020 compatibility
*
* 为了兼容 Cocos Creator、Laya、微信小游戏等目标平台仅支持 ES2020
* 提供 WeakRef 的 Polyfill 实现。
*
* - 现代浏览器:自动使用原生 WeakRef (自动 GC)
* - 旧环境:使用 Polyfill (无自动 GC但 Scene 销毁时会手动清理)
*/
class WeakRefPolyfill<T extends object> implements IWeakRef<T> {
private _target: T;
constructor(target: T) {
this._target = target;
}
deref(): T | undefined {
return this._target;
}
}
/**
* WeakRef 构造函数类型
*/
interface IWeakRefConstructor {
new <T extends object>(target: T): IWeakRef<T>;
}
/**
* WeakRef 实现
*
* 优先使用原生 WeakRef不支持时降级到 Polyfill
*/
const WeakRefImpl: IWeakRefConstructor = (
(typeof globalThis !== 'undefined' && (globalThis as any).WeakRef) ||
(typeof global !== 'undefined' && (global as any).WeakRef) ||
(typeof window !== 'undefined' && (window as any).WeakRef) ||
WeakRefPolyfill
) as IWeakRefConstructor;
/**
* Entity引用记录
*/
export interface EntityRefRecord {
component: IWeakRef<Component>;
propertyKey: string;
}
/**
* 全局EntityID到Scene的映射
*
* 使用全局Map记录每个Entity ID对应的Scene用于装饰器通过Component.entityId查找Scene。
*/
const globalEntitySceneMap = new Map<number, IWeakRef<IScene>>();
/**
* 通过Entity ID获取Scene
*
* @param entityId Entity ID
* @returns Scene实例如果不存在则返回null
*/
export function getSceneByEntityId(entityId: number): IScene | null {
const sceneRef = globalEntitySceneMap.get(entityId);
return sceneRef?.deref() || null;
}
/**
* Entity引用追踪器
*
* 追踪Component中对Entity的引用当Entity被销毁时自动清理所有引用。
*
* @example
* ```typescript
* const tracker = new ReferenceTracker();
* tracker.registerReference(targetEntity, component, 'parent');
* targetEntity.destroy(); // 自动将 component.parent 设为 null
* ```
*/
export class ReferenceTracker {
/**
* Entity ID -> 引用该Entity的所有组件记录
*/
private _references: Map<number, Set<EntityRefRecord>> = new Map();
/**
* 当前Scene的引用
*/
private _scene: IWeakRef<IScene> | null = null;
/**
* 注册Entity引用
*
* @param entity 被引用的Entity
* @param component 持有引用的Component
* @param propertyKey Component中存储引用的属性名
*/
public registerReference(entity: Entity, component: Component, propertyKey: string): void {
const entityId = entity.id;
let records = this._references.get(entityId);
if (!records) {
records = new Set();
this._references.set(entityId, records);
}
const existingRecord = this._findRecord(records, component, propertyKey);
if (existingRecord) {
return;
}
records.add({
component: new WeakRefImpl(component),
propertyKey
});
}
/**
* 注销Entity引用
*
* @param entity 被引用的Entity
* @param component 持有引用的Component
* @param propertyKey Component中存储引用的属性名
*/
public unregisterReference(entity: Entity, component: Component, propertyKey: string): void {
const entityId = entity.id;
const records = this._references.get(entityId);
if (!records) {
return;
}
const record = this._findRecord(records, component, propertyKey);
if (record) {
records.delete(record);
if (records.size === 0) {
this._references.delete(entityId);
}
}
}
/**
* 清理所有指向指定Entity的引用
*
* 将所有引用该Entity的Component属性设为null。
*
* @param entityId 被销毁的Entity ID
*/
public clearReferencesTo(entityId: number): void {
const records = this._references.get(entityId);
if (!records) {
return;
}
const validRecords: EntityRefRecord[] = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push(record);
}
}
for (const record of validRecords) {
const component = record.component.deref();
if (component) {
(component as any)[record.propertyKey] = null;
}
}
this._references.delete(entityId);
}
/**
* 清理Component的所有引用注册
*
* 当Component被移除时调用清理该Component注册的所有引用。
*
* @param component 被移除的Component
*/
public clearComponentReferences(component: Component): void {
for (const [entityId, records] of this._references.entries()) {
const toDelete: EntityRefRecord[] = [];
for (const record of records) {
const comp = record.component.deref();
if (!comp || comp === component) {
toDelete.push(record);
}
}
for (const record of toDelete) {
records.delete(record);
}
if (records.size === 0) {
this._references.delete(entityId);
}
}
}
/**
* 获取指向指定Entity的所有引用记录
*
* @param entityId Entity ID
* @returns 引用记录数组(仅包含有效引用)
*/
public getReferencesTo(entityId: number): EntityRefRecord[] {
const records = this._references.get(entityId);
if (!records) {
return [];
}
const validRecords: EntityRefRecord[] = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push(record);
}
}
return validRecords;
}
/**
* 清理所有失效的WeakRef引用
*
* 遍历所有记录移除已被GC回收的Component引用。
*/
public cleanup(): void {
const entitiesToDelete: number[] = [];
for (const [entityId, records] of this._references.entries()) {
const toDelete: EntityRefRecord[] = [];
for (const record of records) {
if (!record.component.deref()) {
toDelete.push(record);
}
}
for (const record of toDelete) {
records.delete(record);
}
if (records.size === 0) {
entitiesToDelete.push(entityId);
}
}
for (const entityId of entitiesToDelete) {
this._references.delete(entityId);
}
}
/**
* 设置Scene引用
*
* @param scene Scene实例
*/
public setScene(scene: IScene): void {
this._scene = new WeakRefImpl(scene);
}
/**
* 注册Entity到Scene的映射
*
* @param entityId Entity ID
* @param scene Scene实例
*/
public registerEntityScene(entityId: number, scene: IScene): void {
globalEntitySceneMap.set(entityId, new WeakRefImpl(scene));
}
/**
* 注销Entity到Scene的映射
*
* @param entityId Entity ID
*/
public unregisterEntityScene(entityId: number): void {
globalEntitySceneMap.delete(entityId);
}
/**
* 获取调试信息
*/
public getDebugInfo(): object {
const info: Record<string, any> = {};
for (const [entityId, records] of this._references.entries()) {
const validRecords = [];
for (const record of records) {
const component = record.component.deref();
if (component) {
validRecords.push({
componentId: component.id,
propertyKey: record.propertyKey
});
}
}
if (validRecords.length > 0) {
info[`entity_${entityId}`] = validRecords;
}
}
return info;
}
/**
* 查找指定的引用记录
*/
private _findRecord(
records: Set<EntityRefRecord>,
component: Component,
propertyKey: string
): EntityRefRecord | undefined {
for (const record of records) {
const comp = record.component.deref();
if (comp === component && record.propertyKey === propertyKey) {
return record;
}
}
return undefined;
}
}

View File

@@ -0,0 +1,147 @@
import type { Entity } from '../Entity';
import type { Component } from '../Component';
import { getSceneByEntityId } from '../Core/ReferenceTracker';
import { createLogger } from '../../Utils/Logger';
const logger = createLogger('EntityRefDecorator');
/**
* EntityRef元数据的Symbol键
*/
export const ENTITY_REF_METADATA = Symbol('EntityRefMetadata');
/**
* EntityRef值存储的Symbol键
*/
const ENTITY_REF_VALUES = Symbol('EntityRefValues');
/**
* EntityRef元数据
*/
export interface EntityRefMetadata {
properties: Set<string>;
}
/**
* 获取或创建组件的EntityRef值存储Map
*/
function getValueMap(component: Component): Map<string, Entity | null> {
let map = (component as any)[ENTITY_REF_VALUES];
if (!map) {
map = new Map<string, Entity | null>();
(component as any)[ENTITY_REF_VALUES] = map;
}
return map;
}
/**
* Entity引用装饰器
*
* 标记Component属性为Entity引用自动追踪引用关系。
* 当被引用的Entity销毁时该属性会自动设为null。
*
* @example
* ```typescript
* class ParentComponent extends Component {
* @EntityRef() parent: Entity | null = null;
* }
*
* const parent = scene.createEntity('Parent');
* const child = scene.createEntity('Child');
* const comp = child.addComponent(new ParentComponent());
*
* comp.parent = parent;
* parent.destroy(); // comp.parent 自动变为 null
* ```
*/
export function EntityRef(): PropertyDecorator {
return function (target: any, propertyKey: string | symbol) {
const constructor = target.constructor;
let metadata: EntityRefMetadata = constructor[ENTITY_REF_METADATA];
if (!metadata) {
metadata = {
properties: new Set()
};
constructor[ENTITY_REF_METADATA] = metadata;
}
const propKeyString = typeof propertyKey === 'symbol' ? propertyKey.toString() : propertyKey;
metadata.properties.add(propKeyString);
Object.defineProperty(target, propertyKey, {
get: function (this: Component) {
const valueMap = getValueMap(this);
return valueMap.get(propKeyString) || null;
},
set: function (this: Component, newValue: Entity | null) {
const valueMap = getValueMap(this);
const oldValue = valueMap.get(propKeyString) || null;
if (oldValue === newValue) {
return;
}
const scene = this.entityId !== null ? getSceneByEntityId(this.entityId) : null;
if (!scene || !scene.referenceTracker) {
valueMap.set(propKeyString, newValue);
return;
}
const tracker = scene.referenceTracker;
if (oldValue) {
tracker.unregisterReference(oldValue, this, propKeyString);
}
if (newValue) {
if (newValue.scene !== scene) {
logger.error(`Cannot reference Entity from different Scene. Entity: ${newValue.name}, Scene: ${newValue.scene?.name || 'null'}`);
return;
}
if (newValue.isDestroyed) {
logger.warn(`Cannot reference destroyed Entity: ${newValue.name}`);
valueMap.set(propKeyString, null);
return;
}
tracker.registerReference(newValue, this, propKeyString);
}
valueMap.set(propKeyString, newValue);
},
enumerable: true,
configurable: true
});
};
}
/**
* 获取Component的EntityRef元数据
*
* @param component Component实例或Component类
* @returns EntityRef元数据如果不存在则返回null
*/
export function getEntityRefMetadata(component: any): EntityRefMetadata | null {
if (!component) {
return null;
}
const constructor = typeof component === 'function'
? component
: component.constructor;
return constructor[ENTITY_REF_METADATA] || null;
}
/**
* 检查Component是否有EntityRef属性
*
* @param component Component实例或Component类
* @returns 如果有EntityRef属性返回true
*/
export function hasEntityRef(component: any): boolean {
return getEntityRefMetadata(component) !== null;
}

View File

@@ -39,34 +39,72 @@ export function ECSComponent(typeName: string) {
};
}
/**
* System元数据配置
*/
export interface SystemMetadata {
/**
* 更新顺序数值越小越先执行默认0
*/
updateOrder?: number;
/**
* 是否默认启用默认true
*/
enabled?: boolean;
}
/**
* 系统类型装饰器
* 用于为系统类指定固定的类型名称,避免在代码混淆后失效
*
*
* @param typeName 系统类型名称
* @param metadata 系统元数据配置
* @example
* ```typescript
* // 基本使用
* @ECSSystem('Movement')
* class MovementSystem extends EntitySystem {
* protected process(entities: Entity[]): void {
* // 系统逻辑
* }
* }
*
* // 配置更新顺序
* @Injectable()
* @ECSSystem('Physics', { updateOrder: 10 })
* class PhysicsSystem extends EntitySystem {
* constructor(@Inject(CollisionSystem) private collision: CollisionSystem) {
* super(Matcher.of(Transform, RigidBody));
* }
* }
* ```
*/
export function ECSSystem(typeName: string) {
export function ECSSystem(typeName: string, metadata?: SystemMetadata) {
return function <T extends new (...args: any[]) => EntitySystem>(target: T): T {
if (!typeName || typeof typeName !== 'string') {
throw new Error('ECSSystem装饰器必须提供有效的类型名称');
}
// 在构造函数上存储类型名称
(target as any)[SYSTEM_TYPE_NAME] = typeName;
// 存储元数据
if (metadata) {
(target as any).__systemMetadata__ = metadata;
}
return target;
};
}
/**
* 获取System的元数据
*/
export function getSystemMetadata(systemType: new (...args: any[]) => EntitySystem): SystemMetadata | undefined {
return (systemType as any).__systemMetadata__;
}
/**
* 获取组件类型的名称,优先使用装饰器指定的名称
*

View File

@@ -5,6 +5,18 @@ export {
getSystemTypeName,
getComponentInstanceTypeName,
getSystemInstanceTypeName,
getSystemMetadata,
COMPONENT_TYPE_NAME,
SYSTEM_TYPE_NAME
} from './TypeDecorators';
} from './TypeDecorators';
export type { SystemMetadata } from './TypeDecorators';
export {
EntityRef,
getEntityRefMetadata,
hasEntityRef,
ENTITY_REF_METADATA
} from './EntityRefDecorator';
export type { EntityRefMetadata } from './EntityRefDecorator';

View File

@@ -145,12 +145,6 @@ export class Entity {
*/
private _componentCache: Component[] | null = null;
/**
* 本地组件存储(用于没有 Scene 的 Entity
* 当 Entity 添加到 Scene 时,组件会迁移到 Scene 的 componentStorageManager
*/
private _localComponents: Map<ComponentType, Component> = new Map();
/**
* 构造函数
*
@@ -186,28 +180,23 @@ export class Entity {
*/
private _rebuildComponentCache(): void {
const components: Component[] = [];
const mask = this._componentMask;
if (!this.scene?.componentStorageManager) {
this._componentCache = components;
return;
}
const mask = this._componentMask;
const maxBitIndex = ComponentRegistry.getRegisteredCount();
for (let bitIndex = 0; bitIndex < maxBitIndex; bitIndex++) {
if (BitMask64Utils.getBit(mask, bitIndex)) {
const componentType = ComponentRegistry.getTypeByBitIndex(bitIndex);
if (componentType) {
let component: Component | null = null;
// 优先从 Scene 存储获取
if (this.scene?.componentStorageManager) {
component = this.scene.componentStorageManager.getComponent(
this.id,
componentType
);
}
// Fallback 到本地存储
if (!component) {
component = this._localComponents.get(componentType) || null;
}
const component = this.scene.componentStorageManager.getComponent(
this.id,
componentType
);
if (component) {
components.push(component);
@@ -378,9 +367,6 @@ export class Entity {
ComponentRegistry.register(componentType);
}
// 存储到本地 Map
this._localComponents.set(componentType, component);
// 更新位掩码
const componentMask = ComponentRegistry.getBitMask(componentType);
BitMask64Utils.orInPlace(this._componentMask, componentMask);
@@ -406,19 +392,29 @@ export class Entity {
*/
public addComponent<T extends Component>(component: T): T {
const componentType = component.constructor as ComponentType<T>;
if (!this.scene) {
throw new Error(`Entity must be added to Scene before adding components. Use scene.createEntity() instead of new Entity()`);
}
if (!this.scene.componentStorageManager) {
throw new Error(`Scene does not have componentStorageManager`);
}
if (this.hasComponent(componentType)) {
throw new Error(`Entity ${this.name} already has component ${getComponentTypeName(componentType)}`);
}
this.addComponentInternal(component);
if (this.scene && this.scene.componentStorageManager) {
this.scene.componentStorageManager.addComponent(this.id, component);
}
this.scene.componentStorageManager.addComponent(this.id, component);
component.entityId = this.id;
if (this.scene.referenceTracker) {
this.scene.referenceTracker.registerEntityScene(this.id, this.scene);
}
component.onAddedToEntity();
if (Entity.eventBus) {
Entity.eventBus.emitComponentAdded({
timestamp: Date.now(),
@@ -430,7 +426,7 @@ export class Entity {
component: component
});
}
// 通知所有相关的QuerySystem组件已变动
Entity.notifyQuerySystems(this);
@@ -459,16 +455,13 @@ export class Entity {
return null;
}
// 优先从 Scene 存储获取
if (this.scene?.componentStorageManager) {
const component = this.scene.componentStorageManager.getComponent(this.id, type);
if (component) {
return component as T;
}
// Scene存储获取
if (!this.scene?.componentStorageManager) {
return null;
}
// Fallback 到本地存储
return (this._localComponents.get(type) as T) || null;
const component = this.scene.componentStorageManager.getComponent(this.id, type);
return component as T | null;
}
@@ -538,24 +531,27 @@ export class Entity {
const bitIndex = ComponentRegistry.getBitIndex(componentType);
// 从本地存储移除
this._localComponents.delete(componentType);
// 更新位掩码
BitMask64Utils.clearBit(this._componentMask, bitIndex);
// 使缓存失效
this._componentCache = null;
// 从 Scene 存储移除
if (this.scene && this.scene.componentStorageManager) {
// 从Scene存储移除
if (this.scene?.componentStorageManager) {
this.scene.componentStorageManager.removeComponent(this.id, componentType);
}
if (this.scene?.referenceTracker) {
this.scene.referenceTracker.clearComponentReferences(component);
}
if (component.onRemovedFromEntity) {
component.onRemovedFromEntity();
}
component.entityId = null;
if (Entity.eventBus) {
Entity.eventBus.emitComponentRemoved({
timestamp: Date.now(),
@@ -593,9 +589,6 @@ export class Entity {
public removeAllComponents(): void {
const componentsToRemove = [...this.components];
// 清除本地存储
this._localComponents.clear();
// 清除位掩码
BitMask64Utils.clear(this._componentMask);
@@ -605,7 +598,7 @@ export class Entity {
for (const component of componentsToRemove) {
const componentType = component.constructor as ComponentType;
if (this.scene && this.scene.componentStorageManager) {
if (this.scene?.componentStorageManager) {
this.scene.componentStorageManager.removeComponent(this.id, componentType);
}
@@ -884,6 +877,11 @@ export class Entity {
this._isDestroyed = true;
if (this.scene && this.scene.referenceTracker) {
this.scene.referenceTracker.clearReferencesTo(this.id);
this.scene.referenceTracker.unregisterEntityScene(this.id);
}
const childrenToDestroy = [...this._children];
for (const child of childrenToDestroy) {
child.destroy();

View File

@@ -1,11 +1,11 @@
import { Entity } from './Entity';
import { EntityList } from './Utils/EntityList';
import { EntityProcessorList } from './Utils/EntityProcessorList';
import { IdentifierPool } from './Utils/IdentifierPool';
import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem } from './Core/EventSystem';
import type { ReferenceTracker } from './Core/ReferenceTracker';
/**
* 场景接口定义
@@ -41,12 +41,7 @@ export interface IScene {
* 场景中的实体集合
*/
readonly entities: EntityList;
/**
* 实体系统处理器集合
*/
readonly entityProcessors: EntityProcessorList;
/**
* 标识符池
*/
@@ -67,6 +62,11 @@ export interface IScene {
*/
readonly eventSystem: TypeSafeEventSystem;
/**
* 引用追踪器
*/
readonly referenceTracker: ReferenceTracker;
/**
* 获取系统列表
*/
@@ -171,4 +171,12 @@ export interface ISceneConfig {
* 场景名称
*/
name?: string;
/**
* 性能监控器实例(可选)
*
* 如果不提供Scene会自动从Core.services获取全局PerformanceMonitor。
* 提供此参数可以实现场景级别的独立性能监控。
*/
performanceMonitor?: any;
}

View File

@@ -1,18 +1,23 @@
import { Entity } from './Entity';
import { EntityList } from './Utils/EntityList';
import { EntityProcessorList } from './Utils/EntityProcessorList';
import { IdentifierPool } from './Utils/IdentifierPool';
import { EntitySystem } from './Systems/EntitySystem';
import { ComponentStorageManager, ComponentRegistry } from './Core/ComponentStorage';
import { QuerySystem } from './Core/QuerySystem';
import { TypeSafeEventSystem } from './Core/EventSystem';
import { EventBus } from './Core/EventBus';
import { ReferenceTracker } from './Core/ReferenceTracker';
import { IScene, ISceneConfig } from './IScene';
import { getComponentInstanceTypeName, getSystemInstanceTypeName } from './Decorators';
import { getComponentInstanceTypeName, getSystemInstanceTypeName, getSystemMetadata } from "./Decorators";
import { TypedQueryBuilder } from './Core/Query/TypedQuery';
import { SceneSerializer, SceneSerializationOptions, SceneDeserializationOptions } from './Serialization/SceneSerializer';
import { IncrementalSerializer, IncrementalSnapshot, IncrementalSerializationOptions } from './Serialization/IncrementalSerializer';
import { ComponentPoolManager } from './Core/ComponentPool';
import { PerformanceMonitor } from '../Utils/PerformanceMonitor';
import { ServiceContainer, type ServiceType } from '../Core/ServiceContainer';
import { createInstance, isInjectable, injectProperties } from '../Core/DI';
import { isUpdatable, getUpdatableMetadata } from '../Core/DI/Decorators';
import { createLogger } from '../Utils/Logger';
/**
* 游戏场景默认实现类
@@ -42,12 +47,6 @@ export class Scene implements IScene {
*/
public readonly entities: EntityList;
/**
* 实体系统处理器集合
*
* 管理场景内所有实体系统的执行。
*/
public readonly entityProcessors: EntityProcessorList;
/**
* 实体ID池
@@ -72,36 +71,121 @@ export class Scene implements IScene {
/**
* 事件系统
*
*
* 类型安全的事件系统。
*/
public readonly eventSystem: TypeSafeEventSystem;
/**
* 引用追踪器
*
* 追踪Component中对Entity的引用当Entity销毁时自动清理引用。
*/
public readonly referenceTracker: ReferenceTracker;
/**
* 服务容器
*
* 场景级别的依赖注入容器用于管理EntitySystem和其他服务的生命周期。
* 每个Scene拥有独立的服务容器实现场景间的隔离。
*/
private readonly _services: ServiceContainer;
/**
* 日志记录器
*/
private readonly logger: ReturnType<typeof createLogger>;
/**
* 性能监控器
*
* 用于监控场景和系统的性能。可以在构造函数中注入如果不提供则从Core获取。
*/
private readonly _performanceMonitor: PerformanceMonitor;
/**
* 场景是否已开始运行
*/
private _didSceneBegin: boolean = false;
/**
* 获取系统列表(兼容性属性)
* 获取场景中所有已注册的EntitySystem
*
* 按updateOrder排序。
*
* @returns 系统列表
*/
public get systems(): EntitySystem[] {
return this.entityProcessors.processors;
// 从ServiceContainer获取所有EntitySystem实例
const services = this._services.getAll();
const systems: EntitySystem[] = [];
for (const service of services) {
if (service instanceof EntitySystem) {
systems.push(service);
}
}
// 按updateOrder排序
systems.sort((a, b) => a.updateOrder - b.updateOrder);
return systems;
}
/**
* 通过类型获取System实例
*
* @param systemType System类型
* @returns System实例如果未找到则返回null
*
* @example
* ```typescript
* const physics = scene.getSystem(PhysicsSystem);
* if (physics) {
* physics.doSomething();
* }
* ```
*/
public getSystem<T extends EntitySystem>(systemType: ServiceType<T>): T | null {
return this._services.tryResolve(systemType) as T | null;
}
/**
* 获取场景的服务容器
*
* 用于注册和解析场景级别的服务如EntitySystem
*
* @example
* ```typescript
* // 注册服务
* scene.services.registerSingleton(PhysicsSystem);
*
* // 解析服务
* const physics = scene.services.resolve(PhysicsSystem);
* ```
*/
public get services(): ServiceContainer {
return this._services;
}
/**
* 创建场景实例
*/
constructor(config?: ISceneConfig) {
this.entities = new EntityList(this);
this.entityProcessors = new EntityProcessorList();
this.identifierPool = new IdentifierPool();
this.componentStorageManager = new ComponentStorageManager();
this.querySystem = new QuerySystem();
this.eventSystem = new TypeSafeEventSystem();
this.referenceTracker = new ReferenceTracker();
this._services = new ServiceContainer();
this.logger = createLogger('Scene');
// 从配置获取 PerformanceMonitor如果未提供则创建一个新实例
// Scene 应该是独立的,不依赖于 Core通过构造函数参数明确依赖关系
this._performanceMonitor = config?.performanceMonitor || new PerformanceMonitor();
// 应用配置
if (config?.name) {
this.name = config.name;
}
@@ -109,7 +193,7 @@ export class Scene implements IScene {
if (!Entity.eventBus) {
Entity.eventBus = new EventBus(false);
}
if (Entity.eventBus) {
Entity.eventBus.onComponentAdded((data: unknown) => {
this.eventSystem.emitSync('component:added', data);
@@ -147,10 +231,6 @@ export class Scene implements IScene {
* 这个方法会启动场景。它将启动实体处理器等并调用onStart方法。
*/
public begin() {
// 启动实体处理器
if (this.entityProcessors != null)
this.entityProcessors.begin();
// 标记场景已开始运行并调用onStart方法
this._didSceneBegin = true;
this.onStart();
@@ -174,9 +254,8 @@ export class Scene implements IScene {
// 清空组件存储
this.componentStorageManager.clear();
// 结束实体处理器
if (this.entityProcessors)
this.entityProcessors.end();
// 清空服务容器会调用所有服务的dispose方法包括所有EntitySystem
this._services.clear();
// 调用卸载方法
this.unload();
@@ -190,11 +269,28 @@ export class Scene implements IScene {
this.entities.updateLists();
if (this.entityProcessors != null)
this.entityProcessors.update();
// 更新所有EntitySystem
const systems = this.systems;
for (const system of systems) {
if (system.enabled) {
try {
system.update();
} catch (error) {
this.logger.error(`Error in system ${system.constructor.name}.update():`, error);
}
}
}
if (this.entityProcessors != null)
this.entityProcessors.lateUpdate();
// LateUpdate
for (const system of systems) {
if (system.enabled) {
try {
system.lateUpdate();
} catch (error) {
this.logger.error(`Error in system ${system.constructor.name}.lateUpdate():`, error);
}
}
}
}
/**
@@ -214,7 +310,7 @@ export class Scene implements IScene {
* 当实体或组件发生变化时调用
*/
public clearSystemEntityCaches(): void {
for (const system of this.entityProcessors.processors) {
for (const system of this.systems) {
system.clearEntityCache();
}
}
@@ -417,18 +513,122 @@ export class Scene implements IScene {
/**
* 在场景中添加一个EntitySystem处理器
* @param processor 处理器
*
* 支持两种使用方式:
* 1. 传入类型推荐自动使用DI创建实例支持@Injectable和@Inject装饰器
* 2. 传入实例:直接使用提供的实例
*
* @param systemTypeOrInstance 系统类型或系统实例
* @returns 添加的处理器实例
*
* @example
* ```typescript
* // 方式1传入类型自动DI推荐
* @Injectable()
* class PhysicsSystem extends EntitySystem {
* constructor(@Inject(CollisionSystem) private collision: CollisionSystem) {
* super(Matcher.of(Transform));
* }
* }
* scene.addEntityProcessor(PhysicsSystem);
*
* // 方式2传入实例
* const system = new MySystem();
* scene.addEntityProcessor(system);
* ```
*/
public addEntityProcessor(processor: EntitySystem) {
if (this.entityProcessors.processors.includes(processor)) {
return processor;
public addEntityProcessor<T extends EntitySystem>(
systemTypeOrInstance: ServiceType<T> | T
): T {
let system: T;
let constructor: any;
if (typeof systemTypeOrInstance === 'function') {
constructor = systemTypeOrInstance;
if (this._services.isRegistered(constructor)) {
return this._services.resolve(constructor) as T;
}
if (isInjectable(constructor)) {
system = createInstance(constructor, this._services) as T;
} else {
system = new (constructor as any)() as T;
}
} else {
system = systemTypeOrInstance;
constructor = system.constructor;
if (this._services.isRegistered(constructor)) {
return system;
}
}
processor.scene = this;
this.entityProcessors.add(processor);
processor.initialize();
processor.setUpdateOrder(this.entityProcessors.count - 1);
return processor;
system.scene = this;
system.setPerformanceMonitor(this._performanceMonitor);
const metadata = getSystemMetadata(constructor);
if (metadata?.updateOrder !== undefined) {
system.setUpdateOrder(metadata.updateOrder);
}
if (metadata?.enabled !== undefined) {
system.enabled = metadata.enabled;
}
this._services.registerInstance(constructor, system);
injectProperties(system, this._services);
system.initialize();
return system;
}
/**
* 批量注册EntitySystem到场景使用DI
*
* 自动按照依赖顺序注册多个System。
* 所有System必须使用@Injectable装饰器标记。
*
* @param systemTypes System类型数组
* @returns 注册的System实例数组
*
* @example
* ```typescript
* @Injectable()
* @ECSSystem('Collision', { updateOrder: 5 })
* class CollisionSystem extends EntitySystem implements IService {
* constructor() { super(Matcher.of(Collider)); }
* dispose() {}
* }
*
* @Injectable()
* @ECSSystem('Physics', { updateOrder: 10 })
* class PhysicsSystem extends EntitySystem implements IService {
* constructor(@Inject(CollisionSystem) private collision: CollisionSystem) {
* super(Matcher.of(Transform, RigidBody));
* }
* dispose() {}
* }
*
* // 批量注册(自动解析依赖顺序)
* scene.registerSystems([
* CollisionSystem,
* PhysicsSystem, // 自动注入CollisionSystem
* RenderSystem
* ]);
* ```
*/
public registerSystems(systemTypes: Array<ServiceType<EntitySystem>>): EntitySystem[] {
const registeredSystems: EntitySystem[] = [];
for (const systemType of systemTypes) {
const system = this.addEntityProcessor(systemType);
registeredSystems.push(system);
}
return registeredSystems;
}
/**
@@ -443,8 +643,13 @@ export class Scene implements IScene {
* 从场景中删除EntitySystem处理器
* @param processor 要删除的处理器
*/
public removeEntityProcessor(processor: EntitySystem) {
this.entityProcessors.remove(processor);
public removeEntityProcessor(processor: EntitySystem): void {
const constructor = processor.constructor as any;
// 从ServiceContainer移除
this._services.unregister(constructor);
// 重置System状态
processor.reset();
}
@@ -458,10 +663,24 @@ export class Scene implements IScene {
/**
* 获取指定类型的EntitySystem处理器
*
* @deprecated 推荐使用依赖注入代替此方法。使用 `scene.services.resolve(SystemType)` 或在System构造函数中使用 `@Inject(SystemType)` 装饰器。
*
* @param type 处理器类型
* @returns 处理器实例如果未找到则返回null
*
* @example
* ```typescript
* @Injectable()
* class MySystem extends EntitySystem {
* constructor(@Inject(PhysicsSystem) private physics: PhysicsSystem) {
* super();
* }
* }
* ```
*/
public getEntityProcessor<T extends EntitySystem>(type: new (...args: unknown[]) => T): T | null {
return this.entityProcessors.getProcessor(type);
return this._services.tryResolve(type as any) as T | null;
}
/**
@@ -474,7 +693,7 @@ export class Scene implements IScene {
} {
return {
entityCount: this.entities.count,
processorCount: this.entityProcessors.count,
processorCount: this.systems.length,
componentStorageStats: this.componentStorageManager.getAllStats()
};
}
@@ -501,10 +720,11 @@ export class Scene implements IScene {
}>;
componentStats: Map<string, any>;
} {
const systems = this.systems;
return {
name: this.name || this.constructor.name,
entityCount: this.entities.count,
processorCount: this.entityProcessors.count,
processorCount: systems.length,
isRunning: this._didSceneBegin,
entities: this.entities.buffer.map(entity => ({
name: entity.name,
@@ -512,7 +732,7 @@ export class Scene implements IScene {
componentCount: entity.components.length,
componentTypes: entity.components.map(c => getComponentInstanceTypeName(c))
})),
processors: this.entityProcessors.processors.map(processor => ({
processors: systems.map(processor => ({
name: getSystemInstanceTypeName(processor),
updateOrder: processor.updateOrder,
entityCount: (processor as any)._entities?.length || 0
@@ -524,10 +744,10 @@ export class Scene implements IScene {
/**
* 序列化场景
*
* 将场景及其所有实体、组件序列化为JSON字符串或二进制Buffer
* 将场景及其所有实体、组件序列化为JSON字符串或二进制Uint8Array
*
* @param options 序列化选项
* @returns 序列化后的数据JSON字符串或二进制Buffer
* @returns 序列化后的数据JSON字符串或二进制Uint8Array
*
* @example
* ```typescript
@@ -543,7 +763,7 @@ export class Scene implements IScene {
* });
* ```
*/
public serialize(options?: SceneSerializationOptions): string | Buffer {
public serialize(options?: SceneSerializationOptions): string | Uint8Array {
return SceneSerializer.serialize(this, options);
}
@@ -552,7 +772,7 @@ export class Scene implements IScene {
*
* 从序列化数据恢复场景状态
*
* @param saveData 序列化的数据JSON字符串或二进制Buffer
* @param saveData 序列化的数据JSON字符串或二进制Uint8Array
* @param options 反序列化选项
*
* @example
@@ -568,7 +788,7 @@ export class Scene implements IScene {
* });
* ```
*/
public deserialize(saveData: string | Buffer, options?: SceneDeserializationOptions): void {
public deserialize(saveData: string | Uint8Array, options?: SceneDeserializationOptions): void {
SceneSerializer.deserialize(this, saveData, options);
}
@@ -640,7 +860,7 @@ export class Scene implements IScene {
/**
* 应用增量变更到场景
*
* @param incremental 增量快照数据IncrementalSnapshot对象、JSON字符串或二进制Buffer
* @param incremental 增量快照数据IncrementalSnapshot对象、JSON字符串或二进制Uint8Array
* @param componentRegistry 组件类型注册表(可选,默认使用全局注册表)
*
* @example
@@ -652,21 +872,20 @@ export class Scene implements IScene {
* const jsonData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'json' });
* scene.applyIncremental(jsonData);
*
* // 从二进制Buffer应用
* // 从二进制Uint8Array应用
* const binaryData = IncrementalSerializer.serializeIncremental(snapshot, { format: 'binary' });
* scene.applyIncremental(binaryData);
* ```
*/
public applyIncremental(
incremental: IncrementalSnapshot | string | Buffer,
incremental: IncrementalSnapshot | string | Uint8Array,
componentRegistry?: Map<string, any>
): void {
const isSerializedData = typeof incremental === 'string' ||
(typeof Buffer !== 'undefined' && Buffer.isBuffer(incremental)) ||
incremental instanceof Uint8Array;
const snapshot = isSerializedData
? IncrementalSerializer.deserializeIncremental(incremental as string | Buffer)
? IncrementalSerializer.deserializeIncremental(incremental as string | Uint8Array)
: incremental as IncrementalSnapshot;
const registry = componentRegistry || ComponentRegistry.getAllComponentNames() as Map<string, any>;

View File

@@ -1,8 +1,9 @@
import { IScene } from './IScene';
import { ECSFluentAPI, createECSAPI } from './Core/FluentAPI';
import { Time } from '../Utils/Time';
import { Core } from '../Core';
import { createLogger } from '../Utils/Logger';
import type { IService } from '../Core/ServiceContainer';
import { World } from './World';
/**
* 单场景管理器
@@ -46,11 +47,11 @@ import { createLogger } from '../Utils/Logger';
* sceneManager.loadScene(new MenuScene());
* ```
*/
export class SceneManager {
export class SceneManager implements IService {
/**
* 当前活跃场景
* 内部默认World
*/
private _currentScene: IScene | null = null;
private _defaultWorld: World;
/**
* 待切换的下一个场景(延迟切换用)
@@ -67,6 +68,31 @@ export class SceneManager {
*/
private _logger = createLogger('SceneManager');
/**
* 场景切换回调函数
*/
private _onSceneChangedCallback?: () => void;
/**
* 默认场景ID
*/
private static readonly DEFAULT_SCENE_ID = '__main__';
constructor() {
this._defaultWorld = new World({ name: '__default__' });
this._defaultWorld.start();
}
/**
* 设置场景切换回调
*
* @param callback 场景切换时的回调函数
* @internal
*/
public setSceneChangedCallback(callback: () => void): void {
this._onSceneChangedCallback = callback;
}
/**
* 设置当前场景(立即切换)
*
@@ -82,16 +108,12 @@ export class SceneManager {
* ```
*/
public setScene<T extends IScene>(scene: T): T {
// 结束旧场景
if (this._currentScene) {
this._logger.info(`Ending scene: ${this._currentScene.name}`);
this._currentScene.end();
}
// 移除旧场景
this._defaultWorld.removeAllScenes();
// 设置并初始化新场景
this._currentScene = scene;
this._currentScene.initialize();
this._currentScene.begin();
// 通过 World 创建新场景
this._defaultWorld.createScene(SceneManager.DEFAULT_SCENE_ID, scene);
this._defaultWorld.setSceneActive(SceneManager.DEFAULT_SCENE_ID, true);
// 重建ECS API
if (scene.querySystem && scene.eventSystem) {
@@ -103,10 +125,9 @@ export class SceneManager {
// 触发场景切换回调
Time.sceneChanged();
// 通知调试管理器
const coreInstance = Core.Instance;
if (coreInstance && coreInstance._debugManager) {
coreInstance._debugManager.onSceneChanged();
// 通知调试管理器(通过回调)
if (this._onSceneChangedCallback) {
this._onSceneChangedCallback();
}
this._logger.info(`Scene changed to: ${scene.name}`);
@@ -146,7 +167,7 @@ export class SceneManager {
* @returns 当前场景实例如果没有场景则返回null
*/
public get currentScene(): IScene | null {
return this._currentScene;
return this._defaultWorld.getScene(SceneManager.DEFAULT_SCENE_ID);
}
/**
@@ -193,10 +214,9 @@ export class SceneManager {
this._nextScene = null;
}
// 更新当前场景
if (this._currentScene) {
this._currentScene.update();
}
// 通过 World 统一更新
this._defaultWorld.updateGlobalSystems();
this._defaultWorld.updateScenes();
}
/**
@@ -206,12 +226,9 @@ export class SceneManager {
* 通常在应用程序关闭时调用。
*/
public destroy(): void {
if (this._currentScene) {
this._logger.info(`Destroying scene: ${this._currentScene.name}`);
this._currentScene.end();
this._currentScene = null;
}
this._logger.info('SceneManager destroying');
this._defaultWorld.destroy();
this._nextScene = null;
this._ecsAPI = null;
@@ -224,7 +241,7 @@ export class SceneManager {
* @returns 如果有活跃场景返回true否则返回false
*/
public get hasScene(): boolean {
return this._currentScene !== null;
return this._defaultWorld.getScene(SceneManager.DEFAULT_SCENE_ID) !== null;
}
/**
@@ -235,4 +252,11 @@ export class SceneManager {
public get hasPendingScene(): boolean {
return this._nextScene !== null;
}
/**
* 释放资源IService接口
*/
public dispose(): void {
this.destroy();
}
}

View File

@@ -8,6 +8,7 @@ import { Entity } from '../Entity';
import { Component } from '../Component';
import { ComponentType } from '../Core/ComponentStorage';
import { ComponentSerializer, SerializedComponent } from './ComponentSerializer';
import { IScene } from '../IScene';
/**
* 序列化后的实体数据
@@ -108,18 +109,25 @@ export class EntitySerializer {
* @param componentRegistry 组件类型注册表
* @param idGenerator 实体ID生成器用于生成新ID或保持原ID
* @param preserveIds 是否保持原始ID默认false
* @param scene 目标场景可选用于设置entity.scene以支持添加组件
* @returns 反序列化后的实体
*/
public static deserialize(
serializedEntity: SerializedEntity,
componentRegistry: Map<string, ComponentType>,
idGenerator: () => number,
preserveIds: boolean = false
preserveIds: boolean = false,
scene?: IScene
): Entity {
// 创建实体使用原始ID或新生成的ID
const entityId = preserveIds ? serializedEntity.id : idGenerator();
const entity = new Entity(serializedEntity.name, entityId);
// 如果提供了scene先设置entity.scene以支持添加组件
if (scene) {
entity.scene = scene;
}
// 恢复实体属性
entity.tag = serializedEntity.tag;
entity.active = serializedEntity.active;
@@ -142,7 +150,8 @@ export class EntitySerializer {
childData,
componentRegistry,
idGenerator,
preserveIds
preserveIds,
scene
);
entity.addChild(childEntity);
}
@@ -181,13 +190,15 @@ export class EntitySerializer {
* @param componentRegistry 组件类型注册表
* @param idGenerator 实体ID生成器
* @param preserveIds 是否保持原始ID
* @param scene 目标场景可选用于设置entity.scene以支持添加组件
* @returns 反序列化后的实体数组
*/
public static deserializeEntities(
serializedEntities: SerializedEntity[],
componentRegistry: Map<string, ComponentType>,
idGenerator: () => number,
preserveIds: boolean = false
preserveIds: boolean = false,
scene?: IScene
): Entity[] {
const result: Entity[] = [];
@@ -196,7 +207,8 @@ export class EntitySerializer {
serialized,
componentRegistry,
idGenerator,
preserveIds
preserveIds,
scene
);
result.push(entity);
}

View File

@@ -11,7 +11,7 @@ import { Component } from '../Component';
import { ComponentSerializer, SerializedComponent } from './ComponentSerializer';
import { SerializedEntity } from './EntitySerializer';
import { ComponentType } from '../Core/ComponentStorage';
import * as msgpack from 'msgpack-lite';
import { encode, decode } from '@msgpack/msgpack';
/**
* 变更操作类型
@@ -609,7 +609,7 @@ export class IncrementalSerializer {
*
* @param incremental 增量快照
* @param options 序列化选项
* @returns 序列化后的数据JSON字符串或二进制Buffer
* @returns 序列化后的数据JSON字符串或二进制Uint8Array
*
* @example
* ```typescript
@@ -631,7 +631,7 @@ export class IncrementalSerializer {
public static serializeIncremental(
incremental: IncrementalSnapshot,
options?: { format?: IncrementalSerializationFormat; pretty?: boolean }
): string | Buffer {
): string | Uint8Array {
const opts = {
format: 'json' as IncrementalSerializationFormat,
pretty: false,
@@ -639,7 +639,7 @@ export class IncrementalSerializer {
};
if (opts.format === 'binary') {
return msgpack.encode(incremental);
return encode(incremental);
} else {
return opts.pretty
? JSON.stringify(incremental, null, 2)
@@ -650,7 +650,7 @@ export class IncrementalSerializer {
/**
* 反序列化增量快照
*
* @param data 序列化的数据JSON字符串或二进制Buffer
* @param data 序列化的数据JSON字符串或二进制Uint8Array
* @returns 增量快照
*
* @example
@@ -662,13 +662,13 @@ export class IncrementalSerializer {
* const snapshot = IncrementalSerializer.deserializeIncremental(buffer);
* ```
*/
public static deserializeIncremental(data: string | Buffer): IncrementalSnapshot {
public static deserializeIncremental(data: string | Uint8Array): IncrementalSnapshot {
if (typeof data === 'string') {
// JSON格式
return JSON.parse(data);
} else {
// 二进制格式MessagePack
return msgpack.decode(data);
return decode(data) as IncrementalSnapshot;
}
}

View File

@@ -11,7 +11,7 @@ import { ComponentType, ComponentRegistry } from '../Core/ComponentStorage';
import { EntitySerializer, SerializedEntity } from './EntitySerializer';
import { getComponentTypeName } from '../Decorators';
import { getSerializationMetadata } from './SerializationDecorators';
import * as msgpack from 'msgpack-lite';
import { encode, decode } from '@msgpack/msgpack';
/**
* 场景序列化格式
@@ -154,9 +154,9 @@ export class SceneSerializer {
*
* @param scene 要序列化的场景
* @param options 序列化选项
* @returns 序列化后的数据JSON字符串或二进制Buffer
* @returns 序列化后的数据JSON字符串或二进制Uint8Array
*/
public static serialize(scene: IScene, options?: SceneSerializationOptions): string | Buffer {
public static serialize(scene: IScene, options?: SceneSerializationOptions): string | Uint8Array {
const opts: SceneSerializationOptions = {
systems: false,
format: 'json',
@@ -207,7 +207,7 @@ export class SceneSerializer {
: JSON.stringify(serializedScene);
} else {
// 二进制格式(使用 MessagePack
return msgpack.encode(serializedScene);
return encode(serializedScene);
}
}
@@ -215,12 +215,12 @@ export class SceneSerializer {
* 反序列化场景
*
* @param scene 目标场景
* @param saveData 序列化的数据JSON字符串或二进制Buffer
* @param saveData 序列化的数据JSON字符串或二进制Uint8Array
* @param options 反序列化选项
*/
public static deserialize(
scene: IScene,
saveData: string | Buffer,
saveData: string | Uint8Array,
options?: SceneDeserializationOptions
): void {
const opts: SceneDeserializationOptions = {
@@ -237,7 +237,7 @@ export class SceneSerializer {
serializedScene = JSON.parse(saveData);
} else {
// 二进制格式MessagePack
serializedScene = msgpack.decode(saveData);
serializedScene = decode(saveData) as SerializedScene;
}
} catch (error) {
throw new Error(`Failed to parse save data: ${error}`);
@@ -269,7 +269,8 @@ export class SceneSerializer {
serializedScene.entities,
componentRegistry,
idGenerator,
opts.preserveIds || false
opts.preserveIds || false,
scene
);
// 将实体添加到场景

View File

@@ -8,6 +8,7 @@ import { getSystemInstanceTypeName } from '../Decorators';
import { createLogger } from '../../Utils/Logger';
import type { EventListenerConfig, TypeSafeEventSystem, EventHandler } from '../Core/EventSystem';
import type { ComponentConstructor, ComponentInstance } from '../../Types/TypeHelpers';
import type { IService } from '../../Core/ServiceContainer';
/**
* 事件监听器记录
@@ -65,10 +66,10 @@ interface EventListenerRecord {
*/
export abstract class EntitySystem<
TComponents extends readonly ComponentConstructor[] = []
> implements ISystemBase {
> implements ISystemBase, IService {
private _updateOrder: number;
private _enabled: boolean;
private _performanceMonitor: PerformanceMonitor;
private _performanceMonitor: PerformanceMonitor | null;
private _systemName: string;
private _initialized: boolean;
private _matcher: Matcher;
@@ -148,7 +149,7 @@ export abstract class EntitySystem<
constructor(matcher?: Matcher) {
this._updateOrder = 0;
this._enabled = true;
this._performanceMonitor = PerformanceMonitor.instance;
this._performanceMonitor = null;
this._systemName = getSystemInstanceTypeName(this);
this._initialized = false;
this._matcher = matcher || Matcher.empty();
@@ -192,6 +193,23 @@ export abstract class EntitySystem<
this._scene = value;
}
/**
* 设置性能监控器
*/
public setPerformanceMonitor(monitor: PerformanceMonitor): void {
this._performanceMonitor = monitor;
}
/**
* 获取性能监控器
*/
private getPerformanceMonitor(): PerformanceMonitor {
if (!this._performanceMonitor) {
throw new Error(`${this._systemName}: PerformanceMonitor未注入请确保在Core.create()之后再添加System到Scene`);
}
return this._performanceMonitor;
}
/**
* 获取实体匹配器
*/
@@ -205,9 +223,6 @@ export abstract class EntitySystem<
*/
public setUpdateOrder(order: number): void {
this._updateOrder = order;
if (this.scene && this.scene.entityProcessors) {
this.scene.entityProcessors.setDirty();
}
}
/**
@@ -533,7 +548,8 @@ export abstract class EntitySystem<
return;
}
const startTime = this._performanceMonitor.startMonitoring(this._systemName);
const monitor = this.getPerformanceMonitor();
const startTime = monitor.startMonitoring(this._systemName);
let entityCount = 0;
try {
@@ -544,7 +560,7 @@ export abstract class EntitySystem<
this.process(this._entityCache.frame);
} finally {
this._performanceMonitor.endMonitoring(this._systemName, startTime, entityCount);
monitor.endMonitoring(this._systemName, startTime, entityCount);
}
}
@@ -556,7 +572,8 @@ export abstract class EntitySystem<
return;
}
const startTime = this._performanceMonitor.startMonitoring(`${this._systemName}_Late`);
const monitor = this.getPerformanceMonitor();
const startTime = monitor.startMonitoring(`${this._systemName}_Late`);
let entityCount = 0;
try {
@@ -566,7 +583,7 @@ export abstract class EntitySystem<
this.lateProcess(entities);
this.onEnd();
} finally {
this._performanceMonitor.endMonitoring(`${this._systemName}_Late`, startTime, entityCount);
monitor.endMonitoring(`${this._systemName}_Late`, startTime, entityCount);
// 清理帧缓存
this._entityCache.clearFrame();
}
@@ -626,27 +643,27 @@ export abstract class EntitySystem<
/**
* 获取系统的性能数据
*
*
* @returns 性能数据或undefined
*/
public getPerformanceData() {
return this._performanceMonitor.getSystemData(this._systemName);
return this.getPerformanceMonitor().getSystemData(this._systemName);
}
/**
* 获取系统的性能统计
*
*
* @returns 性能统计或undefined
*/
public getPerformanceStats() {
return this._performanceMonitor.getSystemStats(this._systemName);
return this.getPerformanceMonitor().getSystemStats(this._systemName);
}
/**
* 重置系统的性能数据
*/
public resetPerformanceData(): void {
this._performanceMonitor.resetSystem(this._systemName);
this.getPerformanceMonitor().resetSystem(this._systemName);
}
/**
@@ -706,15 +723,43 @@ export abstract class EntitySystem<
/**
* 当实体从系统中移除时调用
*
*
* 子类可以重写此方法来处理实体移除事件。
*
*
* @param entity 被移除的实体
*/
protected onRemoved(entity: Entity): void {
// 子类可以重写此方法
}
/**
* 释放系统资源
*
* 实现IService接口要求的dispose方法。
* 当系统从Scene中移除或Scene销毁时调用。
*
* 默认行为:
* - 移除所有事件监听器
* - 清空所有缓存
* - 重置初始化状态
*
* 子类可以重写此方法来清理自定义资源但应该调用super.dispose()。
*/
public dispose(): void {
// 移除所有事件监听器
this.cleanupManualEventListeners();
// 清空所有缓存
this._entityCache.clearAll();
this._entityIdMap = null;
// 重置状态
this._initialized = false;
this._scene = null;
this.logger.debug(`System ${this._systemName} disposed`);
}
/**
* 添加事件监听器
*

View File

@@ -183,6 +183,17 @@ export class World {
return Array.from(this._scenes.values());
}
/**
* 移除所有Scene
*/
public removeAllScenes(): void {
const sceneIds = Array.from(this._scenes.keys());
for (const sceneId of sceneIds) {
this.removeScene(sceneId);
}
logger.info(`从World '${this.name}' 中移除所有Scene`);
}
/**
* 设置Scene激活状态
*/

View File

@@ -1,5 +1,6 @@
import { World, IWorldConfig } from './World';
import { createLogger } from '../Utils/Logger';
import type { IService } from '../Core/ServiceContainer';
const logger = createLogger('WorldManager');
@@ -61,7 +62,7 @@ export interface IWorldManagerConfig {
* }
* ```
*/
export class WorldManager {
export class WorldManager implements IService {
private readonly _config: IWorldManagerConfig;
private readonly _worlds: Map<string, World> = new Map();
private readonly _activeWorlds: Set<string> = new Set();
@@ -387,6 +388,14 @@ export class WorldManager {
logger.info('WorldManager已销毁');
}
/**
* 实现 IService 接口的 dispose 方法
* 调用 destroy 方法进行清理
*/
public dispose(): void {
this.destroy();
}
// ===== 私有方法 =====
/**

View File

@@ -13,4 +13,6 @@ export * from './Core/Events';
export * from './Core/Query';
export * from './Core/Storage';
export * from './Core/StorageDecorators';
export * from './Serialization';
export * from './Serialization';
export { ReferenceTracker, getSceneByEntityId } from './Core/ReferenceTracker';
export type { EntityRefRecord } from './Core/ReferenceTracker';

View File

@@ -0,0 +1,20 @@
/**
* 可更新接口
*
* 实现此接口的服务将在每帧被Core自动调用update方法
*/
export interface IUpdatable {
/**
* 每帧更新方法
*
* @param deltaTime - 帧时间间隔(秒),可选参数
*/
update(deltaTime?: number): void;
}
/**
* 检查对象是否实现了IUpdatable接口
*/
export function isUpdatable(obj: any): obj is IUpdatable {
return obj && typeof obj.update === 'function';
}

View File

@@ -4,6 +4,7 @@
// 导出TypeScript类型增强工具
export * from './TypeHelpers';
export * from './IUpdatable';
/**
* 组件接口
@@ -15,7 +16,7 @@ export interface IComponent {
/** 组件唯一标识符 */
readonly id: number;
/** 组件所属的实体ID */
entityId?: string | number;
entityId: number | null;
/** 组件添加到实体时的回调 */
onAddedToEntity(): void;

View File

@@ -9,13 +9,18 @@ import { Component } from '../../ECS/Component';
import { ComponentPoolManager } from '../../ECS/Core/ComponentPool';
import { Pool } from '../../Utils/Pool';
import { getComponentInstanceTypeName, getSystemInstanceTypeName } from '../../ECS/Decorators';
import type { IService } from '../../Core/ServiceContainer';
import { SceneManager } from '../../ECS/SceneManager';
import { PerformanceMonitor } from '../PerformanceMonitor';
/**
* 调试管理器
*
* 整合所有调试数据收集器,负责收集和发送调试数据
*
* 通过构造函数接收SceneManager和PerformanceMonitor避免直接依赖Core实例
*/
export class DebugManager {
export class DebugManager implements IService {
private config: IECSDebugConfig;
private webSocketManager: WebSocketManager;
private entityCollector: EntityDataCollector;
@@ -23,8 +28,8 @@ export class DebugManager {
private performanceCollector: PerformanceDataCollector;
private componentCollector: ComponentDataCollector;
private sceneCollector: SceneDataCollector;
private sceneProvider: () => any;
private performanceMonitorProvider: () => any;
private sceneManager: SceneManager;
private performanceMonitor: PerformanceMonitor;
private frameCounter: number = 0;
private lastSendTime: number = 0;
@@ -33,15 +38,18 @@ export class DebugManager {
/**
* 构造调试管理器
* @param core Core实例
* @param sceneManager 场景管理器
* @param performanceMonitor 性能监控器
* @param config 调试配置
*/
constructor(core: any, config: IECSDebugConfig) {
constructor(
sceneManager: SceneManager,
performanceMonitor: PerformanceMonitor,
config: IECSDebugConfig
) {
this.config = config;
// 设置提供器函数
this.sceneProvider = () => (core as any).scene || (core.constructor as any).scene;
this.performanceMonitorProvider = () => core._performanceMonitor;
this.sceneManager = sceneManager;
this.performanceMonitor = performanceMonitor;
// 初始化数据收集器
this.entityCollector = new EntityDataCollector();
@@ -338,7 +346,7 @@ export class DebugManager {
// 收集其他内存统计
const baseMemoryInfo = this.collectBaseMemoryInfo();
const scene = this.sceneProvider();
const scene = this.sceneManager.currentScene;
// 使用专门的内存计算方法收集实体数据
const entityData = this.entityCollector.collectEntityDataWithMemory(scene);
@@ -452,7 +460,7 @@ export class DebugManager {
/**
* 收集组件内存统计(仅用于内存快照)
*/
private collectComponentMemoryStats(entityList: { buffer: Array<{ id: number; name?: string; destroyed?: boolean; components?: Component[] }> }): {
private collectComponentMemoryStats(entityList: { buffer: Array<{ id: number; name?: string; destroyed?: boolean; components?: readonly Component[] }> }): {
totalMemory: number;
componentTypes: number;
totalInstances: number;
@@ -546,7 +554,7 @@ export class DebugManager {
updateOrder: number;
}>;
} {
const scene = this.sceneProvider();
const scene = this.sceneManager.currentScene;
let totalSystemMemory = 0;
const systemBreakdown: Array<{
name: string;
@@ -556,11 +564,11 @@ export class DebugManager {
}> = [];
try {
const entityProcessors = scene?.entityProcessors;
if (entityProcessors && entityProcessors.processors) {
const systems = scene?.systems;
if (systems) {
const systemTypeMemoryCache = new Map<string, number>();
for (const system of entityProcessors.processors) {
for (const system of systems) {
const systemTypeName = getSystemInstanceTypeName(system);
let systemMemory: number;
@@ -720,16 +728,15 @@ export class DebugManager {
error?: string;
} {
try {
const performanceMonitor = this.performanceMonitorProvider();
if (!performanceMonitor) {
if (!this.performanceMonitor) {
return { enabled: false };
}
const stats = performanceMonitor.getAllSystemStats();
const warnings = performanceMonitor.getPerformanceWarnings();
const stats = this.performanceMonitor.getAllSystemStats();
const warnings = this.performanceMonitor.getPerformanceWarnings();
return {
enabled: (performanceMonitor as { enabled?: boolean }).enabled ?? false,
enabled: (this.performanceMonitor as { enabled?: boolean }).enabled ?? false,
systemCount: stats.size,
warnings: warnings.slice(0, 10), // 最多10个警告
topSystems: Array.from(stats.entries()).map((entry) => {
@@ -753,7 +760,7 @@ export class DebugManager {
*/
public getDebugData(): IECSDebugData {
const currentTime = Date.now();
const scene = this.sceneProvider();
const scene = this.sceneManager.currentScene;
const debugData: IECSDebugData = {
timestamp: currentTime,
@@ -769,13 +776,11 @@ export class DebugManager {
}
if (this.config.channels.systems) {
const performanceMonitor = this.performanceMonitorProvider();
debugData.systems = this.systemCollector.collectSystemData(performanceMonitor, scene);
debugData.systems = this.systemCollector.collectSystemData(this.performanceMonitor, scene);
}
if (this.config.channels.performance) {
const performanceMonitor = this.performanceMonitorProvider();
debugData.performance = this.performanceCollector.collectPerformanceData(performanceMonitor);
debugData.performance = this.performanceCollector.collectPerformanceData(this.performanceMonitor);
}
if (this.config.channels.components) {
@@ -821,4 +826,11 @@ export class DebugManager {
// console.error('[ECS Debug] 发送调试数据失败:', error);
}
}
/**
* 释放资源
*/
public dispose(): void {
this.stop();
}
}

View File

@@ -99,20 +99,20 @@ export interface PerformanceThresholds {
};
}
import type { IService } from '../Core/ServiceContainer';
/**
* 高性能监控器
* 用于监控ECS系统的性能表现提供详细的分析和优化建议
*/
export class PerformanceMonitor {
private static _instance: PerformanceMonitor;
export class PerformanceMonitor implements IService {
private _systemData = new Map<string, PerformanceData>();
private _systemStats = new Map<string, PerformanceStats>();
private _warnings: PerformanceWarning[] = [];
private _isEnabled = false;
private _maxRecentSamples = 60; // 保留最近60帧的数据
private _maxWarnings = 100; // 最大警告数量
// 性能阈值配置
private _thresholds: PerformanceThresholds = {
executionTime: { warning: 16.67, critical: 33.33 }, // 60fps和30fps对应的帧时间
@@ -139,18 +139,8 @@ export class PerformanceMonitor {
private _gcCount = 0;
private _lastGcCheck = 0;
private _gcCheckInterval = 1000;
/**
* 获取单例实例
*/
public static get instance(): PerformanceMonitor {
if (!PerformanceMonitor._instance) {
PerformanceMonitor._instance = new PerformanceMonitor();
}
return PerformanceMonitor._instance;
}
private constructor() {}
constructor() {}
/**
* 启用性能监控
@@ -392,7 +382,7 @@ export class PerformanceMonitor {
*/
public setMaxRecentSamples(maxSamples: number): void {
this._maxRecentSamples = maxSamples;
// 裁剪现有数据
for (const stats of this._systemStats.values()) {
while (stats.recentTimes.length > maxSamples) {
@@ -400,4 +390,16 @@ export class PerformanceMonitor {
}
}
}
/**
* 释放资源
*/
public dispose(): void {
this._systemData.clear();
this._systemStats.clear();
this._warnings = [];
this._fpsHistory = [];
this._memoryHistory = [];
this._isEnabled = false;
}
}

View File

@@ -1,21 +1,18 @@
import { IPoolable, PoolStats } from './IPoolable';
import { Pool } from './Pool';
import type { IService } from '../../Core/ServiceContainer';
/**
* 池管理器
* 统一管理所有对象池
*/
export class PoolManager {
private static instance: PoolManager;
export class PoolManager implements IService {
private pools = new Map<string, Pool<any>>();
private autoCompactInterval = 60000; // 60秒
private lastCompactTime = 0;
public static getInstance(): PoolManager {
if (!PoolManager.instance) {
PoolManager.instance = new PoolManager();
}
return PoolManager.instance;
constructor() {
// 普通构造函数,不再使用单例模式
}
/**
@@ -228,4 +225,12 @@ export class PoolManager {
this.pools.clear();
this.lastCompactTime = 0;
}
/**
* 释放资源
* 实现 IService 接口
*/
public dispose(): void {
this.reset();
}
}

View File

@@ -1,14 +1,19 @@
import { GlobalManager } from '../GlobalManager';
import { Timer } from './Timer';
import { ITimer } from './ITimer';
import type { IService } from '../../Core/ServiceContainer';
import type { IUpdatable } from '../../Types/IUpdatable';
import { Updatable } from '../../Core/DI';
/**
* 定时器管理器
*
* 允许动作的延迟和重复执行
*/
export class TimerManager extends GlobalManager {
@Updatable()
export class TimerManager implements IService, IUpdatable {
public _timers: Array<Timer<unknown>> = [];
public override update() {
public update() {
for (let i = this._timers.length - 1; i >= 0; i --){
if (this._timers[i].tick()){
this._timers[i].unload();
@@ -31,4 +36,14 @@ export class TimerManager extends GlobalManager {
return timer;
}
/**
* 释放资源
*/
public dispose(): void {
for (const timer of this._timers) {
timer.unload();
}
this._timers = [];
}
}

View File

@@ -5,6 +5,25 @@
// 核心模块
export { Core } from './Core';
export { ServiceContainer, ServiceLifetime } from './Core/ServiceContainer';
export type { IService, ServiceType } from './Core/ServiceContainer';
// 插件系统
export { PluginManager } from './Core/PluginManager';
export { PluginState } from './Core/Plugin';
export type { IPlugin, IPluginMetadata } from './Core/Plugin';
// 依赖注入
export {
Injectable,
Inject,
Updatable,
registerInjectable,
createInstance,
isUpdatable,
getUpdatableMetadata
} from './Core/DI';
export type { InjectableMetadata, UpdatableMetadata } from './Core/DI';
// 核心管理器
export { Emitter, FuncPack } from './Utils/Emitter';

View File

@@ -3,8 +3,10 @@ import { Scene } from '../src/ECS/Scene';
import { SceneManager } from '../src/ECS/SceneManager';
import { Entity } from '../src/ECS/Entity';
import { Component } from '../src/ECS/Component';
import { GlobalManager } from '../src/Utils/GlobalManager';
import { ITimer } from '../src/Utils/Timers/ITimer';
import { Updatable } from '../src/Core/DI';
import type { IService } from '../src/Core/ServiceContainer';
import type { IUpdatable } from '../src/Types/IUpdatable';
// 测试组件
class TestComponent extends Component {
@@ -41,21 +43,17 @@ class TestScene extends Scene {
}
}
// 测试全局管理器
class TestGlobalManager extends GlobalManager {
// 测试可更新服务
@Updatable()
class TestUpdatableService implements IService, IUpdatable {
public updateCallCount = 0;
public override _enabled = false;
public override get enabled(): boolean {
return this._enabled;
}
public override set enabled(value: boolean) {
this._enabled = value;
public update(): void {
this.updateCallCount++;
}
public override update(): void {
this.updateCallCount++;
public dispose(): void {
// 清理资源
}
}
@@ -129,96 +127,75 @@ describe('Core - 核心管理系统测试', () => {
// 注意场景管理功能已移至SceneManager
// 相关测试请查看 SceneManager.test.ts
describe('更新循环 - 全局服务', () => {
describe('更新循环 - 可更新服务', () => {
let core: Core;
let globalManager: TestGlobalManager;
let updatableService: TestUpdatableService;
beforeEach(() => {
core = Core.create(true);
globalManager = new TestGlobalManager();
Core.registerGlobalManager(globalManager);
updatableService = new TestUpdatableService();
Core.services.registerInstance(TestUpdatableService, updatableService);
});
test('应该能够更新全局管理器', () => {
test('应该能够更新可更新服务', () => {
Core.update(0.016);
expect(globalManager.updateCallCount).toBe(1);
expect(updatableService.updateCallCount).toBe(1);
});
test('暂停状态下不应该执行更新', () => {
Core.paused = true;
Core.update(0.016);
expect(globalManager.updateCallCount).toBe(0);
expect(updatableService.updateCallCount).toBe(0);
// 恢复状态
Core.paused = false;
});
test('禁用的全局管理器不应该被更新', () => {
globalManager.enabled = false;
Core.update(0.016);
expect(globalManager.updateCallCount).toBe(0);
});
test('多次更新应该累积调用', () => {
Core.update(0.016);
Core.update(0.016);
Core.update(0.016);
expect(globalManager.updateCallCount).toBe(3);
expect(updatableService.updateCallCount).toBe(3);
});
});
describe('全局管理器管理', () => {
describe('服务容器集成', () => {
let core: Core;
let manager1: TestGlobalManager;
let manager2: TestGlobalManager;
let service1: TestUpdatableService;
beforeEach(() => {
core = Core.create(true);
manager1 = new TestGlobalManager();
manager2 = new TestGlobalManager();
service1 = new TestUpdatableService();
});
test('应该能够注册全局管理器', () => {
Core.registerGlobalManager(manager1);
expect(manager1.enabled).toBe(true);
test('应该能够通过ServiceContainer注册可更新服务', () => {
Core.services.registerInstance(TestUpdatableService, service1);
// 测试更新是否被调用
Core.update(0.016);
expect(manager1.updateCallCount).toBe(1);
expect(service1.updateCallCount).toBe(1);
});
test('应该能够注销全局管理器', () => {
Core.registerGlobalManager(manager1);
Core.unregisterGlobalManager(manager1);
expect(manager1.enabled).toBe(false);
test('应该能够注销服务', () => {
Core.services.registerInstance(TestUpdatableService, service1);
Core.services.unregister(TestUpdatableService);
// 测试更新不应该被调用
Core.update(0.016);
expect(manager1.updateCallCount).toBe(0);
expect(service1.updateCallCount).toBe(0);
});
test('应该能够获取指定类型的全局管理器', () => {
Core.registerGlobalManager(manager1);
const retrieved = Core.getGlobalManager(TestGlobalManager);
expect(retrieved).toBe(manager1);
test('应该能够通过ServiceContainer解析服务', () => {
Core.services.registerInstance(TestUpdatableService, service1);
const retrieved = Core.services.resolve(TestUpdatableService);
expect(retrieved).toBe(service1);
});
test('获取不存在的管理器应该返回null', () => {
const retrieved = Core.getGlobalManager(TestGlobalManager);
expect(retrieved).toBeNull();
});
test('应该能够管理多个全局管理器', () => {
Core.registerGlobalManager(manager1);
Core.registerGlobalManager(manager2);
Core.update(0.016);
expect(manager1.updateCallCount).toBe(1);
expect(manager2.updateCallCount).toBe(1);
test('解析不存在的服务应该抛出错误', () => {
expect(() => {
Core.services.resolve(TestUpdatableService);
}).toThrow();
});
});

View File

@@ -0,0 +1,206 @@
import { Injectable, Inject, isInjectable, getInjectMetadata, createInstance, registerInjectable } from '../../src/Core/DI';
import { ServiceContainer } from '../../src/Core/ServiceContainer';
import type { IService } from '../../src/Core/ServiceContainer';
// 测试服务类
@Injectable()
class SimpleService implements IService {
public value: string = 'simple';
dispose() {
// 清理资源
}
}
@Injectable()
class DependentService implements IService {
constructor(
@Inject(SimpleService) public simpleService: SimpleService
) {}
dispose() {
// 清理资源
}
}
@Injectable()
class MultiDependencyService implements IService {
constructor(
@Inject(SimpleService) public service1: SimpleService,
@Inject(DependentService) public service2: DependentService
) {}
dispose() {
// 清理资源
}
}
// 非Injectable类用于测试错误情况
class NonInjectableService implements IService {
dispose() {}
}
describe('DI - 依赖注入装饰器测试', () => {
let container: ServiceContainer;
beforeEach(() => {
container = new ServiceContainer();
});
describe('@Injectable 装饰器', () => {
test('应该正确标记类为可注入', () => {
expect(isInjectable(SimpleService)).toBe(true);
expect(isInjectable(DependentService)).toBe(true);
});
test('未标记的类不应该是可注入的', () => {
expect(isInjectable(NonInjectableService)).toBe(false);
});
});
describe('@Inject 装饰器', () => {
test('应该记录参数注入元数据', () => {
const metadata = getInjectMetadata(DependentService);
expect(metadata.size).toBe(1);
expect(metadata.get(0)).toBe(SimpleService);
});
test('应该记录多个参数的注入元数据', () => {
const metadata = getInjectMetadata(MultiDependencyService);
expect(metadata.size).toBe(2);
expect(metadata.get(0)).toBe(SimpleService);
expect(metadata.get(1)).toBe(DependentService);
});
});
describe('createInstance', () => {
test('应该创建无依赖的实例', () => {
container.registerSingleton(SimpleService);
const instance = createInstance(SimpleService, container);
expect(instance).toBeInstanceOf(SimpleService);
expect(instance.value).toBe('simple');
});
test('应该创建有依赖的实例', () => {
container.registerSingleton(SimpleService);
container.registerSingleton(DependentService, () =>
createInstance(DependentService, container)
);
const instance = createInstance(DependentService, container);
expect(instance).toBeInstanceOf(DependentService);
expect(instance.simpleService).toBeInstanceOf(SimpleService);
});
test('应该创建有多个依赖的实例', () => {
container.registerSingleton(SimpleService);
container.registerSingleton(DependentService, () =>
createInstance(DependentService, container)
);
container.registerSingleton(MultiDependencyService, () =>
createInstance(MultiDependencyService, container)
);
const instance = createInstance(MultiDependencyService, container);
expect(instance).toBeInstanceOf(MultiDependencyService);
expect(instance.service1).toBeInstanceOf(SimpleService);
expect(instance.service2).toBeInstanceOf(DependentService);
});
test('依赖应该正确解析为单例', () => {
container.registerSingleton(SimpleService);
container.registerSingleton(DependentService, () =>
createInstance(DependentService, container)
);
const simple1 = container.resolve(SimpleService);
const dependent = createInstance(DependentService, container);
expect(dependent.simpleService).toBe(simple1);
});
});
describe('registerInjectable', () => {
test('应该注册可注入的服务', () => {
registerInjectable(container, SimpleService);
expect(container.isRegistered(SimpleService)).toBe(true);
});
test('应该自动解析依赖', () => {
registerInjectable(container, SimpleService);
registerInjectable(container, DependentService);
const instance = container.resolve(DependentService);
expect(instance).toBeInstanceOf(DependentService);
expect(instance.simpleService).toBeInstanceOf(SimpleService);
});
test('应该正确处理多层依赖', () => {
registerInjectable(container, SimpleService);
registerInjectable(container, DependentService);
registerInjectable(container, MultiDependencyService);
const instance = container.resolve(MultiDependencyService);
expect(instance).toBeInstanceOf(MultiDependencyService);
expect(instance.service1).toBeInstanceOf(SimpleService);
expect(instance.service2).toBeInstanceOf(DependentService);
expect(instance.service2.simpleService).toBeInstanceOf(SimpleService);
});
test('依赖应该是单例的', () => {
registerInjectable(container, SimpleService);
registerInjectable(container, DependentService);
const instance1 = container.resolve(DependentService);
const instance2 = container.resolve(DependentService);
const simple = container.resolve(SimpleService);
expect(instance1).toBe(instance2);
expect(instance1.simpleService).toBe(simple);
});
test('注册瞬时服务应该每次创建新实例', () => {
registerInjectable(container, SimpleService);
registerInjectable(container, DependentService, false); // 瞬时
const instance1 = container.resolve(DependentService);
const instance2 = container.resolve(DependentService);
expect(instance1).not.toBe(instance2);
expect(instance1.simpleService).toBe(instance2.simpleService); // 依赖仍然是单例
});
test('注册非Injectable类应该抛出错误', () => {
expect(() => {
registerInjectable(container, NonInjectableService as any);
}).toThrow(/not marked as @Injectable/);
});
});
describe('集成测试', () => {
test('完整的DI流程应该正常工作', () => {
// 1. 注册所有服务
registerInjectable(container, SimpleService);
registerInjectable(container, DependentService);
registerInjectable(container, MultiDependencyService);
// 2. 解析服务
const multi = container.resolve(MultiDependencyService);
// 3. 验证依赖树
expect(multi).toBeInstanceOf(MultiDependencyService);
expect(multi.service1).toBeInstanceOf(SimpleService);
expect(multi.service2).toBeInstanceOf(DependentService);
expect(multi.service2.simpleService).toBe(multi.service1); // 同一个实例
// 4. 验证服务功能
expect(multi.service1.value).toBe('simple');
});
});
});

View File

@@ -0,0 +1,396 @@
import { Core } from '../../src/Core';
import { IPlugin } from '../../src/Core/Plugin';
import { PluginManager } from '../../src/Core/PluginManager';
import type { ServiceContainer } from '../../src/Core/ServiceContainer';
describe('插件系统', () => {
let core: Core;
beforeEach(() => {
core = Core.create({ debug: false });
});
afterEach(() => {
Core.destroy();
});
describe('基本功能', () => {
it('应该能够安装插件', async () => {
class TestPlugin implements IPlugin {
readonly name = 'test-plugin';
readonly version = '1.0.0';
installed = false;
install() {
this.installed = true;
}
uninstall() {
this.installed = false;
}
}
const plugin = new TestPlugin();
await Core.installPlugin(plugin);
expect(plugin.installed).toBe(true);
expect(Core.isPluginInstalled('test-plugin')).toBe(true);
});
it('应该能够卸载插件', async () => {
class TestPlugin implements IPlugin {
readonly name = 'test-plugin';
readonly version = '1.0.0';
installed = false;
install() {
this.installed = true;
}
uninstall() {
this.installed = false;
}
}
const plugin = new TestPlugin();
await Core.installPlugin(plugin);
expect(plugin.installed).toBe(true);
await Core.uninstallPlugin('test-plugin');
expect(plugin.installed).toBe(false);
expect(Core.isPluginInstalled('test-plugin')).toBe(false);
});
it('应该能够获取已安装的插件', async () => {
class TestPlugin implements IPlugin {
readonly name = 'test-plugin';
readonly version = '1.0.0';
install() {}
uninstall() {}
}
const plugin = new TestPlugin();
await Core.installPlugin(plugin);
const installed = Core.getPlugin('test-plugin');
expect(installed).toBe(plugin);
expect(installed?.version).toBe('1.0.0');
});
it('应该拒绝重复安装同一个插件', async () => {
class TestPlugin implements IPlugin {
readonly name = 'test-plugin';
readonly version = '1.0.0';
install() {}
uninstall() {}
}
const plugin1 = new TestPlugin();
const plugin2 = new TestPlugin();
await Core.installPlugin(plugin1);
await Core.installPlugin(plugin2);
expect(Core.getPlugin('test-plugin')).toBe(plugin1);
});
});
describe('依赖管理', () => {
it('应该检查插件依赖', async () => {
class BasePlugin implements IPlugin {
readonly name = 'base-plugin';
readonly version = '1.0.0';
install() {}
uninstall() {}
}
class DependentPlugin implements IPlugin {
readonly name = 'dependent-plugin';
readonly version = '1.0.0';
readonly dependencies = ['base-plugin'] as const;
install() {}
uninstall() {}
}
const dependentPlugin = new DependentPlugin();
await expect(Core.installPlugin(dependentPlugin)).rejects.toThrow(
'unmet dependencies'
);
});
it('应该允许按正确顺序安装有依赖的插件', async () => {
class BasePlugin implements IPlugin {
readonly name = 'base-plugin';
readonly version = '1.0.0';
install() {}
uninstall() {}
}
class DependentPlugin implements IPlugin {
readonly name = 'dependent-plugin';
readonly version = '1.0.0';
readonly dependencies = ['base-plugin'] as const;
install() {}
uninstall() {}
}
const basePlugin = new BasePlugin();
const dependentPlugin = new DependentPlugin();
await Core.installPlugin(basePlugin);
await Core.installPlugin(dependentPlugin);
expect(Core.isPluginInstalled('base-plugin')).toBe(true);
expect(Core.isPluginInstalled('dependent-plugin')).toBe(true);
});
it('应该防止卸载被依赖的插件', async () => {
class BasePlugin implements IPlugin {
readonly name = 'base-plugin';
readonly version = '1.0.0';
install() {}
uninstall() {}
}
class DependentPlugin implements IPlugin {
readonly name = 'dependent-plugin';
readonly version = '1.0.0';
readonly dependencies = ['base-plugin'] as const;
install() {}
uninstall() {}
}
await Core.installPlugin(new BasePlugin());
await Core.installPlugin(new DependentPlugin());
await expect(Core.uninstallPlugin('base-plugin')).rejects.toThrow(
'required by'
);
});
});
describe('服务注册', () => {
it('应该允许插件注册服务', async () => {
class TestService {
public value = 42;
dispose() {}
}
class ServicePlugin implements IPlugin {
readonly name = 'service-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer) {
services.registerSingleton(TestService);
}
uninstall() {}
}
await Core.installPlugin(new ServicePlugin());
const service = Core.services.resolve(TestService);
expect(service.value).toBe(42);
});
it('应该允许插件访问Core和ServiceContainer', async () => {
let capturedCore: Core | null = null;
let capturedServices: ServiceContainer | null = null;
class TestPlugin implements IPlugin {
readonly name = 'test-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer) {
capturedCore = core;
capturedServices = services;
}
uninstall() {}
}
await Core.installPlugin(new TestPlugin());
expect(capturedCore).toBe(Core.Instance);
expect(capturedServices).toBe(Core.services);
});
});
describe('异步插件', () => {
it('应该支持异步安装', async () => {
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
initialized = false;
async install() {
await new Promise(resolve => setTimeout(resolve, 10));
this.initialized = true;
}
uninstall() {}
}
const plugin = new AsyncPlugin();
await Core.installPlugin(plugin);
expect(plugin.initialized).toBe(true);
});
it('应该支持异步卸载', async () => {
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
cleaned = false;
install() {}
async uninstall() {
await new Promise(resolve => setTimeout(resolve, 10));
this.cleaned = true;
}
}
const plugin = new AsyncPlugin();
await Core.installPlugin(plugin);
await Core.uninstallPlugin('async-plugin');
expect(plugin.cleaned).toBe(true);
});
});
describe('错误处理', () => {
it('应该捕获安装错误', async () => {
class FailingPlugin implements IPlugin {
readonly name = 'failing-plugin';
readonly version = '1.0.0';
install() {
throw new Error('安装失败');
}
uninstall() {}
}
await expect(Core.installPlugin(new FailingPlugin())).rejects.toThrow(
'安装失败'
);
expect(Core.isPluginInstalled('failing-plugin')).toBe(false);
});
it('应该捕获卸载错误', async () => {
class FailingPlugin implements IPlugin {
readonly name = 'failing-plugin';
readonly version = '1.0.0';
install() {}
uninstall() {
throw new Error('卸载失败');
}
}
await Core.installPlugin(new FailingPlugin());
await expect(Core.uninstallPlugin('failing-plugin')).rejects.toThrow(
'卸载失败'
);
});
});
describe('PluginManager直接使用', () => {
it('应该能够从ServiceContainer获取PluginManager', () => {
const pluginManager = Core.services.resolve(PluginManager);
expect(pluginManager).toBeDefined();
});
it('应该能够获取所有插件', async () => {
class Plugin1 implements IPlugin {
readonly name = 'plugin1';
readonly version = '1.0.0';
install() {}
uninstall() {}
}
class Plugin2 implements IPlugin {
readonly name = 'plugin2';
readonly version = '2.0.0';
install() {}
uninstall() {}
}
await Core.installPlugin(new Plugin1());
await Core.installPlugin(new Plugin2());
const pluginManager = Core.services.resolve(PluginManager);
const allPlugins = pluginManager.getAllPlugins();
expect(allPlugins).toHaveLength(2);
expect(allPlugins.map(p => p.name)).toContain('plugin1');
expect(allPlugins.map(p => p.name)).toContain('plugin2');
});
it('应该能够获取插件元数据', async () => {
class TestPlugin implements IPlugin {
readonly name = 'test-plugin';
readonly version = '1.0.0';
install() {}
uninstall() {}
}
await Core.installPlugin(new TestPlugin());
const pluginManager = Core.services.resolve(PluginManager);
const metadata = pluginManager.getMetadata('test-plugin');
expect(metadata).toBeDefined();
expect(metadata?.name).toBe('test-plugin');
expect(metadata?.version).toBe('1.0.0');
expect(metadata?.state).toBe('installed');
expect(metadata?.installedAt).toBeDefined();
});
});
describe('生命周期', () => {
it('应该在Core销毁时卸载所有插件', async () => {
const uninstallCalls: string[] = [];
class Plugin1 implements IPlugin {
readonly name = 'plugin1';
readonly version = '1.0.0';
install() {}
uninstall() {
uninstallCalls.push('plugin1');
}
}
class Plugin2 implements IPlugin {
readonly name = 'plugin2';
readonly version = '1.0.0';
readonly dependencies = ['plugin1'] as const;
install() {}
uninstall() {
uninstallCalls.push('plugin2');
}
}
await Core.installPlugin(new Plugin1());
await Core.installPlugin(new Plugin2());
Core.destroy();
expect(uninstallCalls).toContain('plugin1');
expect(uninstallCalls).toContain('plugin2');
});
});
});

View File

@@ -0,0 +1,235 @@
import { ServiceContainer, ServiceLifetime } from '../../src/Core/ServiceContainer';
// 测试服务类
class TestService {
public value: number = 0;
constructor() {
this.value = Math.random();
}
dispose() {
// 清理资源
}
}
class DependentService {
public testService?: TestService;
constructor(...args: unknown[]) {
this.testService = args[0] as TestService | undefined;
}
dispose() {
// 清理资源
}
}
class DisposableService {
public disposed = false;
dispose() {
this.disposed = true;
}
}
describe('ServiceContainer - 服务容器测试', () => {
let container: ServiceContainer;
beforeEach(() => {
container = new ServiceContainer();
});
describe('单例服务注册', () => {
test('应该能够注册单例服务', () => {
container.registerSingleton(TestService);
expect(container.isRegistered(TestService)).toBe(true);
});
test('单例服务应该返回相同实例', () => {
container.registerSingleton(TestService);
const instance1 = container.resolve(TestService);
const instance2 = container.resolve(TestService);
expect(instance1).toBe(instance2);
expect(instance1.value).toBe(instance2.value);
});
test('应该能够使用工厂函数注册单例', () => {
const fixedValue = 42;
container.registerSingleton(TestService, () => {
const service = new TestService();
service.value = fixedValue;
return service;
});
const instance = container.resolve(TestService);
expect(instance.value).toBe(fixedValue);
});
});
describe('瞬时服务注册', () => {
test('应该能够注册瞬时服务', () => {
container.registerTransient(TestService);
expect(container.isRegistered(TestService)).toBe(true);
});
test('瞬时服务应该每次返回新实例', () => {
container.registerTransient(TestService);
const instance1 = container.resolve(TestService);
const instance2 = container.resolve(TestService);
expect(instance1).not.toBe(instance2);
expect(instance1.value).not.toBe(instance2.value);
});
});
describe('实例注册', () => {
test('应该能够注册已存在的实例', () => {
const instance = new TestService();
instance.value = 99;
container.registerInstance(TestService, instance);
const resolved = container.resolve(TestService);
expect(resolved).toBe(instance);
expect(resolved.value).toBe(99);
});
});
describe('服务解析', () => {
test('解析未注册的服务应该抛出错误', () => {
expect(() => container.resolve(TestService)).toThrow(
'Service TestService is not registered'
);
});
test('tryResolve应该返回null而不是抛出错误', () => {
const result = container.tryResolve(TestService);
expect(result).toBeNull();
});
test('tryResolve应该返回已注册的服务', () => {
container.registerSingleton(TestService);
const result = container.tryResolve(TestService);
expect(result).not.toBeNull();
expect(result).toBeInstanceOf(TestService);
});
});
describe('依赖注入', () => {
test('工厂函数应该能够解析其他服务', () => {
container.registerSingleton(TestService);
container.registerSingleton(DependentService, (c) => {
const testService = c.resolve(TestService);
return new DependentService(testService);
});
const dependent = container.resolve(DependentService);
const test = container.resolve(TestService);
expect(dependent.testService).toBe(test);
});
});
describe('循环依赖检测', () => {
test('应该检测并报告循环依赖', () => {
class ServiceA {
constructor() {
// 在构造函数中尝试解析ServiceB会导致循环依赖
}
dispose() {}
}
class ServiceB {
constructor() {}
dispose() {}
}
container.registerSingleton(ServiceA, (c) => {
c.resolve(ServiceB); // 尝试解析B
return new ServiceA();
});
container.registerSingleton(ServiceB, (c) => {
c.resolve(ServiceA); // 尝试解析A
return new ServiceB();
});
expect(() => container.resolve(ServiceA)).toThrow(/Circular dependency detected/);
});
});
describe('服务注销', () => {
test('应该能够注销服务', () => {
container.registerSingleton(TestService);
expect(container.isRegistered(TestService)).toBe(true);
const result = container.unregister(TestService);
expect(result).toBe(true);
expect(container.isRegistered(TestService)).toBe(false);
});
test('注销不存在的服务应该返回false', () => {
const result = container.unregister(TestService);
expect(result).toBe(false);
});
test('注销服务应该调用dispose方法', () => {
const instance = new DisposableService();
container.registerInstance(DisposableService, instance);
container.unregister(DisposableService);
expect(instance.disposed).toBe(true);
});
});
describe('清空容器', () => {
test('应该能够清空所有服务', () => {
container.registerSingleton(TestService);
container.registerTransient(DependentService);
expect(container.getRegisteredServices().length).toBe(2);
container.clear();
expect(container.getRegisteredServices().length).toBe(0);
});
test('清空容器应该调用所有单例的dispose方法', () => {
const instance = new DisposableService();
container.registerInstance(DisposableService, instance);
container.clear();
expect(instance.disposed).toBe(true);
});
});
describe('重复注册', () => {
test('重复注册应该发出警告但不覆盖', () => {
container.registerSingleton(TestService);
const instance1 = container.resolve(TestService);
// 尝试重复注册
container.registerSingleton(TestService);
const instance2 = container.resolve(TestService);
// 应该返回相同的实例(没有被覆盖)
expect(instance1).toBe(instance2);
});
});
describe('获取已注册服务列表', () => {
test('应该返回所有已注册的服务类型', () => {
container.registerSingleton(TestService);
container.registerTransient(DependentService);
const services = container.getRegisteredServices();
expect(services).toContain(TestService);
expect(services).toContain(DependentService);
expect(services.length).toBe(2);
});
});
});

View File

@@ -1,5 +1,6 @@
import { Component } from '../../src/ECS/Component';
import { Entity } from '../../src/ECS/Entity';
import { Scene } from '../../src/ECS/Scene';
// 测试组件
class TestComponent extends Component {
@@ -23,12 +24,13 @@ class AnotherTestComponent extends Component {
describe('Component - 组件基类测试', () => {
let component: TestComponent;
let entity: Entity;
let scene: Scene;
beforeEach(() => {
// Reset component ID generator to avoid BigInt issues
Component._idGenerator = 0;
component = new TestComponent();
entity = new Entity('TestEntity', 1);
scene = new Scene();
entity = scene.createEntity('TestEntity');
});
describe('基本功能', () => {

View File

@@ -330,12 +330,12 @@ describe('FluentAPI - 流式API测试', () => {
test('应该能够批量添加系统', () => {
const system1 = new TestSystem();
const system2 = new TestSystem();
const scene = builder
.withSystems(system1, system2)
.build();
expect(scene.systems.length).toBe(2);
expect(scene.systems.length).toBe(1);
});
test('流式调用应该工作正常', () => {
@@ -568,9 +568,9 @@ describe('FluentAPI - 流式API测试', () => {
let batchOp: EntityBatchOperator;
beforeEach(() => {
entity1 = new Entity('Entity1', 1);
entity2 = new Entity('Entity2', 2);
entity3 = new Entity('Entity3', 3);
entity1 = scene.createEntity('Entity1');
entity2 = scene.createEntity('Entity2');
entity3 = scene.createEntity('Entity3');
batchOp = new EntityBatchOperator([entity1, entity2, entity3]);
});

View File

@@ -2,6 +2,7 @@ import { QuerySystem, QueryBuilder, QueryConditionType } from '../../../src/ECS/
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { ComponentRegistry, ComponentType } from '../../../src/ECS/Core/ComponentStorage';
import { Scene } from '../../../src/ECS/Scene';
// 测试组件
class PositionComponent extends Component {
@@ -75,6 +76,7 @@ class PhysicsComponent extends Component {
describe('QuerySystem - 查询系统测试', () => {
let querySystem: QuerySystem;
let entities: Entity[];
let scene: Scene;
let originalAddComponent: any;
let originalRemoveComponent: any;
let originalRemoveAllComponents: any;
@@ -82,13 +84,14 @@ describe('QuerySystem - 查询系统测试', () => {
beforeEach(() => {
querySystem = new QuerySystem();
entities = [];
scene = new Scene();
// 创建测试实体
for (let i = 0; i < 10; i++) {
const entity = new Entity(`Entity_${i}`, i + 1);
const entity = scene.createEntity(`Entity_${i}`);
entities.push(entity);
}
// 将实体添加到查询系统
querySystem.setEntities(entities);
@@ -288,16 +291,12 @@ describe('QuerySystem - 查询系统测试', () => {
// 创建大量具有相同组件组合的实体
for (let i = 0; i < entityCount; i++) {
const entity = new Entity(`PerfEntity_${i}`, i + 100);
testEntities.push(entity);
}
// 先添加组件
for (const entity of testEntities) {
const entity = scene.createEntity(`PerfEntity_${i}`);
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
testEntities.push(entity);
}
// 将实体添加到查询系统
querySystem.setEntities([...entities, ...testEntities]);
@@ -343,31 +342,27 @@ describe('QuerySystem - 查询系统测试', () => {
const entityCount = 5000;
const testEntities: Entity[] = [];
// 创建大量实体
// 创建大量实体并分配组件
for (let i = 0; i < entityCount; i++) {
const entity = new Entity(`MaskEntity_${i}`, i + 200);
testEntities.push(entity);
}
// 先随机分配组件
for (let i = 0; i < entityCount; i++) {
const entity = testEntities[i];
const entity = scene.createEntity(`MaskEntity_${i}`);
entity.addComponent(new PositionComponent(i, i));
if (i % 2 === 0) {
entity.addComponent(new VelocityComponent(1, 1));
}
if (i % 3 === 0) {
entity.addComponent(new HealthComponent(100));
}
if (i % 5 === 0) {
entity.addComponent(new RenderComponent(true));
}
testEntities.push(entity);
}
// 将实体添加到查询系统
querySystem.setEntities([...entities, ...testEntities]);
@@ -499,13 +494,13 @@ describe('QuerySystem - 查询系统测试', () => {
// 创建大量实体
for (let i = 0; i < entityCount; i++) {
const entity = new Entity(`MemEntity_${i}`, i + 300);
const entity = scene.createEntity(`MemEntity_${i}`);
entity.addComponent(new PositionComponent(i, i));
if (i % 2 === 0) {
entity.addComponent(new VelocityComponent(1, 1));
}
testEntities.push(entity);
}
@@ -785,7 +780,7 @@ describe('QuerySystem - 查询系统测试', () => {
describe('实体管理功能', () => {
test('应该能够添加和移除单个实体', () => {
const newEntity = new Entity('NewEntity', 999);
const newEntity = scene.createEntity('NewEntity');
querySystem.addEntity(newEntity);
let stats = querySystem.getStats();
@@ -798,9 +793,9 @@ describe('QuerySystem - 查询系统测试', () => {
test('应该能够批量添加实体', () => {
const newEntities = [
new Entity('Batch1', 997),
new Entity('Batch2', 998),
new Entity('Batch3', 999)
scene.createEntity('Batch1'),
scene.createEntity('Batch2'),
scene.createEntity('Batch3')
];
querySystem.addEntities(newEntities);
@@ -810,8 +805,8 @@ describe('QuerySystem - 查询系统测试', () => {
test('应该能够批量添加实体(无重复检查)', () => {
const newEntities = [
new Entity('Unchecked1', 995),
new Entity('Unchecked2', 996)
scene.createEntity('Unchecked1'),
scene.createEntity('Unchecked2')
];
querySystem.addEntitiesUnchecked(newEntities);
@@ -846,43 +841,34 @@ describe('QuerySystem - 查询系统测试', () => {
});
describe('组件变动同步问题测试', () => {
test('没有Scene时组件变动不会自动同步符合ECS架构', () => {
test('Entity必须有Scene才能添加组件', () => {
// 创建一个独立的QuerySystem和实体
const independentQuerySystem = new QuerySystem();
const testEntity = new Entity('TestEntity', 9999);
const testEntity = scene.createEntity('TestEntity');
// 确保实体没有scene
expect(testEntity.scene).toBe(null);
// Entity现在必须有scene
expect(testEntity.scene).toBeTruthy();
// 添加实体到查询系统
independentQuerySystem.addEntity(testEntity);
// 初始查询应该没有PositionComponent的实体
const result1 = independentQuerySystem.queryAll(PositionComponent);
expect(result1.entities.length).toBe(0);
// 添加组件但没有Scene不会自动同步
// 添加组件
testEntity.addComponent(new PositionComponent(100, 200));
// 查询系统不知道组件变化(这是预期行为)
const result2 = independentQuerySystem.queryAll(PositionComponent);
expect(result2.entities.length).toBe(0); // 查询系统没有自动更新
expect(testEntity.hasComponent(PositionComponent)).toBe(true); // 但实体确实有这个组件
// 手动同步后应该能找到
independentQuerySystem.updateEntity(testEntity);
const result3 = independentQuerySystem.queryAll(PositionComponent);
expect(result3.entities.length).toBe(1);
expect(result3.entities[0]).toBe(testEntity);
const result = independentQuerySystem.queryAll(PositionComponent);
expect(result.entities.length).toBe(1);
expect(result.entities[0]).toBe(testEntity);
});
test('有Scene但没有querySystem时组件变动应该安全', () => {
const testEntity = new Entity('TestEntity2', 9998);
const testEntity = scene.createEntity('TestEntity2');
// 模拟一个没有querySystem的scene
// 模拟一个没有querySystem的scene但保留componentStorageManager
const mockScene = {
querySystem: null,
componentStorageManager: null,
componentStorageManager: scene.componentStorageManager,
clearSystemEntityCaches: jest.fn()
};
testEntity.scene = mockScene as any;
@@ -897,12 +883,12 @@ describe('QuerySystem - 查询系统测试', () => {
test('有Scene时ArchetypeSystem组件变动能正确同步', () => {
const independentQuerySystem = new QuerySystem();
const testEntity = new Entity('ArchetypeTestEntity', 9997);
const testEntity = scene.createEntity('ArchetypeTestEntity');
// 模拟Scene环境
// 模拟Scene环境保留componentStorageManager
const mockScene = {
querySystem: independentQuerySystem,
componentStorageManager: null,
componentStorageManager: scene.componentStorageManager,
clearSystemEntityCaches: jest.fn()
};
testEntity.scene = mockScene as any;
@@ -941,12 +927,12 @@ describe('QuerySystem - 查询系统测试', () => {
test('有Scene时removeAllComponents应该正确同步QuerySystem', () => {
const independentQuerySystem = new QuerySystem();
const testEntity = new Entity('RemoveAllTestEntity', 9996);
const testEntity = scene.createEntity('RemoveAllTestEntity');
// 模拟Scene环境
// 模拟Scene环境保留componentStorageManager
const mockScene = {
querySystem: independentQuerySystem,
componentStorageManager: null,
componentStorageManager: scene.componentStorageManager,
clearSystemEntityCaches: jest.fn()
};
testEntity.scene = mockScene as any;
@@ -978,7 +964,7 @@ describe('QuerySystem - 查询系统测试', () => {
test('手动同步updateEntity应该工作正常', () => {
const independentQuerySystem = new QuerySystem();
const testEntity = new Entity('ManualSyncTestEntity', 9995);
const testEntity = scene.createEntity('ManualSyncTestEntity');
independentQuerySystem.addEntity(testEntity);
@@ -1002,9 +988,9 @@ describe('QuerySystem - 查询系统测试', () => {
const querySystem = new QuerySystem();
// 创建带组件的实体
const entity1 = new Entity('BatchEntity1', 8001);
const entity2 = new Entity('BatchEntity2', 8002);
const entity3 = new Entity('BatchEntity3', 8003);
const entity1 = scene.createEntity('BatchEntity1');
const entity2 = scene.createEntity('BatchEntity2');
const entity3 = scene.createEntity('BatchEntity3');
entity1.addComponent(new PositionComponent(10, 20));
entity2.addComponent(new PositionComponent(30, 40));
@@ -1053,9 +1039,9 @@ describe('QuerySystem - 查询系统测试', () => {
describe('组件掩码字符串索引', () => {
test('应该为不同的组件组合生成不同的掩码字符串', () => {
// 创建具有不同组件组合的实体
const entity1 = new Entity('Entity1', 101);
const entity2 = new Entity('Entity2', 102);
const entity3 = new Entity('Entity3', 103);
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const entity3 = scene.createEntity('Entity3');
entity1.addComponent(new PositionComponent(1, 1));
entity2.addComponent(new VelocityComponent(2, 2));
@@ -1071,22 +1057,22 @@ describe('QuerySystem - 查询系统测试', () => {
const withBoth = querySystem.queryAll(PositionComponent, VelocityComponent);
// entity1 应该在 withPosition 中
expect(withPosition.entities.some(e => e.id === 101)).toBe(true);
expect(withPosition.entities).toContain(entity1);
// entity2 应该在 withVelocity 中
expect(withVelocity.entities.some(e => e.id === 102)).toBe(true);
expect(withVelocity.entities).toContain(entity2);
// entity3 应该在所有查询中(因为它包含 Position 和 Velocity
expect(withPosition.entities.some(e => e.id === 103)).toBe(true);
expect(withVelocity.entities.some(e => e.id === 103)).toBe(true);
expect(withBoth.entities.some(e => e.id === 103)).toBe(true);
expect(withPosition.entities).toContain(entity3);
expect(withVelocity.entities).toContain(entity3);
expect(withBoth.entities).toContain(entity3);
// withBoth 只应该包含同时有两个组件的实体
expect(withBoth.entities.some(e => e.id === 101)).toBe(false); // 只有 Position
expect(withBoth.entities.some(e => e.id === 102)).toBe(false); // 只有 Velocity
expect(withBoth.entities).not.toContain(entity1); // 只有 Position
expect(withBoth.entities).not.toContain(entity2); // 只有 Velocity
});
test('相同组件组合的实体应该使用相同的掩码索引', () => {
const entity1 = new Entity('Entity1', 201);
const entity2 = new Entity('Entity2', 202);
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
// 两个实体都有相同的组件组合
entity1.addComponent(new PositionComponent(1, 1));
@@ -1101,8 +1087,8 @@ describe('QuerySystem - 查询系统测试', () => {
// 查询应该同时返回这两个实体
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
expect(result.entities.some(e => e.id === 201)).toBe(true);
expect(result.entities.some(e => e.id === 202)).toBe(true);
expect(result.entities).toContain(entity1);
expect(result.entities).toContain(entity2);
});
});
});

View File

@@ -0,0 +1,254 @@
import { describe, test, expect, beforeEach } from '@jest/globals';
import { ReferenceTracker } from '../../../src/ECS/Core/ReferenceTracker';
import { Component } from '../../../src/ECS/Component';
import { Entity } from '../../../src/ECS/Entity';
import { Scene } from '../../../src/ECS/Scene';
class TestComponent extends Component {
public target: Entity | null = null;
}
describe('ReferenceTracker', () => {
let tracker: ReferenceTracker;
let scene: Scene;
let entity1: Entity;
let entity2: Entity;
let component: TestComponent;
beforeEach(() => {
tracker = new ReferenceTracker();
scene = new Scene();
entity1 = scene.createEntity('Entity1');
entity2 = scene.createEntity('Entity2');
component = new TestComponent();
entity1.addComponent(component);
});
describe('registerReference', () => {
test('应该成功注册Entity引用', () => {
tracker.registerReference(entity2, component, 'target');
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(1);
expect(refs[0].component.deref()).toBe(component);
expect(refs[0].propertyKey).toBe('target');
});
test('应该避免重复注册相同引用', () => {
tracker.registerReference(entity2, component, 'target');
tracker.registerReference(entity2, component, 'target');
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(1);
});
test('应该支持多个Component引用同一Entity', () => {
const component2 = new TestComponent();
const entity3 = scene.createEntity('Entity3');
entity3.addComponent(component2);
tracker.registerReference(entity2, component, 'target');
tracker.registerReference(entity2, component2, 'target');
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(2);
});
test('应该支持同一Component引用多个属性', () => {
tracker.registerReference(entity2, component, 'target');
tracker.registerReference(entity2, component, 'parent');
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(2);
});
});
describe('unregisterReference', () => {
test('应该成功注销Entity引用', () => {
tracker.registerReference(entity2, component, 'target');
tracker.unregisterReference(entity2, component, 'target');
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(0);
});
test('注销不存在的引用不应报错', () => {
expect(() => {
tracker.unregisterReference(entity2, component, 'target');
}).not.toThrow();
});
test('应该只注销指定的引用', () => {
const component2 = new TestComponent();
const entity3 = scene.createEntity('Entity3');
entity3.addComponent(component2);
tracker.registerReference(entity2, component, 'target');
tracker.registerReference(entity2, component2, 'target');
tracker.unregisterReference(entity2, component, 'target');
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(1);
expect(refs[0].component.deref()).toBe(component2);
});
});
describe('clearReferencesTo', () => {
test('应该将所有引用设为null', () => {
component.target = entity2;
tracker.registerReference(entity2, component, 'target');
tracker.clearReferencesTo(entity2.id);
expect(component.target).toBeNull();
});
test('应该清理多个Component的引用', () => {
const component2 = new TestComponent();
const entity3 = scene.createEntity('Entity3');
entity3.addComponent(component2);
component.target = entity2;
component2.target = entity2;
tracker.registerReference(entity2, component, 'target');
tracker.registerReference(entity2, component2, 'target');
tracker.clearReferencesTo(entity2.id);
expect(component.target).toBeNull();
expect(component2.target).toBeNull();
});
test('清理不存在的Entity引用不应报错', () => {
expect(() => {
tracker.clearReferencesTo(999);
}).not.toThrow();
});
test('应该移除引用记录', () => {
tracker.registerReference(entity2, component, 'target');
tracker.clearReferencesTo(entity2.id);
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(0);
});
});
describe('clearComponentReferences', () => {
test('应该清理Component的所有引用注册', () => {
tracker.registerReference(entity2, component, 'target');
tracker.clearComponentReferences(component);
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(0);
});
test('应该只清理指定Component的引用', () => {
const component2 = new TestComponent();
const entity3 = scene.createEntity('Entity3');
entity3.addComponent(component2);
tracker.registerReference(entity2, component, 'target');
tracker.registerReference(entity2, component2, 'target');
tracker.clearComponentReferences(component);
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(1);
expect(refs[0].component.deref()).toBe(component2);
});
});
describe('getReferencesTo', () => {
test('应该返回空数组当Entity没有引用时', () => {
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toEqual([]);
});
test('应该只返回有效的引用记录', () => {
tracker.registerReference(entity2, component, 'target');
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(1);
});
});
describe('cleanup', () => {
test('应该清理失效的WeakRef引用', () => {
let tempComponent: TestComponent | null = new TestComponent();
const entity3 = scene.createEntity('Entity3');
entity3.addComponent(tempComponent);
tracker.registerReference(entity2, tempComponent, 'target');
expect(tracker.getReferencesTo(entity2.id)).toHaveLength(1);
tempComponent = null;
if (global.gc) {
global.gc();
}
tracker.cleanup();
const refs = tracker.getReferencesTo(entity2.id);
expect(refs.length).toBeLessThanOrEqual(1);
});
});
describe('getDebugInfo', () => {
test('应该返回调试信息', () => {
tracker.registerReference(entity2, component, 'target');
const debugInfo = tracker.getDebugInfo();
expect(debugInfo).toHaveProperty(`entity_${entity2.id}`);
const entityRefs = (debugInfo as any)[`entity_${entity2.id}`];
expect(entityRefs).toHaveLength(1);
expect(entityRefs[0]).toMatchObject({
componentId: component.id,
propertyKey: 'target'
});
});
test('应该只包含有效的引用', () => {
tracker.registerReference(entity2, component, 'target');
const debugInfo = tracker.getDebugInfo();
expect(Object.keys(debugInfo)).toHaveLength(1);
});
});
describe('边界情况', () => {
test('应该处理Component被GC回收的情况', () => {
tracker.registerReference(entity2, component, 'target');
tracker.cleanup();
const refs = tracker.getReferencesTo(entity2.id);
expect(refs.length).toBeGreaterThanOrEqual(0);
});
test('应该支持大量引用', () => {
const components: TestComponent[] = [];
for (let i = 0; i < 1000; i++) {
const comp = new TestComponent();
const ent = scene.createEntity(`Entity${i}`);
ent.addComponent(comp);
components.push(comp);
tracker.registerReference(entity2, comp, 'target');
}
const refs = tracker.getReferencesTo(entity2.id);
expect(refs).toHaveLength(1000);
tracker.clearReferencesTo(entity2.id);
const refsAfter = tracker.getReferencesTo(entity2.id);
expect(refsAfter).toHaveLength(0);
});
});
});

View File

@@ -1,5 +1,6 @@
import { Entity } from '../../src/ECS/Entity';
import { Component } from '../../src/ECS/Component';
import { Scene } from '../../src/ECS/Scene';
// 测试组件类
class TestPositionComponent extends Component {
@@ -48,17 +49,19 @@ class TestRenderComponent extends Component {
describe('Entity - 组件缓存优化测试', () => {
let entity: Entity;
let scene: Scene;
beforeEach(() => {
// 创建新的实体
entity = new Entity('TestEntity', 1);
scene = new Scene();
entity = scene.createEntity('TestEntity');
});
describe('基本功能测试', () => {
test('应该能够创建实体', () => {
expect(entity.name).toBe('TestEntity');
expect(entity.id).toBe(1);
expect(entity.id).toBeGreaterThanOrEqual(0);
expect(entity.components.length).toBe(0);
expect(entity.scene).toBe(scene);
});
test('应该能够添加组件', () => {
@@ -262,7 +265,7 @@ describe('Entity - 组件缓存优化测试', () => {
const debugInfo = entity.getDebugInfo();
expect(debugInfo.name).toBe('TestEntity');
expect(debugInfo.id).toBe(1);
expect(debugInfo.id).toBeGreaterThanOrEqual(0);
expect(debugInfo.componentCount).toBe(2);
expect(debugInfo.componentTypes).toContain('TestPositionComponent');
expect(debugInfo.componentTypes).toContain('TestHealthComponent');

View File

@@ -0,0 +1,274 @@
import { describe, test, expect, beforeEach } from '@jest/globals';
import { Scene } from '../../src/ECS/Scene';
import { Component } from '../../src/ECS/Component';
import { Entity } from '../../src/ECS/Entity';
import { EntityRef, ECSComponent } from '../../src/ECS/Decorators';
@ECSComponent('ParentRef')
class ParentComponent extends Component {
@EntityRef() parent: Entity | null = null;
}
@ECSComponent('TargetRef')
class TargetComponent extends Component {
@EntityRef() target: Entity | null = null;
@EntityRef() ally: Entity | null = null;
}
describe('EntityRef Integration Tests', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene({ name: 'TestScene' });
});
describe('基础功能', () => {
test('应该支持EntityRef装饰器', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
comp.parent = entity2;
expect(comp.parent).toBe(entity2);
});
test('Entity销毁时应该自动清理所有引用', () => {
const parent = scene.createEntity('Parent');
const child1 = scene.createEntity('Child1');
const child2 = scene.createEntity('Child2');
const comp1 = child1.addComponent(new ParentComponent());
const comp2 = child2.addComponent(new ParentComponent());
comp1.parent = parent;
comp2.parent = parent;
expect(comp1.parent).toBe(parent);
expect(comp2.parent).toBe(parent);
parent.destroy();
expect(comp1.parent).toBeNull();
expect(comp2.parent).toBeNull();
});
test('修改引用应该更新ReferenceTracker', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const entity3 = scene.createEntity('Entity3');
const comp = entity1.addComponent(new ParentComponent());
comp.parent = entity2;
expect(scene.referenceTracker.getReferencesTo(entity2.id)).toHaveLength(1);
comp.parent = entity3;
expect(scene.referenceTracker.getReferencesTo(entity2.id)).toHaveLength(0);
expect(scene.referenceTracker.getReferencesTo(entity3.id)).toHaveLength(1);
});
test('设置为null应该注销引用', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
comp.parent = entity2;
expect(scene.referenceTracker.getReferencesTo(entity2.id)).toHaveLength(1);
comp.parent = null;
expect(scene.referenceTracker.getReferencesTo(entity2.id)).toHaveLength(0);
});
});
describe('Component生命周期', () => {
test('移除Component应该清理其所有引用注册', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
comp.parent = entity2;
expect(scene.referenceTracker.getReferencesTo(entity2.id)).toHaveLength(1);
entity1.removeComponent(comp);
expect(scene.referenceTracker.getReferencesTo(entity2.id)).toHaveLength(0);
});
test('移除Component应该清除entityId引用', () => {
const entity1 = scene.createEntity('Entity1');
const comp = entity1.addComponent(new ParentComponent());
expect(comp.entityId).toBe(entity1.id);
entity1.removeComponent(comp);
expect(comp.entityId).toBeNull();
});
});
describe('多属性引用', () => {
test('应该支持同一Component的多个EntityRef属性', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const entity3 = scene.createEntity('Entity3');
const comp = entity1.addComponent(new TargetComponent());
comp.target = entity2;
comp.ally = entity3;
expect(comp.target).toBe(entity2);
expect(comp.ally).toBe(entity3);
entity2.destroy();
expect(comp.target).toBeNull();
expect(comp.ally).toBe(entity3);
entity3.destroy();
expect(comp.ally).toBeNull();
});
});
describe('边界情况', () => {
test('跨Scene引用应该失败', () => {
const scene2 = new Scene({ name: 'TestScene2' });
const entity1 = scene.createEntity('Entity1');
const entity2 = scene2.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
comp.parent = entity2;
expect(comp.parent).toBeNull();
});
test('引用已销毁的Entity应该失败', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
entity2.destroy();
comp.parent = entity2;
expect(comp.parent).toBeNull();
});
test('重复设置相同值不应重复注册', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
comp.parent = entity2;
comp.parent = entity2;
comp.parent = entity2;
expect(scene.referenceTracker.getReferencesTo(entity2.id)).toHaveLength(1);
});
test('循环引用应该正常工作', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const comp1 = entity1.addComponent(new ParentComponent());
const comp2 = entity2.addComponent(new ParentComponent());
comp1.parent = entity2;
comp2.parent = entity1;
expect(comp1.parent).toBe(entity2);
expect(comp2.parent).toBe(entity1);
entity1.destroy();
expect(comp2.parent).toBeNull();
expect(entity2.isDestroyed).toBe(false);
});
});
describe('复杂场景', () => {
test('父子实体销毁应该正确清理引用', () => {
const parent = scene.createEntity('Parent');
const child1 = scene.createEntity('Child1');
const child2 = scene.createEntity('Child2');
const observer = scene.createEntity('Observer');
parent.addChild(child1);
parent.addChild(child2);
const observerComp = observer.addComponent(new TargetComponent());
observerComp.target = parent;
observerComp.ally = child1;
expect(observerComp.target).toBe(parent);
expect(observerComp.ally).toBe(child1);
parent.destroy();
expect(observerComp.target).toBeNull();
expect(observerComp.ally).toBeNull();
expect(child1.isDestroyed).toBe(true);
expect(child2.isDestroyed).toBe(true);
});
test('大量引用场景', () => {
const target = scene.createEntity('Target');
const entities: Entity[] = [];
const components: ParentComponent[] = [];
for (let i = 0; i < 100; i++) {
const entity = scene.createEntity(`Entity${i}`);
const comp = entity.addComponent(new ParentComponent());
comp.parent = target;
entities.push(entity);
components.push(comp);
}
expect(scene.referenceTracker.getReferencesTo(target.id)).toHaveLength(100);
target.destroy();
for (const comp of components) {
expect(comp.parent).toBeNull();
}
expect(scene.referenceTracker.getReferencesTo(target.id)).toHaveLength(0);
});
test('批量销毁后引用应全部清理', () => {
const entities: Entity[] = [];
const components: TargetComponent[] = [];
for (let i = 0; i < 50; i++) {
entities.push(scene.createEntity(`Entity${i}`));
}
for (let i = 0; i < 50; i++) {
const comp = entities[i].addComponent(new TargetComponent());
if (i > 0) {
comp.target = entities[i - 1];
}
if (i < 49) {
comp.ally = entities[i + 1];
}
components.push(comp);
}
scene.destroyAllEntities();
for (const comp of components) {
expect(comp.target).toBeNull();
expect(comp.ally).toBeNull();
}
});
});
describe('调试功能', () => {
test('getDebugInfo应该返回引用信息', () => {
const entity1 = scene.createEntity('Entity1');
const entity2 = scene.createEntity('Entity2');
const comp = entity1.addComponent(new ParentComponent());
comp.parent = entity2;
const debugInfo = scene.referenceTracker.getDebugInfo();
expect(debugInfo).toHaveProperty(`entity_${entity2.id}`);
});
});
});

View File

@@ -0,0 +1,592 @@
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';
import { Injectable, Inject, InjectProperty } from '../../src/Core/DI';
import { Core } from '../../src/Core';
import type { IService } from '../../src/Core/ServiceContainer';
import { ECSSystem } from '../../src/ECS/Decorators';
class Transform extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class Velocity extends Component {
constructor(public vx: number = 0, public vy: number = 0) {
super();
}
}
class Health extends Component {
constructor(public value: number = 100) {
super();
}
}
describe('EntitySystem - 依赖注入测试', () => {
let scene: Scene;
beforeAll(() => {
Core.create();
});
beforeEach(() => {
scene = new Scene();
});
afterEach(() => {
scene.end();
});
afterAll(() => {
Core.destroy();
});
describe('基本DI功能', () => {
test('应该支持无依赖的System通过类型添加', () => {
@Injectable()
@ECSSystem('Movement')
class MovementSystem extends EntitySystem implements IService {
constructor() {
super(Matcher.empty().all(Transform, Velocity));
}
override dispose() {}
}
const system = scene.addEntityProcessor(MovementSystem);
expect(system).toBeInstanceOf(MovementSystem);
expect(scene.systems.length).toBe(1);
});
test('应该支持有依赖的System自动注入', () => {
@Injectable()
@ECSSystem('Collision')
class CollisionSystem extends EntitySystem implements IService {
public checkCount = 0;
constructor() {
super(Matcher.empty().all(Transform));
}
public checkCollisions() {
this.checkCount++;
}
override dispose() {}
}
@Injectable()
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem implements IService {
constructor(
@Inject(CollisionSystem) public collision: CollisionSystem
) {
super(Matcher.empty().all(Transform, Velocity));
}
protected override process(entities: readonly Entity[]): void {
this.collision.checkCollisions();
}
override dispose() {}
}
scene.addEntityProcessor(CollisionSystem);
const physics = scene.addEntityProcessor(PhysicsSystem);
expect(physics).toBeInstanceOf(PhysicsSystem);
expect(physics.collision).toBeInstanceOf(CollisionSystem);
expect(scene.systems.length).toBe(2);
const entity = scene.createEntity('test');
entity.addComponent(new Transform());
entity.addComponent(new Velocity());
scene.update();
expect(physics.collision.checkCount).toBe(1);
});
test('应该支持多层级依赖注入', () => {
@Injectable()
@ECSSystem('A')
class SystemA extends EntitySystem implements IService {
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('B')
class SystemB extends EntitySystem implements IService {
constructor(@Inject(SystemA) public systemA: SystemA) {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('C')
class SystemC extends EntitySystem implements IService {
constructor(
@Inject(SystemA) public systemA: SystemA,
@Inject(SystemB) public systemB: SystemB
) {
super(Matcher.empty());
}
override dispose() {}
}
scene.addEntityProcessor(SystemA);
scene.addEntityProcessor(SystemB);
const systemC = scene.addEntityProcessor(SystemC);
expect(systemC.systemA).toBeInstanceOf(SystemA);
expect(systemC.systemB).toBeInstanceOf(SystemB);
expect(systemC.systemB.systemA).toBe(systemC.systemA);
});
});
describe('批量注册', () => {
test('应该支持批量注册System并自动解析依赖', () => {
@Injectable()
@ECSSystem('Collision')
class CollisionSystem extends EntitySystem implements IService {
constructor() {
super(Matcher.empty().all(Transform));
}
override dispose() {}
}
@Injectable()
@ECSSystem('Physics', { updateOrder: 10 })
class PhysicsSystem extends EntitySystem implements IService {
constructor(@Inject(CollisionSystem) public collision: CollisionSystem) {
super(Matcher.empty().all(Transform, Velocity));
}
override dispose() {}
}
@Injectable()
@ECSSystem('Render', { updateOrder: 20 })
class RenderSystem extends EntitySystem implements IService {
constructor(@Inject(PhysicsSystem) public physics: PhysicsSystem) {
super(Matcher.empty().all(Transform));
}
override dispose() {}
}
const systems = scene.registerSystems([
CollisionSystem,
PhysicsSystem,
RenderSystem
]);
expect(systems.length).toBe(3);
expect(scene.systems.length).toBe(3);
const [collision, physics, render] = systems;
expect(collision).toBeInstanceOf(CollisionSystem);
expect(physics).toBeInstanceOf(PhysicsSystem);
expect(render).toBeInstanceOf(RenderSystem);
expect((physics as any).collision).toBe(collision);
expect((render as any).physics).toBe(physics);
});
test('批量注册的System应该按updateOrder排序', () => {
@Injectable()
@ECSSystem('C', { updateOrder: 30 })
class SystemC extends EntitySystem implements IService {
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('A', { updateOrder: 10 })
class SystemA extends EntitySystem implements IService {
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('B', { updateOrder: 20 })
class SystemB extends EntitySystem implements IService {
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
scene.registerSystems([SystemC, SystemA, SystemB]);
const systems = scene.systems;
expect(systems[0]).toBeInstanceOf(SystemA);
expect(systems[1]).toBeInstanceOf(SystemB);
expect(systems[2]).toBeInstanceOf(SystemC);
});
});
describe('场景隔离', () => {
test('不同Scene的System实例应该相互独立', () => {
@Injectable()
@ECSSystem('Counter')
class CounterSystem extends EntitySystem implements IService {
public count = 0;
constructor() {
super(Matcher.empty());
}
protected override process(): void {
this.count++;
}
override dispose() {}
}
const scene1 = new Scene();
const scene2 = new Scene();
const counter1 = scene1.addEntityProcessor(CounterSystem);
const counter2 = scene2.addEntityProcessor(CounterSystem);
expect(counter1).not.toBe(counter2);
scene1.update();
expect(counter1.count).toBe(1);
expect(counter2.count).toBe(0);
scene2.update();
expect(counter1.count).toBe(1);
expect(counter2.count).toBe(1);
scene1.end();
scene2.end();
});
});
describe('getSystem方法', () => {
test('应该能通过getSystem获取已注册的System', () => {
@Injectable()
@ECSSystem('Test')
class TestSystem extends EntitySystem implements IService {
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
scene.addEntityProcessor(TestSystem);
const system = scene.getSystem(TestSystem);
expect(system).toBeInstanceOf(TestSystem);
});
test('获取未注册的System应该返回null', () => {
@Injectable()
@ECSSystem('Test')
class TestSystem extends EntitySystem implements IService {
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
const system = scene.getSystem(TestSystem);
expect(system).toBeNull();
});
});
describe('向后兼容性', () => {
test('应该继续支持手动创建实例的方式', () => {
class LegacySystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(Transform));
}
}
const system = new LegacySystem();
scene.addEntityProcessor(system);
expect(scene.systems.length).toBe(1);
expect(scene.systems[0]).toBe(system);
});
test('混合使用DI和手动创建应该正常工作', () => {
@Injectable()
@ECSSystem('DI')
class DISystem extends EntitySystem implements IService {
constructor() {
super(Matcher.empty().all(Transform));
}
override dispose() {}
}
class ManualSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(Velocity));
}
}
scene.addEntityProcessor(DISystem);
scene.addEntityProcessor(new ManualSystem());
expect(scene.systems.length).toBe(2);
});
});
describe('Issue #76 场景验证', () => {
test('应该消除硬编码依赖,使用构造函数注入', () => {
@Injectable()
@ECSSystem('TimeService')
class TimeService extends EntitySystem implements IService {
public getDeltaTime(): number {
return 0.016;
}
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('CollisionService')
class CollisionService extends EntitySystem implements IService {
public detectCollisions(): string[] {
return ['collision1', 'collision2'];
}
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem implements IService {
constructor(
@Inject(TimeService) private time: TimeService,
@Inject(CollisionService) private collision: CollisionService
) {
super(Matcher.empty().all(Transform, Velocity));
}
protected override process(entities: readonly Entity[]): void {
const dt = this.time.getDeltaTime();
const collisions = this.collision.detectCollisions();
for (const entity of entities) {
const transform = entity.getComponent(Transform)!;
const velocity = entity.getComponent(Velocity)!;
transform.x += velocity.vx * dt;
transform.y += velocity.vy * dt;
}
}
override dispose() {}
}
scene.registerSystems([
TimeService,
CollisionService,
PhysicsSystem
]);
const entity = scene.createEntity('player');
entity.addComponent(new Transform(0, 0));
entity.addComponent(new Velocity(100, 50));
const physics = scene.getSystem(PhysicsSystem);
expect(physics).not.toBeNull();
expect((physics as any).time).toBeInstanceOf(TimeService);
expect((physics as any).collision).toBeInstanceOf(CollisionService);
scene.update();
const transform = entity.getComponent(Transform)!;
expect(transform.x).toBeCloseTo(1.6, 1);
expect(transform.y).toBeCloseTo(0.8, 1);
});
});
describe('属性注入 @InjectProperty', () => {
test('应该支持单个属性注入', () => {
@Injectable()
@ECSSystem('Config')
class GameConfig extends EntitySystem implements IService {
public bulletDamage = 10;
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('Combat')
class CombatSystem extends EntitySystem implements IService {
@InjectProperty(GameConfig)
gameConfig!: GameConfig;
constructor() {
super(Matcher.empty().all(Health));
}
protected override onInitialize(): void {
expect(this.gameConfig).toBeInstanceOf(GameConfig);
expect(this.gameConfig.bulletDamage).toBe(10);
}
override dispose() {}
}
scene.addEntityProcessor(GameConfig);
scene.addEntityProcessor(CombatSystem);
});
test('应该支持多个属性注入', () => {
@Injectable()
@ECSSystem('Time')
class TimeService extends EntitySystem implements IService {
public deltaTime = 0.016;
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('Collision')
class CollisionSystem extends EntitySystem implements IService {
public checkCount = 0;
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('Physics')
class PhysicsSystem extends EntitySystem implements IService {
@InjectProperty(TimeService)
time!: TimeService;
@InjectProperty(CollisionSystem)
collision!: CollisionSystem;
constructor() {
super(Matcher.empty());
}
protected override onInitialize(): void {
expect(this.time).toBeInstanceOf(TimeService);
expect(this.collision).toBeInstanceOf(CollisionSystem);
expect(this.time.deltaTime).toBe(0.016);
}
override dispose() {}
}
scene.registerSystems([TimeService, CollisionSystem, PhysicsSystem]);
});
test('属性注入应该在onInitialize之前完成', () => {
@Injectable()
@ECSSystem('Service')
class TestService extends EntitySystem implements IService {
public value = 42;
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('Consumer')
class ConsumerSystem extends EntitySystem implements IService {
@InjectProperty(TestService)
service!: TestService;
private initializeValue = 0;
constructor() {
super(Matcher.empty());
}
protected override onInitialize(): void {
this.initializeValue = this.service.value;
}
public getInitializeValue(): number {
return this.initializeValue;
}
override dispose() {}
}
scene.addEntityProcessor(TestService);
const consumer = scene.addEntityProcessor(ConsumerSystem);
expect(consumer.getInitializeValue()).toBe(42);
});
test('属性注入可以与构造函数注入混合使用', () => {
@Injectable()
@ECSSystem('A')
class ServiceA extends EntitySystem implements IService {
public valueA = 'A';
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('B')
class ServiceB extends EntitySystem implements IService {
public valueB = 'B';
constructor() {
super(Matcher.empty());
}
override dispose() {}
}
@Injectable()
@ECSSystem('Mixed')
class MixedSystem extends EntitySystem implements IService {
@InjectProperty(ServiceB)
serviceB!: ServiceB;
constructor(@Inject(ServiceA) public serviceA: ServiceA) {
super(Matcher.empty());
}
protected override onInitialize(): void {
expect(this.serviceA).toBeInstanceOf(ServiceA);
expect(this.serviceB).toBeInstanceOf(ServiceB);
expect(this.serviceA.valueA).toBe('A');
expect(this.serviceB.valueB).toBe('B');
}
override dispose() {}
}
scene.registerSystems([ServiceA, ServiceB, MixedSystem]);
});
});
});

View File

@@ -129,7 +129,7 @@ describe('Scene - 场景管理系统测试', () => {
expect(scene).toBeInstanceOf(Scene);
expect(scene.name).toBe("");
expect(scene.entities).toBeDefined();
expect(scene.entityProcessors).toBeDefined();
expect(scene.systems).toBeDefined();
expect(scene.identifierPool).toBeDefined();
});
@@ -140,7 +140,7 @@ describe('Scene - 场景管理系统测试', () => {
test('场景应该有正确的初始状态', () => {
expect(scene.entities.count).toBe(0);
expect(scene.entityProcessors.count).toBe(0);
expect(scene.systems.length).toBe(0);
});
test('应该能够使用配置创建场景', () => {
@@ -248,16 +248,16 @@ describe('Scene - 场景管理系统测试', () => {
test('应该能够添加实体系统', () => {
scene.addEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(1);
expect(scene.systems.length).toBe(1);
expect(movementSystem.scene).toBe(scene);
});
test('应该能够移除实体系统', () => {
scene.addEntityProcessor(movementSystem);
scene.removeEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(0);
expect(scene.systems.length).toBe(0);
expect(movementSystem.scene).toBeNull();
});
@@ -270,8 +270,8 @@ describe('Scene - 场景管理系统测试', () => {
test('应该能够管理多个实体系统', () => {
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(renderSystem);
expect(scene.entityProcessors.count).toBe(2);
expect(scene.systems.length).toBe(2);
});
test('系统应该按更新顺序执行', () => {
@@ -537,11 +537,12 @@ describe('Scene - 场景管理系统测试', () => {
describe('错误处理和边界情况', () => {
test('重复添加同一个系统应该安全处理', () => {
const system = new MovementSystem();
scene.addEntityProcessor(system);
scene.addEntityProcessor(system); // 重复添加
expect(scene.entityProcessors.count).toBe(1);
scene.addEntityProcessor(system);
expect(scene.systems.length).toBe(1);
});
test('系统处理过程中的异常应该被正确处理', () => {
@@ -580,11 +581,64 @@ describe('Scene - 场景管理系统测试', () => {
test('对已销毁实体的操作应该安全处理', () => {
const entity = scene.createEntity("TestEntity");
scene.entities.remove(entity);
// 对已销毁实体的操作应该安全
expect(() => {
entity.addComponent(new PositionComponent(0, 0));
}).not.toThrow();
});
});
describe('依赖注入优化', () => {
test('应该支持注入自定义PerformanceMonitor', () => {
const mockPerfMonitor = {
startMeasure: jest.fn(),
endMeasure: jest.fn(),
recordSystemData: jest.fn(),
recordEntityCount: jest.fn(),
recordComponentCount: jest.fn(),
update: jest.fn(),
getSystemData: jest.fn(),
getSystemStats: jest.fn(),
resetSystem: jest.fn(),
reset: jest.fn(),
dispose: jest.fn()
};
const customScene = new Scene({
name: 'CustomScene',
performanceMonitor: mockPerfMonitor as any
});
class TestSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PositionComponent));
}
}
const system = new TestSystem();
customScene.addEntityProcessor(system);
expect(mockPerfMonitor).toBeDefined();
customScene.end();
});
test('未提供PerformanceMonitor时应该从Core获取', () => {
const defaultScene = new Scene({ name: 'DefaultScene' });
class TestSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PositionComponent));
}
}
const system = new TestSystem();
defaultScene.addEntityProcessor(system);
expect(defaultScene).toBeDefined();
defaultScene.end();
});
});
});

View File

@@ -464,7 +464,7 @@ describe('Incremental Serialization System', () => {
const incremental = scene.serializeIncremental();
const binary = IncrementalSerializer.serializeIncremental(incremental, { format: 'binary' });
expect(Buffer.isBuffer(binary)).toBe(true);
expect(binary instanceof Uint8Array).toBe(true);
const deserialized = IncrementalSerializer.deserializeIncremental(binary);
expect(deserialized.version).toBe(incremental.version);

View File

@@ -82,6 +82,8 @@ class NonSerializableComponent extends Component {
}
describe('ECS Serialization System', () => {
let scene: Scene;
beforeEach(() => {
// 清空测试环境
ComponentRegistry.reset();
@@ -91,6 +93,9 @@ describe('ECS Serialization System', () => {
ComponentRegistry.register(VelocityComponent);
ComponentRegistry.register(PlayerComponent);
ComponentRegistry.register(HealthComponent);
// 创建测试场景
scene = new Scene();
});
describe('Component Serialization', () => {
@@ -185,22 +190,22 @@ describe('ECS Serialization System', () => {
describe('Entity Serialization', () => {
it('should serialize an entity with components', () => {
const entity = new Entity('Player', 1);
const entity = scene.createEntity('Player');
entity.addComponent(new PositionComponent(50, 100));
entity.addComponent(new VelocityComponent());
entity.tag = 10;
const serialized = EntitySerializer.serialize(entity);
expect(serialized.id).toBe(1);
expect(serialized.id).toBe(entity.id);
expect(serialized.name).toBe('Player');
expect(serialized.tag).toBe(10);
expect(serialized.components.length).toBe(2);
});
it('should serialize entity hierarchy', () => {
const parent = new Entity('Parent', 1);
const child = new Entity('Child', 2);
const parent = scene.createEntity('Parent');
const child = scene.createEntity('Child');
parent.addComponent(new PositionComponent(0, 0));
child.addComponent(new PositionComponent(10, 10));
@@ -209,7 +214,7 @@ describe('ECS Serialization System', () => {
const serialized = EntitySerializer.serialize(parent);
expect(serialized.children.length).toBe(1);
expect(serialized.children[0].id).toBe(2);
expect(serialized.children[0].id).toBe(child.id);
expect(serialized.children[0].name).toBe('Child');
});
@@ -237,7 +242,8 @@ describe('ECS Serialization System', () => {
serializedEntity,
registry,
() => idCounter++,
false
false,
scene
);
expect(entity.name).toBe('TestEntity');
@@ -551,14 +557,14 @@ describe('ECS Serialization System', () => {
// 二进制序列化
const binaryData = scene1.serialize({ format: 'binary' });
// 验证是Buffer类型
expect(Buffer.isBuffer(binaryData)).toBe(true);
// 验证是Uint8Array类型
expect(binaryData instanceof Uint8Array).toBe(true);
// JSON序列化对比
const jsonData = scene1.serialize({ format: 'json', pretty: false });
// 二进制应该更小
const binarySize = (binaryData as Buffer).length;
const binarySize = (binaryData as Uint8Array).length;
const jsonSize = (jsonData as string).length;
console.log(`Binary size: ${binarySize} bytes, JSON size: ${jsonSize} bytes`);
expect(binarySize).toBeLessThan(jsonSize);

View File

@@ -90,7 +90,7 @@ describe('EntitySystem', () => {
beforeEach(() => {
scene = new Scene();
system = new ConcreteEntitySystem();
entity = new Entity('test_entity', 1);
entity = scene.createEntity('test_entity');
entity.addComponent(new TestComponent(10));
scene.addEntity(entity);

View File

@@ -1,3 +1,4 @@
import { Scene } from '../../../src/ECS/Scene';
import { PassiveSystem } from '../../../src/ECS/Systems/PassiveSystem';
import { IntervalSystem } from '../../../src/ECS/Systems/IntervalSystem';
import { ProcessingSystem } from '../../../src/ECS/Systems/ProcessingSystem';
@@ -6,6 +7,7 @@ import { Component } from '../../../src/ECS/Component';
import { ComponentRegistry } from '../../../src/ECS/Core/ComponentStorage';
import { Time } from '../../../src/Utils/Time';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { Core } from '../../../src/Core';
// 测试组件
class TestComponent extends Component {
@@ -78,10 +80,13 @@ class ConcreteProcessingSystem extends ProcessingSystem {
}
describe('System Types - 系统类型测试', () => {
let scene: Scene;
let entity: Entity;
beforeEach(() => {
entity = new Entity('TestEntity', 1);
Core.create();
scene = new Scene();
entity = scene.createEntity('TestEntity');
// 重置时间系统
Time.update(0.016);
// 注册测试组件类型
@@ -94,6 +99,7 @@ describe('System Types - 系统类型测试', () => {
beforeEach(() => {
passiveSystem = new ConcretePassiveSystem();
scene.addEntityProcessor(passiveSystem);
});
test('应该能够创建被动系统', () => {
@@ -133,6 +139,7 @@ describe('System Types - 系统类型测试', () => {
beforeEach(() => {
intervalSystem = new ConcreteIntervalSystem(testInterval);
scene.addEntityProcessor(intervalSystem);
});
test('应该能够创建间隔系统', () => {
@@ -215,6 +222,7 @@ describe('System Types - 系统类型测试', () => {
beforeEach(() => {
processingSystem = new ConcreteProcessingSystem();
scene.addEntityProcessor(processingSystem);
});
test('应该能够创建处理系统', () => {
@@ -282,10 +290,10 @@ describe('System Types - 系统类型测试', () => {
const interval = new ConcreteIntervalSystem(0.1);
const processing = new ConcreteProcessingSystem();
const matchingEntity = new Entity('Matching', 1);
const matchingEntity = scene.createEntity('Matching');
matchingEntity.addComponent(new TestComponent(100));
const nonMatchingEntity = new Entity('NonMatching', 2);
const nonMatchingEntity = scene.createEntity('NonMatching');
nonMatchingEntity.addComponent(new AnotherComponent('test'));
// 所有系统都应该匹配TestComponent

View File

@@ -1,6 +1,7 @@
import { ComponentSparseSet } from '../../../src/ECS/Utils/ComponentSparseSet';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { Scene } from '../../../src/ECS/Scene';
// 测试组件类
class PositionComponent extends Component {
@@ -32,20 +33,21 @@ describe('ComponentSparseSet', () => {
let entity1: Entity;
let entity2: Entity;
let entity3: Entity;
let scene: Scene;
beforeEach(() => {
componentSparseSet = new ComponentSparseSet();
// 创建测试实体
entity1 = new Entity('entity1', 1);
scene = new Scene();
entity1 = scene.createEntity('entity1');
entity1.addComponent(new PositionComponent(10, 20));
entity1.addComponent(new VelocityComponent(1, 2));
entity2 = new Entity('entity2', 2);
entity2 = scene.createEntity('entity2');
entity2.addComponent(new PositionComponent(30, 40));
entity2.addComponent(new HealthComponent(80, 100));
entity3 = new Entity('entity3', 3);
entity3 = scene.createEntity('entity3');
entity3.addComponent(new VelocityComponent(3, 4));
entity3.addComponent(new HealthComponent(50, 100));
entity3.addComponent(new RenderComponent(true));
@@ -358,16 +360,16 @@ describe('ComponentSparseSet', () => {
// 创建大量实体
for (let i = 0; i < 1000; i++) {
const entity = new Entity(`entity${i}`, i);
const entity = scene.createEntity(`entity${i}`);
entity.addComponent(new PositionComponent(i, i));
if (i % 2 === 0) {
entity.addComponent(new VelocityComponent(1, 1));
}
if (i % 3 === 0) {
entity.addComponent(new HealthComponent(100, 100));
}
entities.push(entity);
componentSparseSet.addEntity(entity);
}

View File

@@ -28,7 +28,7 @@
"noUncheckedIndexedAccess": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"importHelpers": false,
"importHelpers": true,
"downlevelIteration": true,
"isolatedModules": false,
"allowJs": true,