优化ReactiveQuery: 添加公共API、修复内存泄漏、提升通知性能

This commit is contained in:
YHH
2025-10-13 23:55:43 +08:00
parent 507ed5005f
commit 360106fb92
2 changed files with 179 additions and 15 deletions

View File

@@ -239,17 +239,24 @@ export class QuerySystem {
public removeEntity(entity: Entity): void { public removeEntity(entity: Entity): void {
const index = this.entities.indexOf(entity); const index = this.entities.indexOf(entity);
if (index !== -1) { if (index !== -1) {
const componentTypes: ComponentType[] = [];
for (const component of entity.components) {
componentTypes.push(component.constructor as ComponentType);
}
this.entities.splice(index, 1); this.entities.splice(index, 1);
this.removeEntityFromIndexes(entity); this.removeEntityFromIndexes(entity);
this.archetypeSystem.removeEntity(entity); this.archetypeSystem.removeEntity(entity);
// 通知响应式查询 if (componentTypes.length > 0) {
this.notifyReactiveQueriesEntityRemoved(entity); this.notifyReactiveQueriesEntityRemoved(entity, componentTypes);
} else {
this.notifyReactiveQueriesEntityRemovedFallback(entity);
}
this.clearQueryCache(); this.clearQueryCache();
// 更新版本号
this._version++; this._version++;
} }
} }
@@ -765,6 +772,104 @@ export class QuerySystem {
this.clearReactiveQueries(); 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();
}
/** /**
* 创建组件掩码 * 创建组件掩码
* *
@@ -1016,14 +1121,39 @@ export class QuerySystem {
} }
/** /**
* 通知所有响应式查询实体已移除 * 通知响应式查询实体已移除
* *
* 使用组件类型索引,只通知关心该实体组件的查询 * 使用组件类型索引,只通知关心该实体组件的查询
* 注意:实体移除时可能已经清空了components,所以需要通知所有查询让它们自己判断 *
* @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 移除的实体 * @param entity 移除的实体
*/ */
private notifyReactiveQueriesEntityRemoved(entity: Entity): void { private notifyReactiveQueriesEntityRemovedFallback(entity: Entity): void {
if (this._reactiveQueries.size === 0) return; if (this._reactiveQueries.size === 0) return;
for (const query of this._reactiveQueries.values()) { for (const query of this._reactiveQueries.values()) {
@@ -1032,20 +1162,37 @@ export class QuerySystem {
} }
/** /**
* 通知所有响应式查询实体已变化 * 通知响应式查询实体已变化
* *
* 实体组件变化时需要通知所有查询,因为: * 使用混合策略:
* 1. 可能从匹配变为不匹配(移除组件) * 1. 通知关心当前组件的查询
* 2. 可能从不匹配变为匹配(添加组件) * 2. 通知当前包含该实体的查询(处理组件移除情况)
* 让每个查询自己判断是否需要响应
* *
* @param entity 变化的实体 * @param entity 变化的实体
*/ */
private notifyReactiveQueriesEntityChanged(entity: Entity): void { private notifyReactiveQueriesEntityChanged(entity: Entity): void {
if (this._reactiveQueries.size === 0) return; if (this._reactiveQueries.size === 0) return;
for (const query of this._reactiveQueries.values()) { 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); 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);
}
} }
} }
} }

View File

@@ -149,13 +149,20 @@ export class ReactiveQuery {
* @returns 取消订阅的函数 * @returns 取消订阅的函数
*/ */
public subscribe(listener: ReactiveQueryListener): () => void { 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); this._listeners.push(listener);
if (this._config.debug) { if (this._config.debug) {
logger.debug(`订阅ReactiveQuery: ${this._id}, 监听器数量: ${this._listeners.length}`); logger.debug(`订阅ReactiveQuery: ${this._id}, 监听器数量: ${this._listeners.length}`);
} }
// 返回取消订阅函数
return () => { return () => {
const index = this._listeners.indexOf(listener); const index = this._listeners.indexOf(listener);
if (index !== -1) { if (index !== -1) {
@@ -379,7 +386,9 @@ export class ReactiveQuery {
* 通知所有监听器 * 通知所有监听器
*/ */
private notifyListeners(change: ReactiveQueryChange): void { private notifyListeners(change: ReactiveQueryChange): void {
for (const listener of this._listeners) { const listeners = [...this._listeners];
for (const listener of listeners) {
try { try {
listener(change); listener(change);
} catch (error) { } catch (error) {
@@ -418,7 +427,15 @@ export class ReactiveQuery {
* 释放所有资源,清空监听器和结果集 * 释放所有资源,清空监听器和结果集
*/ */
public dispose(): void { public dispose(): void {
this.pause(); 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.unsubscribeAll();
this._entities.length = 0; this._entities.length = 0;
this._entityIdSet.clear(); this._entityIdSet.clear();