扩展 InjectableMetadata 接口支持属性注入,实现 @InjectProperty 装饰器

This commit is contained in:
YHH
2025-10-12 23:39:32 +08:00
parent 9253686de1
commit 43e6b7bf88
4 changed files with 235 additions and 18 deletions

View File

@@ -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;
}
}
}
/**
* 检查类是否标记为可更新
*

View File

@@ -7,6 +7,7 @@
export {
Injectable,
Inject,
InjectProperty,
Updatable,
isInjectable,
getInjectableMetadata,
@@ -14,6 +15,7 @@ export {
isUpdatable,
getUpdatableMetadata,
createInstance,
injectProperties,
registerInjectable
} from './Decorators';

View File

@@ -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;

View File

@@ -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]);
});
});
});