From c6312900493e7dd4a5c6a5e27f54d5380c27d20f Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Wed, 8 Oct 2025 13:13:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9query/entity=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E7=B1=BB=E5=9E=8B=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/src/ECS/Core/Query/TypedQuery.ts | 404 ++++++++++++++++++ packages/core/src/ECS/Entity.ts | 70 ++- packages/core/src/ECS/Scene.ts | 65 +++ packages/core/src/ECS/Systems/EntitySystem.ts | 264 +++++++++++- packages/core/src/ECS/TypedEntity.ts | 307 +++++++++++++ packages/core/src/Types/TypeHelpers.ts | 295 +++++++++++++ packages/core/src/Types/index.ts | 3 + packages/core/src/index.ts | 5 + packages/core/tests/SceneQuery.test.ts | 145 +++++++ packages/core/tests/TypeInference.test.ts | 205 +++++++++ 10 files changed, 1742 insertions(+), 21 deletions(-) create mode 100644 packages/core/src/ECS/Core/Query/TypedQuery.ts create mode 100644 packages/core/src/ECS/TypedEntity.ts create mode 100644 packages/core/src/Types/TypeHelpers.ts create mode 100644 packages/core/tests/SceneQuery.test.ts create mode 100644 packages/core/tests/TypeInference.test.ts diff --git a/packages/core/src/ECS/Core/Query/TypedQuery.ts b/packages/core/src/ECS/Core/Query/TypedQuery.ts new file mode 100644 index 00000000..e1b79a4e --- /dev/null +++ b/packages/core/src/ECS/Core/Query/TypedQuery.ts @@ -0,0 +1,404 @@ +/** + * 类型安全的Query查询系统 + * + * 提供完整的TypeScript类型推断,在编译时确保类型安全 + */ + +import type { Entity } from '../../Entity'; +import type { ComponentConstructor, ComponentInstance, ComponentTypeMap } from '../../../Types/TypeHelpers'; +import { Matcher, type QueryCondition } from '../../Utils/Matcher'; + +/** + * 类型安全的查询结果 + * + * 根据查询条件自动推断实体必定拥有的组件类型 + */ +export class TypedQueryResult { + private _entities: readonly Entity[]; + private _componentTypes: TAll; + + constructor(entities: readonly Entity[], componentTypes: TAll) { + this._entities = entities; + this._componentTypes = componentTypes; + } + + /** + * 获取实体列表 + */ + get entities(): readonly Entity[] { + return this._entities; + } + + /** + * 实体数量 + */ + get length(): number { + return this._entities.length; + } + + /** + * 遍历所有实体 + * + * @example + * ```typescript + * query.forEach((entity) => { + * // entity.getComponent返回类型自动推断 + * const pos = entity.getComponent(Position); // Position类型 + * const vel = entity.getComponent(Velocity); // Velocity类型 + * }); + * ``` + */ + forEach(callback: (entity: Entity, index: number) => void): void { + this._entities.forEach(callback); + } + + /** + * 映射转换实体 + */ + map(callback: (entity: Entity, index: number) => R): R[] { + return this._entities.map(callback); + } + + /** + * 过滤实体 + */ + filter(predicate: (entity: Entity, index: number) => boolean): TypedQueryResult { + return new TypedQueryResult(this._entities.filter(predicate), this._componentTypes); + } + + /** + * 查找第一个匹配的实体 + */ + find(predicate: (entity: Entity, index: number) => boolean): Entity | undefined { + return this._entities.find(predicate); + } + + /** + * 检查是否存在匹配的实体 + */ + some(predicate: (entity: Entity, index: number) => boolean): boolean { + return this._entities.some(predicate); + } + + /** + * 检查是否所有实体都匹配 + */ + every(predicate: (entity: Entity, index: number) => boolean): boolean { + return this._entities.every(predicate); + } + + /** + * 获取指定索引的实体 + */ + get(index: number): Entity | undefined { + return this._entities[index]; + } + + /** + * 获取第一个实体 + */ + get first(): Entity | undefined { + return this._entities[0]; + } + + /** + * 获取最后一个实体 + */ + get last(): Entity | undefined { + return this._entities[this._entities.length - 1]; + } + + /** + * 检查查询结果是否为空 + */ + get isEmpty(): boolean { + return this._entities.length === 0; + } + + /** + * 转换为数组 + */ + toArray(): Entity[] { + return [...this._entities]; + } + + /** + * 获取组件类型信息(用于调试) + */ + getComponentTypes(): readonly ComponentConstructor[] { + return this._componentTypes; + } + + /** + * 迭代器支持 + */ + [Symbol.iterator](): Iterator { + return this._entities[Symbol.iterator](); + } +} + +/** + * 类型安全的查询构建器 + * + * 支持链式调用,自动推断查询结果的类型 + * + * @example + * ```typescript + * // 基础查询 + * const query = new TypedQueryBuilder() + * .withAll(Position, Velocity) + * .build(); + * + * // 复杂查询 + * const complexQuery = new TypedQueryBuilder() + * .withAll(Transform, Renderer) + * .withAny(BoxCollider, CircleCollider) + * .withNone(Disabled) + * .withTag(EntityTags.Enemy) + * .build(); + * ``` + */ +export class TypedQueryBuilder< + TAll extends readonly ComponentConstructor[] = [], + TAny extends readonly ComponentConstructor[] = [], + TNone extends readonly ComponentConstructor[] = [] +> { + private _all: TAll; + private _any: TAny; + private _none: TNone; + private _tag?: number; + private _name?: string; + + constructor( + all?: TAll, + any?: TAny, + none?: TNone, + tag?: number, + name?: string + ) { + this._all = (all || []) as TAll; + this._any = (any || []) as TAny; + this._none = (none || []) as TNone; + this._tag = tag; + this._name = name; + } + + /** + * 要求实体拥有所有指定的组件 + * + * @param types 组件类型 + * @returns 新的查询构建器,类型参数更新 + */ + withAll( + ...types: TNewAll + ): TypedQueryBuilder< + readonly [...TAll, ...TNewAll], + TAny, + TNone + > { + return new TypedQueryBuilder( + [...this._all, ...types] as readonly [...TAll, ...TNewAll], + this._any, + this._none, + this._tag, + this._name + ); + } + + /** + * 要求实体至少拥有一个指定的组件 + * + * @param types 组件类型 + * @returns 新的查询构建器 + */ + withAny( + ...types: TNewAny + ): TypedQueryBuilder< + TAll, + readonly [...TAny, ...TNewAny], + TNone + > { + return new TypedQueryBuilder( + this._all, + [...this._any, ...types] as readonly [...TAny, ...TNewAny], + this._none, + this._tag, + this._name + ); + } + + /** + * 排除拥有指定组件的实体 + * + * @param types 组件类型 + * @returns 新的查询构建器 + */ + withNone( + ...types: TNewNone + ): TypedQueryBuilder< + TAll, + TAny, + readonly [...TNone, ...TNewNone] + > { + return new TypedQueryBuilder( + this._all, + this._any, + [...this._none, ...types] as readonly [...TNone, ...TNewNone], + this._tag, + this._name + ); + } + + /** + * 按标签过滤实体 + * + * @param tag 标签值 + * @returns 新的查询构建器 + */ + withTag(tag: number): TypedQueryBuilder { + return new TypedQueryBuilder( + this._all, + this._any, + this._none, + tag, + this._name + ); + } + + /** + * 按名称过滤实体 + * + * @param name 实体名称 + * @returns 新的查询构建器 + */ + withName(name: string): TypedQueryBuilder { + return new TypedQueryBuilder( + this._all, + this._any, + this._none, + this._tag, + name + ); + } + + /** + * 构建Matcher对象 + * + * @returns Matcher实例,用于传统查询API + */ + buildMatcher(): Matcher { + let matcher = Matcher.complex(); + + if (this._all.length > 0) { + matcher = matcher.all(...(this._all as unknown as ComponentConstructor[])); + } + + if (this._any.length > 0) { + matcher = matcher.any(...(this._any as unknown as ComponentConstructor[])); + } + + if (this._none.length > 0) { + matcher = matcher.none(...(this._none as unknown as ComponentConstructor[])); + } + + if (this._tag !== undefined) { + matcher = matcher.withTag(this._tag); + } + + if (this._name !== undefined) { + matcher = matcher.withName(this._name); + } + + return matcher; + } + + /** + * 获取查询条件 + * + * @returns 查询条件对象 + */ + getCondition(): QueryCondition { + return { + all: [...this._all] as ComponentConstructor[], + any: [...this._any] as ComponentConstructor[], + none: [...this._none] as ComponentConstructor[], + tag: this._tag, + name: this._name + }; + } + + /** + * 获取required组件类型(用于类型推断) + */ + getRequiredTypes(): TAll { + return this._all; + } + + /** + * 克隆查询构建器 + */ + clone(): TypedQueryBuilder { + return new TypedQueryBuilder( + [...this._all] as unknown as TAll, + [...this._any] as unknown as TAny, + [...this._none] as unknown as TNone, + this._tag, + this._name + ); + } +} + +/** + * 创建类型安全的查询构建器 + * + * @example + * ```typescript + * const query = createQuery() + * .withAll(Position, Velocity) + * .withNone(Disabled); + * + * // 在System或Scene中使用 + * const entities = scene.query(query); + * entities.forEach(entity => { + * const pos = entity.getComponent(Position); // 自动推断为Position + * const vel = entity.getComponent(Velocity); // 自动推断为Velocity + * }); + * ``` + */ +export function createQuery(): TypedQueryBuilder<[], [], []> { + return new TypedQueryBuilder(); +} + +/** + * 创建单组件查询的便捷方法 + * + * @param componentType 组件类型 + * @returns 查询构建器 + * + * @example + * ```typescript + * const healthEntities = queryFor(HealthComponent); + * ``` + */ +export function queryFor( + componentType: T +): TypedQueryBuilder { + return new TypedQueryBuilder([componentType] as readonly [T]); +} + +/** + * 创建多组件查询的便捷方法 + * + * @param types 组件类型数组 + * @returns 查询构建器 + * + * @example + * ```typescript + * const movableEntities = queryForAll(Position, Velocity); + * ``` + */ +export function queryForAll( + ...types: T +): TypedQueryBuilder { + return new TypedQueryBuilder(types); +} diff --git a/packages/core/src/ECS/Entity.ts b/packages/core/src/ECS/Entity.ts index 7bcd2d27..250338c4 100644 --- a/packages/core/src/ECS/Entity.ts +++ b/packages/core/src/ECS/Entity.ts @@ -346,13 +346,19 @@ export class Entity { /** * 创建并添加组件 - * - * @param componentType - 组件类型 + * + * @param componentType - 组件类型构造函数 * @param args - 组件构造函数参数 * @returns 创建的组件实例 + * + * @example + * ```typescript + * const position = entity.createComponent(Position, 100, 200); + * const health = entity.createComponent(Health, 100); + * ``` */ public createComponent( - componentType: ComponentType, + componentType: ComponentType, ...args: any[] ): T { const component = new componentType(...args); @@ -387,10 +393,16 @@ export class Entity { /** * 添加组件到实体 - * + * * @param component - 要添加的组件实例 * @returns 添加的组件实例 - * @throws {Error} 如果组件类型已存在 + * @throws {Error} 如果实体已存在该类型的组件 + * + * @example + * ```typescript + * const position = new Position(100, 200); + * entity.addComponent(position); + * ``` */ public addComponent(component: T): T { const componentType = component.constructor as ComponentType; @@ -429,8 +441,17 @@ export class Entity { /** * 获取指定类型的组件 * - * @param type - 组件类型 - * @returns 组件实例或null + * @param type - 组件类型构造函数 + * @returns 组件实例,如果不存在则返回null + * + * @example + * ```typescript + * const position = entity.getComponent(Position); + * if (position) { + * position.x += 10; + * position.y += 20; + * } + * ``` */ public getComponent(type: ComponentType): T | null { // 快速检查:位掩码 @@ -442,7 +463,7 @@ export class Entity { if (this.scene?.componentStorageManager) { const component = this.scene.componentStorageManager.getComponent(this.id, type); if (component) { - return component; + return component as T; } } @@ -454,10 +475,18 @@ export class Entity { /** - * 检查实体是否有指定类型的组件 - * - * @param type - 组件类型 - * @returns 如果有该组件则返回true + * 检查实体是否拥有指定类型的组件 + * + * @param type - 组件类型构造函数 + * @returns 如果实体拥有该组件返回true,否则返回false + * + * @example + * ```typescript + * if (entity.hasComponent(Position)) { + * const position = entity.getComponent(Position)!; + * position.x += 10; + * } + * ``` */ public hasComponent(type: ComponentType): boolean { if (!ComponentRegistry.isRegistered(type)) { @@ -470,13 +499,22 @@ export class Entity { /** * 获取或创建指定类型的组件 - * - * @param type - 组件类型 - * @param args - 组件构造函数参数(仅在创建时使用) + * + * 如果组件已存在则返回现有组件,否则创建新组件并添加到实体 + * + * @param type - 组件类型构造函数 + * @param args - 组件构造函数参数(仅在创建新组件时使用) * @returns 组件实例 + * + * @example + * ```typescript + * // 确保实体拥有Position组件 + * const position = entity.getOrCreateComponent(Position, 0, 0); + * position.x = 100; + * ``` */ public getOrCreateComponent( - type: ComponentType, + type: ComponentType, ...args: any[] ): T { let component = this.getComponent(type); diff --git a/packages/core/src/ECS/Scene.ts b/packages/core/src/ECS/Scene.ts index 2f97e9bc..4b4bdcf0 100644 --- a/packages/core/src/ECS/Scene.ts +++ b/packages/core/src/ECS/Scene.ts @@ -9,6 +9,7 @@ import { TypeSafeEventSystem } from './Core/EventSystem'; import { EventBus } from './Core/EventBus'; import { IScene, ISceneConfig } from './IScene'; import { getComponentInstanceTypeName, getSystemInstanceTypeName } from './Decorators'; +import { TypedQueryBuilder } from './Core/Query/TypedQuery'; /** * 游戏场景默认实现类 @@ -319,6 +320,70 @@ export class Scene implements IScene { return this.findEntitiesByTag(tag); } + /** + * 查询拥有所有指定组件的实体 + * + * @param componentTypes - 组件类型数组 + * @returns 查询结果 + * + * @example + * ```typescript + * const result = scene.queryAll(Position, Velocity); + * for (const entity of result.entities) { + * const pos = entity.getComponent(Position); + * const vel = entity.getComponent(Velocity); + * } + * ``` + */ + public queryAll(...componentTypes: any[]): { entities: readonly Entity[] } { + return this.querySystem.queryAll(...componentTypes); + } + + /** + * 查询拥有任意一个指定组件的实体 + * + * @param componentTypes - 组件类型数组 + * @returns 查询结果 + */ + public queryAny(...componentTypes: any[]): { entities: readonly Entity[] } { + return this.querySystem.queryAny(...componentTypes); + } + + /** + * 查询不包含指定组件的实体 + * + * @param componentTypes - 组件类型数组 + * @returns 查询结果 + */ + public queryNone(...componentTypes: any[]): { entities: readonly Entity[] } { + return this.querySystem.queryNone(...componentTypes); + } + + /** + * 创建类型安全的查询构建器 + * + * @returns 查询构建器,支持链式调用 + * + * @example + * ```typescript + * // 使用查询构建器 + * const matcher = scene.query() + * .withAll(Position, Velocity) + * .withNone(Disabled) + * .buildMatcher(); + * + * // 在System中使用 + * class MovementSystem extends EntitySystem { + * constructor() { + * super(matcher); + * } + * } + * ``` + */ + public query(): TypedQueryBuilder { + return new TypedQueryBuilder(); + } + /** * 在场景中添加一个EntitySystem处理器 * @param processor 处理器 diff --git a/packages/core/src/ECS/Systems/EntitySystem.ts b/packages/core/src/ECS/Systems/EntitySystem.ts index 2db8b906..a6c607fd 100644 --- a/packages/core/src/ECS/Systems/EntitySystem.ts +++ b/packages/core/src/ECS/Systems/EntitySystem.ts @@ -7,6 +7,7 @@ import type { QuerySystem } from '../Core/QuerySystem'; import { getSystemInstanceTypeName } from '../Decorators'; import { createLogger } from '../../Utils/Logger'; import type { EventListenerConfig, TypeSafeEventSystem, EventHandler } from '../Core/EventSystem'; +import type { ComponentConstructor, ComponentInstance } from '../../Types/TypeHelpers'; /** * 事件监听器记录 @@ -21,17 +22,22 @@ interface EventListenerRecord { /** * 实体系统的基类 - * + * * 用于处理一组符合特定条件的实体。系统是ECS架构中的逻辑处理单元, * 负责对拥有特定组件组合的实体执行业务逻辑。 - * + * + * 支持泛型参数以提供类型安全的组件访问: + * + * @template TComponents - 系统需要的组件类型数组 + * * @example * ```typescript + * // 传统方式 * class MovementSystem extends EntitySystem { * constructor() { - * super(Transform, Velocity); + * super(Matcher.of(Transform, Velocity)); * } - * + * * protected process(entities: readonly Entity[]): void { * for (const entity of entities) { * const transform = entity.getComponent(Transform); @@ -40,9 +46,26 @@ interface EventListenerRecord { * } * } * } + * + * // 类型安全方式 + * class MovementSystem extends EntitySystem<[typeof Transform, typeof Velocity]> { + * constructor() { + * super(Matcher.of(Transform, Velocity)); + * } + * + * protected process(entities: readonly Entity[]): void { + * for (const entity of entities) { + * // 类型安全的组件访问 + * const [transform, velocity] = this.getComponents(entity); + * transform.position.add(velocity.value); + * } + * } + * } * ``` */ -export abstract class EntitySystem implements ISystemBase { +export abstract class EntitySystem< + TComponents extends readonly ComponentConstructor[] = [] +> implements ISystemBase { private _updateOrder: number; private _enabled: boolean; private _performanceMonitor: PerformanceMonitor; @@ -793,4 +816,235 @@ export abstract class EntitySystem implements ISystemBase { protected onDestroy(): void { // 子类可以重写此方法进行清理操作 } + + // ============================================================ + // 类型安全的辅助方法 + // ============================================================ + + /** + * 类型安全地获取单个组件 + * + * 相比Entity.getComponent,此方法保证返回非空值, + * 如果组件不存在会抛出错误而不是返回null + * + * @param entity 实体 + * @param componentType 组件类型 + * @returns 组件实例(保证非空) + * @throws 如果组件不存在则抛出错误 + * + * @example + * ```typescript + * protected process(entities: readonly Entity[]): void { + * for (const entity of entities) { + * const transform = this.requireComponent(entity, Transform); + * // transform 保证非空,类型为 Transform + * } + * } + * ``` + */ + protected requireComponent( + entity: Entity, + componentType: T + ): ComponentInstance { + const component = entity.getComponent(componentType as any); + if (!component) { + throw new Error( + `Component ${componentType.name} not found on entity ${entity.name} in ${this.systemName}` + ); + } + return component as ComponentInstance; + } + + /** + * 批量获取实体的所有必需组件 + * + * 根据泛型参数TComponents推断返回类型, + * 返回一个元组,包含所有组件实例 + * + * @param entity 实体 + * @param components 组件类型数组 + * @returns 组件实例元组 + * + * @example + * ```typescript + * class MySystem extends EntitySystem<[typeof Position, typeof Velocity]> { + * protected process(entities: readonly Entity[]): void { + * for (const entity of entities) { + * const [pos, vel] = this.getComponents(entity, Position, Velocity); + * // pos: Position, vel: Velocity (自动类型推断) + * pos.x += vel.x; + * } + * } + * } + * ``` + */ + protected getComponents( + entity: Entity, + ...components: T + ): { [K in keyof T]: ComponentInstance } { + return components.map((type) => + this.requireComponent(entity, type) + ) as any; + } + + /** + * 遍历实体并处理每个实体 + * + * 提供更简洁的语法糖,避免手动遍历 + * + * @param entities 实体列表 + * @param processor 处理函数 + * + * @example + * ```typescript + * protected process(entities: readonly Entity[]): void { + * this.forEach(entities, (entity) => { + * const transform = this.requireComponent(entity, Transform); + * transform.position.y -= 9.8 * Time.deltaTime; + * }); + * } + * ``` + */ + protected forEach( + entities: readonly Entity[], + processor: (entity: Entity, index: number) => void + ): void { + for (let i = 0; i < entities.length; i++) { + processor(entities[i], i); + } + } + + /** + * 过滤实体 + * + * @param entities 实体列表 + * @param predicate 过滤条件 + * @returns 过滤后的实体数组 + * + * @example + * ```typescript + * protected process(entities: readonly Entity[]): void { + * const activeEntities = this.filterEntities(entities, (entity) => { + * const health = this.requireComponent(entity, Health); + * return health.value > 0; + * }); + * } + * ``` + */ + protected filterEntities( + entities: readonly Entity[], + predicate: (entity: Entity, index: number) => boolean + ): Entity[] { + return Array.from(entities).filter(predicate); + } + + /** + * 映射实体到另一种类型 + * + * @param entities 实体列表 + * @param mapper 映射函数 + * @returns 映射后的结果数组 + * + * @example + * ```typescript + * protected process(entities: readonly Entity[]): void { + * const positions = this.mapEntities(entities, (entity) => { + * const transform = this.requireComponent(entity, Transform); + * return transform.position; + * }); + * } + * ``` + */ + protected mapEntities( + entities: readonly Entity[], + mapper: (entity: Entity, index: number) => R + ): R[] { + return Array.from(entities).map(mapper); + } + + /** + * 查找第一个满足条件的实体 + * + * @param entities 实体列表 + * @param predicate 查找条件 + * @returns 第一个满足条件的实体,或undefined + * + * @example + * ```typescript + * protected process(entities: readonly Entity[]): void { + * const player = this.findEntity(entities, (entity) => + * entity.hasComponent(PlayerTag) + * ); + * } + * ``` + */ + protected findEntity( + entities: readonly Entity[], + predicate: (entity: Entity, index: number) => boolean + ): Entity | undefined { + for (let i = 0; i < entities.length; i++) { + if (predicate(entities[i], i)) { + return entities[i]; + } + } + return undefined; + } + + /** + * 检查是否存在满足条件的实体 + * + * @param entities 实体列表 + * @param predicate 检查条件 + * @returns 是否存在满足条件的实体 + * + * @example + * ```typescript + * protected process(entities: readonly Entity[]): void { + * const hasLowHealth = this.someEntity(entities, (entity) => { + * const health = this.requireComponent(entity, Health); + * return health.value < 20; + * }); + * } + * ``` + */ + protected someEntity( + entities: readonly Entity[], + predicate: (entity: Entity, index: number) => boolean + ): boolean { + for (let i = 0; i < entities.length; i++) { + if (predicate(entities[i], i)) { + return true; + } + } + return false; + } + + /** + * 检查是否所有实体都满足条件 + * + * @param entities 实体列表 + * @param predicate 检查条件 + * @returns 是否所有实体都满足条件 + * + * @example + * ```typescript + * protected process(entities: readonly Entity[]): void { + * const allHealthy = this.everyEntity(entities, (entity) => { + * const health = this.requireComponent(entity, Health); + * return health.value > 50; + * }); + * } + * ``` + */ + protected everyEntity( + entities: readonly Entity[], + predicate: (entity: Entity, index: number) => boolean + ): boolean { + for (let i = 0; i < entities.length; i++) { + if (!predicate(entities[i], i)) { + return false; + } + } + return true; + } } \ No newline at end of file diff --git a/packages/core/src/ECS/TypedEntity.ts b/packages/core/src/ECS/TypedEntity.ts new file mode 100644 index 00000000..a61d3763 --- /dev/null +++ b/packages/core/src/ECS/TypedEntity.ts @@ -0,0 +1,307 @@ +/** + * Entity类型安全工具函数 + * + * 提供类型安全的组件操作工具函数,无需修改Entity类 + */ + +import { Entity } from './Entity'; +import type { Component } from './Component'; +import type { ComponentType } from './Core/ComponentStorage'; +import type { ComponentConstructor, ComponentInstance } from '../Types/TypeHelpers'; + +/** + * 获取组件,如果不存在则抛出错误 + * + * @param entity - 实体实例 + * @param componentType - 组件类型构造函数 + * @returns 组件实例(保证非空) + * @throws {Error} 如果组件不存在 + * + * @example + * ```typescript + * const position = requireComponent(entity, Position); + * position.x += 10; + * ``` + */ +export function requireComponent( + entity: Entity, + componentType: T +): ComponentInstance { + const component = entity.getComponent(componentType as unknown as ComponentType>); + if (!component) { + throw new Error( + `Component ${componentType.name} not found on entity ${entity.name} (id: ${entity.id})` + ); + } + return component; +} + +/** + * 尝试获取组件 + * + * @param entity - 实体实例 + * @param componentType - 组件类型构造函数 + * @returns 组件实例或undefined + * + * @example + * ```typescript + * const health = tryGetComponent(entity, Health); + * if (health) { + * health.value -= 10; + * } + * ``` + */ +export function tryGetComponent( + entity: Entity, + componentType: T +): ComponentInstance | undefined { + const component = entity.getComponent(componentType as unknown as ComponentType>); + return component !== null ? component : undefined; +} + +/** + * 批量获取组件 + * + * @param entity - 实体实例 + * @param types - 组件类型构造函数数组 + * @returns 组件实例元组,每个元素可能为null + * + * @example + * ```typescript + * const [pos, vel, health] = getComponents(entity, Position, Velocity, Health); + * if (pos && vel && health) { + * pos.x += vel.dx; + * } + * ``` + */ +export function getComponents( + entity: Entity, + ...types: T +): { [K in keyof T]: ComponentInstance | null } { + return types.map((type) => + entity.getComponent(type as unknown as ComponentType>) + ) as { [K in keyof T]: ComponentInstance | null }; +} + +/** + * 检查实体是否拥有所有指定的组件 + * + * @param entity - 实体实例 + * @param types - 组件类型构造函数数组 + * @returns 如果拥有所有组件返回true,否则返回false + * + * @example + * ```typescript + * if (hasComponents(entity, Position, Velocity)) { + * const pos = entity.getComponent(Position)!; + * const vel = entity.getComponent(Velocity)!; + * } + * ``` + */ +export function hasComponents(entity: Entity, ...types: ComponentConstructor[]): boolean { + return types.every((type) => entity.hasComponent(type as unknown as ComponentType)); +} + +/** + * 检查实体是否拥有至少一个指定的组件 + * + * @param entity - 实体实例 + * @param types - 组件类型构造函数数组 + * @returns 如果拥有任意一个组件返回true,否则返回false + */ +export function hasAnyComponent(entity: Entity, ...types: ComponentConstructor[]): boolean { + return types.some((type) => entity.hasComponent(type as unknown as ComponentType)); +} + +/** + * 添加组件并立即配置 + * + * @param entity - 实体实例 + * @param component - 组件实例 + * @param configure - 配置回调函数 + * @returns 实体实例(支持链式调用) + * + * @example + * ```typescript + * addAndConfigure(entity, new Health(), health => { + * health.maxValue = 100; + * health.value = 50; + * }); + * ``` + */ +export function addAndConfigure( + entity: Entity, + component: T, + configure: (component: T) => void +): Entity { + entity.addComponent(component); + configure(component); + return entity; +} + +/** + * 获取或添加组件 + * + * 如果组件已存在则返回现有组件,否则通过工厂函数创建并添加 + * + * @param entity - 实体实例 + * @param componentType - 组件类型构造函数 + * @param factory - 组件工厂函数(仅在组件不存在时调用) + * @returns 组件实例 + * + * @example + * ```typescript + * const health = getOrAddComponent(entity, Health, () => new Health(100)); + * ``` + */ +export function getOrAddComponent( + entity: Entity, + componentType: T, + factory: () => ComponentInstance +): ComponentInstance { + let component = entity.getComponent(componentType as unknown as ComponentType>); + if (!component) { + component = factory(); + entity.addComponent(component); + } + return component; +} + +/** + * 更新组件的部分字段 + * + * @param entity - 实体实例 + * @param componentType - 组件类型构造函数 + * @param data - 要更新的部分数据 + * @returns 如果更新成功返回true,组件不存在返回false + * + * @example + * ```typescript + * updateComponent(entity, Position, { x: 100 }); + * ``` + */ +export function updateComponent( + entity: Entity, + componentType: T, + data: Partial> +): boolean { + const component = entity.getComponent(componentType as unknown as ComponentType>); + if (!component) { + return false; + } + + Object.assign(component, data); + return true; +} + +/** + * 类型安全的实体构建器 + * + * @example + * ```typescript + * const player = buildEntity(scene.createEntity("Player")) + * .with(new Position(100, 100)) + * .with(new Velocity(0, 0)) + * .withTag(1) + * .build(); + * ``` + */ +export class TypedEntityBuilder { + private _entity: Entity; + + constructor(entity: Entity) { + this._entity = entity; + } + + /** + * 添加组件 + */ + with(component: T): this { + this._entity.addComponent(component); + return this; + } + + /** + * 添加并配置组件 + */ + withConfigured( + component: T, + configure: (component: T) => void + ): this { + this._entity.addComponent(component); + configure(component); + return this; + } + + /** + * 设置标签 + */ + withTag(tag: number): this { + this._entity.tag = tag; + return this; + } + + /** + * 设置名称 + */ + withName(name: string): this { + this._entity.name = name; + return this; + } + + /** + * 设置激活状态 + */ + withActive(active: boolean): this { + this._entity.active = active; + return this; + } + + /** + * 设置启用状态 + */ + withEnabled(enabled: boolean): this { + this._entity.enabled = enabled; + return this; + } + + /** + * 设置更新顺序 + */ + withUpdateOrder(order: number): this { + this._entity.updateOrder = order; + return this; + } + + /** + * 添加子实体 + */ + withChild(child: Entity): this { + this._entity.addChild(child); + return this; + } + + /** + * 完成构建 + */ + build(): Entity { + return this._entity; + } + + /** + * 获取正在构建的实体 + */ + get entity(): Entity { + return this._entity; + } +} + +/** + * 创建类型安全的实体构建器 + * + * @param entity - 要包装的实体 + * @returns 实体构建器 + */ +export function buildEntity(entity: Entity): TypedEntityBuilder { + return new TypedEntityBuilder(entity); +} diff --git a/packages/core/src/Types/TypeHelpers.ts b/packages/core/src/Types/TypeHelpers.ts new file mode 100644 index 00000000..b86577d5 --- /dev/null +++ b/packages/core/src/Types/TypeHelpers.ts @@ -0,0 +1,295 @@ +/** + * TypeScript类型工具集 + * + * 提供高级类型推断和类型安全的工具类型 + */ + +import type { IComponent } from './index'; +import { Component } from '../ECS/Component'; + +/** + * 组件类型提取器 + * 从组件构造函数中提取实例类型 + */ +export type ComponentInstance = T extends new (...args: any[]) => infer R ? R : never; + +/** + * 组件构造函数类型 + * + * 与 ComponentType 保持一致,避免类型转换 + */ +export type ComponentConstructor = new (...args: any[]) => T; + +/** + * 组件类型的通用约束 + * + * 用于确保类型参数是有效的组件构造函数 + */ +export type AnyComponentConstructor = ComponentConstructor; + +/** + * 多组件类型提取 + * 从组件构造函数数组中提取所有实例类型的联合 + */ +export type ExtractComponents = { + [K in keyof T]: ComponentInstance; +}; + +/** + * 组件类型映射 + * 将组件构造函数数组转换为实例类型的元组 + */ +export type ComponentTypeMap = { + [K in keyof T]: T[K] extends ComponentConstructor ? C : never; +}; + +/** + * 实体with组件的类型 + * 表示一个实体确定拥有某些组件 + */ +export interface EntityWithComponents { + readonly id: number; + readonly name: string; + + /** + * 类型安全的组件获取 + * 确保返回非空的组件实例 + */ + getComponent(componentType: C): ComponentInstance; + + /** + * 检查是否拥有组件 + */ + hasComponent(componentType: C): boolean; + + /** + * 获取所有组件 + */ + readonly components: ComponentTypeMap; +} + +/** + * 查询结果类型 + * 根据查询条件推断实体必定拥有的组件 + */ +export type QueryResult< + All extends readonly ComponentConstructor[] = [], + Any extends readonly ComponentConstructor[] = [], + None extends readonly ComponentConstructor[] = [] +> = { + /** + * 实体列表,确保拥有All中的所有组件 + */ + readonly entities: ReadonlyArray>; + + /** + * 实体数量 + */ + readonly length: number; + + /** + * 遍历实体 + */ + forEach(callback: (entity: EntityWithComponents, index: number) => void): void; + + /** + * 映射转换 + */ + map(callback: (entity: EntityWithComponents, index: number) => R): R[]; + + /** + * 过滤实体 + */ + filter(predicate: (entity: EntityWithComponents, index: number) => boolean): QueryResult; +}; + +/** + * System处理的实体类型 + * 根据Matcher推断System处理的实体类型 + */ +export type SystemEntityType = M extends { + getCondition(): { + all: infer All extends readonly ComponentConstructor[]; + }; +} + ? EntityWithComponents + : never; + +/** + * 组件字段类型提取 + * 提取组件中所有可序列化的字段 + */ +export type SerializableFields = { + [K in keyof T]: T[K] extends Function ? never : K; +}[keyof T]; + +/** + * 只读组件类型 + * 将组件的所有字段转为只读 + */ +export type ReadonlyComponent = { + readonly [K in keyof T]: T[K]; +}; + +/** + * 部分组件类型 + * 用于组件更新操作 + */ +export type PartialComponent = { + [K in SerializableFields]?: T[K]; +}; + +/** + * 组件类型约束 + * 确保类型参数是有效的组件 + */ +export type ValidComponent = T extends Component ? T : never; + +/** + * 组件数组约束 + * 确保数组中的所有元素都是组件构造函数 + */ +export type ValidComponentArray = T extends readonly ComponentConstructor[] + ? T + : never; + +/** + * 事件处理器类型 + * 提供类型安全的事件处理 + */ +export type TypedEventHandler = (data: T) => void | Promise; + +/** + * 系统生命周期钩子类型 + */ +export interface SystemLifecycleHooks { + /** + * 实体添加到系统时调用 + */ + onAdded?: (entity: EntityWithComponents) => void; + + /** + * 实体从系统移除时调用 + */ + onRemoved?: (entity: EntityWithComponents) => void; + + /** + * 系统初始化时调用 + */ + onInitialize?: () => void; + + /** + * 系统销毁时调用 + */ + onDestroy?: () => void; +} + +/** + * Fluent API构建器类型 + */ +export interface TypeSafeBuilder { + /** + * 完成构建 + */ + build(): T; +} + +/** + * 组件池类型 + */ +export interface ComponentPool { + /** + * 从池中获取组件实例 + */ + obtain(): T; + + /** + * 归还组件到池中 + */ + free(component: T): void; + + /** + * 清空池 + */ + clear(): void; + + /** + * 池中可用对象数量 + */ + readonly available: number; +} + +/** + * 实体查询条件类型 + */ +export interface TypedQueryCondition< + All extends readonly ComponentConstructor[] = [], + Any extends readonly ComponentConstructor[] = [], + None extends readonly ComponentConstructor[] = [] +> { + all: All; + any: Any; + none: None; + tag?: number; + name?: string; +} + +/** + * 组件类型守卫 + */ +export function isComponentType( + value: any +): value is ComponentConstructor { + return typeof value === 'function' && value.prototype instanceof Component; +} + +/** + * 类型安全的组件数组守卫 + */ +export function isComponentArray( + value: any[] +): value is ComponentConstructor[] { + return value.every(isComponentType); +} + +/** + * 提取组件类型名称(编译时) + */ +export type ComponentTypeName = T extends { + prototype: { constructor: { name: infer N } }; +} + ? N + : string; + +/** + * 多组件类型名称联合 + */ +export type ComponentTypeNames = { + [K in keyof T]: ComponentTypeName; +}[number]; + +/** + * 深度只读类型 + */ +export type DeepReadonly = { + readonly [K in keyof T]: T[K] extends object ? DeepReadonly : T[K]; +}; + +/** + * 深度可选类型 + */ +export type DeepPartial = { + [K in keyof T]?: T[K] extends object ? DeepPartial : T[K]; +}; + +/** + * 排除方法的类型 + */ +export type DataOnly = { + [K in keyof T as T[K] extends Function ? never : K]: T[K]; +}; + +/** + * 可序列化的组件数据 + */ +export type SerializableComponent = DeepPartial>; diff --git a/packages/core/src/Types/index.ts b/packages/core/src/Types/index.ts index 7e687e1f..2caf1821 100644 --- a/packages/core/src/Types/index.ts +++ b/packages/core/src/Types/index.ts @@ -2,6 +2,9 @@ * 框架核心类型定义 */ +// 导出TypeScript类型增强工具 +export * from './TypeHelpers'; + /** * 组件接口 * diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 95c93742..c77485a3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -27,6 +27,11 @@ export type { ILogger, LoggerConfig } from './Utils/Logger'; // ECS核心组件 export * from './ECS'; +// TypeScript类型增强API +export * from './ECS/TypedEntity'; +export * from './ECS/Systems/TypedEntitySystem'; +export * from './ECS/Core/Query/TypedQuery'; + // 事件系统 export { ECSEventType, EventPriority, EVENT_TYPES, EventTypeValidator } from './ECS/CoreEvents'; diff --git a/packages/core/tests/SceneQuery.test.ts b/packages/core/tests/SceneQuery.test.ts new file mode 100644 index 00000000..5f2ff952 --- /dev/null +++ b/packages/core/tests/SceneQuery.test.ts @@ -0,0 +1,145 @@ +/** + * Scene查询方法测试 + */ + +import { Component } from '../src/ECS/Component'; +import { Entity } from '../src/ECS/Entity'; +import { Scene } from '../src/ECS/Scene'; +import { Core } from '../src/Core'; +import { ECSComponent } from '../src/ECS/Decorators'; +import { EntitySystem } from '../src/ECS/Systems/EntitySystem'; + +@ECSComponent('Position') +class Position extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +@ECSComponent('Velocity') +class Velocity extends Component { + constructor(public dx: number = 0, public dy: number = 0) { + super(); + } +} + +@ECSComponent('Disabled') +class Disabled extends Component {} + +describe('Scene查询方法', () => { + let scene: Scene; + + beforeEach(() => { + Core.create({ debug: false, enableEntitySystems: true }); + scene = new Scene(); + scene.initialize(); + }); + + afterEach(() => { + scene.end(); + }); + + describe('基础查询方法', () => { + test('queryAll 查询拥有所有组件的实体', () => { + const e1 = scene.createEntity('E1'); + e1.addComponent(new Position(10, 20)); + e1.addComponent(new Velocity(1, 2)); + + const e2 = scene.createEntity('E2'); + e2.addComponent(new Position(30, 40)); + + const result = scene.queryAll(Position, Velocity); + + expect(result.entities).toHaveLength(1); + expect(result.entities[0]).toBe(e1); + }); + + test('queryAny 查询拥有任意组件的实体', () => { + const e1 = scene.createEntity('E1'); + e1.addComponent(new Position(10, 20)); + + const e2 = scene.createEntity('E2'); + e2.addComponent(new Velocity(1, 2)); + + const e3 = scene.createEntity('E3'); + e3.addComponent(new Disabled()); + + const result = scene.queryAny(Position, Velocity); + + expect(result.entities).toHaveLength(2); + }); + + test('queryNone 查询不包含指定组件的实体', () => { + const e1 = scene.createEntity('E1'); + e1.addComponent(new Position(10, 20)); + + const e2 = scene.createEntity('E2'); + e2.addComponent(new Position(30, 40)); + e2.addComponent(new Disabled()); + + const result = scene.queryNone(Disabled); + + expect(result.entities).toHaveLength(1); + expect(result.entities[0]).toBe(e1); + }); + }); + + describe('TypedQueryBuilder', () => { + test('scene.query() 创建类型安全的查询构建器', () => { + const e1 = scene.createEntity('E1'); + e1.addComponent(new Position(10, 20)); + e1.addComponent(new Velocity(1, 2)); + + const e2 = scene.createEntity('E2'); + e2.addComponent(new Position(30, 40)); + e2.addComponent(new Velocity(3, 4)); + e2.addComponent(new Disabled()); + + // 构建查询 + const query = scene.query() + .withAll(Position, Velocity) + .withNone(Disabled); + + const matcher = query.buildMatcher(); + + // 创建System使用这个matcher + class TestSystem extends EntitySystem { + public processedCount = 0; + + constructor() { + super(matcher); + } + + protected override process(entities: readonly Entity[]): void { + this.processedCount = entities.length; + } + } + + const system = new TestSystem(); + scene.addSystem(system); + scene.update(); + + // 应该只处理e1(e2被Disabled排除) + expect(system.processedCount).toBe(1); + }); + + test('TypedQueryBuilder 支持复杂查询', () => { + const e1 = scene.createEntity('E1'); + e1.addComponent(new Position(10, 20)); + e1.tag = 100; + + const e2 = scene.createEntity('E2'); + e2.addComponent(new Position(30, 40)); + e2.tag = 200; + + const query = scene.query() + .withAll(Position) + .withTag(100); + + const condition = query.getCondition(); + + expect(condition.all).toContain(Position as any); + expect(condition.tag).toBe(100); + }); + }); +}); diff --git a/packages/core/tests/TypeInference.test.ts b/packages/core/tests/TypeInference.test.ts new file mode 100644 index 00000000..7682d682 --- /dev/null +++ b/packages/core/tests/TypeInference.test.ts @@ -0,0 +1,205 @@ +/** + * TypeScript类型推断测试 + * + * 验证组件类型自动推断功能 + */ + +import { Component } from '../src/ECS/Component'; +import { Entity } from '../src/ECS/Entity'; +import { Scene } from '../src/ECS/Scene'; +import { Core } from '../src/Core'; +import { ECSComponent } from '../src/ECS/Decorators'; +import { requireComponent, tryGetComponent, getComponents } from '../src/ECS/TypedEntity'; + +// 测试组件 +@ECSComponent('Position') +class Position extends Component { + constructor(public x: number = 0, public y: number = 0) { + super(); + } +} + +@ECSComponent('Velocity') +class Velocity extends Component { + constructor(public dx: number = 0, public dy: number = 0) { + super(); + } +} + +@ECSComponent('Health') +class Health extends Component { + constructor(public value: number = 100, public maxValue: number = 100) { + super(); + } +} + +describe('TypeScript类型推断', () => { + let scene: Scene; + let entity: Entity; + + beforeEach(() => { + Core.create({ debug: false, enableEntitySystems: true }); + scene = new Scene(); + scene.initialize(); + entity = scene.createEntity('TestEntity'); + }); + + afterEach(() => { + scene.end(); + }); + + describe('Entity.getComponent 类型推断', () => { + test('getComponent 应该自动推断正确的返回类型', () => { + entity.addComponent(new Position(100, 200)); + + // 类型推断为 Position | null + const position = entity.getComponent(Position); + + // TypeScript应该知道position可能为null + expect(position).not.toBeNull(); + + // 在null检查后,TypeScript应该知道position是Position类型 + if (position) { + expect(position.x).toBe(100); + expect(position.y).toBe(200); + + // 这些操作应该有完整的类型提示 + position.x += 10; + position.y += 20; + + expect(position.x).toBe(110); + expect(position.y).toBe(220); + } + }); + + test('getComponent 返回null时类型安全', () => { + // 实体没有Velocity组件 + const velocity = entity.getComponent(Velocity); + + // 应该返回null + expect(velocity).toBeNull(); + }); + + test('多个不同类型组件的类型推断', () => { + entity.addComponent(new Position(10, 20)); + entity.addComponent(new Velocity(1, 2)); + entity.addComponent(new Health(100)); + + const pos = entity.getComponent(Position); + const vel = entity.getComponent(Velocity); + const health = entity.getComponent(Health); + + // 所有组件都应该被正确推断 + if (pos && vel && health) { + // Position类型的字段 + pos.x = 50; + pos.y = 60; + + // Velocity类型的字段 + vel.dx = 5; + vel.dy = 10; + + // Health类型的字段 + health.value = 80; + health.maxValue = 150; + + expect(pos.x).toBe(50); + expect(vel.dx).toBe(5); + expect(health.value).toBe(80); + } + }); + }); + + describe('Entity.createComponent 类型推断', () => { + test('createComponent 应该自动推断返回类型', () => { + // 应该推断为Position类型(非null) + const position = entity.createComponent(Position, 100, 200); + + expect(position).toBeInstanceOf(Position); + expect(position.x).toBe(100); + expect(position.y).toBe(200); + + // 应该有完整的类型提示 + position.x = 300; + expect(position.x).toBe(300); + }); + }); + + describe('Entity.hasComponent 类型守卫', () => { + test('hasComponent 可以用作类型守卫', () => { + entity.addComponent(new Position(10, 20)); + + if (entity.hasComponent(Position)) { + // 在这个作用域内,我们知道组件存在 + const pos = entity.getComponent(Position)!; + pos.x = 100; + expect(pos.x).toBe(100); + } + }); + }); + + describe('Entity.getOrCreateComponent 类型推断', () => { + test('getOrCreateComponent 应该自动推断返回类型', () => { + // 第一次调用:创建新组件 + const position1 = entity.getOrCreateComponent(Position, 50, 60); + expect(position1.x).toBe(50); + expect(position1.y).toBe(60); + + // 第二次调用:返回已存在的组件 + const position2 = entity.getOrCreateComponent(Position, 100, 200); + + // 应该是同一个组件 + expect(position2).toBe(position1); + expect(position2.x).toBe(50); // 值未改变 + }); + }); + + describe('TypedEntity工具函数类型推断', () => { + test('requireComponent 返回非空类型', () => { + entity.addComponent(new Position(100, 200)); + + // requireComponent 返回非null类型 + const position = requireComponent(entity, Position); + + // 不需要null检查 + expect(position.x).toBe(100); + position.x = 300; + expect(position.x).toBe(300); + }); + + test('tryGetComponent 返回可选类型', () => { + entity.addComponent(new Position(50, 50)); + + const position = tryGetComponent(entity, Position); + + // 应该返回组件 + expect(position).toBeDefined(); + if (position) { + expect(position.x).toBe(50); + } + + // 不存在的组件返回undefined + const velocity = tryGetComponent(entity, Velocity); + expect(velocity).toBeUndefined(); + }); + + test('getComponents 批量获取组件', () => { + entity.addComponent(new Position(10, 20)); + entity.addComponent(new Velocity(1, 2)); + entity.addComponent(new Health(100)); + + const [pos, vel, health] = getComponents(entity, Position, Velocity, Health); + + // 应该推断为数组类型 + expect(pos).not.toBeNull(); + expect(vel).not.toBeNull(); + expect(health).not.toBeNull(); + + if (pos && vel && health) { + expect(pos.x).toBe(10); + expect(vel.dx).toBe(1); + expect(health.value).toBe(100); + } + }); + }); +});