优化EntitySystem初始化逻辑/防止多次初始化

增加matcher和entitysystem的测试
This commit is contained in:
YHH
2025-07-30 15:42:19 +08:00
parent 01fa33e122
commit 65386ff731
13 changed files with 2685 additions and 5 deletions

View File

@@ -0,0 +1,180 @@
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
// 简单测试组件
class TestPositionComponent extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class TestVelocityComponent extends Component {
constructor(public vx: number = 0, public vy: number = 0) {
super();
}
}
class TestHealthComponent extends Component {
constructor(public health: number = 100) {
super();
}
}
describe('Matcher基础功能测试', () => {
let typeManager: ComponentTypeManager;
beforeEach(() => {
typeManager = ComponentTypeManager.instance;
typeManager.reset();
});
describe('基础匹配测试', () => {
test('空匹配器匹配所有实体', () => {
const matcher = Matcher.empty();
const entity1 = new Entity('Entity1', 1);
const entity2 = new Entity('Entity2', 2);
entity2.addComponent(new TestPositionComponent(10, 20));
expect(matcher.isInterestedEntity(entity1)).toBe(true);
expect(matcher.isInterestedEntity(entity2)).toBe(true);
});
test('单一all条件匹配', () => {
const matcher = Matcher.empty().all(TestPositionComponent);
const entityWith = new Entity('With', 1);
entityWith.addComponent(new TestPositionComponent(10, 20));
const entityWithout = new Entity('Without', 2);
expect(matcher.isInterestedEntity(entityWith)).toBe(true);
expect(matcher.isInterestedEntity(entityWithout)).toBe(false);
});
test('多个all条件匹配', () => {
const matcher = Matcher.empty().all(TestPositionComponent, TestVelocityComponent);
const completeEntity = new Entity('Complete', 1);
completeEntity.addComponent(new TestPositionComponent(10, 20));
completeEntity.addComponent(new TestVelocityComponent(1, 1));
const partialEntity = new Entity('Partial', 2);
partialEntity.addComponent(new TestPositionComponent(0, 0));
expect(matcher.isInterestedEntity(completeEntity)).toBe(true);
expect(matcher.isInterestedEntity(partialEntity)).toBe(false);
});
test('exclude条件匹配', () => {
const matcher = Matcher.empty()
.all(TestPositionComponent)
.exclude(TestHealthComponent);
const normalEntity = new Entity('Normal', 1);
normalEntity.addComponent(new TestPositionComponent(10, 20));
const excludedEntity = new Entity('Excluded', 2);
excludedEntity.addComponent(new TestPositionComponent(50, 60));
excludedEntity.addComponent(new TestHealthComponent(100));
expect(matcher.isInterestedEntity(normalEntity)).toBe(true);
expect(matcher.isInterestedEntity(excludedEntity)).toBe(false);
});
test('one条件匹配', () => {
const matcher = Matcher.empty().one(TestVelocityComponent, TestHealthComponent);
const velocityEntity = new Entity('Velocity', 1);
velocityEntity.addComponent(new TestVelocityComponent(1, 1));
const healthEntity = new Entity('Health', 2);
healthEntity.addComponent(new TestHealthComponent(100));
const bothEntity = new Entity('Both', 3);
bothEntity.addComponent(new TestVelocityComponent(2, 2));
bothEntity.addComponent(new TestHealthComponent(80));
const neitherEntity = new Entity('Neither', 4);
neitherEntity.addComponent(new TestPositionComponent(0, 0));
expect(matcher.isInterestedEntity(velocityEntity)).toBe(true);
expect(matcher.isInterestedEntity(healthEntity)).toBe(true);
expect(matcher.isInterestedEntity(bothEntity)).toBe(true);
expect(matcher.isInterestedEntity(neitherEntity)).toBe(false);
});
});
describe('复杂组合测试', () => {
test('all + exclude组合', () => {
const matcher = Matcher.empty()
.all(TestPositionComponent, TestVelocityComponent)
.exclude(TestHealthComponent);
const validEntity = new Entity('Valid', 1);
validEntity.addComponent(new TestPositionComponent(10, 20));
validEntity.addComponent(new TestVelocityComponent(1, 1));
const excludedEntity = new Entity('Excluded', 2);
excludedEntity.addComponent(new TestPositionComponent(30, 40));
excludedEntity.addComponent(new TestVelocityComponent(2, 2));
excludedEntity.addComponent(new TestHealthComponent(100));
expect(matcher.isInterestedEntity(validEntity)).toBe(true);
expect(matcher.isInterestedEntity(excludedEntity)).toBe(false);
});
test('all + one组合', () => {
const matcher = Matcher.empty()
.all(TestPositionComponent)
.one(TestVelocityComponent, TestHealthComponent);
const velocityEntity = new Entity('Velocity', 1);
velocityEntity.addComponent(new TestPositionComponent(10, 20));
velocityEntity.addComponent(new TestVelocityComponent(1, 1));
const healthEntity = new Entity('Health', 2);
healthEntity.addComponent(new TestPositionComponent(30, 40));
healthEntity.addComponent(new TestHealthComponent(100));
const invalidEntity = new Entity('Invalid', 3);
invalidEntity.addComponent(new TestPositionComponent(50, 60));
expect(matcher.isInterestedEntity(velocityEntity)).toBe(true);
expect(matcher.isInterestedEntity(healthEntity)).toBe(true);
expect(matcher.isInterestedEntity(invalidEntity)).toBe(false);
});
});
describe('匹配器属性测试', () => {
test('获取匹配器配置', () => {
const matcher = Matcher.empty()
.all(TestPositionComponent, TestVelocityComponent)
.exclude(TestHealthComponent);
expect(matcher.getAllSet()).toEqual([TestPositionComponent, TestVelocityComponent]);
expect(matcher.getExclusionSet()).toEqual([TestHealthComponent]);
expect(matcher.getOneSet()).toEqual([]);
});
test('toString方法', () => {
const emptyMatcher = Matcher.empty();
expect(emptyMatcher.toString()).toBe('Matcher()');
const simpleMatcher = Matcher.empty().all(TestPositionComponent);
expect(simpleMatcher.toString()).toContain('all: [TestPositionComponent]');
});
test('链式调用返回同一实例', () => {
const matcher = Matcher.empty();
const result = matcher.all(TestPositionComponent).exclude(TestHealthComponent);
expect(result).toBe(matcher);
});
});
afterEach(() => {
typeManager.reset();
});
});

