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