导出soa装饰器
This commit is contained in:
25
package-lock.json
generated
25
package-lock.json
generated
@@ -564,6 +564,10 @@
|
|||||||
"resolved": "packages/core",
|
"resolved": "packages/core",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@esengine/ecs-framework-math": {
|
||||||
|
"resolved": "packages/math",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@esengine/ecs-framework-network": {
|
"node_modules/@esengine/ecs-framework-network": {
|
||||||
"resolved": "packages/network",
|
"resolved": "packages/network",
|
||||||
"link": true
|
"link": true
|
||||||
@@ -11527,9 +11531,28 @@
|
|||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/math": {
|
||||||
|
"name": "@esengine/ecs-framework-math",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.3",
|
||||||
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@types/jest": "^29.5.14",
|
||||||
|
"@types/node": "^20.19.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"rimraf": "^5.0.0",
|
||||||
|
"rollup": "^4.42.0",
|
||||||
|
"rollup-plugin-dts": "^6.2.1",
|
||||||
|
"ts-jest": "^29.4.0",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/network": {
|
"packages/network": {
|
||||||
"name": "@esengine/ecs-framework-network",
|
"name": "@esengine/ecs-framework-network",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isomorphic-ws": "^5.0.0",
|
"isomorphic-ws": "^5.0.0",
|
||||||
|
|||||||
@@ -18,11 +18,13 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"clean": "lerna run clean",
|
"clean": "lerna run clean",
|
||||||
"build": "npm run build:core && npm run build:network",
|
"build": "npm run build:core && npm run build:math && npm run build:network",
|
||||||
"build:core": "cd packages/core && npm run build",
|
"build:core": "cd packages/core && npm run build",
|
||||||
|
"build:math": "cd packages/math && npm run build",
|
||||||
"build:network": "cd packages/network && npm run build",
|
"build:network": "cd packages/network && npm run build",
|
||||||
"build:npm": "npm run build:npm:core && npm run build:npm:network",
|
"build:npm": "npm run build:npm:core && npm run build:npm:math && npm run build:npm:network",
|
||||||
"build:npm:core": "cd packages/core && npm run build:npm",
|
"build:npm:core": "cd packages/core && npm run build:npm",
|
||||||
|
"build:npm:math": "cd packages/math && npm run build:npm",
|
||||||
"build:npm:network": "cd packages/network && npm run build:npm",
|
"build:npm:network": "cd packages/network && npm run build:npm",
|
||||||
"test": "lerna run test",
|
"test": "lerna run test",
|
||||||
"test:coverage": "lerna run test:coverage",
|
"test:coverage": "lerna run test:coverage",
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export { ComponentPool, ComponentPoolManager } from '../ComponentPool';
|
export { ComponentPool, ComponentPoolManager } from '../ComponentPool';
|
||||||
export { ComponentStorage, ComponentRegistry } from '../ComponentStorage';
|
export { ComponentStorage, ComponentRegistry } from '../ComponentStorage';
|
||||||
|
export { EnableSoA, HighPrecision, Float64, Float32, Int32, SerializeMap, SoAStorage } from '../SoAStorage';
|
||||||
@@ -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 { Component } from '../../../src/ECS/Component';
|
||||||
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
import { Matcher } from '../../../src/ECS/Utils/Matcher';
|
||||||
|
import { ComponentType } from '../../../src/ECS/Core/ComponentStorage';
|
||||||
|
|
||||||
// 测试组件
|
|
||||||
class Position extends Component {
|
class Position extends Component {
|
||||||
public x: number = 0;
|
public x: number = 0;
|
||||||
public y: 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 {}
|
class Dead extends Component {}
|
||||||
|
|
||||||
describe('Matcher测试套件', () => {
|
class Weapon extends Component {
|
||||||
let scene: Scene;
|
public damage: number = 10;
|
||||||
let entities: Entity[];
|
|
||||||
|
|
||||||
beforeEach(() => {
|
constructor(...args: unknown[]) {
|
||||||
scene = new Scene();
|
super();
|
||||||
scene.begin();
|
const [damage = 10] = args as [number?];
|
||||||
|
this.damage = damage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 创建测试实体
|
describe('Matcher', () => {
|
||||||
entities = [];
|
describe('静态工厂方法', () => {
|
||||||
|
test('all() 应该创建包含所有指定组件的条件', () => {
|
||||||
// 实体1: 移动的活体
|
const matcher = Matcher.all(Position, Velocity);
|
||||||
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();
|
const condition = matcher.getCondition();
|
||||||
|
|
||||||
|
expect(condition.all).toHaveLength(2);
|
||||||
expect(condition.all).toContain(Position);
|
expect(condition.all).toContain(Position);
|
||||||
expect(condition.all).toContain(Health);
|
expect(condition.all).toContain(Velocity);
|
||||||
expect(condition.all.length).toBe(2);
|
expect(condition.any).toHaveLength(0);
|
||||||
|
expect(condition.none).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Matcher.any()应该创建正确的查询条件', () => {
|
test('any() 应该创建包含任一指定组件的条件', () => {
|
||||||
const matcher = Matcher.any(Health, Dead);
|
const matcher = Matcher.any(Health, Shield);
|
||||||
const condition = matcher.getCondition();
|
const condition = matcher.getCondition();
|
||||||
|
|
||||||
|
expect(condition.any).toHaveLength(2);
|
||||||
expect(condition.any).toContain(Health);
|
expect(condition.any).toContain(Health);
|
||||||
expect(condition.any).toContain(Dead);
|
expect(condition.any).toContain(Shield);
|
||||||
expect(condition.any.length).toBe(2);
|
expect(condition.all).toHaveLength(0);
|
||||||
|
expect(condition.none).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Matcher.none()应该创建正确的查询条件', () => {
|
test('none() 应该创建排除指定组件的条件', () => {
|
||||||
const matcher = Matcher.none(Dead);
|
const matcher = Matcher.none(Dead, Weapon);
|
||||||
const condition = matcher.getCondition();
|
const condition = matcher.getCondition();
|
||||||
|
|
||||||
|
expect(condition.none).toHaveLength(2);
|
||||||
expect(condition.none).toContain(Dead);
|
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('链式调用应该正确工作', () => {
|
test('byTag() 应该创建标签查询条件', () => {
|
||||||
const matcher = Matcher.all(Position)
|
const matcher = Matcher.byTag(123);
|
||||||
.any(Health, Velocity)
|
|
||||||
.none(Dead);
|
|
||||||
|
|
||||||
const condition = matcher.getCondition();
|
const condition = matcher.getCondition();
|
||||||
expect(condition.all).toContain(Position);
|
|
||||||
expect(condition.any).toContain(Health);
|
expect(condition.tag).toBe(123);
|
||||||
expect(condition.any).toContain(Velocity);
|
expect(condition.all).toHaveLength(0);
|
||||||
expect(condition.none).toContain(Dead);
|
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 matcher = Matcher.byComponent(Position);
|
||||||
const condition = matcher.getCondition();
|
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);
|
expect(condition.component).toBe(Position);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('byTag()应该创建标签查询条件', () => {
|
test('withoutTag() 应该移除标签条件', () => {
|
||||||
const matcher = Matcher.byTag(123);
|
const matcher = Matcher.byTag(123).withoutTag();
|
||||||
const condition = matcher.getCondition();
|
const condition = matcher.getCondition();
|
||||||
|
|
||||||
expect(condition.tag).toBe(123);
|
expect(condition.tag).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('byName()应该创建名称查询条件', () => {
|
test('withoutName() 应该移除名称条件', () => {
|
||||||
const matcher = Matcher.byName('TestEntity');
|
const matcher = Matcher.byName('Test').withoutName();
|
||||||
const condition = matcher.getCondition();
|
const condition = matcher.getCondition();
|
||||||
|
|
||||||
expect(condition.name).toBe('TestEntity');
|
expect(condition.name).toBeUndefined();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('QuerySystem集成测试', () => {
|
test('withoutComponent() 应该移除单组件条件', () => {
|
||||||
test('使用QuerySystem的queryAll()查询所有匹配实体', () => {
|
const matcher = Matcher.byComponent(Position).withoutComponent();
|
||||||
const result = scene.querySystem.queryAll(Position, Health);
|
const condition = matcher.getCondition();
|
||||||
expect(result.entities.map(e => e.name).sort()).toEqual(['MovingAlive', 'StillAlive']);
|
|
||||||
});
|
expect(condition.component).toBeUndefined();
|
||||||
|
});
|
||||||
test('使用QuerySystem的queryAny()查询任一匹配实体', () => {
|
|
||||||
const result = scene.querySystem.queryAny(Health, Dead);
|
test('覆盖条件应该替换之前的值', () => {
|
||||||
expect(result.entities.length).toBe(4); // 所有实体都有Health或Dead
|
const matcher = Matcher.byTag(100)
|
||||||
});
|
.withTag(200)
|
||||||
|
.withName('First')
|
||||||
test('使用QuerySystem的queryNone()查询排除实体', () => {
|
.withName('Second')
|
||||||
const result = scene.querySystem.queryNone(Dead);
|
.withComponent(Position)
|
||||||
const aliveEntities = result.entities.filter(e => e.hasComponent(Position));
|
.withComponent(Velocity);
|
||||||
expect(aliveEntities.map(e => e.name).sort()).toEqual(['MovingAlive', 'StillAlive']);
|
|
||||||
});
|
const condition = matcher.getCondition();
|
||||||
|
|
||||||
test('QuerySystem查询性能统计', () => {
|
expect(condition.tag).toBe(200);
|
||||||
scene.querySystem.queryAll(Position, Velocity);
|
expect(condition.name).toBe('Second');
|
||||||
const stats = scene.querySystem.getStats();
|
expect(condition.component).toBe(Velocity);
|
||||||
|
});
|
||||||
expect(stats.queryStats.totalQueries).toBeGreaterThan(0);
|
});
|
||||||
expect(stats.queryStats.cacheHits).toBeGreaterThanOrEqual(0);
|
|
||||||
});
|
describe('工具方法', () => {
|
||||||
});
|
test('isEmpty() 应该正确判断空条件', () => {
|
||||||
|
const emptyMatcher = Matcher.empty();
|
||||||
describe('实际使用场景测试', () => {
|
expect(emptyMatcher.isEmpty()).toBe(true);
|
||||||
test('游戏系统中的移动实体查询', () => {
|
|
||||||
// 查询所有可移动的实体(有位置和速度的)
|
const nonEmptyMatcher = Matcher.all(Position);
|
||||||
const movableEntities = scene.querySystem.queryAll(Position, Velocity);
|
expect(nonEmptyMatcher.isEmpty()).toBe(false);
|
||||||
expect(movableEntities.entities.length).toBe(2); // MovingAlive, MovingDead
|
});
|
||||||
|
|
||||||
movableEntities.entities.forEach(entity => {
|
test('isEmpty() 应该检查所有条件类型', () => {
|
||||||
const pos = entity.getComponent(Position)!;
|
expect(Matcher.all(Position).isEmpty()).toBe(false);
|
||||||
const vel = entity.getComponent(Velocity)!;
|
expect(Matcher.any(Health).isEmpty()).toBe(false);
|
||||||
expect(pos).toBeDefined();
|
expect(Matcher.none(Dead).isEmpty()).toBe(false);
|
||||||
expect(vel).toBeDefined();
|
expect(Matcher.byTag(1).isEmpty()).toBe(false);
|
||||||
});
|
expect(Matcher.byName('test').isEmpty()).toBe(false);
|
||||||
});
|
expect(Matcher.byComponent(Position).isEmpty()).toBe(false);
|
||||||
|
});
|
||||||
test('游戏系统中的活体实体查询', () => {
|
|
||||||
// 查询所有活体实体(有血量,没有死亡标记的)
|
test('reset() 应该清空所有条件', () => {
|
||||||
const aliveEntitiesAll = scene.querySystem.queryAll(Health);
|
const matcher = Matcher.all(Position, Velocity)
|
||||||
const deadEntitiesAll = scene.querySystem.queryAll(Dead);
|
.any(Health, Shield)
|
||||||
|
.none(Dead)
|
||||||
expect(aliveEntitiesAll.entities.length).toBe(2); // MovingAlive, StillAlive
|
.withTag(123)
|
||||||
expect(deadEntitiesAll.entities.length).toBe(2); // MovingDead, StillDead
|
.withName('Test')
|
||||||
});
|
.withComponent(Weapon);
|
||||||
|
|
||||||
test('复杂查询:查找活着的移动实体', () => {
|
matcher.reset();
|
||||||
// 首先获取所有有位置和速度的实体
|
const condition = matcher.getCondition();
|
||||||
const movableEntities = scene.querySystem.queryAll(Position, Velocity);
|
|
||||||
|
expect(condition.all).toHaveLength(0);
|
||||||
// 然后过滤出活着的(有血量的)
|
expect(condition.any).toHaveLength(0);
|
||||||
const aliveMovableEntities = movableEntities.entities.filter(entity =>
|
expect(condition.none).toHaveLength(0);
|
||||||
entity.hasComponent(Health)
|
expect(condition.tag).toBeUndefined();
|
||||||
);
|
expect(condition.name).toBeUndefined();
|
||||||
|
expect(condition.component).toBeUndefined();
|
||||||
expect(aliveMovableEntities.length).toBe(1); // 只有MovingAlive
|
expect(matcher.isEmpty()).toBe(true);
|
||||||
expect(aliveMovableEntities[0].name).toBe('MovingAlive');
|
});
|
||||||
});
|
|
||||||
|
test('clone() 应该创建独立的副本', () => {
|
||||||
test('复合查询条件应用', () => {
|
const original = Matcher.all(Position, Velocity)
|
||||||
// 使用Matcher建立复杂条件,然后用QuerySystem执行
|
.any(Health)
|
||||||
const matcher = Matcher.all(Position).any(Health, Dead);
|
.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('getCondition() 返回值', () => {
|
||||||
|
test('应该返回只读的条件副本', () => {
|
||||||
|
const matcher = Matcher.all(Position, Velocity);
|
||||||
|
const condition1 = matcher.getCondition();
|
||||||
|
const condition2 = matcher.getCondition();
|
||||||
|
|
||||||
|
// 应该是不同的对象实例
|
||||||
|
expect(condition1).not.toBe(condition2);
|
||||||
|
|
||||||
|
// 但内容应该相同
|
||||||
|
expect(condition1.all).toEqual(condition2.all);
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
const condition = matcher.getCondition();
|
||||||
|
|
||||||
// 这里演示如何用条件,实际执行需要QuerySystem支持复合条件
|
|
||||||
expect(condition.all).toContain(Position);
|
expect(condition.all).toContain(Position);
|
||||||
expect(condition.any).toContain(Health);
|
|
||||||
expect(condition.any).toContain(Dead);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('性能测试', () => {
|
test('应该支持泛型组件类型', () => {
|
||||||
test('大量简单查询的性能', () => {
|
class GenericComponent<T> extends Component {
|
||||||
const startTime = performance.now();
|
public data: T;
|
||||||
|
constructor(data: T, ...args: unknown[]) {
|
||||||
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();
|
super();
|
||||||
|
this.data = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = scene.querySystem.queryAll(NonExistentComponent);
|
class StringComponent extends GenericComponent<string> {
|
||||||
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[]) {
|
constructor(...args: unknown[]) {
|
||||||
super();
|
super(args[0] as string || 'default');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = scene.querySystem.queryAll(NonExistentComponent);
|
class NumberComponent extends GenericComponent<number> {
|
||||||
expect(result.entities.length).toBe(0);
|
constructor(...args: unknown[]) {
|
||||||
});
|
super(args[0] as number || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test('Matcher条件构建的边界情况', () => {
|
const matcher = Matcher.all(StringComponent, NumberComponent);
|
||||||
const emptyMatcher = Matcher.complex();
|
const condition = matcher.getCondition();
|
||||||
const condition = emptyMatcher.getCondition();
|
|
||||||
|
|
||||||
expect(condition.all.length).toBe(0);
|
expect(condition.all).toContain(StringComponent);
|
||||||
expect(condition.any.length).toBe(0);
|
expect(condition.all).toContain(NumberComponent);
|
||||||
expect(condition.none.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,15 +1,6 @@
|
|||||||
/**
|
|
||||||
* Matcher性能测试
|
|
||||||
*
|
|
||||||
* 注意:性能测试结果可能因平台而异,主要用于性能回归检测
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Scene } from '../../src/ECS/Scene';
|
|
||||||
import { Entity } from '../../src/ECS/Entity';
|
|
||||||
import { Component } from '../../src/ECS/Component';
|
import { Component } from '../../src/ECS/Component';
|
||||||
import { Matcher } from '../../src/ECS/Utils/Matcher';
|
import { Matcher } from '../../src/ECS/Utils/Matcher';
|
||||||
|
|
||||||
// 测试组件
|
|
||||||
class Position extends Component {
|
class Position extends Component {
|
||||||
public x: number = 0;
|
public x: number = 0;
|
||||||
public y: number = 0;
|
public y: number = 0;
|
||||||
@@ -50,260 +41,301 @@ class Weapon extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Matcher性能测试', () => {
|
describe('Matcher 性能测试', () => {
|
||||||
let scene: Scene;
|
test('大量 Matcher 创建性能', () => {
|
||||||
|
console.log('\n=== Matcher 创建性能测试 ===');
|
||||||
|
|
||||||
beforeEach(() => {
|
const iterationCount = 10000;
|
||||||
scene = new Scene();
|
|
||||||
scene.begin();
|
const staticStart = performance.now();
|
||||||
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
Matcher.all(Position, Velocity);
|
||||||
|
}
|
||||||
|
const staticTime = performance.now() - staticStart;
|
||||||
|
|
||||||
|
const complexStart = performance.now();
|
||||||
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
Matcher.complex()
|
||||||
|
.all(Position, Velocity)
|
||||||
|
.any(Health, Weapon)
|
||||||
|
.none(Weapon);
|
||||||
|
}
|
||||||
|
const complexTime = performance.now() - complexStart;
|
||||||
|
|
||||||
|
console.log(`静态方法创建: ${staticTime.toFixed(3)}ms (${(staticTime/iterationCount*1000).toFixed(3)}μs/次)`);
|
||||||
|
console.log(`复杂链式创建: ${complexTime.toFixed(3)}ms (${(complexTime/iterationCount*1000).toFixed(3)}μs/次)`);
|
||||||
|
|
||||||
|
expect(staticTime).toBeLessThan(1000);
|
||||||
|
expect(complexTime).toBeLessThan(2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
test('Matcher getCondition() 性能', () => {
|
||||||
scene.end();
|
console.log('\n=== getCondition() 性能测试 ===');
|
||||||
|
|
||||||
|
const matcher = Matcher.all(Position, Velocity, Health)
|
||||||
|
.any(Weapon)
|
||||||
|
.none(Health)
|
||||||
|
.withTag(123)
|
||||||
|
.withName('TestEntity')
|
||||||
|
.withComponent(Position);
|
||||||
|
|
||||||
|
const iterationCount = 50000;
|
||||||
|
|
||||||
|
const start = performance.now();
|
||||||
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
matcher.getCondition();
|
||||||
|
}
|
||||||
|
const time = performance.now() - start;
|
||||||
|
|
||||||
|
console.log(`getCondition() 调用: ${time.toFixed(3)}ms (${(time/iterationCount*1000).toFixed(3)}μs/次)`);
|
||||||
|
|
||||||
|
expect(time).toBeLessThan(500);
|
||||||
});
|
});
|
||||||
|
|
||||||
const createTestEntities = (count: number): Entity[] => {
|
test('Matcher clone() 性能', () => {
|
||||||
const entities: Entity[] = [];
|
console.log('\n=== clone() 性能测试 ===');
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const entity = scene.createEntity(`Entity${i}`);
|
|
||||||
|
|
||||||
// 确定性的组件分配
|
const originalMatcher = Matcher.all(Position, Velocity, Health)
|
||||||
if (i % 3 !== 0) entity.addComponent(new Position(i, i));
|
.any(Weapon)
|
||||||
if (i % 2 === 0) entity.addComponent(new Velocity(i % 10, i % 10));
|
.none(Health)
|
||||||
if (i % 4 !== 0) entity.addComponent(new Health(100 - (i % 50)));
|
.withTag(123)
|
||||||
if (i % 5 === 0) entity.addComponent(new Weapon(i % 20));
|
.withName('TestEntity')
|
||||||
|
.withComponent(Position);
|
||||||
|
|
||||||
entities.push(entity);
|
const iterationCount = 10000;
|
||||||
|
|
||||||
|
const start = performance.now();
|
||||||
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
originalMatcher.clone();
|
||||||
}
|
}
|
||||||
return entities;
|
const time = performance.now() - start;
|
||||||
};
|
|
||||||
|
|
||||||
test('小规模性能测试 (100个实体)', () => {
|
console.log(`clone() 调用: ${time.toFixed(3)}ms (${(time/iterationCount*1000).toFixed(3)}μs/次)`);
|
||||||
const entities = createTestEntities(100);
|
|
||||||
const matcher = Matcher.create(scene.querySystem)
|
|
||||||
.all(Position, Velocity);
|
|
||||||
|
|
||||||
console.log('\n=== 小规模测试 (100个实体) ===');
|
expect(time).toBeLessThan(1000);
|
||||||
|
|
||||||
// 传统逐个检查
|
|
||||||
const matcherStart = performance.now();
|
|
||||||
let matcherCount = 0;
|
|
||||||
for (const entity of entities) {
|
|
||||||
if (matcher.matches(entity)) {
|
|
||||||
matcherCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const matcherTime = performance.now() - matcherStart;
|
|
||||||
|
|
||||||
// QuerySystem批量查询
|
|
||||||
const queryStart = performance.now();
|
|
||||||
const queryResult = scene.querySystem.queryAll(Position, Velocity);
|
|
||||||
const queryTime = performance.now() - queryStart;
|
|
||||||
|
|
||||||
// Matcher批量查询
|
|
||||||
const batchStart = performance.now();
|
|
||||||
const batchResult = matcher.query();
|
|
||||||
const batchTime = performance.now() - batchStart;
|
|
||||||
|
|
||||||
console.log(`传统逐个: ${matcherTime.toFixed(3)}ms (${matcherCount}个匹配)`);
|
|
||||||
console.log(`QuerySystem: ${queryTime.toFixed(3)}ms (${queryResult.count}个匹配)`);
|
|
||||||
console.log(`Matcher批量: ${batchTime.toFixed(3)}ms (${batchResult.length}个匹配)`);
|
|
||||||
console.log(`性能提升: ${(matcherTime/batchTime).toFixed(1)}x`);
|
|
||||||
|
|
||||||
// 验证结果一致性
|
|
||||||
expect(matcherCount).toBe(queryResult.count);
|
|
||||||
expect(batchResult.length).toBe(queryResult.count);
|
|
||||||
|
|
||||||
// 性能断言(宽松,避免平台差异)
|
|
||||||
expect(batchTime).toBeLessThan(matcherTime * 2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('中等规模性能测试 (1000个实体)', () => {
|
test('Matcher toString() 性能', () => {
|
||||||
const entities = createTestEntities(1000);
|
console.log('\n=== toString() 性能测试 ===');
|
||||||
const matcher = Matcher.create(scene.querySystem)
|
|
||||||
.all(Position, Velocity);
|
|
||||||
|
|
||||||
console.log('\n=== 中等规模测试 (1000个实体) ===');
|
const simpleMatcherStart = performance.now();
|
||||||
|
const simpleMatcher = Matcher.all(Position);
|
||||||
// 传统方式
|
for (let i = 0; i < 10000; i++) {
|
||||||
const matcherStart = performance.now();
|
simpleMatcher.toString();
|
||||||
let matcherCount = 0;
|
|
||||||
for (const entity of entities) {
|
|
||||||
if (matcher.matches(entity)) {
|
|
||||||
matcherCount++;
|
|
||||||
}
|
}
|
||||||
|
const simpleTime = performance.now() - simpleMatcherStart;
|
||||||
|
|
||||||
|
const complexMatcherStart = performance.now();
|
||||||
|
const complexMatcher = Matcher.all(Position, Velocity, Health)
|
||||||
|
.any(Weapon)
|
||||||
|
.none(Health)
|
||||||
|
.withTag(123)
|
||||||
|
.withName('TestEntity')
|
||||||
|
.withComponent(Position);
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
complexMatcher.toString();
|
||||||
}
|
}
|
||||||
const matcherTime = performance.now() - matcherStart;
|
const complexTime = performance.now() - complexMatcherStart;
|
||||||
|
|
||||||
// QuerySystem方式
|
console.log(`简单 toString(): ${simpleTime.toFixed(3)}ms (${(simpleTime/10000*1000).toFixed(3)}μs/次)`);
|
||||||
const queryStart = performance.now();
|
console.log(`复杂 toString(): ${complexTime.toFixed(3)}ms (${(complexTime/10000*1000).toFixed(3)}μs/次)`);
|
||||||
const queryResult = scene.querySystem.queryAll(Position, Velocity);
|
|
||||||
const queryTime = performance.now() - queryStart;
|
|
||||||
|
|
||||||
console.log(`传统逐个: ${matcherTime.toFixed(3)}ms (${matcherCount}个匹配)`);
|
expect(simpleTime).toBeLessThan(200);
|
||||||
console.log(`QuerySystem: ${queryTime.toFixed(3)}ms (${queryResult.count}个匹配)`);
|
expect(complexTime).toBeLessThan(500);
|
||||||
console.log(`性能提升: ${(matcherTime/queryTime).toFixed(1)}x`);
|
|
||||||
|
|
||||||
expect(matcherCount).toBe(queryResult.count);
|
|
||||||
expect(queryTime).toBeLessThan(matcherTime);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('大规模性能测试 (5000个实体)', () => {
|
test('Matcher isEmpty() 性能', () => {
|
||||||
const entities = createTestEntities(5000);
|
console.log('\n=== isEmpty() 性能测试 ===');
|
||||||
const matcher = Matcher.create(scene.querySystem)
|
|
||||||
.all(Position, Velocity);
|
|
||||||
|
|
||||||
console.log('\n=== 大规模测试 (5000个实体) ===');
|
const emptyMatcher = Matcher.empty();
|
||||||
|
const fullMatcher = Matcher.all(Position, Velocity)
|
||||||
|
.any(Health)
|
||||||
|
.none(Weapon)
|
||||||
|
.withTag(123);
|
||||||
|
|
||||||
// 传统方式
|
const iterationCount = 100000;
|
||||||
const matcherStart = performance.now();
|
|
||||||
let matcherCount = 0;
|
const emptyStart = performance.now();
|
||||||
for (const entity of entities) {
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
if (matcher.matches(entity)) {
|
emptyMatcher.isEmpty();
|
||||||
matcherCount++;
|
|
||||||
}
|
}
|
||||||
|
const emptyTime = performance.now() - emptyStart;
|
||||||
|
|
||||||
|
const fullStart = performance.now();
|
||||||
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
fullMatcher.isEmpty();
|
||||||
}
|
}
|
||||||
const matcherTime = performance.now() - matcherStart;
|
const fullTime = performance.now() - fullStart;
|
||||||
|
|
||||||
// QuerySystem方式
|
console.log(`空匹配器 isEmpty(): ${emptyTime.toFixed(3)}ms (${(emptyTime/iterationCount*1000).toFixed(3)}μs/次)`);
|
||||||
const queryStart = performance.now();
|
console.log(`复杂匹配器 isEmpty(): ${fullTime.toFixed(3)}ms (${(fullTime/iterationCount*1000).toFixed(3)}μs/次)`);
|
||||||
const queryResult = scene.querySystem.queryAll(Position, Velocity);
|
|
||||||
const queryTime = performance.now() - queryStart;
|
|
||||||
|
|
||||||
console.log(`传统逐个: ${matcherTime.toFixed(3)}ms (${matcherCount}个匹配)`);
|
expect(emptyTime).toBeLessThan(100);
|
||||||
console.log(`QuerySystem: ${queryTime.toFixed(3)}ms (${queryResult.count}个匹配)`);
|
expect(fullTime).toBeLessThan(200);
|
||||||
console.log(`性能提升: ${(matcherTime/queryTime).toFixed(1)}x`);
|
|
||||||
|
|
||||||
expect(matcherCount).toBe(queryResult.count);
|
|
||||||
expect(queryTime).toBeLessThan(matcherTime);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('重复查询缓存性能', () => {
|
test('Matcher reset() 性能', () => {
|
||||||
createTestEntities(2000);
|
console.log('\n=== reset() 性能测试 ===');
|
||||||
const matcher = Matcher.create(scene.querySystem)
|
|
||||||
.all(Position, Velocity);
|
|
||||||
const repeatCount = 10;
|
|
||||||
|
|
||||||
console.log('\n=== 重复查询缓存测试 (2000个实体, 10次查询) ===');
|
const iterationCount = 50000;
|
||||||
|
|
||||||
// 首次查询(建立缓存)
|
const start = performance.now();
|
||||||
const firstResult = matcher.query();
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
const matcher = Matcher.all(Position, Velocity, Health)
|
||||||
// 重复查询测试
|
.any(Weapon)
|
||||||
const repeatStart = performance.now();
|
.none(Health)
|
||||||
for (let i = 0; i < repeatCount; i++) {
|
.withTag(123)
|
||||||
const result = matcher.query();
|
.withName('TestEntity')
|
||||||
expect(result.length).toBe(firstResult.length);
|
.withComponent(Position);
|
||||||
|
matcher.reset();
|
||||||
}
|
}
|
||||||
const repeatTime = performance.now() - repeatStart;
|
const time = performance.now() - start;
|
||||||
|
|
||||||
// QuerySystem重复查询对比
|
console.log(`reset() 调用: ${time.toFixed(3)}ms (${(time/iterationCount*1000).toFixed(3)}μs/次)`);
|
||||||
const queryStart = performance.now();
|
|
||||||
for (let i = 0; i < repeatCount; i++) {
|
|
||||||
scene.querySystem.queryAll(Position, Velocity);
|
|
||||||
}
|
|
||||||
const queryTime = performance.now() - queryStart;
|
|
||||||
|
|
||||||
console.log(`Matcher重复: ${repeatTime.toFixed(3)}ms (${(repeatTime/repeatCount).toFixed(3)}ms/次)`);
|
expect(time).toBeLessThan(1000);
|
||||||
console.log(`QuerySystem重复: ${queryTime.toFixed(3)}ms (${(queryTime/repeatCount).toFixed(3)}ms/次)`);
|
|
||||||
console.log(`缓存优势: ${(queryTime/repeatTime).toFixed(1)}x`);
|
|
||||||
|
|
||||||
// 宽松的性能断言(允许平台差异)
|
|
||||||
expect(repeatTime).toBeLessThan(queryTime * 5);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('复杂查询性能测试', () => {
|
test('大规模链式调用性能', () => {
|
||||||
const entities = createTestEntities(1000);
|
console.log('\n=== 大规模链式调用性能测试 ===');
|
||||||
|
|
||||||
console.log('\n=== 复杂查询测试 (1000个实体) ===');
|
const iterationCount = 5000;
|
||||||
console.log('查询条件: all(Position) + any(Velocity, Weapon) + none(Health)');
|
|
||||||
|
|
||||||
// Matcher复杂查询
|
const start = performance.now();
|
||||||
const matcher = Matcher.create(scene.querySystem)
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
Matcher.empty()
|
||||||
.all(Position)
|
.all(Position)
|
||||||
.any(Velocity, Weapon)
|
.all(Velocity)
|
||||||
.none(Health);
|
.all(Health)
|
||||||
|
.any(Weapon)
|
||||||
const matcherStart = performance.now();
|
.any(Health)
|
||||||
let matcherCount = 0;
|
.none(Weapon)
|
||||||
for (const entity of entities) {
|
.none(Health)
|
||||||
if (matcher.matches(entity)) {
|
.withTag(i)
|
||||||
matcherCount++;
|
.withName(`Entity${i}`)
|
||||||
|
.withComponent(Position)
|
||||||
|
.withoutTag()
|
||||||
|
.withoutName()
|
||||||
|
.withoutComponent()
|
||||||
|
.withTag(i * 2)
|
||||||
|
.withName(`NewEntity${i}`)
|
||||||
|
.withComponent(Velocity);
|
||||||
}
|
}
|
||||||
}
|
const time = performance.now() - start;
|
||||||
const matcherTime = performance.now() - matcherStart;
|
|
||||||
|
|
||||||
// QuerySystem分步查询
|
console.log(`大规模链式调用: ${time.toFixed(3)}ms (${(time/iterationCount*1000).toFixed(3)}μs/次)`);
|
||||||
const queryStart = performance.now();
|
|
||||||
const posResult = scene.querySystem.queryAll(Position);
|
|
||||||
const velResult = scene.querySystem.queryAll(Velocity);
|
|
||||||
const weaponResult = scene.querySystem.queryAll(Weapon);
|
|
||||||
const healthResult = scene.querySystem.queryAll(Health);
|
|
||||||
|
|
||||||
const posSet = new Set(posResult.entities);
|
expect(time).toBeLessThan(2000);
|
||||||
const velSet = new Set(velResult.entities);
|
|
||||||
const weaponSet = new Set(weaponResult.entities);
|
|
||||||
const healthSet = new Set(healthResult.entities);
|
|
||||||
|
|
||||||
let queryCount = 0;
|
|
||||||
for (const entity of entities) {
|
|
||||||
const hasPos = posSet.has(entity);
|
|
||||||
const hasVelOrWeapon = velSet.has(entity) || weaponSet.has(entity);
|
|
||||||
const hasHealth = healthSet.has(entity);
|
|
||||||
|
|
||||||
if (hasPos && hasVelOrWeapon && !hasHealth) {
|
|
||||||
queryCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const queryTime = performance.now() - queryStart;
|
|
||||||
|
|
||||||
console.log(`Matcher复杂: ${matcherTime.toFixed(3)}ms (${matcherCount}个匹配)`);
|
|
||||||
console.log(`分步QuerySystem: ${queryTime.toFixed(3)}ms (${queryCount}个匹配)`);
|
|
||||||
console.log(`性能比: ${(matcherTime/queryTime).toFixed(2)}x`);
|
|
||||||
|
|
||||||
// 验证结果一致性
|
|
||||||
expect(matcherCount).toBe(queryCount);
|
|
||||||
|
|
||||||
// 宽松的性能断言(复杂查询可能有差异)
|
|
||||||
expect(matcherTime).toBeLessThan(queryTime * 10);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('缓存失效性能影响', () => {
|
test('内存分配性能测试', () => {
|
||||||
createTestEntities(1000);
|
console.log('\n=== 内存分配性能测试 ===');
|
||||||
const matcher = Matcher.create(scene.querySystem)
|
|
||||||
.all(Position);
|
|
||||||
|
|
||||||
console.log('\n=== 缓存失效性能测试 ===');
|
const iterationCount = 10000;
|
||||||
|
const matchers: Matcher[] = [];
|
||||||
|
|
||||||
// 首次查询
|
const start = performance.now();
|
||||||
const firstStart = performance.now();
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
const firstResult = matcher.query();
|
const matcher = Matcher.all(Position, Velocity)
|
||||||
const firstTime = performance.now() - firstStart;
|
.any(Health, Weapon)
|
||||||
|
.none(Health)
|
||||||
|
.withTag(i)
|
||||||
|
.withName(`Entity${i}`);
|
||||||
|
matchers.push(matcher);
|
||||||
|
}
|
||||||
|
const allocationTime = performance.now() - start;
|
||||||
|
|
||||||
// 重复查询(缓存命中)
|
const cloneStart = performance.now();
|
||||||
const cachedStart = performance.now();
|
const clonedMatchers = matchers.map(m => m.clone());
|
||||||
const cachedResult = matcher.query();
|
const cloneTime = performance.now() - cloneStart;
|
||||||
const cachedTime = performance.now() - cachedStart;
|
|
||||||
|
|
||||||
// 添加新实体(使缓存失效)
|
const conditionStart = performance.now();
|
||||||
const newEntity = scene.createEntity('CacheInvalidator');
|
const conditions = matchers.map(m => m.getCondition());
|
||||||
newEntity.addComponent(new Position(999, 999));
|
const conditionTime = performance.now() - conditionStart;
|
||||||
|
|
||||||
// 缓存失效后的查询
|
console.log(`创建 ${iterationCount} 个 Matcher: ${allocationTime.toFixed(3)}ms`);
|
||||||
const invalidatedStart = performance.now();
|
console.log(`克隆 ${iterationCount} 个 Matcher: ${cloneTime.toFixed(3)}ms`);
|
||||||
const invalidatedResult = matcher.query();
|
console.log(`获取 ${iterationCount} 个条件: ${conditionTime.toFixed(3)}ms`);
|
||||||
const invalidatedTime = performance.now() - invalidatedStart;
|
|
||||||
|
|
||||||
console.log(`首次查询: ${firstTime.toFixed(3)}ms (${firstResult.length}个结果)`);
|
expect(allocationTime).toBeLessThan(1500);
|
||||||
console.log(`缓存查询: ${cachedTime.toFixed(3)}ms (${cachedResult.length}个结果)`);
|
expect(cloneTime).toBeLessThan(1000);
|
||||||
console.log(`失效查询: ${invalidatedTime.toFixed(3)}ms (${invalidatedResult.length}个结果)`);
|
expect(conditionTime).toBeLessThan(500);
|
||||||
|
|
||||||
// 验证功能正确性
|
expect(matchers.length).toBe(iterationCount);
|
||||||
expect(cachedResult.length).toBe(firstResult.length);
|
expect(clonedMatchers.length).toBe(iterationCount);
|
||||||
expect(invalidatedResult.length).toBe(firstResult.length + 1);
|
expect(conditions.length).toBe(iterationCount);
|
||||||
|
});
|
||||||
|
|
||||||
// 性能验证
|
test('字符串操作性能对比', () => {
|
||||||
expect(cachedTime).toBeLessThan(firstTime);
|
console.log('\n=== 字符串操作性能对比 ===');
|
||||||
expect(invalidatedTime).toBeGreaterThan(cachedTime);
|
|
||||||
|
const simpleMatcher = Matcher.all(Position);
|
||||||
|
const complexMatcher = Matcher.all(Position, Velocity, Health, Weapon)
|
||||||
|
.any(Health, Weapon)
|
||||||
|
.none(Position, Velocity)
|
||||||
|
.withTag(123456)
|
||||||
|
.withName('VeryLongEntityNameForPerformanceTesting')
|
||||||
|
.withComponent(Health);
|
||||||
|
|
||||||
|
const iterationCount = 10000;
|
||||||
|
|
||||||
|
const simpleStart = performance.now();
|
||||||
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
simpleMatcher.toString();
|
||||||
|
}
|
||||||
|
const simpleTime = performance.now() - simpleStart;
|
||||||
|
|
||||||
|
const complexStart = performance.now();
|
||||||
|
for (let i = 0; i < iterationCount; i++) {
|
||||||
|
complexMatcher.toString();
|
||||||
|
}
|
||||||
|
const complexTime = performance.now() - complexStart;
|
||||||
|
|
||||||
|
console.log(`简单匹配器字符串化: ${simpleTime.toFixed(3)}ms`);
|
||||||
|
console.log(`复杂匹配器字符串化: ${complexTime.toFixed(3)}ms`);
|
||||||
|
console.log(`复杂度影响: ${(complexTime/simpleTime).toFixed(2)}x`);
|
||||||
|
|
||||||
|
expect(simpleTime).toBeLessThan(200);
|
||||||
|
expect(complexTime).toBeLessThan(800);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('批量操作性能基准', () => {
|
||||||
|
console.log('\n=== 批量操作性能基准 ===');
|
||||||
|
|
||||||
|
const batchSize = 1000;
|
||||||
|
const operationCount = 10;
|
||||||
|
|
||||||
|
const totalStart = performance.now();
|
||||||
|
|
||||||
|
for (let batch = 0; batch < operationCount; batch++) {
|
||||||
|
const matchers: Matcher[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < batchSize; i++) {
|
||||||
|
const matcher = Matcher.complex()
|
||||||
|
.all(Position, Velocity)
|
||||||
|
.any(Health, Weapon)
|
||||||
|
.withTag(batch * batchSize + i);
|
||||||
|
|
||||||
|
matchers.push(matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchers.forEach(m => {
|
||||||
|
m.getCondition();
|
||||||
|
m.toString();
|
||||||
|
m.isEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
const cloned = matchers.map(m => m.clone());
|
||||||
|
cloned.forEach(m => m.reset());
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalTime = performance.now() - totalStart;
|
||||||
|
const totalOperations = batchSize * operationCount * 5; // 每个matcher执行5个操作
|
||||||
|
|
||||||
|
console.log(`批量操作总时间: ${totalTime.toFixed(3)}ms`);
|
||||||
|
console.log(`总操作数: ${totalOperations}`);
|
||||||
|
console.log(`平均每操作: ${(totalTime/totalOperations*1000).toFixed(3)}μs`);
|
||||||
|
|
||||||
|
expect(totalTime).toBeLessThan(5000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@esengine/ecs-framework-math",
|
"name": "@esengine/ecs-framework-math",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"description": "ECS框架2D数学库 - 提供向量、矩阵、几何形状和碰撞检测功能",
|
"description": "ECS框架2D数学库 - 提供向量、矩阵、几何形状和碰撞检测功能",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "bin/index.js",
|
"main": "bin/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@esengine/ecs-framework-network",
|
"name": "@esengine/ecs-framework-network",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "ECS框架网络插件 - 提供TSRPC网络通信、帧同步和快照功能",
|
"description": "ECS框架网络插件 - 提供TSRPC网络通信、帧同步和快照功能",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "bin/index.js",
|
"main": "bin/index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user