Compare commits
34 Commits
issue-78-组
...
issue-96-D
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d798995876 | ||
|
|
43e6b7bf88 | ||
|
|
9253686de1 | ||
|
|
7e7eae2d1a | ||
|
|
1924d979d6 | ||
|
|
ed84394301 | ||
|
|
bb99cf5389 | ||
|
|
2d0700f441 | ||
|
|
e3ead8a695 | ||
|
|
701f538e57 | ||
|
|
bb3017ffc2 | ||
|
|
f2b9c5cc5a | ||
|
|
532a52acfc | ||
|
|
c19b5ae9a7 | ||
|
|
5f507532ed | ||
|
|
6e48f22540 | ||
|
|
66aa9f4f20 | ||
|
|
62f895efe0 | ||
|
|
4a060e1ce3 | ||
|
|
a0177c9163 | ||
|
|
f45af34614 | ||
|
|
14a8d755f0 | ||
|
|
b67ab80c75 | ||
|
|
ae71af856b | ||
|
|
279c1d9bc9 | ||
|
|
9068a109b0 | ||
|
|
7850fc610c | ||
|
|
536871d09b | ||
|
|
1af2cf5f99 | ||
|
|
b13132b259 | ||
|
|
a1a6970ea4 | ||
|
|
41bbe23404 | ||
|
|
1d2a3e283e | ||
|
|
62d7521384 |
@@ -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',
|
||||
|
||||
@@ -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 变为 null,ally 仍然有效
|
||||
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 架构的数据载体,正确设计组件能让你的游戏代码更模块化、可维护和高性能。
|
||||
@@ -314,8 +314,8 @@ import { Core, WorldManager } from '@esengine/ecs-framework';
|
||||
// 初始化Core
|
||||
Core.create({ debug: true });
|
||||
|
||||
// 创建世界管理器(手动管理)
|
||||
const worldManager = new WorldManager();
|
||||
// 从服务容器获取 WorldManager(Core 已自动创建并注册)
|
||||
const worldManager = Core.services.resolve(WorldManager);
|
||||
|
||||
// 创建多个独立的游戏世界
|
||||
const room1 = worldManager.createWorld('room_001');
|
||||
|
||||
@@ -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
643
docs/guide/plugin-system.md
Normal 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
675
docs/guide/scene-manager.md
Normal 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 });
|
||||
|
||||
// 获取 SceneManager(Core 已自动创建并注册)
|
||||
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) 文档。
|
||||
@@ -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 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。
|
||||
|
||||
@@ -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) - 检查是否有快照
|
||||
|
||||
589
docs/guide/service-container.md
Normal file
589
docs/guide/service-container.md
Normal 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 依赖 B,B 依赖 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) - 在系统中使用服务
|
||||
@@ -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
761
docs/guide/world-manager.md
Normal 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 });
|
||||
|
||||
// 从服务容器获取 WorldManager(Core 已自动创建并注册)
|
||||
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
55
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
364
packages/core/src/Core/DI/Decorators.ts
Normal file
364
packages/core/src/Core/DI/Decorators.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
22
packages/core/src/Core/DI/index.ts
Normal file
22
packages/core/src/Core/DI/index.ts
Normal 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';
|
||||
124
packages/core/src/Core/Plugin.ts
Normal file
124
packages/core/src/Core/Plugin.ts
Normal 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;
|
||||
}
|
||||
266
packages/core/src/Core/PluginManager.ts
Normal file
266
packages/core/src/Core/PluginManager.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
423
packages/core/src/Core/ServiceContainer.ts
Normal file
423
packages/core/src/Core/ServiceContainer.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,13 @@ export abstract class Component implements IComponent {
|
||||
*/
|
||||
public readonly id: number;
|
||||
|
||||
/**
|
||||
* 所属实体ID
|
||||
*
|
||||
* 存储实体ID而非引用,避免循环引用,符合ECS数据导向设计。
|
||||
*/
|
||||
public entityId: number | null = null;
|
||||
|
||||
/**
|
||||
* 创建组件实例
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
345
packages/core/src/ECS/Core/ReferenceTracker.ts
Normal file
345
packages/core/src/ECS/Core/ReferenceTracker.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
147
packages/core/src/ECS/Decorators/EntityRefDecorator.ts
Normal file
147
packages/core/src/ECS/Decorators/EntityRefDecorator.ts
Normal 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;
|
||||
}
|
||||
@@ -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__;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件类型的名称,优先使用装饰器指定的名称
|
||||
*
|
||||
|
||||
@@ -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';
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
// 将实体添加到场景
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
*
|
||||
|
||||
@@ -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激活状态
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
// ===== 私有方法 =====
|
||||
|
||||
/**
|
||||
|
||||
@@ -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';
|
||||
20
packages/core/src/Types/IUpdatable.ts
Normal file
20
packages/core/src/Types/IUpdatable.ts
Normal 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';
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
206
packages/core/tests/Core/DI.test.ts
Normal file
206
packages/core/tests/Core/DI.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
396
packages/core/tests/Core/PluginSystem.test.ts
Normal file
396
packages/core/tests/Core/PluginSystem.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
235
packages/core/tests/Core/ServiceContainer.test.ts
Normal file
235
packages/core/tests/Core/ServiceContainer.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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('基本功能', () => {
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
254
packages/core/tests/ECS/Core/ReferenceTracker.test.ts
Normal file
254
packages/core/tests/ECS/Core/ReferenceTracker.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
274
packages/core/tests/ECS/EntityRefIntegration.test.ts
Normal file
274
packages/core/tests/ECS/EntityRefIntegration.test.ts
Normal 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}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
592
packages/core/tests/ECS/EntitySystemDI.test.ts
Normal file
592
packages/core/tests/ECS/EntitySystemDI.test.ts
Normal 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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"importHelpers": false,
|
||||
"importHelpers": true,
|
||||
"downlevelIteration": true,
|
||||
"isolatedModules": false,
|
||||
"allowJs": true,
|
||||
|
||||
Reference in New Issue
Block a user