依赖注入引入DI容器

This commit is contained in:
YHH
2025-10-10 21:52:43 +08:00
parent a1a6970ea4
commit b13132b259
19 changed files with 1529 additions and 231 deletions

View File

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

View File

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

View File

@@ -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('流式调用应该工作正常', () => {

View File

@@ -0,0 +1,426 @@
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 } 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);
});
});
});

View File

@@ -129,7 +129,7 @@ describe('Scene - 场景管理系统测试', () => {
expect(scene).toBeInstanceOf(Scene);
expect(scene.name).toBe("");
expect(scene.entities).toBeDefined();
expect(scene.entityProcessors).toBeDefined();
expect(scene.systems).toBeDefined();
expect(scene.identifierPool).toBeDefined();
});
@@ -140,7 +140,7 @@ describe('Scene - 场景管理系统测试', () => {
test('场景应该有正确的初始状态', () => {
expect(scene.entities.count).toBe(0);
expect(scene.entityProcessors.count).toBe(0);
expect(scene.systems.length).toBe(0);
});
test('应该能够使用配置创建场景', () => {
@@ -248,16 +248,16 @@ describe('Scene - 场景管理系统测试', () => {
test('应该能够添加实体系统', () => {
scene.addEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(1);
expect(scene.systems.length).toBe(1);
expect(movementSystem.scene).toBe(scene);
});
test('应该能够移除实体系统', () => {
scene.addEntityProcessor(movementSystem);
scene.removeEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(0);
expect(scene.systems.length).toBe(0);
expect(movementSystem.scene).toBeNull();
});
@@ -270,8 +270,8 @@ describe('Scene - 场景管理系统测试', () => {
test('应该能够管理多个实体系统', () => {
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(renderSystem);
expect(scene.entityProcessors.count).toBe(2);
expect(scene.systems.length).toBe(2);
});
test('系统应该按更新顺序执行', () => {
@@ -537,11 +537,12 @@ describe('Scene - 场景管理系统测试', () => {
describe('错误处理和边界情况', () => {
test('重复添加同一个系统应该安全处理', () => {
const system = new MovementSystem();
scene.addEntityProcessor(system);
scene.addEntityProcessor(system); // 重复添加
expect(scene.entityProcessors.count).toBe(1);
scene.addEntityProcessor(system);
expect(scene.systems.length).toBe(1);
});
test('系统处理过程中的异常应该被正确处理', () => {