diff --git a/src/ECS/Core/ComponentStorage.ts b/src/ECS/Core/ComponentStorage.ts index d7fdea97..62edd013 100644 --- a/src/ECS/Core/ComponentStorage.ts +++ b/src/ECS/Core/ComponentStorage.ts @@ -75,6 +75,14 @@ export class ComponentRegistry { public static getAllRegisteredTypes(): Map { return new Map(this.componentTypes); } + + /** + * 重置注册表(用于测试) + */ + public static reset(): void { + this.componentTypes.clear(); + this.nextBitIndex = 0; + } } /** diff --git a/tests/ECS/Core/ComponentStorage.test.ts b/tests/ECS/Core/ComponentStorage.test.ts new file mode 100644 index 00000000..36dc1092 --- /dev/null +++ b/tests/ECS/Core/ComponentStorage.test.ts @@ -0,0 +1,639 @@ +import { + ComponentRegistry, + ComponentStorage, + ComponentStorageManager, + ComponentType +} from '../../../src/ECS/Core/ComponentStorage'; +import { Component } from '../../../src/ECS/Component'; + +// 测试组件类 +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(); + (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).toBe(BigInt(1)); // 2^0 + expect(mask2).toBe(BigInt(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; + + beforeEach(() => { + // 重置注册表 + (ComponentRegistry as any).componentTypes = new Map(); + (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(); + (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)的掩码 + const expectedMask = BigInt(1) | BigInt(2); // 0b11 + expect(mask).toBe(expectedMask); + }); + + test('没有组件的实体应该有零掩码', () => { + const mask = manager.getComponentMask(999); + expect(mask).toBe(BigInt(0)); + }); + + test('添加和移除组件应该更新掩码', () => { + ComponentRegistry.register(TestComponent); + ComponentRegistry.register(PositionComponent); + + manager.addComponent(1, new TestComponent(100)); + let mask = manager.getComponentMask(1); + expect(mask).toBe(BigInt(1)); + + manager.addComponent(1, new PositionComponent(10, 20)); + mask = manager.getComponentMask(1); + expect(mask).toBe(BigInt(3)); // 0b11 + + manager.removeComponent(1, TestComponent); + mask = manager.getComponentMask(1); + expect(mask).toBe(BigInt(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); + }); + }); +}); \ No newline at end of file diff --git a/tests/ECS/Core/EventBus.test.ts b/tests/ECS/Core/EventBus.test.ts new file mode 100644 index 00000000..fab7b900 --- /dev/null +++ b/tests/ECS/Core/EventBus.test.ts @@ -0,0 +1,529 @@ +import { EventBus, GlobalEventBus, EventHandler, AsyncEventHandler } from '../../../src/ECS/Core/EventBus'; +import { IEventListenerConfig, IEventStats } from '../../../src/Types'; +import { ECSEventType, EventPriority } from '../../../src/ECS/CoreEvents'; + +// 测试数据接口 +interface TestEventData { + message: string; + value: number; +} + +interface MockEntityData { + entityId: number; + timestamp: number; + eventId?: string; + source?: string; +} + +interface MockComponentData { + entityId: number; + componentType: string; + timestamp: number; + eventId?: string; + source?: string; +} + +describe('EventBus - 事件总线测试', () => { + let eventBus: EventBus; + + beforeEach(() => { + eventBus = new EventBus(false); + }); + + afterEach(() => { + eventBus.clear(); + }); + + describe('基本事件功能', () => { + test('应该能够创建事件总线', () => { + expect(eventBus).toBeInstanceOf(EventBus); + }); + + test('应该能够发射和监听同步事件', () => { + let receivedData: TestEventData | null = null; + + const listenerId = eventBus.on('test:event', (data) => { + receivedData = data; + }); + + const testData: TestEventData = { message: 'hello', value: 42 }; + eventBus.emit('test:event', testData); + + expect(receivedData).not.toBeNull(); + expect(receivedData!.message).toBe('hello'); + expect(receivedData!.value).toBe(42); + expect(typeof listenerId).toBe('string'); + }); + + test('应该能够发射和监听异步事件', async () => { + let receivedData: TestEventData | null = null; + + eventBus.onAsync('async:event', async (data) => { + receivedData = data; + }); + + const testData: TestEventData = { message: 'async hello', value: 100 }; + await eventBus.emitAsync('async:event', testData); + + expect(receivedData).not.toBeNull(); + expect(receivedData!.message).toBe('async hello'); + expect(receivedData!.value).toBe(100); + }); + + test('应该能够一次性监听事件', () => { + let callCount = 0; + + eventBus.once('once:event', () => { + callCount++; + }); + + eventBus.emit('once:event', { message: 'first', value: 1 }); + eventBus.emit('once:event', { message: 'second', value: 2 }); + + expect(callCount).toBe(1); + }); + + test('应该能够移除事件监听器', () => { + let callCount = 0; + + const listenerId = eventBus.on('removable:event', () => { + callCount++; + }); + + eventBus.emit('removable:event', { message: 'test', value: 1 }); + expect(callCount).toBe(1); + + const removed = eventBus.off('removable:event', listenerId); + expect(removed).toBe(true); + + eventBus.emit('removable:event', { message: 'test', value: 2 }); + expect(callCount).toBe(1); // 应该没有增加 + }); + + test('应该能够移除所有事件监听器', () => { + let callCount1 = 0; + let callCount2 = 0; + + eventBus.on('multi:event', () => { callCount1++; }); + eventBus.on('multi:event', () => { callCount2++; }); + + eventBus.emit('multi:event', { message: 'test', value: 1 }); + expect(callCount1).toBe(1); + expect(callCount2).toBe(1); + + eventBus.offAll('multi:event'); + + eventBus.emit('multi:event', { message: 'test', value: 2 }); + expect(callCount1).toBe(1); + expect(callCount2).toBe(1); + }); + }); + + describe('事件配置和优先级', () => { + test('应该能够使用事件监听器配置', () => { + let receivedData: TestEventData | null = null; + const config: IEventListenerConfig = { + once: false, + priority: EventPriority.HIGH, + async: false + }; + + eventBus.on('config:event', (data) => { + receivedData = data; + }, config); + + eventBus.emit('config:event', { message: 'configured', value: 99 }); + expect(receivedData).not.toBeNull(); + expect(receivedData!.message).toBe('configured'); + }); + + test('应该能够检查事件是否有监听器', () => { + expect(eventBus.hasListeners('nonexistent:event')).toBe(false); + + eventBus.on('existing:event', () => {}); + expect(eventBus.hasListeners('existing:event')).toBe(true); + }); + + test('应该能够获取监听器数量', () => { + expect(eventBus.getListenerCount('count:event')).toBe(0); + + eventBus.on('count:event', () => {}); + eventBus.on('count:event', () => {}); + + expect(eventBus.getListenerCount('count:event')).toBe(2); + }); + }); + + describe('系统配置和管理', () => { + test('应该能够启用和禁用事件系统', () => { + let callCount = 0; + + eventBus.on('disable:event', () => { callCount++; }); + + eventBus.emit('disable:event', { message: 'enabled', value: 1 }); + expect(callCount).toBe(1); + + eventBus.setEnabled(false); + eventBus.emit('disable:event', { message: 'disabled', value: 2 }); + expect(callCount).toBe(1); // 应该没有增加 + + eventBus.setEnabled(true); + eventBus.emit('disable:event', { message: 'enabled again', value: 3 }); + expect(callCount).toBe(2); + }); + + test('应该能够设置调试模式', () => { + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + + eventBus.setDebugMode(true); + eventBus.on('debug:event', () => {}); + eventBus.emit('debug:event', { message: 'debug', value: 1 }); + + expect(consoleSpy).toHaveBeenCalled(); + + consoleSpy.mockRestore(); + }); + + test('应该能够设置最大监听器数量', () => { + expect(() => { + eventBus.setMaxListeners(5); + }).not.toThrow(); + }); + + test('应该能够清空所有监听器', () => { + eventBus.on('clear:event1', () => {}); + eventBus.on('clear:event2', () => {}); + + expect(eventBus.hasListeners('clear:event1')).toBe(true); + expect(eventBus.hasListeners('clear:event2')).toBe(true); + + eventBus.clear(); + + expect(eventBus.hasListeners('clear:event1')).toBe(false); + expect(eventBus.hasListeners('clear:event2')).toBe(false); + }); + }); + + describe('批处理功能', () => { + test('应该能够设置批处理配置', () => { + expect(() => { + eventBus.setBatchConfig('batch:event', 5, 100); + }).not.toThrow(); + }); + + test('应该能够刷新批处理队列', () => { + eventBus.setBatchConfig('flush:event', 10, 200); + + expect(() => { + eventBus.flushBatch('flush:event'); + }).not.toThrow(); + }); + }); + + describe('事件统计', () => { + test('应该能够获取事件统计信息', () => { + eventBus.on('stats:event', () => {}); + eventBus.emit('stats:event', { message: 'stat test', value: 1 }); + eventBus.emit('stats:event', { message: 'stat test', value: 2 }); + + const stats = eventBus.getStats('stats:event') as IEventStats; + + expect(stats).toBeDefined(); + expect(stats.eventType).toBe('stats:event'); + expect(stats.triggerCount).toBe(2); + expect(stats.listenerCount).toBe(1); + }); + + test('应该能够获取所有事件的统计信息', () => { + eventBus.on('all-stats:event1', () => {}); + eventBus.on('all-stats:event2', () => {}); + eventBus.emit('all-stats:event1', { message: 'test1', value: 1 }); + eventBus.emit('all-stats:event2', { message: 'test2', value: 2 }); + + const allStats = eventBus.getStats() as Map; + + expect(allStats).toBeInstanceOf(Map); + expect(allStats.size).toBeGreaterThan(0); + }); + + test('应该能够重置事件统计', () => { + eventBus.on('reset:event', () => {}); + eventBus.emit('reset:event', { message: 'before reset', value: 1 }); + + let stats = eventBus.getStats('reset:event') as IEventStats; + expect(stats.triggerCount).toBe(1); + + eventBus.resetStats('reset:event'); + + stats = eventBus.getStats('reset:event') as IEventStats; + expect(stats.triggerCount).toBe(0); + }); + }); + + describe('预定义ECS事件', () => { + test('应该能够发射和监听实体创建事件', () => { + let receivedData: MockEntityData | null = null; + + eventBus.onEntityCreated((data) => { + receivedData = data; + }); + + const entityData: MockEntityData = { + entityId: 1, + timestamp: Date.now() + }; + + eventBus.emitEntityCreated(entityData); + + expect(receivedData).not.toBeNull(); + expect(receivedData!.entityId).toBe(1); + expect(receivedData!.timestamp).toBeDefined(); + expect(receivedData!.eventId).toBeDefined(); + expect(receivedData!.source).toBeDefined(); + }); + + test('应该能够发射和监听组件添加事件', () => { + let receivedData: MockComponentData | null = null; + + eventBus.onComponentAdded((data) => { + receivedData = data; + }); + + const componentData: MockComponentData = { + entityId: 1, + componentType: 'PositionComponent', + timestamp: Date.now() + }; + + eventBus.emitComponentAdded(componentData); + + expect(receivedData).not.toBeNull(); + expect(receivedData!.entityId).toBe(1); + expect(receivedData!.componentType).toBe('PositionComponent'); + }); + + test('应该能够监听系统错误事件', () => { + let errorReceived = false; + + eventBus.onSystemError(() => { + errorReceived = true; + }); + + eventBus.emit(ECSEventType.SYSTEM_ERROR, { + systemName: 'TestSystem', + error: 'Test error' + }); + + expect(errorReceived).toBe(true); + }); + + test('应该能够监听性能警告事件', () => { + let warningReceived = false; + + eventBus.onPerformanceWarning(() => { + warningReceived = true; + }); + + eventBus.emitPerformanceWarning({ + level: 'warning', + message: 'FPS dropped below threshold', + data: { fps: 30, threshold: 60 }, + timestamp: Date.now() + }); + + expect(warningReceived).toBe(true); + }); + + test('应该能够发射其他预定义事件', () => { + let entityDestroyedReceived = false; + let componentRemovedReceived = false; + let systemAddedReceived = false; + let systemRemovedReceived = false; + let sceneChangedReceived = false; + + eventBus.on(ECSEventType.ENTITY_DESTROYED, () => { entityDestroyedReceived = true; }); + eventBus.on(ECSEventType.COMPONENT_REMOVED, () => { componentRemovedReceived = true; }); + eventBus.on(ECSEventType.SYSTEM_ADDED, () => { systemAddedReceived = true; }); + eventBus.on(ECSEventType.SYSTEM_REMOVED, () => { systemRemovedReceived = true; }); + eventBus.on(ECSEventType.SCENE_ACTIVATED, () => { sceneChangedReceived = true; }); + + eventBus.emitEntityDestroyed({ entityId: 1, timestamp: Date.now() }); + eventBus.emitComponentRemoved({ entityId: 1, componentType: 'Test', timestamp: Date.now() }); + eventBus.emitSystemAdded({ systemName: 'TestSystem', systemType: 'EntitySystem', timestamp: Date.now() }); + eventBus.emitSystemRemoved({ systemName: 'TestSystem', systemType: 'EntitySystem', timestamp: Date.now() }); + eventBus.emitSceneChanged({ sceneName: 'TestScene', timestamp: Date.now() }); + + expect(entityDestroyedReceived).toBe(true); + expect(componentRemovedReceived).toBe(true); + expect(systemAddedReceived).toBe(true); + expect(systemRemovedReceived).toBe(true); + expect(sceneChangedReceived).toBe(true); + }); + }); + + describe('数据增强功能', () => { + test('应该能够自动增强事件数据', () => { + let receivedData: any = null; + + eventBus.on('enhanced:event', (data) => { + receivedData = data; + }); + + const originalData = { message: 'test' }; + eventBus.emit('enhanced:event', originalData); + + expect(receivedData.message).toBe('test'); + expect(receivedData.timestamp).toBeDefined(); + expect(receivedData.eventId).toBeDefined(); + expect(receivedData.source).toBeDefined(); + expect(typeof receivedData.timestamp).toBe('number'); + expect(typeof receivedData.eventId).toBe('string'); + expect(receivedData.source).toBe('EventBus'); + }); + + test('增强数据时不应该覆盖现有属性', () => { + let receivedData: any = null; + + eventBus.on('no-override:event', (data) => { + receivedData = data; + }); + + const originalData = { + message: 'test', + timestamp: 12345, + eventId: 'custom-id', + source: 'CustomSource' + }; + eventBus.emit('no-override:event', originalData); + + expect(receivedData.timestamp).toBe(12345); + expect(receivedData.eventId).toBe('custom-id'); + expect(receivedData.source).toBe('CustomSource'); + }); + }); + + describe('边界情况和错误处理', () => { + test('移除不存在的监听器应该返回false', () => { + const removed = eventBus.off('nonexistent:event', 'invalid-id'); + expect(removed).toBe(false); + }); + + test('获取不存在事件的监听器数量应该返回0', () => { + const count = eventBus.getListenerCount('nonexistent:event'); + expect(count).toBe(0); + }); + + test('检查不存在事件的监听器应该返回false', () => { + const hasListeners = eventBus.hasListeners('nonexistent:event'); + expect(hasListeners).toBe(false); + }); + + test('对不存在的事件类型执行操作应该安全', () => { + expect(() => { + eventBus.offAll('nonexistent:event'); + eventBus.resetStats('nonexistent:event'); + eventBus.flushBatch('nonexistent:event'); + }).not.toThrow(); + }); + + test('传入空数据应该安全处理', () => { + let receivedData: any = null; + + eventBus.on('null-data:event', (data) => { + receivedData = data; + }); + + expect(() => { + eventBus.emit('null-data:event', null); + eventBus.emit('null-data:event', undefined); + eventBus.emit('null-data:event', {}); + }).not.toThrow(); + + expect(receivedData).toBeDefined(); + }); + }); +}); + +describe('GlobalEventBus - 全局事件总线测试', () => { + afterEach(() => { + // 重置全局实例以避免测试间干扰 + GlobalEventBus.reset(); + }); + + test('应该能够获取全局事件总线实例', () => { + const instance1 = GlobalEventBus.getInstance(); + const instance2 = GlobalEventBus.getInstance(); + + expect(instance1).toBeInstanceOf(EventBus); + expect(instance1).toBe(instance2); // 应该是同一个实例 + }); + + test('应该能够重置全局事件总线实例', () => { + const instance1 = GlobalEventBus.getInstance(); + instance1.on('test:event', () => {}); + + expect(instance1.hasListeners('test:event')).toBe(true); + + const instance2 = GlobalEventBus.reset(); + + expect(instance2).toBeInstanceOf(EventBus); + expect(instance2).not.toBe(instance1); + expect(instance2.hasListeners('test:event')).toBe(false); + }); + + test('应该能够使用调试模式创建全局实例', () => { + const instance = GlobalEventBus.getInstance(true); + expect(instance).toBeInstanceOf(EventBus); + }); +}); + +describe('事件装饰器测试', () => { + // Mock class for testing decorators + class TestClass { + public receivedEvents: any[] = []; + + @EventHandler('decorator:event') + handleEvent(data: any) { + this.receivedEvents.push(data); + } + + @AsyncEventHandler('async:decorator:event') + async handleAsyncEvent(data: any) { + this.receivedEvents.push(data); + } + } + + test('EventHandler装饰器应该能够自动注册监听器', () => { + const instance = new TestClass(); + + // 手动调用初始化方法(在实际应用中会在构造函数中调用) + if (typeof instance.initEventListeners === 'function') { + (instance as any).initEventListeners(); + } + + const eventBus = GlobalEventBus.getInstance(); + eventBus.emit('decorator:event', { message: 'decorated event' }); + + expect(instance.receivedEvents.length).toBe(1); + expect(instance.receivedEvents[0].message).toBe('decorated event'); + }); + + test('AsyncEventHandler装饰器应该能够自动注册异步监听器', async () => { + const instance = new TestClass(); + + // 手动调用初始化方法 + if (typeof instance.initEventListeners === 'function') { + (instance as any).initEventListeners(); + } + + const eventBus = GlobalEventBus.getInstance(); + await eventBus.emitAsync('async:decorator:event', { message: 'async decorated event' }); + + expect(instance.receivedEvents.length).toBe(1); + expect(instance.receivedEvents[0].message).toBe('async decorated event'); + }); + + afterEach(() => { + GlobalEventBus.reset(); + }); +}); \ No newline at end of file diff --git a/tests/ECS/Core/QuerySystem.test.ts b/tests/ECS/Core/QuerySystem.test.ts index 3fbbfb5e..95901faa 100644 --- a/tests/ECS/Core/QuerySystem.test.ts +++ b/tests/ECS/Core/QuerySystem.test.ts @@ -1,6 +1,7 @@ -import { QuerySystem } from '../../../src/ECS/Core/QuerySystem'; +import { QuerySystem, QueryBuilder, QueryConditionType } from '../../../src/ECS/Core/QuerySystem'; import { Entity } from '../../../src/ECS/Entity'; import { Component } from '../../../src/ECS/Component'; +import { ComponentRegistry, ComponentType } from '../../../src/ECS/Core/ComponentStorage'; // 测试组件 class PositionComponent extends Component { @@ -611,4 +612,217 @@ describe('QuerySystem - 查询系统测试', () => { expect(stats.queryStats.totalQueries).toBeGreaterThan(0); }); }); + + describe('QueryBuilder - 查询构建器功能', () => { + let builder: QueryBuilder; + + beforeEach(() => { + builder = new QueryBuilder(querySystem); + + // 设置测试实体的组件 + entities[0].addComponent(new PositionComponent(10, 20)); + entities[1].addComponent(new VelocityComponent(1, 1)); + entities[2].addComponent(new PositionComponent(30, 40)); + entities[2].addComponent(new VelocityComponent(2, 2)); + }); + + test('应该能够创建查询构建器', () => { + expect(builder).toBeInstanceOf(QueryBuilder); + }); + + test('应该能够构建包含所有组件的查询', () => { + const result = builder + .withAll(PositionComponent) + .execute(); + + expect(result.entities.length).toBe(2); + expect(result.entities).toContain(entities[0]); + expect(result.entities).toContain(entities[2]); + }); + + test('应该能够构建包含任意组件的查询', () => { + const result = builder + .withAny(PositionComponent, VelocityComponent) + .execute(); + + expect(result.entities.length).toBe(3); + }); + + test('应该能够构建排除组件的查询', () => { + const result = builder + .without(HealthComponent) + .execute(); + + expect(result.entities.length).toBe(3); + }); + + test('应该能够重置查询构建器', () => { + builder.withAll(PositionComponent); + const resetBuilder = builder.reset(); + + expect(resetBuilder).toBe(builder); + + const result = builder.execute(); + expect(result.entities.length).toBe(0); // 没有条件,返回空结果 + }); + + test('多条件查询应该返回空结果(当前实现限制)', () => { + const result = builder + .withAll(PositionComponent) + .withAny(VelocityComponent) + .execute(); + + // 当前实现只支持单一条件,多条件返回空结果 + expect(result.entities.length).toBe(0); + }); + + test('链式调用应该工作正常', () => { + const result = builder + .withAll(PositionComponent) + .execute(); + + expect(result).toBeDefined(); + expect(result.executionTime).toBeGreaterThanOrEqual(0); + }); + }); + + describe('高级查询功能', () => { + test('应该能够按标签查询实体', () => { + entities[0].tag = 100; + entities[1].tag = 200; + entities[2].tag = 100; + + const result = querySystem.queryByTag(100); + + expect(result.entities.length).toBe(2); + expect(result.entities).toContain(entities[0]); + expect(result.entities).toContain(entities[2]); + }); + + test('应该能够按名称查询实体', () => { + const result = querySystem.queryByName('Entity_1'); + + expect(result.entities.length).toBe(1); + expect(result.entities).toContain(entities[1]); + }); + + test('应该能够查询包含任意指定组件的实体', () => { + entities[0].addComponent(new PositionComponent(10, 20)); + entities[1].addComponent(new VelocityComponent(1, 1)); + entities[2].addComponent(new HealthComponent(100)); + + const result = querySystem.queryAny(PositionComponent, VelocityComponent); + + expect(result.entities.length).toBe(2); + expect(result.entities).toContain(entities[0]); + expect(result.entities).toContain(entities[1]); + }); + + test('应该能够查询不包含指定组件的实体', () => { + entities[0].addComponent(new PositionComponent(10, 20)); + entities[1].addComponent(new VelocityComponent(1, 1)); + + const result = querySystem.queryNone(HealthComponent); + + expect(result.entities.length).toBe(entities.length); + }); + + test('应该能够按单个组件类型查询', () => { + entities[0].addComponent(new PositionComponent(10, 20)); + entities[1].addComponent(new PositionComponent(30, 40)); + + const result = querySystem.queryByComponent(PositionComponent); + + expect(result.entities.length).toBe(2); + expect(result.entities).toContain(entities[0]); + expect(result.entities).toContain(entities[1]); + }); + }); + + describe('实体管理功能', () => { + test('应该能够添加和移除单个实体', () => { + const newEntity = new Entity('NewEntity', 999); + + querySystem.addEntity(newEntity); + let stats = querySystem.getStats(); + expect(stats.entityCount).toBe(entities.length + 1); + + querySystem.removeEntity(newEntity); + stats = querySystem.getStats(); + expect(stats.entityCount).toBe(entities.length); + }); + + test('应该能够批量添加实体', () => { + const newEntities = [ + new Entity('Batch1', 997), + new Entity('Batch2', 998), + new Entity('Batch3', 999) + ]; + + querySystem.addEntities(newEntities); + const stats = querySystem.getStats(); + expect(stats.entityCount).toBe(entities.length + 3); + }); + + test('应该能够批量添加实体(无重复检查)', () => { + const newEntities = [ + new Entity('Unchecked1', 995), + new Entity('Unchecked2', 996) + ]; + + querySystem.addEntitiesUnchecked(newEntities); + const stats = querySystem.getStats(); + expect(stats.entityCount).toBe(entities.length + 2); + }); + + test('应该能够批量更新组件', () => { + entities[0].addComponent(new PositionComponent(10, 20)); + entities[1].addComponent(new VelocityComponent(1, 1)); + + const updates = [ + { entityId: entities[0].id, componentMask: BigInt(0b1011) }, + { entityId: entities[1].id, componentMask: BigInt(0b1101) } + ]; + + expect(() => { + querySystem.batchUpdateComponents(updates); + }).not.toThrow(); + }); + + test('应该能够标记实体为脏', () => { + entities[0].addComponent(new PositionComponent(10, 20)); + + expect(() => { + querySystem.markEntityDirty(entities[0], [PositionComponent]); + }).not.toThrow(); + }); + }); + + describe('性能优化和配置', () => { + test('应该能够手动触发性能优化', () => { + expect(() => { + querySystem.optimizePerformance(); + }).not.toThrow(); + }); + + test('应该能够配置脏标记系统', () => { + expect(() => { + querySystem.configureDirtyTracking(10, 16); + }).not.toThrow(); + }); + + test('应该能够管理帧生命周期', () => { + expect(() => { + querySystem.beginFrame(); + querySystem.endFrame(); + }).not.toThrow(); + }); + + test('应该能够获取实体的原型信息', () => { + entities[0].addComponent(new PositionComponent(10, 20)); + + const archetype = querySystem.getEntityArchetype(entities[0]); + expect(archetype === undefined || typeof archetype === 'object').toBe(true); + }); + }); }); \ No newline at end of file