扩展 InjectableMetadata 接口支持属性注入,实现 @InjectProperty 装饰器
This commit is contained in:
@@ -35,6 +35,12 @@ export interface InjectableMetadata {
|
||||
* 依赖列表
|
||||
*/
|
||||
dependencies: Array<ServiceType<any> | string | symbol>;
|
||||
|
||||
/**
|
||||
* 属性注入映射
|
||||
* key: 属性名, value: 服务类型
|
||||
*/
|
||||
properties?: Map<string | symbol, ServiceType<any>>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,10 +83,12 @@ export interface UpdatableMetadata {
|
||||
*/
|
||||
export function Injectable(): ClassDecorator {
|
||||
return function <T extends Function>(target: T): T {
|
||||
// 标记为可注入
|
||||
const existing = injectableMetadata.get(target);
|
||||
|
||||
injectableMetadata.set(target, {
|
||||
injectable: true,
|
||||
dependencies: []
|
||||
dependencies: [],
|
||||
properties: existing?.properties
|
||||
});
|
||||
|
||||
return target;
|
||||
@@ -142,20 +150,7 @@ export function Updatable(priority: number = 0): ClassDecorator {
|
||||
*
|
||||
* 标记构造函数参数需要注入的服务类型
|
||||
*
|
||||
* @param serviceType 服务类型标识符(类、字符串或Symbol)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Injectable()
|
||||
* class MySystem extends EntitySystem {
|
||||
* constructor(
|
||||
* @Inject(TimeService) private timeService: TimeService,
|
||||
* @Inject(PhysicsService) private physics: PhysicsService
|
||||
* ) {
|
||||
* super();
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @param serviceType 服务类型标识符
|
||||
*/
|
||||
export function Inject(serviceType: ServiceType<any> | string | symbol): ParameterDecorator {
|
||||
return function (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) {
|
||||
@@ -171,6 +166,35 @@ export function Inject(serviceType: ServiceType<any> | string | symbol): Paramet
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查类是否标记为可注入
|
||||
*
|
||||
@@ -252,6 +276,29 @@ export function createInstance<T>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查类是否标记为可更新
|
||||
*
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
export {
|
||||
Injectable,
|
||||
Inject,
|
||||
InjectProperty,
|
||||
Updatable,
|
||||
isInjectable,
|
||||
getInjectableMetadata,
|
||||
@@ -14,6 +15,7 @@ export {
|
||||
isUpdatable,
|
||||
getUpdatableMetadata,
|
||||
createInstance,
|
||||
injectProperties,
|
||||
registerInjectable
|
||||
} from './Decorators';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IncrementalSerializer, IncrementalSnapshot, IncrementalSerializationOpt
|
||||
import { ComponentPoolManager } from './Core/ComponentPool';
|
||||
import { PerformanceMonitor } from '../Utils/PerformanceMonitor';
|
||||
import { ServiceContainer, type ServiceType } from '../Core/ServiceContainer';
|
||||
import { createInstance, isInjectable } from '../Core/DI';
|
||||
import { createInstance, isInjectable, injectProperties } from '../Core/DI';
|
||||
import { isUpdatable, getUpdatableMetadata } from '../Core/DI/Decorators';
|
||||
import { createLogger } from '../Utils/Logger';
|
||||
|
||||
@@ -578,6 +578,8 @@ export class Scene implements IScene {
|
||||
|
||||
this._services.registerInstance(constructor, system);
|
||||
|
||||
injectProperties(system, this._services);
|
||||
|
||||
system.initialize();
|
||||
|
||||
return system;
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 { 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';
|
||||
@@ -423,4 +423,170 @@ describe('EntitySystem - 依赖注入测试', () => {
|
||||
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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user