Merge pull request #71 from esengine/fix/issue-70-component-mask-string
Fix/issue 70 component mask string
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空所有数据
|
* 清空所有数据
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user