Files
esengine/tests/ECS/Core/ComponentStorage.test.ts
2025-07-30 14:14:04 +08:00

639 lines
24 KiB
TypeScript

import {
ComponentRegistry,
ComponentStorage,
ComponentStorageManager,
ComponentType
} from '../../../src/ECS/Core/ComponentStorage';
import { Component } from '../../../src/ECS/Component';
import { BigIntFactory } from '../../../src/ECS/Utils/BigIntCompatibility';
// 测试组件类(默认使用原始存储)
class TestComponent extends Component {
constructor(public value: number = 0) {
super();
}
}
class PositionComponent extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class VelocityComponent extends Component {
constructor(public vx: number = 0, public vy: number = 0) {
super();
}
}
class HealthComponent extends Component {
constructor(public health: number = 100, public maxHealth: number = 100) {
super();
}
}
describe('ComponentRegistry - 组件注册表测试', () => {
beforeEach(() => {
// 重置注册表状态
(ComponentRegistry as any).componentTypes = new Map<Function, number>();
(ComponentRegistry as any).nextBitIndex = 0;
});
describe('组件注册功能', () => {
test('应该能够注册组件类型', () => {
const bitIndex = ComponentRegistry.register(TestComponent);
expect(bitIndex).toBe(0);
expect(ComponentRegistry.isRegistered(TestComponent)).toBe(true);
});
test('重复注册相同组件应该返回相同的位索引', () => {
const bitIndex1 = ComponentRegistry.register(TestComponent);
const bitIndex2 = ComponentRegistry.register(TestComponent);
expect(bitIndex1).toBe(bitIndex2);
expect(bitIndex1).toBe(0);
});
test('应该能够注册多个组件类型', () => {
const bitIndex1 = ComponentRegistry.register(TestComponent);
const bitIndex2 = ComponentRegistry.register(PositionComponent);
const bitIndex3 = ComponentRegistry.register(VelocityComponent);
expect(bitIndex1).toBe(0);
expect(bitIndex2).toBe(1);
expect(bitIndex3).toBe(2);
});
test('应该能够检查组件是否已注册', () => {
expect(ComponentRegistry.isRegistered(TestComponent)).toBe(false);
ComponentRegistry.register(TestComponent);
expect(ComponentRegistry.isRegistered(TestComponent)).toBe(true);
});
test('超过最大组件数量应该抛出错误', () => {
// 设置较小的最大组件数量用于测试
(ComponentRegistry as any).maxComponents = 3;
ComponentRegistry.register(TestComponent);
ComponentRegistry.register(PositionComponent);
ComponentRegistry.register(VelocityComponent);
expect(() => {
ComponentRegistry.register(HealthComponent);
}).toThrow('Maximum number of component types (3) exceeded');
});
});
describe('位掩码功能', () => {
test('应该能够获取组件的位掩码', () => {
ComponentRegistry.register(TestComponent);
ComponentRegistry.register(PositionComponent);
const mask1 = ComponentRegistry.getBitMask(TestComponent);
const mask2 = ComponentRegistry.getBitMask(PositionComponent);
expect(mask1.toString()).toBe('1'); // 2^0
expect(mask2.toString()).toBe('2'); // 2^1
});
test('应该能够获取组件的位索引', () => {
ComponentRegistry.register(TestComponent);
ComponentRegistry.register(PositionComponent);
const index1 = ComponentRegistry.getBitIndex(TestComponent);
const index2 = ComponentRegistry.getBitIndex(PositionComponent);
expect(index1).toBe(0);
expect(index2).toBe(1);
});
test('获取未注册组件的位掩码应该抛出错误', () => {
expect(() => {
ComponentRegistry.getBitMask(TestComponent);
}).toThrow('Component type TestComponent is not registered');
});
test('获取未注册组件的位索引应该抛出错误', () => {
expect(() => {
ComponentRegistry.getBitIndex(TestComponent);
}).toThrow('Component type TestComponent is not registered');
});
});
describe('注册表管理', () => {
test('应该能够获取所有已注册的组件类型', () => {
ComponentRegistry.register(TestComponent);
ComponentRegistry.register(PositionComponent);
const allTypes = ComponentRegistry.getAllRegisteredTypes();
expect(allTypes.size).toBe(2);
expect(allTypes.has(TestComponent)).toBe(true);
expect(allTypes.has(PositionComponent)).toBe(true);
expect(allTypes.get(TestComponent)).toBe(0);
expect(allTypes.get(PositionComponent)).toBe(1);
});
test('返回的注册表副本不应该影响原始数据', () => {
ComponentRegistry.register(TestComponent);
const allTypes = ComponentRegistry.getAllRegisteredTypes();
allTypes.set(PositionComponent, 999);
expect(ComponentRegistry.isRegistered(PositionComponent)).toBe(false);
});
});
});
describe('ComponentStorage - 组件存储器测试', () => {
let storage: ComponentStorage<TestComponent>;
beforeEach(() => {
// 重置注册表
(ComponentRegistry as any).componentTypes = new Map<Function, number>();
(ComponentRegistry as any).nextBitIndex = 0;
storage = new ComponentStorage(TestComponent);
});
describe('基本存储功能', () => {
test('应该能够创建组件存储器', () => {
expect(storage).toBeInstanceOf(ComponentStorage);
expect(storage.size).toBe(0);
expect(storage.type).toBe(TestComponent);
});
test('应该能够添加组件', () => {
const component = new TestComponent(100);
storage.addComponent(1, component);
expect(storage.size).toBe(1);
expect(storage.hasComponent(1)).toBe(true);
expect(storage.getComponent(1)).toBe(component);
});
test('重复添加组件到同一实体应该抛出错误', () => {
const component1 = new TestComponent(100);
const component2 = new TestComponent(200);
storage.addComponent(1, component1);
expect(() => {
storage.addComponent(1, component2);
}).toThrow('Entity 1 already has component TestComponent');
});
test('应该能够获取组件', () => {
const component = new TestComponent(100);
storage.addComponent(1, component);
const retrieved = storage.getComponent(1);
expect(retrieved).toBe(component);
expect(retrieved!.value).toBe(100);
});
test('获取不存在的组件应该返回null', () => {
const retrieved = storage.getComponent(999);
expect(retrieved).toBeNull();
});
test('应该能够检查实体是否有组件', () => {
expect(storage.hasComponent(1)).toBe(false);
storage.addComponent(1, new TestComponent(100));
expect(storage.hasComponent(1)).toBe(true);
});
test('应该能够移除组件', () => {
const component = new TestComponent(100);
storage.addComponent(1, component);
const removed = storage.removeComponent(1);
expect(removed).toBe(component);
expect(storage.size).toBe(0);
expect(storage.hasComponent(1)).toBe(false);
expect(storage.getComponent(1)).toBeNull();
});
test('移除不存在的组件应该返回null', () => {
const removed = storage.removeComponent(999);
expect(removed).toBeNull();
});
});
describe('遍历和批量操作', () => {
test('应该能够遍历所有组件', () => {
const component1 = new TestComponent(100);
const component2 = new TestComponent(200);
const component3 = new TestComponent(300);
storage.addComponent(1, component1);
storage.addComponent(2, component2);
storage.addComponent(3, component3);
const results: Array<{component: TestComponent, entityId: number, index: number}> = [];
storage.forEach((component, entityId, index) => {
results.push({ component, entityId, index });
});
expect(results.length).toBe(3);
expect(results.find(r => r.entityId === 1)?.component).toBe(component1);
expect(results.find(r => r.entityId === 2)?.component).toBe(component2);
expect(results.find(r => r.entityId === 3)?.component).toBe(component3);
});
test('应该能够获取密集数组', () => {
const component1 = new TestComponent(100);
const component2 = new TestComponent(200);
storage.addComponent(1, component1);
storage.addComponent(2, component2);
const { components, entityIds } = storage.getDenseArray();
expect(components.length).toBe(2);
expect(entityIds.length).toBe(2);
expect(components).toContain(component1);
expect(components).toContain(component2);
expect(entityIds).toContain(1);
expect(entityIds).toContain(2);
});
test('应该能够清空所有组件', () => {
storage.addComponent(1, new TestComponent(100));
storage.addComponent(2, new TestComponent(200));
expect(storage.size).toBe(2);
storage.clear();
expect(storage.size).toBe(0);
expect(storage.hasComponent(1)).toBe(false);
expect(storage.hasComponent(2)).toBe(false);
});
});
describe('内存管理和优化', () => {
test('应该能够重用空闲索引', () => {
const component1 = new TestComponent(100);
const component2 = new TestComponent(200);
const component3 = new TestComponent(300);
// 添加三个组件
storage.addComponent(1, component1);
storage.addComponent(2, component2);
storage.addComponent(3, component3);
// 移除中间的组件
storage.removeComponent(2);
// 添加新组件应该重用空闲索引
const component4 = new TestComponent(400);
storage.addComponent(4, component4);
expect(storage.size).toBe(3);
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);
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.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('应该能够获取存储统计信息', () => {
storage.addComponent(1, new TestComponent(100));
storage.addComponent(2, new TestComponent(200));
storage.addComponent(3, new TestComponent(300));
// 移除一个组件创建空洞
storage.removeComponent(2);
const stats = storage.getStats();
expect(stats.totalSlots).toBe(3);
expect(stats.usedSlots).toBe(2);
expect(stats.freeSlots).toBe(1);
expect(stats.fragmentation).toBeCloseTo(1/3);
});
});
describe('边界情况', () => {
test('空存储器的统计信息应该正确', () => {
const stats = storage.getStats();
expect(stats.totalSlots).toBe(0);
expect(stats.usedSlots).toBe(0);
expect(stats.freeSlots).toBe(0);
expect(stats.fragmentation).toBe(0);
});
test('遍历空存储器应该安全', () => {
let callCount = 0;
storage.forEach(() => { callCount++; });
expect(callCount).toBe(0);
});
test('获取空存储器的密集数组应该返回空数组', () => {
const { components, entityIds } = storage.getDenseArray();
expect(components.length).toBe(0);
expect(entityIds.length).toBe(0);
});
});
});
describe('ComponentStorageManager - 组件存储管理器测试', () => {
let manager: ComponentStorageManager;
beforeEach(() => {
// 重置注册表
(ComponentRegistry as any).componentTypes = new Map<Function, number>();
(ComponentRegistry as any).nextBitIndex = 0;
manager = new ComponentStorageManager();
});
describe('存储器管理', () => {
test('应该能够创建组件存储管理器', () => {
expect(manager).toBeInstanceOf(ComponentStorageManager);
});
test('应该能够获取或创建组件存储器', () => {
const storage1 = manager.getStorage(TestComponent);
const storage2 = manager.getStorage(TestComponent);
expect(storage1).toBeInstanceOf(ComponentStorage);
expect(storage1).toBe(storage2); // 应该是同一个实例
});
test('不同组件类型应该有不同的存储器', () => {
const storage1 = manager.getStorage(TestComponent);
const storage2 = manager.getStorage(PositionComponent);
expect(storage1).not.toBe(storage2);
expect(storage1.type).toBe(TestComponent);
expect(storage2.type).toBe(PositionComponent);
});
});
describe('组件操作', () => {
test('应该能够添加组件', () => {
const component = new TestComponent(100);
manager.addComponent(1, component);
expect(manager.hasComponent(1, TestComponent)).toBe(true);
expect(manager.getComponent(1, TestComponent)).toBe(component);
});
test('应该能够获取组件', () => {
const testComponent = new TestComponent(100);
const positionComponent = new PositionComponent(10, 20);
manager.addComponent(1, testComponent);
manager.addComponent(1, positionComponent);
expect(manager.getComponent(1, TestComponent)).toBe(testComponent);
expect(manager.getComponent(1, PositionComponent)).toBe(positionComponent);
});
test('获取不存在的组件应该返回null', () => {
const result = manager.getComponent(999, TestComponent);
expect(result).toBeNull();
});
test('应该能够检查实体是否有组件', () => {
expect(manager.hasComponent(1, TestComponent)).toBe(false);
manager.addComponent(1, new TestComponent(100));
expect(manager.hasComponent(1, TestComponent)).toBe(true);
});
test('应该能够移除组件', () => {
const component = new TestComponent(100);
manager.addComponent(1, component);
const removed = manager.removeComponent(1, TestComponent);
expect(removed).toBe(component);
expect(manager.hasComponent(1, TestComponent)).toBe(false);
});
test('移除不存在的组件应该返回null', () => {
const removed = manager.removeComponent(999, TestComponent);
expect(removed).toBeNull();
});
test('应该能够移除实体的所有组件', () => {
manager.addComponent(1, new TestComponent(100));
manager.addComponent(1, new PositionComponent(10, 20));
manager.addComponent(1, new VelocityComponent(1, 2));
expect(manager.hasComponent(1, TestComponent)).toBe(true);
expect(manager.hasComponent(1, PositionComponent)).toBe(true);
expect(manager.hasComponent(1, VelocityComponent)).toBe(true);
manager.removeAllComponents(1);
expect(manager.hasComponent(1, TestComponent)).toBe(false);
expect(manager.hasComponent(1, PositionComponent)).toBe(false);
expect(manager.hasComponent(1, VelocityComponent)).toBe(false);
});
});
describe('位掩码功能', () => {
test('应该能够获取实体的组件位掩码', () => {
// 确保组件已注册
ComponentRegistry.register(TestComponent);
ComponentRegistry.register(PositionComponent);
ComponentRegistry.register(VelocityComponent);
manager.addComponent(1, new TestComponent(100));
manager.addComponent(1, new PositionComponent(10, 20));
const mask = manager.getComponentMask(1);
// 应该包含TestComponent(位0)和PositionComponent(位1)的掩码
expect(mask.toString()).toBe('3'); // 1 | 2 = 3
});
test('没有组件的实体应该有零掩码', () => {
const mask = manager.getComponentMask(999);
expect(mask.isZero()).toBe(true);
});
test('添加和移除组件应该更新掩码', () => {
ComponentRegistry.register(TestComponent);
ComponentRegistry.register(PositionComponent);
manager.addComponent(1, new TestComponent(100));
let mask = manager.getComponentMask(1);
expect(mask.toString()).toBe('1');
manager.addComponent(1, new PositionComponent(10, 20));
mask = manager.getComponentMask(1);
expect(mask.toString()).toBe('3'); // 0b11
manager.removeComponent(1, TestComponent);
mask = manager.getComponentMask(1);
expect(mask.toString()).toBe('2'); // 0b10
});
});
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('应该能够获取所有存储器的统计信息', () => {
manager.addComponent(1, new TestComponent(100));
manager.addComponent(2, new TestComponent(200));
manager.addComponent(1, new PositionComponent(10, 20));
const allStats = manager.getAllStats();
expect(allStats).toBeInstanceOf(Map);
expect(allStats.size).toBe(2);
expect(allStats.has('TestComponent')).toBe(true);
expect(allStats.has('PositionComponent')).toBe(true);
const testStats = allStats.get('TestComponent');
expect(testStats.usedSlots).toBe(2);
const positionStats = allStats.get('PositionComponent');
expect(positionStats.usedSlots).toBe(1);
});
test('应该能够清空所有存储器', () => {
manager.addComponent(1, new TestComponent(100));
manager.addComponent(2, new PositionComponent(10, 20));
manager.addComponent(3, new VelocityComponent(1, 2));
expect(manager.hasComponent(1, TestComponent)).toBe(true);
expect(manager.hasComponent(2, PositionComponent)).toBe(true);
expect(manager.hasComponent(3, VelocityComponent)).toBe(true);
manager.clear();
expect(manager.hasComponent(1, TestComponent)).toBe(false);
expect(manager.hasComponent(2, PositionComponent)).toBe(false);
expect(manager.hasComponent(3, VelocityComponent)).toBe(false);
});
});
describe('边界情况和错误处理', () => {
test('对不存在存储器的操作应该安全处理', () => {
expect(manager.getComponent(1, TestComponent)).toBeNull();
expect(manager.hasComponent(1, TestComponent)).toBe(false);
expect(manager.removeComponent(1, TestComponent)).toBeNull();
});
test('移除所有组件对空实体应该安全', () => {
expect(() => {
manager.removeAllComponents(999);
}).not.toThrow();
});
test('统计信息应该处理未知组件类型', () => {
// 创建一个匿名组件类来测试未知类型处理
const AnonymousComponent = class extends Component {};
manager.addComponent(1, new AnonymousComponent());
const stats = manager.getAllStats();
// 检查是否有任何统计条目(匿名类可能显示为空字符串或其他名称)
expect(stats.size).toBeGreaterThan(0);
});
test('多次清空应该安全', () => {
manager.addComponent(1, new TestComponent(100));
manager.clear();
manager.clear(); // 第二次清空应该安全
expect(manager.hasComponent(1, TestComponent)).toBe(false);
});
});
describe('性能和内存测试', () => {
test('大量组件操作应该高效', () => {
const entityCount = 1000;
// 添加大量组件
for (let i = 1; i <= entityCount; i++) {
manager.addComponent(i, new TestComponent(i));
if (i % 2 === 0) {
manager.addComponent(i, new PositionComponent(i, i));
}
}
// 验证添加成功
expect(manager.hasComponent(1, TestComponent)).toBe(true);
expect(manager.hasComponent(500, TestComponent)).toBe(true);
expect(manager.hasComponent(2, PositionComponent)).toBe(true);
expect(manager.hasComponent(1, PositionComponent)).toBe(false);
// 移除部分组件
for (let i = 1; i <= entityCount; i += 3) {
manager.removeComponent(i, TestComponent);
}
// 验证移除成功
expect(manager.hasComponent(1, TestComponent)).toBe(false);
expect(manager.hasComponent(2, TestComponent)).toBe(true);
const stats = manager.getAllStats();
expect(stats.get('TestComponent').usedSlots).toBeLessThan(entityCount);
});
});
});