改成 SparseSet+SwapRemove 的致密存储

This commit is contained in:
YHH
2025-09-02 22:29:11 +08:00
parent 94541d0abb
commit 6e511ae949
5 changed files with 446 additions and 162 deletions

View File

@@ -203,15 +203,12 @@ export class ComponentRegistry {
/** /**
* 高性能组件存储器 * 高性能组件存储器
* 使用SoAStructure of Arrays模式存储组件
*/ */
export class ComponentStorage<T extends Component> { export class ComponentStorage<T extends Component> {
private components: (T | null)[] = []; private dense: T[] = [];
private entityIds: number[] = [];
private entityToIndex = new Map<number, number>(); private entityToIndex = new Map<number, number>();
private indexToEntity: number[] = [];
private freeIndices: number[] = [];
private componentType: ComponentType<T>; private componentType: ComponentType<T>;
private _size = 0;
constructor(componentType: ComponentType<T>) { constructor(componentType: ComponentType<T>) {
this.componentType = componentType; this.componentType = componentType;
@@ -233,22 +230,11 @@ export class ComponentStorage<T extends Component> {
throw new Error(`Entity ${entityId} already has component ${getComponentTypeName(this.componentType)}`); throw new Error(`Entity ${entityId} already has component ${getComponentTypeName(this.componentType)}`);
} }
let index: number; // 末尾插入到致密数组
const index = this.dense.length;
if (this.freeIndices.length > 0) { this.dense.push(component);
// 重用空闲索引 this.entityIds.push(entityId);
index = this.freeIndices.pop()!;
this.components[index] = component;
this.indexToEntity[index] = entityId;
} else {
// 添加到末尾
index = this.components.length;
this.components.push(component);
this.indexToEntity.push(entityId);
}
this.entityToIndex.set(entityId, index); this.entityToIndex.set(entityId, index);
this._size++;
} }
/** /**
@@ -258,7 +244,7 @@ export class ComponentStorage<T extends Component> {
*/ */
public getComponent(entityId: number): T | null { public getComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId); const index = this.entityToIndex.get(entityId);
return index !== undefined ? this.components[index] : null; return index !== undefined ? this.dense[index] : null;
} }
/** /**
@@ -281,11 +267,25 @@ export class ComponentStorage<T extends Component> {
return null; return null;
} }
const component = this.components[index]; const component = this.dense[index];
const lastIndex = this.dense.length - 1;
if (index !== lastIndex) {
// 将末尾元素交换到要删除的位置
const lastComponent = this.dense[lastIndex];
const lastEntityId = this.entityIds[lastIndex];
this.dense[index] = lastComponent;
this.entityIds[index] = lastEntityId;
// 更新被交换元素的映射
this.entityToIndex.set(lastEntityId, index);
}
// 移除末尾元素
this.dense.pop();
this.entityIds.pop();
this.entityToIndex.delete(entityId); this.entityToIndex.delete(entityId);
this.components[index] = null;
this.freeIndices.push(index);
this._size--;
return component; return component;
} }
@@ -295,49 +295,36 @@ export class ComponentStorage<T extends Component> {
* @param callback 回调函数 * @param callback 回调函数
*/ */
public forEach(callback: (component: T, entityId: number, index: number) => void): void { public forEach(callback: (component: T, entityId: number, index: number) => void): void {
for (let i = 0; i < this.components.length; i++) { for (let i = 0; i < this.dense.length; i++) {
const component = this.components[i]; callback(this.dense[i], this.entityIds[i], i);
if (component) {
callback(component, this.indexToEntity[i], i);
}
} }
} }
/** /**
* 获取所有组件(密集数组) * 获取所有组件
* @returns 组件数组 * @returns 组件数组
*/ */
public getDenseArray(): { components: T[]; entityIds: number[] } { public getDenseArray(): { components: T[]; entityIds: number[] } {
const components: T[] = []; return {
const entityIds: number[] = []; components: [...this.dense],
entityIds: [...this.entityIds]
for (let i = 0; i < this.components.length; i++) { };
const component = this.components[i];
if (component) {
components.push(component);
entityIds.push(this.indexToEntity[i]);
}
}
return { components, entityIds };
} }
/** /**
* 清空所有组件 * 清空所有组件
*/ */
public clear(): void { public clear(): void {
this.components.length = 0; this.dense.length = 0;
this.entityIds.length = 0;
this.entityToIndex.clear(); this.entityToIndex.clear();
this.indexToEntity.length = 0;
this.freeIndices.length = 0;
this._size = 0;
} }
/** /**
* 获取组件数量 * 获取组件数量
*/ */
public get size(): number { public get size(): number {
return this._size; return this.dense.length;
} }
/** /**
@@ -347,34 +334,6 @@ export class ComponentStorage<T extends Component> {
return this.componentType; return this.componentType;
} }
/**
* 压缩存储(移除空洞)
*/
public compact(): void {
if (this.freeIndices.length === 0) {
return; // 没有空洞,无需压缩
}
const newComponents: T[] = [];
const newIndexToEntity: number[] = [];
const newEntityToIndex = new Map<number, number>();
let newIndex = 0;
for (let i = 0; i < this.components.length; i++) {
const component = this.components[i];
if (component) {
newComponents[newIndex] = component;
newIndexToEntity[newIndex] = this.indexToEntity[i];
newEntityToIndex.set(this.indexToEntity[i], newIndex);
newIndex++;
}
}
this.components = newComponents;
this.indexToEntity = newIndexToEntity;
this.entityToIndex = newEntityToIndex;
this.freeIndices.length = 0;
}
/** /**
* 获取存储统计信息 * 获取存储统计信息
@@ -385,10 +344,10 @@ export class ComponentStorage<T extends Component> {
freeSlots: number; freeSlots: number;
fragmentation: number; fragmentation: number;
} { } {
const totalSlots = this.components.length; const totalSlots = this.dense.length;
const usedSlots = this._size; const usedSlots = this.dense.length;
const freeSlots = this.freeIndices.length; const freeSlots = 0; // 永远无空洞
const fragmentation = totalSlots > 0 ? freeSlots / totalSlots : 0; const fragmentation = 0; // 永远无碎片
return { return {
totalSlots, totalSlots,
@@ -586,14 +545,6 @@ export class ComponentStorageManager {
return mask; return mask;
} }
/**
* 压缩所有存储器
*/
public compactAll(): void {
for (const storage of this.storages.values()) {
storage.compact();
}
}
/** /**
* 获取所有存储器的统计信息 * 获取所有存储器的统计信息

View File

@@ -364,12 +364,6 @@ export class Scene implements IScene {
}; };
} }
/**
* 压缩组件存储(清理碎片)
*/
public compactComponentStorage(): void {
this.componentStorageManager.compactAll();
}
/** /**
* 获取场景的调试信息 * 获取场景的调试信息

View File

@@ -301,7 +301,7 @@ describe('ComponentStorage - 组件存储器测试', () => {
}); });
describe('内存管理和优化', () => { describe('内存管理和优化', () => {
test('应该能够重用空闲索引', () => { test('应该维持紧凑存储', () => {
const component1 = new TestComponent(100); const component1 = new TestComponent(100);
const component2 = new TestComponent(200); const component2 = new TestComponent(200);
const component3 = new TestComponent(300); const component3 = new TestComponent(300);
@@ -311,68 +311,37 @@ describe('ComponentStorage - 组件存储器测试', () => {
storage.addComponent(2, component2); storage.addComponent(2, component2);
storage.addComponent(3, component3); storage.addComponent(3, component3);
// 移除中间的组件 // 移除中间的组件,稀疏集合会自动保持紧凑
storage.removeComponent(2); storage.removeComponent(2);
// 添加新组件应该重用空闲索引 // 添加新组件
const component4 = new TestComponent(400); const component4 = new TestComponent(400);
storage.addComponent(4, component4); storage.addComponent(4, component4);
expect(storage.size).toBe(3); expect(storage.size).toBe(3);
expect(storage.getComponent(4)).toBe(component4); expect(storage.getComponent(4)).toBe(component4);
});
test('应该能够压缩存储', () => {
// 添加多个组件
storage.addComponent(1, new TestComponent(100));
storage.addComponent(2, new TestComponent(200));
storage.addComponent(3, new TestComponent(300));
storage.addComponent(4, new TestComponent(400));
// 移除部分组件创建空洞 // 验证存储保持紧凑
storage.removeComponent(2); const stats = storage.getStats();
storage.removeComponent(3);
let stats = storage.getStats();
expect(stats.freeSlots).toBe(2);
expect(stats.fragmentation).toBeGreaterThan(0);
// 压缩存储
storage.compact();
stats = storage.getStats();
expect(stats.freeSlots).toBe(0); expect(stats.freeSlots).toBe(0);
expect(stats.fragmentation).toBe(0); expect(stats.fragmentation).toBe(0);
expect(storage.size).toBe(2);
expect(storage.hasComponent(1)).toBe(true);
expect(storage.hasComponent(4)).toBe(true);
}); });
test('没有空洞时压缩应该不做任何操作', () => {
storage.addComponent(1, new TestComponent(100));
storage.addComponent(2, new TestComponent(200));
const statsBefore = storage.getStats();
storage.compact();
const statsAfter = storage.getStats();
expect(statsBefore).toEqual(statsAfter);
});
test('应该能够获取存储统计信息', () => { test('应该能够获取存储统计信息', () => {
storage.addComponent(1, new TestComponent(100)); storage.addComponent(1, new TestComponent(100));
storage.addComponent(2, new TestComponent(200)); storage.addComponent(2, new TestComponent(200));
storage.addComponent(3, new TestComponent(300)); storage.addComponent(3, new TestComponent(300));
// 移除一个组件创建空洞 // 移除一个组件,稀疏集合会自动紧凑
storage.removeComponent(2); storage.removeComponent(2);
const stats = storage.getStats(); const stats = storage.getStats();
expect(stats.totalSlots).toBe(3); expect(stats.totalSlots).toBe(2); // 稀疏集合自动紧凑
expect(stats.usedSlots).toBe(2); expect(stats.usedSlots).toBe(2);
expect(stats.freeSlots).toBe(1); expect(stats.freeSlots).toBe(0); // 无空洞
expect(stats.fragmentation).toBeCloseTo(1/3); expect(stats.fragmentation).toBe(0); // 无碎片
}); });
}); });
@@ -541,22 +510,6 @@ describe('ComponentStorageManager - 组件存储管理器测试', () => {
}); });
describe('管理器级别操作', () => { describe('管理器级别操作', () => {
test('应该能够压缩所有存储器', () => {
manager.addComponent(1, new TestComponent(100));
manager.addComponent(2, new TestComponent(200));
manager.addComponent(3, new TestComponent(300));
manager.addComponent(1, new PositionComponent(10, 20));
manager.addComponent(2, new PositionComponent(30, 40));
// 移除部分组件创建空洞
manager.removeComponent(2, TestComponent);
manager.removeComponent(1, PositionComponent);
expect(() => {
manager.compactAll();
}).not.toThrow();
});
test('应该能够获取所有存储器的统计信息', () => { test('应该能够获取所有存储器的统计信息', () => {
manager.addComponent(1, new TestComponent(100)); manager.addComponent(1, new TestComponent(100));

View File

@@ -468,18 +468,6 @@ describe('Scene - 场景管理系统测试', () => {
expect(parseFloat(stats.cacheStats.hitRate)).toBeGreaterThanOrEqual(0); expect(parseFloat(stats.cacheStats.hitRate)).toBeGreaterThanOrEqual(0);
}); });
test('应该能够压缩组件存储', () => {
// 创建一些实体和组件
const entities = scene.createEntities(10, "Entity");
entities.forEach(entity => {
entity.addComponent(new PositionComponent(Math.random() * 100, Math.random() * 100));
});
// 压缩组件存储应该不抛出异常
expect(() => {
scene.compactComponentStorage();
}).not.toThrow();
});
}); });
describe('内存管理和性能', () => { describe('内存管理和性能', () => {

View File

@@ -0,0 +1,398 @@
import { ComponentStorage, ComponentRegistry, ComponentType } from '../../src/ECS/Core/ComponentStorage';
import { Component } from '../../src/ECS/Component';
// 测试组件类
class PerformanceTestComponent extends Component {
public value: number;
public x: number;
public y: number;
public active: boolean;
constructor(value = 0, x = 0, y = 0, active = true) {
super();
this.value = value;
this.x = x;
this.y = y;
this.active = active;
}
}
// 模拟旧的基于holes的ComponentStorage实现
class LegacyComponentStorage<T extends Component> {
private components: (T | null)[] = [];
private freeIndices: number[] = [];
private entityToIndex = new Map<number, number>();
private componentType: ComponentType<T>;
constructor(componentType: ComponentType<T>) {
this.componentType = componentType;
}
public addComponent(entityId: number, component: T): void {
if (this.entityToIndex.has(entityId)) {
throw new Error(`Entity ${entityId} already has component`);
}
let index: number;
if (this.freeIndices.length > 0) {
index = this.freeIndices.pop()!;
} else {
index = this.components.length;
this.components.push(null);
}
this.components[index] = component;
this.entityToIndex.set(entityId, index);
}
public getComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId);
return index !== undefined ? this.components[index] : null;
}
public hasComponent(entityId: number): boolean {
return this.entityToIndex.has(entityId);
}
public removeComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId);
if (index === undefined) {
return null;
}
const component = this.components[index];
this.components[index] = null;
this.freeIndices.push(index);
this.entityToIndex.delete(entityId);
return component;
}
public forEach(callback: (component: T, entityId: number, index: number) => void): void {
for (let i = 0; i < this.components.length; i++) {
const component = this.components[i];
if (component !== null) {
// 需要找到对应的entityId这在holes存储中是低效的
for (const [entityId, compIndex] of this.entityToIndex) {
if (compIndex === i) {
callback(component, entityId, i);
break;
}
}
}
}
}
public clear(): void {
this.components.length = 0;
this.freeIndices.length = 0;
this.entityToIndex.clear();
}
public get size(): number {
return this.entityToIndex.size;
}
}
describe('ComponentStorage 性能对比测试', () => {
let newStorage: ComponentStorage<PerformanceTestComponent>;
let legacyStorage: LegacyComponentStorage<PerformanceTestComponent>;
beforeEach(() => {
ComponentRegistry.reset();
newStorage = new ComponentStorage(PerformanceTestComponent);
legacyStorage = new LegacyComponentStorage(PerformanceTestComponent);
});
const createComponent = (id: number) => new PerformanceTestComponent(id, id * 2, id * 3, true);
describe('基础操作性能对比', () => {
const entityCount = 10000;
test('批量添加组件性能', () => {
console.log(`\n=== 批量添加${entityCount}个组件性能对比 ===`);
// 测试新实现
const newStartTime = performance.now();
for (let i = 1; i <= entityCount; i++) {
newStorage.addComponent(i, createComponent(i));
}
const newAddTime = performance.now() - newStartTime;
// 测试旧实现
const legacyStartTime = performance.now();
for (let i = 1; i <= entityCount; i++) {
legacyStorage.addComponent(i, createComponent(i));
}
const legacyAddTime = performance.now() - legacyStartTime;
console.log(`旧实现holes存储: ${legacyAddTime.toFixed(3)}ms`);
console.log(`新实现(稀疏集合): ${newAddTime.toFixed(3)}ms`);
console.log(`性能提升: ${((legacyAddTime - newAddTime) / legacyAddTime * 100).toFixed(1)}%`);
expect(newStorage.size).toBe(entityCount);
expect(legacyStorage.size).toBe(entityCount);
});
test('批量查找组件性能', () => {
console.log(`\n=== 批量查找${entityCount}个组件性能对比 ===`);
// 先添加数据
for (let i = 1; i <= entityCount; i++) {
newStorage.addComponent(i, createComponent(i));
legacyStorage.addComponent(i, createComponent(i));
}
// 测试新实现查找
const newStartTime = performance.now();
for (let i = 1; i <= entityCount; i++) {
const component = newStorage.getComponent(i);
expect(component?.value).toBe(i);
}
const newGetTime = performance.now() - newStartTime;
// 测试旧实现查找
const legacyStartTime = performance.now();
for (let i = 1; i <= entityCount; i++) {
const component = legacyStorage.getComponent(i);
expect(component?.value).toBe(i);
}
const legacyGetTime = performance.now() - legacyStartTime;
console.log(`旧实现holes存储: ${legacyGetTime.toFixed(3)}ms`);
console.log(`新实现(稀疏集合): ${newGetTime.toFixed(3)}ms`);
console.log(`性能提升: ${((legacyGetTime - newGetTime) / legacyGetTime * 100).toFixed(1)}%`);
});
test('批量移除组件性能', () => {
console.log(`\n=== 批量移除${entityCount / 2}个组件性能对比 ===`);
// 先添加数据
for (let i = 1; i <= entityCount; i++) {
newStorage.addComponent(i, createComponent(i));
legacyStorage.addComponent(i, createComponent(i));
}
// 测试新实现移除移除奇数ID
const newStartTime = performance.now();
for (let i = 1; i <= entityCount; i += 2) {
newStorage.removeComponent(i);
}
const newRemoveTime = performance.now() - newStartTime;
// 测试旧实现移除移除奇数ID
const legacyStartTime = performance.now();
for (let i = 1; i <= entityCount; i += 2) {
legacyStorage.removeComponent(i);
}
const legacyRemoveTime = performance.now() - legacyStartTime;
console.log(`旧实现holes存储: ${legacyRemoveTime.toFixed(3)}ms`);
console.log(`新实现(稀疏集合): ${newRemoveTime.toFixed(3)}ms`);
console.log(`性能提升: ${((legacyRemoveTime - newRemoveTime) / legacyRemoveTime * 100).toFixed(1)}%`);
expect(newStorage.size).toBe(entityCount / 2);
expect(legacyStorage.size).toBe(entityCount / 2);
});
});
describe('遍历性能对比', () => {
const entityCount = 50000;
test('完整遍历性能 - 无碎片情况', () => {
console.log(`\n=== 完整遍历${entityCount}个组件性能对比(无碎片) ===`);
// 先添加数据
for (let i = 1; i <= entityCount; i++) {
newStorage.addComponent(i, createComponent(i));
legacyStorage.addComponent(i, createComponent(i));
}
let newSum = 0;
let legacySum = 0;
// 测试新实现遍历
const newStartTime = performance.now();
newStorage.forEach((component) => {
newSum += component.value;
});
const newForEachTime = performance.now() - newStartTime;
// 测试旧实现遍历
const legacyStartTime = performance.now();
legacyStorage.forEach((component) => {
legacySum += component.value;
});
const legacyForEachTime = performance.now() - legacyStartTime;
console.log(`旧实现holes存储: ${legacyForEachTime.toFixed(3)}ms`);
console.log(`新实现(稀疏集合): ${newForEachTime.toFixed(3)}ms`);
console.log(`性能提升: ${((legacyForEachTime - newForEachTime) / legacyForEachTime * 100).toFixed(1)}%`);
expect(newSum).toBe(legacySum);
});
test('遍历性能 - 高碎片情况', () => {
console.log(`\n=== 遍历性能对比高碎片情况70%组件被移除) ===`);
// 添加大量数据
for (let i = 1; i <= entityCount; i++) {
newStorage.addComponent(i, createComponent(i));
legacyStorage.addComponent(i, createComponent(i));
}
// 移除70%的组件,创建大量碎片
for (let i = 1; i <= entityCount; i++) {
if (i % 10 < 7) { // 移除70%
newStorage.removeComponent(i);
legacyStorage.removeComponent(i);
}
}
console.log(`剩余组件数量: ${newStorage.size}`);
let newSum = 0;
let legacySum = 0;
// 测试新实现遍历(稀疏集合天然无碎片)
const newStartTime = performance.now();
newStorage.forEach((component) => {
newSum += component.value;
});
const newForEachTime = performance.now() - newStartTime;
// 测试旧实现遍历需要跳过大量holes
const legacyStartTime = performance.now();
legacyStorage.forEach((component) => {
legacySum += component.value;
});
const legacyForEachTime = performance.now() - legacyStartTime;
console.log(`旧实现holes存储70%空洞): ${legacyForEachTime.toFixed(3)}ms`);
console.log(`新实现(稀疏集合,无空洞): ${newForEachTime.toFixed(3)}ms`);
console.log(`性能提升: ${((legacyForEachTime - newForEachTime) / legacyForEachTime * 100).toFixed(1)}%`);
expect(newSum).toBe(legacySum);
});
});
describe('内存效率对比', () => {
const entityCount = 20000;
test('内存使用和碎片对比', () => {
console.log(`\n=== 内存效率对比 ===`);
// 添加组件
for (let i = 1; i <= entityCount; i++) {
newStorage.addComponent(i, createComponent(i));
legacyStorage.addComponent(i, createComponent(i));
}
// 移除一半组件创建碎片
for (let i = 1; i <= entityCount; i += 2) {
newStorage.removeComponent(i);
legacyStorage.removeComponent(i);
}
const newStats = newStorage.getStats();
console.log(`=== 移除50%组件后的存储效率 ===`);
console.log(`新实现(稀疏集合):`);
console.log(` - 总槽位: ${newStats.totalSlots}`);
console.log(` - 使用槽位: ${newStats.usedSlots}`);
console.log(` - 空闲槽位: ${newStats.freeSlots}`);
console.log(` - 碎片率: ${(newStats.fragmentation * 100).toFixed(1)}%`);
console.log(` - 内存效率: ${((newStats.usedSlots / newStats.totalSlots) * 100).toFixed(1)}%`);
console.log(`旧实现holes存储:`);
console.log(` - 总槽位: ${entityCount} (固定数组大小)`);
console.log(` - 使用槽位: ${entityCount / 2}`);
console.log(` - 空闲槽位: ${entityCount / 2}`);
console.log(` - 碎片率: 50.0%`);
console.log(` - 内存效率: 50.0%`);
// 验证新实现的优势
expect(newStats.fragmentation).toBe(0); // 稀疏集合无碎片
expect(newStats.totalSlots).toBe(newStats.usedSlots); // 完全紧凑
});
});
describe('随机访问模式性能测试', () => {
const entityCount = 10000;
const operationCount = 5000;
test('混合操作性能对比', () => {
console.log(`\n=== 混合操作性能对比(${operationCount}次随机操作) ===`);
// 预先添加一些数据
for (let i = 1; i <= entityCount / 2; i++) {
newStorage.addComponent(i, createComponent(i));
legacyStorage.addComponent(i, createComponent(i));
}
// 生成随机操作序列
const operations: Array<{type: 'add' | 'get' | 'remove', entityId: number}> = [];
for (let i = 0; i < operationCount; i++) {
const type = Math.random() < 0.4 ? 'add' : Math.random() < 0.7 ? 'get' : 'remove';
const entityId = Math.floor(Math.random() * entityCount) + 1;
operations.push({ type, entityId });
}
// 测试新实现
const newStartTime = performance.now();
operations.forEach(op => {
try {
switch (op.type) {
case 'add':
if (!newStorage.hasComponent(op.entityId)) {
newStorage.addComponent(op.entityId, createComponent(op.entityId));
}
break;
case 'get':
newStorage.getComponent(op.entityId);
break;
case 'remove':
newStorage.removeComponent(op.entityId);
break;
}
} catch (e) {
// 忽略重复添加等错误
}
});
const newMixedTime = performance.now() - newStartTime;
// 重置旧实现状态
legacyStorage.clear();
for (let i = 1; i <= entityCount / 2; i++) {
legacyStorage.addComponent(i, createComponent(i));
}
// 测试旧实现
const legacyStartTime = performance.now();
operations.forEach(op => {
try {
switch (op.type) {
case 'add':
if (!legacyStorage.hasComponent(op.entityId)) {
legacyStorage.addComponent(op.entityId, createComponent(op.entityId));
}
break;
case 'get':
legacyStorage.getComponent(op.entityId);
break;
case 'remove':
legacyStorage.removeComponent(op.entityId);
break;
}
} catch (e) {
// 忽略重复添加等错误
}
});
const legacyMixedTime = performance.now() - legacyStartTime;
console.log(`旧实现holes存储: ${legacyMixedTime.toFixed(3)}ms`);
console.log(`新实现(稀疏集合): ${newMixedTime.toFixed(3)}ms`);
console.log(`性能提升: ${((legacyMixedTime - newMixedTime) / legacyMixedTime * 100).toFixed(1)}%`);
});
});
});