View File

@@ -0,0 +1,493 @@
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
// 测试组件定义
class PositionComponent extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class VelocityComponent extends Component {
constructor(public vx: number = 0, public vy: number = 0) {
super();
}
}
class HealthComponent extends Component {
constructor(public health: number = 100, public maxHealth: number = 100) {
super();
}
}
class RenderComponent extends Component {
constructor(public visible: boolean = true, public layer: number = 0) {
super();
}
}
class AIComponent extends Component {
constructor(public behavior: string = 'idle') {
super();
}
}
class PlayerComponent extends Component {
constructor(public playerId: number = 0) {
super();
}
}
class WeaponComponent extends Component {
constructor(public damage: number = 10, public range: number = 100) {
super();
}
}
class ArmorComponent extends Component {
constructor(public defense: number = 5) {
super();
}
}
describe('Matcher综合测试', () => {
let typeManager: ComponentTypeManager;
beforeEach(() => {
// 重置组件类型管理器以确保测试隔离
typeManager = ComponentTypeManager.instance;
typeManager.reset();
});
describe('基础匹配器创建和配置', () => {
test('空匹配器应该匹配所有实体', () => {
const matcher = Matcher.empty();
// 创建具有不同组件的实体
const entity1 = new Entity('Entity1', 1);
const entity2 = new Entity('Entity2', 2);
entity2.addComponent(new PositionComponent(10, 20));
const entity3 = new Entity('Entity3', 3);
entity3.addComponent(new PositionComponent(0, 0));
entity3.addComponent(new VelocityComponent(1, 1));
// 空匹配器应该匹配所有实体
expect(matcher.isInterestedEntity(entity1)).toBe(true);
expect(matcher.isInterestedEntity(entity2)).toBe(true);
expect(matcher.isInterestedEntity(entity3)).toBe(true);
});
test('单一all条件匹配器', () => {
const matcher = Matcher.empty().all(PositionComponent);
const entityWithPosition = new Entity('WithPosition', 1);
entityWithPosition.addComponent(new PositionComponent(10, 20));
const entityWithoutPosition = new Entity('WithoutPosition', 2);
entityWithoutPosition.addComponent(new VelocityComponent(1, 1));
expect(matcher.isInterestedEntity(entityWithPosition)).toBe(true);
expect(matcher.isInterestedEntity(entityWithoutPosition)).toBe(false);
});
test('多个all条件匹配器', () => {
const matcher = Matcher.empty().all(PositionComponent, VelocityComponent);
const completeEntity = new Entity('Complete', 1);
completeEntity.addComponent(new PositionComponent(10, 20));
completeEntity.addComponent(new VelocityComponent(1, 1));
const partialEntity1 = new Entity('Partial1', 2);
partialEntity1.addComponent(new PositionComponent(0, 0));
const partialEntity2 = new Entity('Partial2', 3);
partialEntity2.addComponent(new VelocityComponent(1, 1));
const emptyEntity = new Entity('Empty', 4);
expect(matcher.isInterestedEntity(completeEntity)).toBe(true);
expect(matcher.isInterestedEntity(partialEntity1)).toBe(false);
expect(matcher.isInterestedEntity(partialEntity2)).toBe(false);
expect(matcher.isInterestedEntity(emptyEntity)).toBe(false);
});
test('exclude条件匹配器', () => {
const matcher = Matcher.empty()
.all(PositionComponent)
.exclude(AIComponent);
const playerEntity = new Entity('Player', 1);
playerEntity.addComponent(new PositionComponent(10, 20));
playerEntity.addComponent(new PlayerComponent(1));
const aiEntity = new Entity('AI', 2);
aiEntity.addComponent(new PositionComponent(50, 60));
aiEntity.addComponent(new AIComponent('patrol'));
const staticEntity = new Entity('Static', 3);
staticEntity.addComponent(new RenderComponent());
expect(matcher.isInterestedEntity(playerEntity)).toBe(true);
expect(matcher.isInterestedEntity(aiEntity)).toBe(false); // 被exclude排除
expect(matcher.isInterestedEntity(staticEntity)).toBe(false); // 缺少required组件
});
test('one条件匹配器', () => {
const matcher = Matcher.empty().one(WeaponComponent, ArmorComponent);
const weaponEntity = new Entity('Weapon', 1);
weaponEntity.addComponent(new WeaponComponent(15, 150));
const armorEntity = new Entity('Armor', 2);
armorEntity.addComponent(new ArmorComponent(8));
const bothEntity = new Entity('Both', 3);
bothEntity.addComponent(new WeaponComponent(20, 200));
bothEntity.addComponent(new ArmorComponent(10));
const neitherEntity = new Entity('Neither', 4);
neitherEntity.addComponent(new PositionComponent(0, 0));
expect(matcher.isInterestedEntity(weaponEntity)).toBe(true);
expect(matcher.isInterestedEntity(armorEntity)).toBe(true);
expect(matcher.isInterestedEntity(bothEntity)).toBe(true);
expect(matcher.isInterestedEntity(neitherEntity)).toBe(false);
});
});
describe('复杂匹配器组合', () => {
test('all + exclude组合', () => {
// 匹配有位置和速度但不是AI的实体
const matcher = Matcher.empty()
.all(PositionComponent, VelocityComponent)
.exclude(AIComponent);
const playerEntity = new Entity('Player', 1);
playerEntity.addComponent(new PositionComponent(10, 20));
playerEntity.addComponent(new VelocityComponent(2, 2));
playerEntity.addComponent(new PlayerComponent(1));
const aiEntity = new Entity('AI', 2);
aiEntity.addComponent(new PositionComponent(50, 60));
aiEntity.addComponent(new VelocityComponent(1, 0));
aiEntity.addComponent(new AIComponent('chase'));
const incompleteEntity = new Entity('Incomplete', 3);
incompleteEntity.addComponent(new PositionComponent(0, 0));
expect(matcher.isInterestedEntity(playerEntity)).toBe(true);
expect(matcher.isInterestedEntity(aiEntity)).toBe(false);
expect(matcher.isInterestedEntity(incompleteEntity)).toBe(false);
});
test('all + one组合', () => {
// 匹配有位置,且有武器或护甲的实体
const matcher = Matcher.empty()
.all(PositionComponent)
.one(WeaponComponent, ArmorComponent);
const warriorEntity = new Entity('Warrior', 1);
warriorEntity.addComponent(new PositionComponent(10, 20));
warriorEntity.addComponent(new WeaponComponent(25, 180));
const guardEntity = new Entity('Guard', 2);
guardEntity.addComponent(new PositionComponent(30, 40));
guardEntity.addComponent(new ArmorComponent(12));
const knightEntity = new Entity('Knight', 3);
knightEntity.addComponent(new PositionComponent(50, 60));
knightEntity.addComponent(new WeaponComponent(30, 200));
knightEntity.addComponent(new ArmorComponent(15));
const civilianEntity = new Entity('Civilian', 4);
civilianEntity.addComponent(new PositionComponent(70, 80));
civilianEntity.addComponent(new HealthComponent(80));
const weaponNoPositionEntity = new Entity('WeaponNoPos', 5);
weaponNoPositionEntity.addComponent(new WeaponComponent(20, 160));
expect(matcher.isInterestedEntity(warriorEntity)).toBe(true);
expect(matcher.isInterestedEntity(guardEntity)).toBe(true);
expect(matcher.isInterestedEntity(knightEntity)).toBe(true);
expect(matcher.isInterestedEntity(civilianEntity)).toBe(false);
expect(matcher.isInterestedEntity(weaponNoPositionEntity)).toBe(false);
});
test('all + exclude + one组合', () => {
// 匹配有位置和健康有武器或护甲但不是AI的实体
const matcher = Matcher.empty()
.all(PositionComponent, HealthComponent)
.exclude(AIComponent)
.one(WeaponComponent, ArmorComponent);
const playerWarriorEntity = new Entity('PlayerWarrior', 1);
playerWarriorEntity.addComponent(new PositionComponent(10, 20));
playerWarriorEntity.addComponent(new HealthComponent(120));
playerWarriorEntity.addComponent(new WeaponComponent(25, 180));
playerWarriorEntity.addComponent(new PlayerComponent(1));
const aiWarriorEntity = new Entity('AIWarrior', 2);
aiWarriorEntity.addComponent(new PositionComponent(30, 40));
aiWarriorEntity.addComponent(new HealthComponent(100));
aiWarriorEntity.addComponent(new WeaponComponent(20, 160));
aiWarriorEntity.addComponent(new AIComponent('attack'));
const civilianEntity = new Entity('Civilian', 3);
civilianEntity.addComponent(new PositionComponent(50, 60));
civilianEntity.addComponent(new HealthComponent(80));
// 没有武器或护甲
const incompleteEntity = new Entity('Incomplete', 4);
incompleteEntity.addComponent(new PositionComponent(70, 80));
incompleteEntity.addComponent(new WeaponComponent(15, 140));
// 没有健康组件
expect(matcher.isInterestedEntity(playerWarriorEntity)).toBe(true);
expect(matcher.isInterestedEntity(aiWarriorEntity)).toBe(false); // 被AI排除
expect(matcher.isInterestedEntity(civilianEntity)).toBe(false); // 缺少武器/护甲
expect(matcher.isInterestedEntity(incompleteEntity)).toBe(false); // 缺少健康组件
});
});
describe('匹配器性能和缓存测试', () => {
test('位掩码缓存应该正确工作', () => {
const matcher = Matcher.empty().all(PositionComponent, VelocityComponent);
// 创建测试实体
const entity = new Entity('TestEntity', 1);
entity.addComponent(new PositionComponent(10, 20));
entity.addComponent(new VelocityComponent(1, 1));
// 第一次匹配会构建缓存
const result1 = matcher.isInterestedEntity(entity);
// 再次匹配应该使用缓存
const result2 = matcher.isInterestedEntity(entity);
const result3 = matcher.isInterestedEntity(entity);
expect(result1).toBe(true);
expect(result2).toBe(true);
expect(result3).toBe(true);
});
test('修改匹配器后应该重新构建缓存', () => {
const matcher = Matcher.empty().all(PositionComponent);
const entity = new Entity('TestEntity', 1);
entity.addComponent(new PositionComponent(10, 20));
entity.addComponent(new VelocityComponent(1, 1));
// 初始匹配
expect(matcher.isInterestedEntity(entity)).toBe(true);
// 修改匹配器
matcher.all(HealthComponent);
// 应该重新计算匹配结果
expect(matcher.isInterestedEntity(entity)).toBe(false);
// 添加健康组件后应该匹配
entity.addComponent(new HealthComponent(100));
expect(matcher.isInterestedEntity(entity)).toBe(true);
});
test('适量实体匹配性能测试', () => {
const matcher = Matcher.empty()
.all(PositionComponent, VelocityComponent)
.exclude(AIComponent);
// 创建适量测试实体
const entities: Entity[] = [];
for (let i = 0; i < 100; i++) {
const entity = new Entity(`Entity${i}`, i);
entity.addComponent(new PositionComponent(i, i));
if (i % 2 === 0) {
entity.addComponent(new VelocityComponent(1, 1));
}
if (i % 5 === 0) {
entity.addComponent(new AIComponent('patrol'));
}
entities.push(entity);
}
// 测试匹配性能
const startTime = performance.now();
let matchCount = 0;
for (const entity of entities) {
if (matcher.isInterestedEntity(entity)) {
matchCount++;
}
}
const endTime = performance.now();
const executionTime = endTime - startTime;
// 性能验证100个实体的匹配应该在合理时间内完成
expect(executionTime).toBeLessThan(50); // 50ms内完成
// 逻辑验证只有偶数索引且不是5的倍数的实体应该匹配
// 偶数50个5的倍数20个重叠的偶数且是5倍数10个
// 所以匹配的应该是50 - 10 = 40个
expect(matchCount).toBe(40);
});
});
describe('边界情况和错误处理', () => {
test('重复添加同一组件类型', () => {
const matcher = Matcher.empty()
.all(PositionComponent)
.all(PositionComponent); // 重复添加
const entity = new Entity('TestEntity', 1);
entity.addComponent(new PositionComponent(10, 20));
// 应该仍然正常工作
expect(matcher.isInterestedEntity(entity)).toBe(true);
// 检查内部状态
expect(matcher.getAllSet().length).toBe(2); // 会有重复
});
test('空实体匹配测试', () => {
const matchers = [
Matcher.empty().all(PositionComponent),
Matcher.empty().exclude(PositionComponent),
Matcher.empty().one(PositionComponent, VelocityComponent)
];
const emptyEntity = new Entity('EmptyEntity', 1);
expect(matchers[0].isInterestedEntity(emptyEntity)).toBe(false); // all
expect(matchers[1].isInterestedEntity(emptyEntity)).toBe(true); // exclude
expect(matchers[2].isInterestedEntity(emptyEntity)).toBe(false); // one
});
test('实体组件动态变化', () => {
const matcher = Matcher.empty()
.all(PositionComponent, VelocityComponent)
.exclude(AIComponent);
const entity = new Entity('DynamicEntity', 1);
// 初始状态:无组件
expect(matcher.isInterestedEntity(entity)).toBe(false);
// 添加位置组件
entity.addComponent(new PositionComponent(10, 20));
expect(matcher.isInterestedEntity(entity)).toBe(false);
// 添加速度组件
entity.addComponent(new VelocityComponent(1, 1));
expect(matcher.isInterestedEntity(entity)).toBe(true);
// 添加AI组件被排除
entity.addComponent(new AIComponent('idle'));
expect(matcher.isInterestedEntity(entity)).toBe(false);
// 移除AI组件
const aiComponent = entity.getComponent(AIComponent);
if (aiComponent) {
entity.removeComponent(aiComponent);
}
expect(matcher.isInterestedEntity(entity)).toBe(true);
});
test('链式调用应该返回同一个匹配器实例', () => {
const matcher = Matcher.empty();
const result = matcher
.all(PositionComponent)
.exclude(AIComponent)
.one(WeaponComponent, ArmorComponent);
expect(result).toBe(matcher);
});
});
describe('匹配器调试和工具方法', () => {
test('toString方法应该返回有意义的描述', () => {
const emptyMatcher = Matcher.empty();
expect(emptyMatcher.toString()).toBe('Matcher()');
const simpleMatcher = Matcher.empty().all(PositionComponent);
expect(simpleMatcher.toString()).toContain('all: [PositionComponent]');
const complexMatcher = Matcher.empty()
.all(PositionComponent, VelocityComponent)
.exclude(AIComponent)
.one(WeaponComponent, ArmorComponent);
const str = complexMatcher.toString();
expect(str).toContain('all: [PositionComponent, VelocityComponent]');
expect(str).toContain('exclude: [AIComponent]');
expect(str).toContain('one: [WeaponComponent, ArmorComponent]');
});
test('获取匹配器配置', () => {
const matcher = Matcher.empty()
.all(PositionComponent, VelocityComponent)
.exclude(AIComponent, PlayerComponent)
.one(WeaponComponent);
expect(matcher.getAllSet()).toEqual([PositionComponent, VelocityComponent]);
expect(matcher.getExclusionSet()).toEqual([AIComponent, PlayerComponent]);
expect(matcher.getOneSet()).toEqual([WeaponComponent]);
});
});
describe('位掩码直接匹配测试', () => {
test('isInterested方法应该正确处理Bits对象', () => {
const matcher = Matcher.empty().all(PositionComponent, VelocityComponent);
// 创建包含Position和Velocity的位掩码
const matchingBits = typeManager.createBits(PositionComponent, VelocityComponent);
expect(matcher.isInterested(matchingBits)).toBe(true);
// 创建只包含Position的位掩码
const partialBits = typeManager.createBits(PositionComponent);
expect(matcher.isInterested(partialBits)).toBe(false);
// 创建包含Position、Velocity和Health的位掩码
const extraBits = typeManager.createBits(PositionComponent, VelocityComponent, HealthComponent);
expect(matcher.isInterested(extraBits)).toBe(true);
});
test('复杂位掩码匹配测试', () => {
const matcher = Matcher.empty()
.all(PositionComponent)
.exclude(AIComponent)
.one(WeaponComponent, ArmorComponent);
// 匹配情况Position + Weapon
const bits1 = typeManager.createBits(PositionComponent, WeaponComponent);
expect(matcher.isInterested(bits1)).toBe(true);
// 匹配情况Position + Armor
const bits2 = typeManager.createBits(PositionComponent, ArmorComponent);
expect(matcher.isInterested(bits2)).toBe(true);
// 不匹配Position + AI + Weapon被排除
const bits3 = typeManager.createBits(PositionComponent, AIComponent, WeaponComponent);
expect(matcher.isInterested(bits3)).toBe(false);
// 不匹配Position only缺少one条件
const bits4 = typeManager.createBits(PositionComponent);
expect(matcher.isInterested(bits4)).toBe(false);
// 不匹配Weapon only缺少all条件
const bits5 = typeManager.createBits(WeaponComponent);
expect(matcher.isInterested(bits5)).toBe(false);
});
});
afterEach(() => {
// 清理组件类型管理器
typeManager.reset();
});
});

