Merge pull request #105 from esengine/issue-94-响应式查询(Reactive_Query_System)/_Event-driven_Query
响应式查询
This commit is contained in:
@@ -6,6 +6,7 @@ import { createLogger } from '../../Utils/Logger';
|
||||
import { getComponentTypeName } from '../Decorators';
|
||||
import { Archetype, ArchetypeSystem } from './ArchetypeSystem';
|
||||
import { ComponentTypeManager } from "../Utils";
|
||||
import { ReactiveQuery, ReactiveQueryConfig } from './ReactiveQuery';
|
||||
|
||||
/**
|
||||
* 查询条件类型
|
||||
@@ -133,6 +134,7 @@ export class QuerySystem {
|
||||
public setEntities(entities: Entity[]): void {
|
||||
this.entities = entities;
|
||||
this.clearQueryCache();
|
||||
this.clearReactiveQueries();
|
||||
this.rebuildIndexes();
|
||||
}
|
||||
|
||||
@@ -152,6 +154,8 @@ export class QuerySystem {
|
||||
|
||||
this.archetypeSystem.addEntity(entity);
|
||||
|
||||
// 通知响应式查询
|
||||
this.notifyReactiveQueriesEntityAdded(entity);
|
||||
|
||||
// 只有在非延迟模式下才立即清理缓存
|
||||
if (!deferCacheClear) {
|
||||
@@ -235,14 +239,24 @@ export class QuerySystem {
|
||||
public removeEntity(entity: Entity): void {
|
||||
const index = this.entities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
const componentTypes: ComponentType[] = [];
|
||||
for (const component of entity.components) {
|
||||
componentTypes.push(component.constructor as ComponentType);
|
||||
}
|
||||
|
||||
this.entities.splice(index, 1);
|
||||
this.removeEntityFromIndexes(entity);
|
||||
|
||||
this.archetypeSystem.removeEntity(entity);
|
||||
|
||||
if (componentTypes.length > 0) {
|
||||
this.notifyReactiveQueriesEntityRemoved(entity, componentTypes);
|
||||
} else {
|
||||
this.notifyReactiveQueriesEntityRemovedFallback(entity);
|
||||
}
|
||||
|
||||
this.clearQueryCache();
|
||||
|
||||
// 更新版本号
|
||||
this._version++;
|
||||
}
|
||||
}
|
||||
@@ -270,6 +284,9 @@ export class QuerySystem {
|
||||
// 重新添加实体到索引(基于新的组件状态)
|
||||
this.addEntityToIndexes(entity);
|
||||
|
||||
// 通知响应式查询
|
||||
this.notifyReactiveQueriesEntityChanged(entity);
|
||||
|
||||
// 清理查询缓存,因为实体组件状态已改变
|
||||
this.clearQueryCache();
|
||||
|
||||
@@ -359,7 +376,7 @@ export class QuerySystem {
|
||||
* 查询包含所有指定组件的实体
|
||||
*
|
||||
* 返回同时包含所有指定组件类型的实体列表。
|
||||
* 系统会自动选择最高效的查询策略,包括索引查找和缓存机制。
|
||||
* 内部使用响应式查询作为智能缓存,自动跟踪实体变化,性能更优。
|
||||
*
|
||||
* @param componentTypes 要查询的组件类型列表
|
||||
* @returns 查询结果,包含匹配的实体和性能信息
|
||||
@@ -375,38 +392,20 @@ export class QuerySystem {
|
||||
const startTime = performance.now();
|
||||
this.queryStats.totalQueries++;
|
||||
|
||||
// 生成缓存键
|
||||
const cacheKey = this.generateCacheKey('all', componentTypes);
|
||||
// 使用内部响应式查询作为智能缓存
|
||||
const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.ALL, componentTypes);
|
||||
|
||||
// 检查缓存
|
||||
const cached = this.getFromCache(cacheKey);
|
||||
if (cached) {
|
||||
// 从响应式查询获取结果(永远是最新的)
|
||||
const entities = reactiveQuery.getEntities();
|
||||
|
||||
// 统计为缓存命中(响应式查询本质上是永不过期的智能缓存)
|
||||
this.queryStats.cacheHits++;
|
||||
return {
|
||||
entities: cached,
|
||||
count: cached.length,
|
||||
executionTime: performance.now() - startTime,
|
||||
fromCache: true
|
||||
};
|
||||
}
|
||||
|
||||
this.queryStats.archetypeHits++;
|
||||
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'AND');
|
||||
|
||||
const entities: Entity[] = [];
|
||||
for (const archetype of archetypeResult.archetypes) {
|
||||
for (const entity of archetype.entities) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
this.addToCache(cacheKey, entities);
|
||||
|
||||
return {
|
||||
entities,
|
||||
count: entities.length,
|
||||
executionTime: performance.now() - startTime,
|
||||
fromCache: false
|
||||
fromCache: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -438,7 +437,7 @@ export class QuerySystem {
|
||||
* 查询包含任意指定组件的实体
|
||||
*
|
||||
* 返回包含任意一个指定组件类型的实体列表。
|
||||
* 使用集合合并算法确保高效的查询性能。
|
||||
* 内部使用响应式查询作为智能缓存,自动跟踪实体变化,性能更优。
|
||||
*
|
||||
* @param componentTypes 要查询的组件类型列表
|
||||
* @returns 查询结果,包含匹配的实体和性能信息
|
||||
@@ -454,48 +453,28 @@ export class QuerySystem {
|
||||
const startTime = performance.now();
|
||||
this.queryStats.totalQueries++;
|
||||
|
||||
const cacheKey = this.generateCacheKey('any', componentTypes);
|
||||
// 使用内部响应式查询作为智能缓存
|
||||
const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.ANY, componentTypes);
|
||||
|
||||
// 检查缓存
|
||||
const cached = this.getFromCache(cacheKey);
|
||||
if (cached) {
|
||||
// 从响应式查询获取结果(永远是最新的)
|
||||
const entities = reactiveQuery.getEntities();
|
||||
|
||||
// 统计为缓存命中(响应式查询本质上是永不过期的智能缓存)
|
||||
this.queryStats.cacheHits++;
|
||||
|
||||
return {
|
||||
entities: cached,
|
||||
count: cached.length,
|
||||
entities,
|
||||
count: entities.length,
|
||||
executionTime: performance.now() - startTime,
|
||||
fromCache: true
|
||||
};
|
||||
}
|
||||
|
||||
this.queryStats.archetypeHits++;
|
||||
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'OR');
|
||||
|
||||
const entities = this.acquireResultArray();
|
||||
for (const archetype of archetypeResult.archetypes) {
|
||||
for (const entity of archetype.entities) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
const frozenEntities = [...entities];
|
||||
this.releaseResultArray(entities);
|
||||
|
||||
this.addToCache(cacheKey, frozenEntities);
|
||||
|
||||
return {
|
||||
entities: frozenEntities,
|
||||
count: frozenEntities.length,
|
||||
executionTime: performance.now() - startTime,
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询不包含任何指定组件的实体
|
||||
*
|
||||
* 返回不包含任何指定组件类型的实体列表。
|
||||
* 适用于排除特定类型实体的查询场景。
|
||||
* 内部使用响应式查询作为智能缓存,自动跟踪实体变化,性能更优。
|
||||
*
|
||||
* @param componentTypes 要排除的组件类型列表
|
||||
* @returns 查询结果,包含匹配的实体和性能信息
|
||||
@@ -511,32 +490,20 @@ export class QuerySystem {
|
||||
const startTime = performance.now();
|
||||
this.queryStats.totalQueries++;
|
||||
|
||||
const cacheKey = this.generateCacheKey('none', componentTypes);
|
||||
// 使用内部响应式查询作为智能缓存
|
||||
const reactiveQuery = this.getOrCreateReactiveQuery(QueryConditionType.NONE, componentTypes);
|
||||
|
||||
// 检查缓存
|
||||
const cached = this.getFromCache(cacheKey);
|
||||
if (cached) {
|
||||
// 从响应式查询获取结果(永远是最新的)
|
||||
const entities = reactiveQuery.getEntities();
|
||||
|
||||
// 统计为缓存命中(响应式查询本质上是永不过期的智能缓存)
|
||||
this.queryStats.cacheHits++;
|
||||
return {
|
||||
entities: cached,
|
||||
count: cached.length,
|
||||
executionTime: performance.now() - startTime,
|
||||
fromCache: true
|
||||
};
|
||||
}
|
||||
|
||||
const mask = this.createComponentMask(componentTypes);
|
||||
const entities = this.entities.filter(entity =>
|
||||
BitMask64Utils.hasNone(entity.componentMask, mask)
|
||||
);
|
||||
|
||||
this.addToCache(cacheKey, entities);
|
||||
|
||||
return {
|
||||
entities,
|
||||
count: entities.length,
|
||||
executionTime: performance.now() - startTime,
|
||||
fromCache: false
|
||||
fromCache: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -759,6 +726,20 @@ export class QuerySystem {
|
||||
this.componentMaskCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有响应式查询
|
||||
*
|
||||
* 销毁所有响应式查询实例并清理索引
|
||||
* 通常在setEntities时调用以确保缓存一致性
|
||||
*/
|
||||
private clearReactiveQueries(): void {
|
||||
for (const query of this._reactiveQueries.values()) {
|
||||
query.dispose();
|
||||
}
|
||||
this._reactiveQueries.clear();
|
||||
this._reactiveQueriesByComponent.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 高效的缓存键生成
|
||||
*/
|
||||
@@ -784,9 +765,109 @@ export class QuerySystem {
|
||||
* 清理查询缓存
|
||||
*
|
||||
* 用于外部调用清理缓存,通常在批量操作后使用。
|
||||
* 注意:此方法也会清理响应式查询缓存
|
||||
*/
|
||||
public clearCache(): void {
|
||||
this.clearQueryCache();
|
||||
this.clearReactiveQueries();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建响应式查询
|
||||
*
|
||||
* 响应式查询会自动跟踪实体/组件的变化,并通过事件通知订阅者。
|
||||
* 适合需要实时响应实体变化的场景(如UI更新、AI系统等)。
|
||||
*
|
||||
* @param componentTypes 查询的组件类型列表
|
||||
* @param config 可选的查询配置
|
||||
* @returns 响应式查询实例
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const query = querySystem.createReactiveQuery([Position, Velocity], {
|
||||
* enableBatchMode: true,
|
||||
* batchDelay: 16
|
||||
* });
|
||||
*
|
||||
* query.subscribe((change) => {
|
||||
* if (change.type === ReactiveQueryChangeType.ADDED) {
|
||||
* console.log('新实体:', change.entity);
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
public createReactiveQuery(
|
||||
componentTypes: ComponentType[],
|
||||
config?: ReactiveQueryConfig
|
||||
): ReactiveQuery {
|
||||
if (!componentTypes || componentTypes.length === 0) {
|
||||
throw new Error('组件类型列表不能为空');
|
||||
}
|
||||
|
||||
const mask = this.createComponentMask(componentTypes);
|
||||
const condition: QueryCondition = {
|
||||
type: QueryConditionType.ALL,
|
||||
componentTypes,
|
||||
mask
|
||||
};
|
||||
|
||||
const query = new ReactiveQuery(condition, config);
|
||||
|
||||
const initialEntities = this.executeTraditionalQuery(
|
||||
QueryConditionType.ALL,
|
||||
componentTypes
|
||||
);
|
||||
query.initializeWith(initialEntities);
|
||||
|
||||
const cacheKey = this.generateCacheKey('all', componentTypes);
|
||||
this._reactiveQueries.set(cacheKey, query);
|
||||
|
||||
for (const type of componentTypes) {
|
||||
let queries = this._reactiveQueriesByComponent.get(type);
|
||||
if (!queries) {
|
||||
queries = new Set();
|
||||
this._reactiveQueriesByComponent.set(type, queries);
|
||||
}
|
||||
queries.add(query);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁响应式查询
|
||||
*
|
||||
* 清理查询占用的资源,包括监听器和实体引用。
|
||||
* 销毁后的查询不应再被使用。
|
||||
*
|
||||
* @param query 要销毁的响应式查询
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const query = querySystem.createReactiveQuery([Position, Velocity]);
|
||||
* // ... 使用查询
|
||||
* querySystem.destroyReactiveQuery(query);
|
||||
* ```
|
||||
*/
|
||||
public destroyReactiveQuery(query: ReactiveQuery): void {
|
||||
if (!query) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cacheKey = query.id;
|
||||
this._reactiveQueries.delete(cacheKey);
|
||||
|
||||
for (const type of query.condition.componentTypes) {
|
||||
const queries = this._reactiveQueriesByComponent.get(type);
|
||||
if (queries) {
|
||||
queries.delete(query);
|
||||
if (queries.size === 0) {
|
||||
this._reactiveQueriesByComponent.delete(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -882,7 +963,7 @@ export class QuerySystem {
|
||||
}))
|
||||
},
|
||||
cacheStats: {
|
||||
size: this.queryCache.size,
|
||||
size: this._reactiveQueries.size,
|
||||
hitRate: this.queryStats.totalQueries > 0 ?
|
||||
(this.queryStats.cacheHits / this.queryStats.totalQueries * 100).toFixed(2) + '%' : '0%'
|
||||
}
|
||||
@@ -897,6 +978,223 @@ export class QuerySystem {
|
||||
public getEntityArchetype(entity: Entity): Archetype | undefined {
|
||||
return this.archetypeSystem.getEntityArchetype(entity);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 响应式查询支持(内部智能缓存)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 响应式查询集合(内部使用,作为智能缓存)
|
||||
* 传统查询API(queryAll/queryAny/queryNone)内部自动使用响应式查询优化性能
|
||||
*/
|
||||
private _reactiveQueries: Map<string, ReactiveQuery> = new Map();
|
||||
|
||||
/**
|
||||
* 按组件类型索引的响应式查询
|
||||
* 用于快速定位哪些查询关心某个组件类型
|
||||
*/
|
||||
private _reactiveQueriesByComponent: Map<ComponentType, Set<ReactiveQuery>> = new Map();
|
||||
|
||||
/**
|
||||
* 获取或创建内部响应式查询(作为智能缓存)
|
||||
*
|
||||
* @param queryType 查询类型
|
||||
* @param componentTypes 组件类型列表
|
||||
* @returns 响应式查询实例
|
||||
*/
|
||||
private getOrCreateReactiveQuery(
|
||||
queryType: QueryConditionType,
|
||||
componentTypes: ComponentType[]
|
||||
): ReactiveQuery {
|
||||
// 生成缓存键(与传统缓存键格式一致)
|
||||
const cacheKey = this.generateCacheKey(queryType, componentTypes);
|
||||
|
||||
// 检查是否已存在响应式查询
|
||||
let reactiveQuery = this._reactiveQueries.get(cacheKey);
|
||||
|
||||
if (!reactiveQuery) {
|
||||
// 创建查询条件
|
||||
const mask = this.createComponentMask(componentTypes);
|
||||
const condition: QueryCondition = {
|
||||
type: queryType,
|
||||
componentTypes,
|
||||
mask
|
||||
};
|
||||
|
||||
// 创建响应式查询(禁用批量模式,保持实时性)
|
||||
reactiveQuery = new ReactiveQuery(condition, {
|
||||
enableBatchMode: false,
|
||||
debug: false
|
||||
});
|
||||
|
||||
// 初始化查询结果(使用传统方式获取初始数据)
|
||||
const initialEntities = this.executeTraditionalQuery(queryType, componentTypes);
|
||||
reactiveQuery.initializeWith(initialEntities);
|
||||
|
||||
// 注册响应式查询
|
||||
this._reactiveQueries.set(cacheKey, reactiveQuery);
|
||||
|
||||
// 为每个组件类型注册索引
|
||||
for (const type of componentTypes) {
|
||||
let queries = this._reactiveQueriesByComponent.get(type);
|
||||
if (!queries) {
|
||||
queries = new Set();
|
||||
this._reactiveQueriesByComponent.set(type, queries);
|
||||
}
|
||||
queries.add(reactiveQuery);
|
||||
}
|
||||
|
||||
this._logger.debug(`创建内部响应式查询缓存: ${cacheKey}`);
|
||||
}
|
||||
|
||||
return reactiveQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行传统查询(内部使用,用于响应式查询初始化)
|
||||
*
|
||||
* @param queryType 查询类型
|
||||
* @param componentTypes 组件类型列表
|
||||
* @returns 匹配的实体列表
|
||||
*/
|
||||
private executeTraditionalQuery(
|
||||
queryType: QueryConditionType,
|
||||
componentTypes: ComponentType[]
|
||||
): Entity[] {
|
||||
switch (queryType) {
|
||||
case QueryConditionType.ALL: {
|
||||
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'AND');
|
||||
const entities: Entity[] = [];
|
||||
for (const archetype of archetypeResult.archetypes) {
|
||||
for (const entity of archetype.entities) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
case QueryConditionType.ANY: {
|
||||
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'OR');
|
||||
const entities: Entity[] = [];
|
||||
for (const archetype of archetypeResult.archetypes) {
|
||||
for (const entity of archetype.entities) {
|
||||
entities.push(entity);
|
||||
}
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
case QueryConditionType.NONE: {
|
||||
const mask = this.createComponentMask(componentTypes);
|
||||
return this.entities.filter(entity =>
|
||||
BitMask64Utils.hasNone(entity.componentMask, mask)
|
||||
);
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有响应式查询实体已添加
|
||||
*
|
||||
* 使用组件类型索引,只通知关心该实体组件的查询
|
||||
*
|
||||
* @param entity 添加的实体
|
||||
*/
|
||||
private notifyReactiveQueriesEntityAdded(entity: Entity): void {
|
||||
if (this._reactiveQueries.size === 0) return;
|
||||
|
||||
const notified = new Set<ReactiveQuery>();
|
||||
|
||||
for (const component of entity.components) {
|
||||
const componentType = component.constructor as ComponentType;
|
||||
const queries = this._reactiveQueriesByComponent.get(componentType);
|
||||
|
||||
if (queries) {
|
||||
for (const query of queries) {
|
||||
if (!notified.has(query)) {
|
||||
query.notifyEntityAdded(entity);
|
||||
notified.add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知响应式查询实体已移除
|
||||
*
|
||||
* 使用组件类型索引,只通知关心该实体组件的查询
|
||||
*
|
||||
* @param entity 移除的实体
|
||||
* @param componentTypes 实体移除前的组件类型列表
|
||||
*/
|
||||
private notifyReactiveQueriesEntityRemoved(entity: Entity, componentTypes: ComponentType[]): void {
|
||||
if (this._reactiveQueries.size === 0) return;
|
||||
|
||||
const notified = new Set<ReactiveQuery>();
|
||||
|
||||
for (const componentType of componentTypes) {
|
||||
const queries = this._reactiveQueriesByComponent.get(componentType);
|
||||
if (queries) {
|
||||
for (const query of queries) {
|
||||
if (!notified.has(query)) {
|
||||
query.notifyEntityRemoved(entity);
|
||||
notified.add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知响应式查询实体已移除(后备方案)
|
||||
*
|
||||
* 当实体已经清空组件时使用,通知所有查询
|
||||
*
|
||||
* @param entity 移除的实体
|
||||
*/
|
||||
private notifyReactiveQueriesEntityRemovedFallback(entity: Entity): void {
|
||||
if (this._reactiveQueries.size === 0) return;
|
||||
|
||||
for (const query of this._reactiveQueries.values()) {
|
||||
query.notifyEntityRemoved(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知响应式查询实体已变化
|
||||
*
|
||||
* 使用混合策略:
|
||||
* 1. 通知关心当前组件的查询
|
||||
* 2. 通知当前包含该实体的查询(处理组件移除情况)
|
||||
*
|
||||
* @param entity 变化的实体
|
||||
*/
|
||||
private notifyReactiveQueriesEntityChanged(entity: Entity): void {
|
||||
if (this._reactiveQueries.size === 0) return;
|
||||
|
||||
const notified = new Set<ReactiveQuery>();
|
||||
|
||||
for (const component of entity.components) {
|
||||
const componentType = component.constructor as ComponentType;
|
||||
const queries = this._reactiveQueriesByComponent.get(componentType);
|
||||
if (queries) {
|
||||
for (const query of queries) {
|
||||
if (!notified.has(query)) {
|
||||
query.notifyEntityChanged(entity);
|
||||
notified.add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const query of this._reactiveQueries.values()) {
|
||||
if (!notified.has(query) && query.getEntities().includes(entity)) {
|
||||
query.notifyEntityChanged(entity);
|
||||
notified.add(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
475
packages/core/src/ECS/Core/ReactiveQuery.ts
Normal file
475
packages/core/src/ECS/Core/ReactiveQuery.ts
Normal file
@@ -0,0 +1,475 @@
|
||||
import { Entity } from '../Entity';
|
||||
import { QueryCondition, QueryConditionType } from './QuerySystem';
|
||||
import { BitMask64Utils } from '../Utils/BigIntCompatibility';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
|
||||
const logger = createLogger('ReactiveQuery');
|
||||
|
||||
/**
|
||||
* 响应式查询变化类型
|
||||
*/
|
||||
export enum ReactiveQueryChangeType {
|
||||
/** 实体添加到查询结果 */
|
||||
ADDED = 'added',
|
||||
/** 实体从查询结果移除 */
|
||||
REMOVED = 'removed',
|
||||
/** 查询结果批量更新 */
|
||||
BATCH_UPDATE = 'batch_update'
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应式查询变化事件
|
||||
*/
|
||||
export interface ReactiveQueryChange {
|
||||
/** 变化类型 */
|
||||
type: ReactiveQueryChangeType;
|
||||
/** 变化的实体 */
|
||||
entity?: Entity;
|
||||
/** 批量变化的实体 */
|
||||
entities?: readonly Entity[];
|
||||
/** 新增的实体列表(仅batch_update时有效) */
|
||||
added?: readonly Entity[];
|
||||
/** 移除的实体列表(仅batch_update时有效) */
|
||||
removed?: readonly Entity[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应式查询监听器
|
||||
*/
|
||||
export type ReactiveQueryListener = (change: ReactiveQueryChange) => void;
|
||||
|
||||
/**
|
||||
* 响应式查询配置
|
||||
*/
|
||||
export interface ReactiveQueryConfig {
|
||||
/** 是否启用批量模式(减少通知频率) */
|
||||
enableBatchMode?: boolean;
|
||||
/** 批量模式的延迟时间(毫秒) */
|
||||
batchDelay?: number;
|
||||
/** 调试模式 */
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应式查询类
|
||||
*
|
||||
* 提供基于事件驱动的实体查询机制,只在实体/组件真正变化时触发通知。
|
||||
*
|
||||
* 核心特性:
|
||||
* - Event-driven: 基于事件的增量更新
|
||||
* - 精确通知: 只通知真正匹配的变化
|
||||
* - 性能优化: 避免每帧重复查询
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 创建响应式查询
|
||||
* const query = new ReactiveQuery(querySystem, {
|
||||
* type: QueryConditionType.ALL,
|
||||
* componentTypes: [Position, Velocity],
|
||||
* mask: createMask([Position, Velocity])
|
||||
* });
|
||||
*
|
||||
* // 订阅变化
|
||||
* query.subscribe((change) => {
|
||||
* if (change.type === ReactiveQueryChangeType.ADDED) {
|
||||
* console.log('新实体:', change.entity);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // 获取当前结果
|
||||
* const entities = query.getEntities();
|
||||
* ```
|
||||
*/
|
||||
export class ReactiveQuery {
|
||||
/** 当前查询结果 */
|
||||
private _entities: Entity[] = [];
|
||||
|
||||
/** 实体ID集合,用于快速查找 */
|
||||
private _entityIdSet: Set<number> = new Set();
|
||||
|
||||
/** 查询条件 */
|
||||
private readonly _condition: QueryCondition;
|
||||
|
||||
/** 监听器列表 */
|
||||
private _listeners: ReactiveQueryListener[] = [];
|
||||
|
||||
/** 配置 */
|
||||
private readonly _config: ReactiveQueryConfig;
|
||||
|
||||
/** 批量变化缓存 */
|
||||
private _batchChanges: {
|
||||
added: Entity[];
|
||||
removed: Entity[];
|
||||
timer: ReturnType<typeof setTimeout> | null;
|
||||
};
|
||||
|
||||
/** 查询ID(用于调试) */
|
||||
private readonly _id: string;
|
||||
|
||||
/** 是否已激活 */
|
||||
private _active: boolean = true;
|
||||
|
||||
constructor(condition: QueryCondition, config: ReactiveQueryConfig = {}) {
|
||||
this._condition = condition;
|
||||
this._config = {
|
||||
enableBatchMode: config.enableBatchMode ?? true,
|
||||
batchDelay: config.batchDelay ?? 16, // 默认一帧
|
||||
debug: config.debug ?? false
|
||||
};
|
||||
|
||||
this._id = this.generateQueryId();
|
||||
|
||||
this._batchChanges = {
|
||||
added: [],
|
||||
removed: [],
|
||||
timer: null
|
||||
};
|
||||
|
||||
if (this._config.debug) {
|
||||
logger.debug(`创建ReactiveQuery: ${this._id}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成查询ID
|
||||
*/
|
||||
private generateQueryId(): string {
|
||||
const typeStr = this._condition.type;
|
||||
const componentsStr = this._condition.componentTypes
|
||||
.map(t => t.name)
|
||||
.sort()
|
||||
.join(',');
|
||||
return `${typeStr}:${componentsStr}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅查询变化
|
||||
*
|
||||
* @param listener 监听器函数
|
||||
* @returns 取消订阅的函数
|
||||
*/
|
||||
public subscribe(listener: ReactiveQueryListener): () => void {
|
||||
if (!this._active) {
|
||||
throw new Error(`Cannot subscribe to disposed ReactiveQuery ${this._id}`);
|
||||
}
|
||||
|
||||
if (typeof listener !== 'function') {
|
||||
throw new TypeError('Listener must be a function');
|
||||
}
|
||||
|
||||
this._listeners.push(listener);
|
||||
|
||||
if (this._config.debug) {
|
||||
logger.debug(`订阅ReactiveQuery: ${this._id}, 监听器数量: ${this._listeners.length}`);
|
||||
}
|
||||
|
||||
return () => {
|
||||
const index = this._listeners.indexOf(listener);
|
||||
if (index !== -1) {
|
||||
this._listeners.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有订阅
|
||||
*/
|
||||
public unsubscribeAll(): void {
|
||||
this._listeners.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前查询结果
|
||||
*/
|
||||
public getEntities(): readonly Entity[] {
|
||||
return this._entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询结果数量
|
||||
*/
|
||||
public get count(): number {
|
||||
return this._entities.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查实体是否匹配查询条件
|
||||
*
|
||||
* @param entity 要检查的实体
|
||||
* @returns 是否匹配
|
||||
*/
|
||||
public matches(entity: Entity): boolean {
|
||||
const entityMask = entity.componentMask;
|
||||
|
||||
switch (this._condition.type) {
|
||||
case QueryConditionType.ALL:
|
||||
return BitMask64Utils.hasAll(entityMask, this._condition.mask);
|
||||
case QueryConditionType.ANY:
|
||||
return BitMask64Utils.hasAny(entityMask, this._condition.mask);
|
||||
case QueryConditionType.NONE:
|
||||
return BitMask64Utils.hasNone(entityMask, this._condition.mask);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知实体添加
|
||||
*
|
||||
* 当Scene中添加实体时调用
|
||||
*
|
||||
* @param entity 添加的实体
|
||||
*/
|
||||
public notifyEntityAdded(entity: Entity): void {
|
||||
if (!this._active) return;
|
||||
|
||||
// 检查实体是否匹配查询条件
|
||||
if (!this.matches(entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
if (this._entityIdSet.has(entity.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到结果集
|
||||
this._entities.push(entity);
|
||||
this._entityIdSet.add(entity.id);
|
||||
|
||||
// 通知监听器
|
||||
if (this._config.enableBatchMode) {
|
||||
this.addToBatch('added', entity);
|
||||
} else {
|
||||
this.notifyListeners({
|
||||
type: ReactiveQueryChangeType.ADDED,
|
||||
entity
|
||||
});
|
||||
}
|
||||
|
||||
if (this._config.debug) {
|
||||
logger.debug(`ReactiveQuery ${this._id}: 实体添加 ${entity.name}(${entity.id})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知实体移除
|
||||
*
|
||||
* 当Scene中移除实体时调用
|
||||
*
|
||||
* @param entity 移除的实体
|
||||
*/
|
||||
public notifyEntityRemoved(entity: Entity): void {
|
||||
if (!this._active) return;
|
||||
|
||||
// 检查是否在结果集中
|
||||
if (!this._entityIdSet.has(entity.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从结果集移除
|
||||
const index = this._entities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
this._entities.splice(index, 1);
|
||||
}
|
||||
this._entityIdSet.delete(entity.id);
|
||||
|
||||
// 通知监听器
|
||||
if (this._config.enableBatchMode) {
|
||||
this.addToBatch('removed', entity);
|
||||
} else {
|
||||
this.notifyListeners({
|
||||
type: ReactiveQueryChangeType.REMOVED,
|
||||
entity
|
||||
});
|
||||
}
|
||||
|
||||
if (this._config.debug) {
|
||||
logger.debug(`ReactiveQuery ${this._id}: 实体移除 ${entity.name}(${entity.id})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知实体组件变化
|
||||
*
|
||||
* 当实体的组件发生变化时调用
|
||||
*
|
||||
* @param entity 变化的实体
|
||||
*/
|
||||
public notifyEntityChanged(entity: Entity): void {
|
||||
if (!this._active) return;
|
||||
|
||||
const wasMatching = this._entityIdSet.has(entity.id);
|
||||
const isMatching = this.matches(entity);
|
||||
|
||||
if (wasMatching && !isMatching) {
|
||||
// 实体不再匹配,从结果集移除
|
||||
this.notifyEntityRemoved(entity);
|
||||
} else if (!wasMatching && isMatching) {
|
||||
// 实体现在匹配,添加到结果集
|
||||
this.notifyEntityAdded(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量初始化查询结果
|
||||
*
|
||||
* @param entities 初始实体列表
|
||||
*/
|
||||
public initializeWith(entities: readonly Entity[]): void {
|
||||
// 清空现有结果
|
||||
this._entities.length = 0;
|
||||
this._entityIdSet.clear();
|
||||
|
||||
// 筛选匹配的实体
|
||||
for (const entity of entities) {
|
||||
if (this.matches(entity)) {
|
||||
this._entities.push(entity);
|
||||
this._entityIdSet.add(entity.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._config.debug) {
|
||||
logger.debug(`ReactiveQuery ${this._id}: 初始化 ${this._entities.length} 个实体`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到批量变化缓存
|
||||
*/
|
||||
private addToBatch(type: 'added' | 'removed', entity: Entity): void {
|
||||
if (type === 'added') {
|
||||
this._batchChanges.added.push(entity);
|
||||
} else {
|
||||
this._batchChanges.removed.push(entity);
|
||||
}
|
||||
|
||||
// 启动批量通知定时器
|
||||
if (this._batchChanges.timer === null) {
|
||||
this._batchChanges.timer = setTimeout(() => {
|
||||
this.flushBatchChanges();
|
||||
}, this._config.batchDelay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新批量变化
|
||||
*/
|
||||
private flushBatchChanges(): void {
|
||||
if (this._batchChanges.added.length === 0 && this._batchChanges.removed.length === 0) {
|
||||
this._batchChanges.timer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const added = [...this._batchChanges.added];
|
||||
const removed = [...this._batchChanges.removed];
|
||||
|
||||
// 清空缓存
|
||||
this._batchChanges.added.length = 0;
|
||||
this._batchChanges.removed.length = 0;
|
||||
this._batchChanges.timer = null;
|
||||
|
||||
// 通知监听器
|
||||
this.notifyListeners({
|
||||
type: ReactiveQueryChangeType.BATCH_UPDATE,
|
||||
added,
|
||||
removed,
|
||||
entities: this._entities
|
||||
});
|
||||
|
||||
if (this._config.debug) {
|
||||
logger.debug(`ReactiveQuery ${this._id}: 批量更新 +${added.length} -${removed.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听器
|
||||
*/
|
||||
private notifyListeners(change: ReactiveQueryChange): void {
|
||||
const listeners = [...this._listeners];
|
||||
|
||||
for (const listener of listeners) {
|
||||
try {
|
||||
listener(change);
|
||||
} catch (error) {
|
||||
logger.error(`ReactiveQuery ${this._id}: 监听器执行出错`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停响应式查询
|
||||
*
|
||||
* 暂停后不再响应实体变化,但可以继续获取当前结果
|
||||
*/
|
||||
public pause(): void {
|
||||
this._active = false;
|
||||
|
||||
// 清空批量变化缓存
|
||||
if (this._batchChanges.timer !== null) {
|
||||
clearTimeout(this._batchChanges.timer);
|
||||
this._batchChanges.timer = null;
|
||||
}
|
||||
this._batchChanges.added.length = 0;
|
||||
this._batchChanges.removed.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复响应式查询
|
||||
*/
|
||||
public resume(): void {
|
||||
this._active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁响应式查询
|
||||
*
|
||||
* 释放所有资源,清空监听器和结果集
|
||||
*/
|
||||
public dispose(): void {
|
||||
if (this._batchChanges.timer !== null) {
|
||||
clearTimeout(this._batchChanges.timer);
|
||||
this._batchChanges.timer = null;
|
||||
}
|
||||
|
||||
this._batchChanges.added.length = 0;
|
||||
this._batchChanges.removed.length = 0;
|
||||
|
||||
this._active = false;
|
||||
this.unsubscribeAll();
|
||||
this._entities.length = 0;
|
||||
this._entityIdSet.clear();
|
||||
|
||||
if (this._config.debug) {
|
||||
logger.debug(`ReactiveQuery ${this._id}: 已销毁`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询条件
|
||||
*/
|
||||
public get condition(): QueryCondition {
|
||||
return this._condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询ID
|
||||
*/
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否激活
|
||||
*/
|
||||
public get active(): boolean {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取监听器数量
|
||||
*/
|
||||
public get listenerCount(): number {
|
||||
return this._listeners.length;
|
||||
}
|
||||
}
|
||||
@@ -547,7 +547,9 @@ export class Scene implements IScene {
|
||||
constructor = systemTypeOrInstance;
|
||||
|
||||
if (this._services.isRegistered(constructor)) {
|
||||
return this._services.resolve(constructor) as T;
|
||||
const existingSystem = this._services.resolve(constructor) as T;
|
||||
this.logger.debug(`System ${constructor.name} already registered, returning existing instance`);
|
||||
return existingSystem;
|
||||
}
|
||||
|
||||
if (isInjectable(constructor)) {
|
||||
@@ -560,7 +562,17 @@ export class Scene implements IScene {
|
||||
constructor = system.constructor;
|
||||
|
||||
if (this._services.isRegistered(constructor)) {
|
||||
const existingSystem = this._services.resolve(constructor);
|
||||
if (existingSystem === system) {
|
||||
this.logger.debug(`System ${constructor.name} instance already registered, returning it`);
|
||||
return system;
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`Attempting to register a different instance of ${constructor.name}, ` +
|
||||
`but type is already registered. Returning existing instance.`
|
||||
);
|
||||
return existingSystem as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,6 +594,8 @@ export class Scene implements IScene {
|
||||
|
||||
system.initialize();
|
||||
|
||||
this.logger.debug(`System ${constructor.name} registered and initialized`);
|
||||
|
||||
return system;
|
||||
}
|
||||
|
||||
|
||||
@@ -555,6 +555,7 @@ export abstract class EntitySystem<
|
||||
try {
|
||||
this.onBegin();
|
||||
// 查询实体并存储到帧缓存中
|
||||
// 响应式查询会自动维护最新的实体列表,updateEntityTracking会在检测到变化时invalidate
|
||||
this._entityCache.frame = this.queryEntities();
|
||||
entityCount = this._entityCache.frame.length;
|
||||
|
||||
|
||||
@@ -16,3 +16,5 @@ export * from './Core/StorageDecorators';
|
||||
export * from './Serialization';
|
||||
export { ReferenceTracker, getSceneByEntityId } from './Core/ReferenceTracker';
|
||||
export type { EntityRefRecord } from './Core/ReferenceTracker';
|
||||
export { ReactiveQuery, ReactiveQueryChangeType } from './Core/ReactiveQuery';
|
||||
export type { ReactiveQueryChange, ReactiveQueryListener, ReactiveQueryConfig } from './Core/ReactiveQuery';
|
||||
94
packages/core/tests/ECS/Core/MinimalSystemInit.test.ts
Normal file
94
packages/core/tests/ECS/Core/MinimalSystemInit.test.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||||
|
||||
// 简单的测试组件
|
||||
class HealthComponent extends Component {
|
||||
public health: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [health = 100] = args as [number?];
|
||||
this.health = health;
|
||||
}
|
||||
}
|
||||
|
||||
// 简单的测试系统
|
||||
class HealthSystem extends EntitySystem {
|
||||
public onAddedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(HealthComponent));
|
||||
}
|
||||
|
||||
protected override onAdded(entity: Entity): void {
|
||||
console.log('[HealthSystem] onAdded called:', { id: entity.id, name: entity.name });
|
||||
this.onAddedEntities.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
describe('MinimalSystemInit - 最小化系统初始化测试', () => {
|
||||
let scene: Scene;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
scene = new Scene();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (scene) {
|
||||
scene.end();
|
||||
}
|
||||
});
|
||||
|
||||
test('先创建实体和组件,再添加系统 - 应该触发onAdded', () => {
|
||||
console.log('\\n=== Test 1: 先创建实体再添加系统 ===');
|
||||
|
||||
// 1. 创建实体并添加组件
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
|
||||
console.log('[Test] Entity created with HealthComponent');
|
||||
console.log('[Test] ComponentTypeManager registered types:', ComponentTypeManager.instance.registeredTypeCount);
|
||||
|
||||
// 2. 验证QuerySystem能查询到实体
|
||||
const queryResult = scene.querySystem.queryAll(HealthComponent);
|
||||
console.log('[Test] QuerySystem result:', { count: queryResult.count });
|
||||
|
||||
// 3. 添加系统
|
||||
const system = new HealthSystem();
|
||||
console.log('[Test] Adding system to scene...');
|
||||
scene.addEntityProcessor(system);
|
||||
|
||||
console.log('[Test] System added, onAddedEntities.length =', system.onAddedEntities.length);
|
||||
|
||||
// 4. 验证
|
||||
expect(system.onAddedEntities).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('先添加系统,再创建实体和组件 - 应该在update时触发onAdded', () => {
|
||||
console.log('\\n=== Test 2: 先添加系统再创建实体 ===');
|
||||
|
||||
// 1. 先添加系统
|
||||
const system = new HealthSystem();
|
||||
scene.addEntityProcessor(system);
|
||||
console.log('[Test] System added, onAddedEntities.length =', system.onAddedEntities.length);
|
||||
|
||||
// 2. 创建实体并添加组件
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
console.log('[Test] Entity created with HealthComponent');
|
||||
|
||||
// 3. 调用update触发系统查询
|
||||
console.log('[Test] Calling scene.update()...');
|
||||
scene.update();
|
||||
|
||||
console.log('[Test] After update, onAddedEntities.length =', system.onAddedEntities.length);
|
||||
|
||||
// 4. 验证
|
||||
expect(system.onAddedEntities).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
160
packages/core/tests/ECS/Core/MultiSystemInit.test.ts
Normal file
160
packages/core/tests/ECS/Core/MultiSystemInit.test.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||||
|
||||
// 测试组件
|
||||
class PositionComponent extends Component {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number;
|
||||
public vy: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public health: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [health = 100] = args as [number?];
|
||||
this.health = health;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试系统
|
||||
class MovementSystem extends EntitySystem {
|
||||
public onAddedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||
}
|
||||
|
||||
protected override onAdded(entity: Entity): void {
|
||||
console.log('[MovementSystem] onAdded:', { id: entity.id, name: entity.name });
|
||||
this.onAddedEntities.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
class HealthSystem extends EntitySystem {
|
||||
public onAddedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(HealthComponent));
|
||||
}
|
||||
|
||||
protected override onAdded(entity: Entity): void {
|
||||
console.log('[HealthSystem] onAdded:', { id: entity.id, name: entity.name });
|
||||
this.onAddedEntities.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
describe('MultiSystemInit - 多系统初始化测试', () => {
|
||||
let scene: Scene;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
scene = new Scene();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (scene) {
|
||||
scene.end();
|
||||
}
|
||||
});
|
||||
|
||||
test('多个系统同时响应同一实体 - 复现失败场景', () => {
|
||||
console.log('\\n=== Test: 多个系统同时响应同一实体 ===');
|
||||
|
||||
// 1. 创建实体并添加所有组件
|
||||
const entity = scene.createEntity('Entity');
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
|
||||
console.log('[Test] Entity created with Position, Velocity, Health');
|
||||
console.log('[Test] ComponentTypeManager registered types:', ComponentTypeManager.instance.registeredTypeCount);
|
||||
|
||||
// 2. 验证QuerySystem能查询到实体
|
||||
const movementQuery = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
const healthQuery = scene.querySystem.queryAll(HealthComponent);
|
||||
console.log('[Test] MovementQuery result:', { count: movementQuery.count });
|
||||
console.log('[Test] HealthQuery result:', { count: healthQuery.count });
|
||||
|
||||
// 3. 添加两个系统
|
||||
console.log('[Test] Adding MovementSystem...');
|
||||
const movementSystem = new MovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
console.log('[Test] MovementSystem added, onAddedEntities.length =', movementSystem.onAddedEntities.length);
|
||||
|
||||
console.log('[Test] Adding HealthSystem...');
|
||||
const healthSystem = new HealthSystem();
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
console.log('[Test] HealthSystem added, onAddedEntities.length =', healthSystem.onAddedEntities.length);
|
||||
|
||||
// 4. 验证
|
||||
console.log('[Test] Final check:');
|
||||
console.log(' MovementSystem.onAddedEntities.length =', movementSystem.onAddedEntities.length);
|
||||
console.log(' HealthSystem.onAddedEntities.length =', healthSystem.onAddedEntities.length);
|
||||
|
||||
expect(movementSystem.onAddedEntities).toHaveLength(1);
|
||||
expect(healthSystem.onAddedEntities).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('不同系统匹配不同实体 - 复现失败场景', () => {
|
||||
console.log('\\n=== Test: 不同系统匹配不同实体 ===');
|
||||
|
||||
// 1. 创建两个实体
|
||||
const movingEntity = scene.createEntity('Moving');
|
||||
movingEntity.addComponent(new PositionComponent(0, 0));
|
||||
movingEntity.addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
const healthEntity = scene.createEntity('Health');
|
||||
healthEntity.addComponent(new HealthComponent(100));
|
||||
|
||||
console.log('[Test] Two entities created');
|
||||
|
||||
// 2. 验证QuerySystem
|
||||
const movementQuery = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
const healthQuery = scene.querySystem.queryAll(HealthComponent);
|
||||
console.log('[Test] MovementQuery result:', { count: movementQuery.count });
|
||||
console.log('[Test] HealthQuery result:', { count: healthQuery.count });
|
||||
|
||||
// 3. 添加系统
|
||||
console.log('[Test] Adding systems...');
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
console.log('[Test] Systems added');
|
||||
console.log(' MovementSystem.onAddedEntities.length =', movementSystem.onAddedEntities.length);
|
||||
console.log(' HealthSystem.onAddedEntities.length =', healthSystem.onAddedEntities.length);
|
||||
|
||||
// 4. 验证
|
||||
expect(movementSystem.onAddedEntities).toHaveLength(1);
|
||||
expect(movementSystem.onAddedEntities[0]).toBe(movingEntity);
|
||||
|
||||
expect(healthSystem.onAddedEntities).toHaveLength(1);
|
||||
expect(healthSystem.onAddedEntities[0]).toBe(healthEntity);
|
||||
});
|
||||
});
|
||||
@@ -449,22 +449,24 @@ describe('QuerySystem - 查询系统测试', () => {
|
||||
expect(parseFloat(stats.cacheStats.hitRate)).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
test('缓存命中率应该在重复查询时提高', () => {
|
||||
test('缓存命中率应该在重复查询时保持高命中率', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
// 第一次查询(缓存未命中)
|
||||
// 第一次查询(创建响应式查询缓存)
|
||||
querySystem.queryAll(PositionComponent);
|
||||
let stats = querySystem.getStats();
|
||||
const initialHitRate = parseFloat(stats.cacheStats.hitRate);
|
||||
|
||||
// 重复查询(应该命中缓存)
|
||||
// 重复查询(应该持续命中响应式查询缓存)
|
||||
for (let i = 0; i < 10; i++) {
|
||||
querySystem.queryAll(PositionComponent);
|
||||
}
|
||||
|
||||
stats = querySystem.getStats();
|
||||
expect(parseFloat(stats.cacheStats.hitRate)).toBeGreaterThan(initialHitRate);
|
||||
// 响应式查询永远100%命中,这是期望的优化效果
|
||||
expect(parseFloat(stats.cacheStats.hitRate)).toBeGreaterThanOrEqual(initialHitRate);
|
||||
expect(parseFloat(stats.cacheStats.hitRate)).toBe(100);
|
||||
});
|
||||
|
||||
test('应该能够清理查询缓存', () => {
|
||||
|
||||
@@ -205,11 +205,16 @@ describe('SystemInitialization - 系统初始化测试', () => {
|
||||
let scene: Scene;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
scene = new Scene();
|
||||
scene.name = 'InitializationTestScene';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (scene) {
|
||||
scene.end();
|
||||
}
|
||||
});
|
||||
|
||||
describe('初始化时序', () => {
|
||||
test('先添加实体再添加系统 - 系统应该正确初始化', () => {
|
||||
const player = scene.createEntity('Player');
|
||||
|
||||
@@ -60,15 +60,15 @@ afterEach(() => {
|
||||
const { Core } = require('../src/Core');
|
||||
const { WorldManager } = require('../src/ECS/WorldManager');
|
||||
|
||||
// 重置 Core 和 WorldManager 单例
|
||||
// 销毁 Core 和 WorldManager 单例
|
||||
if (Core._instance) {
|
||||
Core.reset();
|
||||
Core.destroy();
|
||||
}
|
||||
if (WorldManager._instance) {
|
||||
if (WorldManager._instance.destroy) {
|
||||
WorldManager._instance.destroy();
|
||||
}
|
||||
WorldManager.reset();
|
||||
WorldManager._instance = null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略清理错误
|
||||
|
||||
Reference in New Issue
Block a user