导出soa装饰器

This commit is contained in:
YHH
2025-08-11 09:01:01 +08:00
parent d539bb3dd9
commit 1361fd8a90
7 changed files with 745 additions and 450 deletions

View File

@@ -1,14 +1,7 @@
/**
* 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';
import { ComponentType } from '../../../src/ECS/Core/ComponentStorage';
// 测试组件
class Position extends Component {
public x: number = 0;
public y: number = 0;
@@ -43,246 +36,490 @@ class Health extends Component {
}
}
class Shield extends Component {
public value: number = 50;
constructor(...args: unknown[]) {
super();
const [value = 50] = args as [number?];
this.value = value;
}
}
class Dead extends Component {}
describe('Matcher测试套件', () => {
let scene: Scene;
let entities: Entity[];
class Weapon extends Component {
public damage: number = 10;
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();
});
constructor(...args: unknown[]) {
super();
const [damage = 10] = args as [number?];
this.damage = damage;
}
}
describe('Matcher条件构建测试', () => {
test('Matcher.all()应该创建正确的查询条件', () => {
const matcher = Matcher.all(Position, Health);
describe('Matcher', () => {
describe('静态工厂方法', () => {
test('all() 应该创建包含所有指定组件的条件', () => {
const matcher = Matcher.all(Position, Velocity);
const condition = matcher.getCondition();
expect(condition.all).toHaveLength(2);
expect(condition.all).toContain(Position);
expect(condition.all).toContain(Health);
expect(condition.all.length).toBe(2);
expect(condition.all).toContain(Velocity);
expect(condition.any).toHaveLength(0);
expect(condition.none).toHaveLength(0);
});
test('Matcher.any()应该创建正确的查询条件', () => {
const matcher = Matcher.any(Health, Dead);
test('any() 应该创建包含任一指定组件的条件', () => {
const matcher = Matcher.any(Health, Shield);
const condition = matcher.getCondition();
expect(condition.any).toHaveLength(2);
expect(condition.any).toContain(Health);
expect(condition.any).toContain(Dead);
expect(condition.any.length).toBe(2);
expect(condition.any).toContain(Shield);
expect(condition.all).toHaveLength(0);
expect(condition.none).toHaveLength(0);
});
test('Matcher.none()应该创建正确的查询条件', () => {
const matcher = Matcher.none(Dead);
test('none() 应该创建排除指定组件的条件', () => {
const matcher = Matcher.none(Dead, Weapon);
const condition = matcher.getCondition();
expect(condition.none).toHaveLength(2);
expect(condition.none).toContain(Dead);
expect(condition.none.length).toBe(1);
expect(condition.none).toContain(Weapon);
expect(condition.all).toHaveLength(0);
expect(condition.any).toHaveLength(0);
});
test('链式调用应该正确工作', () => {
const matcher = Matcher.all(Position)
.any(Health, Velocity)
.none(Dead);
test('byTag() 应该创建标签查询条件', () => {
const matcher = Matcher.byTag(123);
const condition = matcher.getCondition();
expect(condition.all).toContain(Position);
expect(condition.any).toContain(Health);
expect(condition.any).toContain(Velocity);
expect(condition.none).toContain(Dead);
expect(condition.tag).toBe(123);
expect(condition.all).toHaveLength(0);
expect(condition.any).toHaveLength(0);
expect(condition.none).toHaveLength(0);
});
test('byComponent()应该创建单组件查询条件', () => {
test('byName() 应该创建名称查询条件', () => {
const matcher = Matcher.byName('TestEntity');
const condition = matcher.getCondition();
expect(condition.name).toBe('TestEntity');
expect(condition.all).toHaveLength(0);
expect(condition.any).toHaveLength(0);
expect(condition.none).toHaveLength(0);
});
test('byComponent() 应该创建单组件查询条件', () => {
const matcher = Matcher.byComponent(Position);
const condition = matcher.getCondition();
expect(condition.component).toBe(Position);
expect(condition.all).toHaveLength(0);
expect(condition.any).toHaveLength(0);
expect(condition.none).toHaveLength(0);
});
test('complex() 应该创建空的复杂查询构建器', () => {
const matcher = Matcher.complex();
const condition = matcher.getCondition();
expect(condition.all).toHaveLength(0);
expect(condition.any).toHaveLength(0);
expect(condition.none).toHaveLength(0);
expect(condition.tag).toBeUndefined();
expect(condition.name).toBeUndefined();
expect(condition.component).toBeUndefined();
});
test('empty() 应该创建空匹配器', () => {
const matcher = Matcher.empty();
const condition = matcher.getCondition();
expect(condition.all).toHaveLength(0);
expect(condition.any).toHaveLength(0);
expect(condition.none).toHaveLength(0);
expect(condition.tag).toBeUndefined();
expect(condition.name).toBeUndefined();
expect(condition.component).toBeUndefined();
});
});
describe('实例方法', () => {
test('all() 应该添加到 all 条件数组', () => {
const matcher = Matcher.empty();
matcher.all(Position, Velocity);
const condition = matcher.getCondition();
expect(condition.all).toHaveLength(2);
expect(condition.all).toContain(Position);
expect(condition.all).toContain(Velocity);
});
test('any() 应该添加到 any 条件数组', () => {
const matcher = Matcher.empty();
matcher.any(Health, Shield);
const condition = matcher.getCondition();
expect(condition.any).toHaveLength(2);
expect(condition.any).toContain(Health);
expect(condition.any).toContain(Shield);
});
test('none() 应该添加到 none 条件数组', () => {
const matcher = Matcher.empty();
matcher.none(Dead);
const condition = matcher.getCondition();
expect(condition.none).toHaveLength(1);
expect(condition.none).toContain(Dead);
});
test('exclude() 应该是 none() 的别名', () => {
const matcher = Matcher.empty();
matcher.exclude(Dead, Weapon);
const condition = matcher.getCondition();
expect(condition.none).toHaveLength(2);
expect(condition.none).toContain(Dead);
expect(condition.none).toContain(Weapon);
});
test('one() 应该是 any() 的别名', () => {
const matcher = Matcher.empty();
matcher.one(Health, Shield);
const condition = matcher.getCondition();
expect(condition.any).toHaveLength(2);
expect(condition.any).toContain(Health);
expect(condition.any).toContain(Shield);
});
});
describe('链式调用', () => {
test('应该支持复杂的链式调用', () => {
const matcher = Matcher.all(Position)
.any(Health, Shield)
.none(Dead)
.withTag(100)
.withName('Player');
const condition = matcher.getCondition();
expect(condition.all).toContain(Position);
expect(condition.any).toContain(Health);
expect(condition.any).toContain(Shield);
expect(condition.none).toContain(Dead);
expect(condition.tag).toBe(100);
expect(condition.name).toBe('Player');
});
test('多次调用同一方法应该累积组件', () => {
const matcher = Matcher.empty()
.all(Position)
.all(Velocity)
.any(Health)
.any(Shield)
.none(Dead)
.none(Weapon);
const condition = matcher.getCondition();
expect(condition.all).toHaveLength(2);
expect(condition.any).toHaveLength(2);
expect(condition.none).toHaveLength(2);
});
});
describe('条件管理方法', () => {
test('withTag() 应该设置标签条件', () => {
const matcher = Matcher.empty().withTag(42);
const condition = matcher.getCondition();
expect(condition.tag).toBe(42);
});
test('withName() 应该设置名称条件', () => {
const matcher = Matcher.empty().withName('Enemy');
const condition = matcher.getCondition();
expect(condition.name).toBe('Enemy');
});
test('withComponent() 应该设置单组件条件', () => {
const matcher = Matcher.empty().withComponent(Position);
const condition = matcher.getCondition();
expect(condition.component).toBe(Position);
});
test('byTag()应该创建标签查询条件', () => {
const matcher = Matcher.byTag(123);
test('withoutTag() 应该移除标签条件', () => {
const matcher = Matcher.byTag(123).withoutTag();
const condition = matcher.getCondition();
expect(condition.tag).toBe(123);
expect(condition.tag).toBeUndefined();
});
test('byName()应该创建名称查询条件', () => {
const matcher = Matcher.byName('TestEntity');
test('withoutName() 应该移除名称条件', () => {
const matcher = Matcher.byName('Test').withoutName();
const condition = matcher.getCondition();
expect(condition.name).toBe('TestEntity');
expect(condition.name).toBeUndefined();
});
test('withoutComponent() 应该移除单组件条件', () => {
const matcher = Matcher.byComponent(Position).withoutComponent();
const condition = matcher.getCondition();
expect(condition.component).toBeUndefined();
});
test('覆盖条件应该替换之前的值', () => {
const matcher = Matcher.byTag(100)
.withTag(200)
.withName('First')
.withName('Second')
.withComponent(Position)
.withComponent(Velocity);
const condition = matcher.getCondition();
expect(condition.tag).toBe(200);
expect(condition.name).toBe('Second');
expect(condition.component).toBe(Velocity);
});
});
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();
describe('工具方法', () => {
test('isEmpty() 应该正确判断空条件', () => {
const emptyMatcher = Matcher.empty();
expect(emptyMatcher.isEmpty()).toBe(true);
expect(stats.queryStats.totalQueries).toBeGreaterThan(0);
expect(stats.queryStats.cacheHits).toBeGreaterThanOrEqual(0);
const nonEmptyMatcher = Matcher.all(Position);
expect(nonEmptyMatcher.isEmpty()).toBe(false);
});
test('isEmpty() 应该检查所有条件类型', () => {
expect(Matcher.all(Position).isEmpty()).toBe(false);
expect(Matcher.any(Health).isEmpty()).toBe(false);
expect(Matcher.none(Dead).isEmpty()).toBe(false);
expect(Matcher.byTag(1).isEmpty()).toBe(false);
expect(Matcher.byName('test').isEmpty()).toBe(false);
expect(Matcher.byComponent(Position).isEmpty()).toBe(false);
});
test('reset() 应该清空所有条件', () => {
const matcher = Matcher.all(Position, Velocity)
.any(Health, Shield)
.none(Dead)
.withTag(123)
.withName('Test')
.withComponent(Weapon);
matcher.reset();
const condition = matcher.getCondition();
expect(condition.all).toHaveLength(0);
expect(condition.any).toHaveLength(0);
expect(condition.none).toHaveLength(0);
expect(condition.tag).toBeUndefined();
expect(condition.name).toBeUndefined();
expect(condition.component).toBeUndefined();
expect(matcher.isEmpty()).toBe(true);
});
test('clone() 应该创建独立的副本', () => {
const original = Matcher.all(Position, Velocity)
.any(Health)
.none(Dead)
.withTag(100)
.withName('Original')
.withComponent(Weapon);
const cloned = original.clone();
const originalCondition = original.getCondition();
const clonedCondition = cloned.getCondition();
expect(clonedCondition.all).toEqual(originalCondition.all);
expect(clonedCondition.any).toEqual(originalCondition.any);
expect(clonedCondition.none).toEqual(originalCondition.none);
expect(clonedCondition.tag).toBe(originalCondition.tag);
expect(clonedCondition.name).toBe(originalCondition.name);
expect(clonedCondition.component).toBe(originalCondition.component);
// 修改克隆不应影响原对象
cloned.all(Shield).withTag(200);
expect(original.getCondition().all).not.toContain(Shield);
expect(original.getCondition().tag).toBe(100);
});
test('toString() 应该生成可读的字符串表示', () => {
const matcher = Matcher.all(Position, Velocity)
.any(Health, Shield)
.none(Dead)
.withTag(123)
.withName('TestEntity')
.withComponent(Weapon);
const str = matcher.toString();
expect(str).toContain('all(Position, Velocity)');
expect(str).toContain('any(Health, Shield)');
expect(str).toContain('none(Dead)');
expect(str).toContain('tag(123)');
expect(str).toContain('name(TestEntity)');
expect(str).toContain('component(Weapon)');
expect(str).toContain('Matcher[');
expect(str).toContain(' & ');
});
test('toString() 应该处理空条件', () => {
const emptyMatcher = Matcher.empty();
const str = emptyMatcher.toString();
expect(str).toBe('Matcher[]');
});
test('toString() 应该处理部分条件', () => {
const matcher = Matcher.all(Position).withTag(42);
const str = matcher.toString();
expect(str).toContain('all(Position)');
expect(str).toContain('tag(42)');
expect(str).not.toContain('any(');
expect(str).not.toContain('none(');
});
});
describe('实际使用场景测试', () => {
test('游戏系统中的移动实体查询', () => {
// 查询所有可移动的实体(有位置和速度的)
const movableEntities = scene.querySystem.queryAll(Position, Velocity);
expect(movableEntities.entities.length).toBe(2); // MovingAlive, MovingDead
describe('getCondition() 返回值', () => {
test('应该返回只读的条件副本', () => {
const matcher = Matcher.all(Position, Velocity);
const condition1 = matcher.getCondition();
const condition2 = matcher.getCondition();
movableEntities.entities.forEach(entity => {
const pos = entity.getComponent(Position)!;
const vel = entity.getComponent(Velocity)!;
expect(pos).toBeDefined();
expect(vel).toBeDefined();
});
// 应该是不同的对象实例
expect(condition1).not.toBe(condition2);
// 但内容应该相同
expect(condition1.all).toEqual(condition2.all);
});
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);
test('修改返回的条件不应影响原 Matcher', () => {
const matcher = Matcher.all(Position);
const condition = matcher.getCondition();
// 尝试修改返回的条件
condition.all.push(Velocity as ComponentType);
// 原 Matcher 不应被影响
const freshCondition = matcher.getCondition();
expect(freshCondition.all).toHaveLength(1);
expect(freshCondition.all).toContain(Position);
expect(freshCondition.all).not.toContain(Velocity);
});
});
describe('边界情况', () => {
test('应该处理空参数调用', () => {
const matcher = Matcher.empty();
matcher.all();
matcher.any();
matcher.none();
const condition = matcher.getCondition();
expect(condition.all).toHaveLength(0);
expect(condition.any).toHaveLength(0);
expect(condition.none).toHaveLength(0);
});
test('应该处理重复的组件类型', () => {
const matcher = Matcher.all(Position, Position, Velocity);
const condition = matcher.getCondition();
expect(condition.all).toHaveLength(3);
expect(condition.all.filter(t => t === Position)).toHaveLength(2);
});
test('应该处理标签值为0的情况', () => {
const matcher = Matcher.byTag(0);
const condition = matcher.getCondition();
expect(condition.tag).toBe(0);
expect(matcher.isEmpty()).toBe(false);
});
test('应该处理空字符串名称', () => {
const matcher = Matcher.byName('');
const condition = matcher.getCondition();
expect(condition.name).toBe('');
expect(matcher.isEmpty()).toBe(false);
});
test('reset() 应该返回自身以支持链式调用', () => {
const matcher = Matcher.all(Position);
const result = matcher.reset();
expect(result).toBe(matcher);
});
test('所有实例方法都应该返回自身以支持链式调用', () => {
const matcher = Matcher.empty();
expect(matcher.all(Position)).toBe(matcher);
expect(matcher.any(Health)).toBe(matcher);
expect(matcher.none(Dead)).toBe(matcher);
expect(matcher.exclude(Weapon)).toBe(matcher);
expect(matcher.one(Shield)).toBe(matcher);
expect(matcher.withTag(1)).toBe(matcher);
expect(matcher.withName('test')).toBe(matcher);
expect(matcher.withComponent(Position)).toBe(matcher);
expect(matcher.withoutTag()).toBe(matcher);
expect(matcher.withoutName()).toBe(matcher);
expect(matcher.withoutComponent()).toBe(matcher);
});
});
describe('类型安全性', () => {
test('ComponentType 应该正确工作', () => {
// 这个测试主要是确保类型编译正确
const matcher = Matcher.all(Position as ComponentType<Position>);
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[]) {
test('应该支持泛型组件类型', () => {
class GenericComponent<T> extends Component {
public data: T;
constructor(data: T, ...args: unknown[]) {
super();
this.data = data;
}
}
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 {
class StringComponent extends GenericComponent<string> {
constructor(...args: unknown[]) {
super();
super(args[0] as string || 'default');
}
}
const result = scene.querySystem.queryAll(NonExistentComponent);
expect(result.entities.length).toBe(0);
});
test('Matcher条件构建的边界情况', () => {
const emptyMatcher = Matcher.complex();
const condition = emptyMatcher.getCondition();
class NumberComponent extends GenericComponent<number> {
constructor(...args: unknown[]) {
super(args[0] as number || 0);
}
}
expect(condition.all.length).toBe(0);
expect(condition.any.length).toBe(0);
expect(condition.none.length).toBe(0);
const matcher = Matcher.all(StringComponent, NumberComponent);
const condition = matcher.getCondition();
expect(condition.all).toContain(StringComponent);
expect(condition.all).toContain(NumberComponent);
});
});
});