diff --git a/packages/core/src/Core/DI/Decorators.ts b/packages/core/src/Core/DI/Decorators.ts index 838d787a..7225ee9c 100644 --- a/packages/core/src/Core/DI/Decorators.ts +++ b/packages/core/src/Core/DI/Decorators.ts @@ -35,6 +35,12 @@ export interface InjectableMetadata { * 依赖列表 */ dependencies: Array | string | symbol>; + + /** + * 属性注入映射 + * key: 属性名, value: 服务类型 + */ + properties?: Map>; } /** @@ -77,10 +83,12 @@ export interface UpdatableMetadata { */ export function Injectable(): ClassDecorator { return 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 | string | symbol): ParameterDecorator { return function (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) { @@ -171,6 +166,35 @@ export function Inject(serviceType: ServiceType | string | symbol): Paramet }; } +/** + * @InjectProperty() 装饰器 + * + * 通过属性装饰器注入依赖 + * + * 注入时机:在构造函数执行后、onInitialize() 调用前完成 + * + * @param serviceType 服务类型 + */ +export function InjectProperty(serviceType: ServiceType): 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( return new constructor(...dependencies); } +/** + * 为实例注入属性依赖 + * + * @param instance 目标实例 + * @param container 服务容器 + */ +export function injectProperties(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; + } + } +} + /** * 检查类是否标记为可更新 * diff --git a/packages/core/src/Core/DI/index.ts b/packages/core/src/Core/DI/index.ts index 4dd71639..43803bd9 100644 --- a/packages/core/src/Core/DI/index.ts +++ b/packages/core/src/Core/DI/index.ts @@ -7,6 +7,7 @@ export { Injectable, Inject, + InjectProperty, Updatable, isInjectable, getInjectableMetadata, @@ -14,6 +15,7 @@ export { isUpdatable, getUpdatableMetadata, createInstance, + injectProperties, registerInjectable } from './Decorators'; diff --git a/packages/core/src/ECS/Scene.ts b/packages/core/src/ECS/Scene.ts index 255cb461..5f0fd46e 100644 --- a/packages/core/src/ECS/Scene.ts +++ b/packages/core/src/ECS/Scene.ts @@ -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; diff --git a/packages/core/tests/ECS/EntitySystemDI.test.ts b/packages/core/tests/ECS/EntitySystemDI.test.ts index 087b9374..f02802e5 100644 --- a/packages/core/tests/ECS/EntitySystemDI.test.ts +++ b/packages/core/tests/ECS/EntitySystemDI.test.ts @@ -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]); + }); + }); });