优化ReactiveQuery: 添加公共API、修复内存泄漏、提升通知性能
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user