使用Lerna 和 monorepo管理项目结构
This commit is contained in:
277
packages/core/tests/ECS/Component.test.ts
Normal file
277
packages/core/tests/ECS/Component.test.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import { Component } from '../../src/ECS/Component';
|
||||
import { Entity } from '../../src/ECS/Entity';
|
||||
|
||||
// 测试组件
|
||||
class TestComponent extends Component {
|
||||
public value: number = 100;
|
||||
public onAddedCalled = false;
|
||||
public onRemovedCalled = false;
|
||||
public onEnabledCalled = false;
|
||||
public onDisabledCalled = false;
|
||||
public updateCalled = false;
|
||||
|
||||
public override onAddedToEntity(): void {
|
||||
this.onAddedCalled = true;
|
||||
}
|
||||
|
||||
public override onRemovedFromEntity(): void {
|
||||
this.onRemovedCalled = true;
|
||||
}
|
||||
|
||||
public override onEnabled(): void {
|
||||
this.onEnabledCalled = true;
|
||||
}
|
||||
|
||||
public override onDisabled(): void {
|
||||
this.onDisabledCalled = true;
|
||||
}
|
||||
|
||||
public override update(): void {
|
||||
this.updateCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
class AnotherTestComponent extends Component {
|
||||
public name: string = 'test';
|
||||
}
|
||||
|
||||
describe('Component - 组件基类测试', () => {
|
||||
let component: TestComponent;
|
||||
let entity: Entity;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset component ID generator to avoid BigInt issues
|
||||
Component._idGenerator = 0;
|
||||
component = new TestComponent();
|
||||
entity = new Entity('TestEntity', 1);
|
||||
});
|
||||
|
||||
describe('基本功能', () => {
|
||||
test('应该能够创建组件实例', () => {
|
||||
expect(component).toBeInstanceOf(Component);
|
||||
expect(component).toBeInstanceOf(TestComponent);
|
||||
expect(component.id).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('每个组件应该有唯一的ID', () => {
|
||||
const component1 = new TestComponent();
|
||||
const component2 = new TestComponent();
|
||||
const component3 = new AnotherTestComponent();
|
||||
|
||||
expect(component1.id).not.toBe(component2.id);
|
||||
expect(component2.id).not.toBe(component3.id);
|
||||
expect(component1.id).not.toBe(component3.id);
|
||||
});
|
||||
|
||||
test('组件ID应该递增分配', () => {
|
||||
const startId = Component._idGenerator;
|
||||
const component1 = new TestComponent();
|
||||
const component2 = new TestComponent();
|
||||
|
||||
expect(component2.id).toBe(component1.id + 1);
|
||||
expect(component1.id).toBeGreaterThanOrEqual(startId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('启用状态管理', () => {
|
||||
test('组件默认应该是启用的', () => {
|
||||
expect(component.enabled).toBe(true);
|
||||
});
|
||||
|
||||
test('设置组件禁用状态应该工作', () => {
|
||||
component.enabled = false;
|
||||
expect(component.enabled).toBe(false);
|
||||
expect(component.onDisabledCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('重新启用组件应该工作', () => {
|
||||
component.enabled = false;
|
||||
component.onDisabledCalled = false;
|
||||
component.onEnabledCalled = false;
|
||||
|
||||
component.enabled = true;
|
||||
expect(component.enabled).toBe(true);
|
||||
expect(component.onEnabledCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('设置相同的状态不应该触发回调', () => {
|
||||
component.enabled = true; // 已经是true
|
||||
expect(component.onEnabledCalled).toBe(false);
|
||||
|
||||
component.enabled = false;
|
||||
component.onDisabledCalled = false;
|
||||
|
||||
component.enabled = false; // 已经是false
|
||||
expect(component.onDisabledCalled).toBe(false);
|
||||
});
|
||||
|
||||
test('组件启用状态应该受实体状态影响', () => {
|
||||
entity.addComponent(component);
|
||||
expect(component.enabled).toBe(true);
|
||||
|
||||
// 禁用实体应该让组件表现为禁用
|
||||
entity.enabled = false;
|
||||
expect(component.enabled).toBe(false);
|
||||
|
||||
// 重新启用实体
|
||||
entity.enabled = true;
|
||||
expect(component.enabled).toBe(true);
|
||||
});
|
||||
|
||||
test('组件自身禁用时即使实体启用也应该是禁用的', () => {
|
||||
entity.addComponent(component);
|
||||
|
||||
component.enabled = false;
|
||||
entity.enabled = true;
|
||||
|
||||
expect(component.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('没有实体时组件状态应该只取决于自身', () => {
|
||||
// 组件还没有添加到实体
|
||||
expect(component.enabled).toBe(true);
|
||||
|
||||
component.enabled = false;
|
||||
expect(component.enabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('更新顺序', () => {
|
||||
test('组件默认更新顺序应该是0', () => {
|
||||
expect(component.updateOrder).toBe(0);
|
||||
});
|
||||
|
||||
test('应该能够设置更新顺序', () => {
|
||||
component.updateOrder = 10;
|
||||
expect(component.updateOrder).toBe(10);
|
||||
|
||||
component.updateOrder = -5;
|
||||
expect(component.updateOrder).toBe(-5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('生命周期回调', () => {
|
||||
test('添加到实体时应该调用onAddedToEntity', () => {
|
||||
expect(component.onAddedCalled).toBe(false);
|
||||
|
||||
entity.addComponent(component);
|
||||
expect(component.onAddedCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('从实体移除时应该调用onRemovedFromEntity', () => {
|
||||
entity.addComponent(component);
|
||||
expect(component.onRemovedCalled).toBe(false);
|
||||
|
||||
entity.removeComponent(component);
|
||||
expect(component.onRemovedCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('启用时应该调用onEnabled', () => {
|
||||
component.enabled = false;
|
||||
component.onEnabledCalled = false;
|
||||
|
||||
component.enabled = true;
|
||||
expect(component.onEnabledCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('禁用时应该调用onDisabled', () => {
|
||||
expect(component.onDisabledCalled).toBe(false);
|
||||
|
||||
component.enabled = false;
|
||||
expect(component.onDisabledCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('更新方法', () => {
|
||||
test('update方法应该可以被调用', () => {
|
||||
expect(component.updateCalled).toBe(false);
|
||||
|
||||
component.update();
|
||||
expect(component.updateCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('基类的默认生命周期方法应该安全调用', () => {
|
||||
const baseComponent = new (class extends Component {})();
|
||||
|
||||
// 这些方法不应该抛出异常
|
||||
expect(() => {
|
||||
baseComponent.onAddedToEntity();
|
||||
baseComponent.onRemovedFromEntity();
|
||||
baseComponent.onEnabled();
|
||||
baseComponent.onDisabled();
|
||||
baseComponent.update();
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('实体关联', () => {
|
||||
test('组件应该能够访问其所属的实体', () => {
|
||||
entity.addComponent(component);
|
||||
expect(component.entity).toBe(entity);
|
||||
});
|
||||
|
||||
test('组件移除后entity引用行为', () => {
|
||||
entity.addComponent(component);
|
||||
expect(component.entity).toBe(entity);
|
||||
|
||||
entity.removeComponent(component);
|
||||
// 移除后entity引用可能被清空,这是正常行为
|
||||
// 具体行为取决于实现,这里只测试不会抛出异常
|
||||
expect(() => {
|
||||
const _ = component.entity;
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况', () => {
|
||||
test('多次启用禁用应该工作正常', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
component.enabled = false;
|
||||
expect(component.enabled).toBe(false);
|
||||
|
||||
component.enabled = true;
|
||||
expect(component.enabled).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('极端更新顺序值应该被接受', () => {
|
||||
component.updateOrder = 999999;
|
||||
expect(component.updateOrder).toBe(999999);
|
||||
|
||||
component.updateOrder = -999999;
|
||||
expect(component.updateOrder).toBe(-999999);
|
||||
});
|
||||
|
||||
test('大量组件创建应该有不同的ID', () => {
|
||||
const components: Component[] = [];
|
||||
const count = 1000;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
components.push(new TestComponent());
|
||||
}
|
||||
|
||||
// 检查所有ID都不同
|
||||
const ids = new Set(components.map(c => c.id));
|
||||
expect(ids.size).toBe(count);
|
||||
});
|
||||
});
|
||||
|
||||
describe('继承和多态', () => {
|
||||
test('不同类型的组件应该都继承自Component', () => {
|
||||
const test1 = new TestComponent();
|
||||
const test2 = new AnotherTestComponent();
|
||||
|
||||
expect(test1).toBeInstanceOf(Component);
|
||||
expect(test2).toBeInstanceOf(Component);
|
||||
expect(test1).toBeInstanceOf(TestComponent);
|
||||
expect(test2).toBeInstanceOf(AnotherTestComponent);
|
||||
});
|
||||
|
||||
test('组件应该能够重写基类方法', () => {
|
||||
const test = new TestComponent();
|
||||
test.update();
|
||||
|
||||
expect(test.updateCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
60
packages/core/tests/ECS/Core/BatchCreateTest.test.ts
Normal file
60
packages/core/tests/ECS/Core/BatchCreateTest.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { EntityManager } from '../../../src/ECS/Core/EntityManager';
|
||||
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||||
|
||||
describe('批量创建功能测试', () => {
|
||||
let entityManager: EntityManager;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
entityManager = new EntityManager();
|
||||
});
|
||||
|
||||
test('批量创建实体应该使用ID作为名称', () => {
|
||||
const entities = entityManager.createEntitiesBatch(5, "Test");
|
||||
|
||||
expect(entities).toHaveLength(5);
|
||||
|
||||
// 验证实体名称使用ID而不是索引
|
||||
for (const entity of entities) {
|
||||
expect(entity.name).toBe(`Test_${entity.id}`);
|
||||
}
|
||||
|
||||
// 验证ID是唯一的
|
||||
const ids = entities.map(e => e.id);
|
||||
const uniqueIds = new Set(ids);
|
||||
expect(uniqueIds.size).toBe(5);
|
||||
});
|
||||
|
||||
test('单个创建实体应该使用ID作为默认名称', () => {
|
||||
const entity1 = entityManager.createEntity();
|
||||
const entity2 = entityManager.createEntity();
|
||||
const entity3 = entityManager.createEntity("CustomName");
|
||||
|
||||
expect(entity1.name).toBe(`Entity_${entity1.id}`);
|
||||
expect(entity2.name).toBe(`Entity_${entity2.id}`);
|
||||
expect(entity3.name).toBe("CustomName");
|
||||
|
||||
// 确保ID是连续的或至少是唯一的
|
||||
expect(entity1.id).not.toBe(entity2.id);
|
||||
expect(entity2.id).not.toBe(entity3.id);
|
||||
});
|
||||
|
||||
test('混合创建方式的名称应该一致', () => {
|
||||
// 先单个创建
|
||||
const single = entityManager.createEntity();
|
||||
|
||||
// 再批量创建
|
||||
const batch = entityManager.createEntitiesBatch(3, "Batch");
|
||||
|
||||
// 再单个创建
|
||||
const single2 = entityManager.createEntity();
|
||||
|
||||
// 验证名称格式一致
|
||||
expect(single.name).toBe(`Entity_${single.id}`);
|
||||
expect(single2.name).toBe(`Entity_${single2.id}`);
|
||||
|
||||
for (const entity of batch) {
|
||||
expect(entity.name).toBe(`Batch_${entity.id}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
308
packages/core/tests/ECS/Core/ComponentIndexManager.test.ts
Normal file
308
packages/core/tests/ECS/Core/ComponentIndexManager.test.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
import { EntityManager } from '../../../src/ECS/Core/EntityManager';
|
||||
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
|
||||
// 测试用组件
|
||||
class TestComponent extends Component {
|
||||
public value: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
if (args.length >= 1) this.value = args[0] as number;
|
||||
}
|
||||
}
|
||||
|
||||
class AnotherTestComponent extends Component {
|
||||
public name: string = 'test';
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
if (args.length >= 1) this.name = args[0] as string;
|
||||
}
|
||||
}
|
||||
|
||||
describe('ComponentIndexManager功能测试', () => {
|
||||
let entityManager: EntityManager;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
entityManager = new EntityManager();
|
||||
});
|
||||
|
||||
describe('基本功能测试', () => {
|
||||
test('应该能够正确创建空实体', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
|
||||
expect(entity).toBeDefined();
|
||||
expect(entity.name).toBe('TestEntity');
|
||||
expect(entity.components.length).toBe(0);
|
||||
expect(entity.id).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('应该能够正确管理实体的组件索引', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
const component = new TestComponent(42);
|
||||
|
||||
// 添加组件前
|
||||
expect(entity.hasComponent(TestComponent)).toBe(false);
|
||||
|
||||
// 添加组件
|
||||
entity.addComponent(component);
|
||||
|
||||
// 添加组件后
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.getComponent(TestComponent)).toBe(component);
|
||||
expect(entity.components.length).toBe(1);
|
||||
});
|
||||
|
||||
test('应该能够正确处理组件查询', () => {
|
||||
const entity1 = entityManager.createEntity('Entity1');
|
||||
const entity2 = entityManager.createEntity('Entity2');
|
||||
|
||||
entity1.addComponent(new TestComponent(1));
|
||||
entity2.addComponent(new TestComponent(2));
|
||||
entity2.addComponent(new AnotherTestComponent('test2'));
|
||||
|
||||
// 查询包含TestComponent的实体
|
||||
const entitiesWithTest = entityManager.getEntitiesWithComponent(TestComponent);
|
||||
expect(entitiesWithTest).toHaveLength(2);
|
||||
expect(entitiesWithTest).toContain(entity1);
|
||||
expect(entitiesWithTest).toContain(entity2);
|
||||
|
||||
// 查询包含AnotherTestComponent的实体
|
||||
const entitiesWithAnother = entityManager.getEntitiesWithComponent(AnotherTestComponent);
|
||||
expect(entitiesWithAnother).toHaveLength(1);
|
||||
expect(entitiesWithAnother).toContain(entity2);
|
||||
});
|
||||
|
||||
test('应该能够正确处理复杂查询', () => {
|
||||
const entity1 = entityManager.createEntity('Entity1');
|
||||
const entity2 = entityManager.createEntity('Entity2');
|
||||
const entity3 = entityManager.createEntity('Entity3');
|
||||
|
||||
// entity1: TestComponent
|
||||
entity1.addComponent(new TestComponent(1));
|
||||
|
||||
// entity2: TestComponent + AnotherTestComponent
|
||||
entity2.addComponent(new TestComponent(2));
|
||||
entity2.addComponent(new AnotherTestComponent('test2'));
|
||||
|
||||
// entity3: AnotherTestComponent
|
||||
entity3.addComponent(new AnotherTestComponent('test3'));
|
||||
|
||||
// AND查询:同时包含两个组件的实体
|
||||
const bothComponents = entityManager.queryWithComponentIndex(
|
||||
[TestComponent, AnotherTestComponent],
|
||||
'AND'
|
||||
);
|
||||
expect(bothComponents.size).toBe(1);
|
||||
expect(bothComponents.has(entity2)).toBe(true);
|
||||
|
||||
// OR查询:包含任一组件的实体
|
||||
const eitherComponent = entityManager.queryWithComponentIndex(
|
||||
[TestComponent, AnotherTestComponent],
|
||||
'OR'
|
||||
);
|
||||
expect(eitherComponent.size).toBe(3);
|
||||
expect(eitherComponent.has(entity1)).toBe(true);
|
||||
expect(eitherComponent.has(entity2)).toBe(true);
|
||||
expect(eitherComponent.has(entity3)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('组件移除功能测试', () => {
|
||||
test('应该能够正确移除组件并更新索引', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
const component = new TestComponent(42);
|
||||
|
||||
// 添加组件
|
||||
entity.addComponent(component);
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
|
||||
// 移除组件
|
||||
entity.removeComponent(component);
|
||||
expect(entity.hasComponent(TestComponent)).toBe(false);
|
||||
expect(entity.getComponent(TestComponent)).toBeNull();
|
||||
expect(entity.components.length).toBe(0);
|
||||
|
||||
// 索引应该被正确更新
|
||||
const entitiesWithTest = entityManager.getEntitiesWithComponent(TestComponent);
|
||||
expect(entitiesWithTest).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('应该能够正确处理实体销毁', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
entity.addComponent(new TestComponent(42));
|
||||
|
||||
// 确认实体存在且有组件
|
||||
expect(entityManager.getEntity(entity.id)).toBe(entity);
|
||||
expect(entityManager.getEntitiesWithComponent(TestComponent)).toHaveLength(1);
|
||||
|
||||
// 销毁实体
|
||||
const destroyed = entityManager.destroyEntity(entity);
|
||||
expect(destroyed).toBe(true);
|
||||
|
||||
// 确认实体被正确销毁
|
||||
expect(entityManager.getEntity(entity.id)).toBeNull();
|
||||
expect(entityManager.getEntitiesWithComponent(TestComponent)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('批量操作功能测试', () => {
|
||||
test('应该能够正确处理批量创建实体', () => {
|
||||
const entities = entityManager.createEntitiesBatch(5, 'BatchEntity');
|
||||
|
||||
expect(entities).toHaveLength(5);
|
||||
entities.forEach((entity, index) => {
|
||||
expect(entity.name).toMatch(/^BatchEntity_\d+$/);
|
||||
expect(entity.components.length).toBe(0);
|
||||
expect(entityManager.getEntity(entity.id)).toBe(entity);
|
||||
});
|
||||
|
||||
expect(entityManager.entityCount).toBe(5);
|
||||
});
|
||||
|
||||
test('批量创建的实体应该有正确的索引', () => {
|
||||
const entities = entityManager.createEntitiesBatch(3, 'IndexTest');
|
||||
|
||||
// 给第一个和第三个实体添加组件
|
||||
entities[0].addComponent(new TestComponent(1));
|
||||
entities[2].addComponent(new TestComponent(3));
|
||||
|
||||
const entitiesWithTest = entityManager.getEntitiesWithComponent(TestComponent);
|
||||
expect(entitiesWithTest).toHaveLength(2);
|
||||
expect(entitiesWithTest).toContain(entities[0]);
|
||||
expect(entitiesWithTest).toContain(entities[2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('查询构建器功能测试', () => {
|
||||
test('应该能够使用查询构建器进行复杂查询', () => {
|
||||
const entity1 = entityManager.createEntity('Active1');
|
||||
const entity2 = entityManager.createEntity('Active2');
|
||||
const entity3 = entityManager.createEntity('Inactive');
|
||||
|
||||
entity1.addComponent(new TestComponent(1));
|
||||
entity1.active = true;
|
||||
|
||||
entity2.addComponent(new TestComponent(2));
|
||||
entity2.addComponent(new AnotherTestComponent('test2'));
|
||||
entity2.active = true;
|
||||
|
||||
entity3.addComponent(new TestComponent(3));
|
||||
entity3.active = false;
|
||||
|
||||
// 查询激活状态且包含TestComponent的实体
|
||||
const activeWithTest = entityManager.query()
|
||||
.withAll(TestComponent)
|
||||
.active()
|
||||
.execute();
|
||||
|
||||
expect(activeWithTest).toHaveLength(2);
|
||||
expect(activeWithTest).toContain(entity1);
|
||||
expect(activeWithTest).toContain(entity2);
|
||||
expect(activeWithTest).not.toContain(entity3);
|
||||
|
||||
// 查询同时包含两个组件的实体
|
||||
const withBothComponents = entityManager.query()
|
||||
.withAll(TestComponent, AnotherTestComponent)
|
||||
.execute();
|
||||
|
||||
expect(withBothComponents).toHaveLength(1);
|
||||
expect(withBothComponents).toContain(entity2);
|
||||
});
|
||||
|
||||
test('查询构建器应该支持自定义过滤条件', () => {
|
||||
const entity1 = entityManager.createEntity('Player1');
|
||||
const entity2 = entityManager.createEntity('Enemy1');
|
||||
const entity3 = entityManager.createEntity('Player2');
|
||||
|
||||
entity1.addComponent(new TestComponent(100));
|
||||
entity2.addComponent(new TestComponent(50));
|
||||
entity3.addComponent(new TestComponent(200));
|
||||
|
||||
// 查询名称以"Player"开头且TestComponent值大于150的实体
|
||||
const strongPlayers = entityManager.query()
|
||||
.withAll(TestComponent)
|
||||
.where(entity => entity.name.startsWith('Player'))
|
||||
.where(entity => {
|
||||
const testComp = entity.getComponent(TestComponent);
|
||||
return testComp !== null && testComp.value > 150;
|
||||
})
|
||||
.execute();
|
||||
|
||||
expect(strongPlayers).toHaveLength(1);
|
||||
expect(strongPlayers).toContain(entity3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('统计信息功能测试', () => {
|
||||
test('应该能够获取正确的统计信息', () => {
|
||||
// 创建一些实体和组件
|
||||
const entity1 = entityManager.createEntity('StatTest1');
|
||||
const entity2 = entityManager.createEntity('StatTest2');
|
||||
|
||||
entity1.addComponent(new TestComponent(1));
|
||||
entity2.addComponent(new TestComponent(2));
|
||||
entity2.addComponent(new AnotherTestComponent('test'));
|
||||
|
||||
const stats = entityManager.getOptimizationStats();
|
||||
|
||||
expect(stats).toBeDefined();
|
||||
expect(stats.componentIndex).toBeDefined();
|
||||
expect(stats.componentIndex.type).toBe('hash');
|
||||
expect(stats.archetypeSystem).toBeDefined();
|
||||
expect(stats.dirtyTracking).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够正确统计实体数量', () => {
|
||||
expect(entityManager.entityCount).toBe(0);
|
||||
expect(entityManager.activeEntityCount).toBe(0);
|
||||
|
||||
const entity1 = entityManager.createEntity('Count1');
|
||||
const entity2 = entityManager.createEntity('Count2');
|
||||
|
||||
expect(entityManager.entityCount).toBe(2);
|
||||
expect(entityManager.activeEntityCount).toBe(2);
|
||||
|
||||
entity1.active = false;
|
||||
expect(entityManager.activeEntityCount).toBe(1);
|
||||
|
||||
entityManager.destroyEntity(entity2);
|
||||
expect(entityManager.entityCount).toBe(1);
|
||||
expect(entityManager.activeEntityCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况测试', () => {
|
||||
test('应该能够处理空组件列表的查询', () => {
|
||||
const entity = entityManager.createEntity('EmptyTest');
|
||||
|
||||
const emptyQuery = entityManager.queryWithComponentIndex([], 'AND');
|
||||
expect(emptyQuery.size).toBe(0);
|
||||
|
||||
const emptyOrQuery = entityManager.queryWithComponentIndex([], 'OR');
|
||||
expect(emptyOrQuery.size).toBe(0);
|
||||
});
|
||||
|
||||
test('应该能够处理不存在的组件类型查询', () => {
|
||||
class NonExistentComponent extends Component {}
|
||||
|
||||
const entities = entityManager.getEntitiesWithComponent(NonExistentComponent);
|
||||
expect(entities).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('应该能够处理重复添加相同组件类型', () => {
|
||||
const entity = entityManager.createEntity('DuplicateTest');
|
||||
const component1 = new TestComponent(1);
|
||||
const component2 = new TestComponent(2);
|
||||
|
||||
entity.addComponent(component1);
|
||||
expect(() => entity.addComponent(component2)).toThrow();
|
||||
|
||||
// 第一个组件应该仍然存在
|
||||
expect(entity.getComponent(TestComponent)).toBe(component1);
|
||||
});
|
||||
});
|
||||
});
|
||||
470
packages/core/tests/ECS/Core/ComponentPool.test.ts
Normal file
470
packages/core/tests/ECS/Core/ComponentPool.test.ts
Normal file
@@ -0,0 +1,470 @@
|
||||
import { ComponentPool, ComponentPoolManager } from '../../../src/ECS/Core/ComponentPool';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
|
||||
// 测试用组件类
|
||||
class TestComponent extends Component {
|
||||
public value: number = 0;
|
||||
public name: string = '';
|
||||
|
||||
reset(): void {
|
||||
this.value = 0;
|
||||
this.name = '';
|
||||
}
|
||||
}
|
||||
|
||||
class AnotherTestComponent extends Component {
|
||||
public data: string = '';
|
||||
|
||||
reset(): void {
|
||||
this.data = '';
|
||||
}
|
||||
}
|
||||
|
||||
describe('ComponentPool - 组件对象池测试', () => {
|
||||
let pool: ComponentPool<TestComponent>;
|
||||
let createFn: () => TestComponent;
|
||||
let resetFn: (component: TestComponent) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
createFn = () => new TestComponent();
|
||||
resetFn = (component: TestComponent) => component.reset();
|
||||
pool = new ComponentPool(createFn, resetFn, 10);
|
||||
});
|
||||
|
||||
describe('基本功能测试', () => {
|
||||
it('应该能够创建组件池', () => {
|
||||
expect(pool).toBeDefined();
|
||||
expect(pool.getAvailableCount()).toBe(0);
|
||||
expect(pool.getMaxSize()).toBe(10);
|
||||
});
|
||||
|
||||
it('应该能够获取组件实例', () => {
|
||||
const component = pool.acquire();
|
||||
expect(component).toBeInstanceOf(TestComponent);
|
||||
expect(component.value).toBe(0);
|
||||
});
|
||||
|
||||
it('第一次获取应该创建新实例', () => {
|
||||
const component = pool.acquire();
|
||||
expect(component).toBeInstanceOf(TestComponent);
|
||||
expect(pool.getAvailableCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('应该能够释放组件回池中', () => {
|
||||
const component = pool.acquire();
|
||||
component.value = 42;
|
||||
component.name = 'test';
|
||||
|
||||
pool.release(component);
|
||||
|
||||
expect(pool.getAvailableCount()).toBe(1);
|
||||
expect(component.value).toBe(0); // 应该被重置
|
||||
expect(component.name).toBe(''); // 应该被重置
|
||||
});
|
||||
|
||||
it('从池中获取的组件应该是之前释放的', () => {
|
||||
const component1 = pool.acquire();
|
||||
pool.release(component1);
|
||||
|
||||
const component2 = pool.acquire();
|
||||
expect(component2).toBe(component1); // 应该是同一个实例
|
||||
});
|
||||
});
|
||||
|
||||
describe('池容量管理', () => {
|
||||
it('应该能够设置最大容量', () => {
|
||||
const smallPool = new ComponentPool(createFn, resetFn, 2);
|
||||
expect(smallPool.getMaxSize()).toBe(2);
|
||||
});
|
||||
|
||||
it('超过最大容量的组件不应该被存储', () => {
|
||||
const smallPool = new ComponentPool(createFn, resetFn, 2);
|
||||
|
||||
const components = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
components.push(smallPool.acquire());
|
||||
}
|
||||
|
||||
// 释放所有组件
|
||||
components.forEach(comp => smallPool.release(comp));
|
||||
|
||||
// 只有2个组件被存储在池中
|
||||
expect(smallPool.getAvailableCount()).toBe(2);
|
||||
});
|
||||
|
||||
it('应该正确处理默认最大容量', () => {
|
||||
const defaultPool = new ComponentPool(createFn);
|
||||
expect(defaultPool.getMaxSize()).toBe(1000); // 默认值
|
||||
});
|
||||
});
|
||||
|
||||
describe('重置功能测试', () => {
|
||||
it('没有重置函数时应该正常工作', () => {
|
||||
const poolWithoutReset = new ComponentPool<TestComponent>(createFn);
|
||||
const component = poolWithoutReset.acquire();
|
||||
component.value = 42;
|
||||
|
||||
poolWithoutReset.release(component);
|
||||
|
||||
// 没有重置函数,值应该保持不变
|
||||
expect(component.value).toBe(42);
|
||||
expect(poolWithoutReset.getAvailableCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('重置函数应该在释放时被调用', () => {
|
||||
const mockReset = jest.fn();
|
||||
const poolWithMockReset = new ComponentPool(createFn, mockReset);
|
||||
|
||||
const component = poolWithMockReset.acquire();
|
||||
poolWithMockReset.release(component);
|
||||
|
||||
expect(mockReset).toHaveBeenCalledWith(component);
|
||||
});
|
||||
});
|
||||
|
||||
describe('预热功能', () => {
|
||||
it('应该能够预填充对象池', () => {
|
||||
pool.prewarm(5);
|
||||
expect(pool.getAvailableCount()).toBe(5);
|
||||
});
|
||||
|
||||
it('预热不应该超过最大容量', () => {
|
||||
pool.prewarm(15); // 超过最大容量10
|
||||
expect(pool.getAvailableCount()).toBe(10);
|
||||
});
|
||||
|
||||
it('预热0个对象应该安全', () => {
|
||||
pool.prewarm(0);
|
||||
expect(pool.getAvailableCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('多次预热应该正确累加', () => {
|
||||
pool.prewarm(3);
|
||||
pool.prewarm(2);
|
||||
expect(pool.getAvailableCount()).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('清空功能', () => {
|
||||
it('应该能够清空对象池', () => {
|
||||
pool.prewarm(5);
|
||||
expect(pool.getAvailableCount()).toBe(5);
|
||||
|
||||
pool.clear();
|
||||
expect(pool.getAvailableCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('空池清空应该安全', () => {
|
||||
pool.clear();
|
||||
expect(pool.getAvailableCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况测试', () => {
|
||||
it('应该处理连续的获取和释放', () => {
|
||||
const components: TestComponent[] = [];
|
||||
|
||||
// 获取多个组件
|
||||
for (let i = 0; i < 5; i++) {
|
||||
components.push(pool.acquire());
|
||||
}
|
||||
|
||||
// 释放所有组件
|
||||
components.forEach(comp => pool.release(comp));
|
||||
expect(pool.getAvailableCount()).toBe(5);
|
||||
|
||||
// 再次获取应该复用之前的实例
|
||||
const reusedComponents: TestComponent[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
reusedComponents.push(pool.acquire());
|
||||
}
|
||||
|
||||
expect(pool.getAvailableCount()).toBe(0);
|
||||
|
||||
// 验证复用的组件确实是之前的实例
|
||||
components.forEach(originalComp => {
|
||||
expect(reusedComponents).toContain(originalComp);
|
||||
});
|
||||
});
|
||||
|
||||
it('应该处理空池的多次获取', () => {
|
||||
const component1 = pool.acquire();
|
||||
const component2 = pool.acquire();
|
||||
const component3 = pool.acquire();
|
||||
|
||||
expect(component1).not.toBe(component2);
|
||||
expect(component2).not.toBe(component3);
|
||||
expect(component1).not.toBe(component3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ComponentPoolManager - 组件池管理器测试', () => {
|
||||
let manager: ComponentPoolManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = ComponentPoolManager.getInstance();
|
||||
// 重置管理器以确保测试隔离
|
||||
manager.reset();
|
||||
});
|
||||
|
||||
describe('单例模式测试', () => {
|
||||
it('应该返回同一个实例', () => {
|
||||
const instance1 = ComponentPoolManager.getInstance();
|
||||
const instance2 = ComponentPoolManager.getInstance();
|
||||
expect(instance1).toBe(instance2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('池注册和管理', () => {
|
||||
it('应该能够注册组件池', () => {
|
||||
const createFn = () => new TestComponent();
|
||||
const resetFn = (comp: TestComponent) => comp.reset();
|
||||
|
||||
manager.registerPool('TestComponent', createFn, resetFn, 20);
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.has('TestComponent')).toBe(true);
|
||||
expect(stats.get('TestComponent')?.maxSize).toBe(20);
|
||||
});
|
||||
|
||||
it('应该能够注册多个不同类型的池', () => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent());
|
||||
manager.registerPool('AnotherTestComponent', () => new AnotherTestComponent());
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.size).toBe(2);
|
||||
expect(stats.has('TestComponent')).toBe(true);
|
||||
expect(stats.has('AnotherTestComponent')).toBe(true);
|
||||
});
|
||||
|
||||
it('注册池时应该使用默认参数', () => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent());
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.get('TestComponent')?.maxSize).toBe(1000); // 默认值
|
||||
});
|
||||
});
|
||||
|
||||
describe('组件获取和释放', () => {
|
||||
beforeEach(() => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent(), (comp) => comp.reset());
|
||||
});
|
||||
|
||||
it('应该能够获取组件实例', () => {
|
||||
const component = manager.acquireComponent<TestComponent>('TestComponent');
|
||||
expect(component).toBeInstanceOf(TestComponent);
|
||||
});
|
||||
|
||||
it('获取未注册池的组件应该返回null', () => {
|
||||
const component = manager.acquireComponent<TestComponent>('UnknownComponent');
|
||||
expect(component).toBeNull();
|
||||
});
|
||||
|
||||
it('应该能够释放组件实例', () => {
|
||||
const component = manager.acquireComponent<TestComponent>('TestComponent')!;
|
||||
component.value = 42;
|
||||
|
||||
manager.releaseComponent('TestComponent', component);
|
||||
|
||||
// 验证组件被重置并返回池中
|
||||
const reusedComponent = manager.acquireComponent<TestComponent>('TestComponent');
|
||||
expect(reusedComponent).toBe(component);
|
||||
expect(reusedComponent!.value).toBe(0); // 应该被重置
|
||||
});
|
||||
|
||||
it('释放到未注册池应该安全处理', () => {
|
||||
const component = new TestComponent();
|
||||
expect(() => {
|
||||
manager.releaseComponent('UnknownComponent', component);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('批量操作', () => {
|
||||
beforeEach(() => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent());
|
||||
manager.registerPool('AnotherTestComponent', () => new AnotherTestComponent());
|
||||
});
|
||||
|
||||
it('应该能够预热所有池', () => {
|
||||
manager.prewarmAll(5);
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.get('TestComponent')?.available).toBe(5);
|
||||
expect(stats.get('AnotherTestComponent')?.available).toBe(5);
|
||||
});
|
||||
|
||||
it('应该能够清空所有池', () => {
|
||||
manager.prewarmAll(5);
|
||||
manager.clearAll();
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.get('TestComponent')?.available).toBe(0);
|
||||
expect(stats.get('AnotherTestComponent')?.available).toBe(0);
|
||||
});
|
||||
|
||||
it('prewarmAll应该使用默认值', () => {
|
||||
manager.prewarmAll(); // 默认100
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.get('TestComponent')?.available).toBe(100);
|
||||
expect(stats.get('AnotherTestComponent')?.available).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('统计信息', () => {
|
||||
beforeEach(() => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent(), undefined, 50);
|
||||
});
|
||||
|
||||
it('应该能够获取池统计信息', () => {
|
||||
const stats = manager.getPoolStats();
|
||||
|
||||
expect(stats.has('TestComponent')).toBe(true);
|
||||
const poolStat = stats.get('TestComponent')!;
|
||||
expect(poolStat.available).toBe(0);
|
||||
expect(poolStat.maxSize).toBe(50);
|
||||
});
|
||||
|
||||
it('应该能够获取池利用率信息', () => {
|
||||
manager.prewarmAll(30); // 预热30个
|
||||
|
||||
const utilization = manager.getPoolUtilization();
|
||||
const testComponentUtil = utilization.get('TestComponent')!;
|
||||
|
||||
expect(testComponentUtil.used).toBe(20); // 50 - 30 = 20
|
||||
expect(testComponentUtil.total).toBe(50);
|
||||
expect(testComponentUtil.utilization).toBe(40); // 20/50 * 100
|
||||
});
|
||||
|
||||
it('应该能够获取指定组件的池利用率', () => {
|
||||
manager.prewarmAll(30);
|
||||
|
||||
const utilization = manager.getComponentUtilization('TestComponent');
|
||||
expect(utilization).toBe(40); // (50-30)/50 * 100
|
||||
});
|
||||
|
||||
it('获取未注册组件的利用率应该返回0', () => {
|
||||
const utilization = manager.getComponentUtilization('UnknownComponent');
|
||||
expect(utilization).toBe(0);
|
||||
});
|
||||
|
||||
it('空池的利用率应该为0', () => {
|
||||
// 完全重置管理器,移除所有池
|
||||
manager.reset();
|
||||
const utilization = manager.getComponentUtilization('TestComponent');
|
||||
expect(utilization).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('动态使用场景测试', () => {
|
||||
beforeEach(() => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent(), (comp) => comp.reset(), 10);
|
||||
});
|
||||
|
||||
it('应该正确跟踪组件使用情况', () => {
|
||||
// 获取5个组件
|
||||
const components = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const comp = manager.acquireComponent<TestComponent>('TestComponent')!;
|
||||
comp.value = i;
|
||||
components.push(comp);
|
||||
}
|
||||
|
||||
// 利用率 = (maxSize - available) / maxSize * 100
|
||||
// 获取了5个,池中应该没有可用的(因为是从空池开始),所以利用率是 10/10 * 100 = 100%
|
||||
let utilization = manager.getComponentUtilization('TestComponent');
|
||||
expect(utilization).toBe(100); // 10/10 * 100
|
||||
|
||||
// 释放3个组件
|
||||
for (let i = 0; i < 3; i++) {
|
||||
manager.releaseComponent('TestComponent', components[i]);
|
||||
}
|
||||
|
||||
// 现在池中有3个可用,7个在使用
|
||||
utilization = manager.getComponentUtilization('TestComponent');
|
||||
expect(utilization).toBe(70); // 7/10 * 100
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.get('TestComponent')?.available).toBe(3); // 池中有3个可用
|
||||
});
|
||||
|
||||
it('应该处理池满的情况', () => {
|
||||
// 预热到满容量
|
||||
manager.prewarmAll(10);
|
||||
|
||||
// 获取所有组件
|
||||
const components = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
components.push(manager.acquireComponent<TestComponent>('TestComponent')!);
|
||||
}
|
||||
|
||||
expect(manager.getComponentUtilization('TestComponent')).toBe(100);
|
||||
|
||||
// 尝试释放更多组件(超过容量)
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const extraComp = new TestComponent();
|
||||
manager.releaseComponent('TestComponent', extraComp);
|
||||
}
|
||||
|
||||
// 池应该仍然是满的,不会超过容量
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.get('TestComponent')?.available).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能测试', () => {
|
||||
beforeEach(() => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent(), (comp) => comp.reset());
|
||||
});
|
||||
|
||||
it('大量组件获取和释放应该高效', () => {
|
||||
const startTime = performance.now();
|
||||
const components = [];
|
||||
|
||||
// 获取1000个组件
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
components.push(manager.acquireComponent<TestComponent>('TestComponent')!);
|
||||
}
|
||||
|
||||
// 释放所有组件
|
||||
components.forEach(comp => manager.releaseComponent('TestComponent', comp));
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况和错误处理', () => {
|
||||
it('空管理器的统计信息应该正确', () => {
|
||||
// 完全重置管理器以确保清洁状态
|
||||
const emptyManager = ComponentPoolManager.getInstance();
|
||||
emptyManager.reset();
|
||||
|
||||
const stats = emptyManager.getPoolStats();
|
||||
const utilization = emptyManager.getPoolUtilization();
|
||||
|
||||
expect(stats.size).toBe(0);
|
||||
expect(utilization.size).toBe(0);
|
||||
});
|
||||
|
||||
it('重复注册同一组件类型应该覆盖之前的池', () => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent(), undefined, 10);
|
||||
manager.registerPool('TestComponent', () => new TestComponent(), undefined, 20);
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.get('TestComponent')?.maxSize).toBe(20);
|
||||
});
|
||||
|
||||
it('处理极端的预热数量', () => {
|
||||
manager.registerPool('TestComponent', () => new TestComponent(), undefined, 5);
|
||||
|
||||
// 预热超过最大容量
|
||||
manager.prewarmAll(100);
|
||||
|
||||
const stats = manager.getPoolStats();
|
||||
expect(stats.get('TestComponent')?.available).toBe(5); // 不应该超过最大容量
|
||||
});
|
||||
});
|
||||
});
|
||||
88
packages/core/tests/ECS/Core/ComponentStorage.auto.test.ts
Normal file
88
packages/core/tests/ECS/Core/ComponentStorage.auto.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { ComponentStorageManager, EnableSoA } from '../../../src/ECS/Core/ComponentStorage';
|
||||
|
||||
// 默认原始存储组件
|
||||
class PositionComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
public z: number = 0;
|
||||
}
|
||||
|
||||
// 启用SoA优化的组件(用于大规模批量操作)
|
||||
@EnableSoA
|
||||
class LargeScaleComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
public z: number = 0;
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
public vz: number = 0;
|
||||
}
|
||||
|
||||
describe('SoA优化选择测试', () => {
|
||||
let manager: ComponentStorageManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new ComponentStorageManager();
|
||||
});
|
||||
|
||||
test('默认使用原始存储', () => {
|
||||
const storage = manager.getStorage(PositionComponent);
|
||||
|
||||
// 添加组件
|
||||
manager.addComponent(1, new PositionComponent());
|
||||
|
||||
// 验证能正常工作
|
||||
const component = manager.getComponent(1, PositionComponent);
|
||||
expect(component).toBeTruthy();
|
||||
expect(component?.x).toBe(0);
|
||||
|
||||
// 验证使用原始存储
|
||||
expect(storage.constructor.name).toBe('ComponentStorage');
|
||||
});
|
||||
|
||||
test('@EnableSoA装饰器启用优化', () => {
|
||||
const storage = manager.getStorage(LargeScaleComponent);
|
||||
|
||||
// 添加组件
|
||||
const component = new LargeScaleComponent();
|
||||
component.x = 100;
|
||||
component.vx = 10;
|
||||
manager.addComponent(1, component);
|
||||
|
||||
// 验证能正常工作
|
||||
const retrieved = manager.getComponent(1, LargeScaleComponent);
|
||||
expect(retrieved).toBeTruthy();
|
||||
expect(retrieved?.x).toBe(100);
|
||||
expect(retrieved?.vx).toBe(10);
|
||||
|
||||
// 验证使用SoA存储
|
||||
expect(storage.constructor.name).toBe('SoAStorage');
|
||||
});
|
||||
|
||||
test('SoA存储功能验证', () => {
|
||||
const entityCount = 1000;
|
||||
|
||||
// 创建实体(使用SoA优化)
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const component = new LargeScaleComponent();
|
||||
component.x = i;
|
||||
component.y = i * 2;
|
||||
component.vx = 1;
|
||||
component.vy = 2;
|
||||
manager.addComponent(i, component);
|
||||
}
|
||||
|
||||
// 验证数据正确性
|
||||
const testComponent = manager.getComponent(100, LargeScaleComponent);
|
||||
expect(testComponent?.x).toBe(100);
|
||||
expect(testComponent?.y).toBe(200);
|
||||
expect(testComponent?.vx).toBe(1);
|
||||
expect(testComponent?.vy).toBe(2);
|
||||
|
||||
// 验证存储类型
|
||||
const storage = manager.getStorage(LargeScaleComponent);
|
||||
expect(storage.constructor.name).toBe('SoAStorage');
|
||||
console.log(`成功创建 ${entityCount} 个SoA实体,数据验证通过`);
|
||||
});
|
||||
});
|
||||
536
packages/core/tests/ECS/Core/ComponentStorage.comparison.test.ts
Normal file
536
packages/core/tests/ECS/Core/ComponentStorage.comparison.test.ts
Normal file
@@ -0,0 +1,536 @@
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { ComponentStorage, ComponentStorageManager, EnableSoA } from '../../../src/ECS/Core/ComponentStorage';
|
||||
import { SoAStorage } from '../../../src/ECS/Core/SoAStorage';
|
||||
|
||||
// 测试用统一组件结构(启用SoA)
|
||||
@EnableSoA
|
||||
class TestPositionComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
public z: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0, z = 0] = args as [number?, number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
|
||||
@EnableSoA
|
||||
class TestVelocityComponent extends Component {
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
public vz: number = 0;
|
||||
public maxSpeed: number = 100;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0, vz = 0] = args as [number?, number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
this.vz = vz;
|
||||
}
|
||||
}
|
||||
|
||||
@EnableSoA
|
||||
class TestHealthComponent extends Component {
|
||||
public current: number = 100;
|
||||
public max: number = 100;
|
||||
public regeneration: number = 1;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [current = 100, max = 100] = args as [number?, number?];
|
||||
this.current = current;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
// 用于原始存储测试的版本(默认原始存储)
|
||||
class OriginalPositionComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
public z: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0, z = 0] = args as [number?, number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
|
||||
class OriginalVelocityComponent extends Component {
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
public vz: number = 0;
|
||||
public maxSpeed: number = 100;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0, vz = 0] = args as [number?, number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
this.vz = vz;
|
||||
}
|
||||
}
|
||||
|
||||
class OriginalHealthComponent extends Component {
|
||||
public current: number = 100;
|
||||
public max: number = 100;
|
||||
public regeneration: number = 1;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [current = 100, max = 100] = args as [number?, number?];
|
||||
this.current = current;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
interface PerformanceResult {
|
||||
name: string;
|
||||
storageType: 'Original' | 'SoA';
|
||||
entityCount: number;
|
||||
operations: number;
|
||||
totalTime: number;
|
||||
averageTime: number;
|
||||
operationsPerSecond: number;
|
||||
}
|
||||
|
||||
describe('ComponentStorage 严谨性能对比测试', () => {
|
||||
const entityCounts = [1000, 5000, 20000];
|
||||
let results: PerformanceResult[] = [];
|
||||
|
||||
afterAll(() => {
|
||||
generateDetailedReport();
|
||||
});
|
||||
|
||||
describe('存储器创建和初始化', () => {
|
||||
test('验证SoA和原始存储使用相同接口', () => {
|
||||
const originalManager = new ComponentStorageManager();
|
||||
const soaManager = new ComponentStorageManager();
|
||||
|
||||
const originalStorage = originalManager.getStorage(OriginalPositionComponent);
|
||||
const soaStorage = soaManager.getStorage(TestPositionComponent);
|
||||
|
||||
// 验证都实现了相同的接口
|
||||
expect(typeof originalStorage.addComponent).toBe('function');
|
||||
expect(typeof originalStorage.getComponent).toBe('function');
|
||||
expect(typeof originalStorage.hasComponent).toBe('function');
|
||||
expect(typeof originalStorage.removeComponent).toBe('function');
|
||||
|
||||
expect(typeof soaStorage.addComponent).toBe('function');
|
||||
expect(typeof soaStorage.getComponent).toBe('function');
|
||||
expect(typeof soaStorage.hasComponent).toBe('function');
|
||||
expect(typeof soaStorage.removeComponent).toBe('function');
|
||||
|
||||
// 验证存储器类型
|
||||
expect(originalStorage).toBeInstanceOf(ComponentStorage);
|
||||
expect(soaStorage).toBeInstanceOf(SoAStorage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('实体创建性能对比', () => {
|
||||
entityCounts.forEach(entityCount => {
|
||||
test(`创建 ${entityCount} 个完整实体`, () => {
|
||||
console.log(`\\n=== 实体创建性能测试: ${entityCount} 个实体 ===`);
|
||||
|
||||
// 原始存储测试
|
||||
const originalResult = measureOriginalEntityCreation(entityCount);
|
||||
results.push(originalResult);
|
||||
|
||||
// SoA存储测试
|
||||
const soaResult = measureSoAEntityCreation(entityCount);
|
||||
results.push(soaResult);
|
||||
|
||||
// 输出对比结果
|
||||
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
||||
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
||||
|
||||
const speedup = originalResult.totalTime / soaResult.totalTime;
|
||||
const improvement = ((speedup - 1) * 100);
|
||||
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
|
||||
|
||||
// 验证功能正确性
|
||||
expect(originalResult.operations).toBe(soaResult.operations);
|
||||
expect(originalResult.totalTime).toBeGreaterThan(0);
|
||||
expect(soaResult.totalTime).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('组件访问性能对比', () => {
|
||||
entityCounts.forEach(entityCount => {
|
||||
test(`随机访问 ${entityCount} 个实体组件`, () => {
|
||||
console.log(`\\n=== 组件访问性能测试: ${entityCount} 个实体 ===`);
|
||||
|
||||
// 原始存储测试
|
||||
const originalResult = measureOriginalComponentAccess(entityCount, 100);
|
||||
results.push(originalResult);
|
||||
|
||||
// SoA存储测试
|
||||
const soaResult = measureSoAComponentAccess(entityCount, 100);
|
||||
results.push(soaResult);
|
||||
|
||||
// 输出对比结果
|
||||
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
||||
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
||||
|
||||
const speedup = originalResult.totalTime / soaResult.totalTime;
|
||||
const improvement = ((speedup - 1) * 100);
|
||||
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
|
||||
|
||||
expect(originalResult.operations).toBe(soaResult.operations);
|
||||
expect(originalResult.totalTime).toBeGreaterThan(0);
|
||||
expect(soaResult.totalTime).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('批量更新性能对比(SoA优势场景)', () => {
|
||||
entityCounts.forEach(entityCount => {
|
||||
test(`批量更新 ${entityCount} 个实体`, () => {
|
||||
console.log(`\\n=== 批量更新性能测试: ${entityCount} 个实体 ===`);
|
||||
|
||||
// 原始存储测试
|
||||
const originalResult = measureOriginalBatchUpdate(entityCount, 50);
|
||||
results.push(originalResult);
|
||||
|
||||
// SoA存储测试(向量化操作)
|
||||
const soaResult = measureSoABatchUpdate(entityCount, 50);
|
||||
results.push(soaResult);
|
||||
|
||||
// 输出对比结果
|
||||
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
||||
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
||||
|
||||
const speedup = originalResult.totalTime / soaResult.totalTime;
|
||||
const improvement = ((speedup - 1) * 100);
|
||||
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
|
||||
|
||||
// 这是SoA的优势场景,应该有性能提升
|
||||
if (entityCount > 5000) {
|
||||
expect(speedup).toBeGreaterThan(1.0); // SoA应该更快
|
||||
}
|
||||
|
||||
expect(originalResult.operations).toBe(soaResult.operations);
|
||||
expect(originalResult.totalTime).toBeGreaterThan(0);
|
||||
expect(soaResult.totalTime).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 测试辅助函数
|
||||
function measureOriginalEntityCreation(entityCount: number): PerformanceResult {
|
||||
const manager = new ComponentStorageManager();
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
manager.addComponent(i, new OriginalPositionComponent(
|
||||
Math.random() * 1000,
|
||||
Math.random() * 1000,
|
||||
Math.random() * 100
|
||||
));
|
||||
manager.addComponent(i, new OriginalVelocityComponent(
|
||||
(Math.random() - 0.5) * 20,
|
||||
(Math.random() - 0.5) * 20,
|
||||
(Math.random() - 0.5) * 10
|
||||
));
|
||||
if (i % 2 === 0) {
|
||||
manager.addComponent(i, new OriginalHealthComponent(
|
||||
80 + Math.random() * 20,
|
||||
100
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
const operations = entityCount * 2.5; // 平均每个实体2.5个组件
|
||||
|
||||
return {
|
||||
name: 'Entity Creation',
|
||||
storageType: 'Original',
|
||||
entityCount,
|
||||
operations,
|
||||
totalTime,
|
||||
averageTime: totalTime / operations,
|
||||
operationsPerSecond: operations / (totalTime / 1000)
|
||||
};
|
||||
}
|
||||
|
||||
function measureSoAEntityCreation(entityCount: number): PerformanceResult {
|
||||
const manager = new ComponentStorageManager();
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
manager.addComponent(i, new TestPositionComponent(
|
||||
Math.random() * 1000,
|
||||
Math.random() * 1000,
|
||||
Math.random() * 100
|
||||
));
|
||||
manager.addComponent(i, new TestVelocityComponent(
|
||||
(Math.random() - 0.5) * 20,
|
||||
(Math.random() - 0.5) * 20,
|
||||
(Math.random() - 0.5) * 10
|
||||
));
|
||||
if (i % 2 === 0) {
|
||||
manager.addComponent(i, new TestHealthComponent(
|
||||
80 + Math.random() * 20,
|
||||
100
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
const operations = entityCount * 2.5;
|
||||
|
||||
return {
|
||||
name: 'Entity Creation',
|
||||
storageType: 'SoA',
|
||||
entityCount,
|
||||
operations,
|
||||
totalTime,
|
||||
averageTime: totalTime / operations,
|
||||
operationsPerSecond: operations / (totalTime / 1000)
|
||||
};
|
||||
}
|
||||
|
||||
function measureOriginalComponentAccess(entityCount: number, iterations: number): PerformanceResult {
|
||||
const manager = new ComponentStorageManager();
|
||||
|
||||
// 预创建实体
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
manager.addComponent(i, new OriginalPositionComponent(i, i, i));
|
||||
manager.addComponent(i, new OriginalVelocityComponent(1, 1, 1));
|
||||
if (i % 2 === 0) {
|
||||
manager.addComponent(i, new OriginalHealthComponent(100, 100));
|
||||
}
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let iter = 0; iter < iterations; iter++) {
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const pos = manager.getComponent(i, OriginalPositionComponent);
|
||||
const vel = manager.getComponent(i, OriginalVelocityComponent);
|
||||
|
||||
if (pos && vel) {
|
||||
// 模拟简单的读取操作
|
||||
const sum = pos.x + pos.y + pos.z + vel.vx + vel.vy + vel.vz;
|
||||
if (sum < 0) continue; // 防止优化
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
const operations = entityCount * iterations;
|
||||
|
||||
return {
|
||||
name: 'Component Access',
|
||||
storageType: 'Original',
|
||||
entityCount,
|
||||
operations,
|
||||
totalTime,
|
||||
averageTime: totalTime / operations,
|
||||
operationsPerSecond: operations / (totalTime / 1000)
|
||||
};
|
||||
}
|
||||
|
||||
function measureSoAComponentAccess(entityCount: number, iterations: number): PerformanceResult {
|
||||
const manager = new ComponentStorageManager();
|
||||
|
||||
// 预创建实体
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
manager.addComponent(i, new TestPositionComponent(i, i, i));
|
||||
manager.addComponent(i, new TestVelocityComponent(1, 1, 1));
|
||||
if (i % 2 === 0) {
|
||||
manager.addComponent(i, new TestHealthComponent(100, 100));
|
||||
}
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let iter = 0; iter < iterations; iter++) {
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const pos = manager.getComponent(i, TestPositionComponent);
|
||||
const vel = manager.getComponent(i, TestVelocityComponent);
|
||||
|
||||
if (pos && vel) {
|
||||
// 模拟简单的读取操作
|
||||
const sum = pos.x + pos.y + pos.z + vel.vx + vel.vy + vel.vz;
|
||||
if (sum < 0) continue; // 防止优化
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
const operations = entityCount * iterations;
|
||||
|
||||
return {
|
||||
name: 'Component Access',
|
||||
storageType: 'SoA',
|
||||
entityCount,
|
||||
operations,
|
||||
totalTime,
|
||||
averageTime: totalTime / operations,
|
||||
operationsPerSecond: operations / (totalTime / 1000)
|
||||
};
|
||||
}
|
||||
|
||||
function measureOriginalBatchUpdate(entityCount: number, iterations: number): PerformanceResult {
|
||||
const manager = new ComponentStorageManager();
|
||||
|
||||
// 预创建实体
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
manager.addComponent(i, new OriginalPositionComponent(i, i, 0));
|
||||
manager.addComponent(i, new OriginalVelocityComponent(1, 1, 0));
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
const deltaTime = 0.016;
|
||||
|
||||
for (let iter = 0; iter < iterations; iter++) {
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const pos = manager.getComponent(i, OriginalPositionComponent);
|
||||
const vel = manager.getComponent(i, OriginalVelocityComponent);
|
||||
|
||||
if (pos && vel) {
|
||||
// 物理更新
|
||||
pos.x += vel.vx * deltaTime;
|
||||
pos.y += vel.vy * deltaTime;
|
||||
pos.z += vel.vz * deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
const operations = entityCount * iterations;
|
||||
|
||||
return {
|
||||
name: 'Batch Update',
|
||||
storageType: 'Original',
|
||||
entityCount,
|
||||
operations,
|
||||
totalTime,
|
||||
averageTime: totalTime / operations,
|
||||
operationsPerSecond: operations / (totalTime / 1000)
|
||||
};
|
||||
}
|
||||
|
||||
function measureSoABatchUpdate(entityCount: number, iterations: number): PerformanceResult {
|
||||
const manager = new ComponentStorageManager();
|
||||
|
||||
// 预创建实体
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
manager.addComponent(i, new TestPositionComponent(i, i, 0));
|
||||
manager.addComponent(i, new TestVelocityComponent(1, 1, 0));
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
const deltaTime = 0.016;
|
||||
|
||||
// 获取SoA存储器进行向量化操作
|
||||
const posStorage = manager.getStorage(TestPositionComponent) as SoAStorage<TestPositionComponent>;
|
||||
const velStorage = manager.getStorage(TestVelocityComponent) as SoAStorage<TestVelocityComponent>;
|
||||
|
||||
for (let iter = 0; iter < iterations; iter++) {
|
||||
// 使用向量化操作
|
||||
posStorage.performVectorizedOperation((posFields, activeIndices) => {
|
||||
const velFields = velStorage.getFieldArray('vx') ?
|
||||
new Map([
|
||||
['vx', velStorage.getFieldArray('vx')!],
|
||||
['vy', velStorage.getFieldArray('vy')!],
|
||||
['vz', velStorage.getFieldArray('vz')!]
|
||||
]) : new Map();
|
||||
|
||||
const posX = posFields.get('x') as Float32Array;
|
||||
const posY = posFields.get('y') as Float32Array;
|
||||
const posZ = posFields.get('z') as Float32Array;
|
||||
|
||||
const velX = velFields.get('vx') as Float32Array;
|
||||
const velY = velFields.get('vy') as Float32Array;
|
||||
const velZ = velFields.get('vz') as Float32Array;
|
||||
|
||||
// 向量化物理更新
|
||||
for (let j = 0; j < activeIndices.length; j++) {
|
||||
const idx = activeIndices[j];
|
||||
posX[idx] += velX[idx] * deltaTime;
|
||||
posY[idx] += velY[idx] * deltaTime;
|
||||
posZ[idx] += velZ[idx] * deltaTime;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
const operations = entityCount * iterations;
|
||||
|
||||
return {
|
||||
name: 'Batch Update',
|
||||
storageType: 'SoA',
|
||||
entityCount,
|
||||
operations,
|
||||
totalTime,
|
||||
averageTime: totalTime / operations,
|
||||
operationsPerSecond: operations / (totalTime / 1000)
|
||||
};
|
||||
}
|
||||
|
||||
function generateDetailedReport(): void {
|
||||
console.log('\\n' + '='.repeat(80));
|
||||
console.log('ComponentStorage 严谨性能对比报告');
|
||||
console.log('='.repeat(80));
|
||||
|
||||
// 按测试类型分组
|
||||
const groupedResults = new Map<string, PerformanceResult[]>();
|
||||
|
||||
for (const result of results) {
|
||||
const key = `${result.name}-${result.entityCount}`;
|
||||
if (!groupedResults.has(key)) {
|
||||
groupedResults.set(key, []);
|
||||
}
|
||||
groupedResults.get(key)!.push(result);
|
||||
}
|
||||
|
||||
let totalOriginalTime = 0;
|
||||
let totalSoATime = 0;
|
||||
let testCount = 0;
|
||||
|
||||
for (const [key, testResults] of groupedResults.entries()) {
|
||||
console.log(`\\n${key}:`);
|
||||
|
||||
const originalResult = testResults.find(r => r.storageType === 'Original');
|
||||
const soaResult = testResults.find(r => r.storageType === 'SoA');
|
||||
|
||||
if (originalResult && soaResult) {
|
||||
const speedup = originalResult.totalTime / soaResult.totalTime;
|
||||
const improvement = ((speedup - 1) * 100);
|
||||
|
||||
console.log(` 原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
||||
console.log(` SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
||||
console.log(` 性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
|
||||
|
||||
totalOriginalTime += originalResult.totalTime;
|
||||
totalSoATime += soaResult.totalTime;
|
||||
testCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (testCount > 0) {
|
||||
const overallSpeedup = totalOriginalTime / totalSoATime;
|
||||
const overallImprovement = ((overallSpeedup - 1) * 100);
|
||||
|
||||
console.log('\\n' + '='.repeat(80));
|
||||
console.log('总体性能对比:');
|
||||
console.log(` 原始存储总耗时: ${totalOriginalTime.toFixed(2)}ms`);
|
||||
console.log(` SoA存储总耗时: ${totalSoATime.toFixed(2)}ms`);
|
||||
console.log(` 总体性能对比: ${overallSpeedup.toFixed(2)}x ${overallImprovement > 0 ? '提升' : '下降'} ${Math.abs(overallImprovement).toFixed(1)}%`);
|
||||
console.log('\\n结论: SoA优化在批量操作场景中表现优异,在小规模随机访问场景中有轻微开销。');
|
||||
console.log('建议: 对于大规模游戏实体和批量系统更新,SoA优化能带来显著性能提升。');
|
||||
console.log('='.repeat(80));
|
||||
}
|
||||
}
|
||||
});
|
||||
661
packages/core/tests/ECS/Core/ComponentStorage.test.ts
Normal file
661
packages/core/tests/ECS/Core/ComponentStorage.test.ts
Normal file
@@ -0,0 +1,661 @@
|
||||
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 {
|
||||
public value: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [value = 0] = args as [number?];
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class PositionComponent extends Component {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number;
|
||||
public vy: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public health: number;
|
||||
public maxHealth: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [health = 100, maxHealth = 100] = args as [number?, number?];
|
||||
this.health = health;
|
||||
this.maxHealth = maxHealth;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
433
packages/core/tests/ECS/Core/DecoratorSystem.test.ts
Normal file
433
packages/core/tests/ECS/Core/DecoratorSystem.test.ts
Normal file
@@ -0,0 +1,433 @@
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
import { EventBus } from '../../../src/ECS/Core/EventBus';
|
||||
|
||||
// 测试组件
|
||||
class TransformComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
public rotation: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
if (args.length >= 1) this.x = args[0] as number;
|
||||
if (args.length >= 2) this.y = args[1] as number;
|
||||
if (args.length >= 3) this.rotation = args[2] as number;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
if (args.length >= 1) this.vx = args[0] as number;
|
||||
if (args.length >= 2) this.vy = args[1] as number;
|
||||
}
|
||||
}
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public health: number = 100;
|
||||
public maxHealth: number = 100;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
if (args.length >= 1) this.health = args[0] as number;
|
||||
if (args.length >= 2) this.maxHealth = args[1] as number;
|
||||
}
|
||||
}
|
||||
|
||||
// 简单的事件装饰器实现(用于测试)
|
||||
function EventHandler(eventType: string, priority: number = 0) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// 在原型上标记事件处理器信息
|
||||
if (!target.constructor._eventHandlers) {
|
||||
target.constructor._eventHandlers = [];
|
||||
}
|
||||
target.constructor._eventHandlers.push({
|
||||
eventType,
|
||||
methodName: propertyKey,
|
||||
priority,
|
||||
handler: originalMethod
|
||||
});
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
// 自动初始化事件监听器的基类
|
||||
class EventAwareSystem extends EntitySystem {
|
||||
private eventListenerIds: string[] = [];
|
||||
|
||||
constructor(matcher: Matcher) {
|
||||
super(matcher);
|
||||
}
|
||||
|
||||
public override initialize(): void {
|
||||
super.initialize();
|
||||
this.initializeEventHandlers();
|
||||
}
|
||||
|
||||
private initializeEventHandlers(): void {
|
||||
const eventHandlers = (this.constructor as any)._eventHandlers;
|
||||
if (!eventHandlers || !this.scene?.eventSystem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 按优先级排序并注册事件处理器
|
||||
eventHandlers
|
||||
.sort((a: any, b: any) => b.priority - a.priority)
|
||||
.forEach((handlerInfo: any) => {
|
||||
const listenerId = this.scene!.eventSystem.on(
|
||||
handlerInfo.eventType,
|
||||
handlerInfo.handler.bind(this),
|
||||
{ priority: handlerInfo.priority }
|
||||
);
|
||||
this.eventListenerIds.push(listenerId);
|
||||
});
|
||||
}
|
||||
|
||||
public cleanup(): void {
|
||||
// 清理事件监听器
|
||||
if (this.scene?.eventSystem) {
|
||||
this.eventListenerIds.forEach(id => {
|
||||
// 注意:这里需要修改EventSystem来支持通过ID移除监听器
|
||||
// this.scene!.eventSystem.removeListener(id);
|
||||
});
|
||||
}
|
||||
this.eventListenerIds = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 使用装饰器的测试系统
|
||||
class DecoratedMovementSystem extends EventAwareSystem {
|
||||
public processedEntities: Entity[] = [];
|
||||
public receivedEvents: any[] = [];
|
||||
public entityMovedEvents: any[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(TransformComponent, VelocityComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processedEntities = [...entities];
|
||||
|
||||
for (const entity of entities) {
|
||||
const transform = entity.getComponent(TransformComponent)!;
|
||||
const velocity = entity.getComponent(VelocityComponent)!;
|
||||
|
||||
const oldX = transform.x;
|
||||
const oldY = transform.y;
|
||||
|
||||
// 更新位置
|
||||
transform.x += velocity.vx;
|
||||
transform.y += velocity.vy;
|
||||
|
||||
// 发射实体移动事件
|
||||
if (this.scene?.eventSystem) {
|
||||
this.scene.eventSystem.emit('entity:moved', {
|
||||
entityId: entity.id,
|
||||
entityName: entity.name,
|
||||
oldPosition: { x: oldX, y: oldY },
|
||||
newPosition: { x: transform.x, y: transform.y }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler('entity:moved', 10)
|
||||
onEntityMoved(data: any): void {
|
||||
this.entityMovedEvents.push(data);
|
||||
}
|
||||
|
||||
@EventHandler('entity:health_changed', 5)
|
||||
onHealthChanged(data: any): void {
|
||||
this.receivedEvents.push({ type: 'health_changed', data });
|
||||
}
|
||||
|
||||
@EventHandler('system:initialized', 15)
|
||||
onSystemInitialized(data: any): void {
|
||||
this.receivedEvents.push({ type: 'system_initialized', data });
|
||||
}
|
||||
}
|
||||
|
||||
class HealthSystem extends EventAwareSystem {
|
||||
public processedEntities: Entity[] = [];
|
||||
public receivedEvents: any[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(HealthComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processedEntities = [...entities];
|
||||
|
||||
for (const entity of entities) {
|
||||
const health = entity.getComponent(HealthComponent)!;
|
||||
|
||||
// 模拟健康值变化
|
||||
if (health.health > 0) {
|
||||
const oldHealth = health.health;
|
||||
health.health = Math.max(0, health.health - 1);
|
||||
|
||||
// 发射健康值变化事件
|
||||
if (this.scene?.eventSystem && oldHealth !== health.health) {
|
||||
this.scene.eventSystem.emit('entity:health_changed', {
|
||||
entityId: entity.id,
|
||||
entityName: entity.name,
|
||||
oldHealth,
|
||||
newHealth: health.health,
|
||||
isDead: health.health <= 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler('entity:health_changed', 8)
|
||||
onHealthChanged(data: any): void {
|
||||
this.receivedEvents.push(data);
|
||||
|
||||
// 如果实体死亡,禁用它
|
||||
if (data.isDead) {
|
||||
const entity = this.scene?.findEntityById(data.entityId);
|
||||
if (entity) {
|
||||
entity.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('装饰器系统测试', () => {
|
||||
let scene: Scene;
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene();
|
||||
scene.name = "DecoratorTestScene";
|
||||
});
|
||||
|
||||
describe('事件装饰器基础功能', () => {
|
||||
test('装饰器应该正确注册事件处理器', () => {
|
||||
const movementSystem = new DecoratedMovementSystem();
|
||||
|
||||
// 检查装饰器是否正确注册了事件处理器信息
|
||||
const eventHandlers = (DecoratedMovementSystem as any)._eventHandlers;
|
||||
expect(eventHandlers).toBeDefined();
|
||||
expect(eventHandlers.length).toBe(3);
|
||||
|
||||
// 检查事件处理器信息
|
||||
const entityMovedHandler = eventHandlers.find((h: any) => h.eventType === 'entity:moved');
|
||||
expect(entityMovedHandler).toBeDefined();
|
||||
expect(entityMovedHandler.priority).toBe(10);
|
||||
expect(entityMovedHandler.methodName).toBe('onEntityMoved');
|
||||
});
|
||||
|
||||
test('系统初始化时应该自动注册事件监听器', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new TransformComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
const movementSystem = new DecoratedMovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 验证系统正确初始化
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
|
||||
// 运行一次更新,应该触发entity:moved事件
|
||||
scene.update();
|
||||
|
||||
// 检查事件是否被正确处理
|
||||
expect(movementSystem.entityMovedEvents.length).toBe(1);
|
||||
expect(movementSystem.entityMovedEvents[0].entityId).toBe(entity.id);
|
||||
expect(movementSystem.entityMovedEvents[0].newPosition.x).toBe(1);
|
||||
expect(movementSystem.entityMovedEvents[0].newPosition.y).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('多系统事件交互', () => {
|
||||
test('多个系统应该能够响应同一事件', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new TransformComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
entity.addComponent(new HealthComponent(10));
|
||||
|
||||
const movementSystem = new DecoratedMovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
// 运行几次更新
|
||||
scene.update();
|
||||
scene.update();
|
||||
scene.update();
|
||||
|
||||
// 检查健康系统是否处理了健康变化事件
|
||||
expect(healthSystem.receivedEvents.length).toBeGreaterThan(0);
|
||||
|
||||
// 检查移动系统是否也接收到了健康变化事件
|
||||
const healthChangedEvents = movementSystem.receivedEvents.filter(e => e.type === 'health_changed');
|
||||
expect(healthChangedEvents.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('事件优先级应该正确工作', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new TransformComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
const movementSystem = new DecoratedMovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 发射系统初始化事件(如果有的话)
|
||||
if (scene.eventSystem) {
|
||||
scene.eventSystem.emit('system:initialized', {
|
||||
systemName: 'DecoratedMovementSystem',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
// 检查事件是否被接收
|
||||
scene.update();
|
||||
|
||||
// 验证不同优先级的事件都被处理了
|
||||
const systemInitEvents = movementSystem.receivedEvents.filter(e => e.type === 'system_initialized');
|
||||
expect(systemInitEvents.length).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('装饰器系统的时序问题', () => {
|
||||
test('先添加实体再添加装饰器系统 - 事件应该正常工作', () => {
|
||||
// 先创建实体
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new TransformComponent(5, 5));
|
||||
entity.addComponent(new VelocityComponent(2, 3));
|
||||
|
||||
// 然后添加装饰器系统
|
||||
const movementSystem = new DecoratedMovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 验证系统正确识别了实体
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
|
||||
// 运行更新,应该触发事件
|
||||
scene.update();
|
||||
|
||||
// 检查事件装饰器是否正常工作
|
||||
expect(movementSystem.entityMovedEvents.length).toBe(1);
|
||||
expect(movementSystem.entityMovedEvents[0].oldPosition.x).toBe(5);
|
||||
expect(movementSystem.entityMovedEvents[0].newPosition.x).toBe(7);
|
||||
});
|
||||
|
||||
test('动态添加组件后装饰器事件应该正常', () => {
|
||||
const movementSystem = new DecoratedMovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
const entity = scene.createEntity("DynamicEntity");
|
||||
entity.addComponent(new TransformComponent(0, 0));
|
||||
|
||||
// 初始状态:系统不匹配
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
|
||||
// 动态添加速度组件
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
// 系统应该匹配
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
|
||||
// 运行更新,事件应该正常触发
|
||||
scene.update();
|
||||
expect(movementSystem.entityMovedEvents.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('装饰器系统的清理', () => {
|
||||
test('系统移除时应该清理事件监听器', () => {
|
||||
const movementSystem = new DecoratedMovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 验证系统已添加
|
||||
expect(scene.entityProcessors.count).toBe(1);
|
||||
|
||||
// 移除系统
|
||||
scene.removeEntityProcessor(movementSystem);
|
||||
expect(scene.entityProcessors.count).toBe(0);
|
||||
|
||||
// 清理事件监听器
|
||||
movementSystem.cleanup();
|
||||
|
||||
// 验证事件监听器已清理(这里主要是检查不抛出异常)
|
||||
expect(() => {
|
||||
if (scene.eventSystem) {
|
||||
scene.eventSystem.emit('entity:moved', { test: true });
|
||||
}
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况测试', () => {
|
||||
test('没有装饰器的系统应该正常工作', () => {
|
||||
class SimpleSystem extends EventAwareSystem {
|
||||
public processedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(TransformComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processedEntities = [...entities];
|
||||
}
|
||||
}
|
||||
|
||||
const entity = scene.createEntity("SimpleEntity");
|
||||
entity.addComponent(new TransformComponent(1, 1));
|
||||
|
||||
const simpleSystem = new SimpleSystem();
|
||||
|
||||
expect(() => {
|
||||
scene.addEntityProcessor(simpleSystem);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(simpleSystem.entities.length).toBe(1);
|
||||
|
||||
scene.update();
|
||||
expect(simpleSystem.processedEntities.length).toBe(1);
|
||||
});
|
||||
|
||||
test('空事件数据应该正常处理', () => {
|
||||
const movementSystem = new DecoratedMovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 发射空事件
|
||||
if (scene.eventSystem) {
|
||||
scene.eventSystem.emit('entity:health_changed', null);
|
||||
scene.eventSystem.emit('entity:health_changed', undefined);
|
||||
scene.eventSystem.emit('entity:health_changed', {});
|
||||
}
|
||||
|
||||
// 系统应该能够处理空数据而不崩溃
|
||||
expect(() => {
|
||||
scene.update();
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 清理场景
|
||||
scene.destroyAllEntities();
|
||||
|
||||
// 清理系统并清理它们的事件监听器
|
||||
const processors = [...scene.entityProcessors.processors];
|
||||
processors.forEach(processor => {
|
||||
if (processor instanceof EventAwareSystem) {
|
||||
processor.cleanup();
|
||||
}
|
||||
scene.removeEntityProcessor(processor);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,227 @@
|
||||
import { EntityManager } from '../../../src/ECS/Core/EntityManager';
|
||||
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
|
||||
describe('详细性能分析 - 逐步测量', () => {
|
||||
let entityManager: EntityManager;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
entityManager = new EntityManager();
|
||||
});
|
||||
|
||||
test('精确测量createEntity中每个步骤的耗时', () => {
|
||||
const testCount = 1000;
|
||||
console.log(`\n=== 详细性能分析 (${testCount}个实体) ===`);
|
||||
|
||||
const timings = {
|
||||
idCheckOut: 0,
|
||||
nameGeneration: 0,
|
||||
entityConstruction: 0,
|
||||
mapSet: 0,
|
||||
nameIndexUpdate: 0,
|
||||
tagIndexUpdate: 0,
|
||||
componentIndexManager: 0,
|
||||
archetypeSystem: 0,
|
||||
dirtyTracking: 0,
|
||||
eventEmission: 0,
|
||||
total: 0
|
||||
};
|
||||
|
||||
const totalStart = performance.now();
|
||||
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
// 步骤1: ID分配
|
||||
let stepStart = performance.now();
|
||||
const id = entityManager['_identifierPool'].checkOut();
|
||||
timings.idCheckOut += performance.now() - stepStart;
|
||||
|
||||
// 步骤2: 名称生成
|
||||
stepStart = performance.now();
|
||||
const name = `Entity_${id}`;
|
||||
timings.nameGeneration += performance.now() - stepStart;
|
||||
|
||||
// 步骤3: Entity构造
|
||||
stepStart = performance.now();
|
||||
const entity = new Entity(name, id);
|
||||
timings.entityConstruction += performance.now() - stepStart;
|
||||
|
||||
// 步骤4: Map存储
|
||||
stepStart = performance.now();
|
||||
entityManager['_entities'].set(id, entity);
|
||||
timings.mapSet += performance.now() - stepStart;
|
||||
|
||||
// 步骤5: 名称索引更新
|
||||
stepStart = performance.now();
|
||||
entityManager['updateNameIndex'](entity, true);
|
||||
timings.nameIndexUpdate += performance.now() - stepStart;
|
||||
|
||||
// 步骤6: 标签索引更新
|
||||
stepStart = performance.now();
|
||||
entityManager['updateTagIndex'](entity, true);
|
||||
timings.tagIndexUpdate += performance.now() - stepStart;
|
||||
|
||||
// 步骤7: 组件索引管理器
|
||||
stepStart = performance.now();
|
||||
entityManager['_componentIndexManager'].addEntity(entity);
|
||||
timings.componentIndexManager += performance.now() - stepStart;
|
||||
|
||||
// 步骤8: 原型系统
|
||||
stepStart = performance.now();
|
||||
entityManager['_archetypeSystem'].addEntity(entity);
|
||||
timings.archetypeSystem += performance.now() - stepStart;
|
||||
|
||||
// 步骤9: 脏标记系统
|
||||
stepStart = performance.now();
|
||||
entityManager['_dirtyTrackingSystem'].markDirty(entity, 1); // DirtyFlag.COMPONENT_ADDED
|
||||
timings.dirtyTracking += performance.now() - stepStart;
|
||||
|
||||
// 步骤10: 事件发射
|
||||
stepStart = performance.now();
|
||||
entityManager['_eventBus'].emitEntityCreated({
|
||||
timestamp: Date.now(),
|
||||
source: 'EntityManager',
|
||||
entityId: entity.id,
|
||||
entityName: entity.name,
|
||||
entityTag: entity.tag?.toString()
|
||||
});
|
||||
timings.eventEmission += performance.now() - stepStart;
|
||||
}
|
||||
|
||||
timings.total = performance.now() - totalStart;
|
||||
|
||||
console.log('\n各步骤耗时统计:');
|
||||
console.log(`总耗时: ${timings.total.toFixed(2)}ms`);
|
||||
console.log(`平均每个实体: ${(timings.total / testCount).toFixed(3)}ms`);
|
||||
console.log('\n详细分解:');
|
||||
|
||||
const sortedTimings = Object.entries(timings)
|
||||
.filter(([key]) => key !== 'total')
|
||||
.sort(([,a], [,b]) => b - a)
|
||||
.map(([key, time]) => ({
|
||||
step: key,
|
||||
timeMs: time,
|
||||
percentage: (time / timings.total * 100),
|
||||
avgPerEntity: (time / testCount * 1000) // 转换为微秒
|
||||
}));
|
||||
|
||||
for (const timing of sortedTimings) {
|
||||
console.log(` ${timing.step.padEnd(20)}: ${timing.timeMs.toFixed(2)}ms (${timing.percentage.toFixed(1)}%) - ${timing.avgPerEntity.toFixed(1)}μs/entity`);
|
||||
}
|
||||
|
||||
console.log('\n最耗时的前3个步骤:');
|
||||
for (let i = 0; i < Math.min(3, sortedTimings.length); i++) {
|
||||
const timing = sortedTimings[i];
|
||||
console.log(` ${i + 1}. ${timing.step}: ${timing.percentage.toFixed(1)}% (${timing.timeMs.toFixed(2)}ms)`);
|
||||
}
|
||||
});
|
||||
|
||||
test('对比纯Entity创建和完整创建流程', () => {
|
||||
const testCount = 1000;
|
||||
console.log(`\n=== 创建方式对比 (${testCount}个实体) ===`);
|
||||
|
||||
// 1. 纯Entity创建
|
||||
let startTime = performance.now();
|
||||
const pureEntities = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
pureEntities.push(new Entity(`Pure_${i}`, i));
|
||||
}
|
||||
const pureTime = performance.now() - startTime;
|
||||
|
||||
// 2. 完整EntityManager创建
|
||||
startTime = performance.now();
|
||||
const managedEntities = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
managedEntities.push(entityManager.createEntity(`Managed_${i}`));
|
||||
}
|
||||
const managedTime = performance.now() - startTime;
|
||||
|
||||
console.log(`纯Entity创建: ${pureTime.toFixed(2)}ms`);
|
||||
console.log(`EntityManager创建: ${managedTime.toFixed(2)}ms`);
|
||||
console.log(`性能差距: ${(managedTime / pureTime).toFixed(1)}倍`);
|
||||
console.log(`管理开销: ${(managedTime - pureTime).toFixed(2)}ms (${((managedTime - pureTime) / managedTime * 100).toFixed(1)}%)`);
|
||||
});
|
||||
|
||||
test('测量批量操作的效果', () => {
|
||||
const testCount = 1000;
|
||||
console.log(`\n=== 批量操作效果测试 (${testCount}个实体) ===`);
|
||||
|
||||
// 1. 逐个处理
|
||||
let startTime = performance.now();
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entityManager.createEntity(`Individual_${i}`);
|
||||
}
|
||||
const individualTime = performance.now() - startTime;
|
||||
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 2. 批量处理
|
||||
startTime = performance.now();
|
||||
entityManager.createEntitiesBatch(testCount, "Batch", false);
|
||||
const batchTime = performance.now() - startTime;
|
||||
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 3. 批量处理(跳过事件)
|
||||
startTime = performance.now();
|
||||
entityManager.createEntitiesBatch(testCount, "BatchNoEvents", true);
|
||||
const batchNoEventsTime = performance.now() - startTime;
|
||||
|
||||
console.log(`逐个创建: ${individualTime.toFixed(2)}ms`);
|
||||
console.log(`批量创建: ${batchTime.toFixed(2)}ms`);
|
||||
console.log(`批量创建(跳过事件): ${batchNoEventsTime.toFixed(2)}ms`);
|
||||
console.log(`批量vs逐个: ${(individualTime / batchTime).toFixed(2)}x`);
|
||||
console.log(`跳过事件优化: ${(batchTime / batchNoEventsTime).toFixed(2)}x`);
|
||||
});
|
||||
|
||||
test('分析最耗时组件的内部实现', () => {
|
||||
console.log(`\n=== 最耗时组件内部分析 ===`);
|
||||
|
||||
const testCount = 500; // 较少数量以便详细分析
|
||||
|
||||
// 单独测试各个重要组件
|
||||
const entity = new Entity("TestEntity", 1);
|
||||
|
||||
// 测试组件索引管理器
|
||||
let startTime = performance.now();
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
const testEntity = new Entity(`Test_${i}`, i);
|
||||
entityManager['_componentIndexManager'].addEntity(testEntity);
|
||||
}
|
||||
const componentIndexTime = performance.now() - startTime;
|
||||
|
||||
// 测试原型系统
|
||||
startTime = performance.now();
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
const testEntity = new Entity(`Test_${i}`, i + testCount);
|
||||
entityManager['_archetypeSystem'].addEntity(testEntity);
|
||||
}
|
||||
const archetypeTime = performance.now() - startTime;
|
||||
|
||||
// 测试脏标记系统
|
||||
startTime = performance.now();
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
const testEntity = new Entity(`Test_${i}`, i + testCount * 2);
|
||||
entityManager['_dirtyTrackingSystem'].markDirty(testEntity, 1);
|
||||
}
|
||||
const dirtyTrackingTime = performance.now() - startTime;
|
||||
|
||||
// 测试事件发射
|
||||
startTime = performance.now();
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entityManager['_eventBus'].emitEntityCreated({
|
||||
timestamp: Date.now(),
|
||||
source: 'EntityManager',
|
||||
entityId: i,
|
||||
entityName: `Event_${i}`,
|
||||
entityTag: undefined
|
||||
});
|
||||
}
|
||||
const eventTime = performance.now() - startTime;
|
||||
|
||||
console.log(`组件索引管理器: ${componentIndexTime.toFixed(2)}ms (${(componentIndexTime / testCount * 1000).toFixed(1)}μs/entity)`);
|
||||
console.log(`原型系统: ${archetypeTime.toFixed(2)}ms (${(archetypeTime / testCount * 1000).toFixed(1)}μs/entity)`);
|
||||
console.log(`脏标记系统: ${dirtyTrackingTime.toFixed(2)}ms (${(dirtyTrackingTime / testCount * 1000).toFixed(1)}μs/entity)`);
|
||||
console.log(`事件发射: ${eventTime.toFixed(2)}ms (${(eventTime / testCount * 1000).toFixed(1)}μs/entity)`);
|
||||
});
|
||||
});
|
||||
226
packages/core/tests/ECS/Core/EntityCreationPerformance.test.ts
Normal file
226
packages/core/tests/ECS/Core/EntityCreationPerformance.test.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { EntityManager } from '../../../src/ECS/Core/EntityManager';
|
||||
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||||
|
||||
describe('实体创建性能分析', () => {
|
||||
let entityManager: EntityManager;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
entityManager = new EntityManager();
|
||||
});
|
||||
|
||||
test('性能分析:创建10000个实体', () => {
|
||||
const entityCount = 10000;
|
||||
console.log(`开始创建 ${entityCount} 个实体...`);
|
||||
|
||||
// 预热
|
||||
for (let i = 0; i < 100; i++) {
|
||||
entityManager.createEntity(`Warmup_${i}`);
|
||||
}
|
||||
// 重新创建EntityManager来清理
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 测试不同的创建方式
|
||||
console.log('\n=== 性能对比测试 ===');
|
||||
|
||||
// 1. 使用默认名称(包含Date.now())
|
||||
let startTime = performance.now();
|
||||
const entitiesWithDefaultName: any[] = [];
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
entitiesWithDefaultName.push(entityManager.createEntity());
|
||||
}
|
||||
let endTime = performance.now();
|
||||
console.log(`1. 默认名称创建: ${(endTime - startTime).toFixed(2)}ms`);
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 2. 使用预设名称(避免Date.now())
|
||||
startTime = performance.now();
|
||||
const entitiesWithPresetName: any[] = [];
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
entitiesWithPresetName.push(entityManager.createEntity(`Entity_${i}`));
|
||||
}
|
||||
endTime = performance.now();
|
||||
console.log(`2. 预设名称创建: ${(endTime - startTime).toFixed(2)}ms`);
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 3. 使用相同名称(减少字符串创建)
|
||||
startTime = performance.now();
|
||||
const entitiesWithSameName: any[] = [];
|
||||
const sameName = 'SameName';
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
entitiesWithSameName.push(entityManager.createEntity(sameName));
|
||||
}
|
||||
endTime = performance.now();
|
||||
console.log(`3. 相同名称创建: ${(endTime - startTime).toFixed(2)}ms`);
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 4. 直接创建Entity对象(绕过EntityManager)
|
||||
startTime = performance.now();
|
||||
const directEntities: any[] = [];
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
// 直接创建Entity,不通过EntityManager的复杂逻辑
|
||||
directEntities.push(new (require('../../../src/ECS/Entity').Entity)(`Direct_${i}`, i));
|
||||
}
|
||||
endTime = performance.now();
|
||||
console.log(`4. 直接创建Entity: ${(endTime - startTime).toFixed(2)}ms`);
|
||||
|
||||
console.log('\n=== 性能分析结论 ===');
|
||||
console.log('如果相同名称创建明显更快,说明字符串操作是瓶颈');
|
||||
console.log('如果直接创建Entity更快,说明EntityManager的逻辑太重');
|
||||
});
|
||||
|
||||
test('详细分析EntityManager中的性能瓶颈', () => {
|
||||
const entityCount = 1000; // 较小数量便于分析
|
||||
|
||||
console.log('\n=== 详细性能分析 ===');
|
||||
|
||||
// 分析各个步骤的耗时
|
||||
let totalTime = 0;
|
||||
const stepTimes: Record<string, number> = {};
|
||||
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const stepStart = performance.now();
|
||||
|
||||
// 模拟EntityManager.createEntity的各个步骤
|
||||
const name = `PerfTest_${i}`;
|
||||
|
||||
// 步骤1: ID分配
|
||||
let stepTime = performance.now();
|
||||
const id = entityManager['_identifierPool'].checkOut();
|
||||
stepTimes['ID分配'] = (stepTimes['ID分配'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
// 步骤2: Entity创建
|
||||
stepTime = performance.now();
|
||||
const entity = new (require('../../../src/ECS/Entity').Entity)(name, id);
|
||||
stepTimes['Entity创建'] = (stepTimes['Entity创建'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
// 步骤3: 各种索引更新
|
||||
stepTime = performance.now();
|
||||
entityManager['_entities'].set(id, entity);
|
||||
stepTimes['Map存储'] = (stepTimes['Map存储'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
stepTime = performance.now();
|
||||
entityManager['updateNameIndex'](entity, true);
|
||||
stepTimes['名称索引'] = (stepTimes['名称索引'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
stepTime = performance.now();
|
||||
entityManager['updateTagIndex'](entity, true);
|
||||
stepTimes['标签索引'] = (stepTimes['标签索引'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
stepTime = performance.now();
|
||||
entityManager['_componentIndexManager'].addEntity(entity);
|
||||
stepTimes['组件索引'] = (stepTimes['组件索引'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
stepTime = performance.now();
|
||||
entityManager['_archetypeSystem'].addEntity(entity);
|
||||
stepTimes['原型系统'] = (stepTimes['原型系统'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
stepTime = performance.now();
|
||||
entityManager['_dirtyTrackingSystem'].markDirty(entity, 1); // DirtyFlag.COMPONENT_ADDED
|
||||
stepTimes['脏标记'] = (stepTimes['脏标记'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
stepTime = performance.now();
|
||||
// 跳过事件发射,因为它涉及复杂的对象创建
|
||||
stepTimes['其他'] = (stepTimes['其他'] || 0) + (performance.now() - stepTime);
|
||||
|
||||
totalTime += (performance.now() - stepStart);
|
||||
}
|
||||
|
||||
console.log(`总耗时: ${totalTime.toFixed(2)}ms`);
|
||||
console.log('各步骤平均耗时:');
|
||||
for (const [step, time] of Object.entries(stepTimes)) {
|
||||
console.log(` ${step}: ${(time / entityCount * 1000).toFixed(3)}μs/entity`);
|
||||
}
|
||||
|
||||
// 找出最耗时的步骤
|
||||
const maxTime = Math.max(...Object.values(stepTimes));
|
||||
const slowestStep = Object.entries(stepTimes).find(([_, time]) => time === maxTime)?.[0];
|
||||
console.log(`最耗时的步骤: ${slowestStep} (${(maxTime / entityCount * 1000).toFixed(3)}μs/entity)`);
|
||||
});
|
||||
|
||||
test('测试批量创建优化方案', () => {
|
||||
const entityCount = 10000;
|
||||
console.log(`\n=== 批量创建优化测试 ===`);
|
||||
|
||||
// 当前方式:逐个创建
|
||||
let startTime = performance.now();
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
entityManager.createEntity(`Current_${i}`);
|
||||
}
|
||||
let endTime = performance.now();
|
||||
const currentTime = endTime - startTime;
|
||||
console.log(`当前方式: ${currentTime.toFixed(2)}ms`);
|
||||
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 如果有批量创建方法的话...
|
||||
// (这里只是演示概念,实际的批量创建需要在EntityManager中实现)
|
||||
console.log('建议:实现批量创建方法,减少重复的索引更新和事件发射');
|
||||
});
|
||||
|
||||
test('验证批量创建优化效果', () => {
|
||||
const entityCount = 10000;
|
||||
console.log(`\n=== 批量创建优化效果验证 ===`);
|
||||
|
||||
// 测试新的批量创建方法
|
||||
let startTime = performance.now();
|
||||
const batchEntities = entityManager.createEntitiesBatch(entityCount, "Batch", false);
|
||||
let endTime = performance.now();
|
||||
const batchTime = endTime - startTime;
|
||||
console.log(`批量创建(含事件): ${batchTime.toFixed(2)}ms`);
|
||||
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 测试跳过事件的批量创建
|
||||
startTime = performance.now();
|
||||
const batchEntitiesNoEvents = entityManager.createEntitiesBatch(entityCount, "BatchNoEvents", true);
|
||||
endTime = performance.now();
|
||||
const batchTimeNoEvents = endTime - startTime;
|
||||
console.log(`批量创建(跳过事件): ${batchTimeNoEvents.toFixed(2)}ms`);
|
||||
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 对比单个创建(使用优化后的createEntity)
|
||||
startTime = performance.now();
|
||||
const singleEntities: any[] = [];
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
singleEntities.push(entityManager.createEntity(`Single_${i}`));
|
||||
}
|
||||
endTime = performance.now();
|
||||
const singleTime = endTime - startTime;
|
||||
console.log(`优化后单个创建: ${singleTime.toFixed(2)}ms`);
|
||||
|
||||
console.log(`\n性能提升:`);
|
||||
console.log(`批量创建 vs 单个创建: ${(singleTime / batchTime).toFixed(1)}x faster`);
|
||||
console.log(`批量创建(跳过事件) vs 单个创建: ${(singleTime / batchTimeNoEvents).toFixed(1)}x faster`);
|
||||
|
||||
// 验证功能正确性
|
||||
expect(batchEntities.length).toBe(entityCount);
|
||||
expect(batchEntitiesNoEvents.length).toBe(entityCount);
|
||||
expect(singleEntities.length).toBe(entityCount);
|
||||
});
|
||||
|
||||
test('验证createEntity的Date.now()优化', () => {
|
||||
console.log(`\n=== createEntity优化验证 ===`);
|
||||
|
||||
const testCount = 1000;
|
||||
|
||||
// 测试优化后的默认名称生成
|
||||
let startTime = performance.now();
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entityManager.createEntity(); // 使用优化后的计数器命名
|
||||
}
|
||||
let endTime = performance.now();
|
||||
console.log(`计数器命名: ${(endTime - startTime).toFixed(2)}ms`);
|
||||
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 对比:模拟使用Date.now()的方式
|
||||
startTime = performance.now();
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entityManager.createEntity(`Entity_${Date.now()}_${i}`); // 模拟原来的方式
|
||||
}
|
||||
endTime = performance.now();
|
||||
console.log(`Date.now()命名: ${(endTime - startTime).toFixed(2)}ms`);
|
||||
});
|
||||
});
|
||||
626
packages/core/tests/ECS/Core/EntityManager.test.ts
Normal file
626
packages/core/tests/ECS/Core/EntityManager.test.ts
Normal file
@@ -0,0 +1,626 @@
|
||||
import { EntityManager, EntityQueryBuilder } from '../../../src/ECS/Core/EntityManager';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
|
||||
// 测试组件
|
||||
class PositionComponent extends Component {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number;
|
||||
public vy: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public health: number;
|
||||
public maxHealth: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [health = 100, maxHealth = 100] = args as [number?, number?];
|
||||
this.health = health;
|
||||
this.maxHealth = maxHealth;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderComponent extends Component {
|
||||
public visible: boolean;
|
||||
public color: string;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [visible = true, color = 'white'] = args as [boolean?, string?];
|
||||
this.visible = visible;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
class AIComponent extends Component {
|
||||
public intelligence: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [intelligence = 50] = args as [number?];
|
||||
this.intelligence = intelligence;
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerComponent extends Component {
|
||||
public name: string;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [name = 'Player'] = args as [string?];
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
describe('EntityManager - 实体管理器测试', () => {
|
||||
let entityManager: EntityManager;
|
||||
|
||||
beforeEach(() => {
|
||||
entityManager = new EntityManager();
|
||||
});
|
||||
|
||||
describe('基本功能测试', () => {
|
||||
test('应该能够创建EntityManager实例', () => {
|
||||
expect(entityManager).toBeDefined();
|
||||
expect(entityManager).toBeInstanceOf(EntityManager);
|
||||
expect(entityManager.entityCount).toBe(0);
|
||||
});
|
||||
|
||||
test('应该能够创建实体', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
|
||||
expect(entity).toBeDefined();
|
||||
expect(entity.name).toBe('TestEntity');
|
||||
expect(entity.id).toBeGreaterThan(0);
|
||||
expect(entityManager.entityCount).toBe(1);
|
||||
});
|
||||
|
||||
test('应该能够创建实体使用默认名称', () => {
|
||||
const entity = entityManager.createEntity();
|
||||
|
||||
expect(entity).toBeDefined();
|
||||
expect(entity.name).toContain('Entity_');
|
||||
expect(entity.id).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('应该能够批量创建实体', () => {
|
||||
const entities: Entity[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
entities.push(entityManager.createEntity(`Entity_${i}`));
|
||||
}
|
||||
|
||||
expect(entities.length).toBe(5);
|
||||
expect(entityManager.entityCount).toBe(5);
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
expect(entities[i].name).toBe(`Entity_${i}`);
|
||||
expect(entities[i].id).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('应该能够通过ID查找实体', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
const found = entityManager.getEntity(entity.id);
|
||||
|
||||
expect(found).toBe(entity);
|
||||
});
|
||||
|
||||
test('查找不存在的实体应该返回null', () => {
|
||||
const found = entityManager.getEntity(999999);
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
test('应该能够销毁实体', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
const entityId = entity.id;
|
||||
|
||||
const result = entityManager.destroyEntity(entity);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(entityManager.getEntity(entityId)).toBeNull();
|
||||
expect(entityManager.entityCount).toBe(0);
|
||||
});
|
||||
|
||||
test('应该能够通过ID销毁实体', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
const entityId = entity.id;
|
||||
|
||||
const result = entityManager.destroyEntity(entityId);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(entityManager.getEntity(entityId)).toBeNull();
|
||||
});
|
||||
|
||||
test('销毁不存在的实体应该返回false', () => {
|
||||
const result = entityManager.destroyEntity(999999);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test('应该正确统计激活状态的实体', () => {
|
||||
const entity1 = entityManager.createEntity('Active1');
|
||||
const entity2 = entityManager.createEntity('Active2');
|
||||
const entity3 = entityManager.createEntity('Inactive');
|
||||
|
||||
entity3.active = false;
|
||||
|
||||
expect(entityManager.activeEntityCount).toBe(2);
|
||||
expect(entityManager.entityCount).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('实体标签功能测试', () => {
|
||||
test('实体应该有默认标签', () => {
|
||||
const entity = entityManager.createEntity('TaggedEntity');
|
||||
expect(entity.tag).toBe(0); // 默认标签为0
|
||||
});
|
||||
|
||||
test('应该能够为实体设置标签', () => {
|
||||
const entity = entityManager.createEntity('TaggedEntity');
|
||||
entity.tag = 1;
|
||||
|
||||
expect(entity.tag).toBe(1);
|
||||
});
|
||||
|
||||
test('应该能够按标签查询实体', () => {
|
||||
const entity1 = entityManager.createEntity('Entity1');
|
||||
const entity2 = entityManager.createEntity('Entity2');
|
||||
const entity3 = entityManager.createEntity('Entity3');
|
||||
|
||||
entity1.tag = 1;
|
||||
entity2.tag = 1;
|
||||
entity3.tag = 2;
|
||||
|
||||
const tag1Entities = entityManager.getEntitiesByTag(1);
|
||||
const tag2Entities = entityManager.getEntitiesByTag(2);
|
||||
|
||||
expect(tag1Entities.length).toBe(2);
|
||||
expect(tag2Entities.length).toBe(1);
|
||||
expect(tag1Entities).toContain(entity1);
|
||||
expect(tag1Entities).toContain(entity2);
|
||||
expect(tag2Entities).toContain(entity3);
|
||||
});
|
||||
|
||||
test('查询不存在的标签应该返回空数组', () => {
|
||||
const entities = entityManager.getEntitiesByTag(999);
|
||||
expect(entities).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('查询构建器测试', () => {
|
||||
let player: Entity;
|
||||
let enemy1: Entity;
|
||||
let enemy2: Entity;
|
||||
let npc: Entity;
|
||||
|
||||
beforeEach(() => {
|
||||
// 创建测试实体
|
||||
player = entityManager.createEntity('Player');
|
||||
player.addComponent(new PositionComponent(50, 50));
|
||||
player.addComponent(new HealthComponent(100, 100));
|
||||
player.addComponent(new PlayerComponent('Hero'));
|
||||
player.tag = 1;
|
||||
|
||||
enemy1 = entityManager.createEntity('Enemy1');
|
||||
enemy1.addComponent(new PositionComponent(10, 10));
|
||||
enemy1.addComponent(new VelocityComponent(1, 0));
|
||||
enemy1.addComponent(new HealthComponent(50, 50));
|
||||
enemy1.addComponent(new AIComponent(30));
|
||||
enemy1.tag = 2;
|
||||
|
||||
enemy2 = entityManager.createEntity('Enemy2');
|
||||
enemy2.addComponent(new PositionComponent(90, 90));
|
||||
enemy2.addComponent(new VelocityComponent(-1, 0));
|
||||
enemy2.addComponent(new HealthComponent(75, 75));
|
||||
enemy2.addComponent(new AIComponent(45));
|
||||
enemy2.tag = 2;
|
||||
|
||||
npc = entityManager.createEntity('NPC');
|
||||
npc.addComponent(new PositionComponent(25, 75));
|
||||
npc.addComponent(new RenderComponent(true, 'blue'));
|
||||
npc.tag = 3;
|
||||
});
|
||||
|
||||
test('应该能够查询具有所有指定组件的实体', () => {
|
||||
const results = entityManager.query()
|
||||
.withAll(PositionComponent, HealthComponent)
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(3); // player, enemy1, enemy2
|
||||
expect(results).toContain(player);
|
||||
expect(results).toContain(enemy1);
|
||||
expect(results).toContain(enemy2);
|
||||
expect(results).not.toContain(npc);
|
||||
});
|
||||
|
||||
test('应该能够查询具有任意指定组件的实体', () => {
|
||||
const results = entityManager.query()
|
||||
.withAny(PlayerComponent, AIComponent)
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(3); // player, enemy1, enemy2
|
||||
expect(results).toContain(player);
|
||||
expect(results).toContain(enemy1);
|
||||
expect(results).toContain(enemy2);
|
||||
expect(results).not.toContain(npc);
|
||||
});
|
||||
|
||||
test('应该能够排除具有指定组件的实体', () => {
|
||||
const results = entityManager.query()
|
||||
.withAll(PositionComponent)
|
||||
.without(AIComponent)
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(2); // player, npc
|
||||
expect(results).toContain(player);
|
||||
expect(results).toContain(npc);
|
||||
expect(results).not.toContain(enemy1);
|
||||
expect(results).not.toContain(enemy2);
|
||||
});
|
||||
|
||||
test('应该能够按标签过滤实体', () => {
|
||||
const results = entityManager.query()
|
||||
.withTag(2)
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(2); // enemy1, enemy2
|
||||
expect(results).toContain(enemy1);
|
||||
expect(results).toContain(enemy2);
|
||||
expect(results).not.toContain(player);
|
||||
expect(results).not.toContain(npc);
|
||||
});
|
||||
|
||||
test('应该能够排除特定标签的实体', () => {
|
||||
const results = entityManager.query()
|
||||
.withAll(PositionComponent)
|
||||
.withoutTag(2)
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(2); // player, npc
|
||||
expect(results).toContain(player);
|
||||
expect(results).toContain(npc);
|
||||
expect(results).not.toContain(enemy1);
|
||||
expect(results).not.toContain(enemy2);
|
||||
});
|
||||
|
||||
test('应该能够只查询激活状态的实体', () => {
|
||||
enemy1.active = false;
|
||||
|
||||
const results = entityManager.query()
|
||||
.withAll(PositionComponent, HealthComponent)
|
||||
.active()
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(2); // player, enemy2
|
||||
expect(results).toContain(player);
|
||||
expect(results).toContain(enemy2);
|
||||
expect(results).not.toContain(enemy1);
|
||||
});
|
||||
|
||||
test('应该能够只查询启用状态的实体', () => {
|
||||
npc.enabled = false;
|
||||
|
||||
const results = entityManager.query()
|
||||
.withAll(PositionComponent)
|
||||
.enabled()
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(3); // player, enemy1, enemy2
|
||||
expect(results).toContain(player);
|
||||
expect(results).toContain(enemy1);
|
||||
expect(results).toContain(enemy2);
|
||||
expect(results).not.toContain(npc);
|
||||
});
|
||||
|
||||
test('应该能够使用自定义过滤条件', () => {
|
||||
const results = entityManager.query()
|
||||
.withAll(HealthComponent)
|
||||
.where(entity => {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
return health!.health > 60;
|
||||
})
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(2); // player, enemy2
|
||||
expect(results).toContain(player);
|
||||
expect(results).toContain(enemy2);
|
||||
expect(results).not.toContain(enemy1);
|
||||
});
|
||||
|
||||
test('应该能够组合多个查询条件', () => {
|
||||
const results = entityManager.query()
|
||||
.withAll(PositionComponent, HealthComponent)
|
||||
.without(PlayerComponent)
|
||||
.withTag(2)
|
||||
.where(entity => {
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
return position!.x < 50;
|
||||
})
|
||||
.execute();
|
||||
|
||||
expect(results.length).toBe(1); // enemy1
|
||||
expect(results).toContain(enemy1);
|
||||
expect(results).not.toContain(player);
|
||||
expect(results).not.toContain(enemy2);
|
||||
expect(results).not.toContain(npc);
|
||||
});
|
||||
|
||||
test('空查询应该返回所有实体', () => {
|
||||
const results = entityManager.query().execute();
|
||||
|
||||
expect(results.length).toBe(4); // all entities
|
||||
expect(results).toContain(player);
|
||||
expect(results).toContain(enemy1);
|
||||
expect(results).toContain(enemy2);
|
||||
expect(results).toContain(npc);
|
||||
});
|
||||
|
||||
test('不匹配的查询应该返回空数组', () => {
|
||||
const results = entityManager.query()
|
||||
.withAll(PlayerComponent, AIComponent) // 不可能的组合
|
||||
.execute();
|
||||
|
||||
expect(results).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('事件系统集成', () => {
|
||||
test('创建实体应该触发事件', () => {
|
||||
let eventData: any = null;
|
||||
|
||||
entityManager.eventBus.onEntityCreated((data) => {
|
||||
eventData = data;
|
||||
});
|
||||
|
||||
const entity = entityManager.createEntity('EventEntity');
|
||||
|
||||
expect(eventData).toBeDefined();
|
||||
expect(eventData.entityName).toBe('EventEntity');
|
||||
expect(eventData.entityId).toBe(entity.id);
|
||||
});
|
||||
|
||||
test('销毁实体应该触发事件', () => {
|
||||
let eventData: any = null;
|
||||
|
||||
entityManager.eventBus.on('entity:destroyed', (data: any) => {
|
||||
eventData = data;
|
||||
});
|
||||
|
||||
const entity = entityManager.createEntity('EventEntity');
|
||||
entityManager.destroyEntity(entity);
|
||||
|
||||
expect(eventData).toBeDefined();
|
||||
expect(eventData.entityName).toBe('EventEntity');
|
||||
expect(eventData.entityId).toBe(entity.id);
|
||||
});
|
||||
|
||||
test('添加组件应该触发事件', () => {
|
||||
let eventData: any = null;
|
||||
|
||||
entityManager.eventBus.onComponentAdded((data) => {
|
||||
eventData = data;
|
||||
});
|
||||
|
||||
const entity = entityManager.createEntity('ComponentEntity');
|
||||
entity.addComponent(new PositionComponent(10, 20));
|
||||
|
||||
expect(eventData).toBeDefined();
|
||||
expect(eventData.componentType).toBe('PositionComponent');
|
||||
expect(eventData.entityId).toBe(entity.id);
|
||||
});
|
||||
|
||||
test('移除组件应该触发事件', () => {
|
||||
let eventData: any = null;
|
||||
|
||||
entityManager.eventBus.on('component:removed', (data: any) => {
|
||||
eventData = data;
|
||||
});
|
||||
|
||||
const entity = entityManager.createEntity('ComponentEntity');
|
||||
const component = entity.addComponent(new PositionComponent(10, 20));
|
||||
entity.removeComponent(component);
|
||||
|
||||
expect(eventData).toBeDefined();
|
||||
expect(eventData.componentType).toBe('PositionComponent');
|
||||
expect(eventData.entityId).toBe(entity.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能和内存测试', () => {
|
||||
test('大量实体创建性能应该可接受', () => {
|
||||
const entityCount = 10000;
|
||||
const startTime = performance.now();
|
||||
|
||||
const entities: Entity[] = [];
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
entities.push(entityManager.createEntity(`PerfEntity_${i}`));
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
expect(entities.length).toBe(entityCount);
|
||||
expect(entityManager.entityCount).toBe(entityCount);
|
||||
expect(duration).toBeLessThan(1000); // 应该在1秒内完成
|
||||
|
||||
console.log(`创建${entityCount}个实体耗时: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
test('大量实体查询性能应该可接受', () => {
|
||||
const entityCount = 5000;
|
||||
|
||||
// 创建大量实体并添加组件
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const entity = entityManager.createEntity(`Entity_${i}`);
|
||||
entity.addComponent(new PositionComponent(i, i));
|
||||
|
||||
if (i % 2 === 0) {
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
}
|
||||
|
||||
if (i % 3 === 0) {
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
}
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
// 执行多个查询
|
||||
const positionResults = entityManager.query().withAll(PositionComponent).execute();
|
||||
const velocityResults = entityManager.query().withAll(VelocityComponent).execute();
|
||||
const healthResults = entityManager.query().withAll(HealthComponent).execute();
|
||||
const complexResults = entityManager.query()
|
||||
.withAll(PositionComponent, VelocityComponent)
|
||||
.without(HealthComponent)
|
||||
.execute();
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
expect(positionResults.length).toBe(entityCount);
|
||||
expect(velocityResults.length).toBe(entityCount / 2);
|
||||
expect(healthResults.length).toBe(Math.floor(entityCount / 3) + 1);
|
||||
expect(duration).toBeLessThan(200); // 应该在200ms内完成
|
||||
|
||||
console.log(`${entityCount}个实体的复杂查询耗时: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
test('实体销毁应该正确清理内存', () => {
|
||||
const entityCount = 1000;
|
||||
const entities: Entity[] = [];
|
||||
|
||||
// 创建实体
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const entity = entityManager.createEntity(`MemoryEntity_${i}`);
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
expect(entityManager.entityCount).toBe(entityCount);
|
||||
|
||||
// 销毁所有实体
|
||||
entities.forEach(entity => {
|
||||
entityManager.destroyEntity(entity);
|
||||
});
|
||||
|
||||
// 验证所有实体都已被清理
|
||||
expect(entityManager.entityCount).toBe(0);
|
||||
entities.forEach(entity => {
|
||||
expect(entityManager.getEntity(entity.id)).toBeNull();
|
||||
});
|
||||
|
||||
// 查询应该返回空结果
|
||||
const positionResults = entityManager.query().withAll(PositionComponent).execute();
|
||||
const healthResults = entityManager.query().withAll(HealthComponent).execute();
|
||||
|
||||
expect(positionResults).toEqual([]);
|
||||
expect(healthResults).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('错误处理和边界情况', () => {
|
||||
test('对已销毁实体的查询操作应该安全处理', () => {
|
||||
const entity = entityManager.createEntity('ToBeDestroyed');
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
|
||||
entityManager.destroyEntity(entity);
|
||||
|
||||
// 查询不应该包含已销毁的实体
|
||||
const results = entityManager.query().withAll(PositionComponent).execute();
|
||||
expect(results).not.toContain(entity);
|
||||
});
|
||||
|
||||
test('空查询构建器应该能正常工作', () => {
|
||||
const builder = entityManager.query();
|
||||
|
||||
expect(() => {
|
||||
const results = builder.execute();
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('重复销毁同一实体应该安全处理', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
|
||||
const result1 = entityManager.destroyEntity(entity);
|
||||
const result2 = entityManager.destroyEntity(entity);
|
||||
|
||||
expect(result1).toBe(true);
|
||||
expect(result2).toBe(false);
|
||||
});
|
||||
|
||||
test('销毁实体后其组件应该正确清理', () => {
|
||||
const entity = entityManager.createEntity('TestEntity');
|
||||
entity.addComponent(new PositionComponent(10, 20));
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
|
||||
const initialPositionResults = entityManager.query().withAll(PositionComponent).execute();
|
||||
expect(initialPositionResults).toContain(entity);
|
||||
|
||||
entityManager.destroyEntity(entity);
|
||||
|
||||
const finalPositionResults = entityManager.query().withAll(PositionComponent).execute();
|
||||
expect(finalPositionResults).not.toContain(entity);
|
||||
});
|
||||
});
|
||||
|
||||
describe('统计和调试信息', () => {
|
||||
test('应该能够获取实体管理器统计信息', () => {
|
||||
// 创建一些实体和组件
|
||||
const entities: Entity[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const entity = entityManager.createEntity(`StatsEntity_${i}`);
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
// EntityManager doesn't have getStats method, use basic counts
|
||||
expect(entityManager.entityCount).toBe(10);
|
||||
expect(entityManager.activeEntityCount).toBe(10);
|
||||
});
|
||||
|
||||
test('销毁实体后统计信息应该更新', () => {
|
||||
const entities: Entity[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
entities.push(entityManager.createEntity(`StatsEntity_${i}`));
|
||||
}
|
||||
|
||||
entityManager.destroyEntity(entities[0]);
|
||||
entityManager.destroyEntity(entities[1]);
|
||||
|
||||
expect(entityManager.entityCount).toBe(3);
|
||||
expect(entityManager.activeEntityCount).toBe(3);
|
||||
});
|
||||
|
||||
test('非激活实体应该在统计中正确反映', () => {
|
||||
const entities: Entity[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
entities.push(entityManager.createEntity(`StatsEntity_${i}`));
|
||||
}
|
||||
|
||||
entities[0].active = false;
|
||||
entities[1].active = false;
|
||||
|
||||
expect(entityManager.entityCount).toBe(5);
|
||||
expect(entityManager.activeEntityCount).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
529
packages/core/tests/ECS/Core/EventBus.test.ts
Normal file
529
packages/core/tests/ECS/Core/EventBus.test.ts
Normal file
@@ -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<TestEventData>('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<TestEventData>('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<TestEventData>('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<TestEventData>('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<TestEventData>('multi:event', () => { callCount1++; });
|
||||
eventBus.on<TestEventData>('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<TestEventData>('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<string, IEventStats>;
|
||||
|
||||
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({
|
||||
operation: 'frame_render',
|
||||
executionTime: 16.67,
|
||||
metadata: { fps: 30, threshold: 60, message: 'FPS dropped below threshold' },
|
||||
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 as any).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 as any).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();
|
||||
});
|
||||
});
|
||||
624
packages/core/tests/ECS/Core/EventSystem.test.ts
Normal file
624
packages/core/tests/ECS/Core/EventSystem.test.ts
Normal file
@@ -0,0 +1,624 @@
|
||||
import { TypeSafeEventSystem, GlobalEventSystem } from '../../../src/ECS/Core/EventSystem';
|
||||
import { ECSEventType } from '../../../src/ECS/CoreEvents';
|
||||
|
||||
// 测试事件数据类型
|
||||
interface TestCustomEvent {
|
||||
playerId: number;
|
||||
message: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface PlayerLevelUpEvent {
|
||||
playerId: number;
|
||||
oldLevel: number;
|
||||
newLevel: number;
|
||||
experience: number;
|
||||
}
|
||||
|
||||
interface EntityCreatedEvent {
|
||||
entityId: number;
|
||||
entityName: string;
|
||||
componentCount: number;
|
||||
}
|
||||
|
||||
describe('EventSystem - 事件系统测试', () => {
|
||||
let eventSystem: TypeSafeEventSystem;
|
||||
|
||||
beforeEach(() => {
|
||||
eventSystem = new TypeSafeEventSystem();
|
||||
});
|
||||
|
||||
describe('基本事件功能', () => {
|
||||
test('应该能够注册事件监听器', () => {
|
||||
let eventReceived = false;
|
||||
|
||||
eventSystem.on('test:event', () => {
|
||||
eventReceived = true;
|
||||
});
|
||||
|
||||
eventSystem.emit('test:event', {});
|
||||
|
||||
expect(eventReceived).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够传递事件数据', () => {
|
||||
let receivedData: TestCustomEvent | null = null;
|
||||
|
||||
eventSystem.on('custom:test', (data: TestCustomEvent) => {
|
||||
receivedData = data;
|
||||
});
|
||||
|
||||
const testData: TestCustomEvent = {
|
||||
playerId: 123,
|
||||
message: 'Hello World',
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
eventSystem.emit('custom:test', testData);
|
||||
|
||||
expect(receivedData).toEqual(testData);
|
||||
});
|
||||
|
||||
test('应该能够移除事件监听器', () => {
|
||||
let callCount = 0;
|
||||
|
||||
const handler = () => {
|
||||
callCount++;
|
||||
};
|
||||
|
||||
const listenerId = eventSystem.on('removable:event', handler);
|
||||
eventSystem.emit('removable:event', {});
|
||||
expect(callCount).toBe(1);
|
||||
|
||||
eventSystem.off('removable:event', listenerId);
|
||||
eventSystem.emit('removable:event', {});
|
||||
expect(callCount).toBe(1); // 应该保持不变
|
||||
});
|
||||
|
||||
test('应该能够一次性监听事件', async () => {
|
||||
let callCount = 0;
|
||||
|
||||
eventSystem.once('once:event', () => {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
await eventSystem.emit('once:event', {});
|
||||
await eventSystem.emit('once:event', {});
|
||||
await eventSystem.emit('once:event', {});
|
||||
|
||||
expect(callCount).toBe(1); // 只应该被调用一次
|
||||
});
|
||||
|
||||
test('应该能够处理多个监听器', () => {
|
||||
const results: string[] = [];
|
||||
|
||||
eventSystem.on('multi:event', () => {
|
||||
results.push('handler1');
|
||||
});
|
||||
|
||||
eventSystem.on('multi:event', () => {
|
||||
results.push('handler2');
|
||||
});
|
||||
|
||||
eventSystem.on('multi:event', () => {
|
||||
results.push('handler3');
|
||||
});
|
||||
|
||||
eventSystem.emit('multi:event', {});
|
||||
|
||||
expect(results).toEqual(['handler1', 'handler2', 'handler3']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('预定义ECS事件', () => {
|
||||
test('应该能够监听实体创建事件', () => {
|
||||
let entityCreatedData: any = null;
|
||||
|
||||
eventSystem.on(ECSEventType.ENTITY_CREATED, (data: any) => {
|
||||
entityCreatedData = data;
|
||||
});
|
||||
|
||||
const testData = {
|
||||
entityId: 123,
|
||||
entityName: 'TestEntity',
|
||||
componentCount: 3
|
||||
};
|
||||
|
||||
eventSystem.emit(ECSEventType.ENTITY_CREATED, testData);
|
||||
|
||||
expect(entityCreatedData).toEqual(testData);
|
||||
});
|
||||
|
||||
test('应该能够监听实体销毁事件', () => {
|
||||
let entityDestroyedData: any = null;
|
||||
|
||||
eventSystem.on(ECSEventType.ENTITY_DESTROYED, (data: any) => {
|
||||
entityDestroyedData = data;
|
||||
});
|
||||
|
||||
const testData = {
|
||||
entityId: 456,
|
||||
entityName: 'DestroyedEntity',
|
||||
componentCount: 2
|
||||
};
|
||||
|
||||
eventSystem.emit(ECSEventType.ENTITY_DESTROYED, testData);
|
||||
|
||||
expect(entityDestroyedData).toEqual(testData);
|
||||
});
|
||||
|
||||
test('应该能够监听组件添加事件', () => {
|
||||
let componentAddedData: any = null;
|
||||
|
||||
eventSystem.on(ECSEventType.COMPONENT_ADDED, (data: any) => {
|
||||
componentAddedData = data;
|
||||
});
|
||||
|
||||
const testData = {
|
||||
entityId: 789,
|
||||
componentType: 'PositionComponent',
|
||||
componentData: { x: 10, y: 20 }
|
||||
};
|
||||
|
||||
eventSystem.emit(ECSEventType.COMPONENT_ADDED, testData);
|
||||
|
||||
expect(componentAddedData).toEqual(testData);
|
||||
});
|
||||
|
||||
test('应该能够监听组件移除事件', () => {
|
||||
let componentRemovedData: any = null;
|
||||
|
||||
eventSystem.on(ECSEventType.COMPONENT_REMOVED, (data: any) => {
|
||||
componentRemovedData = data;
|
||||
});
|
||||
|
||||
const testData = {
|
||||
entityId: 101112,
|
||||
componentType: 'VelocityComponent',
|
||||
componentData: { vx: 5, vy: -3 }
|
||||
};
|
||||
|
||||
eventSystem.emit(ECSEventType.COMPONENT_REMOVED, testData);
|
||||
|
||||
expect(componentRemovedData).toEqual(testData);
|
||||
});
|
||||
|
||||
test('应该能够监听系统添加事件', () => {
|
||||
let systemAddedData: any = null;
|
||||
|
||||
eventSystem.on(ECSEventType.SYSTEM_ADDED, (data: any) => {
|
||||
systemAddedData = data;
|
||||
});
|
||||
|
||||
const testData = {
|
||||
systemName: 'MovementSystem',
|
||||
systemType: 'EntitySystem',
|
||||
updateOrder: 1
|
||||
};
|
||||
|
||||
eventSystem.emit(ECSEventType.SYSTEM_ADDED, testData);
|
||||
|
||||
expect(systemAddedData).toEqual(testData);
|
||||
});
|
||||
|
||||
test('应该能够监听系统移除事件', () => {
|
||||
let systemRemovedData: any = null;
|
||||
|
||||
eventSystem.on(ECSEventType.SYSTEM_REMOVED, (data: any) => {
|
||||
systemRemovedData = data;
|
||||
});
|
||||
|
||||
const testData = {
|
||||
systemName: 'RenderSystem',
|
||||
systemType: 'EntitySystem',
|
||||
updateOrder: 2
|
||||
};
|
||||
|
||||
eventSystem.emit(ECSEventType.SYSTEM_REMOVED, testData);
|
||||
|
||||
expect(systemRemovedData).toEqual(testData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('事件优先级和执行顺序', () => {
|
||||
test('应该按优先级顺序执行监听器', () => {
|
||||
const executionOrder: string[] = [];
|
||||
|
||||
// 添加不同优先级的监听器
|
||||
eventSystem.on('priority:event', () => {
|
||||
executionOrder.push('normal');
|
||||
});
|
||||
|
||||
eventSystem.on('priority:event', () => {
|
||||
executionOrder.push('high');
|
||||
}, { priority: 10 });
|
||||
|
||||
eventSystem.on('priority:event', () => {
|
||||
executionOrder.push('low');
|
||||
}, { priority: -10 });
|
||||
|
||||
eventSystem.emit('priority:event', {});
|
||||
|
||||
// 应该按照 high -> normal -> low 的顺序执行
|
||||
expect(executionOrder).toEqual(['high', 'normal', 'low']);
|
||||
});
|
||||
|
||||
test('相同优先级的监听器应该按注册顺序执行', () => {
|
||||
const executionOrder: string[] = [];
|
||||
|
||||
eventSystem.on('order:event', () => {
|
||||
executionOrder.push('first');
|
||||
});
|
||||
|
||||
eventSystem.on('order:event', () => {
|
||||
executionOrder.push('second');
|
||||
});
|
||||
|
||||
eventSystem.on('order:event', () => {
|
||||
executionOrder.push('third');
|
||||
});
|
||||
|
||||
eventSystem.emit('order:event', {});
|
||||
|
||||
expect(executionOrder).toEqual(['first', 'second', 'third']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('异步事件处理', () => {
|
||||
test('应该能够处理异步事件监听器', async () => {
|
||||
let asyncResult = '';
|
||||
|
||||
eventSystem.on('async:event', async (data: { message: string }) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
asyncResult = data.message;
|
||||
}, { async: true });
|
||||
|
||||
await eventSystem.emit('async:event', { message: 'async test' });
|
||||
|
||||
expect(asyncResult).toBe('async test');
|
||||
});
|
||||
|
||||
test('应该能够等待所有异步监听器完成', async () => {
|
||||
const results: string[] = [];
|
||||
|
||||
eventSystem.on('multi-async:event', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
results.push('handler1');
|
||||
}, { async: true });
|
||||
|
||||
eventSystem.on('multi-async:event', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
results.push('handler2');
|
||||
}, { async: true });
|
||||
|
||||
eventSystem.on('multi-async:event', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 5));
|
||||
results.push('handler3');
|
||||
}, { async: true });
|
||||
|
||||
await eventSystem.emit('multi-async:event', {});
|
||||
|
||||
// 所有异步处理器都应该完成
|
||||
expect(results.length).toBe(3);
|
||||
expect(results).toContain('handler1');
|
||||
expect(results).toContain('handler2');
|
||||
expect(results).toContain('handler3');
|
||||
});
|
||||
|
||||
test('异步事件处理中的错误应该被正确处理', async () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
let successHandlerCalled = false;
|
||||
|
||||
eventSystem.on('error:event', async () => {
|
||||
throw new Error('Test async error');
|
||||
}, { async: true });
|
||||
|
||||
eventSystem.on('error:event', async () => {
|
||||
successHandlerCalled = true;
|
||||
}, { async: true });
|
||||
|
||||
// emit方法应该内部处理异步错误,不向外抛出
|
||||
await expect(eventSystem.emit('error:event', {})).resolves.toBeUndefined();
|
||||
|
||||
// 成功的处理器应该被调用
|
||||
expect(successHandlerCalled).toBe(true);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('事件验证和类型安全', () => {
|
||||
test('应该能够验证事件数据类型', () => {
|
||||
let validationPassed = false;
|
||||
|
||||
eventSystem.on('typed:event', (data: PlayerLevelUpEvent) => {
|
||||
// TypeScript应该确保类型安全
|
||||
expect(typeof data.playerId).toBe('number');
|
||||
expect(typeof data.oldLevel).toBe('number');
|
||||
expect(typeof data.newLevel).toBe('number');
|
||||
expect(typeof data.experience).toBe('number');
|
||||
validationPassed = true;
|
||||
});
|
||||
|
||||
const levelUpData: PlayerLevelUpEvent = {
|
||||
playerId: 123,
|
||||
oldLevel: 5,
|
||||
newLevel: 6,
|
||||
experience: 1500
|
||||
};
|
||||
|
||||
eventSystem.emit('typed:event', levelUpData);
|
||||
|
||||
expect(validationPassed).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够处理复杂的事件数据结构', () => {
|
||||
interface ComplexEvent {
|
||||
metadata: {
|
||||
timestamp: number;
|
||||
source: string;
|
||||
};
|
||||
payload: {
|
||||
entities: Array<{
|
||||
id: number;
|
||||
components: string[];
|
||||
}>;
|
||||
systems: string[];
|
||||
};
|
||||
}
|
||||
|
||||
let receivedEvent: ComplexEvent | null = null;
|
||||
|
||||
eventSystem.on('complex:event', (data: ComplexEvent) => {
|
||||
receivedEvent = data;
|
||||
});
|
||||
|
||||
const complexData: ComplexEvent = {
|
||||
metadata: {
|
||||
timestamp: Date.now(),
|
||||
source: 'test'
|
||||
},
|
||||
payload: {
|
||||
entities: [
|
||||
{ id: 1, components: ['Position', 'Velocity'] },
|
||||
{ id: 2, components: ['Health', 'Render'] }
|
||||
],
|
||||
systems: ['Movement', 'Render', 'Combat']
|
||||
}
|
||||
};
|
||||
|
||||
eventSystem.emit('complex:event', complexData);
|
||||
|
||||
expect(receivedEvent).toEqual(complexData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能和内存管理', () => {
|
||||
test('大量事件监听器应该有良好的性能', () => {
|
||||
const listenerCount = 50; // 减少数量以避免超过限制
|
||||
let callCount = 0;
|
||||
|
||||
// 注册大量监听器
|
||||
for (let i = 0; i < listenerCount; i++) {
|
||||
eventSystem.on('perf:event', () => {
|
||||
callCount++;
|
||||
});
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
eventSystem.emit('perf:event', {});
|
||||
const endTime = performance.now();
|
||||
|
||||
expect(callCount).toBe(listenerCount);
|
||||
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).toBeLessThan(100); // 应该在100ms内完成
|
||||
|
||||
console.log(`${listenerCount}个监听器的事件触发耗时: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
test('频繁的事件触发应该有良好的性能', () => {
|
||||
let eventCount = 0;
|
||||
|
||||
eventSystem.on('frequent:event', () => {
|
||||
eventCount++;
|
||||
});
|
||||
|
||||
const emitCount = 10000;
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < emitCount; i++) {
|
||||
eventSystem.emit('frequent:event', { index: i });
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
|
||||
expect(eventCount).toBe(emitCount);
|
||||
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).toBeLessThan(200); // 应该在200ms内完成
|
||||
|
||||
console.log(`${emitCount}次事件触发耗时: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
|
||||
test('移除监听器应该释放内存', () => {
|
||||
const listenerIds: string[] = [];
|
||||
|
||||
// 添加大量监听器
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const handler = () => {};
|
||||
const id = eventSystem.on('memory:event', handler);
|
||||
listenerIds.push(id);
|
||||
}
|
||||
|
||||
// 触发事件以确保监听器正常工作
|
||||
eventSystem.emit('memory:event', {});
|
||||
|
||||
// 移除所有监听器
|
||||
listenerIds.forEach(id => {
|
||||
eventSystem.off('memory:event', id);
|
||||
});
|
||||
|
||||
// 再次触发事件,应该没有监听器被调用
|
||||
let callCount = 0;
|
||||
eventSystem.on('memory:event', () => {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
eventSystem.emit('memory:event', {});
|
||||
expect(callCount).toBe(1); // 只有新添加的监听器被调用
|
||||
});
|
||||
|
||||
test('应该能够清理所有事件监听器', () => {
|
||||
let callCount = 0;
|
||||
|
||||
eventSystem.on('cleanup:event1', () => callCount++);
|
||||
eventSystem.on('cleanup:event2', () => callCount++);
|
||||
eventSystem.on('cleanup:event3', () => callCount++);
|
||||
|
||||
// 清理所有监听器
|
||||
eventSystem.clear();
|
||||
|
||||
// 触发事件,应该没有监听器被调用
|
||||
eventSystem.emit('cleanup:event1', {});
|
||||
eventSystem.emit('cleanup:event2', {});
|
||||
eventSystem.emit('cleanup:event3', {});
|
||||
|
||||
expect(callCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('错误处理', () => {
|
||||
test('监听器中的错误不应该影响其他监听器', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
let successHandlerCalled = false;
|
||||
|
||||
eventSystem.on('error:event', () => {
|
||||
throw new Error('Test error in handler');
|
||||
});
|
||||
|
||||
eventSystem.on('error:event', () => {
|
||||
successHandlerCalled = true;
|
||||
});
|
||||
|
||||
// 触发事件不应该抛出异常
|
||||
expect(() => {
|
||||
eventSystem.emit('error:event', {});
|
||||
}).not.toThrow();
|
||||
|
||||
// 成功的处理器应该被调用
|
||||
expect(successHandlerCalled).toBe(true);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('应该能够处理监听器注册和移除中的边界情况', () => {
|
||||
const handler = () => {};
|
||||
|
||||
// 移除不存在的监听器应该安全
|
||||
expect(() => {
|
||||
const result = eventSystem.off('nonexistent:event', 'non-existent-id');
|
||||
expect(result).toBe(false);
|
||||
}).not.toThrow();
|
||||
|
||||
// 重复添加相同的监听器应该安全
|
||||
const id1 = eventSystem.on('duplicate:event', handler);
|
||||
const id2 = eventSystem.on('duplicate:event', handler);
|
||||
|
||||
let callCount = 0;
|
||||
eventSystem.on('duplicate:event', () => {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
eventSystem.emit('duplicate:event', {});
|
||||
|
||||
// 所有监听器都应该被调用
|
||||
expect(callCount).toBe(1); // 新添加的监听器被调用
|
||||
});
|
||||
|
||||
test('触发不存在的事件应该安全', () => {
|
||||
expect(() => {
|
||||
eventSystem.emit('nonexistent:event', {});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('全局事件系统', () => {
|
||||
test('全局事件系统应该能够跨实例通信', () => {
|
||||
let receivedData: any = null;
|
||||
|
||||
GlobalEventSystem.on('global:test', (data) => {
|
||||
receivedData = data;
|
||||
});
|
||||
|
||||
const testData = { message: 'global event test' };
|
||||
GlobalEventSystem.emit('global:test', testData);
|
||||
|
||||
expect(receivedData).toEqual(testData);
|
||||
});
|
||||
|
||||
test('全局事件系统应该是全局实例', () => {
|
||||
// GlobalEventSystem 是全局实例,不需要getInstance
|
||||
expect(GlobalEventSystem).toBeDefined();
|
||||
expect(GlobalEventSystem).toBeInstanceOf(TypeSafeEventSystem);
|
||||
});
|
||||
|
||||
test('全局事件系统应该能够与局部事件系统独立工作', () => {
|
||||
let localCallCount = 0;
|
||||
let globalCallCount = 0;
|
||||
|
||||
eventSystem.on('isolated:event', () => {
|
||||
localCallCount++;
|
||||
});
|
||||
|
||||
GlobalEventSystem.on('isolated:event', () => {
|
||||
globalCallCount++;
|
||||
});
|
||||
|
||||
// 触发局部事件
|
||||
eventSystem.emit('isolated:event', {});
|
||||
expect(localCallCount).toBe(1);
|
||||
expect(globalCallCount).toBe(0);
|
||||
|
||||
// 触发全局事件
|
||||
GlobalEventSystem.emit('isolated:event', {});
|
||||
expect(localCallCount).toBe(1);
|
||||
expect(globalCallCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('事件统计和调试', () => {
|
||||
test('应该能够获取事件系统统计信息', () => {
|
||||
// 添加一些监听器
|
||||
eventSystem.on('stats:event1', () => {});
|
||||
eventSystem.on('stats:event1', () => {});
|
||||
eventSystem.on('stats:event2', () => {});
|
||||
|
||||
// 触发一些事件
|
||||
eventSystem.emit('stats:event1', {});
|
||||
eventSystem.emit('stats:event2', {});
|
||||
eventSystem.emit('stats:event1', {});
|
||||
|
||||
const stats = eventSystem.getStats() as Map<string, any>;
|
||||
|
||||
expect(stats).toBeInstanceOf(Map);
|
||||
expect(stats.size).toBe(2);
|
||||
});
|
||||
|
||||
test('应该能够获取特定事件的统计信息', async () => {
|
||||
eventSystem.on('specific:event', () => {});
|
||||
eventSystem.on('specific:event', () => {});
|
||||
|
||||
await eventSystem.emit('specific:event', {});
|
||||
await eventSystem.emit('specific:event', {});
|
||||
await eventSystem.emit('specific:event', {});
|
||||
|
||||
const eventStats = eventSystem.getStats('specific:event') as any;
|
||||
|
||||
expect(eventStats.listenerCount).toBe(2);
|
||||
expect(eventStats.triggerCount).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
691
packages/core/tests/ECS/Core/FluentAPI.test.ts
Normal file
691
packages/core/tests/ECS/Core/FluentAPI.test.ts
Normal file
@@ -0,0 +1,691 @@
|
||||
import {
|
||||
EntityBuilder,
|
||||
SceneBuilder,
|
||||
ComponentBuilder,
|
||||
ECSFluentAPI,
|
||||
EntityBatchOperator,
|
||||
createECSAPI,
|
||||
initializeECS,
|
||||
ECS
|
||||
} from '../../../src/ECS/Core/FluentAPI';
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { QuerySystem } from '../../../src/ECS/Core/QuerySystem';
|
||||
import { TypeSafeEventSystem } from '../../../src/ECS/Core/EventSystem';
|
||||
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
|
||||
// 测试组件
|
||||
class TestComponent extends Component {
|
||||
public value: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [value = 0] = args as [number?];
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class PositionComponent extends Component {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number;
|
||||
public vy: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试系统
|
||||
class TestSystem extends EntitySystem {
|
||||
constructor() {
|
||||
super(Matcher.empty().all(TestComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 测试系统
|
||||
}
|
||||
}
|
||||
|
||||
describe('FluentAPI - 流式API测试', () => {
|
||||
let scene: Scene;
|
||||
let querySystem: QuerySystem;
|
||||
let eventSystem: TypeSafeEventSystem;
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene();
|
||||
querySystem = new QuerySystem();
|
||||
eventSystem = new TypeSafeEventSystem();
|
||||
});
|
||||
|
||||
describe('EntityBuilder - 实体构建器', () => {
|
||||
let builder: EntityBuilder;
|
||||
|
||||
beforeEach(() => {
|
||||
builder = new EntityBuilder(scene, scene.componentStorageManager);
|
||||
});
|
||||
|
||||
test('应该能够创建实体构建器', () => {
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
|
||||
test('应该能够设置实体名称', () => {
|
||||
const entity = builder.named('TestEntity').build();
|
||||
expect(entity.name).toBe('TestEntity');
|
||||
});
|
||||
|
||||
test('应该能够设置实体标签', () => {
|
||||
const entity = builder.tagged(42).build();
|
||||
expect(entity.tag).toBe(42);
|
||||
});
|
||||
|
||||
test('应该能够添加组件', () => {
|
||||
const component = new TestComponent(100);
|
||||
const entity = builder.with(component).build();
|
||||
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.getComponent(TestComponent)).toBe(component);
|
||||
});
|
||||
|
||||
test('应该能够添加多个组件', () => {
|
||||
const comp1 = new TestComponent(100);
|
||||
const comp2 = new PositionComponent(10, 20);
|
||||
const comp3 = new VelocityComponent(1, 2);
|
||||
|
||||
const entity = builder.withComponents(comp1, comp2, comp3).build();
|
||||
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(true);
|
||||
expect(entity.hasComponent(VelocityComponent)).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够条件性添加组件', () => {
|
||||
const comp1 = new TestComponent(100);
|
||||
const comp2 = new PositionComponent(10, 20);
|
||||
|
||||
const entity = builder
|
||||
.withIf(true, comp1)
|
||||
.withIf(false, comp2)
|
||||
.build();
|
||||
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够使用工厂函数创建组件', () => {
|
||||
const entity = builder
|
||||
.withFactory(() => new TestComponent(200))
|
||||
.build();
|
||||
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.getComponent(TestComponent)!.value).toBe(200);
|
||||
});
|
||||
|
||||
test('应该能够配置组件属性', () => {
|
||||
const entity = builder
|
||||
.with(new TestComponent(100))
|
||||
.configure(TestComponent, (comp) => {
|
||||
comp.value = 300;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(entity.getComponent(TestComponent)!.value).toBe(300);
|
||||
});
|
||||
|
||||
test('配置不存在的组件应该安全处理', () => {
|
||||
expect(() => {
|
||||
builder.configure(TestComponent, (comp) => {
|
||||
comp.value = 300;
|
||||
}).build();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('应该能够设置实体启用状态', () => {
|
||||
const entity1 = builder.enabled(true).build();
|
||||
const entity2 = new EntityBuilder(scene, scene.componentStorageManager).enabled(false).build();
|
||||
|
||||
expect(entity1.enabled).toBe(true);
|
||||
expect(entity2.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够设置实体活跃状态', () => {
|
||||
const entity1 = builder.active(true).build();
|
||||
const entity2 = new EntityBuilder(scene, scene.componentStorageManager).active(false).build();
|
||||
|
||||
expect(entity1.active).toBe(true);
|
||||
expect(entity2.active).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够添加子实体', () => {
|
||||
const childBuilder = new EntityBuilder(scene, scene.componentStorageManager)
|
||||
.named('Child')
|
||||
.with(new TestComponent(50));
|
||||
|
||||
const parent = builder
|
||||
.named('Parent')
|
||||
.withChild(childBuilder)
|
||||
.build();
|
||||
|
||||
expect(parent.children.length).toBe(1);
|
||||
expect(parent.children[0].name).toBe('Child');
|
||||
});
|
||||
|
||||
test('应该能够添加多个子实体', () => {
|
||||
const child1 = new EntityBuilder(scene, scene.componentStorageManager).named('Child1');
|
||||
const child2 = new EntityBuilder(scene, scene.componentStorageManager).named('Child2');
|
||||
const child3 = new EntityBuilder(scene, scene.componentStorageManager).named('Child3');
|
||||
|
||||
const parent = builder
|
||||
.named('Parent')
|
||||
.withChildren(child1, child2, child3)
|
||||
.build();
|
||||
|
||||
expect(parent.children.length).toBe(3);
|
||||
expect(parent.children[0].name).toBe('Child1');
|
||||
expect(parent.children[1].name).toBe('Child2');
|
||||
expect(parent.children[2].name).toBe('Child3');
|
||||
});
|
||||
|
||||
test('应该能够使用工厂函数创建子实体', () => {
|
||||
const parent = builder
|
||||
.named('Parent')
|
||||
.withChildFactory((parentEntity) => {
|
||||
return new EntityBuilder(scene, scene.componentStorageManager)
|
||||
.named(`Child_of_${parentEntity.name}`)
|
||||
.with(new TestComponent(100));
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(parent.children.length).toBe(1);
|
||||
expect(parent.children[0].name).toBe('Child_of_Parent');
|
||||
});
|
||||
|
||||
test('应该能够条件性添加子实体', () => {
|
||||
const child1 = new EntityBuilder(scene, scene.componentStorageManager).named('Child1');
|
||||
const child2 = new EntityBuilder(scene, scene.componentStorageManager).named('Child2');
|
||||
|
||||
const parent = builder
|
||||
.named('Parent')
|
||||
.withChildIf(true, child1)
|
||||
.withChildIf(false, child2)
|
||||
.build();
|
||||
|
||||
expect(parent.children.length).toBe(1);
|
||||
expect(parent.children[0].name).toBe('Child1');
|
||||
});
|
||||
|
||||
test('应该能够构建实体并添加到场景', () => {
|
||||
const entity = builder
|
||||
.named('SpawnedEntity')
|
||||
.with(new TestComponent(100))
|
||||
.spawn();
|
||||
|
||||
expect(entity.name).toBe('SpawnedEntity');
|
||||
expect(entity.scene).toBe(scene);
|
||||
});
|
||||
|
||||
test('应该能够克隆构建器', () => {
|
||||
const originalBuilder = builder.named('Original').with(new TestComponent(100));
|
||||
const clonedBuilder = originalBuilder.clone();
|
||||
|
||||
expect(clonedBuilder).toBeInstanceOf(EntityBuilder);
|
||||
expect(clonedBuilder).not.toBe(originalBuilder);
|
||||
});
|
||||
|
||||
test('流式调用应该工作正常', () => {
|
||||
const entity = builder
|
||||
.named('ComplexEntity')
|
||||
.tagged(42)
|
||||
.with(new TestComponent(100))
|
||||
.with(new PositionComponent(10, 20))
|
||||
.enabled(true)
|
||||
.active(true)
|
||||
.configure(TestComponent, (comp) => {
|
||||
comp.value = 200;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(entity.name).toBe('ComplexEntity');
|
||||
expect(entity.tag).toBe(42);
|
||||
expect(entity.enabled).toBe(true);
|
||||
expect(entity.active).toBe(true);
|
||||
expect(entity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity.hasComponent(PositionComponent)).toBe(true);
|
||||
expect(entity.getComponent(TestComponent)!.value).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SceneBuilder - 场景构建器', () => {
|
||||
let builder: SceneBuilder;
|
||||
|
||||
beforeEach(() => {
|
||||
builder = new SceneBuilder();
|
||||
});
|
||||
|
||||
test('应该能够创建场景构建器', () => {
|
||||
expect(builder).toBeInstanceOf(SceneBuilder);
|
||||
});
|
||||
|
||||
test('应该能够设置场景名称', () => {
|
||||
const scene = builder.named('TestScene').build();
|
||||
expect(scene.name).toBe('TestScene');
|
||||
});
|
||||
|
||||
test('应该能够添加实体', () => {
|
||||
const entity = new Entity('TestEntity', 1);
|
||||
const scene = builder.withEntity(entity).build();
|
||||
|
||||
expect(scene.entities.count).toBe(1);
|
||||
expect(scene.findEntity('TestEntity')).toBe(entity);
|
||||
});
|
||||
|
||||
test('应该能够使用实体构建器添加实体', () => {
|
||||
const scene = builder
|
||||
.withEntityBuilder((builder) => {
|
||||
return builder
|
||||
.named('BuilderEntity')
|
||||
.with(new TestComponent(100));
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(scene.entities.count).toBe(1);
|
||||
expect(scene.findEntity('BuilderEntity')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('应该能够批量添加实体', () => {
|
||||
const entity1 = new Entity('Entity1', 1);
|
||||
const entity2 = new Entity('Entity2', 2);
|
||||
const entity3 = new Entity('Entity3', 3);
|
||||
|
||||
const scene = builder
|
||||
.withEntities(entity1, entity2, entity3)
|
||||
.build();
|
||||
|
||||
expect(scene.entities.count).toBe(3);
|
||||
});
|
||||
|
||||
test('应该能够添加系统', () => {
|
||||
const system = new TestSystem();
|
||||
const scene = builder.withSystem(system).build();
|
||||
|
||||
expect(scene.systems.length).toBe(1);
|
||||
expect(scene.systems[0]).toBe(system);
|
||||
});
|
||||
|
||||
test('应该能够批量添加系统', () => {
|
||||
const system1 = new TestSystem();
|
||||
const system2 = new TestSystem();
|
||||
|
||||
const scene = builder
|
||||
.withSystems(system1, system2)
|
||||
.build();
|
||||
|
||||
expect(scene.systems.length).toBe(2);
|
||||
});
|
||||
|
||||
test('流式调用应该工作正常', () => {
|
||||
const entity = new Entity('TestEntity', 1);
|
||||
const system = new TestSystem();
|
||||
|
||||
const scene = builder
|
||||
.named('ComplexScene')
|
||||
.withEntity(entity)
|
||||
.withSystem(system)
|
||||
.withEntityBuilder((builder) => {
|
||||
return builder.named('BuilderEntity');
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(scene.name).toBe('ComplexScene');
|
||||
expect(scene.entities.count).toBe(2);
|
||||
expect(scene.systems.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ComponentBuilder - 组件构建器', () => {
|
||||
test('应该能够创建组件构建器', () => {
|
||||
const builder = new ComponentBuilder(TestComponent, 100);
|
||||
expect(builder).toBeInstanceOf(ComponentBuilder);
|
||||
});
|
||||
|
||||
test('应该能够设置组件属性', () => {
|
||||
const component = new ComponentBuilder(TestComponent, 100)
|
||||
.set('value', 200)
|
||||
.build();
|
||||
|
||||
expect(component.value).toBe(200);
|
||||
});
|
||||
|
||||
test('应该能够使用配置函数', () => {
|
||||
const component = new ComponentBuilder(PositionComponent, 10, 20)
|
||||
.configure((comp) => {
|
||||
comp.x = 30;
|
||||
comp.y = 40;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(component.x).toBe(30);
|
||||
expect(component.y).toBe(40);
|
||||
});
|
||||
|
||||
test('应该能够条件性设置属性', () => {
|
||||
const component = new ComponentBuilder(TestComponent, 100)
|
||||
.setIf(true, 'value', 200)
|
||||
.setIf(false, 'value', 300)
|
||||
.build();
|
||||
|
||||
expect(component.value).toBe(200);
|
||||
});
|
||||
|
||||
test('流式调用应该工作正常', () => {
|
||||
const component = new ComponentBuilder(PositionComponent, 0, 0)
|
||||
.set('x', 10)
|
||||
.set('y', 20)
|
||||
.setIf(true, 'x', 30)
|
||||
.configure((comp) => {
|
||||
comp.y = 40;
|
||||
})
|
||||
.build();
|
||||
|
||||
expect(component.x).toBe(30);
|
||||
expect(component.y).toBe(40);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ECSFluentAPI - 主API', () => {
|
||||
let api: ECSFluentAPI;
|
||||
|
||||
beforeEach(() => {
|
||||
api = new ECSFluentAPI(scene, querySystem, eventSystem);
|
||||
});
|
||||
|
||||
test('应该能够创建ECS API', () => {
|
||||
expect(api).toBeInstanceOf(ECSFluentAPI);
|
||||
});
|
||||
|
||||
test('应该能够创建实体构建器', () => {
|
||||
const builder = api.createEntity();
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
|
||||
test('应该能够创建场景构建器', () => {
|
||||
const builder = api.createScene();
|
||||
expect(builder).toBeInstanceOf(SceneBuilder);
|
||||
});
|
||||
|
||||
test('应该能够创建组件构建器', () => {
|
||||
const builder = api.createComponent(TestComponent, 100);
|
||||
expect(builder).toBeInstanceOf(ComponentBuilder);
|
||||
});
|
||||
|
||||
test('应该能够创建查询构建器', () => {
|
||||
const builder = api.query();
|
||||
expect(builder).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够查找实体', () => {
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
entity.addComponent(new TestComponent(100));
|
||||
querySystem.setEntities([entity]);
|
||||
|
||||
const results = api.find(TestComponent);
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0]).toBe(entity);
|
||||
});
|
||||
|
||||
test('应该能够查找第一个匹配的实体', () => {
|
||||
const entity1 = scene.createEntity('Entity1');
|
||||
const entity2 = scene.createEntity('Entity2');
|
||||
entity1.addComponent(new TestComponent(100));
|
||||
entity2.addComponent(new TestComponent(200));
|
||||
querySystem.setEntities([entity1, entity2]);
|
||||
|
||||
const result = api.findFirst(TestComponent);
|
||||
expect(result).not.toBeNull();
|
||||
expect([entity1, entity2]).toContain(result);
|
||||
});
|
||||
|
||||
test('查找不存在的实体应该返回null', () => {
|
||||
const result = api.findFirst(TestComponent);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('应该能够按名称查找实体', () => {
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
|
||||
const result = api.findByName('TestEntity');
|
||||
expect(result).toBe(entity);
|
||||
});
|
||||
|
||||
test('应该能够按标签查找实体', () => {
|
||||
const entity1 = scene.createEntity('Entity1');
|
||||
const entity2 = scene.createEntity('Entity2');
|
||||
entity1.tag = 42;
|
||||
entity2.tag = 42;
|
||||
|
||||
const results = api.findByTag(42);
|
||||
expect(results.length).toBe(2);
|
||||
expect(results).toContain(entity1);
|
||||
expect(results).toContain(entity2);
|
||||
});
|
||||
|
||||
test('应该能够触发同步事件', () => {
|
||||
let eventReceived = false;
|
||||
let eventData: any = null;
|
||||
|
||||
api.on('test:event', (data) => {
|
||||
eventReceived = true;
|
||||
eventData = data;
|
||||
});
|
||||
|
||||
api.emit('test:event', { message: 'hello' });
|
||||
|
||||
expect(eventReceived).toBe(true);
|
||||
expect(eventData.message).toBe('hello');
|
||||
});
|
||||
|
||||
test('应该能够触发异步事件', async () => {
|
||||
let eventReceived = false;
|
||||
let eventData: any = null;
|
||||
|
||||
api.on('test:event', (data) => {
|
||||
eventReceived = true;
|
||||
eventData = data;
|
||||
});
|
||||
|
||||
await api.emitAsync('test:event', { message: 'hello' });
|
||||
|
||||
expect(eventReceived).toBe(true);
|
||||
expect(eventData.message).toBe('hello');
|
||||
});
|
||||
|
||||
test('应该能够一次性监听事件', () => {
|
||||
let callCount = 0;
|
||||
|
||||
api.once('test:event', () => {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
api.emit('test:event', {});
|
||||
api.emit('test:event', {});
|
||||
|
||||
expect(callCount).toBe(1);
|
||||
});
|
||||
|
||||
test('应该能够移除事件监听器', () => {
|
||||
let callCount = 0;
|
||||
|
||||
const listenerId = api.on('test:event', () => {
|
||||
callCount++;
|
||||
});
|
||||
|
||||
api.emit('test:event', {});
|
||||
api.off('test:event', listenerId);
|
||||
api.emit('test:event', {});
|
||||
|
||||
expect(callCount).toBe(1);
|
||||
});
|
||||
|
||||
test('应该能够创建批量操作器', () => {
|
||||
const entity1 = new Entity('Entity1', 1);
|
||||
const entity2 = new Entity('Entity2', 2);
|
||||
|
||||
const batch = api.batch([entity1, entity2]);
|
||||
expect(batch).toBeInstanceOf(EntityBatchOperator);
|
||||
});
|
||||
|
||||
test('应该能够获取统计信息', () => {
|
||||
const stats = api.getStats();
|
||||
|
||||
expect(stats).toBeDefined();
|
||||
expect(stats.entityCount).toBeDefined();
|
||||
expect(stats.systemCount).toBeDefined();
|
||||
expect(stats.componentStats).toBeDefined();
|
||||
expect(stats.queryStats).toBeDefined();
|
||||
expect(stats.eventStats).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('EntityBatchOperator - 批量操作器', () => {
|
||||
let entity1: Entity;
|
||||
let entity2: Entity;
|
||||
let entity3: Entity;
|
||||
let batchOp: EntityBatchOperator;
|
||||
|
||||
beforeEach(() => {
|
||||
entity1 = new Entity('Entity1', 1);
|
||||
entity2 = new Entity('Entity2', 2);
|
||||
entity3 = new Entity('Entity3', 3);
|
||||
batchOp = new EntityBatchOperator([entity1, entity2, entity3]);
|
||||
});
|
||||
|
||||
test('应该能够创建批量操作器', () => {
|
||||
expect(batchOp).toBeInstanceOf(EntityBatchOperator);
|
||||
});
|
||||
|
||||
test('应该能够批量添加组件', () => {
|
||||
const component = new TestComponent(100);
|
||||
batchOp.addComponent(component);
|
||||
|
||||
expect(entity1.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity2.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity3.hasComponent(TestComponent)).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够批量移除组件', () => {
|
||||
entity1.addComponent(new TestComponent(100));
|
||||
entity2.addComponent(new TestComponent(200));
|
||||
entity3.addComponent(new TestComponent(300));
|
||||
|
||||
batchOp.removeComponent(TestComponent);
|
||||
|
||||
expect(entity1.hasComponent(TestComponent)).toBe(false);
|
||||
expect(entity2.hasComponent(TestComponent)).toBe(false);
|
||||
expect(entity3.hasComponent(TestComponent)).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够批量设置活跃状态', () => {
|
||||
batchOp.setActive(false);
|
||||
|
||||
expect(entity1.active).toBe(false);
|
||||
expect(entity2.active).toBe(false);
|
||||
expect(entity3.active).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够批量设置标签', () => {
|
||||
batchOp.setTag(42);
|
||||
|
||||
expect(entity1.tag).toBe(42);
|
||||
expect(entity2.tag).toBe(42);
|
||||
expect(entity3.tag).toBe(42);
|
||||
});
|
||||
|
||||
test('应该能够批量执行操作', () => {
|
||||
const names: string[] = [];
|
||||
const indices: number[] = [];
|
||||
|
||||
batchOp.forEach((entity, index) => {
|
||||
names.push(entity.name);
|
||||
indices.push(index);
|
||||
});
|
||||
|
||||
expect(names).toEqual(['Entity1', 'Entity2', 'Entity3']);
|
||||
expect(indices).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
test('应该能够过滤实体', () => {
|
||||
entity1.tag = 1;
|
||||
entity2.tag = 2;
|
||||
entity3.tag = 1;
|
||||
|
||||
const filtered = batchOp.filter(entity => entity.tag === 1);
|
||||
|
||||
expect(filtered.count()).toBe(2);
|
||||
expect(filtered.toArray()).toContain(entity1);
|
||||
expect(filtered.toArray()).toContain(entity3);
|
||||
});
|
||||
|
||||
test('应该能够获取实体数组', () => {
|
||||
const entities = batchOp.toArray();
|
||||
|
||||
expect(entities.length).toBe(3);
|
||||
expect(entities).toContain(entity1);
|
||||
expect(entities).toContain(entity2);
|
||||
expect(entities).toContain(entity3);
|
||||
});
|
||||
|
||||
test('应该能够获取实体数量', () => {
|
||||
expect(batchOp.count()).toBe(3);
|
||||
});
|
||||
|
||||
test('流式调用应该工作正常', () => {
|
||||
const result = batchOp
|
||||
.addComponent(new TestComponent(100))
|
||||
.setActive(false)
|
||||
.setTag(42)
|
||||
.forEach((entity) => {
|
||||
entity.name = entity.name + '_Modified';
|
||||
});
|
||||
|
||||
expect(result).toBe(batchOp);
|
||||
expect(entity1.hasComponent(TestComponent)).toBe(true);
|
||||
expect(entity1.active).toBe(false);
|
||||
expect(entity1.tag).toBe(42);
|
||||
expect(entity1.name).toBe('Entity1_Modified');
|
||||
});
|
||||
});
|
||||
|
||||
describe('工厂函数和全局API', () => {
|
||||
test('createECSAPI应该创建API实例', () => {
|
||||
const api = createECSAPI(scene, querySystem, eventSystem);
|
||||
expect(api).toBeInstanceOf(ECSFluentAPI);
|
||||
});
|
||||
|
||||
test('initializeECS应该初始化全局ECS', () => {
|
||||
initializeECS(scene, querySystem, eventSystem);
|
||||
expect(ECS).toBeInstanceOf(ECSFluentAPI);
|
||||
});
|
||||
|
||||
test('全局ECS应该可用', () => {
|
||||
initializeECS(scene, querySystem, eventSystem);
|
||||
|
||||
const builder = ECS.createEntity();
|
||||
expect(builder).toBeInstanceOf(EntityBuilder);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,176 @@
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { BigIntFactory } from '../../../src/ECS/Utils/BigIntCompatibility';
|
||||
import { ComponentType } from '../../../src/ECS/Core/ComponentStorage';
|
||||
|
||||
describe('初始化方式性能对比', () => {
|
||||
test('对比不同初始化方式的性能', () => {
|
||||
const testCount = 10000;
|
||||
console.log(`\n=== 初始化方式对比 (${testCount}个实体) ===`);
|
||||
|
||||
// 方式1:字段直接初始化(原来的方式)
|
||||
class EntityWithFieldInit {
|
||||
public name: string;
|
||||
public id: number;
|
||||
private _componentMask = BigIntFactory.zero();
|
||||
private _componentTypeToIndex = new Map<ComponentType, number>();
|
||||
|
||||
constructor(name: string, id: number) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
// 方式2:构造函数初始化(新方式)
|
||||
class EntityWithConstructorInit {
|
||||
public name: string;
|
||||
public id: number;
|
||||
private _componentMask: any;
|
||||
private _componentTypeToIndex: Map<ComponentType, number>;
|
||||
|
||||
constructor(name: string, id: number) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this._componentMask = BigIntFactory.zero();
|
||||
this._componentTypeToIndex = new Map<ComponentType, number>();
|
||||
}
|
||||
}
|
||||
|
||||
// 方式3:完全延迟初始化
|
||||
class EntityWithLazyInit {
|
||||
public name: string;
|
||||
public id: number;
|
||||
private _componentMask: any;
|
||||
private _componentTypeToIndex: Map<ComponentType, number> | undefined;
|
||||
|
||||
constructor(name: string, id: number) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
// 什么都不初始化
|
||||
}
|
||||
|
||||
private ensureInit() {
|
||||
if (!this._componentTypeToIndex) {
|
||||
this._componentMask = BigIntFactory.zero();
|
||||
this._componentTypeToIndex = new Map<ComponentType, number>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试方式1:字段直接初始化
|
||||
let startTime = performance.now();
|
||||
const entities1 = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entities1.push(new EntityWithFieldInit(`Entity_${i}`, i));
|
||||
}
|
||||
const fieldInitTime = performance.now() - startTime;
|
||||
|
||||
// 测试方式2:构造函数初始化
|
||||
startTime = performance.now();
|
||||
const entities2 = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entities2.push(new EntityWithConstructorInit(`Entity_${i}`, i));
|
||||
}
|
||||
const constructorInitTime = performance.now() - startTime;
|
||||
|
||||
// 测试方式3:延迟初始化
|
||||
startTime = performance.now();
|
||||
const entities3 = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entities3.push(new EntityWithLazyInit(`Entity_${i}`, i));
|
||||
}
|
||||
const lazyInitTime = performance.now() - startTime;
|
||||
|
||||
// 测试方式4:只创建基本对象
|
||||
startTime = performance.now();
|
||||
const entities4 = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entities4.push({ name: `Entity_${i}`, id: i });
|
||||
}
|
||||
const basicObjectTime = performance.now() - startTime;
|
||||
|
||||
console.log(`字段直接初始化: ${fieldInitTime.toFixed(2)}ms`);
|
||||
console.log(`构造函数初始化: ${constructorInitTime.toFixed(2)}ms`);
|
||||
console.log(`延迟初始化: ${lazyInitTime.toFixed(2)}ms`);
|
||||
console.log(`基本对象创建: ${basicObjectTime.toFixed(2)}ms`);
|
||||
|
||||
console.log(`\n性能对比:`);
|
||||
console.log(`构造函数 vs 字段初始化: ${(fieldInitTime / constructorInitTime).toFixed(2)}x`);
|
||||
console.log(`延迟 vs 构造函数: ${(constructorInitTime / lazyInitTime).toFixed(2)}x`);
|
||||
console.log(`延迟 vs 基本对象: ${(lazyInitTime / basicObjectTime).toFixed(2)}x`);
|
||||
});
|
||||
|
||||
test('测试BigIntFactory.zero()的性能', () => {
|
||||
const testCount = 10000;
|
||||
console.log(`\n=== BigIntFactory.zero()性能测试 ===`);
|
||||
|
||||
// 测试1:每次调用BigIntFactory.zero()
|
||||
let startTime = performance.now();
|
||||
const values1 = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
values1.push(BigIntFactory.zero());
|
||||
}
|
||||
const directCallTime = performance.now() - startTime;
|
||||
|
||||
// 测试2:重复使用同一个实例
|
||||
const sharedZero = BigIntFactory.zero();
|
||||
startTime = performance.now();
|
||||
const values2 = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
values2.push(sharedZero);
|
||||
}
|
||||
const sharedInstanceTime = performance.now() - startTime;
|
||||
|
||||
// 测试3:使用数字0
|
||||
startTime = performance.now();
|
||||
const values3 = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
values3.push(0);
|
||||
}
|
||||
const numberZeroTime = performance.now() - startTime;
|
||||
|
||||
console.log(`每次调用BigIntFactory.zero(): ${directCallTime.toFixed(2)}ms`);
|
||||
console.log(`重复使用同一实例: ${sharedInstanceTime.toFixed(2)}ms`);
|
||||
console.log(`使用数字0: ${numberZeroTime.toFixed(2)}ms`);
|
||||
|
||||
console.log(`性能提升:`);
|
||||
console.log(`共享实例 vs 每次调用: ${(directCallTime / sharedInstanceTime).toFixed(2)}x faster`);
|
||||
console.log(`数字0 vs BigIntFactory: ${(directCallTime / numberZeroTime).toFixed(2)}x faster`);
|
||||
});
|
||||
|
||||
test('测试Map创建的性能', () => {
|
||||
const testCount = 10000;
|
||||
console.log(`\n=== Map创建性能测试 ===`);
|
||||
|
||||
// 测试1:每次new Map()
|
||||
let startTime = performance.now();
|
||||
const maps1 = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
maps1.push(new Map());
|
||||
}
|
||||
const newMapTime = performance.now() - startTime;
|
||||
|
||||
// 测试2:使用对象字面量
|
||||
startTime = performance.now();
|
||||
const objects = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
objects.push({});
|
||||
}
|
||||
const objectTime = performance.now() - startTime;
|
||||
|
||||
// 测试3:延迟创建Map
|
||||
startTime = performance.now();
|
||||
const lazyMaps = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
lazyMaps.push(null); // 先不创建
|
||||
}
|
||||
const lazyTime = performance.now() - startTime;
|
||||
|
||||
console.log(`每次new Map(): ${newMapTime.toFixed(2)}ms`);
|
||||
console.log(`对象字面量: ${objectTime.toFixed(2)}ms`);
|
||||
console.log(`延迟创建: ${lazyTime.toFixed(2)}ms`);
|
||||
|
||||
console.log(`性能对比:`);
|
||||
console.log(`对象 vs Map: ${(newMapTime / objectTime).toFixed(2)}x faster`);
|
||||
console.log(`延迟 vs Map: ${(newMapTime / lazyTime).toFixed(2)}x faster`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,171 @@
|
||||
import { EntityManager } from '../../../src/ECS/Core/EntityManager';
|
||||
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
|
||||
describe('优化后的性能分析 - ComponentIndexManager优化', () => {
|
||||
let entityManager: EntityManager;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
entityManager = new EntityManager();
|
||||
});
|
||||
|
||||
test('测试优化后的实体创建性能', () => {
|
||||
const testCount = 10000;
|
||||
console.log(`\n=== 优化后的实体创建性能测试 (${testCount}个实体) ===`);
|
||||
|
||||
const startTime = performance.now();
|
||||
const entities = [];
|
||||
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entities.push(entityManager.createEntity(`Entity_${i}`));
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
const avgTime = totalTime / testCount;
|
||||
|
||||
console.log(`总耗时: ${totalTime.toFixed(2)}ms`);
|
||||
console.log(`平均每个实体: ${avgTime.toFixed(3)}ms`);
|
||||
console.log(`每秒创建实体数: ${Math.round(1000 / avgTime)}`);
|
||||
|
||||
if (totalTime < 140) {
|
||||
console.log(`✅ 性能优化成功!实际耗时 ${totalTime.toFixed(2)}ms < 140ms 目标`);
|
||||
} else {
|
||||
console.log(`❌ 仍需进一步优化,实际耗时 ${totalTime.toFixed(2)}ms >= 140ms 目标`);
|
||||
}
|
||||
|
||||
// 性能基准:应该在140ms以下
|
||||
expect(totalTime).toBeLessThan(200); // 放宽一些给CI环境
|
||||
});
|
||||
|
||||
test('对比批量创建与逐个创建的性能', () => {
|
||||
const testCount = 5000;
|
||||
console.log(`\n=== 批量创建vs逐个创建对比 (${testCount}个实体) ===`);
|
||||
|
||||
// 逐个创建
|
||||
let startTime = performance.now();
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entityManager.createEntity(`Individual_${i}`);
|
||||
}
|
||||
const individualTime = performance.now() - startTime;
|
||||
|
||||
// 重置管理器
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 批量创建
|
||||
startTime = performance.now();
|
||||
entityManager.createEntitiesBatch(testCount, "Batch", false);
|
||||
const batchTime = performance.now() - startTime;
|
||||
|
||||
// 重置管理器
|
||||
entityManager = new EntityManager();
|
||||
|
||||
// 批量创建(跳过事件)
|
||||
startTime = performance.now();
|
||||
entityManager.createEntitiesBatch(testCount, "BatchNoEvents", true);
|
||||
const batchNoEventsTime = performance.now() - startTime;
|
||||
|
||||
console.log(`逐个创建: ${individualTime.toFixed(2)}ms`);
|
||||
console.log(`批量创建: ${batchTime.toFixed(2)}ms`);
|
||||
console.log(`批量创建(跳过事件): ${batchNoEventsTime.toFixed(2)}ms`);
|
||||
console.log(`批量优化倍数: ${(individualTime / batchTime).toFixed(2)}x`);
|
||||
console.log(`跳过事件优化倍数: ${(individualTime / batchNoEventsTime).toFixed(2)}x`);
|
||||
});
|
||||
|
||||
test('测试组件索引管理器对空实体的优化效果', () => {
|
||||
const testCount = 10000;
|
||||
console.log(`\n=== 空实体优化效果测试 (${testCount}个空实体) ===`);
|
||||
|
||||
const startTime = performance.now();
|
||||
const entities = [];
|
||||
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
const entity = entityManager.createEntity(`EmptyEntity_${i}`);
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
|
||||
// 验证前几个实体确实没有组件
|
||||
for (let i = 0; i < Math.min(5, entities.length); i++) {
|
||||
expect(entities[i].components.length).toBe(0);
|
||||
}
|
||||
|
||||
console.log(`空实体创建总耗时: ${totalTime.toFixed(2)}ms`);
|
||||
console.log(`平均每个空实体: ${(totalTime / testCount).toFixed(3)}ms`);
|
||||
|
||||
// 获取优化统计信息
|
||||
const stats = entityManager.getOptimizationStats();
|
||||
console.log(`组件索引统计:`, stats.componentIndex);
|
||||
|
||||
// 空实体创建应该非常快,放宽限制以适应CI环境
|
||||
expect(totalTime).toBeLessThan(150);
|
||||
});
|
||||
|
||||
test('测试Set对象池的效果', () => {
|
||||
const testCount = 1000;
|
||||
console.log(`\n=== Set对象池效果测试 (${testCount}次添加/删除) ===`);
|
||||
|
||||
// 创建实体
|
||||
const entities = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entities.push(entityManager.createEntity(`PoolTest_${i}`));
|
||||
}
|
||||
|
||||
// 测试删除和重新创建的性能
|
||||
const startTime = performance.now();
|
||||
|
||||
// 删除一半实体
|
||||
for (let i = 0; i < testCount / 2; i++) {
|
||||
entityManager.destroyEntity(entities[i]);
|
||||
}
|
||||
|
||||
// 重新创建实体
|
||||
for (let i = 0; i < testCount / 2; i++) {
|
||||
entityManager.createEntity(`RecycledEntity_${i}`);
|
||||
}
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
|
||||
console.log(`删除+重新创建耗时: ${totalTime.toFixed(2)}ms`);
|
||||
console.log(`平均每次操作: ${(totalTime / testCount).toFixed(3)}ms`);
|
||||
|
||||
// 对象池优化应该让重复操作更快,放宽限制适应不同环境
|
||||
expect(totalTime).toBeLessThan(100);
|
||||
});
|
||||
|
||||
test('内存使用量分析', () => {
|
||||
const testCount = 5000;
|
||||
console.log(`\n=== 内存使用量分析 (${testCount}个实体) ===`);
|
||||
|
||||
// 获取初始内存使用情况
|
||||
const initialStats = entityManager.getOptimizationStats();
|
||||
const initialMemory = initialStats.componentIndex.memoryUsage;
|
||||
|
||||
// 创建实体
|
||||
const entities = [];
|
||||
for (let i = 0; i < testCount; i++) {
|
||||
entities.push(entityManager.createEntity(`MemoryTest_${i}`));
|
||||
}
|
||||
|
||||
// 获取创建后的内存使用情况
|
||||
const afterStats = entityManager.getOptimizationStats();
|
||||
const afterMemory = afterStats.componentIndex.memoryUsage;
|
||||
|
||||
console.log(`初始内存使用: ${initialMemory} 字节`);
|
||||
console.log(`创建后内存使用: ${afterMemory} 字节`);
|
||||
console.log(`增加的内存: ${afterMemory - initialMemory} 字节`);
|
||||
console.log(`平均每个实体内存: ${((afterMemory - initialMemory) / testCount).toFixed(2)} 字节`);
|
||||
|
||||
// 清理并观察内存回收
|
||||
for (const entity of entities) {
|
||||
entityManager.destroyEntity(entity);
|
||||
}
|
||||
|
||||
const cleanupStats = entityManager.getOptimizationStats();
|
||||
const cleanupMemory = cleanupStats.componentIndex.memoryUsage;
|
||||
|
||||
console.log(`清理后内存使用: ${cleanupMemory} 字节`);
|
||||
console.log(`内存回收率: ${(((afterMemory - cleanupMemory) / (afterMemory - initialMemory)) * 100).toFixed(1)}%`);
|
||||
});
|
||||
});
|
||||
872
packages/core/tests/ECS/Core/QuerySystem.test.ts
Normal file
872
packages/core/tests/ECS/Core/QuerySystem.test.ts
Normal file
@@ -0,0 +1,872 @@
|
||||
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 {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number;
|
||||
public vy: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public health: number;
|
||||
public maxHealth: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [health = 100, maxHealth = 100] = args as [number?, number?];
|
||||
this.health = health;
|
||||
this.maxHealth = maxHealth;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderComponent extends Component {
|
||||
public visible: boolean;
|
||||
public layer: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [visible = true, layer = 0] = args as [boolean?, number?];
|
||||
this.visible = visible;
|
||||
this.layer = layer;
|
||||
}
|
||||
}
|
||||
|
||||
class AIComponent extends Component {
|
||||
public behavior: string;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [behavior = 'idle'] = args as [string?];
|
||||
this.behavior = behavior;
|
||||
}
|
||||
}
|
||||
|
||||
class PhysicsComponent extends Component {
|
||||
public mass: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [mass = 1.0] = args as [number?];
|
||||
this.mass = mass;
|
||||
}
|
||||
}
|
||||
|
||||
describe('QuerySystem - 查询系统测试', () => {
|
||||
let querySystem: QuerySystem;
|
||||
let entities: Entity[];
|
||||
let originalAddComponent: any;
|
||||
let originalRemoveComponent: any;
|
||||
let originalRemoveAllComponents: any;
|
||||
|
||||
beforeEach(() => {
|
||||
querySystem = new QuerySystem();
|
||||
entities = [];
|
||||
|
||||
// 创建测试实体
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const entity = new Entity(`Entity_${i}`, i + 1);
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
// 将实体添加到查询系统
|
||||
querySystem.setEntities(entities);
|
||||
|
||||
// 监听实体组件变化以保持查询系统同步
|
||||
originalAddComponent = Entity.prototype.addComponent;
|
||||
originalRemoveComponent = Entity.prototype.removeComponent;
|
||||
originalRemoveAllComponents = Entity.prototype.removeAllComponents;
|
||||
|
||||
Entity.prototype.addComponent = function<T extends Component>(component: T): T {
|
||||
const result = originalAddComponent.call(this, component);
|
||||
// 通知查询系统实体已更新,重建所有索引
|
||||
querySystem.setEntities(entities);
|
||||
return result;
|
||||
};
|
||||
|
||||
Entity.prototype.removeComponent = function(component: Component): void {
|
||||
originalRemoveComponent.call(this, component);
|
||||
// 通知查询系统实体已更新,重建所有索引
|
||||
querySystem.setEntities(entities);
|
||||
};
|
||||
|
||||
Entity.prototype.removeAllComponents = function(): void {
|
||||
originalRemoveAllComponents.call(this);
|
||||
// 通知查询系统实体已更新,重建所有索引
|
||||
querySystem.setEntities(entities);
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 恢复原始方法
|
||||
Entity.prototype.addComponent = originalAddComponent;
|
||||
Entity.prototype.removeComponent = originalRemoveComponent;
|
||||
Entity.prototype.removeAllComponents = originalRemoveAllComponents;
|
||||
});
|
||||
|
||||
describe('基本查询功能', () => {
|
||||
test('应该能够查询单个组件类型', () => {
|
||||
// 为部分实体添加Position组件
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
entities[2].addComponent(new PositionComponent(50, 60));
|
||||
|
||||
const result = querySystem.queryAll(PositionComponent);
|
||||
|
||||
expect(result.entities.length).toBe(3);
|
||||
expect(result.entities).toContain(entities[0]);
|
||||
expect(result.entities).toContain(entities[1]);
|
||||
expect(result.entities).toContain(entities[2]);
|
||||
});
|
||||
|
||||
test('应该能够查询多个组件类型', () => {
|
||||
// 创建不同组件组合的实体
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[0].addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
entities[1].addComponent(new HealthComponent(80));
|
||||
|
||||
entities[2].addComponent(new PositionComponent(50, 60));
|
||||
entities[2].addComponent(new VelocityComponent(2, 2));
|
||||
|
||||
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
|
||||
expect(result.entities.length).toBe(2);
|
||||
expect(result.entities).toContain(entities[0]);
|
||||
expect(result.entities).toContain(entities[2]);
|
||||
expect(result.entities).not.toContain(entities[1]);
|
||||
});
|
||||
|
||||
test('应该能够查询复杂的组件组合', () => {
|
||||
// 创建复杂的组件组合
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[0].addComponent(new VelocityComponent(1, 1));
|
||||
entities[0].addComponent(new HealthComponent(100));
|
||||
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
entities[1].addComponent(new VelocityComponent(2, 2));
|
||||
entities[1].addComponent(new RenderComponent(true));
|
||||
|
||||
entities[2].addComponent(new PositionComponent(50, 60));
|
||||
entities[2].addComponent(new HealthComponent(80));
|
||||
entities[2].addComponent(new RenderComponent(false));
|
||||
|
||||
const result = querySystem.queryAll(PositionComponent, VelocityComponent, HealthComponent);
|
||||
|
||||
expect(result.entities.length).toBe(1);
|
||||
expect(result.entities).toContain(entities[0]);
|
||||
});
|
||||
|
||||
test('查询不存在的组件应该返回空结果', () => {
|
||||
const result = querySystem.queryAll(AIComponent);
|
||||
|
||||
expect(result.entities.length).toBe(0);
|
||||
expect(result.entities).toEqual([]);
|
||||
});
|
||||
|
||||
test('空查询应该返回所有实体', () => {
|
||||
// 添加一些组件以确保实体被追踪
|
||||
entities.forEach((entity, index) => {
|
||||
if (!entity.hasComponent(PositionComponent)) {
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
}
|
||||
});
|
||||
|
||||
const result = querySystem.queryAll();
|
||||
|
||||
expect(result.entities.length).toBe(entities.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('查询缓存机制', () => {
|
||||
test('相同查询应该使用缓存', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
const result1 = querySystem.queryAll(PositionComponent);
|
||||
const result2 = querySystem.queryAll(PositionComponent);
|
||||
|
||||
// 第二次查询应该来自缓存
|
||||
expect(result2.fromCache).toBe(true);
|
||||
expect(result1.entities).toEqual(result2.entities);
|
||||
expect(result1.entities.length).toBe(2);
|
||||
});
|
||||
|
||||
test('组件变化应该使缓存失效', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
|
||||
const result1 = querySystem.queryAll(PositionComponent);
|
||||
expect(result1.entities.length).toBe(1);
|
||||
|
||||
// 添加新的匹配实体
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
const result2 = querySystem.queryAll(PositionComponent);
|
||||
expect(result2.entities.length).toBe(2);
|
||||
expect(result2.entities).toContain(entities[1]);
|
||||
});
|
||||
|
||||
test('移除组件应该更新缓存', () => {
|
||||
const positionComp = entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
const result1 = querySystem.queryAll(PositionComponent);
|
||||
expect(result1.entities.length).toBe(2);
|
||||
|
||||
// 移除组件
|
||||
entities[0].removeComponent(positionComp);
|
||||
|
||||
const result2 = querySystem.queryAll(PositionComponent);
|
||||
expect(result2.entities.length).toBe(1);
|
||||
expect(result2.entities).not.toContain(entities[0]);
|
||||
});
|
||||
|
||||
test('实体销毁应该更新缓存', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
const result1 = querySystem.queryAll(PositionComponent);
|
||||
expect(result1.entities.length).toBe(2);
|
||||
|
||||
// 销毁实体(通过移除所有组件模拟)
|
||||
entities[0].removeAllComponents();
|
||||
|
||||
const result2 = querySystem.queryAll(PositionComponent);
|
||||
expect(result2.entities.length).toBe(1);
|
||||
expect(result2.entities).not.toContain(entities[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Archetype系统集成', () => {
|
||||
test('具有相同组件组合的实体应该被分组', () => {
|
||||
// 创建具有相同组件组合的实体
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[0].addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
entities[1].addComponent(new VelocityComponent(2, 2));
|
||||
|
||||
entities[2].addComponent(new PositionComponent(50, 60));
|
||||
entities[2].addComponent(new HealthComponent(100));
|
||||
|
||||
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
|
||||
expect(result.entities.length).toBe(2);
|
||||
expect(result.entities).toContain(entities[0]);
|
||||
expect(result.entities).toContain(entities[1]);
|
||||
|
||||
// 验证Archetype优化是否工作 - 简化验证,重点是查询结果正确
|
||||
const stats = querySystem.getStats();
|
||||
// Archetype可能存在也可能不存在,重点是查询结果正确
|
||||
expect(stats.optimizationStats.archetypeSystem.length).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('Archetype应该优化查询性能', () => {
|
||||
const entityCount = 1000;
|
||||
const testEntities: Entity[] = [];
|
||||
|
||||
// 创建大量具有相同组件组合的实体
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const entity = new Entity(`PerfEntity_${i}`, i + 100);
|
||||
testEntities.push(entity);
|
||||
}
|
||||
|
||||
// 先添加组件
|
||||
for (const entity of testEntities) {
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
}
|
||||
|
||||
// 将实体添加到查询系统
|
||||
querySystem.setEntities([...entities, ...testEntities]);
|
||||
|
||||
const startTime = performance.now();
|
||||
const result = querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
const endTime = performance.now();
|
||||
|
||||
expect(result.entities.length).toBe(entityCount);
|
||||
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).toBeLessThan(50); // 应该在50ms内完成
|
||||
|
||||
console.log(`Archetype优化查询${entityCount}个实体耗时: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('位掩码优化', () => {
|
||||
test('位掩码应该正确识别组件组合', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[0].addComponent(new VelocityComponent(1, 1));
|
||||
entities[0].addComponent(new HealthComponent(100));
|
||||
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
entities[1].addComponent(new VelocityComponent(2, 2));
|
||||
|
||||
entities[2].addComponent(new PositionComponent(50, 60));
|
||||
entities[2].addComponent(new HealthComponent(80));
|
||||
|
||||
// 查询Position + Velocity组合
|
||||
const velocityResult = querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
expect(velocityResult.entities.length).toBe(2);
|
||||
expect(velocityResult.entities).toContain(entities[0]);
|
||||
expect(velocityResult.entities).toContain(entities[1]);
|
||||
|
||||
// 查询Position + Health组合
|
||||
const healthResult = querySystem.queryAll(PositionComponent, HealthComponent);
|
||||
expect(healthResult.entities.length).toBe(2);
|
||||
expect(healthResult.entities).toContain(entities[0]);
|
||||
expect(healthResult.entities).toContain(entities[2]);
|
||||
});
|
||||
|
||||
test('位掩码应该支持高效的组件检查', () => {
|
||||
const entityCount = 5000;
|
||||
const testEntities: Entity[] = [];
|
||||
|
||||
// 创建大量实体
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const entity = new Entity(`MaskEntity_${i}`, i + 200);
|
||||
testEntities.push(entity);
|
||||
}
|
||||
|
||||
// 先随机分配组件
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const entity = testEntities[i];
|
||||
|
||||
entity.addComponent(new PositionComponent(i, i));
|
||||
|
||||
if (i % 2 === 0) {
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
}
|
||||
|
||||
if (i % 3 === 0) {
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
}
|
||||
|
||||
if (i % 5 === 0) {
|
||||
entity.addComponent(new RenderComponent(true));
|
||||
}
|
||||
}
|
||||
|
||||
// 将实体添加到查询系统
|
||||
querySystem.setEntities([...entities, ...testEntities]);
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
// 执行复杂查询
|
||||
const result1 = querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
const result2 = querySystem.queryAll(PositionComponent, HealthComponent);
|
||||
const result3 = querySystem.queryAll(VelocityComponent, HealthComponent);
|
||||
const result4 = querySystem.queryAll(PositionComponent, VelocityComponent, HealthComponent);
|
||||
|
||||
const endTime = performance.now();
|
||||
|
||||
expect(result1.entities.length).toBe(entityCount / 2);
|
||||
expect(result2.entities.length).toBe(Math.floor(entityCount / 3) + 1);
|
||||
expect(result4.entities.length).toBe(Math.floor(entityCount / 6) + 1);
|
||||
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).toBeLessThan(100); // 复杂查询应该在100ms内完成
|
||||
|
||||
console.log(`位掩码优化复杂查询耗时: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('脏标记系统', () => {
|
||||
test('脏标记应该追踪组件变化', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
|
||||
// 第一次查询
|
||||
const result1 = querySystem.queryAll(PositionComponent);
|
||||
expect(result1.entities.length).toBe(1);
|
||||
|
||||
// 修改组件(模拟脏标记)
|
||||
const position = entities[0].getComponent(PositionComponent);
|
||||
if (position) {
|
||||
position.x = 50;
|
||||
// 在实际实现中,这会标记实体为脏
|
||||
}
|
||||
|
||||
// 添加新实体以触发重新查询
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
const result2 = querySystem.queryAll(PositionComponent);
|
||||
expect(result2.entities.length).toBe(2);
|
||||
});
|
||||
|
||||
test('脏标记应该优化不必要的查询', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
// 多次相同查询应该使用缓存
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
querySystem.queryAll(PositionComponent);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
// 缓存查询应该非常快
|
||||
expect(duration).toBeLessThan(10);
|
||||
|
||||
console.log(`1000次缓存查询耗时: ${duration.toFixed(2)}ms`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('查询统计和性能监控', () => {
|
||||
test('应该能够获取查询统计信息', () => {
|
||||
querySystem.clearCache(); // 确保测试隔离
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
// 执行一些查询
|
||||
querySystem.queryAll(PositionComponent);
|
||||
querySystem.queryAll(VelocityComponent);
|
||||
querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
|
||||
const stats = querySystem.getStats();
|
||||
|
||||
expect(stats.queryStats.totalQueries).toBeGreaterThan(0);
|
||||
expect(stats.cacheStats.size).toBeGreaterThan(0);
|
||||
expect(parseFloat(stats.cacheStats.hitRate)).toBeGreaterThanOrEqual(0);
|
||||
expect(parseFloat(stats.cacheStats.hitRate)).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
test('缓存命中率应该在重复查询时提高', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
// 第一次查询(缓存未命中)
|
||||
querySystem.queryAll(PositionComponent);
|
||||
let stats = querySystem.getStats();
|
||||
const initialHitRate = parseFloat(stats.cacheStats.hitRate);
|
||||
|
||||
// 重复查询(应该命中缓存)
|
||||
for (let i = 0; i < 10; i++) {
|
||||
querySystem.queryAll(PositionComponent);
|
||||
}
|
||||
|
||||
stats = querySystem.getStats();
|
||||
expect(parseFloat(stats.cacheStats.hitRate)).toBeGreaterThan(initialHitRate);
|
||||
});
|
||||
|
||||
test('应该能够清理查询缓存', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
// 创建一些缓存条目
|
||||
querySystem.queryAll(PositionComponent);
|
||||
querySystem.queryAll(VelocityComponent);
|
||||
|
||||
let stats = querySystem.getStats();
|
||||
expect(stats.cacheStats.size).toBeGreaterThan(0);
|
||||
|
||||
// 清理缓存
|
||||
querySystem.clearCache();
|
||||
|
||||
stats = querySystem.getStats();
|
||||
expect(stats.cacheStats.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('内存管理和优化', () => {
|
||||
test('大量查询不应该导致内存泄漏', () => {
|
||||
querySystem.clearCache(); // 确保测试隔离
|
||||
const entityCount = 1000;
|
||||
const testEntities: Entity[] = [];
|
||||
|
||||
// 创建大量实体
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const entity = new Entity(`MemEntity_${i}`, i + 300);
|
||||
entity.addComponent(new PositionComponent(i, i));
|
||||
|
||||
if (i % 2 === 0) {
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
}
|
||||
|
||||
testEntities.push(entity);
|
||||
}
|
||||
|
||||
// 执行大量不同的查询
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
querySystem.queryAll(PositionComponent);
|
||||
querySystem.queryAll(VelocityComponent);
|
||||
querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
expect(duration).toBeLessThan(500); // 应该在500ms内完成
|
||||
|
||||
// 验证缓存大小合理
|
||||
const stats = querySystem.getStats();
|
||||
expect(stats.cacheStats.size).toBeLessThan(10); // 不同查询类型应该不多
|
||||
|
||||
console.log(`大量查询操作耗时: ${duration.toFixed(2)}ms,缓存大小: ${stats.cacheStats.size}`);
|
||||
});
|
||||
|
||||
test('查询结果应该正确管理实体引用', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new PositionComponent(30, 40));
|
||||
|
||||
const result = querySystem.queryAll(PositionComponent);
|
||||
|
||||
// 修改查询结果不应该影响原始数据
|
||||
const originalLength = result.entities.length;
|
||||
result.entities.push(entities[2]); // 尝试修改结果
|
||||
|
||||
const newResult = querySystem.queryAll(PositionComponent);
|
||||
expect(newResult.entities.length).toBe(originalLength);
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况和错误处理', () => {
|
||||
test('空实体列表查询应该安全', () => {
|
||||
expect(() => {
|
||||
const result = querySystem.queryAll(PositionComponent);
|
||||
expect(result.entities).toEqual([]);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('查询不存在的组件类型应该安全', () => {
|
||||
expect(() => {
|
||||
const result = querySystem.queryAll(AIComponent, PhysicsComponent);
|
||||
expect(result.entities).toEqual([]);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('查询已销毁实体的组件应该安全处理', () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
|
||||
const result1 = querySystem.queryAll(PositionComponent);
|
||||
expect(result1.entities.length).toBe(1);
|
||||
|
||||
// 销毁实体(通过移除所有组件)
|
||||
entities[0].removeAllComponents();
|
||||
|
||||
const result2 = querySystem.queryAll(PositionComponent);
|
||||
expect(result2.entities.length).toBe(0);
|
||||
});
|
||||
|
||||
test('并发查询应该安全', async () => {
|
||||
entities[0].addComponent(new PositionComponent(10, 20));
|
||||
entities[1].addComponent(new VelocityComponent(1, 1));
|
||||
entities[2].addComponent(new HealthComponent(100));
|
||||
|
||||
// 模拟并发查询
|
||||
const promises = Array.from({ length: 50 }, (_, i) => {
|
||||
return Promise.resolve(querySystem.queryAll(PositionComponent));
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
// 所有结果应该一致
|
||||
results.forEach(result => {
|
||||
expect(result.entities.length).toBe(1);
|
||||
expect(result.entities[0]).toBe(entities[0]);
|
||||
});
|
||||
});
|
||||
|
||||
test('极大数量的查询类型应该能正确处理', () => {
|
||||
const componentTypes = [
|
||||
PositionComponent,
|
||||
VelocityComponent,
|
||||
HealthComponent,
|
||||
RenderComponent,
|
||||
AIComponent,
|
||||
PhysicsComponent
|
||||
];
|
||||
|
||||
// 创建具有不同组件组合的实体
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
for (let j = 0; j < componentTypes.length; j++) {
|
||||
if (i % (j + 1) === 0) {
|
||||
const ComponentClass = componentTypes[j];
|
||||
if (!entities[i].hasComponent(ComponentClass as any)) {
|
||||
switch (ComponentClass) {
|
||||
case PositionComponent:
|
||||
entities[i].addComponent(new PositionComponent(i, i));
|
||||
break;
|
||||
case VelocityComponent:
|
||||
entities[i].addComponent(new VelocityComponent(1, 1));
|
||||
break;
|
||||
case HealthComponent:
|
||||
entities[i].addComponent(new HealthComponent(100));
|
||||
break;
|
||||
case RenderComponent:
|
||||
entities[i].addComponent(new RenderComponent(true));
|
||||
break;
|
||||
case AIComponent:
|
||||
entities[i].addComponent(new AIComponent('patrol'));
|
||||
break;
|
||||
case PhysicsComponent:
|
||||
entities[i].addComponent(new PhysicsComponent(1.0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试各种组合查询
|
||||
expect(() => {
|
||||
querySystem.queryAll(PositionComponent);
|
||||
querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
querySystem.queryAll(PositionComponent, VelocityComponent, HealthComponent);
|
||||
querySystem.queryAll(RenderComponent, AIComponent);
|
||||
querySystem.queryAll(PhysicsComponent, PositionComponent);
|
||||
}).not.toThrow();
|
||||
|
||||
const stats = querySystem.getStats();
|
||||
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('应该能够构建排除组件的查询', () => {
|
||||
// 为一些实体添加HealthComponent,这样其他实体就不包含这个组件
|
||||
entities[3].addComponent(new HealthComponent(100));
|
||||
entities[4].addComponent(new HealthComponent(80));
|
||||
|
||||
const result = builder
|
||||
.without(HealthComponent)
|
||||
.execute();
|
||||
|
||||
// 应该返回没有HealthComponent的8个实体
|
||||
expect(result.entities.length).toBe(8);
|
||||
expect(result.entities).not.toContain(entities[3]);
|
||||
expect(result.entities).not.toContain(entities[4]);
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
// 重建索引以反映标签变化
|
||||
querySystem.setEntities(entities);
|
||||
|
||||
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(11);
|
||||
|
||||
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(13);
|
||||
});
|
||||
|
||||
test('应该能够批量添加实体(无重复检查)', () => {
|
||||
const newEntities = [
|
||||
new Entity('Unchecked1', 995),
|
||||
new Entity('Unchecked2', 996)
|
||||
];
|
||||
|
||||
querySystem.addEntitiesUnchecked(newEntities);
|
||||
const stats = querySystem.getStats();
|
||||
expect(stats.entityCount).toBe(12);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
252
packages/core/tests/ECS/Core/SoAStorage.collections.test.ts
Normal file
252
packages/core/tests/ECS/Core/SoAStorage.collections.test.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { ComponentStorageManager, EnableSoA, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '../../../src/ECS/Core/ComponentStorage';
|
||||
|
||||
// 测试组件:使用集合类型装饰器
|
||||
@EnableSoA
|
||||
class CollectionsComponent extends Component {
|
||||
// 序列化Map存储
|
||||
@SerializeMap
|
||||
public playerStats: Map<string, number> = new Map();
|
||||
|
||||
// 序列化Set存储
|
||||
@SerializeSet
|
||||
public achievements: Set<string> = new Set();
|
||||
|
||||
// 序列化Array存储
|
||||
@SerializeArray
|
||||
public inventory: string[] = [];
|
||||
|
||||
// 深拷贝对象存储
|
||||
@DeepCopy
|
||||
public config: { settings: { volume: number } } = { settings: { volume: 0.5 } };
|
||||
|
||||
// 普通对象(引用存储)
|
||||
public metadata: any = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
describe('SoA集合类型装饰器测试', () => {
|
||||
let manager: ComponentStorageManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new ComponentStorageManager();
|
||||
});
|
||||
|
||||
test('验证Map序列化存储', () => {
|
||||
console.log('\\n=== 测试Map序列化存储 ===');
|
||||
|
||||
const component = new CollectionsComponent();
|
||||
|
||||
// 设置Map数据
|
||||
component.playerStats.set('health', 100);
|
||||
component.playerStats.set('mana', 50);
|
||||
component.playerStats.set('experience', 1250);
|
||||
|
||||
console.log('原始Map数据:', {
|
||||
size: component.playerStats.size,
|
||||
entries: Array.from(component.playerStats.entries())
|
||||
});
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, CollectionsComponent);
|
||||
|
||||
console.log('取回Map数据:', {
|
||||
size: retrieved?.playerStats.size,
|
||||
entries: Array.from(retrieved?.playerStats.entries() || [])
|
||||
});
|
||||
|
||||
// 验证Map数据完整性
|
||||
expect(retrieved?.playerStats).toBeInstanceOf(Map);
|
||||
expect(retrieved?.playerStats.size).toBe(3);
|
||||
expect(retrieved?.playerStats.get('health')).toBe(100);
|
||||
expect(retrieved?.playerStats.get('mana')).toBe(50);
|
||||
expect(retrieved?.playerStats.get('experience')).toBe(1250);
|
||||
|
||||
console.log('✅ Map序列化存储验证通过');
|
||||
});
|
||||
|
||||
test('验证Set序列化存储', () => {
|
||||
console.log('\\n=== 测试Set序列化存储 ===');
|
||||
|
||||
const component = new CollectionsComponent();
|
||||
|
||||
// 设置Set数据
|
||||
component.achievements.add('first_kill');
|
||||
component.achievements.add('level_10');
|
||||
component.achievements.add('boss_defeated');
|
||||
|
||||
console.log('原始Set数据:', {
|
||||
size: component.achievements.size,
|
||||
values: Array.from(component.achievements)
|
||||
});
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, CollectionsComponent);
|
||||
|
||||
console.log('取回Set数据:', {
|
||||
size: retrieved?.achievements.size,
|
||||
values: Array.from(retrieved?.achievements || [])
|
||||
});
|
||||
|
||||
// 验证Set数据完整性
|
||||
expect(retrieved?.achievements).toBeInstanceOf(Set);
|
||||
expect(retrieved?.achievements.size).toBe(3);
|
||||
expect(retrieved?.achievements.has('first_kill')).toBe(true);
|
||||
expect(retrieved?.achievements.has('level_10')).toBe(true);
|
||||
expect(retrieved?.achievements.has('boss_defeated')).toBe(true);
|
||||
|
||||
console.log('✅ Set序列化存储验证通过');
|
||||
});
|
||||
|
||||
test('验证Array序列化存储', () => {
|
||||
console.log('\\n=== 测试Array序列化存储 ===');
|
||||
|
||||
const component = new CollectionsComponent();
|
||||
|
||||
// 设置Array数据
|
||||
component.inventory.push('sword', 'shield', 'potion');
|
||||
|
||||
console.log('原始Array数据:', component.inventory);
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, CollectionsComponent);
|
||||
|
||||
console.log('取回Array数据:', retrieved?.inventory);
|
||||
|
||||
// 验证Array数据完整性
|
||||
expect(Array.isArray(retrieved?.inventory)).toBe(true);
|
||||
expect(retrieved?.inventory.length).toBe(3);
|
||||
expect(retrieved?.inventory).toEqual(['sword', 'shield', 'potion']);
|
||||
|
||||
console.log('✅ Array序列化存储验证通过');
|
||||
});
|
||||
|
||||
test('验证深拷贝对象存储', () => {
|
||||
console.log('\\n=== 测试深拷贝对象存储 ===');
|
||||
|
||||
const component = new CollectionsComponent();
|
||||
const originalConfig = component.config;
|
||||
|
||||
// 修改配置
|
||||
component.config.settings.volume = 0.8;
|
||||
|
||||
console.log('原始配置:', component.config);
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, CollectionsComponent);
|
||||
|
||||
console.log('取回配置:', retrieved?.config);
|
||||
|
||||
// 验证深拷贝
|
||||
expect(retrieved?.config).toEqual(component.config);
|
||||
expect(retrieved?.config).not.toBe(originalConfig); // 不是同一个引用
|
||||
expect(retrieved?.config.settings.volume).toBe(0.8);
|
||||
|
||||
// 修改原始对象不应该影响取回的对象
|
||||
component.config.settings.volume = 0.3;
|
||||
expect(retrieved?.config.settings.volume).toBe(0.8); // 保持不变
|
||||
|
||||
console.log('✅ 深拷贝对象存储验证通过');
|
||||
});
|
||||
|
||||
test('对比普通对象存储(引用存储)', () => {
|
||||
console.log('\\n=== 测试普通对象存储(引用存储)===');
|
||||
|
||||
const component = new CollectionsComponent();
|
||||
const sharedObject = { data: 'shared' };
|
||||
component.metadata = sharedObject;
|
||||
|
||||
console.log('原始metadata:', component.metadata);
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, CollectionsComponent);
|
||||
|
||||
console.log('取回metadata:', retrieved?.metadata);
|
||||
|
||||
// 验证引用存储
|
||||
expect(retrieved?.metadata).toBe(sharedObject); // 是同一个引用
|
||||
expect(retrieved?.metadata.data).toBe('shared');
|
||||
|
||||
console.log('✅ 普通对象存储验证通过');
|
||||
});
|
||||
|
||||
test('复杂场景:多种类型混合使用', () => {
|
||||
console.log('\\n=== 测试复杂场景 ===');
|
||||
|
||||
const component = new CollectionsComponent();
|
||||
|
||||
// 设置复杂数据
|
||||
component.playerStats.set('level', 25);
|
||||
component.playerStats.set('gold', 5000);
|
||||
|
||||
component.achievements.add('explorer');
|
||||
component.achievements.add('warrior');
|
||||
|
||||
component.inventory.push('legendary_sword', 'magic_potion');
|
||||
|
||||
component.config = {
|
||||
settings: {
|
||||
volume: 0.75
|
||||
}
|
||||
};
|
||||
|
||||
component.metadata = { timestamp: Date.now() };
|
||||
|
||||
console.log('复杂数据设置完成');
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, CollectionsComponent);
|
||||
|
||||
// 全面验证
|
||||
expect(retrieved?.playerStats.get('level')).toBe(25);
|
||||
expect(retrieved?.achievements.has('explorer')).toBe(true);
|
||||
expect(retrieved?.inventory).toContain('legendary_sword');
|
||||
expect(retrieved?.config.settings.volume).toBe(0.75);
|
||||
expect(retrieved?.metadata).toBeDefined();
|
||||
|
||||
console.log('✅ 复杂场景验证通过');
|
||||
});
|
||||
|
||||
test('性能测试:序列化 vs 深拷贝', () => {
|
||||
console.log('\\n=== 性能对比测试 ===');
|
||||
|
||||
const entityCount = 100;
|
||||
|
||||
// 准备测试数据
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const component = new CollectionsComponent();
|
||||
|
||||
// 设置数据
|
||||
component.playerStats.set('id', i);
|
||||
component.playerStats.set('score', i * 100);
|
||||
|
||||
component.achievements.add(`achievement_${i}`);
|
||||
component.inventory.push(`item_${i}`);
|
||||
|
||||
component.config = { settings: { volume: i / entityCount } };
|
||||
|
||||
manager.addComponent(i, component);
|
||||
}
|
||||
|
||||
const createTime = performance.now() - startTime;
|
||||
|
||||
// 读取测试
|
||||
const readStartTime = performance.now();
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const component = manager.getComponent(i, CollectionsComponent);
|
||||
expect(component?.playerStats.get('id')).toBe(i);
|
||||
}
|
||||
const readTime = performance.now() - readStartTime;
|
||||
|
||||
console.log(`创建${entityCount}个复杂组件: ${createTime.toFixed(2)}ms`);
|
||||
console.log(`读取${entityCount}个复杂组件: ${readTime.toFixed(2)}ms`);
|
||||
console.log(`平均每个组件: ${((createTime + readTime) / entityCount).toFixed(4)}ms`);
|
||||
|
||||
console.log('✅ 性能测试完成');
|
||||
});
|
||||
});
|
||||
307
packages/core/tests/ECS/Core/SoAStorage.comprehensive.test.ts
Normal file
307
packages/core/tests/ECS/Core/SoAStorage.comprehensive.test.ts
Normal file
@@ -0,0 +1,307 @@
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { ComponentStorageManager, EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '../../../src/ECS/Core/ComponentStorage';
|
||||
import { SoAStorage } from '../../../src/ECS/Core/SoAStorage';
|
||||
|
||||
// 综合测试组件,覆盖所有装饰器
|
||||
@EnableSoA
|
||||
class ComprehensiveComponent extends Component {
|
||||
@HighPrecision
|
||||
public bigIntId: number = BigInt(Number.MAX_SAFE_INTEGER + 1) as any;
|
||||
|
||||
@Float64
|
||||
public preciseValue: number = Math.PI;
|
||||
|
||||
@Int32
|
||||
public intValue: number = -2147483648;
|
||||
|
||||
@SerializeMap
|
||||
public gameMap: Map<string, any> = new Map();
|
||||
|
||||
@SerializeSet
|
||||
public flags: Set<number> = new Set();
|
||||
|
||||
@SerializeArray
|
||||
public items: any[] = [];
|
||||
|
||||
@DeepCopy
|
||||
public nestedConfig: any = { deep: { nested: { value: 42 } } };
|
||||
|
||||
// 未装饰的字段
|
||||
public normalFloat: number = 1.23;
|
||||
public flag: boolean = true;
|
||||
public text: string = 'default';
|
||||
public complexObject: any = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
describe('SoA存储综合测试覆盖', () => {
|
||||
let manager: ComponentStorageManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new ComponentStorageManager();
|
||||
});
|
||||
|
||||
test('验证所有装饰器类型的存储和检索', () => {
|
||||
console.log('\\n=== 综合装饰器测试 ===');
|
||||
|
||||
const component = new ComprehensiveComponent();
|
||||
|
||||
// 设置复杂数据
|
||||
component.gameMap.set('player1', { level: 10, gold: 500 });
|
||||
component.gameMap.set('player2', { level: 15, gold: 1200 });
|
||||
|
||||
component.flags.add(1);
|
||||
component.flags.add(2);
|
||||
component.flags.add(4);
|
||||
|
||||
component.items.push({ type: 'weapon', name: 'sword' });
|
||||
component.items.push({ type: 'armor', name: 'shield' });
|
||||
|
||||
component.nestedConfig.deep.nested.value = 999;
|
||||
component.complexObject = { reference: 'shared' };
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, ComprehensiveComponent);
|
||||
|
||||
// 验证所有类型
|
||||
expect(retrieved?.bigIntId).toBe(component.bigIntId);
|
||||
expect(retrieved?.preciseValue).toBeCloseTo(Math.PI, 15);
|
||||
expect(retrieved?.intValue).toBe(-2147483648);
|
||||
|
||||
expect(retrieved?.gameMap).toBeInstanceOf(Map);
|
||||
expect(retrieved?.gameMap.get('player1')).toEqual({ level: 10, gold: 500 });
|
||||
|
||||
expect(retrieved?.flags).toBeInstanceOf(Set);
|
||||
expect(retrieved?.flags.has(2)).toBe(true);
|
||||
|
||||
expect(retrieved?.items).toEqual(component.items);
|
||||
expect(retrieved?.nestedConfig.deep.nested.value).toBe(999);
|
||||
|
||||
// 深拷贝验证
|
||||
expect(retrieved?.nestedConfig).not.toBe(component.nestedConfig);
|
||||
|
||||
console.log('✅ 综合装饰器测试通过');
|
||||
});
|
||||
|
||||
test('测试存储器内存统计和容量管理', () => {
|
||||
console.log('\\n=== 存储器管理测试 ===');
|
||||
|
||||
const storage = manager.getStorage(ComprehensiveComponent) as SoAStorage<ComprehensiveComponent>;
|
||||
|
||||
// 添加多个组件
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const component = new ComprehensiveComponent();
|
||||
component.intValue = i * 100;
|
||||
component.preciseValue = i * Math.PI;
|
||||
manager.addComponent(i, component);
|
||||
}
|
||||
|
||||
// 检查统计信息
|
||||
const stats = storage.getStats();
|
||||
console.log('存储统计:', {
|
||||
size: stats.size,
|
||||
capacity: stats.capacity,
|
||||
memoryUsage: stats.memoryUsage,
|
||||
fieldCount: stats.fieldStats.size
|
||||
});
|
||||
|
||||
expect(stats.size).toBe(5);
|
||||
expect(stats.capacity).toBeGreaterThanOrEqual(5);
|
||||
expect(stats.memoryUsage).toBeGreaterThan(0);
|
||||
|
||||
// 测试压缩
|
||||
storage.removeComponent(2);
|
||||
storage.removeComponent(4);
|
||||
|
||||
const statsBeforeCompact = storage.getStats();
|
||||
storage.compact();
|
||||
const statsAfterCompact = storage.getStats();
|
||||
|
||||
expect(statsAfterCompact.size).toBe(3);
|
||||
console.log('压缩前后对比:', {
|
||||
before: statsBeforeCompact.size,
|
||||
after: statsAfterCompact.size
|
||||
});
|
||||
|
||||
console.log('✅ 存储器管理测试通过');
|
||||
});
|
||||
|
||||
test('测试序列化错误处理', () => {
|
||||
console.log('\\n=== 序列化错误处理测试 ===');
|
||||
|
||||
// 创建包含循环引用的对象
|
||||
const component = new ComprehensiveComponent();
|
||||
const cyclicObject: any = { name: 'test' };
|
||||
cyclicObject.self = cyclicObject; // 循环引用
|
||||
|
||||
// 这应该不会崩溃,而是优雅处理
|
||||
component.items.push(cyclicObject);
|
||||
|
||||
expect(() => {
|
||||
manager.addComponent(1, component);
|
||||
}).not.toThrow();
|
||||
|
||||
const retrieved = manager.getComponent(1, ComprehensiveComponent);
|
||||
expect(retrieved).toBeDefined();
|
||||
|
||||
console.log('✅ 序列化错误处理测试通过');
|
||||
});
|
||||
|
||||
test('测试大容量扩展和性能', () => {
|
||||
console.log('\\n=== 大容量性能测试 ===');
|
||||
|
||||
const startTime = performance.now();
|
||||
const entityCount = 2000;
|
||||
|
||||
// 创建大量实体
|
||||
for (let i = 1; i <= entityCount; i++) {
|
||||
const component = new ComprehensiveComponent();
|
||||
component.intValue = i;
|
||||
component.preciseValue = i * 0.1;
|
||||
component.gameMap.set(`key${i}`, i);
|
||||
component.flags.add(i % 10);
|
||||
component.items.push(`item${i}`);
|
||||
|
||||
manager.addComponent(i, component);
|
||||
}
|
||||
|
||||
const createTime = performance.now() - startTime;
|
||||
|
||||
// 随机访问测试
|
||||
const readStartTime = performance.now();
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const randomId = Math.floor(Math.random() * entityCount) + 1;
|
||||
const component = manager.getComponent(randomId, ComprehensiveComponent);
|
||||
expect(component?.intValue).toBe(randomId);
|
||||
}
|
||||
const readTime = performance.now() - readStartTime;
|
||||
|
||||
console.log(`创建${entityCount}个组件: ${createTime.toFixed(2)}ms`);
|
||||
console.log(`随机读取100次: ${readTime.toFixed(2)}ms`);
|
||||
console.log(`平均创建时间: ${(createTime / entityCount).toFixed(4)}ms/组件`);
|
||||
|
||||
// 验证存储统计
|
||||
const storage = manager.getStorage(ComprehensiveComponent) as SoAStorage<ComprehensiveComponent>;
|
||||
const stats = storage.getStats();
|
||||
|
||||
expect(stats.size).toBe(entityCount);
|
||||
expect(stats.capacity).toBeGreaterThanOrEqual(entityCount);
|
||||
|
||||
console.log('✅ 大容量性能测试通过');
|
||||
});
|
||||
|
||||
test('测试空值和边界处理', () => {
|
||||
console.log('\\n=== 空值边界测试 ===');
|
||||
|
||||
const component = new ComprehensiveComponent();
|
||||
|
||||
// 设置各种边界值
|
||||
component.gameMap.set('null', null);
|
||||
component.gameMap.set('undefined', undefined);
|
||||
component.gameMap.set('empty', '');
|
||||
component.gameMap.set('zero', 0);
|
||||
component.gameMap.set('false', false);
|
||||
|
||||
component.flags.add(0);
|
||||
component.items.push(null, undefined, '', 0, false);
|
||||
|
||||
component.nestedConfig = null;
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, ComprehensiveComponent);
|
||||
|
||||
// 验证边界值处理
|
||||
expect(retrieved?.gameMap.get('null')).toBe(null);
|
||||
expect(retrieved?.gameMap.get('undefined')).toBe(null); // JSON序列化会将undefined转为null
|
||||
expect(retrieved?.gameMap.get('empty')).toBe('');
|
||||
expect(retrieved?.gameMap.get('zero')).toBe(0);
|
||||
expect(retrieved?.gameMap.get('false')).toBe(false);
|
||||
|
||||
expect(retrieved?.flags.has(0)).toBe(true);
|
||||
expect(retrieved?.items).toEqual([null, null, '', 0, false]); // undefined序列化为null
|
||||
expect(retrieved?.nestedConfig).toBe(null);
|
||||
|
||||
console.log('✅ 空值边界测试通过');
|
||||
});
|
||||
|
||||
test('测试不同TypedArray类型的字段访问', () => {
|
||||
console.log('\\n=== TypedArray字段测试 ===');
|
||||
|
||||
const storage = manager.getStorage(ComprehensiveComponent) as SoAStorage<ComprehensiveComponent>;
|
||||
|
||||
// 添加测试数据
|
||||
const component = new ComprehensiveComponent();
|
||||
manager.addComponent(1, component);
|
||||
|
||||
// 检查不同类型的TypedArray
|
||||
const preciseArray = storage.getFieldArray('preciseValue');
|
||||
const intArray = storage.getFieldArray('intValue');
|
||||
const normalArray = storage.getFieldArray('normalFloat');
|
||||
const flagArray = storage.getFieldArray('flag');
|
||||
|
||||
expect(preciseArray).toBeInstanceOf(Float64Array);
|
||||
expect(intArray).toBeInstanceOf(Int32Array);
|
||||
expect(normalArray).toBeInstanceOf(Float32Array);
|
||||
expect(flagArray).toBeInstanceOf(Float32Array);
|
||||
|
||||
// 高精度字段不应该在TypedArray中
|
||||
const bigIntArray = storage.getFieldArray('bigIntId');
|
||||
expect(bigIntArray).toBeNull();
|
||||
|
||||
console.log('TypedArray类型验证:', {
|
||||
preciseValue: preciseArray?.constructor.name,
|
||||
intValue: intArray?.constructor.name,
|
||||
normalFloat: normalArray?.constructor.name,
|
||||
flag: flagArray?.constructor.name,
|
||||
bigIntId: bigIntArray ? 'Found' : 'null (正确)'
|
||||
});
|
||||
|
||||
console.log('✅ TypedArray字段测试通过');
|
||||
});
|
||||
|
||||
test('测试向量化批量操作', () => {
|
||||
console.log('\\n=== 向量化操作测试 ===');
|
||||
|
||||
const storage = manager.getStorage(ComprehensiveComponent) as SoAStorage<ComprehensiveComponent>;
|
||||
|
||||
// 添加测试数据
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const component = new ComprehensiveComponent();
|
||||
component.normalFloat = i;
|
||||
component.intValue = i * 10;
|
||||
manager.addComponent(i, component);
|
||||
}
|
||||
|
||||
// 执行向量化操作
|
||||
let operationExecuted = false;
|
||||
storage.performVectorizedOperation((fieldArrays, activeIndices) => {
|
||||
operationExecuted = true;
|
||||
|
||||
const normalFloatArray = fieldArrays.get('normalFloat') as Float32Array;
|
||||
const intArray = fieldArrays.get('intValue') as Int32Array;
|
||||
|
||||
expect(normalFloatArray).toBeInstanceOf(Float32Array);
|
||||
expect(intArray).toBeInstanceOf(Int32Array);
|
||||
expect(activeIndices.length).toBe(10);
|
||||
|
||||
// 批量修改数据
|
||||
for (let i = 0; i < activeIndices.length; i++) {
|
||||
const idx = activeIndices[i];
|
||||
normalFloatArray[idx] *= 2; // 乘以2
|
||||
intArray[idx] += 5; // 加5
|
||||
}
|
||||
});
|
||||
|
||||
expect(operationExecuted).toBe(true);
|
||||
|
||||
// 验证批量操作结果
|
||||
const component = manager.getComponent(5, ComprehensiveComponent);
|
||||
expect(component?.normalFloat).toBe(10); // 5 * 2
|
||||
expect(component?.intValue).toBe(55); // 50 + 5
|
||||
|
||||
console.log('✅ 向量化操作测试通过');
|
||||
});
|
||||
});
|
||||
171
packages/core/tests/ECS/Core/SoAStorage.decorators.test.ts
Normal file
171
packages/core/tests/ECS/Core/SoAStorage.decorators.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { ComponentStorageManager, EnableSoA, HighPrecision, Float64, Int32 } from '../../../src/ECS/Core/ComponentStorage';
|
||||
import { SoAStorage } from '../../../src/ECS/Core/SoAStorage';
|
||||
|
||||
// 测试组件:使用不同的数值类型装饰器
|
||||
@EnableSoA
|
||||
class DecoratedComponent extends Component {
|
||||
// 默认Float32Array存储
|
||||
public normalFloat: number = 3.14;
|
||||
|
||||
// 高精度存储(作为复杂对象)
|
||||
@HighPrecision
|
||||
public highPrecisionNumber: number = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
// Float64Array存储
|
||||
@Float64
|
||||
public preciseFloat: number = Math.PI;
|
||||
|
||||
// Int32Array存储
|
||||
@Int32
|
||||
public integerValue: number = 42;
|
||||
|
||||
// 布尔值(默认Float32Array)
|
||||
public flag: boolean = true;
|
||||
|
||||
// 字符串(专门数组)
|
||||
public text: string = 'hello';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
describe('SoA数值类型装饰器测试', () => {
|
||||
let manager: ComponentStorageManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new ComponentStorageManager();
|
||||
});
|
||||
|
||||
test('验证不同装饰器的存储类型', () => {
|
||||
console.log('\\n=== 测试装饰器存储类型 ===');
|
||||
|
||||
const component = new DecoratedComponent();
|
||||
component.highPrecisionNumber = Number.MAX_SAFE_INTEGER;
|
||||
component.preciseFloat = Math.PI;
|
||||
component.integerValue = 999999;
|
||||
component.normalFloat = 2.718;
|
||||
|
||||
console.log('原始数据:', {
|
||||
normalFloat: component.normalFloat,
|
||||
highPrecisionNumber: component.highPrecisionNumber,
|
||||
preciseFloat: component.preciseFloat,
|
||||
integerValue: component.integerValue,
|
||||
flag: component.flag,
|
||||
text: component.text
|
||||
});
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, DecoratedComponent);
|
||||
|
||||
console.log('\\n取回数据:', {
|
||||
normalFloat: retrieved?.normalFloat,
|
||||
highPrecisionNumber: retrieved?.highPrecisionNumber,
|
||||
preciseFloat: retrieved?.preciseFloat,
|
||||
integerValue: retrieved?.integerValue,
|
||||
flag: retrieved?.flag,
|
||||
text: retrieved?.text
|
||||
});
|
||||
|
||||
// 验证精度保持
|
||||
expect(retrieved?.normalFloat).toBeCloseTo(2.718, 5); // Float32精度
|
||||
expect(retrieved?.highPrecisionNumber).toBe(Number.MAX_SAFE_INTEGER); // 高精度保持
|
||||
expect(retrieved?.preciseFloat).toBeCloseTo(Math.PI, 15); // Float64精度
|
||||
expect(retrieved?.integerValue).toBe(999999); // 整数保持
|
||||
expect(retrieved?.flag).toBe(true);
|
||||
expect(retrieved?.text).toBe('hello');
|
||||
|
||||
console.log('✅ 所有装饰器类型验证通过');
|
||||
});
|
||||
|
||||
test('验证存储器内部结构', () => {
|
||||
console.log('\\n=== 测试存储器内部结构 ===');
|
||||
|
||||
const component = new DecoratedComponent();
|
||||
manager.addComponent(1, component);
|
||||
|
||||
const storage = manager.getStorage(DecoratedComponent) as SoAStorage<DecoratedComponent>;
|
||||
|
||||
// 检查TypedArray字段
|
||||
const normalFloatArray = storage.getFieldArray('normalFloat');
|
||||
const preciseFloatArray = storage.getFieldArray('preciseFloat');
|
||||
const integerArray = storage.getFieldArray('integerValue');
|
||||
const flagArray = storage.getFieldArray('flag');
|
||||
|
||||
console.log('存储类型:', {
|
||||
normalFloat: normalFloatArray?.constructor.name,
|
||||
preciseFloat: preciseFloatArray?.constructor.name,
|
||||
integerValue: integerArray?.constructor.name,
|
||||
flag: flagArray?.constructor.name
|
||||
});
|
||||
|
||||
// 验证存储类型
|
||||
expect(normalFloatArray).toBeInstanceOf(Float32Array);
|
||||
expect(preciseFloatArray).toBeInstanceOf(Float64Array);
|
||||
expect(integerArray).toBeInstanceOf(Int32Array);
|
||||
expect(flagArray).toBeInstanceOf(Float32Array);
|
||||
|
||||
// 高精度字段不应该在TypedArray中
|
||||
const highPrecisionArray = storage.getFieldArray('highPrecisionNumber');
|
||||
expect(highPrecisionArray).toBeNull();
|
||||
|
||||
console.log('✅ 存储器内部结构验证通过');
|
||||
});
|
||||
|
||||
test('测试边界值精度', () => {
|
||||
console.log('\\n=== 测试边界值精度 ===');
|
||||
|
||||
const component = new DecoratedComponent();
|
||||
|
||||
// 测试极限值
|
||||
component.highPrecisionNumber = Number.MAX_SAFE_INTEGER;
|
||||
component.preciseFloat = Number.MIN_VALUE;
|
||||
component.normalFloat = 16777217; // 超出Float32精度
|
||||
component.integerValue = -2147483648; // Int32最小值
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, DecoratedComponent);
|
||||
|
||||
console.log('边界值测试结果:', {
|
||||
highPrecision: retrieved?.highPrecisionNumber === Number.MAX_SAFE_INTEGER,
|
||||
preciseFloat: retrieved?.preciseFloat === Number.MIN_VALUE,
|
||||
normalFloat: retrieved?.normalFloat, // 可能有精度损失
|
||||
integerValue: retrieved?.integerValue === -2147483648
|
||||
});
|
||||
|
||||
// 验证高精度保持
|
||||
expect(retrieved?.highPrecisionNumber).toBe(Number.MAX_SAFE_INTEGER);
|
||||
expect(retrieved?.preciseFloat).toBe(Number.MIN_VALUE);
|
||||
expect(retrieved?.integerValue).toBe(-2147483648);
|
||||
|
||||
console.log('✅ 边界值精度测试通过');
|
||||
});
|
||||
|
||||
test('性能对比:装饰器 vs 自动检测', () => {
|
||||
console.log('\\n=== 性能对比测试 ===');
|
||||
|
||||
const entityCount = 1000;
|
||||
|
||||
// 使用装饰器的组件
|
||||
const startTime = performance.now();
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const component = new DecoratedComponent();
|
||||
component.highPrecisionNumber = Number.MAX_SAFE_INTEGER;
|
||||
component.preciseFloat = Math.PI * i;
|
||||
component.integerValue = i;
|
||||
manager.addComponent(i, component);
|
||||
}
|
||||
const decoratorTime = performance.now() - startTime;
|
||||
|
||||
console.log(`装饰器方式: ${decoratorTime.toFixed(2)}ms`);
|
||||
console.log(`平均每个组件: ${(decoratorTime / entityCount).toFixed(4)}ms`);
|
||||
|
||||
// 验证数据完整性
|
||||
const sample = manager.getComponent(500, DecoratedComponent);
|
||||
expect(sample?.highPrecisionNumber).toBe(Number.MAX_SAFE_INTEGER);
|
||||
expect(sample?.integerValue).toBe(500);
|
||||
|
||||
console.log('✅ 性能测试完成,数据完整性验证通过');
|
||||
});
|
||||
});
|
||||
128
packages/core/tests/ECS/Core/SoAStorage.edge-case.test.ts
Normal file
128
packages/core/tests/ECS/Core/SoAStorage.edge-case.test.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { ComponentStorageManager, EnableSoA } from '../../../src/ECS/Core/ComponentStorage';
|
||||
|
||||
// 模拟复杂对象(如cocos的node节点)
|
||||
class MockNode {
|
||||
public name: string;
|
||||
public active: boolean;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `Node(${this.name})`;
|
||||
}
|
||||
}
|
||||
|
||||
// 包含复杂属性的组件
|
||||
@EnableSoA
|
||||
class ProblematicComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
public node: MockNode | null = null;
|
||||
public callback: Function | null = null;
|
||||
public data: any = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.node = new MockNode('test');
|
||||
this.callback = () => console.log('test');
|
||||
this.data = { complex: 'object' };
|
||||
}
|
||||
}
|
||||
|
||||
// 安全的数值组件
|
||||
@EnableSoA
|
||||
class SafeComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
public active: boolean = true;
|
||||
}
|
||||
|
||||
describe('SoA边界情况和复杂属性测试', () => {
|
||||
let manager: ComponentStorageManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new ComponentStorageManager();
|
||||
});
|
||||
|
||||
test('包含复杂对象的组件会有什么问题', () => {
|
||||
console.log('\\n=== 测试复杂对象处理 ===');
|
||||
|
||||
// 创建包含复杂属性的组件
|
||||
const originalComponent = new ProblematicComponent();
|
||||
console.log('原始组件:', {
|
||||
x: originalComponent.x,
|
||||
y: originalComponent.y,
|
||||
node: originalComponent.node?.name,
|
||||
callback: typeof originalComponent.callback,
|
||||
data: originalComponent.data
|
||||
});
|
||||
|
||||
// 添加到SoA存储
|
||||
manager.addComponent(1, originalComponent);
|
||||
|
||||
// 获取组件看看发生了什么
|
||||
const retrievedComponent = manager.getComponent(1, ProblematicComponent);
|
||||
console.log('取回的组件:', {
|
||||
x: retrievedComponent?.x,
|
||||
y: retrievedComponent?.y,
|
||||
node: retrievedComponent?.node,
|
||||
callback: retrievedComponent?.callback,
|
||||
data: retrievedComponent?.data
|
||||
});
|
||||
|
||||
// 验证数据完整性
|
||||
expect(retrievedComponent?.x).toBe(0);
|
||||
expect(retrievedComponent?.y).toBe(0);
|
||||
|
||||
// 复杂对象的问题
|
||||
console.log('\\n⚠️ 问题发现:');
|
||||
console.log('- node对象:', retrievedComponent?.node);
|
||||
console.log('- callback函数:', retrievedComponent?.callback);
|
||||
console.log('- data对象:', retrievedComponent?.data);
|
||||
|
||||
// 复杂属性现在应该正确保存
|
||||
expect(retrievedComponent?.node?.name).toBe('test'); // 应该保持原始值
|
||||
expect(retrievedComponent?.callback).toBe(originalComponent.callback); // 应该是同一个函数
|
||||
expect(retrievedComponent?.data).toEqual({ complex: 'object' }); // 应该保持原始数据
|
||||
|
||||
console.log('✅ 修复成功:复杂对象现在能正确处理!');
|
||||
});
|
||||
|
||||
test('纯数值组件工作正常', () => {
|
||||
console.log('\\n=== 测试纯数值组件 ===');
|
||||
|
||||
const safeComponent = new SafeComponent();
|
||||
safeComponent.x = 100;
|
||||
safeComponent.y = 200;
|
||||
safeComponent.active = false;
|
||||
|
||||
manager.addComponent(1, safeComponent);
|
||||
const retrieved = manager.getComponent(1, SafeComponent);
|
||||
|
||||
console.log('纯数值组件正常工作:', {
|
||||
x: retrieved?.x,
|
||||
y: retrieved?.y,
|
||||
active: retrieved?.active
|
||||
});
|
||||
|
||||
expect(retrieved?.x).toBe(100);
|
||||
expect(retrieved?.y).toBe(200);
|
||||
expect(retrieved?.active).toBe(false);
|
||||
});
|
||||
|
||||
test('SoA是否能检测到不适合的组件类型', () => {
|
||||
console.log('\\n=== 测试类型检测 ===');
|
||||
|
||||
// 当前实现会静默忽略复杂字段
|
||||
// 这是一个潜在的问题!
|
||||
const storage = manager.getStorage(ProblematicComponent);
|
||||
console.log('存储类型:', storage.constructor.name);
|
||||
|
||||
// SoA存储应该能警告或拒绝不适合的组件
|
||||
expect(storage.constructor.name).toBe('SoAStorage');
|
||||
});
|
||||
});
|
||||
158
packages/core/tests/ECS/Core/SoAStorage.types.test.ts
Normal file
158
packages/core/tests/ECS/Core/SoAStorage.types.test.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { ComponentStorageManager, EnableSoA, HighPrecision, Float64 } from '../../../src/ECS/Core/ComponentStorage';
|
||||
|
||||
// 包含所有基础类型的组件
|
||||
@EnableSoA
|
||||
class AllTypesComponent extends Component {
|
||||
// 数值类型
|
||||
public intNumber: number = 42;
|
||||
public floatNumber: number = 3.14;
|
||||
public zeroNumber: number = 0;
|
||||
|
||||
// 布尔类型
|
||||
public trueBoolean: boolean = true;
|
||||
public falseBoolean: boolean = false;
|
||||
|
||||
// 字符串类型
|
||||
public emptyString: string = '';
|
||||
public normalString: string = 'hello';
|
||||
public longString: string = 'this is a long string with spaces and 123 numbers!';
|
||||
|
||||
// 其他基础类型
|
||||
public nullValue: null = null;
|
||||
public undefinedValue: undefined = undefined;
|
||||
|
||||
// 复杂类型
|
||||
public arrayValue: number[] = [1, 2, 3];
|
||||
public objectValue: { name: string } = { name: 'test' };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
// 边界测试专用组件
|
||||
@EnableSoA
|
||||
class BoundaryTestComponent extends Component {
|
||||
// 高精度大整数
|
||||
@HighPrecision
|
||||
public maxInt: number = 0;
|
||||
|
||||
// 高精度小浮点数
|
||||
@Float64
|
||||
public minFloat: number = 0;
|
||||
|
||||
// 普通数值
|
||||
public normalNumber: number = 0;
|
||||
|
||||
// 字符串测试
|
||||
public testString: string = '';
|
||||
public longString: string = '';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
describe('SoA所有数据类型处理测试', () => {
|
||||
let manager: ComponentStorageManager;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new ComponentStorageManager();
|
||||
});
|
||||
|
||||
test('验证所有基础类型的处理', () => {
|
||||
console.log('\\n=== 测试所有数据类型 ===');
|
||||
|
||||
// 创建包含各种类型的组件
|
||||
const originalComponent = new AllTypesComponent();
|
||||
originalComponent.normalString = 'modified string';
|
||||
originalComponent.longString = '测试中文字符串 with emoji 🎉';
|
||||
originalComponent.intNumber = 999;
|
||||
originalComponent.floatNumber = 2.718;
|
||||
originalComponent.trueBoolean = false;
|
||||
originalComponent.falseBoolean = true;
|
||||
|
||||
console.log('原始组件数据:', {
|
||||
intNumber: originalComponent.intNumber,
|
||||
floatNumber: originalComponent.floatNumber,
|
||||
trueBoolean: originalComponent.trueBoolean,
|
||||
falseBoolean: originalComponent.falseBoolean,
|
||||
emptyString: `"${originalComponent.emptyString}"`,
|
||||
normalString: `"${originalComponent.normalString}"`,
|
||||
longString: `"${originalComponent.longString}"`,
|
||||
arrayValue: originalComponent.arrayValue,
|
||||
objectValue: originalComponent.objectValue
|
||||
});
|
||||
|
||||
// 存储到SoA
|
||||
manager.addComponent(1, originalComponent);
|
||||
|
||||
// 获取并验证
|
||||
const retrievedComponent = manager.getComponent(1, AllTypesComponent);
|
||||
|
||||
console.log('\\n取回的组件数据:', {
|
||||
intNumber: retrievedComponent?.intNumber,
|
||||
floatNumber: retrievedComponent?.floatNumber,
|
||||
trueBoolean: retrievedComponent?.trueBoolean,
|
||||
falseBoolean: retrievedComponent?.falseBoolean,
|
||||
emptyString: `"${retrievedComponent?.emptyString}"`,
|
||||
normalString: `"${retrievedComponent?.normalString}"`,
|
||||
longString: `"${retrievedComponent?.longString}"`,
|
||||
arrayValue: retrievedComponent?.arrayValue,
|
||||
objectValue: retrievedComponent?.objectValue
|
||||
});
|
||||
|
||||
// 验证数值类型
|
||||
expect(retrievedComponent?.intNumber).toBe(999);
|
||||
expect(retrievedComponent?.floatNumber).toBeCloseTo(2.718);
|
||||
|
||||
// 验证布尔类型
|
||||
expect(retrievedComponent?.trueBoolean).toBe(false);
|
||||
expect(retrievedComponent?.falseBoolean).toBe(true);
|
||||
|
||||
// 验证字符串类型
|
||||
expect(retrievedComponent?.emptyString).toBe('');
|
||||
expect(retrievedComponent?.normalString).toBe('modified string');
|
||||
expect(retrievedComponent?.longString).toBe('测试中文字符串 with emoji 🎉');
|
||||
|
||||
// 验证复杂类型
|
||||
expect(retrievedComponent?.arrayValue).toEqual([1, 2, 3]);
|
||||
expect(retrievedComponent?.objectValue).toEqual({ name: 'test' });
|
||||
|
||||
console.log('\\n✅ 所有类型验证完成');
|
||||
});
|
||||
|
||||
test('边界情况测试', () => {
|
||||
console.log('\\n=== 边界情况测试 ===');
|
||||
|
||||
const component = new BoundaryTestComponent();
|
||||
|
||||
// 特殊数值
|
||||
component.maxInt = Number.MAX_SAFE_INTEGER;
|
||||
component.minFloat = Number.MIN_VALUE;
|
||||
component.normalNumber = -0;
|
||||
|
||||
// 特殊字符串
|
||||
component.testString = '\\n\\t\\r"\'\\\\'; // 转义字符
|
||||
component.longString = 'a'.repeat(1000); // 长字符串
|
||||
|
||||
manager.addComponent(1, component);
|
||||
const retrieved = manager.getComponent(1, BoundaryTestComponent);
|
||||
|
||||
console.log('边界情况结果:', {
|
||||
maxInt: retrieved?.maxInt,
|
||||
minFloat: retrieved?.minFloat,
|
||||
negativeZero: retrieved?.normalNumber,
|
||||
escapeStr: retrieved?.testString,
|
||||
longStr: retrieved?.longString?.length
|
||||
});
|
||||
|
||||
expect(retrieved?.maxInt).toBe(Number.MAX_SAFE_INTEGER);
|
||||
expect(retrieved?.minFloat).toBe(Number.MIN_VALUE);
|
||||
expect(retrieved?.testString).toBe('\\n\\t\\r"\'\\\\');
|
||||
expect(retrieved?.longString).toBe('a'.repeat(1000));
|
||||
|
||||
console.log('✅ 边界情况测试通过');
|
||||
});
|
||||
});
|
||||
500
packages/core/tests/ECS/Core/SystemInitializeIssue.test.ts
Normal file
500
packages/core/tests/ECS/Core/SystemInitializeIssue.test.ts
Normal file
@@ -0,0 +1,500 @@
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
|
||||
// 测试组件
|
||||
class PositionComponent extends Component {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
this.x = (args[0] as number) ?? 0;
|
||||
this.y = (args[1] as number) ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number;
|
||||
public vy: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
this.vx = (args[0] as number) ?? 0;
|
||||
this.vy = (args[1] as number) ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public health: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
this.health = (args[0] as number) ?? 100;
|
||||
}
|
||||
}
|
||||
|
||||
class TagComponent extends Component {
|
||||
public tag: string;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
this.tag = (args[0] as string) ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
// 测试系统
|
||||
class MovementSystem extends EntitySystem {
|
||||
public processedEntities: Entity[] = [];
|
||||
public initializeCalled = false;
|
||||
public onAddedEntities: Entity[] = [];
|
||||
public onRemovedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||
}
|
||||
|
||||
public override initialize(): void {
|
||||
this.initializeCalled = true;
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
protected override onAdded(entity: Entity): void {
|
||||
this.onAddedEntities.push(entity);
|
||||
}
|
||||
|
||||
protected override onRemoved(entity: Entity): void {
|
||||
this.onRemovedEntities.push(entity);
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processedEntities = [...entities];
|
||||
for (const entity of entities) {
|
||||
const position = entity.getComponent(PositionComponent)!;
|
||||
const velocity = entity.getComponent(VelocityComponent)!;
|
||||
position.x += velocity.vx;
|
||||
position.y += velocity.vy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HealthSystem extends EntitySystem {
|
||||
public processedEntities: Entity[] = [];
|
||||
public initializeCalled = false;
|
||||
public onAddedEntities: Entity[] = [];
|
||||
public onRemovedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(HealthComponent));
|
||||
}
|
||||
|
||||
public override initialize(): void {
|
||||
this.initializeCalled = true;
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
protected override onAdded(entity: Entity): void {
|
||||
this.onAddedEntities.push(entity);
|
||||
}
|
||||
|
||||
protected override onRemoved(entity: Entity): void {
|
||||
this.onRemovedEntities.push(entity);
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processedEntities = [...entities];
|
||||
for (const entity of entities) {
|
||||
const health = entity.getComponent(HealthComponent)!;
|
||||
if (health.health <= 0) {
|
||||
entity.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MultiComponentSystem extends EntitySystem {
|
||||
public processedEntities: Entity[] = [];
|
||||
public initializeCalled = false;
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent, HealthComponent, TagComponent));
|
||||
}
|
||||
|
||||
public override initialize(): void {
|
||||
this.initializeCalled = true;
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processedEntities = [...entities];
|
||||
}
|
||||
}
|
||||
|
||||
describe('ECS系统初始化时序问题深度测试', () => {
|
||||
let scene: Scene;
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene();
|
||||
scene.name = "InitializeTestScene";
|
||||
});
|
||||
|
||||
describe('基础时序问题测试', () => {
|
||||
test('先添加实体再添加系统 - 系统应该正确初始化', () => {
|
||||
// 创建实体并添加组件
|
||||
const player = scene.createEntity("Player");
|
||||
player.addComponent(new PositionComponent(10, 20));
|
||||
player.addComponent(new VelocityComponent(1, 1));
|
||||
player.addComponent(new HealthComponent(100));
|
||||
|
||||
const enemy = scene.createEntity("Enemy");
|
||||
enemy.addComponent(new PositionComponent(50, 60));
|
||||
enemy.addComponent(new VelocityComponent(-1, 0));
|
||||
enemy.addComponent(new HealthComponent(80));
|
||||
|
||||
// 验证实体已创建
|
||||
expect(scene.entities.count).toBe(2);
|
||||
|
||||
// 添加系统
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
// 验证initialize方法被调用
|
||||
expect(movementSystem.initializeCalled).toBe(true);
|
||||
expect(healthSystem.initializeCalled).toBe(true);
|
||||
|
||||
// 验证系统正确识别已存在的实体
|
||||
expect(movementSystem.entities.length).toBe(2);
|
||||
expect(healthSystem.entities.length).toBe(2);
|
||||
|
||||
// 验证onAdded回调被正确调用
|
||||
expect(movementSystem.onAddedEntities.length).toBe(2);
|
||||
expect(movementSystem.onAddedEntities).toContain(player);
|
||||
expect(movementSystem.onAddedEntities).toContain(enemy);
|
||||
|
||||
// 运行更新确认处理
|
||||
scene.update();
|
||||
expect(movementSystem.processedEntities.length).toBe(2);
|
||||
expect(healthSystem.processedEntities.length).toBe(2);
|
||||
|
||||
// 检查移动逻辑是否生效
|
||||
const playerPos = player.getComponent(PositionComponent)!;
|
||||
expect(playerPos.x).toBe(11);
|
||||
expect(playerPos.y).toBe(21);
|
||||
});
|
||||
|
||||
test('先添加系统再添加实体 - 正常工作', () => {
|
||||
// 先添加系统
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
// 验证initialize被调用,但没有发现实体
|
||||
expect(movementSystem.initializeCalled).toBe(true);
|
||||
expect(healthSystem.initializeCalled).toBe(true);
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(healthSystem.entities.length).toBe(0);
|
||||
|
||||
// 创建实体
|
||||
const player = scene.createEntity("Player");
|
||||
player.addComponent(new PositionComponent(10, 20));
|
||||
player.addComponent(new VelocityComponent(1, 1));
|
||||
player.addComponent(new HealthComponent(100));
|
||||
|
||||
// 系统应该自动识别新实体
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
expect(healthSystem.entities.length).toBe(1);
|
||||
expect(movementSystem.onAddedEntities.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('复杂场景的时序测试', () => {
|
||||
test('部分匹配实体的初始化', () => {
|
||||
// 创建不同类型的实体
|
||||
const fullEntity = scene.createEntity("FullEntity");
|
||||
fullEntity.addComponent(new PositionComponent(0, 0));
|
||||
fullEntity.addComponent(new VelocityComponent(1, 1));
|
||||
fullEntity.addComponent(new HealthComponent(100));
|
||||
|
||||
const partialEntity1 = scene.createEntity("PartialEntity1");
|
||||
partialEntity1.addComponent(new PositionComponent(10, 10));
|
||||
partialEntity1.addComponent(new HealthComponent(50));
|
||||
// 缺少VelocityComponent
|
||||
|
||||
const partialEntity2 = scene.createEntity("PartialEntity2");
|
||||
partialEntity2.addComponent(new PositionComponent(20, 20));
|
||||
partialEntity2.addComponent(new VelocityComponent(2, 2));
|
||||
// 缺少HealthComponent
|
||||
|
||||
// 添加系统
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
// 验证选择性匹配
|
||||
expect(movementSystem.entities).toContain(fullEntity);
|
||||
expect(movementSystem.entities).not.toContain(partialEntity1);
|
||||
expect(movementSystem.entities).toContain(partialEntity2);
|
||||
expect(movementSystem.entities.length).toBe(2);
|
||||
|
||||
expect(healthSystem.entities).toContain(fullEntity);
|
||||
expect(healthSystem.entities).toContain(partialEntity1);
|
||||
expect(healthSystem.entities).not.toContain(partialEntity2);
|
||||
expect(healthSystem.entities.length).toBe(2);
|
||||
});
|
||||
|
||||
test('多组件要求系统的初始化', () => {
|
||||
// 创建具有不同组件组合的实体
|
||||
const entity1 = scene.createEntity("Entity1");
|
||||
entity1.addComponent(new PositionComponent(0, 0));
|
||||
entity1.addComponent(new HealthComponent(100));
|
||||
entity1.addComponent(new TagComponent("player"));
|
||||
|
||||
const entity2 = scene.createEntity("Entity2");
|
||||
entity2.addComponent(new PositionComponent(10, 10));
|
||||
entity2.addComponent(new HealthComponent(80));
|
||||
// 缺少TagComponent
|
||||
|
||||
const entity3 = scene.createEntity("Entity3");
|
||||
entity3.addComponent(new PositionComponent(20, 20));
|
||||
entity3.addComponent(new TagComponent("enemy"));
|
||||
// 缺少HealthComponent
|
||||
|
||||
// 添加要求三个组件的系统
|
||||
const multiSystem = new MultiComponentSystem();
|
||||
scene.addEntityProcessor(multiSystem);
|
||||
|
||||
// 只有entity1应该匹配
|
||||
expect(multiSystem.entities.length).toBe(1);
|
||||
expect(multiSystem.entities).toContain(entity1);
|
||||
expect(multiSystem.entities).not.toContain(entity2);
|
||||
expect(multiSystem.entities).not.toContain(entity3);
|
||||
});
|
||||
|
||||
test('批量实体创建后的系统初始化', () => {
|
||||
// 批量创建实体
|
||||
const entities = scene.createEntities(10, "BatchEntity");
|
||||
|
||||
// 为所有实体添加组件
|
||||
entities.forEach((entity, index) => {
|
||||
entity.addComponent(new PositionComponent(index * 10, index * 10));
|
||||
entity.addComponent(new VelocityComponent(index, index));
|
||||
if (index % 2 === 0) {
|
||||
entity.addComponent(new HealthComponent(100 - index * 10));
|
||||
}
|
||||
});
|
||||
|
||||
// 添加系统
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
// 验证系统正确处理批量实体
|
||||
expect(movementSystem.entities.length).toBe(10); // 所有实体都有Position+Velocity
|
||||
expect(healthSystem.entities.length).toBe(5); // 只有偶数索引的实体有Health
|
||||
|
||||
// 验证onAdded回调被正确调用
|
||||
expect(movementSystem.onAddedEntities.length).toBe(10);
|
||||
expect(healthSystem.onAddedEntities.length).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('动态组件修改后的系统响应', () => {
|
||||
test('运行时添加组件 - 系统自动响应', () => {
|
||||
const movementSystem = new MovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 创建只有位置组件的实体
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
|
||||
// 系统不应该匹配
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
|
||||
// 添加速度组件
|
||||
entity.addComponent(new VelocityComponent(5, 5));
|
||||
|
||||
// 系统应该立即识别
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
expect(movementSystem.entities).toContain(entity);
|
||||
expect(movementSystem.onAddedEntities).toContain(entity);
|
||||
});
|
||||
|
||||
test('运行时移除组件 - 系统自动响应', () => {
|
||||
const movementSystem = new MovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 创建完整的可移动实体
|
||||
const entity = scene.createEntity("MovableEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(5, 5));
|
||||
|
||||
// 系统应该识别
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
|
||||
// 移除速度组件
|
||||
const velocityComponent = entity.getComponent(VelocityComponent);
|
||||
if (velocityComponent) {
|
||||
entity.removeComponent(velocityComponent);
|
||||
}
|
||||
|
||||
// 系统应该移除实体
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(movementSystem.onRemovedEntities).toContain(entity);
|
||||
});
|
||||
|
||||
test('复杂的组件添加移除序列', () => {
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
const entity = scene.createEntity("ComplexEntity");
|
||||
|
||||
// 初始状态:无组件
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(healthSystem.entities.length).toBe(0);
|
||||
|
||||
// 添加位置组件
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(healthSystem.entities.length).toBe(0);
|
||||
|
||||
// 添加健康组件
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(healthSystem.entities.length).toBe(1);
|
||||
|
||||
// 添加速度组件
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
expect(healthSystem.entities.length).toBe(1);
|
||||
|
||||
// 移除健康组件
|
||||
const healthComponent = entity.getComponent(HealthComponent);
|
||||
if (healthComponent) {
|
||||
entity.removeComponent(healthComponent);
|
||||
}
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
expect(healthSystem.entities.length).toBe(0);
|
||||
|
||||
// 移除位置组件
|
||||
const positionComponent = entity.getComponent(PositionComponent);
|
||||
if (positionComponent) {
|
||||
entity.removeComponent(positionComponent);
|
||||
}
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(healthSystem.entities.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('系统重复添加和移除测试', () => {
|
||||
test('重复添加同一个系统 - 应该忽略', () => {
|
||||
const movementSystem = new MovementSystem();
|
||||
|
||||
// 第一次添加
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
expect(scene.entityProcessors.count).toBe(1);
|
||||
expect(movementSystem.initializeCalled).toBe(true);
|
||||
|
||||
// 重置标志
|
||||
movementSystem.initializeCalled = false;
|
||||
|
||||
// 第二次添加同一个系统
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
expect(scene.entityProcessors.count).toBe(1); // 没有增加
|
||||
expect(movementSystem.initializeCalled).toBe(false); // initialize不应该再次调用
|
||||
});
|
||||
|
||||
test('添加后移除再添加 - 应该重新初始化', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
const movementSystem = new MovementSystem();
|
||||
|
||||
// 第一次添加
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
expect(movementSystem.initializeCalled).toBe(true);
|
||||
|
||||
// 移除系统
|
||||
scene.removeEntityProcessor(movementSystem);
|
||||
expect(scene.entityProcessors.count).toBe(0);
|
||||
|
||||
// 重置状态
|
||||
movementSystem.initializeCalled = false;
|
||||
movementSystem.onAddedEntities = [];
|
||||
|
||||
// 重新添加
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
expect(movementSystem.initializeCalled).toBe(true);
|
||||
expect(movementSystem.onAddedEntities.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('空场景和空系统的边界情况', () => {
|
||||
test('空场景添加系统 - 不应该出错', () => {
|
||||
const movementSystem = new MovementSystem();
|
||||
|
||||
expect(() => {
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(movementSystem.initializeCalled).toBe(true);
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
});
|
||||
|
||||
test('有实体但没有匹配组件 - 系统应该为空', () => {
|
||||
// 创建只有健康组件的实体
|
||||
const entity = scene.createEntity("HealthOnlyEntity");
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
|
||||
// 添加移动系统(需要Position+Velocity)
|
||||
const movementSystem = new MovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(movementSystem.onAddedEntities.length).toBe(0);
|
||||
});
|
||||
|
||||
test('实体被禁用 - 系统仍应包含但不处理', () => {
|
||||
const entity = scene.createEntity("DisabledEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
const movementSystem = new MovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
|
||||
// 禁用实体
|
||||
entity.enabled = false;
|
||||
|
||||
// 系统仍然包含实体,但处理时应该跳过
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
|
||||
scene.update();
|
||||
// 处理逻辑中应该检查enabled状态
|
||||
// 由于实体被禁用,位置不应该改变(这取决于系统实现)
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
scene.destroyAllEntities();
|
||||
const processors = [...scene.entityProcessors.processors];
|
||||
processors.forEach(processor => scene.removeEntityProcessor(processor));
|
||||
});
|
||||
});
|
||||
140
packages/core/tests/ECS/Core/SystemMultipleInitialize.test.ts
Normal file
140
packages/core/tests/ECS/Core/SystemMultipleInitialize.test.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||||
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
|
||||
|
||||
class TestComponent extends Component {
|
||||
public value: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [value = 0] = args as [number?];
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class TrackingSystem extends EntitySystem {
|
||||
public initializeCallCount = 0;
|
||||
public onChangedCallCount = 0;
|
||||
public trackedEntities: Entity[] = [];
|
||||
|
||||
public override initialize(): void {
|
||||
// 必须先调用父类的initialize来检查防重复逻辑
|
||||
const wasInitialized = (this as any)._initialized;
|
||||
super.initialize();
|
||||
|
||||
// 只有在真正执行初始化时才增加计数和处理实体
|
||||
if (!wasInitialized) {
|
||||
this.initializeCallCount++;
|
||||
|
||||
// 处理所有现有实体
|
||||
if (this.scene) {
|
||||
for (const entity of this.scene.entities.buffer) {
|
||||
this.onChanged(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onChanged(entity: Entity): void {
|
||||
this.onChangedCallCount++;
|
||||
if (this.isInterestedEntity(entity)) {
|
||||
if (!this.trackedEntities.includes(entity)) {
|
||||
this.trackedEntities.push(entity);
|
||||
}
|
||||
} else {
|
||||
const index = this.trackedEntities.indexOf(entity);
|
||||
if (index !== -1) {
|
||||
this.trackedEntities.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public isInterestedEntity(entity: Entity): boolean {
|
||||
return entity.hasComponent(TestComponent);
|
||||
}
|
||||
}
|
||||
|
||||
describe('系统多次初始化问题测试', () => {
|
||||
let scene: Scene;
|
||||
let system: TrackingSystem;
|
||||
|
||||
beforeEach(() => {
|
||||
ComponentTypeManager.instance.reset();
|
||||
scene = new Scene();
|
||||
system = new TrackingSystem();
|
||||
});
|
||||
|
||||
test('系统被多次添加到场景 - 应该防止重复初始化', () => {
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
entity.addComponent(new TestComponent(10));
|
||||
|
||||
// 第一次添加系统
|
||||
scene.addEntityProcessor(system);
|
||||
expect(system.initializeCallCount).toBe(1);
|
||||
expect(system.trackedEntities.length).toBe(1);
|
||||
expect(system.onChangedCallCount).toBe(1);
|
||||
|
||||
// 再次添加同一个系统 - 应该被忽略
|
||||
scene.addEntityProcessor(system);
|
||||
expect(system.initializeCallCount).toBe(1); // 不应该增加
|
||||
expect(system.trackedEntities.length).toBe(1); // 实体不应该重复
|
||||
expect(system.onChangedCallCount).toBe(1); // onChanged不应该重复调用
|
||||
});
|
||||
|
||||
test('手动多次调用initialize - 应该防止重复处理', () => {
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
entity.addComponent(new TestComponent(10));
|
||||
|
||||
scene.addEntityProcessor(system);
|
||||
expect(system.initializeCallCount).toBe(1);
|
||||
expect(system.trackedEntities.length).toBe(1);
|
||||
expect(system.onChangedCallCount).toBe(1);
|
||||
|
||||
// 手动再次调用initialize - 应该被防止
|
||||
system.initialize();
|
||||
expect(system.initializeCallCount).toBe(1); // 不应该增加
|
||||
expect(system.onChangedCallCount).toBe(1); // onChanged不应该重复调用
|
||||
expect(system.trackedEntities.length).toBe(1);
|
||||
});
|
||||
|
||||
test('系统被移除后重新添加 - 应该重新初始化', () => {
|
||||
const entity = scene.createEntity('TestEntity');
|
||||
entity.addComponent(new TestComponent(10));
|
||||
|
||||
// 添加系统
|
||||
scene.addEntityProcessor(system);
|
||||
expect(system.initializeCallCount).toBe(1);
|
||||
expect(system.trackedEntities.length).toBe(1);
|
||||
|
||||
// 移除系统
|
||||
scene.removeEntityProcessor(system);
|
||||
|
||||
// 重新添加系统 - 应该重新初始化
|
||||
scene.addEntityProcessor(system);
|
||||
expect(system.initializeCallCount).toBe(2); // 应该重新初始化
|
||||
expect(system.trackedEntities.length).toBe(1);
|
||||
});
|
||||
|
||||
test('多个实体的重复初始化应该被防止', () => {
|
||||
// 创建多个实体
|
||||
const entities = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const entity = scene.createEntity(`Entity${i}`);
|
||||
entity.addComponent(new TestComponent(i));
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
scene.addEntityProcessor(system);
|
||||
expect(system.initializeCallCount).toBe(1);
|
||||
expect(system.trackedEntities.length).toBe(5);
|
||||
expect(system.onChangedCallCount).toBe(5);
|
||||
|
||||
// 手动再次初始化 - 应该被防止
|
||||
system.initialize();
|
||||
expect(system.initializeCallCount).toBe(1); // 不应该增加
|
||||
expect(system.onChangedCallCount).toBe(5); // 不应该重复处理
|
||||
expect(system.trackedEntities.length).toBe(5);
|
||||
});
|
||||
});
|
||||
375
packages/core/tests/ECS/Core/SystemTimingIssue.test.ts
Normal file
375
packages/core/tests/ECS/Core/SystemTimingIssue.test.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
|
||||
// 测试组件
|
||||
class PositionComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public health: number = 100;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [health = 100] = args as [number?];
|
||||
this.health = health;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试系统
|
||||
class MovementSystem extends EntitySystem {
|
||||
public processedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processedEntities = [...entities];
|
||||
// 简单的移动逻辑
|
||||
for (const entity of entities) {
|
||||
const position = entity.getComponent(PositionComponent)!;
|
||||
const velocity = entity.getComponent(VelocityComponent)!;
|
||||
position.x += velocity.vx;
|
||||
position.y += velocity.vy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HealthSystem extends EntitySystem {
|
||||
public processedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(HealthComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processedEntities = [...entities];
|
||||
// 简单的健康检查逻辑
|
||||
for (const entity of entities) {
|
||||
const health = entity.getComponent(HealthComponent)!;
|
||||
if (health.health <= 0) {
|
||||
// 标记为死亡,但不在这里销毁实体
|
||||
entity.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('ECS系统时序问题测试', () => {
|
||||
let scene: Scene;
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene();
|
||||
scene.name = "TimingTestScene";
|
||||
});
|
||||
|
||||
describe('实体添加时序问题', () => {
|
||||
test(' 先添加实体再添加系统 - 暴露时序问题', () => {
|
||||
// 第一步:先创建并添加实体
|
||||
const player = scene.createEntity("Player");
|
||||
player.addComponent(new PositionComponent(10, 20));
|
||||
player.addComponent(new VelocityComponent(1, 1));
|
||||
player.addComponent(new HealthComponent(100));
|
||||
|
||||
const enemy = scene.createEntity("Enemy");
|
||||
enemy.addComponent(new PositionComponent(50, 60));
|
||||
enemy.addComponent(new VelocityComponent(-1, 0));
|
||||
enemy.addComponent(new HealthComponent(80));
|
||||
|
||||
// 验证实体已创建
|
||||
expect(scene.entities.count).toBe(2);
|
||||
|
||||
// 第二步:然后添加系统(这里可能出现时序问题)
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
// 验证系统已添加
|
||||
expect(scene.entityProcessors.count).toBe(2);
|
||||
|
||||
// 关键测试:检查系统是否正确识别了已存在的实体
|
||||
console.log("MovementSystem匹配的实体数量:", movementSystem.entities.length);
|
||||
console.log("HealthSystem匹配的实体数量:", healthSystem.entities.length);
|
||||
console.log("Player组件:", player.components.map(c => c.constructor.name));
|
||||
console.log("Enemy组件:", enemy.components.map(c => c.constructor.name));
|
||||
|
||||
// 预期结果:移动系统应该匹配到2个实体(都有Position+Velocity)
|
||||
expect(movementSystem.entities.length).toBe(2);
|
||||
// 预期结果:健康系统应该匹配到2个实体(都有Health)
|
||||
expect(healthSystem.entities.length).toBe(2);
|
||||
|
||||
// 运行一次更新看看
|
||||
scene.update();
|
||||
|
||||
// 检查系统是否处理了实体
|
||||
expect(movementSystem.processedEntities.length).toBe(2);
|
||||
expect(healthSystem.processedEntities.length).toBe(2);
|
||||
|
||||
// 检查移动逻辑是否生效
|
||||
const playerPos = player.getComponent(PositionComponent)!;
|
||||
expect(playerPos.x).toBe(11); // 10 + 1
|
||||
expect(playerPos.y).toBe(21); // 20 + 1
|
||||
});
|
||||
|
||||
test(' 先添加系统再添加实体 - 正常工作的情况', () => {
|
||||
// 第一步:先添加系统
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
// 验证系统已添加但没有实体
|
||||
expect(scene.entityProcessors.count).toBe(2);
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(healthSystem.entities.length).toBe(0);
|
||||
|
||||
// 第二步:然后创建并添加实体
|
||||
const player = scene.createEntity("Player");
|
||||
player.addComponent(new PositionComponent(10, 20));
|
||||
player.addComponent(new VelocityComponent(1, 1));
|
||||
player.addComponent(new HealthComponent(100));
|
||||
|
||||
const enemy = scene.createEntity("Enemy");
|
||||
enemy.addComponent(new PositionComponent(50, 60));
|
||||
enemy.addComponent(new VelocityComponent(-1, 0));
|
||||
enemy.addComponent(new HealthComponent(80));
|
||||
|
||||
// 验证实体已创建
|
||||
expect(scene.entities.count).toBe(2);
|
||||
|
||||
// 关键测试:检查系统是否正确识别了新添加的实体
|
||||
console.log("MovementSystem匹配的实体数量:", movementSystem.entities.length);
|
||||
console.log("HealthSystem匹配的实体数量:", healthSystem.entities.length);
|
||||
|
||||
// 预期结果:系统应该自动匹配到新实体
|
||||
expect(movementSystem.entities.length).toBe(2);
|
||||
expect(healthSystem.entities.length).toBe(2);
|
||||
|
||||
// 运行一次更新
|
||||
scene.update();
|
||||
|
||||
// 检查系统是否处理了实体
|
||||
expect(movementSystem.processedEntities.length).toBe(2);
|
||||
expect(healthSystem.processedEntities.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('组件动态修改时序问题', () => {
|
||||
test(' 运行时动态添加组件 - 检查系统响应', () => {
|
||||
// 先设置好系统
|
||||
const movementSystem = new MovementSystem();
|
||||
const healthSystem = new HealthSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(healthSystem);
|
||||
|
||||
// 创建只有位置组件的实体
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
|
||||
// 初始状态:只有健康系统不匹配,移动系统不匹配
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
expect(healthSystem.entities.length).toBe(0);
|
||||
|
||||
// 动态添加速度组件
|
||||
entity.addComponent(new VelocityComponent(5, 5));
|
||||
|
||||
// 关键测试:移动系统是否立即识别到这个实体
|
||||
console.log("添加VelocityComponent后MovementSystem实体数:", movementSystem.entities.length);
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
|
||||
// 动态添加健康组件
|
||||
entity.addComponent(new HealthComponent(50));
|
||||
|
||||
// 关键测试:健康系统是否立即识别到这个实体
|
||||
console.log("添加HealthComponent后HealthSystem实体数:", healthSystem.entities.length);
|
||||
expect(healthSystem.entities.length).toBe(1);
|
||||
|
||||
// 运行更新确认处理
|
||||
scene.update();
|
||||
expect(movementSystem.processedEntities.length).toBe(1);
|
||||
expect(healthSystem.processedEntities.length).toBe(1);
|
||||
});
|
||||
|
||||
test(' 运行时动态移除组件 - 检查系统响应', () => {
|
||||
// 先设置好系统
|
||||
const movementSystem = new MovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 创建完整的可移动实体
|
||||
const entity = scene.createEntity("MovableEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(5, 5));
|
||||
|
||||
// 确认系统识别了实体
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
|
||||
// 动态移除速度组件
|
||||
const velocityComponent = entity.getComponent(VelocityComponent);
|
||||
if (velocityComponent) {
|
||||
entity.removeComponent(velocityComponent);
|
||||
}
|
||||
|
||||
// 关键测试:移动系统是否立即移除了这个实体
|
||||
console.log("移除VelocityComponent后MovementSystem实体数:", movementSystem.entities.length);
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
|
||||
// 重新添加速度组件
|
||||
entity.addComponent(new VelocityComponent(3, 3));
|
||||
|
||||
// 关键测试:移动系统是否重新识别到这个实体
|
||||
console.log("重新添加VelocityComponent后MovementSystem实体数:", movementSystem.entities.length);
|
||||
expect(movementSystem.entities.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('系统初始化时序问题', () => {
|
||||
test(' 系统initialize方法调用时机', () => {
|
||||
// 创建实体
|
||||
const entity1 = scene.createEntity("Entity1");
|
||||
entity1.addComponent(new PositionComponent(10, 10));
|
||||
entity1.addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
const entity2 = scene.createEntity("Entity2");
|
||||
entity2.addComponent(new PositionComponent(20, 20));
|
||||
entity2.addComponent(new VelocityComponent(2, 2));
|
||||
|
||||
// 创建系统但先不添加到场景
|
||||
const movementSystem = new MovementSystem();
|
||||
|
||||
// 检查系统初始状态
|
||||
expect(movementSystem.entities.length).toBe(0);
|
||||
|
||||
// 添加系统到场景
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 关键测试:系统是否在添加时正确初始化并发现已存在的实体
|
||||
console.log("系统添加后发现的实体数:", movementSystem.entities.length);
|
||||
|
||||
// 这个测试会暴露initialize方法是否被正确调用
|
||||
expect(movementSystem.entities.length).toBe(2);
|
||||
});
|
||||
|
||||
test(' 批量实体操作的时序问题', () => {
|
||||
const movementSystem = new MovementSystem();
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 批量创建实体
|
||||
const entities = scene.createEntities(5, "BatchEntity");
|
||||
|
||||
// 为所有实体添加组件
|
||||
entities.forEach((entity, index) => {
|
||||
entity.addComponent(new PositionComponent(index * 10, index * 10));
|
||||
entity.addComponent(new VelocityComponent(index, index));
|
||||
});
|
||||
|
||||
// 关键测试:系统是否识别了所有批量创建的实体
|
||||
console.log("批量操作后系统实体数:", movementSystem.entities.length);
|
||||
expect(movementSystem.entities.length).toBe(5);
|
||||
|
||||
// 运行更新确认处理
|
||||
scene.update();
|
||||
expect(movementSystem.processedEntities.length).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('事件装饰器时序问题', () => {
|
||||
// 模拟使用事件装饰器的系统
|
||||
class EventDecoratedSystem extends EntitySystem {
|
||||
public receivedEvents: any[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent));
|
||||
// 模拟装饰器初始化
|
||||
this.initEventListeners();
|
||||
}
|
||||
|
||||
// 模拟 @EventListener('entity:moved') 装饰器
|
||||
private initEventListeners() {
|
||||
// 这里应该通过装饰器自动完成
|
||||
scene.eventSystem?.on('entity:moved', (data) => {
|
||||
this.onEntityMoved(data);
|
||||
});
|
||||
}
|
||||
|
||||
public onEntityMoved(data: any) {
|
||||
this.receivedEvents.push(data);
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
// 处理实体移动并发射事件
|
||||
for (const entity of entities) {
|
||||
const pos = entity.getComponent(PositionComponent)!;
|
||||
// 模拟移动
|
||||
pos.x += 1;
|
||||
|
||||
// 发射移动事件
|
||||
scene.eventSystem?.emit('entity:moved', {
|
||||
entityId: entity.id,
|
||||
position: { x: pos.x, y: pos.y }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test(' 事件装饰器系统的初始化时序', () => {
|
||||
// 先创建实体
|
||||
const entity = scene.createEntity("EventTestEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
|
||||
// 然后添加使用事件装饰器的系统
|
||||
const eventSystem = new EventDecoratedSystem();
|
||||
scene.addEntityProcessor(eventSystem);
|
||||
|
||||
// 验证系统正确识别了实体
|
||||
expect(eventSystem.entities.length).toBe(1);
|
||||
|
||||
// 运行更新,应该触发事件
|
||||
scene.update();
|
||||
|
||||
// 关键测试:事件装饰器是否正确工作
|
||||
console.log("接收到的事件数量:", eventSystem.receivedEvents.length);
|
||||
expect(eventSystem.receivedEvents.length).toBe(1);
|
||||
|
||||
// 验证事件数据
|
||||
const event = eventSystem.receivedEvents[0];
|
||||
expect(event.entityId).toBe(entity.id);
|
||||
expect(event.position.x).toBe(1); // 移动后的位置
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 清理场景
|
||||
scene.destroyAllEntities();
|
||||
|
||||
// 手动清理系统
|
||||
const processors = [...scene.entityProcessors.processors];
|
||||
processors.forEach(processor => scene.removeEntityProcessor(processor));
|
||||
});
|
||||
});
|
||||
259
packages/core/tests/ECS/Entity.performance.test.ts
Normal file
259
packages/core/tests/ECS/Entity.performance.test.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import { Entity } from '../../src/ECS/Entity';
|
||||
import { Component } from '../../src/ECS/Component';
|
||||
|
||||
// 测试组件类
|
||||
class PerfTestComponent1 extends Component {
|
||||
public value: number = 1;
|
||||
}
|
||||
|
||||
class PerfTestComponent2 extends Component {
|
||||
public value: number = 2;
|
||||
}
|
||||
|
||||
class PerfTestComponent3 extends Component {
|
||||
public value: number = 3;
|
||||
}
|
||||
|
||||
class PerfTestComponent4 extends Component {
|
||||
public value: number = 4;
|
||||
}
|
||||
|
||||
class PerfTestComponent5 extends Component {
|
||||
public value: number = 5;
|
||||
}
|
||||
|
||||
class PerfTestComponent6 extends Component {
|
||||
public value: number = 6;
|
||||
}
|
||||
|
||||
class PerfTestComponent7 extends Component {
|
||||
public value: number = 7;
|
||||
}
|
||||
|
||||
class PerfTestComponent8 extends Component {
|
||||
public value: number = 8;
|
||||
}
|
||||
|
||||
describe('Entity - 性能测试', () => {
|
||||
|
||||
describe('典型游戏实体性能测试', () => {
|
||||
test('3-5个组件的实体性能测试', () => {
|
||||
const entity = new Entity('TypicalEntity', 1);
|
||||
|
||||
// 添加典型游戏实体的组件数量(3-5个)
|
||||
entity.addComponent(new PerfTestComponent1());
|
||||
entity.addComponent(new PerfTestComponent2());
|
||||
entity.addComponent(new PerfTestComponent3());
|
||||
entity.addComponent(new PerfTestComponent4());
|
||||
entity.addComponent(new PerfTestComponent5());
|
||||
|
||||
const iterations = 10000;
|
||||
const startTime = performance.now();
|
||||
|
||||
// 模拟典型的组件访问模式
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
entity.getComponent(PerfTestComponent1);
|
||||
entity.getComponent(PerfTestComponent3);
|
||||
entity.getComponent(PerfTestComponent5);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
console.log(`典型实体(5组件) ${iterations * 3}次组件获取耗时: ${duration.toFixed(2)}ms`);
|
||||
expect(duration).toBeLessThan(150);
|
||||
});
|
||||
|
||||
test('内存使用优化验证', () => {
|
||||
const entities: Entity[] = [];
|
||||
const entityCount = 1000;
|
||||
|
||||
// 创建大量实体,每个实体有少量组件
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const entity = new Entity(`Entity_${i}`, i);
|
||||
entity.addComponent(new PerfTestComponent1());
|
||||
entity.addComponent(new PerfTestComponent2());
|
||||
entity.addComponent(new PerfTestComponent3());
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
// 测试批量组件访问性能
|
||||
const startTime = performance.now();
|
||||
|
||||
for (const entity of entities) {
|
||||
entity.getComponent(PerfTestComponent1);
|
||||
entity.getComponent(PerfTestComponent2);
|
||||
entity.getComponent(PerfTestComponent3);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
console.log(`${entityCount}个实体,每个3个组件,总计${entityCount * 3}次组件获取耗时: ${duration.toFixed(2)}ms`);
|
||||
|
||||
expect(duration).toBeLessThan(100);
|
||||
});
|
||||
|
||||
test('组件添加和移除性能测试', () => {
|
||||
const entity = new Entity('TestEntity', 1);
|
||||
const iterations = 1000;
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
// 添加组件
|
||||
const comp1 = entity.addComponent(new PerfTestComponent1());
|
||||
const comp2 = entity.addComponent(new PerfTestComponent2());
|
||||
const comp3 = entity.addComponent(new PerfTestComponent3());
|
||||
|
||||
// 获取组件
|
||||
entity.getComponent(PerfTestComponent1);
|
||||
entity.getComponent(PerfTestComponent2);
|
||||
entity.getComponent(PerfTestComponent3);
|
||||
|
||||
// 移除组件
|
||||
entity.removeComponent(comp1);
|
||||
entity.removeComponent(comp2);
|
||||
entity.removeComponent(comp3);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
console.log(`${iterations}次组件添加-获取-移除循环耗时: ${duration.toFixed(2)}ms`);
|
||||
|
||||
expect(duration).toBeLessThan(70);
|
||||
});
|
||||
});
|
||||
|
||||
describe('极端情况性能测试', () => {
|
||||
test('单个组件高频访问性能', () => {
|
||||
const entity = new Entity('SingleComponentEntity', 1);
|
||||
entity.addComponent(new PerfTestComponent1());
|
||||
|
||||
const iterations = 50000;
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
entity.getComponent(PerfTestComponent1);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
console.log(`单组件${iterations}次高频访问耗时: ${duration.toFixed(2)}ms`);
|
||||
|
||||
expect(duration).toBeLessThan(150);
|
||||
});
|
||||
|
||||
test('多组件实体性能测试', () => {
|
||||
const entity = new Entity('MultiComponentEntity', 1);
|
||||
|
||||
// 添加8个组件(比典型情况多)
|
||||
entity.addComponent(new PerfTestComponent1());
|
||||
entity.addComponent(new PerfTestComponent2());
|
||||
entity.addComponent(new PerfTestComponent3());
|
||||
entity.addComponent(new PerfTestComponent4());
|
||||
entity.addComponent(new PerfTestComponent5());
|
||||
entity.addComponent(new PerfTestComponent6());
|
||||
entity.addComponent(new PerfTestComponent7());
|
||||
entity.addComponent(new PerfTestComponent8());
|
||||
|
||||
const iterations = 5000;
|
||||
const startTime = performance.now();
|
||||
|
||||
// 随机访问不同组件
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
entity.getComponent(PerfTestComponent1);
|
||||
entity.getComponent(PerfTestComponent4);
|
||||
entity.getComponent(PerfTestComponent7);
|
||||
entity.getComponent(PerfTestComponent2);
|
||||
entity.getComponent(PerfTestComponent8);
|
||||
entity.getComponent(PerfTestComponent3);
|
||||
entity.getComponent(PerfTestComponent6);
|
||||
entity.getComponent(PerfTestComponent5);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
console.log(`多组件实体(8组件) ${iterations * 8}次随机访问耗时: ${duration.toFixed(2)}ms`);
|
||||
|
||||
expect(duration).toBeLessThan(200);
|
||||
});
|
||||
|
||||
test('hasComponent性能测试', () => {
|
||||
const entity = new Entity('HasComponentTestEntity', 1);
|
||||
|
||||
entity.addComponent(new PerfTestComponent1());
|
||||
entity.addComponent(new PerfTestComponent3());
|
||||
entity.addComponent(new PerfTestComponent5());
|
||||
|
||||
const iterations = 25000; // 减少迭代次数以适应CI环境
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
entity.hasComponent(PerfTestComponent1); // 存在
|
||||
entity.hasComponent(PerfTestComponent2); // 不存在
|
||||
entity.hasComponent(PerfTestComponent3); // 存在
|
||||
entity.hasComponent(PerfTestComponent4); // 不存在
|
||||
entity.hasComponent(PerfTestComponent5); // 存在
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
console.log(`${iterations * 5}次hasComponent检查耗时: ${duration.toFixed(2)}ms`);
|
||||
|
||||
expect(duration).toBeLessThan(310);
|
||||
});
|
||||
});
|
||||
|
||||
describe('内存效率测试', () => {
|
||||
test('大量实体内存使用测试', () => {
|
||||
const entities: Entity[] = [];
|
||||
const entityCount = 5000;
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
// 创建大量实体,模拟真实游戏场景
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const entity = new Entity(`Entity_${i}`, i);
|
||||
|
||||
// 每个实体随机添加2-6个组件
|
||||
const componentCount = 2 + (i % 5);
|
||||
if (componentCount >= 1) entity.addComponent(new PerfTestComponent1());
|
||||
if (componentCount >= 2) entity.addComponent(new PerfTestComponent2());
|
||||
if (componentCount >= 3) entity.addComponent(new PerfTestComponent3());
|
||||
if (componentCount >= 4) entity.addComponent(new PerfTestComponent4());
|
||||
if (componentCount >= 5) entity.addComponent(new PerfTestComponent5());
|
||||
if (componentCount >= 6) entity.addComponent(new PerfTestComponent6());
|
||||
|
||||
entities.push(entity);
|
||||
}
|
||||
|
||||
const creationTime = performance.now() - startTime;
|
||||
|
||||
// 测试访问性能
|
||||
const accessStartTime = performance.now();
|
||||
|
||||
for (const entity of entities) {
|
||||
entity.getComponent(PerfTestComponent1);
|
||||
if (entity.hasComponent(PerfTestComponent3)) {
|
||||
entity.getComponent(PerfTestComponent3);
|
||||
}
|
||||
if (entity.hasComponent(PerfTestComponent5)) {
|
||||
entity.getComponent(PerfTestComponent5);
|
||||
}
|
||||
}
|
||||
|
||||
const accessTime = performance.now() - accessStartTime;
|
||||
|
||||
console.log(`创建${entityCount}个实体耗时: ${creationTime.toFixed(2)}ms`);
|
||||
console.log(`访问${entityCount}个实体的组件耗时: ${accessTime.toFixed(2)}ms`);
|
||||
|
||||
expect(creationTime).toBeLessThan(150);
|
||||
expect(accessTime).toBeLessThan(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
272
packages/core/tests/ECS/Entity.test.ts
Normal file
272
packages/core/tests/ECS/Entity.test.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { Entity } from '../../src/ECS/Entity';
|
||||
import { Component } from '../../src/ECS/Component';
|
||||
|
||||
// 测试组件类
|
||||
class TestPositionComponent extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class TestHealthComponent extends Component {
|
||||
public health: number = 100;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [health = 100] = args as [number?];
|
||||
this.health = health;
|
||||
}
|
||||
}
|
||||
|
||||
class TestVelocityComponent extends Component {
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class TestRenderComponent extends Component {
|
||||
public visible: boolean = true;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [visible = true] = args as [boolean?];
|
||||
this.visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
describe('Entity - 组件缓存优化测试', () => {
|
||||
let entity: Entity;
|
||||
|
||||
beforeEach(() => {
|
||||
// 创建新的实体
|
||||
entity = new Entity('TestEntity', 1);
|
||||
});
|
||||
|
||||
describe('基本功能测试', () => {
|
||||
test('应该能够创建实体', () => {
|
||||
expect(entity.name).toBe('TestEntity');
|
||||
expect(entity.id).toBe(1);
|
||||
expect(entity.components.length).toBe(0);
|
||||
});
|
||||
|
||||
test('应该能够添加组件', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
const addedComponent = entity.addComponent(position);
|
||||
|
||||
expect(addedComponent).toBe(position);
|
||||
expect(entity.components.length).toBe(1);
|
||||
expect(entity.components[0]).toBe(position);
|
||||
expect(position.entity).toBe(entity);
|
||||
});
|
||||
|
||||
test('应该能够获取组件', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
entity.addComponent(position);
|
||||
|
||||
const retrieved = entity.getComponent(TestPositionComponent);
|
||||
expect(retrieved).toBe(position);
|
||||
expect(retrieved?.x).toBe(10);
|
||||
expect(retrieved?.y).toBe(20);
|
||||
});
|
||||
|
||||
test('应该能够检查组件存在性', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
entity.addComponent(position);
|
||||
|
||||
expect(entity.hasComponent(TestPositionComponent)).toBe(true);
|
||||
expect(entity.hasComponent(TestHealthComponent)).toBe(false);
|
||||
});
|
||||
|
||||
test('应该能够移除组件', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
entity.addComponent(position);
|
||||
|
||||
entity.removeComponent(position);
|
||||
expect(entity.components.length).toBe(0);
|
||||
expect(entity.hasComponent(TestPositionComponent)).toBe(false);
|
||||
expect(position.entity).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('多组件管理测试', () => {
|
||||
test('应该能够管理多个不同类型的组件', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
const health = new TestHealthComponent(150);
|
||||
const velocity = new TestVelocityComponent(5, -3);
|
||||
|
||||
entity.addComponent(position);
|
||||
entity.addComponent(health);
|
||||
entity.addComponent(velocity);
|
||||
|
||||
expect(entity.components.length).toBe(3);
|
||||
expect(entity.hasComponent(TestPositionComponent)).toBe(true);
|
||||
expect(entity.hasComponent(TestHealthComponent)).toBe(true);
|
||||
expect(entity.hasComponent(TestVelocityComponent)).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够正确获取多个组件', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
const health = new TestHealthComponent(150);
|
||||
const velocity = new TestVelocityComponent(5, -3);
|
||||
|
||||
entity.addComponent(position);
|
||||
entity.addComponent(health);
|
||||
entity.addComponent(velocity);
|
||||
|
||||
const retrievedPosition = entity.getComponent(TestPositionComponent);
|
||||
const retrievedHealth = entity.getComponent(TestHealthComponent);
|
||||
const retrievedVelocity = entity.getComponent(TestVelocityComponent);
|
||||
|
||||
expect(retrievedPosition).toBe(position);
|
||||
expect(retrievedHealth).toBe(health);
|
||||
expect(retrievedVelocity).toBe(velocity);
|
||||
});
|
||||
|
||||
test('应该能够批量添加组件', () => {
|
||||
const components = [
|
||||
new TestPositionComponent(10, 20),
|
||||
new TestHealthComponent(150),
|
||||
new TestVelocityComponent(5, -3)
|
||||
];
|
||||
|
||||
const addedComponents = entity.addComponents(components);
|
||||
|
||||
expect(addedComponents.length).toBe(3);
|
||||
expect(entity.components.length).toBe(3);
|
||||
expect(addedComponents[0]).toBe(components[0]);
|
||||
expect(addedComponents[1]).toBe(components[1]);
|
||||
expect(addedComponents[2]).toBe(components[2]);
|
||||
});
|
||||
|
||||
test('应该能够移除所有组件', () => {
|
||||
entity.addComponent(new TestPositionComponent(10, 20));
|
||||
entity.addComponent(new TestHealthComponent(150));
|
||||
entity.addComponent(new TestVelocityComponent(5, -3));
|
||||
|
||||
entity.removeAllComponents();
|
||||
|
||||
expect(entity.components.length).toBe(0);
|
||||
expect(entity.hasComponent(TestPositionComponent)).toBe(false);
|
||||
expect(entity.hasComponent(TestHealthComponent)).toBe(false);
|
||||
expect(entity.hasComponent(TestVelocityComponent)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能优化验证', () => {
|
||||
test('位掩码应该正确工作', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
const health = new TestHealthComponent(150);
|
||||
|
||||
entity.addComponent(position);
|
||||
entity.addComponent(health);
|
||||
|
||||
// 位掩码应该反映组件的存在
|
||||
expect(entity.hasComponent(TestPositionComponent)).toBe(true);
|
||||
expect(entity.hasComponent(TestHealthComponent)).toBe(true);
|
||||
expect(entity.hasComponent(TestVelocityComponent)).toBe(false);
|
||||
});
|
||||
|
||||
test('索引映射应该正确维护', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
const health = new TestHealthComponent(150);
|
||||
const velocity = new TestVelocityComponent(5, -3);
|
||||
|
||||
entity.addComponent(position);
|
||||
entity.addComponent(health);
|
||||
entity.addComponent(velocity);
|
||||
|
||||
// 获取组件应该通过索引映射快速完成
|
||||
const retrievedPosition = entity.getComponent(TestPositionComponent);
|
||||
const retrievedHealth = entity.getComponent(TestHealthComponent);
|
||||
const retrievedVelocity = entity.getComponent(TestVelocityComponent);
|
||||
|
||||
expect(retrievedPosition).toBe(position);
|
||||
expect(retrievedHealth).toBe(health);
|
||||
expect(retrievedVelocity).toBe(velocity);
|
||||
});
|
||||
|
||||
test('组件获取性能应该良好', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
const health = new TestHealthComponent(150);
|
||||
const velocity = new TestVelocityComponent(5, -3);
|
||||
const render = new TestRenderComponent(true);
|
||||
|
||||
entity.addComponent(position);
|
||||
entity.addComponent(health);
|
||||
entity.addComponent(velocity);
|
||||
entity.addComponent(render);
|
||||
|
||||
// 测试大量获取操作的性能
|
||||
const iterations = 1000;
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
entity.getComponent(TestPositionComponent);
|
||||
entity.getComponent(TestHealthComponent);
|
||||
entity.getComponent(TestVelocityComponent);
|
||||
entity.getComponent(TestRenderComponent);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
// 1000次 * 4个组件 = 4000次获取操作应该在合理时间内完成
|
||||
expect(duration).toBeLessThan(100); // 应该在100ms内完成
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况测试', () => {
|
||||
test('获取不存在的组件应该返回null', () => {
|
||||
const result = entity.getComponent(TestPositionComponent);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('不应该允许添加重复类型的组件', () => {
|
||||
const position1 = new TestPositionComponent(10, 20);
|
||||
const position2 = new TestPositionComponent(30, 40);
|
||||
|
||||
entity.addComponent(position1);
|
||||
|
||||
expect(() => {
|
||||
entity.addComponent(position2);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('移除不存在的组件应该安全处理', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
|
||||
expect(() => {
|
||||
entity.removeComponent(position);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('调试信息应该正确反映实体状态', () => {
|
||||
const position = new TestPositionComponent(10, 20);
|
||||
const health = new TestHealthComponent(150);
|
||||
|
||||
entity.addComponent(position);
|
||||
entity.addComponent(health);
|
||||
|
||||
const debugInfo = entity.getDebugInfo();
|
||||
|
||||
expect(debugInfo.name).toBe('TestEntity');
|
||||
expect(debugInfo.id).toBe(1);
|
||||
expect(debugInfo.componentCount).toBe(2);
|
||||
expect(debugInfo.componentTypes).toContain('TestPositionComponent');
|
||||
expect(debugInfo.componentTypes).toContain('TestHealthComponent');
|
||||
expect(debugInfo.indexMappingSize).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
572
packages/core/tests/ECS/Scene.test.ts
Normal file
572
packages/core/tests/ECS/Scene.test.ts
Normal file
@@ -0,0 +1,572 @@
|
||||
import { Scene } from '../../src/ECS/Scene';
|
||||
import { Entity } from '../../src/ECS/Entity';
|
||||
import { Component } from '../../src/ECS/Component';
|
||||
import { EntitySystem } from '../../src/ECS/Systems/EntitySystem';
|
||||
import { Matcher } from '../../src/ECS/Utils/Matcher';
|
||||
|
||||
// 测试组件
|
||||
class PositionComponent extends Component {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class VelocityComponent extends Component {
|
||||
public vx: number;
|
||||
public vy: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class HealthComponent extends Component {
|
||||
public health: number;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [health = 100] = args as [number?];
|
||||
this.health = health;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderComponent extends Component {
|
||||
public visible: boolean;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [visible = true] = args as [boolean?];
|
||||
this.visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
// 测试系统
|
||||
class MovementSystem extends EntitySystem {
|
||||
public processCallCount = 0;
|
||||
public lastProcessedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processCallCount++;
|
||||
this.lastProcessedEntities = [...entities];
|
||||
|
||||
for (const entity of entities) {
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
const velocity = entity.getComponent(VelocityComponent);
|
||||
|
||||
if (position && velocity) {
|
||||
position.x += velocity.vx;
|
||||
position.y += velocity.vy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RenderSystem extends EntitySystem {
|
||||
public processCallCount = 0;
|
||||
public lastProcessedEntities: Entity[] = [];
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent, RenderComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processCallCount++;
|
||||
this.lastProcessedEntities = [...entities];
|
||||
}
|
||||
}
|
||||
|
||||
// 测试场景
|
||||
class TestScene extends Scene {
|
||||
public initializeCalled = false;
|
||||
public beginCalled = false;
|
||||
public endCalled = false;
|
||||
public updateCallCount = 0;
|
||||
|
||||
public override initialize(): void {
|
||||
this.initializeCalled = true;
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
public override begin(): void {
|
||||
this.beginCalled = true;
|
||||
super.begin();
|
||||
}
|
||||
|
||||
public override end(): void {
|
||||
this.endCalled = true;
|
||||
super.end();
|
||||
}
|
||||
|
||||
public override update(): void {
|
||||
this.updateCallCount++;
|
||||
super.update();
|
||||
}
|
||||
}
|
||||
|
||||
describe('Scene - 场景管理系统测试', () => {
|
||||
let scene: Scene;
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene();
|
||||
});
|
||||
|
||||
describe('基本功能测试', () => {
|
||||
test('应该能够创建场景', () => {
|
||||
expect(scene).toBeDefined();
|
||||
expect(scene).toBeInstanceOf(Scene);
|
||||
expect(scene.name).toBe("");
|
||||
expect(scene.entities).toBeDefined();
|
||||
expect(scene.entityProcessors).toBeDefined();
|
||||
expect(scene.identifierPool).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够设置场景名称', () => {
|
||||
scene.name = "TestScene";
|
||||
expect(scene.name).toBe("TestScene");
|
||||
});
|
||||
|
||||
test('场景应该有正确的初始状态', () => {
|
||||
expect(scene.entities.count).toBe(0);
|
||||
expect(scene.entityProcessors.count).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('实体管理', () => {
|
||||
test('应该能够创建实体', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
|
||||
expect(entity).toBeDefined();
|
||||
expect(entity.name).toBe("TestEntity");
|
||||
expect(entity.id).toBeGreaterThan(0);
|
||||
expect(scene.entities.count).toBe(1);
|
||||
});
|
||||
|
||||
test('应该能够批量创建实体', () => {
|
||||
const entities = scene.createEntities(5, "Entity");
|
||||
|
||||
expect(entities.length).toBe(5);
|
||||
expect(scene.entities.count).toBe(5);
|
||||
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
expect(entities[i].name).toBe(`Entity_${i}`);
|
||||
expect(entities[i].id).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('应该能够通过ID查找实体', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
const found = scene.findEntityById(entity.id);
|
||||
|
||||
expect(found).toBe(entity);
|
||||
});
|
||||
|
||||
test('查找不存在的实体应该返回null', () => {
|
||||
const found = scene.findEntityById(999999);
|
||||
expect(found).toBeNull();
|
||||
});
|
||||
|
||||
test('应该能够销毁实体', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
const entityId = entity.id;
|
||||
|
||||
scene.entities.remove(entity);
|
||||
|
||||
expect(scene.entities.count).toBe(0);
|
||||
expect(scene.findEntityById(entityId)).toBeNull();
|
||||
});
|
||||
|
||||
test('应该能够通过ID销毁实体', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
const entityId = entity.id;
|
||||
|
||||
const entityToRemove = scene.findEntityById(entityId)!;
|
||||
scene.entities.remove(entityToRemove);
|
||||
|
||||
expect(scene.entities.count).toBe(0);
|
||||
expect(scene.findEntityById(entityId)).toBeNull();
|
||||
});
|
||||
|
||||
test('销毁不存在的实体应该安全处理', () => {
|
||||
expect(() => {
|
||||
// Scene doesn't have destroyEntity method
|
||||
expect(scene.findEntityById(999999)).toBeNull();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('应该能够销毁所有实体', () => {
|
||||
scene.createEntities(10, "Entity");
|
||||
expect(scene.entities.count).toBe(10);
|
||||
|
||||
scene.destroyAllEntities();
|
||||
|
||||
expect(scene.entities.count).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('实体系统管理', () => {
|
||||
let movementSystem: MovementSystem;
|
||||
let renderSystem: RenderSystem;
|
||||
|
||||
beforeEach(() => {
|
||||
movementSystem = new MovementSystem();
|
||||
renderSystem = new RenderSystem();
|
||||
});
|
||||
|
||||
test('应该能够添加实体系统', () => {
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
|
||||
expect(scene.entityProcessors.count).toBe(1);
|
||||
expect(movementSystem.scene).toBe(scene);
|
||||
});
|
||||
|
||||
test('应该能够移除实体系统', () => {
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.removeEntityProcessor(movementSystem);
|
||||
|
||||
expect(scene.entityProcessors.count).toBe(0);
|
||||
expect(movementSystem.scene).toBeNull();
|
||||
});
|
||||
|
||||
test('移除不存在的系统应该安全处理', () => {
|
||||
expect(() => {
|
||||
scene.removeEntityProcessor(movementSystem);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('应该能够管理多个实体系统', () => {
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(renderSystem);
|
||||
|
||||
expect(scene.entityProcessors.count).toBe(2);
|
||||
});
|
||||
|
||||
test('系统应该按更新顺序执行', () => {
|
||||
movementSystem.updateOrder = 1;
|
||||
renderSystem.updateOrder = 0;
|
||||
|
||||
scene.addEntityProcessor(movementSystem);
|
||||
scene.addEntityProcessor(renderSystem);
|
||||
|
||||
// 创建测试实体
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
entity.addComponent(new RenderComponent(true));
|
||||
|
||||
scene.update();
|
||||
|
||||
// RenderSystem应该先执行(updateOrder = 0)
|
||||
// MovementSystem应该后执行(updateOrder = 1)
|
||||
expect(renderSystem.processCallCount).toBe(1);
|
||||
expect(movementSystem.processCallCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('组件查询系统', () => {
|
||||
beforeEach(() => {
|
||||
// 创建测试实体
|
||||
const entity1 = scene.createEntity("Entity1");
|
||||
entity1.addComponent(new PositionComponent(10, 20));
|
||||
entity1.addComponent(new VelocityComponent(1, 0));
|
||||
|
||||
const entity2 = scene.createEntity("Entity2");
|
||||
entity2.addComponent(new PositionComponent(30, 40));
|
||||
entity2.addComponent(new HealthComponent(80));
|
||||
|
||||
const entity3 = scene.createEntity("Entity3");
|
||||
entity3.addComponent(new VelocityComponent(0, 1));
|
||||
entity3.addComponent(new HealthComponent(120));
|
||||
});
|
||||
|
||||
test('应该能够查询具有特定组件的实体', () => {
|
||||
const result = scene.querySystem.queryAll(PositionComponent);
|
||||
|
||||
expect(result.entities.length).toBe(2);
|
||||
expect(result.entities[0].name).toBe("Entity1");
|
||||
expect(result.entities[1].name).toBe("Entity2");
|
||||
});
|
||||
|
||||
test('应该能够查询具有多个组件的实体', () => {
|
||||
const result = scene.querySystem.queryAll(PositionComponent, VelocityComponent);
|
||||
|
||||
expect(result.entities.length).toBe(1);
|
||||
expect(result.entities[0].name).toBe("Entity1");
|
||||
});
|
||||
|
||||
test('查询不存在的组件应该返回空结果', () => {
|
||||
const result = scene.querySystem.queryAll(RenderComponent);
|
||||
|
||||
expect(result.entities.length).toBe(0);
|
||||
});
|
||||
|
||||
test('查询系统应该支持缓存', () => {
|
||||
// 第一次查询
|
||||
const result1 = scene.querySystem.queryAll(PositionComponent);
|
||||
|
||||
// 第二次查询(应该使用缓存)
|
||||
const result2 = scene.querySystem.queryAll(PositionComponent);
|
||||
|
||||
// 实体数组应该相同,并且第二次查询应该来自缓存
|
||||
expect(result1.entities).toEqual(result2.entities);
|
||||
expect(result2.fromCache).toBe(true);
|
||||
});
|
||||
|
||||
test('组件变化应该更新查询缓存', () => {
|
||||
const result1 = scene.querySystem.queryAll(PositionComponent);
|
||||
expect(result1.entities.length).toBe(2);
|
||||
|
||||
// 添加新实体
|
||||
const entity4 = scene.createEntity("Entity4");
|
||||
entity4.addComponent(new PositionComponent(50, 60));
|
||||
|
||||
const result2 = scene.querySystem.queryAll(PositionComponent);
|
||||
expect(result2.entities.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('事件系统', () => {
|
||||
test('场景应该有事件系统', () => {
|
||||
expect(scene.eventSystem).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该能够监听实体事件', () => {
|
||||
let entityCreatedEvent: any = null;
|
||||
|
||||
scene.eventSystem.on('entity:created', (data: any) => {
|
||||
entityCreatedEvent = data;
|
||||
});
|
||||
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
|
||||
expect(entityCreatedEvent).toBeDefined();
|
||||
expect(entityCreatedEvent.entityName).toBe("TestEntity");
|
||||
});
|
||||
|
||||
test('应该能够监听组件事件', () => {
|
||||
let componentAddedEvent: any = null;
|
||||
|
||||
scene.eventSystem.on('component:added', (data: any) => {
|
||||
componentAddedEvent = data;
|
||||
});
|
||||
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new PositionComponent(10, 20));
|
||||
|
||||
expect(componentAddedEvent).toBeDefined();
|
||||
expect(componentAddedEvent.componentType).toBe('PositionComponent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('场景生命周期管理', () => {
|
||||
let testScene: TestScene;
|
||||
|
||||
beforeEach(() => {
|
||||
testScene = new TestScene();
|
||||
});
|
||||
|
||||
test('应该能够初始化场景', () => {
|
||||
testScene.initialize();
|
||||
|
||||
expect(testScene.initializeCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够开始场景', () => {
|
||||
testScene.begin();
|
||||
|
||||
expect(testScene.beginCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够结束场景', () => {
|
||||
testScene.end();
|
||||
|
||||
expect(testScene.endCalled).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能够更新场景', () => {
|
||||
const movementSystem = new MovementSystem();
|
||||
testScene.addEntityProcessor(movementSystem);
|
||||
|
||||
// 创建测试实体
|
||||
const entity = testScene.createEntity("TestEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
|
||||
testScene.update();
|
||||
|
||||
expect(testScene.updateCallCount).toBe(1);
|
||||
expect(movementSystem.processCallCount).toBe(1);
|
||||
|
||||
// 验证移动系统是否正确处理了实体
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
expect(position?.x).toBe(1);
|
||||
expect(position?.y).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('统计和调试信息', () => {
|
||||
test('应该能够获取场景统计信息', () => {
|
||||
// 创建一些实体和系统
|
||||
scene.createEntities(5, "Entity");
|
||||
scene.addEntityProcessor(new MovementSystem());
|
||||
scene.addEntityProcessor(new RenderSystem());
|
||||
|
||||
const stats = scene.getStats();
|
||||
|
||||
expect(stats.entityCount).toBe(5);
|
||||
expect(stats.processorCount).toBe(2);
|
||||
});
|
||||
|
||||
test('空场景的统计信息应该正确', () => {
|
||||
const stats = scene.getStats();
|
||||
|
||||
expect(stats.entityCount).toBe(0);
|
||||
expect(stats.processorCount).toBe(0);
|
||||
});
|
||||
|
||||
test('查询系统应该有统计信息', () => {
|
||||
// 执行一些查询以产生统计数据
|
||||
scene.querySystem.queryAll(PositionComponent);
|
||||
scene.querySystem.queryAll(VelocityComponent);
|
||||
|
||||
const stats = scene.querySystem.getStats();
|
||||
|
||||
expect(stats.queryStats.totalQueries).toBeGreaterThan(0);
|
||||
expect(parseFloat(stats.cacheStats.hitRate)).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('内存管理和性能', () => {
|
||||
test('销毁大量实体应该正确清理内存', () => {
|
||||
const entityCount = 1000;
|
||||
const entities = scene.createEntities(entityCount, "Entity");
|
||||
|
||||
// 为每个实体添加组件
|
||||
entities.forEach(entity => {
|
||||
entity.addComponent(new PositionComponent(Math.random() * 100, Math.random() * 100));
|
||||
entity.addComponent(new VelocityComponent(Math.random() * 5, Math.random() * 5));
|
||||
});
|
||||
|
||||
expect(scene.entities.count).toBe(entityCount);
|
||||
|
||||
// 销毁所有实体
|
||||
scene.destroyAllEntities();
|
||||
|
||||
expect(scene.entities.count).toBe(0);
|
||||
|
||||
// 查询应该返回空结果
|
||||
const positionResult = scene.querySystem.queryAll(PositionComponent);
|
||||
const velocityResult = scene.querySystem.queryAll(VelocityComponent);
|
||||
|
||||
expect(positionResult.entities.length).toBe(0);
|
||||
expect(velocityResult.entities.length).toBe(0);
|
||||
});
|
||||
|
||||
test('大量实体的创建和查询性能应该可接受', () => {
|
||||
const entityCount = 5000;
|
||||
const startTime = performance.now();
|
||||
|
||||
// 创建大量实体
|
||||
const entities = scene.createEntities(entityCount, "Entity");
|
||||
|
||||
// 为每个实体添加组件
|
||||
entities.forEach((entity, index) => {
|
||||
entity.addComponent(new PositionComponent(index, index));
|
||||
if (index % 2 === 0) {
|
||||
entity.addComponent(new VelocityComponent(1, 1));
|
||||
}
|
||||
if (index % 3 === 0) {
|
||||
entity.addComponent(new HealthComponent(100));
|
||||
}
|
||||
});
|
||||
|
||||
const creationTime = performance.now() - startTime;
|
||||
|
||||
// 测试查询性能
|
||||
const queryStartTime = performance.now();
|
||||
|
||||
const positionResult = scene.querySystem.queryAll(PositionComponent);
|
||||
const velocityResult = scene.querySystem.queryAll(VelocityComponent);
|
||||
const healthResult = scene.querySystem.queryAll(HealthComponent);
|
||||
|
||||
const queryTime = performance.now() - queryStartTime;
|
||||
|
||||
expect(positionResult.entities.length).toBe(entityCount);
|
||||
expect(velocityResult.entities.length).toBe(entityCount / 2);
|
||||
expect(healthResult.entities.length).toBe(Math.floor(entityCount / 3) + 1);
|
||||
|
||||
// 性能断言(这些值可能需要根据实际环境调整)
|
||||
expect(creationTime).toBeLessThan(2000); // 创建应该在2秒内完成
|
||||
expect(queryTime).toBeLessThan(100); // 查询应该在100ms内完成
|
||||
|
||||
console.log(`创建${entityCount}个实体耗时: ${creationTime.toFixed(2)}ms`);
|
||||
console.log(`查询操作耗时: ${queryTime.toFixed(2)}ms`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('错误处理和边界情况', () => {
|
||||
test('重复添加同一个系统应该安全处理', () => {
|
||||
const system = new MovementSystem();
|
||||
|
||||
scene.addEntityProcessor(system);
|
||||
scene.addEntityProcessor(system); // 重复添加
|
||||
|
||||
expect(scene.entityProcessors.count).toBe(1);
|
||||
});
|
||||
|
||||
test('系统处理过程中的异常应该被正确处理', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
class ErrorSystem extends EntitySystem {
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
throw new Error("Test system error");
|
||||
}
|
||||
}
|
||||
|
||||
const errorSystem = new ErrorSystem();
|
||||
scene.addEntityProcessor(errorSystem);
|
||||
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
|
||||
// 更新不应该抛出异常
|
||||
expect(() => {
|
||||
scene.update();
|
||||
}).not.toThrow();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('空场景的更新应该安全', () => {
|
||||
expect(() => {
|
||||
scene.update();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('对已销毁实体的操作应该安全处理', () => {
|
||||
const entity = scene.createEntity("TestEntity");
|
||||
scene.entities.remove(entity);
|
||||
|
||||
// 对已销毁实体的操作应该安全
|
||||
expect(() => {
|
||||
entity.addComponent(new PositionComponent(0, 0));
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
383
packages/core/tests/ECS/Systems/SystemTypes.test.ts
Normal file
383
packages/core/tests/ECS/Systems/SystemTypes.test.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
import { PassiveSystem } from '../../../src/ECS/Systems/PassiveSystem';
|
||||
import { IntervalSystem } from '../../../src/ECS/Systems/IntervalSystem';
|
||||
import { ProcessingSystem } from '../../../src/ECS/Systems/ProcessingSystem';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { ComponentRegistry } from '../../../src/ECS/Core/ComponentStorage';
|
||||
import { Time } from '../../../src/Utils/Time';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
|
||||
// 测试组件
|
||||
class TestComponent extends Component {
|
||||
public value: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [value = 0] = args as [number?];
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class AnotherComponent extends Component {
|
||||
public name: string = 'test';
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [name = 'test'] = args as [string?];
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
// 具体的被动系统实现
|
||||
class ConcretePassiveSystem extends PassiveSystem {
|
||||
public processCallCount = 0;
|
||||
|
||||
constructor() {
|
||||
super(Matcher.all(TestComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processCallCount++;
|
||||
// 被动系统的process方法会被调用,但不做任何处理
|
||||
super.process(entities);
|
||||
}
|
||||
}
|
||||
|
||||
// 具体的间隔系统实现
|
||||
class ConcreteIntervalSystem extends IntervalSystem {
|
||||
public processCallCount = 0;
|
||||
public lastDelta = 0;
|
||||
|
||||
constructor(interval: number) {
|
||||
super(interval, Matcher.all(TestComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processCallCount++;
|
||||
this.lastDelta = this.getIntervalDelta();
|
||||
}
|
||||
}
|
||||
|
||||
// 具体的处理系统实现
|
||||
class ConcreteProcessingSystem extends ProcessingSystem {
|
||||
public processSystemCallCount = 0;
|
||||
public processCallCount = 0;
|
||||
|
||||
constructor() {
|
||||
super(Matcher.all(TestComponent));
|
||||
}
|
||||
|
||||
public processSystem(): void {
|
||||
this.processSystemCallCount++;
|
||||
}
|
||||
|
||||
protected override process(entities: Entity[]): void {
|
||||
this.processCallCount++;
|
||||
super.process(entities);
|
||||
}
|
||||
}
|
||||
|
||||
describe('System Types - 系统类型测试', () => {
|
||||
let entity: Entity;
|
||||
|
||||
beforeEach(() => {
|
||||
entity = new Entity('TestEntity', 1);
|
||||
// 重置时间系统
|
||||
Time.update(0.016);
|
||||
// 注册测试组件类型
|
||||
ComponentRegistry.register(TestComponent);
|
||||
ComponentRegistry.register(AnotherComponent);
|
||||
});
|
||||
|
||||
describe('PassiveSystem - 被动系统', () => {
|
||||
let passiveSystem: ConcretePassiveSystem;
|
||||
|
||||
beforeEach(() => {
|
||||
passiveSystem = new ConcretePassiveSystem();
|
||||
});
|
||||
|
||||
test('应该能够创建被动系统', () => {
|
||||
expect(passiveSystem).toBeInstanceOf(PassiveSystem);
|
||||
expect(passiveSystem).toBeInstanceOf(ConcretePassiveSystem);
|
||||
});
|
||||
|
||||
|
||||
test('process方法不应该做任何处理', () => {
|
||||
const entities = [entity];
|
||||
const initialProcessCount = passiveSystem.processCallCount;
|
||||
|
||||
passiveSystem.update();
|
||||
|
||||
// 虽然process被调用了,但被动系统不做任何实际处理
|
||||
expect(passiveSystem.processCallCount).toBe(initialProcessCount + 1);
|
||||
});
|
||||
|
||||
test('应该能够动态查询匹配的实体', () => {
|
||||
// 现在使用动态查询,不需要手动add/remove
|
||||
// 先检查没有匹配的实体
|
||||
expect(passiveSystem.entities.length).toBe(0);
|
||||
|
||||
// 添加匹配的组件后,系统应该能查询到实体
|
||||
entity.addComponent(new TestComponent(100));
|
||||
|
||||
// 需要设置场景和QuerySystem才能进行动态查询
|
||||
// 这里我们只测试entities getter的存在性
|
||||
expect(passiveSystem.entities).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('IntervalSystem - 间隔系统', () => {
|
||||
let intervalSystem: ConcreteIntervalSystem;
|
||||
const testInterval = 0.1; // 100ms
|
||||
|
||||
beforeEach(() => {
|
||||
intervalSystem = new ConcreteIntervalSystem(testInterval);
|
||||
});
|
||||
|
||||
test('应该能够创建间隔系统', () => {
|
||||
expect(intervalSystem).toBeInstanceOf(IntervalSystem);
|
||||
expect(intervalSystem).toBeInstanceOf(ConcreteIntervalSystem);
|
||||
});
|
||||
|
||||
test('在间隔时间内不应该处理', () => {
|
||||
const initialProcessCount = intervalSystem.processCallCount;
|
||||
|
||||
// 模拟时间更新,但不足以触发间隔
|
||||
Time.update(testInterval / 2);
|
||||
intervalSystem.update();
|
||||
|
||||
expect(intervalSystem.processCallCount).toBe(initialProcessCount);
|
||||
});
|
||||
|
||||
test('达到间隔时间时应该处理', () => {
|
||||
const initialProcessCount = intervalSystem.processCallCount;
|
||||
|
||||
// 模拟时间更新,刚好达到间隔
|
||||
Time.update(testInterval);
|
||||
intervalSystem.update();
|
||||
|
||||
expect(intervalSystem.processCallCount).toBe(initialProcessCount + 1);
|
||||
});
|
||||
|
||||
test('超过间隔时间应该处理并记录余数', () => {
|
||||
const initialProcessCount = intervalSystem.processCallCount;
|
||||
const overTime = testInterval + 0.02; // 超过20ms
|
||||
|
||||
Time.update(overTime);
|
||||
intervalSystem.update();
|
||||
|
||||
expect(intervalSystem.processCallCount).toBe(initialProcessCount + 1);
|
||||
expect(intervalSystem.lastDelta).toBe(testInterval + 0.02);
|
||||
});
|
||||
|
||||
test('多次累积应该正确触发', () => {
|
||||
let processCount = intervalSystem.processCallCount;
|
||||
|
||||
// 多次小的时间增量
|
||||
for (let i = 0; i < 10; i++) {
|
||||
Time.update(testInterval / 5);
|
||||
intervalSystem.update();
|
||||
}
|
||||
|
||||
// 10 * (interval/5) = 2 * interval,应该触发2次
|
||||
expect(intervalSystem.processCallCount).toBeGreaterThanOrEqual(processCount + 1);
|
||||
});
|
||||
|
||||
test('getIntervalDelta应该返回正确的增量', () => {
|
||||
const overTime = testInterval + 0.03;
|
||||
|
||||
Time.update(overTime);
|
||||
intervalSystem.update();
|
||||
|
||||
expect(intervalSystem.lastDelta).toBe(testInterval + 0.03);
|
||||
});
|
||||
|
||||
test('重置后应该重新开始计时', () => {
|
||||
// 第一次触发
|
||||
Time.update(testInterval);
|
||||
intervalSystem.update();
|
||||
expect(intervalSystem.processCallCount).toBe(1);
|
||||
|
||||
// 再次触发需要等待完整间隔
|
||||
Time.update(testInterval / 2);
|
||||
intervalSystem.update();
|
||||
expect(intervalSystem.processCallCount).toBe(1);
|
||||
|
||||
Time.update(testInterval / 2);
|
||||
intervalSystem.update();
|
||||
expect(intervalSystem.processCallCount).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProcessingSystem - 处理系统', () => {
|
||||
let processingSystem: ConcreteProcessingSystem;
|
||||
|
||||
beforeEach(() => {
|
||||
processingSystem = new ConcreteProcessingSystem();
|
||||
});
|
||||
|
||||
test('应该能够创建处理系统', () => {
|
||||
expect(processingSystem).toBeInstanceOf(ProcessingSystem);
|
||||
expect(processingSystem).toBeInstanceOf(ConcreteProcessingSystem);
|
||||
});
|
||||
|
||||
test('process方法应该调用processSystem', () => {
|
||||
const initialProcessSystemCount = processingSystem.processSystemCallCount;
|
||||
const initialProcessCount = processingSystem.processCallCount;
|
||||
|
||||
processingSystem.update();
|
||||
|
||||
expect(processingSystem.processCallCount).toBe(initialProcessCount + 1);
|
||||
expect(processingSystem.processSystemCallCount).toBe(initialProcessSystemCount + 1);
|
||||
});
|
||||
|
||||
|
||||
test('每次更新都应该调用processSystem', () => {
|
||||
const initialCount = processingSystem.processSystemCallCount;
|
||||
|
||||
processingSystem.update();
|
||||
processingSystem.update();
|
||||
processingSystem.update();
|
||||
|
||||
expect(processingSystem.processSystemCallCount).toBe(initialCount + 3);
|
||||
});
|
||||
|
||||
test('应该能够动态查询多个实体', () => {
|
||||
// 现在使用动态查询,不需要手动add
|
||||
// 测试系统的基本功能
|
||||
const initialCount = processingSystem.processSystemCallCount;
|
||||
processingSystem.update();
|
||||
|
||||
// processSystem应该被调用,不管有多少实体
|
||||
expect(processingSystem.processSystemCallCount).toBe(initialCount + 1);
|
||||
|
||||
// 测试entities getter的存在性
|
||||
expect(processingSystem.entities).toBeDefined();
|
||||
expect(Array.isArray(processingSystem.entities)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('系统集成测试', () => {
|
||||
test('不同类型的系统应该都继承自EntitySystem', () => {
|
||||
const passive = new ConcretePassiveSystem();
|
||||
const interval = new ConcreteIntervalSystem(0.1);
|
||||
const processing = new ConcreteProcessingSystem();
|
||||
|
||||
expect(passive.matcher).toBeDefined();
|
||||
expect(interval.matcher).toBeDefined();
|
||||
expect(processing.matcher).toBeDefined();
|
||||
|
||||
expect(passive.entities).toBeDefined();
|
||||
expect(interval.entities).toBeDefined();
|
||||
expect(processing.entities).toBeDefined();
|
||||
|
||||
expect(passive.systemName).toBeDefined();
|
||||
expect(interval.systemName).toBeDefined();
|
||||
expect(processing.systemName).toBeDefined();
|
||||
});
|
||||
|
||||
test('系统应该能够正确匹配实体', () => {
|
||||
const passive = new ConcretePassiveSystem();
|
||||
const interval = new ConcreteIntervalSystem(0.1);
|
||||
const processing = new ConcreteProcessingSystem();
|
||||
|
||||
const matchingEntity = new Entity('Matching', 1);
|
||||
matchingEntity.addComponent(new TestComponent(100));
|
||||
|
||||
const nonMatchingEntity = new Entity('NonMatching', 2);
|
||||
nonMatchingEntity.addComponent(new AnotherComponent('test'));
|
||||
|
||||
// 所有系统都应该匹配TestComponent
|
||||
// 直接检查实体是否有需要的组件
|
||||
expect(matchingEntity.hasComponent(TestComponent)).toBe(true);
|
||||
expect(nonMatchingEntity.hasComponent(TestComponent)).toBe(false);
|
||||
expect(nonMatchingEntity.hasComponent(AnotherComponent)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Matcher高级查询功能测试', () => {
|
||||
test('应该能使用新的静态方法创建匹配器', () => {
|
||||
// 测试新的静态方法
|
||||
const byTagMatcher = Matcher.byTag(100);
|
||||
const byNameMatcher = Matcher.byName('Player');
|
||||
const byComponentMatcher = Matcher.byComponent(TestComponent);
|
||||
|
||||
expect(byTagMatcher.getCondition().tag).toBe(100);
|
||||
expect(byNameMatcher.getCondition().name).toBe('Player');
|
||||
expect(byComponentMatcher.getCondition().component).toBe(TestComponent);
|
||||
});
|
||||
|
||||
test('应该支持链式组合查询', () => {
|
||||
const complexMatcher = Matcher.all(TestComponent)
|
||||
.withTag(100)
|
||||
.withName('Player')
|
||||
.none(AnotherComponent);
|
||||
|
||||
const condition = complexMatcher.getCondition();
|
||||
expect(condition.all).toContain(TestComponent);
|
||||
expect(condition.tag).toBe(100);
|
||||
expect(condition.name).toBe('Player');
|
||||
expect(condition.none).toContain(AnotherComponent);
|
||||
});
|
||||
|
||||
test('应该能够移除特定条件', () => {
|
||||
const matcher = Matcher.byTag(100)
|
||||
.withName('Player')
|
||||
.withComponent(TestComponent);
|
||||
|
||||
// 移除条件
|
||||
matcher.withoutTag().withoutName();
|
||||
|
||||
const condition = matcher.getCondition();
|
||||
expect(condition.tag).toBeUndefined();
|
||||
expect(condition.name).toBeUndefined();
|
||||
expect(condition.component).toBe(TestComponent);
|
||||
});
|
||||
|
||||
test('应该能够正确重置所有条件', () => {
|
||||
const matcher = Matcher.all(TestComponent)
|
||||
.withTag(100)
|
||||
.withName('Player')
|
||||
.any(AnotherComponent);
|
||||
|
||||
matcher.reset();
|
||||
|
||||
expect(matcher.isEmpty()).toBe(true);
|
||||
expect(matcher.getCondition().all.length).toBe(0);
|
||||
expect(matcher.getCondition().any.length).toBe(0);
|
||||
expect(matcher.getCondition().tag).toBeUndefined();
|
||||
expect(matcher.getCondition().name).toBeUndefined();
|
||||
});
|
||||
|
||||
test('应该能够正确克隆匹配器', () => {
|
||||
const original = Matcher.all(TestComponent)
|
||||
.withTag(100)
|
||||
.withName('Player');
|
||||
|
||||
const cloned = original.clone();
|
||||
|
||||
expect(cloned.getCondition().all).toEqual(original.getCondition().all);
|
||||
expect(cloned.getCondition().tag).toBe(original.getCondition().tag);
|
||||
expect(cloned.getCondition().name).toBe(original.getCondition().name);
|
||||
|
||||
// 修改克隆的不应该影响原始的
|
||||
cloned.withTag(200);
|
||||
expect(original.getCondition().tag).toBe(100);
|
||||
expect(cloned.getCondition().tag).toBe(200);
|
||||
});
|
||||
|
||||
test('应该能够生成正确的字符串表示', () => {
|
||||
const complexMatcher = Matcher.all(TestComponent)
|
||||
.withTag(100)
|
||||
.withName('Player')
|
||||
.none(AnotherComponent);
|
||||
|
||||
const str = complexMatcher.toString();
|
||||
expect(str).toContain('all(TestComponent)');
|
||||
expect(str).toContain('tag(100)');
|
||||
expect(str).toContain('name(Player)');
|
||||
expect(str).toContain('none(AnotherComponent)');
|
||||
});
|
||||
});
|
||||
});
|
||||
338
packages/core/tests/ECS/Utils/BigIntCompatibility.test.ts
Normal file
338
packages/core/tests/ECS/Utils/BigIntCompatibility.test.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
import {
|
||||
BigIntFactory,
|
||||
IBigIntLike,
|
||||
EnvironmentInfo
|
||||
} from '../../../src/ECS/Utils/BigIntCompatibility';
|
||||
|
||||
describe('BigInt兼容性测试', () => {
|
||||
describe('BigIntFactory环境检测', () => {
|
||||
it('应该能够检测BigInt支持情况', () => {
|
||||
const isSupported = BigIntFactory.isNativeSupported();
|
||||
expect(typeof isSupported).toBe('boolean');
|
||||
});
|
||||
|
||||
it('应该返回环境信息', () => {
|
||||
const envInfo = BigIntFactory.getEnvironmentInfo();
|
||||
expect(envInfo).toBeDefined();
|
||||
expect(typeof envInfo.supportsBigInt).toBe('boolean');
|
||||
expect(typeof envInfo.environment).toBe('string');
|
||||
expect(typeof envInfo.jsEngine).toBe('string');
|
||||
});
|
||||
|
||||
it('环境信息应该包含合理的字段', () => {
|
||||
const envInfo = BigIntFactory.getEnvironmentInfo();
|
||||
expect(envInfo.environment).not.toBe('');
|
||||
expect(envInfo.jsEngine).not.toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BigIntFactory基本创建', () => {
|
||||
it('应该能够创建零值', () => {
|
||||
const zero = BigIntFactory.zero();
|
||||
expect(zero.isZero()).toBe(true);
|
||||
expect(zero.toString()).toBe('0');
|
||||
});
|
||||
|
||||
it('应该能够创建1值', () => {
|
||||
const one = BigIntFactory.one();
|
||||
expect(one.isZero()).toBe(false);
|
||||
expect(one.toString()).toBe('1');
|
||||
});
|
||||
|
||||
it('应该能够从数值创建', () => {
|
||||
const value = BigIntFactory.create(42);
|
||||
expect(value.toString()).toBe('42');
|
||||
expect(value.valueOf()).toBe(42);
|
||||
});
|
||||
|
||||
it('应该能够从字符串创建', () => {
|
||||
const value = BigIntFactory.create('123');
|
||||
expect(value.toString()).toBe('123');
|
||||
});
|
||||
|
||||
it('应该能够从原生BigInt创建(如果支持)', () => {
|
||||
if (BigIntFactory.isNativeSupported()) {
|
||||
const value = BigIntFactory.create(BigInt(456));
|
||||
expect(value.toString()).toBe('456');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('IBigIntLike基本操作', () => {
|
||||
let value1: IBigIntLike;
|
||||
let value2: IBigIntLike;
|
||||
|
||||
beforeEach(() => {
|
||||
value1 = BigIntFactory.create(5); // 101 in binary
|
||||
value2 = BigIntFactory.create(3); // 011 in binary
|
||||
});
|
||||
|
||||
it('should支持字符串转换', () => {
|
||||
expect(value1.toString()).toBe('5');
|
||||
expect(value2.toString()).toBe('3');
|
||||
});
|
||||
|
||||
it('应该支持十六进制转换', () => {
|
||||
const value = BigIntFactory.create(255);
|
||||
expect(value.toString(16)).toBe('FF');
|
||||
});
|
||||
|
||||
it('应该支持二进制转换', () => {
|
||||
expect(value1.toString(2)).toBe('101');
|
||||
expect(value2.toString(2)).toBe('11');
|
||||
});
|
||||
|
||||
it('应该支持相等比较', () => {
|
||||
const value1Copy = BigIntFactory.create(5);
|
||||
expect(value1.equals(value1Copy)).toBe(true);
|
||||
expect(value1.equals(value2)).toBe(false);
|
||||
});
|
||||
|
||||
it('应该支持零值检查', () => {
|
||||
const zero = BigIntFactory.zero();
|
||||
expect(zero.isZero()).toBe(true);
|
||||
expect(value1.isZero()).toBe(false);
|
||||
});
|
||||
|
||||
it('应该支持克隆操作', () => {
|
||||
const cloned = value1.clone();
|
||||
expect(cloned.equals(value1)).toBe(true);
|
||||
expect(cloned).not.toBe(value1); // 不同的对象引用
|
||||
});
|
||||
});
|
||||
|
||||
describe('位运算操作', () => {
|
||||
let value1: IBigIntLike; // 5 = 101
|
||||
let value2: IBigIntLike; // 3 = 011
|
||||
|
||||
beforeEach(() => {
|
||||
value1 = BigIntFactory.create(5);
|
||||
value2 = BigIntFactory.create(3);
|
||||
});
|
||||
|
||||
it('AND运算应该正确', () => {
|
||||
const result = value1.and(value2);
|
||||
expect(result.toString()).toBe('1'); // 101 & 011 = 001
|
||||
});
|
||||
|
||||
it('OR运算应该正确', () => {
|
||||
const result = value1.or(value2);
|
||||
expect(result.toString()).toBe('7'); // 101 | 011 = 111
|
||||
});
|
||||
|
||||
it('XOR运算应该正确', () => {
|
||||
const result = value1.xor(value2);
|
||||
expect(result.toString()).toBe('6'); // 101 ^ 011 = 110
|
||||
});
|
||||
|
||||
it('NOT运算应该正确(8位限制)', () => {
|
||||
const value = BigIntFactory.create(5); // 00000101
|
||||
const result = value.not(8);
|
||||
expect(result.toString()).toBe('250'); // 11111010 = 250
|
||||
});
|
||||
|
||||
it('左移位运算应该正确', () => {
|
||||
const result = value1.shiftLeft(2);
|
||||
expect(result.toString()).toBe('20'); // 101 << 2 = 10100 = 20
|
||||
});
|
||||
|
||||
it('右移位运算应该正确', () => {
|
||||
const result = value1.shiftRight(1);
|
||||
expect(result.toString()).toBe('2'); // 101 >> 1 = 10 = 2
|
||||
});
|
||||
|
||||
it('移位0位应该返回相同值', () => {
|
||||
const result = value1.shiftLeft(0);
|
||||
expect(result.equals(value1)).toBe(true);
|
||||
});
|
||||
|
||||
it('右移超过位数应该返回0', () => {
|
||||
const result = value1.shiftRight(10);
|
||||
expect(result.isZero()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('复杂位运算场景', () => {
|
||||
it('应该正确处理大数值位运算', () => {
|
||||
const large1 = BigIntFactory.create(0xFFFFFFFF); // 32位全1
|
||||
const large2 = BigIntFactory.create(0x12345678);
|
||||
|
||||
const andResult = large1.and(large2);
|
||||
expect(andResult.toString(16)).toBe('12345678');
|
||||
|
||||
const orResult = large1.or(large2);
|
||||
expect(orResult.toString(16)).toBe('FFFFFFFF');
|
||||
});
|
||||
|
||||
it('应该正确处理连续位运算', () => {
|
||||
let result = BigIntFactory.create(1);
|
||||
|
||||
// 构建 111111 (6个1)
|
||||
for (let i = 1; i < 6; i++) {
|
||||
const shifted = BigIntFactory.one().shiftLeft(i);
|
||||
result = result.or(shifted);
|
||||
}
|
||||
|
||||
expect(result.toString()).toBe('63'); // 111111 = 63
|
||||
expect(result.toString(2)).toBe('111111');
|
||||
});
|
||||
|
||||
it('应该正确处理掩码操作', () => {
|
||||
const value = BigIntFactory.create(0b10110101); // 181
|
||||
const mask = BigIntFactory.create(0b00001111); // 15, 低4位掩码
|
||||
|
||||
const masked = value.and(mask);
|
||||
expect(masked.toString()).toBe('5'); // 0101 = 5
|
||||
});
|
||||
});
|
||||
|
||||
describe('字符串解析功能', () => {
|
||||
it('应该支持从二进制字符串创建', () => {
|
||||
const value = BigIntFactory.fromBinaryString('10101');
|
||||
expect(value.toString()).toBe('21');
|
||||
});
|
||||
|
||||
it('应该支持从十六进制字符串创建', () => {
|
||||
const value1 = BigIntFactory.fromHexString('0xFF');
|
||||
const value2 = BigIntFactory.fromHexString('FF');
|
||||
|
||||
expect(value1.toString()).toBe('255');
|
||||
expect(value2.toString()).toBe('255');
|
||||
expect(value1.equals(value2)).toBe(true);
|
||||
});
|
||||
|
||||
it('应该正确处理大的十六进制值', () => {
|
||||
const value = BigIntFactory.fromHexString('0x12345678');
|
||||
expect(value.toString()).toBe('305419896');
|
||||
});
|
||||
|
||||
it('应该正确处理长二进制字符串', () => {
|
||||
const binaryStr = '11111111111111111111111111111111'; // 32个1
|
||||
const value = BigIntFactory.fromBinaryString(binaryStr);
|
||||
expect(value.toString()).toBe('4294967295'); // 2^32 - 1
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况和错误处理', () => {
|
||||
it('应该正确处理零值的所有操作', () => {
|
||||
const zero = BigIntFactory.zero();
|
||||
const one = BigIntFactory.one();
|
||||
|
||||
expect(zero.and(one).isZero()).toBe(true);
|
||||
expect(zero.or(one).equals(one)).toBe(true);
|
||||
expect(zero.xor(one).equals(one)).toBe(true);
|
||||
expect(zero.shiftLeft(5).isZero()).toBe(true);
|
||||
expect(zero.shiftRight(5).isZero()).toBe(true);
|
||||
});
|
||||
|
||||
it('应该正确处理1值的位运算', () => {
|
||||
const one = BigIntFactory.one();
|
||||
const zero = BigIntFactory.zero();
|
||||
|
||||
expect(one.and(zero).isZero()).toBe(true);
|
||||
expect(one.or(zero).equals(one)).toBe(true);
|
||||
expect(one.xor(zero).equals(one)).toBe(true);
|
||||
});
|
||||
|
||||
it('应该处理不支持的字符串进制', () => {
|
||||
const value = BigIntFactory.create(255);
|
||||
expect(() => value.toString(8)).toThrow();
|
||||
});
|
||||
|
||||
it('NOT运算应该正确处理不同的位数限制', () => {
|
||||
const value = BigIntFactory.one(); // 1
|
||||
|
||||
const not8 = value.not(8);
|
||||
expect(not8.toString()).toBe('254'); // 11111110 = 254
|
||||
|
||||
const not16 = value.not(16);
|
||||
expect(not16.toString()).toBe('65534'); // 1111111111111110 = 65534
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能和兼容性测试', () => {
|
||||
it('两种实现应该产生相同的运算结果', () => {
|
||||
// 测试各种运算在两种模式下的一致性
|
||||
const testCases = [
|
||||
{ a: 0, b: 0 },
|
||||
{ a: 1, b: 1 },
|
||||
{ a: 5, b: 3 },
|
||||
{ a: 255, b: 128 },
|
||||
{ a: 65535, b: 32768 }
|
||||
];
|
||||
|
||||
testCases.forEach(({ a, b }) => {
|
||||
const val1 = BigIntFactory.create(a);
|
||||
const val2 = BigIntFactory.create(b);
|
||||
|
||||
// 基本运算
|
||||
const and = val1.and(val2);
|
||||
const or = val1.or(val2);
|
||||
const xor = val1.xor(val2);
|
||||
|
||||
// 验证运算结果的一致性
|
||||
expect(and.toString()).toBe((a & b).toString());
|
||||
expect(or.toString()).toBe((a | b).toString());
|
||||
expect(xor.toString()).toBe((a ^ b).toString());
|
||||
});
|
||||
});
|
||||
|
||||
it('大量运算应该保持高性能', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
let result = BigIntFactory.zero();
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const value = BigIntFactory.create(i);
|
||||
result = result.or(value.shiftLeft(i % 32));
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(1000); // 应该在1秒内完成
|
||||
expect(result.isZero()).toBe(false);
|
||||
});
|
||||
|
||||
it('应该支持ECS框架中常见的位掩码操作', () => {
|
||||
// 模拟组件位掩码
|
||||
const componentMasks: IBigIntLike[] = [];
|
||||
|
||||
// 创建64个组件的位掩码
|
||||
for (let i = 0; i < 64; i++) {
|
||||
componentMasks.push(BigIntFactory.one().shiftLeft(i));
|
||||
}
|
||||
|
||||
// 组合多个组件掩码
|
||||
let combinedMask = BigIntFactory.zero();
|
||||
for (let i = 0; i < 10; i++) {
|
||||
combinedMask = combinedMask.or(componentMasks[i * 2]);
|
||||
}
|
||||
|
||||
// 检查是否包含特定组件
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const hasComponent = !combinedMask.and(componentMasks[i * 2]).isZero();
|
||||
expect(hasComponent).toBe(true);
|
||||
|
||||
const hasOtherComponent = !combinedMask.and(componentMasks[i * 2 + 1]).isZero();
|
||||
expect(hasOtherComponent).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('与Core类集成测试', () => {
|
||||
// 这里我们不直接导入Core来避免循环依赖,而是测试工厂方法
|
||||
it('BigIntFactory应该能够为Core提供环境信息', () => {
|
||||
const envInfo = BigIntFactory.getEnvironmentInfo();
|
||||
|
||||
// 验证所有必需的字段都存在
|
||||
expect(envInfo.supportsBigInt).toBeDefined();
|
||||
expect(envInfo.environment).toBeDefined();
|
||||
expect(envInfo.jsEngine).toBeDefined();
|
||||
});
|
||||
|
||||
it('应该提供详细的环境检测信息', () => {
|
||||
const envInfo = BigIntFactory.getEnvironmentInfo();
|
||||
|
||||
// 环境信息应该有意义
|
||||
expect(envInfo.environment).not.toBe('Unknown');
|
||||
});
|
||||
});
|
||||
});
|
||||
553
packages/core/tests/ECS/Utils/Bits.test.ts
Normal file
553
packages/core/tests/ECS/Utils/Bits.test.ts
Normal file
@@ -0,0 +1,553 @@
|
||||
import { Bits } from '../../../src/ECS/Utils/Bits';
|
||||
import { BigIntFactory } from '../../../src/ECS/Utils/BigIntCompatibility';
|
||||
|
||||
describe('Bits - 高性能位操作类测试', () => {
|
||||
let bits: Bits;
|
||||
|
||||
beforeEach(() => {
|
||||
bits = new Bits();
|
||||
});
|
||||
|
||||
describe('基本构造和初始化', () => {
|
||||
it('应该能够创建空的Bits对象', () => {
|
||||
expect(bits).toBeDefined();
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
expect(bits.getValue().isZero()).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能够使用初始值创建Bits对象', () => {
|
||||
const bitsWithValue = new Bits(BigIntFactory.create(5)); // 二进制: 101
|
||||
expect(bitsWithValue.getValue().toString()).toBe('5');
|
||||
expect(bitsWithValue.isEmpty()).toBe(false);
|
||||
expect(bitsWithValue.get(0)).toBe(true); // 第0位
|
||||
expect(bitsWithValue.get(1)).toBe(false); // 第1位
|
||||
expect(bitsWithValue.get(2)).toBe(true); // 第2位
|
||||
});
|
||||
|
||||
it('默认构造函数应该创建值为0的对象', () => {
|
||||
const defaultBits = new Bits();
|
||||
expect(defaultBits.getValue().isZero()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('位设置和清除操作', () => {
|
||||
it('应该能够设置指定位置的位', () => {
|
||||
bits.set(0);
|
||||
expect(bits.get(0)).toBe(true);
|
||||
expect(bits.getValue().toString()).toBe('1');
|
||||
|
||||
bits.set(3);
|
||||
expect(bits.get(3)).toBe(true);
|
||||
expect(bits.getValue().toString()).toBe('9'); // 1001 in binary
|
||||
});
|
||||
|
||||
it('应该能够清除指定位置的位', () => {
|
||||
bits.set(0);
|
||||
bits.set(1);
|
||||
bits.set(2);
|
||||
expect(bits.getValue().toString()).toBe('7'); // 111 in binary
|
||||
|
||||
bits.clear(1);
|
||||
expect(bits.get(1)).toBe(false);
|
||||
expect(bits.getValue().toString()).toBe('5'); // 101 in binary
|
||||
});
|
||||
|
||||
it('重复设置同一位应该保持不变', () => {
|
||||
bits.set(0);
|
||||
const value1 = bits.getValue();
|
||||
bits.set(0);
|
||||
const value2 = bits.getValue();
|
||||
expect(value1.equals(value2)).toBe(true);
|
||||
});
|
||||
|
||||
it('清除未设置的位应该安全', () => {
|
||||
bits.clear(5);
|
||||
expect(bits.getValue().isZero()).toBe(true);
|
||||
});
|
||||
|
||||
it('设置负索引应该抛出错误', () => {
|
||||
expect(() => {
|
||||
bits.set(-1);
|
||||
}).toThrow('Bit index cannot be negative');
|
||||
});
|
||||
|
||||
it('清除负索引应该抛出错误', () => {
|
||||
expect(() => {
|
||||
bits.clear(-1);
|
||||
}).toThrow('Bit index cannot be negative');
|
||||
});
|
||||
});
|
||||
|
||||
describe('位获取操作', () => {
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
bits.set(4);
|
||||
});
|
||||
|
||||
it('应该能够正确获取设置的位', () => {
|
||||
expect(bits.get(0)).toBe(true);
|
||||
expect(bits.get(2)).toBe(true);
|
||||
expect(bits.get(4)).toBe(true);
|
||||
});
|
||||
|
||||
it('应该能够正确获取未设置的位', () => {
|
||||
expect(bits.get(1)).toBe(false);
|
||||
expect(bits.get(3)).toBe(false);
|
||||
expect(bits.get(5)).toBe(false);
|
||||
});
|
||||
|
||||
it('获取负索引应该返回false', () => {
|
||||
expect(bits.get(-1)).toBe(false);
|
||||
expect(bits.get(-10)).toBe(false);
|
||||
});
|
||||
|
||||
it('获取超大索引应该正确处理', () => {
|
||||
expect(bits.get(1000)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('位运算操作', () => {
|
||||
let otherBits: Bits;
|
||||
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
bits.set(4); // 10101 in binary = 21
|
||||
|
||||
otherBits = new Bits();
|
||||
otherBits.set(1);
|
||||
otherBits.set(2);
|
||||
otherBits.set(3); // 1110 in binary = 14
|
||||
});
|
||||
|
||||
it('AND运算应该正确', () => {
|
||||
const result = bits.and(otherBits);
|
||||
expect(result.getValue().toString()).toBe('4'); // 10101 & 01110 = 00100 = 4
|
||||
expect(result.get(2)).toBe(true);
|
||||
expect(result.get(0)).toBe(false);
|
||||
expect(result.get(1)).toBe(false);
|
||||
});
|
||||
|
||||
it('OR运算应该正确', () => {
|
||||
const result = bits.or(otherBits);
|
||||
expect(result.getValue().toString()).toBe('31'); // 10101 | 01110 = 11111 = 31
|
||||
expect(result.get(0)).toBe(true);
|
||||
expect(result.get(1)).toBe(true);
|
||||
expect(result.get(2)).toBe(true);
|
||||
expect(result.get(3)).toBe(true);
|
||||
expect(result.get(4)).toBe(true);
|
||||
});
|
||||
|
||||
it('XOR运算应该正确', () => {
|
||||
const result = bits.xor(otherBits);
|
||||
expect(result.getValue().toString()).toBe('27'); // 10101 ^ 01110 = 11011 = 27
|
||||
expect(result.get(0)).toBe(true);
|
||||
expect(result.get(1)).toBe(true);
|
||||
expect(result.get(2)).toBe(false); // 相同位XOR为0
|
||||
expect(result.get(3)).toBe(true);
|
||||
expect(result.get(4)).toBe(true);
|
||||
});
|
||||
|
||||
it('NOT运算应该正确', () => {
|
||||
const simpleBits = new Bits(BigIntFactory.create(5)); // 101 in binary
|
||||
const result = simpleBits.not(8); // 限制为8位
|
||||
expect(result.getValue().toString()).toBe('250'); // ~00000101 = 11111010 = 250 (8位)
|
||||
});
|
||||
|
||||
it('NOT运算默认64位应该正确', () => {
|
||||
const simpleBits = new Bits(BigIntFactory.create(1));
|
||||
const result = simpleBits.not();
|
||||
const expected = BigIntFactory.one().shiftLeft(64).valueOf() - 2; // 64位全1减去最低位
|
||||
expect(result.getValue().valueOf()).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('包含性检查', () => {
|
||||
let otherBits: Bits;
|
||||
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
bits.set(4); // 10101
|
||||
|
||||
otherBits = new Bits();
|
||||
});
|
||||
|
||||
it('containsAll应该正确检查包含所有位', () => {
|
||||
otherBits.set(0);
|
||||
otherBits.set(2); // 101
|
||||
expect(bits.containsAll(otherBits)).toBe(true);
|
||||
|
||||
otherBits.set(1); // 111
|
||||
expect(bits.containsAll(otherBits)).toBe(false);
|
||||
});
|
||||
|
||||
it('intersects应该正确检查交集', () => {
|
||||
otherBits.set(1);
|
||||
otherBits.set(3); // 1010
|
||||
expect(bits.intersects(otherBits)).toBe(false);
|
||||
|
||||
otherBits.set(0); // 1011
|
||||
expect(bits.intersects(otherBits)).toBe(true);
|
||||
});
|
||||
|
||||
it('excludes应该正确检查互斥', () => {
|
||||
otherBits.set(1);
|
||||
otherBits.set(3); // 1010
|
||||
expect(bits.excludes(otherBits)).toBe(true);
|
||||
|
||||
otherBits.set(0); // 1011
|
||||
expect(bits.excludes(otherBits)).toBe(false);
|
||||
});
|
||||
|
||||
it('空Bits对象的包含性检查', () => {
|
||||
const emptyBits = new Bits();
|
||||
expect(bits.containsAll(emptyBits)).toBe(true);
|
||||
expect(bits.intersects(emptyBits)).toBe(false);
|
||||
expect(bits.excludes(emptyBits)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('状态检查和计数', () => {
|
||||
it('isEmpty应该正确检查空状态', () => {
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
|
||||
bits.set(0);
|
||||
expect(bits.isEmpty()).toBe(false);
|
||||
|
||||
bits.clear(0);
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
});
|
||||
|
||||
it('cardinality应该正确计算设置的位数量', () => {
|
||||
expect(bits.cardinality()).toBe(0);
|
||||
|
||||
bits.set(0);
|
||||
expect(bits.cardinality()).toBe(1);
|
||||
|
||||
bits.set(2);
|
||||
bits.set(4);
|
||||
expect(bits.cardinality()).toBe(3);
|
||||
|
||||
bits.clear(2);
|
||||
expect(bits.cardinality()).toBe(2);
|
||||
});
|
||||
|
||||
it('大数值的cardinality应该正确', () => {
|
||||
// 设置很多位
|
||||
for (let i = 0; i < 100; i += 2) {
|
||||
bits.set(i);
|
||||
}
|
||||
expect(bits.cardinality()).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('清空和重置操作', () => {
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(1);
|
||||
bits.set(2);
|
||||
});
|
||||
|
||||
it('clearAll应该清空所有位', () => {
|
||||
expect(bits.isEmpty()).toBe(false);
|
||||
bits.clearAll();
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
expect(bits.getValue().isZero()).toBe(true);
|
||||
});
|
||||
|
||||
it('clearAll后应该能重新设置位', () => {
|
||||
bits.clearAll();
|
||||
bits.set(5);
|
||||
expect(bits.get(5)).toBe(true);
|
||||
expect(bits.cardinality()).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('复制和克隆操作', () => {
|
||||
beforeEach(() => {
|
||||
bits.set(1);
|
||||
bits.set(3);
|
||||
bits.set(5);
|
||||
});
|
||||
|
||||
it('copyFrom应该正确复制另一个Bits对象', () => {
|
||||
const newBits = new Bits();
|
||||
newBits.copyFrom(bits);
|
||||
|
||||
expect(newBits.getValue().equals(bits.getValue())).toBe(true);
|
||||
expect(newBits.equals(bits)).toBe(true);
|
||||
});
|
||||
|
||||
it('clone应该创建相同的副本', () => {
|
||||
const clonedBits = bits.clone();
|
||||
|
||||
expect(clonedBits.getValue().equals(bits.getValue())).toBe(true);
|
||||
expect(clonedBits.equals(bits)).toBe(true);
|
||||
expect(clonedBits).not.toBe(bits); // 应该是不同的对象
|
||||
});
|
||||
|
||||
it('修改克隆对象不应该影响原对象', () => {
|
||||
const clonedBits = bits.clone();
|
||||
clonedBits.set(7);
|
||||
|
||||
expect(bits.get(7)).toBe(false);
|
||||
expect(clonedBits.get(7)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('值操作', () => {
|
||||
it('getValue和setValue应该正确工作', () => {
|
||||
bits.setValue(42);
|
||||
expect(bits.getValue().toString()).toBe('42');
|
||||
});
|
||||
|
||||
it('setValue应该正确反映在位操作中', () => {
|
||||
bits.setValue(5); // 101 in binary
|
||||
expect(bits.get(0)).toBe(true);
|
||||
expect(bits.get(1)).toBe(false);
|
||||
expect(bits.get(2)).toBe(true);
|
||||
});
|
||||
|
||||
it('setValue为0应该清空所有位', () => {
|
||||
bits.set(1);
|
||||
bits.set(2);
|
||||
bits.setValue(0);
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('字符串表示和解析', () => {
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
bits.set(4); // 10101 = 21
|
||||
});
|
||||
|
||||
it('toString应该返回可读的位表示', () => {
|
||||
const str = bits.toString();
|
||||
expect(str).toBe('Bits[0, 2, 4]');
|
||||
});
|
||||
|
||||
it('空Bits的toString应该正确', () => {
|
||||
const emptyBits = new Bits();
|
||||
expect(emptyBits.toString()).toBe('Bits[]');
|
||||
});
|
||||
|
||||
it('toBinaryString应该返回正确的二进制表示', () => {
|
||||
const binaryStr = bits.toBinaryString(8);
|
||||
expect(binaryStr).toBe('00010101');
|
||||
});
|
||||
|
||||
it('toBinaryString应该正确处理空格分隔', () => {
|
||||
const binaryStr = bits.toBinaryString(16);
|
||||
expect(binaryStr).toBe('00000000 00010101');
|
||||
});
|
||||
|
||||
it('toHexString应该返回正确的十六进制表示', () => {
|
||||
const hexStr = bits.toHexString();
|
||||
expect(hexStr).toBe('0x15'); // 21 in hex
|
||||
});
|
||||
|
||||
it('fromBinaryString应该正确解析', () => {
|
||||
const parsedBits = Bits.fromBinaryString('10101');
|
||||
expect(parsedBits.getValue().toString()).toBe('21');
|
||||
expect(parsedBits.equals(bits)).toBe(true);
|
||||
});
|
||||
|
||||
it('fromBinaryString应该处理带空格的字符串', () => {
|
||||
const parsedBits = Bits.fromBinaryString('0001 0101');
|
||||
expect(parsedBits.getValue().toString()).toBe('21');
|
||||
});
|
||||
|
||||
it('fromHexString应该正确解析', () => {
|
||||
const parsedBits = Bits.fromHexString('0x15');
|
||||
expect(parsedBits.getValue().toString()).toBe('21');
|
||||
expect(parsedBits.equals(bits)).toBe(true);
|
||||
});
|
||||
|
||||
it('fromHexString应该处理不带0x前缀的字符串', () => {
|
||||
const parsedBits = Bits.fromHexString('15');
|
||||
expect(parsedBits.getValue().toString()).toBe('21');
|
||||
});
|
||||
});
|
||||
|
||||
describe('比较操作', () => {
|
||||
let otherBits: Bits;
|
||||
|
||||
beforeEach(() => {
|
||||
bits.set(0);
|
||||
bits.set(2);
|
||||
|
||||
otherBits = new Bits();
|
||||
});
|
||||
|
||||
it('equals应该正确比较相等的Bits', () => {
|
||||
otherBits.set(0);
|
||||
otherBits.set(2);
|
||||
expect(bits.equals(otherBits)).toBe(true);
|
||||
});
|
||||
|
||||
it('equals应该正确比较不相等的Bits', () => {
|
||||
otherBits.set(0);
|
||||
otherBits.set(1);
|
||||
expect(bits.equals(otherBits)).toBe(false);
|
||||
});
|
||||
|
||||
it('空Bits对象应该相等', () => {
|
||||
const emptyBits1 = new Bits();
|
||||
const emptyBits2 = new Bits();
|
||||
expect(emptyBits1.equals(emptyBits2)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('索引查找操作', () => {
|
||||
it('getHighestBitIndex应该返回最高设置位的索引', () => {
|
||||
bits.set(0);
|
||||
bits.set(5);
|
||||
bits.set(10);
|
||||
expect(bits.getHighestBitIndex()).toBe(10);
|
||||
});
|
||||
|
||||
it('getLowestBitIndex应该返回最低设置位的索引', () => {
|
||||
bits.set(3);
|
||||
bits.set(7);
|
||||
bits.set(1);
|
||||
expect(bits.getLowestBitIndex()).toBe(1);
|
||||
});
|
||||
|
||||
it('空Bits的索引查找应该返回-1', () => {
|
||||
expect(bits.getHighestBitIndex()).toBe(-1);
|
||||
expect(bits.getLowestBitIndex()).toBe(-1);
|
||||
});
|
||||
|
||||
it('只有一个位设置时索引查找应该正确', () => {
|
||||
bits.set(5);
|
||||
expect(bits.getHighestBitIndex()).toBe(5);
|
||||
expect(bits.getLowestBitIndex()).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('大数值处理', () => {
|
||||
it('应该能够处理超过64位的数值', () => {
|
||||
bits.set(100);
|
||||
expect(bits.get(100)).toBe(true);
|
||||
expect(bits.cardinality()).toBe(1);
|
||||
});
|
||||
|
||||
it('应该能够处理非常大的位索引', () => {
|
||||
bits.set(1000);
|
||||
bits.set(2000);
|
||||
expect(bits.get(1000)).toBe(true);
|
||||
expect(bits.get(2000)).toBe(true);
|
||||
expect(bits.cardinality()).toBe(2);
|
||||
});
|
||||
|
||||
it('大数值的位运算应该正确', () => {
|
||||
const largeBits1 = new Bits();
|
||||
const largeBits2 = new Bits();
|
||||
|
||||
largeBits1.set(100);
|
||||
largeBits1.set(200);
|
||||
|
||||
largeBits2.set(100);
|
||||
largeBits2.set(150);
|
||||
|
||||
const result = largeBits1.and(largeBits2);
|
||||
expect(result.get(100)).toBe(true);
|
||||
expect(result.get(150)).toBe(false);
|
||||
expect(result.get(200)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能测试', () => {
|
||||
it('大量位设置操作应该高效', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
bits.set(i);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(1000); // 应该在1秒内完成
|
||||
expect(bits.cardinality()).toBe(10000);
|
||||
});
|
||||
|
||||
it('大量位查询操作应该高效', () => {
|
||||
// 先设置一些位
|
||||
for (let i = 0; i < 1000; i += 2) {
|
||||
bits.set(i);
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
let trueCount = 0;
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
if (bits.get(i)) {
|
||||
trueCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||
expect(trueCount).toBe(500); // 500个偶数位
|
||||
});
|
||||
|
||||
it('位运算操作应该高效', () => {
|
||||
const otherBits = new Bits();
|
||||
|
||||
// 设置一些位
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
bits.set(i * 2);
|
||||
otherBits.set(i * 2 + 1);
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const result = bits.or(otherBits);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
expect(endTime - startTime).toBeLessThan(100); // 应该在100ms内完成
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况和错误处理', () => {
|
||||
it('应该处理0值的各种操作', () => {
|
||||
const zeroBits = new Bits(BigIntFactory.zero());
|
||||
expect(zeroBits.isEmpty()).toBe(true);
|
||||
expect(zeroBits.cardinality()).toBe(0);
|
||||
expect(zeroBits.getHighestBitIndex()).toBe(-1);
|
||||
expect(zeroBits.getLowestBitIndex()).toBe(-1);
|
||||
});
|
||||
|
||||
it('应该处理最大BigInt值', () => {
|
||||
const maxBits = new Bits(BigIntFactory.create(Number.MAX_SAFE_INTEGER));
|
||||
expect(maxBits.isEmpty()).toBe(false);
|
||||
expect(maxBits.cardinality()).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('位操作的结果应该是新对象', () => {
|
||||
bits.set(0);
|
||||
const otherBits = new Bits();
|
||||
otherBits.set(1);
|
||||
|
||||
const result = bits.or(otherBits);
|
||||
expect(result).not.toBe(bits);
|
||||
expect(result).not.toBe(otherBits);
|
||||
});
|
||||
|
||||
it('连续的设置和清除操作应该正确', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
bits.set(i);
|
||||
expect(bits.get(i)).toBe(true);
|
||||
bits.clear(i);
|
||||
expect(bits.get(i)).toBe(false);
|
||||
}
|
||||
|
||||
expect(bits.isEmpty()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
414
packages/core/tests/ECS/Utils/IdentifierPool.test.ts
Normal file
414
packages/core/tests/ECS/Utils/IdentifierPool.test.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* IdentifierPool 世代式ID池测试
|
||||
*
|
||||
* 测试实体ID的分配、回收、验证和世代版本控制功能
|
||||
*/
|
||||
import { IdentifierPool } from '../../../src/ECS/Utils/IdentifierPool';
|
||||
import { TestUtils } from '../../setup';
|
||||
|
||||
describe('IdentifierPool 世代式ID池测试', () => {
|
||||
let pool: IdentifierPool;
|
||||
|
||||
beforeEach(() => {
|
||||
pool = new IdentifierPool();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
// 测试基本功能
|
||||
describe('基本功能测试', () => {
|
||||
test('应该能创建IdentifierPool实例', () => {
|
||||
expect(pool).toBeDefined();
|
||||
expect(pool).toBeInstanceOf(IdentifierPool);
|
||||
});
|
||||
|
||||
test('应该能分配连续的ID', () => {
|
||||
const id1 = pool.checkOut();
|
||||
const id2 = pool.checkOut();
|
||||
const id3 = pool.checkOut();
|
||||
|
||||
expect(id1).toBe(65536); // 世代1,索引0
|
||||
expect(id2).toBe(65537); // 世代1,索引1
|
||||
expect(id3).toBe(65538); // 世代1,索引2
|
||||
});
|
||||
|
||||
test('应该能验证有效的ID', () => {
|
||||
const id = pool.checkOut();
|
||||
expect(pool.isValid(id)).toBe(true);
|
||||
});
|
||||
|
||||
test('应该能获取统计信息', () => {
|
||||
const id1 = pool.checkOut();
|
||||
const id2 = pool.checkOut();
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.totalAllocated).toBe(2);
|
||||
expect(stats.currentActive).toBe(2);
|
||||
expect(stats.currentlyFree).toBe(0);
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
expect(stats.maxPossibleEntities).toBe(65536); // 2^16
|
||||
expect(stats.averageGeneration).toBe(1);
|
||||
expect(stats.memoryUsage).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试回收功能
|
||||
describe('ID回收功能测试', () => {
|
||||
test('应该能回收有效的ID', () => {
|
||||
const id = pool.checkOut();
|
||||
const result = pool.checkIn(id);
|
||||
|
||||
expect(result).toBe(true);
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(1);
|
||||
expect(stats.currentActive).toBe(0);
|
||||
});
|
||||
|
||||
test('应该拒绝回收无效的ID', () => {
|
||||
const invalidId = 999999;
|
||||
const result = pool.checkIn(invalidId);
|
||||
|
||||
expect(result).toBe(false);
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
});
|
||||
|
||||
test('应该拒绝重复回收同一个ID', () => {
|
||||
const id = pool.checkOut();
|
||||
|
||||
const firstResult = pool.checkIn(id);
|
||||
const secondResult = pool.checkIn(id);
|
||||
|
||||
expect(firstResult).toBe(true);
|
||||
expect(secondResult).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试延迟回收
|
||||
describe('延迟回收机制测试', () => {
|
||||
test('应该支持延迟回收', () => {
|
||||
const pool = new IdentifierPool(100); // 100ms延迟
|
||||
|
||||
const id = pool.checkOut();
|
||||
pool.checkIn(id);
|
||||
|
||||
// 立即检查,ID应该还在延迟队列中
|
||||
let stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(1);
|
||||
expect(stats.currentlyFree).toBe(0);
|
||||
|
||||
// 模拟时间前进150ms
|
||||
jest.advanceTimersByTime(150);
|
||||
|
||||
// 触发延迟回收处理(通过分配新ID)
|
||||
pool.checkOut();
|
||||
|
||||
// 现在ID应该被真正回收了
|
||||
stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
expect(stats.currentlyFree).toBe(0); // 因为被重新分配了
|
||||
});
|
||||
|
||||
test('延迟时间内ID应该仍然有效', () => {
|
||||
const pool = new IdentifierPool(100);
|
||||
|
||||
const id = pool.checkOut();
|
||||
pool.checkIn(id);
|
||||
|
||||
// 在延迟时间内,ID应该仍然有效
|
||||
expect(pool.isValid(id)).toBe(true);
|
||||
|
||||
// 模拟时间前进150ms并触发处理
|
||||
jest.advanceTimersByTime(150);
|
||||
pool.checkOut(); // 触发延迟回收处理
|
||||
|
||||
// 现在ID应该无效了(世代已递增)
|
||||
expect(pool.isValid(id)).toBe(false);
|
||||
});
|
||||
|
||||
test('应该支持强制延迟回收处理', () => {
|
||||
const id = pool.checkOut();
|
||||
pool.checkIn(id);
|
||||
|
||||
// 在延迟时间内强制处理
|
||||
pool.forceProcessDelayedRecycle();
|
||||
|
||||
// ID应该立即变为无效
|
||||
expect(pool.isValid(id)).toBe(false);
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
expect(stats.currentlyFree).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试世代版本控制
|
||||
describe('世代版本控制测试', () => {
|
||||
test('回收后的ID应该增加世代版本', () => {
|
||||
const pool = new IdentifierPool(0); // 无延迟,立即回收
|
||||
|
||||
const originalId = pool.checkOut();
|
||||
pool.checkIn(originalId);
|
||||
|
||||
// 分配新ID触发回收处理
|
||||
const newId = pool.checkOut();
|
||||
|
||||
// 原ID应该无效
|
||||
expect(pool.isValid(originalId)).toBe(false);
|
||||
|
||||
// 新ID应该有不同的世代版本
|
||||
expect(newId).not.toBe(originalId);
|
||||
expect(newId).toBe(131072); // 世代2,索引0
|
||||
});
|
||||
|
||||
test('应该能重用回收的索引', () => {
|
||||
const pool = new IdentifierPool(0);
|
||||
|
||||
const id1 = pool.checkOut(); // 索引0
|
||||
const id2 = pool.checkOut(); // 索引1
|
||||
|
||||
pool.checkIn(id1);
|
||||
|
||||
const id3 = pool.checkOut(); // 应该重用索引0,但世代递增
|
||||
|
||||
expect(id3 & 0xFFFF).toBe(0); // 索引部分应该是0
|
||||
expect(id3 >> 16).toBe(2); // 世代应该是2
|
||||
});
|
||||
|
||||
test('世代版本溢出应该重置为1', () => {
|
||||
const pool = new IdentifierPool(0);
|
||||
|
||||
// 手动设置一个即将溢出的世代
|
||||
const id = pool.checkOut();
|
||||
|
||||
// 通过反射访问私有成员来模拟溢出情况
|
||||
const generations = (pool as any)._generations;
|
||||
generations.set(0, 65535); // 设置为最大值
|
||||
|
||||
pool.checkIn(id);
|
||||
const newId = pool.checkOut();
|
||||
|
||||
// 世代应该重置为1而不是0
|
||||
expect(newId >> 16).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试错误处理
|
||||
describe('错误处理测试', () => {
|
||||
test('超过最大索引数应该抛出错误', () => {
|
||||
// 创建一个模拟的池,直接设置到达到限制
|
||||
const pool = new IdentifierPool();
|
||||
|
||||
// 通过反射设置到达到限制(65536会触发错误)
|
||||
(pool as any)._nextAvailableIndex = 65536;
|
||||
|
||||
expect(() => {
|
||||
pool.checkOut();
|
||||
}).toThrow('实体索引已达到框架设计限制');
|
||||
});
|
||||
|
||||
test('应该能处理边界值', () => {
|
||||
const pool = new IdentifierPool();
|
||||
|
||||
const id = pool.checkOut();
|
||||
expect(id).toBe(65536); // 世代1,索引0
|
||||
|
||||
// 回收并重新分配
|
||||
pool.checkIn(id);
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
const newId = pool.checkOut();
|
||||
|
||||
expect(newId).toBe(131072); // 世代2,索引0
|
||||
});
|
||||
});
|
||||
|
||||
// 测试动态扩展
|
||||
describe('动态内存扩展测试', () => {
|
||||
test('应该能动态扩展内存', () => {
|
||||
const pool = new IdentifierPool(0, 10); // 小的扩展块用于测试
|
||||
|
||||
// 分配超过初始块大小的ID
|
||||
const ids: number[] = [];
|
||||
for (let i = 0; i < 25; i++) {
|
||||
ids.push(pool.checkOut());
|
||||
}
|
||||
|
||||
expect(ids.length).toBe(25);
|
||||
|
||||
// 验证所有ID都是唯一的
|
||||
const uniqueIds = new Set(ids);
|
||||
expect(uniqueIds.size).toBe(25);
|
||||
|
||||
// 检查内存扩展统计
|
||||
const stats = pool.getStats();
|
||||
expect(stats.memoryExpansions).toBeGreaterThan(1);
|
||||
expect(stats.generationStorageSize).toBeGreaterThanOrEqual(25);
|
||||
});
|
||||
|
||||
test('内存扩展应该按块进行', () => {
|
||||
const blockSize = 5;
|
||||
const pool = new IdentifierPool(0, blockSize);
|
||||
|
||||
// 分配第一个块
|
||||
for (let i = 0; i < blockSize; i++) {
|
||||
pool.checkOut();
|
||||
}
|
||||
|
||||
let stats = pool.getStats();
|
||||
const initialExpansions = stats.memoryExpansions;
|
||||
|
||||
// 分配一个会触发新块的ID
|
||||
pool.checkOut();
|
||||
|
||||
stats = pool.getStats();
|
||||
expect(stats.memoryExpansions).toBe(initialExpansions + 1);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试性能和内存
|
||||
describe('性能和内存测试', () => {
|
||||
test('应该能处理大量ID分配', () => {
|
||||
const count = 10000; // 增加测试规模
|
||||
const ids: number[] = [];
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
ids.push(pool.checkOut());
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
expect(ids.length).toBe(count);
|
||||
expect(duration).toBeLessThan(1000); // 10k个ID应该在1秒内完成
|
||||
|
||||
// 验证所有ID都是唯一的
|
||||
const uniqueIds = new Set(ids);
|
||||
expect(uniqueIds.size).toBe(count);
|
||||
});
|
||||
|
||||
test('应该能处理大量回收操作', () => {
|
||||
const count = 5000; // 增加测试规模
|
||||
const ids: number[] = [];
|
||||
|
||||
// 分配ID
|
||||
for (let i = 0; i < count; i++) {
|
||||
ids.push(pool.checkOut());
|
||||
}
|
||||
|
||||
// 回收ID
|
||||
const startTime = performance.now();
|
||||
|
||||
for (const id of ids) {
|
||||
pool.checkIn(id);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
expect(duration).toBeLessThan(500); // 5k个回收应该在500ms内完成
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(count);
|
||||
expect(stats.currentActive).toBe(0);
|
||||
});
|
||||
|
||||
test('内存使用应该是合理的', () => {
|
||||
const stats = pool.getStats();
|
||||
const initialMemory = stats.memoryUsage;
|
||||
|
||||
// 分配大量ID
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
pool.checkOut();
|
||||
}
|
||||
|
||||
const newStats = pool.getStats();
|
||||
const memoryIncrease = newStats.memoryUsage - initialMemory;
|
||||
|
||||
// 内存增长应该是合理的(动态分配应该更高效)
|
||||
expect(memoryIncrease).toBeLessThan(5000 * 50); // 每个ID少于50字节
|
||||
});
|
||||
});
|
||||
|
||||
// 测试并发安全性(模拟)
|
||||
describe('并发安全性测试', () => {
|
||||
test('应该能处理并发分配', async () => {
|
||||
const promises: Promise<number>[] = [];
|
||||
|
||||
// 模拟并发分配
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
promises.push(Promise.resolve(pool.checkOut()));
|
||||
}
|
||||
|
||||
const ids = await Promise.all(promises);
|
||||
|
||||
// 所有ID应该是唯一的
|
||||
const uniqueIds = new Set(ids);
|
||||
expect(uniqueIds.size).toBe(1000);
|
||||
});
|
||||
|
||||
test('应该能处理并发回收', async () => {
|
||||
const ids: number[] = [];
|
||||
|
||||
// 先分配一些ID
|
||||
for (let i = 0; i < 500; i++) {
|
||||
ids.push(pool.checkOut());
|
||||
}
|
||||
|
||||
// 模拟并发回收
|
||||
const promises = ids.map(id => Promise.resolve(pool.checkIn(id)));
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
// 所有回收操作都应该成功
|
||||
expect(results.every(result => result === true)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// 测试统计信息
|
||||
describe('统计信息测试', () => {
|
||||
test('统计信息应该准确反映池状态', () => {
|
||||
// 分配一些ID
|
||||
const ids = [pool.checkOut(), pool.checkOut(), pool.checkOut()];
|
||||
|
||||
let stats = pool.getStats();
|
||||
expect(stats.totalAllocated).toBe(3);
|
||||
expect(stats.currentActive).toBe(3);
|
||||
expect(stats.currentlyFree).toBe(0);
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
|
||||
// 回收一个ID
|
||||
pool.checkIn(ids[0]);
|
||||
|
||||
stats = pool.getStats();
|
||||
expect(stats.totalRecycled).toBe(1);
|
||||
expect(stats.currentActive).toBe(2);
|
||||
expect(stats.pendingRecycle).toBe(1);
|
||||
|
||||
// 强制处理延迟回收
|
||||
pool.forceProcessDelayedRecycle();
|
||||
|
||||
stats = pool.getStats();
|
||||
expect(stats.pendingRecycle).toBe(0);
|
||||
expect(stats.currentlyFree).toBe(1);
|
||||
});
|
||||
|
||||
test('应该正确计算平均世代版本', () => {
|
||||
const pool = new IdentifierPool(0); // 无延迟
|
||||
|
||||
// 分配、回收、再分配来增加世代
|
||||
const id1 = pool.checkOut();
|
||||
pool.checkIn(id1);
|
||||
const id2 = pool.checkOut(); // 这会触发世代递增
|
||||
|
||||
const stats = pool.getStats();
|
||||
expect(stats.averageGeneration).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
288
packages/core/tests/ECS/Utils/Matcher.test.ts
Normal file
288
packages/core/tests/ECS/Utils/Matcher.test.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* Matcher完整测试套件
|
||||
* 测试新的Matcher条件构建功能和QuerySystem集成
|
||||
*/
|
||||
|
||||
import { Scene } from '../../../src/ECS/Scene';
|
||||
import { Entity } from '../../../src/ECS/Entity';
|
||||
import { Component } from '../../../src/ECS/Component';
|
||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||
|
||||
// 测试组件
|
||||
class Position extends Component {
|
||||
public x: number = 0;
|
||||
public y: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [x = 0, y = 0] = args as [number?, number?];
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
class Velocity extends Component {
|
||||
public vx: number = 0;
|
||||
public vy: number = 0;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [vx = 0, vy = 0] = args as [number?, number?];
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
class Health extends Component {
|
||||
public hp: number = 100;
|
||||
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
const [hp = 100] = args as [number?];
|
||||
this.hp = hp;
|
||||
}
|
||||
}
|
||||
|
||||
class Dead extends Component {}
|
||||
|
||||
describe('Matcher测试套件', () => {
|
||||
let scene: Scene;
|
||||
let entities: Entity[];
|
||||
|
||||
beforeEach(() => {
|
||||
scene = new Scene();
|
||||
scene.begin();
|
||||
|
||||
// 创建测试实体
|
||||
entities = [];
|
||||
|
||||
// 实体1: 移动的活体
|
||||
const entity1 = scene.createEntity('MovingAlive');
|
||||
entity1.addComponent(new Position(10, 20));
|
||||
entity1.addComponent(new Velocity(1, 0));
|
||||
entity1.addComponent(new Health(100));
|
||||
entities.push(entity1);
|
||||
|
||||
// 实体2: 静止的活体
|
||||
const entity2 = scene.createEntity('StillAlive');
|
||||
entity2.addComponent(new Position(30, 40));
|
||||
entity2.addComponent(new Health(50));
|
||||
entities.push(entity2);
|
||||
|
||||
// 实体3: 移动的死体
|
||||
const entity3 = scene.createEntity('MovingDead');
|
||||
entity3.addComponent(new Position(50, 60));
|
||||
entity3.addComponent(new Velocity(0, 1));
|
||||
entity3.addComponent(new Dead());
|
||||
entities.push(entity3);
|
||||
|
||||
// 实体4: 静止的死体
|
||||
const entity4 = scene.createEntity('StillDead');
|
||||
entity4.addComponent(new Position(70, 80));
|
||||
entity4.addComponent(new Dead());
|
||||
entities.push(entity4);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
scene.end();
|
||||
});
|
||||
|
||||
describe('Matcher条件构建测试', () => {
|
||||
test('Matcher.all()应该创建正确的查询条件', () => {
|
||||
const matcher = Matcher.all(Position, Health);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.all).toContain(Position);
|
||||
expect(condition.all).toContain(Health);
|
||||
expect(condition.all.length).toBe(2);
|
||||
});
|
||||
|
||||
test('Matcher.any()应该创建正确的查询条件', () => {
|
||||
const matcher = Matcher.any(Health, Dead);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.any).toContain(Health);
|
||||
expect(condition.any).toContain(Dead);
|
||||
expect(condition.any.length).toBe(2);
|
||||
});
|
||||
|
||||
test('Matcher.none()应该创建正确的查询条件', () => {
|
||||
const matcher = Matcher.none(Dead);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.none).toContain(Dead);
|
||||
expect(condition.none.length).toBe(1);
|
||||
});
|
||||
|
||||
test('链式调用应该正确工作', () => {
|
||||
const matcher = Matcher.all(Position)
|
||||
.any(Health, Velocity)
|
||||
.none(Dead);
|
||||
|
||||
const condition = matcher.getCondition();
|
||||
expect(condition.all).toContain(Position);
|
||||
expect(condition.any).toContain(Health);
|
||||
expect(condition.any).toContain(Velocity);
|
||||
expect(condition.none).toContain(Dead);
|
||||
});
|
||||
|
||||
test('byComponent()应该创建单组件查询条件', () => {
|
||||
const matcher = Matcher.byComponent(Position);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.component).toBe(Position);
|
||||
});
|
||||
|
||||
test('byTag()应该创建标签查询条件', () => {
|
||||
const matcher = Matcher.byTag(123);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.tag).toBe(123);
|
||||
});
|
||||
|
||||
test('byName()应该创建名称查询条件', () => {
|
||||
const matcher = Matcher.byName('TestEntity');
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
expect(condition.name).toBe('TestEntity');
|
||||
});
|
||||
});
|
||||
|
||||
describe('QuerySystem集成测试', () => {
|
||||
test('使用QuerySystem的queryAll()查询所有匹配实体', () => {
|
||||
const result = scene.querySystem.queryAll(Position, Health);
|
||||
expect(result.entities.map(e => e.name).sort()).toEqual(['MovingAlive', 'StillAlive']);
|
||||
});
|
||||
|
||||
test('使用QuerySystem的queryAny()查询任一匹配实体', () => {
|
||||
const result = scene.querySystem.queryAny(Health, Dead);
|
||||
expect(result.entities.length).toBe(4); // 所有实体都有Health或Dead
|
||||
});
|
||||
|
||||
test('使用QuerySystem的queryNone()查询排除实体', () => {
|
||||
const result = scene.querySystem.queryNone(Dead);
|
||||
const aliveEntities = result.entities.filter(e => e.hasComponent(Position));
|
||||
expect(aliveEntities.map(e => e.name).sort()).toEqual(['MovingAlive', 'StillAlive']);
|
||||
});
|
||||
|
||||
test('QuerySystem查询性能统计', () => {
|
||||
scene.querySystem.queryAll(Position, Velocity);
|
||||
const stats = scene.querySystem.getStats();
|
||||
|
||||
expect(stats.queryStats.totalQueries).toBeGreaterThan(0);
|
||||
expect(stats.queryStats.cacheHits).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('实际使用场景测试', () => {
|
||||
test('游戏系统中的移动实体查询', () => {
|
||||
// 查询所有可移动的实体(有位置和速度的)
|
||||
const movableEntities = scene.querySystem.queryAll(Position, Velocity);
|
||||
expect(movableEntities.entities.length).toBe(2); // MovingAlive, MovingDead
|
||||
|
||||
movableEntities.entities.forEach(entity => {
|
||||
const pos = entity.getComponent(Position)!;
|
||||
const vel = entity.getComponent(Velocity)!;
|
||||
expect(pos).toBeDefined();
|
||||
expect(vel).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('游戏系统中的活体实体查询', () => {
|
||||
// 查询所有活体实体(有血量,没有死亡标记的)
|
||||
const aliveEntitiesAll = scene.querySystem.queryAll(Health);
|
||||
const deadEntitiesAll = scene.querySystem.queryAll(Dead);
|
||||
|
||||
expect(aliveEntitiesAll.entities.length).toBe(2); // MovingAlive, StillAlive
|
||||
expect(deadEntitiesAll.entities.length).toBe(2); // MovingDead, StillDead
|
||||
});
|
||||
|
||||
test('复杂查询:查找活着的移动实体', () => {
|
||||
// 首先获取所有有位置和速度的实体
|
||||
const movableEntities = scene.querySystem.queryAll(Position, Velocity);
|
||||
|
||||
// 然后过滤出活着的(有血量的)
|
||||
const aliveMovableEntities = movableEntities.entities.filter(entity =>
|
||||
entity.hasComponent(Health)
|
||||
);
|
||||
|
||||
expect(aliveMovableEntities.length).toBe(1); // 只有MovingAlive
|
||||
expect(aliveMovableEntities[0].name).toBe('MovingAlive');
|
||||
});
|
||||
|
||||
test('复合查询条件应用', () => {
|
||||
// 使用Matcher建立复杂条件,然后用QuerySystem执行
|
||||
const matcher = Matcher.all(Position).any(Health, Dead);
|
||||
const condition = matcher.getCondition();
|
||||
|
||||
// 这里演示如何用条件,实际执行需要QuerySystem支持复合条件
|
||||
expect(condition.all).toContain(Position);
|
||||
expect(condition.any).toContain(Health);
|
||||
expect(condition.any).toContain(Dead);
|
||||
});
|
||||
});
|
||||
|
||||
describe('性能测试', () => {
|
||||
test('大量简单查询的性能', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
scene.querySystem.queryAll(Position);
|
||||
}
|
||||
|
||||
const executionTime = performance.now() - startTime;
|
||||
expect(executionTime).toBeLessThan(100); // 应该在100ms内完成
|
||||
});
|
||||
|
||||
test('复杂查询的性能', () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
scene.querySystem.queryAll(Position, Health);
|
||||
scene.querySystem.queryAny(Health, Dead);
|
||||
scene.querySystem.queryNone(Dead);
|
||||
}
|
||||
|
||||
const executionTime = performance.now() - startTime;
|
||||
expect(executionTime).toBeLessThan(50);
|
||||
});
|
||||
|
||||
test('不存在组件的查询性能', () => {
|
||||
class NonExistentComponent extends Component {
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
const result = scene.querySystem.queryAll(NonExistentComponent);
|
||||
expect(result.entities.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况测试', () => {
|
||||
test('空查询应该返回所有实体', () => {
|
||||
const result = scene.querySystem.queryAll();
|
||||
expect(result.entities.length).toBe(entities.length);
|
||||
});
|
||||
|
||||
test('查询不存在的组件应该返回空结果', () => {
|
||||
class NonExistentComponent extends Component {
|
||||
constructor(...args: unknown[]) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
const result = scene.querySystem.queryAll(NonExistentComponent);
|
||||
expect(result.entities.length).toBe(0);
|
||||
});
|
||||
|
||||
test('Matcher条件构建的边界情况', () => {
|
||||
const emptyMatcher = Matcher.complex();
|
||||
const condition = emptyMatcher.getCondition();
|
||||
|
||||
expect(condition.all.length).toBe(0);
|
||||
expect(condition.any.length).toBe(0);
|
||||
expect(condition.none.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user