Merge pull request #71 from esengine/fix/issue-70-component-mask-string

Fix/issue 70 component mask string
This commit is contained in:
YHH
2025-09-30 18:19:11 +08:00
committed by GitHub
3 changed files with 93 additions and 132 deletions

View File

@@ -187,6 +187,24 @@ export class ArchetypeSystem {
return this._allArchetypes.slice(); return this._allArchetypes.slice();
} }
/**
* 获取包含指定组件类型的所有实体
*/
public getEntitiesByComponent(componentType: ComponentType): Entity[] {
const archetypes = this._componentToArchetypes.get(componentType);
if (!archetypes || archetypes.size === 0) {
return [];
}
const entities: Entity[] = [];
for (const archetype of archetypes) {
for (const entity of archetype.entities) {
entities.push(entity);
}
}
return entities;
}
/** /**
* 清空所有数据 * 清空所有数据
*/ */

View File

@@ -6,7 +6,6 @@ import { createLogger } from '../../Utils/Logger';
import { getComponentTypeName } from '../Decorators'; import { getComponentTypeName } from '../Decorators';
import { ComponentPoolManager } from './ComponentPool'; import { ComponentPoolManager } from './ComponentPool';
import { ComponentIndexManager } from './ComponentIndex';
import { ArchetypeSystem, Archetype, ArchetypeQueryResult } from './ArchetypeSystem'; import { ArchetypeSystem, Archetype, ArchetypeQueryResult } from './ArchetypeSystem';
/** /**
@@ -46,8 +45,6 @@ export interface QueryResult {
* 实体索引结构 * 实体索引结构
*/ */
interface EntityIndex { interface EntityIndex {
byMask: Map<string, Set<Entity>>;
byComponentType: Map<ComponentType, Set<Entity>>;
byTag: Map<number, Set<Entity>>; byTag: Map<number, Set<Entity>>;
byName: Map<string, Set<Entity>>; byName: Map<string, Set<Entity>>;
} }
@@ -91,7 +88,6 @@ export class QuerySystem {
private componentMaskCache = new Map<string, BitMask64Data>(); private componentMaskCache = new Map<string, BitMask64Data>();
private componentIndexManager: ComponentIndexManager;
private archetypeSystem: ArchetypeSystem; private archetypeSystem: ArchetypeSystem;
// 性能统计 // 性能统计
@@ -106,14 +102,10 @@ export class QuerySystem {
constructor() { constructor() {
this.entityIndex = { this.entityIndex = {
byMask: new Map(),
byComponentType: new Map(),
byTag: new Map(), byTag: new Map(),
byName: new Map() byName: new Map()
}; };
// 初始化新的性能优化系统
this.componentIndexManager = new ComponentIndexManager();
this.archetypeSystem = new ArchetypeSystem(); this.archetypeSystem = new ArchetypeSystem();
} }
@@ -147,7 +139,6 @@ export class QuerySystem {
this.entities.push(entity); this.entities.push(entity);
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
this.componentIndexManager.addEntity(entity);
this.archetypeSystem.addEntity(entity); this.archetypeSystem.addEntity(entity);
@@ -182,7 +173,6 @@ export class QuerySystem {
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
// 更新索引管理器 // 更新索引管理器
this.componentIndexManager.addEntity(entity);
this.archetypeSystem.addEntity(entity); this.archetypeSystem.addEntity(entity);
existingIds.add(entity.id); existingIds.add(entity.id);
@@ -217,7 +207,6 @@ export class QuerySystem {
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
// 更新索引管理器 // 更新索引管理器
this.componentIndexManager.addEntity(entity);
this.archetypeSystem.addEntity(entity); this.archetypeSystem.addEntity(entity);
} }
@@ -238,7 +227,6 @@ export class QuerySystem {
this.entities.splice(index, 1); this.entities.splice(index, 1);
this.removeEntityFromIndexes(entity); this.removeEntityFromIndexes(entity);
this.componentIndexManager.removeEntity(entity);
this.archetypeSystem.removeEntity(entity); this.archetypeSystem.removeEntity(entity);
this.clearQueryCache(); this.clearQueryCache();
@@ -268,11 +256,6 @@ export class QuerySystem {
// 更新ArchetypeSystem中的实体状态 // 更新ArchetypeSystem中的实体状态
this.archetypeSystem.updateEntity(entity); this.archetypeSystem.updateEntity(entity);
// 更新ComponentIndexManager中的实体状态
this.componentIndexManager.removeEntity(entity);
this.componentIndexManager.addEntity(entity);
// 重新添加实体到索引(基于新的组件状态) // 重新添加实体到索引(基于新的组件状态)
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
@@ -287,21 +270,6 @@ export class QuerySystem {
* 将实体添加到各种索引中 * 将实体添加到各种索引中
*/ */
private addEntityToIndexes(entity: Entity): void { private addEntityToIndexes(entity: Entity): void {
const mask = entity.componentMask;
// 组件掩码索引
const maskKey = mask.toString();
const maskSet = this.entityIndex.byMask.get(maskKey) || this.createAndSetMaskIndex(maskKey);
maskSet.add(entity);
// 组件类型索引 - 批量处理预获取所有相关的Set
const components = entity.components;
for (let i = 0; i < components.length; i++) {
const componentType = components[i].constructor as ComponentType;
const typeSet = this.entityIndex.byComponentType.get(componentType) || this.createAndSetComponentIndex(componentType);
typeSet.add(entity);
}
// 标签索引 // 标签索引
const tag = entity.tag; const tag = entity.tag;
if (tag !== undefined) { if (tag !== undefined) {
@@ -317,17 +285,6 @@ export class QuerySystem {
} }
} }
private createAndSetMaskIndex(maskKey: string): Set<Entity> {
const set = new Set<Entity>();
this.entityIndex.byMask.set(maskKey, set);
return set;
}
private createAndSetComponentIndex(componentType: ComponentType): Set<Entity> {
const set = new Set<Entity>();
this.entityIndex.byComponentType.set(componentType, set);
return set;
}
private createAndSetTagIndex(tag: number): Set<Entity> { private createAndSetTagIndex(tag: number): Set<Entity> {
const set = new Set<Entity>(); const set = new Set<Entity>();
@@ -345,30 +302,6 @@ export class QuerySystem {
* 从各种索引中移除实体 * 从各种索引中移除实体
*/ */
private removeEntityFromIndexes(entity: Entity): void { private removeEntityFromIndexes(entity: Entity): void {
const mask = entity.componentMask;
// 从组件掩码索引移除
const maskKey = mask.toString();
const maskSet = this.entityIndex.byMask.get(maskKey);
if (maskSet) {
maskSet.delete(entity);
if (maskSet.size === 0) {
this.entityIndex.byMask.delete(maskKey);
}
}
// 从组件类型索引移除
for (const component of entity.components) {
const componentType = component.constructor as ComponentType;
const typeSet = this.entityIndex.byComponentType.get(componentType);
if (typeSet) {
typeSet.delete(entity);
if (typeSet.size === 0) {
this.entityIndex.byComponentType.delete(componentType);
}
}
}
// 从标签索引移除 // 从标签索引移除
if (entity.tag !== undefined) { if (entity.tag !== undefined) {
const tagSet = this.entityIndex.byTag.get(entity.tag); const tagSet = this.entityIndex.byTag.get(entity.tag);
@@ -399,18 +332,14 @@ export class QuerySystem {
* 通常在大量实体变更后调用以确保索引一致性。 * 通常在大量实体变更后调用以确保索引一致性。
*/ */
private rebuildIndexes(): void { private rebuildIndexes(): void {
this.entityIndex.byMask.clear();
this.entityIndex.byComponentType.clear();
this.entityIndex.byTag.clear(); this.entityIndex.byTag.clear();
this.entityIndex.byName.clear(); this.entityIndex.byName.clear();
// 清理ArchetypeSystem和ComponentIndexManager // 清理ArchetypeSystem和ComponentIndexManager
this.archetypeSystem.clear(); this.archetypeSystem.clear();
this.componentIndexManager.clear();
for (const entity of this.entities) { for (const entity of this.entities) {
this.addEntityToIndexes(entity); this.addEntityToIndexes(entity);
this.componentIndexManager.addEntity(entity);
this.archetypeSystem.addEntity(entity); this.archetypeSystem.addEntity(entity);
} }
} }
@@ -450,26 +379,13 @@ export class QuerySystem {
}; };
} }
let entities: Entity[] = []; this.queryStats.archetypeHits++;
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'AND'); const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'AND');
if (archetypeResult.archetypes.length > 0) {
this.queryStats.archetypeHits++; const entities: Entity[] = [];
for (const archetype of archetypeResult.archetypes) { for (const archetype of archetypeResult.archetypes) {
entities.push(...archetype.entities); for (const entity of archetype.entities) {
} entities.push(entity);
} else {
try {
if (componentTypes.length === 1) {
this.queryStats.indexHits++;
const indexResult = this.componentIndexManager.query(componentTypes[0]);
entities = Array.from(indexResult);
} else {
const indexResult = this.componentIndexManager.queryMultiple(componentTypes, 'AND');
entities = Array.from(indexResult);
}
} catch (error) {
entities = [];
} }
} }
@@ -493,31 +409,11 @@ export class QuerySystem {
* @returns 匹配的实体列表 * @returns 匹配的实体列表
*/ */
private queryMultipleComponents(componentTypes: ComponentType[]): Entity[] { private queryMultipleComponents(componentTypes: ComponentType[]): Entity[] {
// 找到最小的组件集合作为起点 const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'AND');
let smallestSet: Set<Entity> | null = null;
let smallestSize = Infinity;
for (const componentType of componentTypes) {
const set = this.entityIndex.byComponentType.get(componentType);
if (!set || set.size === 0) {
return []; // 如果任何组件没有实体,直接返回空结果
}
if (set.size < smallestSize) {
smallestSize = set.size;
smallestSet = set;
}
}
if (!smallestSet) {
return []; // 如果没有找到任何组件集合,返回空结果
}
// 从最小集合开始,逐步过滤
const mask = this.createComponentMask(componentTypes);
const result: Entity[] = []; const result: Entity[] = [];
for (const entity of smallestSet) { for (const archetype of archetypeResult.archetypes) {
if (BitMask64Utils.hasAll(entity.componentMask, mask)) { for (const entity of archetype.entities) {
result.push(entity); result.push(entity);
} }
} }
@@ -561,18 +457,14 @@ export class QuerySystem {
}; };
} }
this.queryStats.archetypeHits++;
const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'OR'); const archetypeResult = this.archetypeSystem.queryArchetypes(componentTypes, 'OR');
let entities: Entity[];
if (archetypeResult.archetypes.length > 0) { const entities: Entity[] = [];
this.queryStats.archetypeHits++; for (const archetype of archetypeResult.archetypes) {
entities = []; for (const entity of archetype.entities) {
for (const archetype of archetypeResult.archetypes) { entities.push(entity);
entities.push(...archetype.entities);
} }
} else {
const indexResult = this.componentIndexManager.queryMultiple(componentTypes, 'OR');
entities = Array.from(indexResult);
} }
this.addToCache(cacheKey, entities); this.addToCache(cacheKey, entities);
@@ -763,9 +655,8 @@ export class QuerySystem {
}; };
} }
// 使用索引查询
this.queryStats.indexHits++; this.queryStats.indexHits++;
const entities = Array.from(this.entityIndex.byComponentType.get(componentType) || []); const entities = this.archetypeSystem.getEntitiesByComponent(componentType);
// 缓存结果 // 缓存结果
this.addToCache(cacheKey, entities); this.addToCache(cacheKey, entities);
@@ -954,7 +845,6 @@ export class QuerySystem {
public getStats(): { public getStats(): {
entityCount: number; entityCount: number;
indexStats: { indexStats: {
maskIndexSize: number;
componentIndexSize: number; componentIndexSize: number;
tagIndexSize: number; tagIndexSize: number;
nameIndexSize: number; nameIndexSize: number;
@@ -969,7 +859,6 @@ export class QuerySystem {
cacheHitRate: string; cacheHitRate: string;
}; };
optimizationStats: { optimizationStats: {
componentIndex: any;
archetypeSystem: any; archetypeSystem: any;
}; };
cacheStats: { cacheStats: {
@@ -980,8 +869,7 @@ export class QuerySystem {
return { return {
entityCount: this.entities.length, entityCount: this.entities.length,
indexStats: { indexStats: {
maskIndexSize: this.entityIndex.byMask.size, componentIndexSize: this.archetypeSystem.getAllArchetypes().length,
componentIndexSize: this.entityIndex.byComponentType.size,
tagIndexSize: this.entityIndex.byTag.size, tagIndexSize: this.entityIndex.byTag.size,
nameIndexSize: this.entityIndex.byName.size nameIndexSize: this.entityIndex.byName.size
}, },
@@ -991,7 +879,6 @@ export class QuerySystem {
(this.queryStats.cacheHits / this.queryStats.totalQueries * 100).toFixed(2) + '%' : '0%' (this.queryStats.cacheHits / this.queryStats.totalQueries * 100).toFixed(2) + '%' : '0%'
}, },
optimizationStats: { optimizationStats: {
componentIndex: this.componentIndexManager.getStats(),
archetypeSystem: this.archetypeSystem.getAllArchetypes().map(a => ({ archetypeSystem: this.archetypeSystem.getAllArchetypes().map(a => ({
id: a.id, id: a.id,
componentTypes: a.componentTypes.map(t => getComponentTypeName(t)), componentTypes: a.componentTypes.map(t => getComponentTypeName(t)),

View File

@@ -1049,4 +1049,60 @@ describe('QuerySystem - 查询系统测试', () => {
} }
}); });
}); });
describe('组件掩码字符串索引', () => {
test('应该为不同的组件组合生成不同的掩码字符串', () => {
// 创建具有不同组件组合的实体
const entity1 = new Entity('Entity1', 101);
const entity2 = new Entity('Entity2', 102);
const entity3 = new Entity('Entity3', 103);
entity1.addComponent(new PositionComponent(1, 1));
entity2.addComponent(new VelocityComponent(2, 2));
entity3.addComponent(new PositionComponent(3, 3));
entity3.addComponent(new VelocityComponent(4, 4));
const allEntities = [...entities, entity1, entity2, entity3];
querySystem.setEntities(allEntities);
// 查询包含指定组件的实体queryAll 是包含关系,不是仅有)
const withPosition = querySystem.queryAll(PositionComponent);
const withVelocity = querySystem.queryAll(VelocityComponent);
const withBoth = querySystem.queryAll(PositionComponent, VelocityComponent);
// entity1 应该在 withPosition 中
expect(withPosition.entities.some(e => e.id === 101)).toBe(true);
// entity2 应该在 withVelocity 中
expect(withVelocity.entities.some(e => e.id === 102)).toBe(true);
// entity3 应该在所有查询中(因为它包含 Position 和 Velocity
expect(withPosition.entities.some(e => e.id === 103)).toBe(true);
expect(withVelocity.entities.some(e => e.id === 103)).toBe(true);
expect(withBoth.entities.some(e => e.id === 103)).toBe(true);
// withBoth 只应该包含同时有两个组件的实体
expect(withBoth.entities.some(e => e.id === 101)).toBe(false); // 只有 Position
expect(withBoth.entities.some(e => e.id === 102)).toBe(false); // 只有 Velocity
});
test('相同组件组合的实体应该使用相同的掩码索引', () => {
const entity1 = new Entity('Entity1', 201);
const entity2 = new Entity('Entity2', 202);
// 两个实体都有相同的组件组合
entity1.addComponent(new PositionComponent(1, 1));
entity1.addComponent(new VelocityComponent(2, 2));
entity2.addComponent(new PositionComponent(3, 3));
entity2.addComponent(new VelocityComponent(4, 4));
const allEntities = [...entities, entity1, entity2];
querySystem.setEntities(allEntities);
// 查询应该同时返回这两个实体
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
expect(result.entities.some(e => e.id === 201)).toBe(true);
expect(result.entities.some(e => e.id === 202)).toBe(true);
});
});
}); });