View File

@@ -0,0 +1,273 @@
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
// 简单测试组件
class SimplePositionComponent extends Component {
constructor(public x: number = 0, public y: number = 0) {
super();
}
}
class SimpleVelocityComponent extends Component {
constructor(public vx: number = 0, public vy: number = 0) {
super();
}
}
class SimpleHealthComponent extends Component {
constructor(public health: number = 100) {
super();
}
}
describe('Matcher集成测试', () => {
beforeEach(() => {
// 重置组件类型管理器
ComponentTypeManager.instance.reset();
// 重置Entity的静态eventBus以避免副作用
Entity.eventBus = null;
});
describe('基础实体匹配测试', () => {
test('空匹配器匹配所有实体', () => {
const matcher = Matcher.empty();
const entity1 = new Entity('Entity1', 1);
const entity2 = new Entity('Entity2', 2);
expect(matcher.isInterestedEntity(entity1)).toBe(true);
expect(matcher.isInterestedEntity(entity2)).toBe(true);
});
test('单一组件匹配测试', () => {
const matcher = Matcher.empty().all(SimplePositionComponent);
const entityWithoutComponents = new Entity('Empty', 1);
expect(matcher.isInterestedEntity(entityWithoutComponents)).toBe(false);
// 小心添加组件,这里可能是问题所在
const entityWithPosition = new Entity('WithPosition', 2);
try {
entityWithPosition.addComponent(new SimplePositionComponent(10, 20));
expect(matcher.isInterestedEntity(entityWithPosition)).toBe(true);
} catch (error) {
console.error('Error adding component:', error);
throw error;
}
});
test('多组件匹配测试', () => {
const matcher = Matcher.empty().all(SimplePositionComponent, SimpleVelocityComponent);
const completeEntity = new Entity('Complete', 1);
completeEntity.addComponent(new SimplePositionComponent(10, 20));
completeEntity.addComponent(new SimpleVelocityComponent(1, 1));
const partialEntity = new Entity('Partial', 2);
partialEntity.addComponent(new SimplePositionComponent(0, 0));
expect(matcher.isInterestedEntity(completeEntity)).toBe(true);
expect(matcher.isInterestedEntity(partialEntity)).toBe(false);
});
test('排除组件匹配测试', () => {
const matcher = Matcher.empty()
.all(SimplePositionComponent)
.exclude(SimpleHealthComponent);
const normalEntity = new Entity('Normal', 1);
normalEntity.addComponent(new SimplePositionComponent(10, 20));
const excludedEntity = new Entity('Excluded', 2);
excludedEntity.addComponent(new SimplePositionComponent(50, 60));
excludedEntity.addComponent(new SimpleHealthComponent(100));
expect(matcher.isInterestedEntity(normalEntity)).toBe(true);
expect(matcher.isInterestedEntity(excludedEntity)).toBe(false);
});
test('任一组件匹配测试', () => {
const matcher = Matcher.empty().one(SimpleVelocityComponent, SimpleHealthComponent);
const velocityEntity = new Entity('Velocity', 1);
velocityEntity.addComponent(new SimpleVelocityComponent(1, 1));
const healthEntity = new Entity('Health', 2);
healthEntity.addComponent(new SimpleHealthComponent(100));
const neitherEntity = new Entity('Neither', 3);
neitherEntity.addComponent(new SimplePositionComponent(0, 0));
expect(matcher.isInterestedEntity(velocityEntity)).toBe(true);
expect(matcher.isInterestedEntity(healthEntity)).toBe(true);
expect(matcher.isInterestedEntity(neitherEntity)).toBe(false);
});
});
describe('复杂匹配组合测试', () => {
test('all + exclude组合', () => {
const matcher = Matcher.empty()
.all(SimplePositionComponent, SimpleVelocityComponent)
.exclude(SimpleHealthComponent);
const validEntity = new Entity('Valid', 1);
validEntity.addComponent(new SimplePositionComponent(10, 20));
validEntity.addComponent(new SimpleVelocityComponent(1, 1));
const excludedEntity = new Entity('Excluded', 2);
excludedEntity.addComponent(new SimplePositionComponent(30, 40));
excludedEntity.addComponent(new SimpleVelocityComponent(2, 2));
excludedEntity.addComponent(new SimpleHealthComponent(100));
const incompleteEntity = new Entity('Incomplete', 3);
incompleteEntity.addComponent(new SimplePositionComponent(50, 60));
expect(matcher.isInterestedEntity(validEntity)).toBe(true);
expect(matcher.isInterestedEntity(excludedEntity)).toBe(false);
expect(matcher.isInterestedEntity(incompleteEntity)).toBe(false);
});
test('all + one组合', () => {
const matcher = Matcher.empty()
.all(SimplePositionComponent)
.one(SimpleVelocityComponent, SimpleHealthComponent);
const velocityEntity = new Entity('Velocity', 1);
velocityEntity.addComponent(new SimplePositionComponent(10, 20));
velocityEntity.addComponent(new SimpleVelocityComponent(1, 1));
const healthEntity = new Entity('Health', 2);
healthEntity.addComponent(new SimplePositionComponent(30, 40));
healthEntity.addComponent(new SimpleHealthComponent(100));
const bothEntity = new Entity('Both', 3);
bothEntity.addComponent(new SimplePositionComponent(50, 60));
bothEntity.addComponent(new SimpleVelocityComponent(2, 2));
bothEntity.addComponent(new SimpleHealthComponent(80));
const invalidEntity = new Entity('Invalid', 4);
invalidEntity.addComponent(new SimplePositionComponent(70, 80));
expect(matcher.isInterestedEntity(velocityEntity)).toBe(true);
expect(matcher.isInterestedEntity(healthEntity)).toBe(true);
expect(matcher.isInterestedEntity(bothEntity)).toBe(true);
expect(matcher.isInterestedEntity(invalidEntity)).toBe(false);
});
});
describe('动态组件变化测试', () => {
test('实体组件动态添加', () => {
const matcher = Matcher.empty().all(SimplePositionComponent, SimpleVelocityComponent);
const entity = new Entity('Dynamic', 1);
// 初始状态:不匹配
expect(matcher.isInterestedEntity(entity)).toBe(false);
// 添加位置组件:仍不匹配
entity.addComponent(new SimplePositionComponent(10, 20));
expect(matcher.isInterestedEntity(entity)).toBe(false);
// 添加速度组件:现在匹配
entity.addComponent(new SimpleVelocityComponent(1, 1));
expect(matcher.isInterestedEntity(entity)).toBe(true);
});
test('匹配器配置修改', () => {
const matcher = Matcher.empty().all(SimplePositionComponent);
const entity = new Entity('Test', 1);
entity.addComponent(new SimplePositionComponent(10, 20));
entity.addComponent(new SimpleVelocityComponent(1, 1));
// 初始匹配
expect(matcher.isInterestedEntity(entity)).toBe(true);
// 修改匹配器,添加更多要求
matcher.all(SimpleHealthComponent);
// 现在应该不匹配(缺少健康组件)
expect(matcher.isInterestedEntity(entity)).toBe(false);
// 添加健康组件后应该匹配
entity.addComponent(new SimpleHealthComponent(100));
expect(matcher.isInterestedEntity(entity)).toBe(true);
});
});
describe('边界情况测试', () => {
test('空实体测试', () => {
const allMatcher = Matcher.empty().all(SimplePositionComponent);
const excludeMatcher = Matcher.empty().exclude(SimplePositionComponent);
const oneMatcher = Matcher.empty().one(SimplePositionComponent, SimpleVelocityComponent);
const emptyEntity = new Entity('Empty', 1);
expect(allMatcher.isInterestedEntity(emptyEntity)).toBe(false);
expect(excludeMatcher.isInterestedEntity(emptyEntity)).toBe(true);
expect(oneMatcher.isInterestedEntity(emptyEntity)).toBe(false);
});
test('重复组件类型配置', () => {
const matcher = Matcher.empty()
.all(SimplePositionComponent)
.all(SimplePositionComponent); // 重复添加
const entity = new Entity('Test', 1);
entity.addComponent(new SimplePositionComponent(10, 20));
// 应该仍然正常工作
expect(matcher.isInterestedEntity(entity)).toBe(true);
// 内部应该有重复的组件类型
expect(matcher.getAllSet().length).toBe(2);
});
});
describe('匹配器工具方法测试', () => {
test('toString方法', () => {
const emptyMatcher = Matcher.empty();
expect(emptyMatcher.toString()).toBe('Matcher()');
const simpleMatcher = Matcher.empty().all(SimplePositionComponent);
const str = simpleMatcher.toString();
expect(str).toContain('all: [SimplePositionComponent]');
const complexMatcher = Matcher.empty()
.all(SimplePositionComponent, SimpleVelocityComponent)
.exclude(SimpleHealthComponent);
const complexStr = complexMatcher.toString();
expect(complexStr).toContain('all: [SimplePositionComponent, SimpleVelocityComponent]');
expect(complexStr).toContain('exclude: [SimpleHealthComponent]');
});
test('配置获取方法', () => {
const matcher = Matcher.empty()
.all(SimplePositionComponent, SimpleVelocityComponent)
.exclude(SimpleHealthComponent);
expect(matcher.getAllSet()).toEqual([SimplePositionComponent, SimpleVelocityComponent]);
expect(matcher.getExclusionSet()).toEqual([SimpleHealthComponent]);
expect(matcher.getOneSet()).toEqual([]);
});
test('链式调用', () => {
const matcher = Matcher.empty();
const result = matcher
.all(SimplePositionComponent)
.exclude(SimpleHealthComponent)
.one(SimpleVelocityComponent);
expect(result).toBe(matcher);
});
});
afterEach(() => {
// 清理
ComponentTypeManager.instance.reset();
Entity.eventBus = null;
});
});

View File

@@ -0,0 +1,26 @@
import { Matcher } from '../../../src/ECS/Utils/Matcher';
describe('Matcher最小测试', () => {
test('创建空匹配器', () => {
const matcher = Matcher.empty();
expect(matcher).toBeDefined();
expect(matcher.toString()).toBe('Matcher()');
});
test('匹配器配置方法', () => {
const matcher = Matcher.empty();
// 这些方法应该返回匹配器本身
expect(matcher.all()).toBe(matcher);
expect(matcher.exclude()).toBe(matcher);
expect(matcher.one()).toBe(matcher);
});
test('获取匹配器配置', () => {
const matcher = Matcher.empty();
expect(matcher.getAllSet()).toEqual([]);
expect(matcher.getExclusionSet()).toEqual([]);
expect(matcher.getOneSet()).toEqual([]);
});
});