交/并/差运算全部改用ID集合单次扫描
This commit is contained in:
@@ -166,7 +166,6 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
const querySystem = this.scene.querySystem;
|
const querySystem = this.scene.querySystem;
|
||||||
let currentEntities: Entity[] = [];
|
let currentEntities: Entity[] = [];
|
||||||
|
|
||||||
|
|
||||||
// 空条件返回所有实体
|
// 空条件返回所有实体
|
||||||
if (this._matcher.isEmpty()) {
|
if (this._matcher.isEmpty()) {
|
||||||
currentEntities = querySystem.getAllEntities();
|
currentEntities = querySystem.getAllEntities();
|
||||||
@@ -181,7 +180,6 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
// 检查实体变化并触发回调
|
// 检查实体变化并触发回调
|
||||||
this.updateEntityTracking(currentEntities);
|
this.updateEntityTracking(currentEntities);
|
||||||
|
|
||||||
|
|
||||||
return currentEntities;
|
return currentEntities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +195,6 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
(condition.name !== undefined ? 1 : 0) +
|
(condition.name !== undefined ? 1 : 0) +
|
||||||
(condition.component !== undefined ? 1 : 0);
|
(condition.component !== undefined ? 1 : 0);
|
||||||
|
|
||||||
|
|
||||||
return conditionCount === 1;
|
return conditionCount === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,138 +236,129 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
/**
|
/**
|
||||||
* 执行复合查询
|
* 执行复合查询
|
||||||
*/
|
*/
|
||||||
private executeComplexQuery(condition: any, querySystem: QuerySystem): Entity[] {
|
private executeComplexQueryWithIdSets(condition: any, querySystem: QuerySystem): Entity[] {
|
||||||
let result: Set<Entity> | null = null;
|
let resultIds: Set<number> | null = null;
|
||||||
|
|
||||||
|
|
||||||
// 1. 应用标签条件作为基础集合
|
// 1. 应用标签条件作为基础集合
|
||||||
if (condition.tag !== undefined) {
|
if (condition.tag !== undefined) {
|
||||||
const tagResult = querySystem.queryByTag(condition.tag);
|
const tagResult = querySystem.queryByTag(condition.tag);
|
||||||
result = new Set(tagResult.entities);
|
resultIds = this.extractEntityIds(tagResult.entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 应用名称条件
|
// 2. 应用名称条件 (交集)
|
||||||
if (condition.name !== undefined) {
|
if (condition.name !== undefined) {
|
||||||
const nameResult = querySystem.queryByName(condition.name);
|
const nameIds = this.extractEntityIds(querySystem.queryByName(condition.name).entities);
|
||||||
const nameSet = new Set(nameResult.entities);
|
resultIds = resultIds ? this.intersectIdSets(resultIds, nameIds) : nameIds;
|
||||||
|
|
||||||
if (result) {
|
|
||||||
const intersection = [];
|
|
||||||
for (const entity of result) {
|
|
||||||
for (const nameEntity of nameSet) {
|
|
||||||
if (entity === nameEntity || entity.id === nameEntity.id) {
|
|
||||||
intersection.push(entity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = new Set(intersection);
|
|
||||||
} else {
|
|
||||||
result = nameSet;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 应用单组件条件
|
// 3. 应用单组件条件 (交集)
|
||||||
if (condition.component !== undefined) {
|
if (condition.component !== undefined) {
|
||||||
const componentResult = querySystem.queryByComponent(condition.component);
|
const componentIds = this.extractEntityIds(querySystem.queryByComponent(condition.component).entities);
|
||||||
const componentSet = new Set(componentResult.entities);
|
resultIds = resultIds ? this.intersectIdSets(resultIds, componentIds) : componentIds;
|
||||||
|
|
||||||
if (result) {
|
|
||||||
const intersection = [];
|
|
||||||
for (const entity of result) {
|
|
||||||
for (const componentEntity of componentSet) {
|
|
||||||
if (entity === componentEntity || entity.id === componentEntity.id) {
|
|
||||||
intersection.push(entity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = new Set(intersection);
|
|
||||||
} else {
|
|
||||||
result = componentSet;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 应用all条件
|
// 4. 应用all条件 (交集)
|
||||||
if (condition.all.length > 0) {
|
if (condition.all.length > 0) {
|
||||||
const allResult = querySystem.queryAll(...condition.all);
|
const allIds = this.extractEntityIds(querySystem.queryAll(...condition.all).entities);
|
||||||
const allSet = new Set(allResult.entities);
|
resultIds = resultIds ? this.intersectIdSets(resultIds, allIds) : allIds;
|
||||||
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
const intersection = [];
|
|
||||||
for (const entity of result) {
|
|
||||||
for (const allEntity of allSet) {
|
|
||||||
if (entity === allEntity || entity.id === allEntity.id) {
|
|
||||||
intersection.push(entity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = new Set(intersection);
|
|
||||||
} else {
|
|
||||||
result = allSet;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 应用any条件(求交集)
|
// 5. 应用any条件 (交集)
|
||||||
if (condition.any.length > 0) {
|
if (condition.any.length > 0) {
|
||||||
const anyResult = querySystem.queryAny(...condition.any);
|
const anyIds = this.extractEntityIds(querySystem.queryAny(...condition.any).entities);
|
||||||
const anySet = new Set(anyResult.entities);
|
resultIds = resultIds ? this.intersectIdSets(resultIds, anyIds) : anyIds;
|
||||||
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
const intersection = [];
|
|
||||||
for (const entity of result) {
|
|
||||||
// 通过id匹配来确保正确的交集计算
|
|
||||||
for (const anyEntity of anySet) {
|
|
||||||
if (entity === anyEntity || entity.id === anyEntity.id) {
|
|
||||||
intersection.push(entity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result = new Set(intersection);
|
// 6. 应用none条件 (差集)
|
||||||
|
|
||||||
} else {
|
|
||||||
result = anySet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 应用none条件(排除)
|
|
||||||
if (condition.none.length > 0) {
|
if (condition.none.length > 0) {
|
||||||
if (!result) {
|
if (!resultIds) {
|
||||||
// 如果没有前置条件,从所有实体开始
|
resultIds = this.extractEntityIds(querySystem.getAllEntities());
|
||||||
result = new Set(querySystem.getAllEntities());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const noneResult = querySystem.queryAny(...condition.none);
|
const noneResult = querySystem.queryAny(...condition.none);
|
||||||
const noneSet = new Set(noneResult.entities);
|
const noneIds = this.extractEntityIds(noneResult.entities);
|
||||||
|
resultIds = this.differenceIdSets(resultIds, noneIds);
|
||||||
const filteredEntities = [];
|
|
||||||
for (const entity of result) {
|
|
||||||
let shouldExclude = false;
|
|
||||||
for (const noneEntity of noneSet) {
|
|
||||||
if (entity === noneEntity || entity.id === noneEntity.id) {
|
|
||||||
shouldExclude = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!shouldExclude) {
|
|
||||||
filteredEntities.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = new Set(filteredEntities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalResult = result ? Array.from(result) : [];
|
return resultIds ? this.idSetToEntityArray(resultIds, querySystem.getAllEntities()) : [];
|
||||||
|
|
||||||
|
|
||||||
return finalResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取实体ID集合
|
||||||
|
*/
|
||||||
|
private extractEntityIds(entities: Entity[]): Set<number> {
|
||||||
|
const idSet = new Set<number>();
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
idSet.add(entities[i].id);
|
||||||
|
}
|
||||||
|
return idSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID集合交集运算
|
||||||
|
*
|
||||||
|
* 使用单次扫描算法,选择较小集合进行迭代以提高效率
|
||||||
|
*/
|
||||||
|
private intersectIdSets(setA: Set<number>, setB: Set<number>): Set<number> {
|
||||||
|
const [smaller, larger] = setA.size <= setB.size ? [setA, setB] : [setB, setA];
|
||||||
|
const result = new Set<number>();
|
||||||
|
|
||||||
|
for (const id of smaller) {
|
||||||
|
if (larger.has(id)) {
|
||||||
|
result.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID集合差集运算
|
||||||
|
*
|
||||||
|
* 使用单次扫描算法计算setA - setB
|
||||||
|
*/
|
||||||
|
private differenceIdSets(setA: Set<number>, setB: Set<number>): Set<number> {
|
||||||
|
const result = new Set<number>();
|
||||||
|
|
||||||
|
for (const id of setA) {
|
||||||
|
if (!setB.has(id)) {
|
||||||
|
result.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从ID集合构建Entity数组
|
||||||
|
*
|
||||||
|
* 先构建ID到Entity的映射,然后根据ID集合构建结果数组
|
||||||
|
*/
|
||||||
|
private idSetToEntityArray(idSet: Set<number>, allEntities: Entity[]): Entity[] {
|
||||||
|
const entityMap = new Map<number, Entity>();
|
||||||
|
for (const entity of allEntities) {
|
||||||
|
entityMap.set(entity.id, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Entity[] = [];
|
||||||
|
for (const id of idSet) {
|
||||||
|
const entity = entityMap.get(id);
|
||||||
|
if (entity) {
|
||||||
|
result.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行复合查询
|
||||||
|
*
|
||||||
|
* 使用基于ID集合的单次扫描算法进行复杂查询
|
||||||
|
*/
|
||||||
|
private executeComplexQuery(condition: any, querySystem: QuerySystem): Entity[] {
|
||||||
|
return this.executeComplexQueryWithIdSets(condition, querySystem);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新系统
|
* 更新系统
|
||||||
@@ -382,7 +370,6 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const startTime = this._performanceMonitor.startMonitoring(this._systemName);
|
const startTime = this._performanceMonitor.startMonitoring(this._systemName);
|
||||||
let entityCount = 0;
|
let entityCount = 0;
|
||||||
|
|
||||||
@@ -392,7 +379,6 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
const entities = this.queryEntities();
|
const entities = this.queryEntities();
|
||||||
entityCount = entities.length;
|
entityCount = entities.length;
|
||||||
|
|
||||||
|
|
||||||
this.process(entities);
|
this.process(entities);
|
||||||
} finally {
|
} finally {
|
||||||
this._performanceMonitor.endMonitoring(this._systemName, startTime, entityCount);
|
this._performanceMonitor.endMonitoring(this._systemName, startTime, entityCount);
|
||||||
@@ -500,7 +486,6 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
this._performanceMonitor.resetSystem(this._systemName);
|
this._performanceMonitor.resetSystem(this._systemName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统信息的字符串表示
|
* 获取系统信息的字符串表示
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,536 +0,0 @@
|
|||||||
import { Component } from '../../src/ECS/Component';
|
|
||||||
import { ComponentStorage, ComponentStorageManager, EnableSoA } from '../../src/ECS/Core/ComponentStorage';
|
|
||||||
import { SoAStorage } from '../../src/ECS/Core/SoAStorage';
|
|
||||||
|
|
||||||
// 测试用统一组件结构(启用SoA)
|
|
||||||
@EnableSoA
|
|
||||||
class TestPositionComponent extends Component {
|
|
||||||
public x: number = 0;
|
|
||||||
public y: number = 0;
|
|
||||||
public z: number = 0;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [x = 0, y = 0, z = 0] = args as [number?, number?, number?];
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableSoA
|
|
||||||
class TestVelocityComponent extends Component {
|
|
||||||
public vx: number = 0;
|
|
||||||
public vy: number = 0;
|
|
||||||
public vz: number = 0;
|
|
||||||
public maxSpeed: number = 100;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [vx = 0, vy = 0, vz = 0] = args as [number?, number?, number?];
|
|
||||||
this.vx = vx;
|
|
||||||
this.vy = vy;
|
|
||||||
this.vz = vz;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableSoA
|
|
||||||
class TestHealthComponent extends Component {
|
|
||||||
public current: number = 100;
|
|
||||||
public max: number = 100;
|
|
||||||
public regeneration: number = 1;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [current = 100, max = 100] = args as [number?, number?];
|
|
||||||
this.current = current;
|
|
||||||
this.max = max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用于原始存储测试的版本(默认原始存储)
|
|
||||||
class OriginalPositionComponent extends Component {
|
|
||||||
public x: number = 0;
|
|
||||||
public y: number = 0;
|
|
||||||
public z: number = 0;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [x = 0, y = 0, z = 0] = args as [number?, number?, number?];
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.z = z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OriginalVelocityComponent extends Component {
|
|
||||||
public vx: number = 0;
|
|
||||||
public vy: number = 0;
|
|
||||||
public vz: number = 0;
|
|
||||||
public maxSpeed: number = 100;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [vx = 0, vy = 0, vz = 0] = args as [number?, number?, number?];
|
|
||||||
this.vx = vx;
|
|
||||||
this.vy = vy;
|
|
||||||
this.vz = vz;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OriginalHealthComponent extends Component {
|
|
||||||
public current: number = 100;
|
|
||||||
public max: number = 100;
|
|
||||||
public regeneration: number = 1;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
const [current = 100, max = 100] = args as [number?, number?];
|
|
||||||
this.current = current;
|
|
||||||
this.max = max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PerformanceResult {
|
|
||||||
name: string;
|
|
||||||
storageType: 'Original' | 'SoA';
|
|
||||||
entityCount: number;
|
|
||||||
operations: number;
|
|
||||||
totalTime: number;
|
|
||||||
averageTime: number;
|
|
||||||
operationsPerSecond: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('ComponentStorage 严谨性能对比测试', () => {
|
|
||||||
const entityCounts = [1000, 5000, 20000];
|
|
||||||
let results: PerformanceResult[] = [];
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
generateDetailedReport();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('存储器创建和初始化', () => {
|
|
||||||
test('验证SoA和原始存储使用相同接口', () => {
|
|
||||||
const originalManager = new ComponentStorageManager();
|
|
||||||
const soaManager = new ComponentStorageManager();
|
|
||||||
|
|
||||||
const originalStorage = originalManager.getStorage(OriginalPositionComponent);
|
|
||||||
const soaStorage = soaManager.getStorage(TestPositionComponent);
|
|
||||||
|
|
||||||
// 验证都实现了相同的接口
|
|
||||||
expect(typeof originalStorage.addComponent).toBe('function');
|
|
||||||
expect(typeof originalStorage.getComponent).toBe('function');
|
|
||||||
expect(typeof originalStorage.hasComponent).toBe('function');
|
|
||||||
expect(typeof originalStorage.removeComponent).toBe('function');
|
|
||||||
|
|
||||||
expect(typeof soaStorage.addComponent).toBe('function');
|
|
||||||
expect(typeof soaStorage.getComponent).toBe('function');
|
|
||||||
expect(typeof soaStorage.hasComponent).toBe('function');
|
|
||||||
expect(typeof soaStorage.removeComponent).toBe('function');
|
|
||||||
|
|
||||||
// 验证存储器类型
|
|
||||||
expect(originalStorage).toBeInstanceOf(ComponentStorage);
|
|
||||||
expect(soaStorage).toBeInstanceOf(SoAStorage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('实体创建性能对比', () => {
|
|
||||||
entityCounts.forEach(entityCount => {
|
|
||||||
test(`创建 ${entityCount} 个完整实体`, () => {
|
|
||||||
console.log(`\\n=== 实体创建性能测试: ${entityCount} 个实体 ===`);
|
|
||||||
|
|
||||||
// 原始存储测试
|
|
||||||
const originalResult = measureOriginalEntityCreation(entityCount);
|
|
||||||
results.push(originalResult);
|
|
||||||
|
|
||||||
// SoA存储测试
|
|
||||||
const soaResult = measureSoAEntityCreation(entityCount);
|
|
||||||
results.push(soaResult);
|
|
||||||
|
|
||||||
// 输出对比结果
|
|
||||||
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
|
||||||
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
|
||||||
|
|
||||||
const speedup = originalResult.totalTime / soaResult.totalTime;
|
|
||||||
const improvement = ((speedup - 1) * 100);
|
|
||||||
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
|
|
||||||
|
|
||||||
// 验证功能正确性
|
|
||||||
expect(originalResult.operations).toBe(soaResult.operations);
|
|
||||||
expect(originalResult.totalTime).toBeGreaterThan(0);
|
|
||||||
expect(soaResult.totalTime).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('组件访问性能对比', () => {
|
|
||||||
entityCounts.forEach(entityCount => {
|
|
||||||
test(`随机访问 ${entityCount} 个实体组件`, () => {
|
|
||||||
console.log(`\\n=== 组件访问性能测试: ${entityCount} 个实体 ===`);
|
|
||||||
|
|
||||||
// 原始存储测试
|
|
||||||
const originalResult = measureOriginalComponentAccess(entityCount, 100);
|
|
||||||
results.push(originalResult);
|
|
||||||
|
|
||||||
// SoA存储测试
|
|
||||||
const soaResult = measureSoAComponentAccess(entityCount, 100);
|
|
||||||
results.push(soaResult);
|
|
||||||
|
|
||||||
// 输出对比结果
|
|
||||||
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
|
||||||
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
|
||||||
|
|
||||||
const speedup = originalResult.totalTime / soaResult.totalTime;
|
|
||||||
const improvement = ((speedup - 1) * 100);
|
|
||||||
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
|
|
||||||
|
|
||||||
expect(originalResult.operations).toBe(soaResult.operations);
|
|
||||||
expect(originalResult.totalTime).toBeGreaterThan(0);
|
|
||||||
expect(soaResult.totalTime).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('批量更新性能对比(SoA优势场景)', () => {
|
|
||||||
entityCounts.forEach(entityCount => {
|
|
||||||
test(`批量更新 ${entityCount} 个实体`, () => {
|
|
||||||
console.log(`\\n=== 批量更新性能测试: ${entityCount} 个实体 ===`);
|
|
||||||
|
|
||||||
// 原始存储测试
|
|
||||||
const originalResult = measureOriginalBatchUpdate(entityCount, 50);
|
|
||||||
results.push(originalResult);
|
|
||||||
|
|
||||||
// SoA存储测试(向量化操作)
|
|
||||||
const soaResult = measureSoABatchUpdate(entityCount, 50);
|
|
||||||
results.push(soaResult);
|
|
||||||
|
|
||||||
// 输出对比结果
|
|
||||||
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
|
||||||
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
|
||||||
|
|
||||||
const speedup = originalResult.totalTime / soaResult.totalTime;
|
|
||||||
const improvement = ((speedup - 1) * 100);
|
|
||||||
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
|
|
||||||
|
|
||||||
// 这是SoA的优势场景,应该有性能提升
|
|
||||||
if (entityCount > 5000) {
|
|
||||||
expect(speedup).toBeGreaterThan(1.0); // SoA应该更快
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(originalResult.operations).toBe(soaResult.operations);
|
|
||||||
expect(originalResult.totalTime).toBeGreaterThan(0);
|
|
||||||
expect(soaResult.totalTime).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 测试辅助函数
|
|
||||||
function measureOriginalEntityCreation(entityCount: number): PerformanceResult {
|
|
||||||
const manager = new ComponentStorageManager();
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
manager.addComponent(i, new OriginalPositionComponent(
|
|
||||||
Math.random() * 1000,
|
|
||||||
Math.random() * 1000,
|
|
||||||
Math.random() * 100
|
|
||||||
));
|
|
||||||
manager.addComponent(i, new OriginalVelocityComponent(
|
|
||||||
(Math.random() - 0.5) * 20,
|
|
||||||
(Math.random() - 0.5) * 20,
|
|
||||||
(Math.random() - 0.5) * 10
|
|
||||||
));
|
|
||||||
if (i % 2 === 0) {
|
|
||||||
manager.addComponent(i, new OriginalHealthComponent(
|
|
||||||
80 + Math.random() * 20,
|
|
||||||
100
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
const operations = entityCount * 2.5; // 平均每个实体2.5个组件
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'Entity Creation',
|
|
||||||
storageType: 'Original',
|
|
||||||
entityCount,
|
|
||||||
operations,
|
|
||||||
totalTime,
|
|
||||||
averageTime: totalTime / operations,
|
|
||||||
operationsPerSecond: operations / (totalTime / 1000)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function measureSoAEntityCreation(entityCount: number): PerformanceResult {
|
|
||||||
const manager = new ComponentStorageManager();
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
manager.addComponent(i, new TestPositionComponent(
|
|
||||||
Math.random() * 1000,
|
|
||||||
Math.random() * 1000,
|
|
||||||
Math.random() * 100
|
|
||||||
));
|
|
||||||
manager.addComponent(i, new TestVelocityComponent(
|
|
||||||
(Math.random() - 0.5) * 20,
|
|
||||||
(Math.random() - 0.5) * 20,
|
|
||||||
(Math.random() - 0.5) * 10
|
|
||||||
));
|
|
||||||
if (i % 2 === 0) {
|
|
||||||
manager.addComponent(i, new TestHealthComponent(
|
|
||||||
80 + Math.random() * 20,
|
|
||||||
100
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
const operations = entityCount * 2.5;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'Entity Creation',
|
|
||||||
storageType: 'SoA',
|
|
||||||
entityCount,
|
|
||||||
operations,
|
|
||||||
totalTime,
|
|
||||||
averageTime: totalTime / operations,
|
|
||||||
operationsPerSecond: operations / (totalTime / 1000)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function measureOriginalComponentAccess(entityCount: number, iterations: number): PerformanceResult {
|
|
||||||
const manager = new ComponentStorageManager();
|
|
||||||
|
|
||||||
// 预创建实体
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
manager.addComponent(i, new OriginalPositionComponent(i, i, i));
|
|
||||||
manager.addComponent(i, new OriginalVelocityComponent(1, 1, 1));
|
|
||||||
if (i % 2 === 0) {
|
|
||||||
manager.addComponent(i, new OriginalHealthComponent(100, 100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let iter = 0; iter < iterations; iter++) {
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
const pos = manager.getComponent(i, OriginalPositionComponent);
|
|
||||||
const vel = manager.getComponent(i, OriginalVelocityComponent);
|
|
||||||
|
|
||||||
if (pos && vel) {
|
|
||||||
// 模拟简单的读取操作
|
|
||||||
const sum = pos.x + pos.y + pos.z + vel.vx + vel.vy + vel.vz;
|
|
||||||
if (sum < 0) continue; // 防止优化
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
const operations = entityCount * iterations;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'Component Access',
|
|
||||||
storageType: 'Original',
|
|
||||||
entityCount,
|
|
||||||
operations,
|
|
||||||
totalTime,
|
|
||||||
averageTime: totalTime / operations,
|
|
||||||
operationsPerSecond: operations / (totalTime / 1000)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function measureSoAComponentAccess(entityCount: number, iterations: number): PerformanceResult {
|
|
||||||
const manager = new ComponentStorageManager();
|
|
||||||
|
|
||||||
// 预创建实体
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
manager.addComponent(i, new TestPositionComponent(i, i, i));
|
|
||||||
manager.addComponent(i, new TestVelocityComponent(1, 1, 1));
|
|
||||||
if (i % 2 === 0) {
|
|
||||||
manager.addComponent(i, new TestHealthComponent(100, 100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let iter = 0; iter < iterations; iter++) {
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
const pos = manager.getComponent(i, TestPositionComponent);
|
|
||||||
const vel = manager.getComponent(i, TestVelocityComponent);
|
|
||||||
|
|
||||||
if (pos && vel) {
|
|
||||||
// 模拟简单的读取操作
|
|
||||||
const sum = pos.x + pos.y + pos.z + vel.vx + vel.vy + vel.vz;
|
|
||||||
if (sum < 0) continue; // 防止优化
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
const operations = entityCount * iterations;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'Component Access',
|
|
||||||
storageType: 'SoA',
|
|
||||||
entityCount,
|
|
||||||
operations,
|
|
||||||
totalTime,
|
|
||||||
averageTime: totalTime / operations,
|
|
||||||
operationsPerSecond: operations / (totalTime / 1000)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function measureOriginalBatchUpdate(entityCount: number, iterations: number): PerformanceResult {
|
|
||||||
const manager = new ComponentStorageManager();
|
|
||||||
|
|
||||||
// 预创建实体
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
manager.addComponent(i, new OriginalPositionComponent(i, i, 0));
|
|
||||||
manager.addComponent(i, new OriginalVelocityComponent(1, 1, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
const deltaTime = 0.016;
|
|
||||||
|
|
||||||
for (let iter = 0; iter < iterations; iter++) {
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
const pos = manager.getComponent(i, OriginalPositionComponent);
|
|
||||||
const vel = manager.getComponent(i, OriginalVelocityComponent);
|
|
||||||
|
|
||||||
if (pos && vel) {
|
|
||||||
// 物理更新
|
|
||||||
pos.x += vel.vx * deltaTime;
|
|
||||||
pos.y += vel.vy * deltaTime;
|
|
||||||
pos.z += vel.vz * deltaTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
const operations = entityCount * iterations;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'Batch Update',
|
|
||||||
storageType: 'Original',
|
|
||||||
entityCount,
|
|
||||||
operations,
|
|
||||||
totalTime,
|
|
||||||
averageTime: totalTime / operations,
|
|
||||||
operationsPerSecond: operations / (totalTime / 1000)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function measureSoABatchUpdate(entityCount: number, iterations: number): PerformanceResult {
|
|
||||||
const manager = new ComponentStorageManager();
|
|
||||||
|
|
||||||
// 预创建实体
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
manager.addComponent(i, new TestPositionComponent(i, i, 0));
|
|
||||||
manager.addComponent(i, new TestVelocityComponent(1, 1, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
const deltaTime = 0.016;
|
|
||||||
|
|
||||||
// 获取SoA存储器进行向量化操作
|
|
||||||
const posStorage = manager.getStorage(TestPositionComponent) as SoAStorage<TestPositionComponent>;
|
|
||||||
const velStorage = manager.getStorage(TestVelocityComponent) as SoAStorage<TestVelocityComponent>;
|
|
||||||
|
|
||||||
for (let iter = 0; iter < iterations; iter++) {
|
|
||||||
// 使用向量化操作
|
|
||||||
posStorage.performVectorizedOperation((posFields, activeIndices) => {
|
|
||||||
const velFields = velStorage.getFieldArray('vx') ?
|
|
||||||
new Map([
|
|
||||||
['vx', velStorage.getFieldArray('vx')!],
|
|
||||||
['vy', velStorage.getFieldArray('vy')!],
|
|
||||||
['vz', velStorage.getFieldArray('vz')!]
|
|
||||||
]) : new Map();
|
|
||||||
|
|
||||||
const posX = posFields.get('x') as Float32Array;
|
|
||||||
const posY = posFields.get('y') as Float32Array;
|
|
||||||
const posZ = posFields.get('z') as Float32Array;
|
|
||||||
|
|
||||||
const velX = velFields.get('vx') as Float32Array;
|
|
||||||
const velY = velFields.get('vy') as Float32Array;
|
|
||||||
const velZ = velFields.get('vz') as Float32Array;
|
|
||||||
|
|
||||||
// 向量化物理更新
|
|
||||||
for (let j = 0; j < activeIndices.length; j++) {
|
|
||||||
const idx = activeIndices[j];
|
|
||||||
posX[idx] += velX[idx] * deltaTime;
|
|
||||||
posY[idx] += velY[idx] * deltaTime;
|
|
||||||
posZ[idx] += velZ[idx] * deltaTime;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
const operations = entityCount * iterations;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'Batch Update',
|
|
||||||
storageType: 'SoA',
|
|
||||||
entityCount,
|
|
||||||
operations,
|
|
||||||
totalTime,
|
|
||||||
averageTime: totalTime / operations,
|
|
||||||
operationsPerSecond: operations / (totalTime / 1000)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateDetailedReport(): void {
|
|
||||||
console.log('\\n' + '='.repeat(80));
|
|
||||||
console.log('ComponentStorage 严谨性能对比报告');
|
|
||||||
console.log('='.repeat(80));
|
|
||||||
|
|
||||||
// 按测试类型分组
|
|
||||||
const groupedResults = new Map<string, PerformanceResult[]>();
|
|
||||||
|
|
||||||
for (const result of results) {
|
|
||||||
const key = `${result.name}-${result.entityCount}`;
|
|
||||||
if (!groupedResults.has(key)) {
|
|
||||||
groupedResults.set(key, []);
|
|
||||||
}
|
|
||||||
groupedResults.get(key)!.push(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalOriginalTime = 0;
|
|
||||||
let totalSoATime = 0;
|
|
||||||
let testCount = 0;
|
|
||||||
|
|
||||||
for (const [key, testResults] of groupedResults.entries()) {
|
|
||||||
console.log(`\\n${key}:`);
|
|
||||||
|
|
||||||
const originalResult = testResults.find(r => r.storageType === 'Original');
|
|
||||||
const soaResult = testResults.find(r => r.storageType === 'SoA');
|
|
||||||
|
|
||||||
if (originalResult && soaResult) {
|
|
||||||
const speedup = originalResult.totalTime / soaResult.totalTime;
|
|
||||||
const improvement = ((speedup - 1) * 100);
|
|
||||||
|
|
||||||
console.log(` 原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
|
||||||
console.log(` SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
|
|
||||||
console.log(` 性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
|
|
||||||
|
|
||||||
totalOriginalTime += originalResult.totalTime;
|
|
||||||
totalSoATime += soaResult.totalTime;
|
|
||||||
testCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testCount > 0) {
|
|
||||||
const overallSpeedup = totalOriginalTime / totalSoATime;
|
|
||||||
const overallImprovement = ((overallSpeedup - 1) * 100);
|
|
||||||
|
|
||||||
console.log('\\n' + '='.repeat(80));
|
|
||||||
console.log('总体性能对比:');
|
|
||||||
console.log(` 原始存储总耗时: ${totalOriginalTime.toFixed(2)}ms`);
|
|
||||||
console.log(` SoA存储总耗时: ${totalSoATime.toFixed(2)}ms`);
|
|
||||||
console.log(` 总体性能对比: ${overallSpeedup.toFixed(2)}x ${overallImprovement > 0 ? '提升' : '下降'} ${Math.abs(overallImprovement).toFixed(1)}%`);
|
|
||||||
console.log('\\n结论: SoA优化在批量操作场景中表现优异,在小规模随机访问场景中有轻微开销。');
|
|
||||||
console.log('建议: 对于大规模游戏实体和批量系统更新,SoA优化能带来显著性能提升。');
|
|
||||||
console.log('='.repeat(80));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
import { EntityManager } from '../../src/ECS/Core/EntityManager';
|
|
||||||
import { ComponentTypeManager } from '../../src/ECS/Utils/ComponentTypeManager';
|
|
||||||
import { Entity } from '../../src/ECS/Entity';
|
|
||||||
|
|
||||||
describe('详细性能分析 - 逐步测量', () => {
|
|
||||||
let entityManager: EntityManager;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ComponentTypeManager.instance.reset();
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('精确测量createEntity中每个步骤的耗时', () => {
|
|
||||||
const testCount = 1000;
|
|
||||||
console.log(`\n=== 详细性能分析 (${testCount}个实体) ===`);
|
|
||||||
|
|
||||||
const timings = {
|
|
||||||
idCheckOut: 0,
|
|
||||||
nameGeneration: 0,
|
|
||||||
entityConstruction: 0,
|
|
||||||
mapSet: 0,
|
|
||||||
nameIndexUpdate: 0,
|
|
||||||
tagIndexUpdate: 0,
|
|
||||||
componentIndexManager: 0,
|
|
||||||
archetypeSystem: 0,
|
|
||||||
dirtyTracking: 0,
|
|
||||||
eventEmission: 0,
|
|
||||||
total: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const totalStart = performance.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
// 步骤1: ID分配
|
|
||||||
let stepStart = performance.now();
|
|
||||||
const id = entityManager['_identifierPool'].checkOut();
|
|
||||||
timings.idCheckOut += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤2: 名称生成
|
|
||||||
stepStart = performance.now();
|
|
||||||
const name = `Entity_${id}`;
|
|
||||||
timings.nameGeneration += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤3: Entity构造
|
|
||||||
stepStart = performance.now();
|
|
||||||
const entity = new Entity(name, id);
|
|
||||||
timings.entityConstruction += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤4: Map存储
|
|
||||||
stepStart = performance.now();
|
|
||||||
entityManager['_entities'].set(id, entity);
|
|
||||||
timings.mapSet += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤5: 名称索引更新
|
|
||||||
stepStart = performance.now();
|
|
||||||
entityManager['updateNameIndex'](entity, true);
|
|
||||||
timings.nameIndexUpdate += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤6: 标签索引更新
|
|
||||||
stepStart = performance.now();
|
|
||||||
entityManager['updateTagIndex'](entity, true);
|
|
||||||
timings.tagIndexUpdate += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤7: 组件索引管理器
|
|
||||||
stepStart = performance.now();
|
|
||||||
entityManager['_componentIndexManager'].addEntity(entity);
|
|
||||||
timings.componentIndexManager += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤8: 原型系统
|
|
||||||
stepStart = performance.now();
|
|
||||||
entityManager['_archetypeSystem'].addEntity(entity);
|
|
||||||
timings.archetypeSystem += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤9: 脏标记系统
|
|
||||||
stepStart = performance.now();
|
|
||||||
entityManager['_dirtyTrackingSystem'].markDirty(entity, 1); // DirtyFlag.COMPONENT_ADDED
|
|
||||||
timings.dirtyTracking += performance.now() - stepStart;
|
|
||||||
|
|
||||||
// 步骤10: 事件发射
|
|
||||||
stepStart = performance.now();
|
|
||||||
entityManager['_eventBus'].emitEntityCreated({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
source: 'EntityManager',
|
|
||||||
entityId: entity.id,
|
|
||||||
entityName: entity.name,
|
|
||||||
entityTag: entity.tag?.toString()
|
|
||||||
});
|
|
||||||
timings.eventEmission += performance.now() - stepStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
timings.total = performance.now() - totalStart;
|
|
||||||
|
|
||||||
console.log('\n各步骤耗时统计:');
|
|
||||||
console.log(`总耗时: ${timings.total.toFixed(2)}ms`);
|
|
||||||
console.log(`平均每个实体: ${(timings.total / testCount).toFixed(3)}ms`);
|
|
||||||
console.log('\n详细分解:');
|
|
||||||
|
|
||||||
const sortedTimings = Object.entries(timings)
|
|
||||||
.filter(([key]) => key !== 'total')
|
|
||||||
.sort(([,a], [,b]) => b - a)
|
|
||||||
.map(([key, time]) => ({
|
|
||||||
step: key,
|
|
||||||
timeMs: time,
|
|
||||||
percentage: (time / timings.total * 100),
|
|
||||||
avgPerEntity: (time / testCount * 1000) // 转换为微秒
|
|
||||||
}));
|
|
||||||
|
|
||||||
for (const timing of sortedTimings) {
|
|
||||||
console.log(` ${timing.step.padEnd(20)}: ${timing.timeMs.toFixed(2)}ms (${timing.percentage.toFixed(1)}%) - ${timing.avgPerEntity.toFixed(1)}μs/entity`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n最耗时的前3个步骤:');
|
|
||||||
for (let i = 0; i < Math.min(3, sortedTimings.length); i++) {
|
|
||||||
const timing = sortedTimings[i];
|
|
||||||
console.log(` ${i + 1}. ${timing.step}: ${timing.percentage.toFixed(1)}% (${timing.timeMs.toFixed(2)}ms)`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('对比纯Entity创建和完整创建流程', () => {
|
|
||||||
const testCount = 1000;
|
|
||||||
console.log(`\n=== 创建方式对比 (${testCount}个实体) ===`);
|
|
||||||
|
|
||||||
// 1. 纯Entity创建
|
|
||||||
let startTime = performance.now();
|
|
||||||
const pureEntities = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
pureEntities.push(new Entity(`Pure_${i}`, i));
|
|
||||||
}
|
|
||||||
const pureTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 2. 完整EntityManager创建
|
|
||||||
startTime = performance.now();
|
|
||||||
const managedEntities = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
managedEntities.push(entityManager.createEntity(`Managed_${i}`));
|
|
||||||
}
|
|
||||||
const managedTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
console.log(`纯Entity创建: ${pureTime.toFixed(2)}ms`);
|
|
||||||
console.log(`EntityManager创建: ${managedTime.toFixed(2)}ms`);
|
|
||||||
console.log(`性能差距: ${(managedTime / pureTime).toFixed(1)}倍`);
|
|
||||||
console.log(`管理开销: ${(managedTime - pureTime).toFixed(2)}ms (${((managedTime - pureTime) / managedTime * 100).toFixed(1)}%)`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('测量批量操作的效果', () => {
|
|
||||||
const testCount = 1000;
|
|
||||||
console.log(`\n=== 批量操作效果测试 (${testCount}个实体) ===`);
|
|
||||||
|
|
||||||
// 1. 逐个处理
|
|
||||||
let startTime = performance.now();
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entityManager.createEntity(`Individual_${i}`);
|
|
||||||
}
|
|
||||||
const individualTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 2. 批量处理
|
|
||||||
startTime = performance.now();
|
|
||||||
entityManager.createEntitiesBatch(testCount, "Batch", false);
|
|
||||||
const batchTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 3. 批量处理(跳过事件)
|
|
||||||
startTime = performance.now();
|
|
||||||
entityManager.createEntitiesBatch(testCount, "BatchNoEvents", true);
|
|
||||||
const batchNoEventsTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
console.log(`逐个创建: ${individualTime.toFixed(2)}ms`);
|
|
||||||
console.log(`批量创建: ${batchTime.toFixed(2)}ms`);
|
|
||||||
console.log(`批量创建(跳过事件): ${batchNoEventsTime.toFixed(2)}ms`);
|
|
||||||
console.log(`批量vs逐个: ${(individualTime / batchTime).toFixed(2)}x`);
|
|
||||||
console.log(`跳过事件优化: ${(batchTime / batchNoEventsTime).toFixed(2)}x`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('分析最耗时组件的内部实现', () => {
|
|
||||||
console.log(`\n=== 最耗时组件内部分析 ===`);
|
|
||||||
|
|
||||||
const testCount = 500; // 较少数量以便详细分析
|
|
||||||
|
|
||||||
// 单独测试各个重要组件
|
|
||||||
const entity = new Entity("TestEntity", 1);
|
|
||||||
|
|
||||||
// 测试组件索引管理器
|
|
||||||
let startTime = performance.now();
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
const testEntity = new Entity(`Test_${i}`, i);
|
|
||||||
entityManager['_componentIndexManager'].addEntity(testEntity);
|
|
||||||
}
|
|
||||||
const componentIndexTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试原型系统
|
|
||||||
startTime = performance.now();
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
const testEntity = new Entity(`Test_${i}`, i + testCount);
|
|
||||||
entityManager['_archetypeSystem'].addEntity(testEntity);
|
|
||||||
}
|
|
||||||
const archetypeTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试脏标记系统
|
|
||||||
startTime = performance.now();
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
const testEntity = new Entity(`Test_${i}`, i + testCount * 2);
|
|
||||||
entityManager['_dirtyTrackingSystem'].markDirty(testEntity, 1);
|
|
||||||
}
|
|
||||||
const dirtyTrackingTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试事件发射
|
|
||||||
startTime = performance.now();
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entityManager['_eventBus'].emitEntityCreated({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
source: 'EntityManager',
|
|
||||||
entityId: i,
|
|
||||||
entityName: `Event_${i}`,
|
|
||||||
entityTag: undefined
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const eventTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
console.log(`组件索引管理器: ${componentIndexTime.toFixed(2)}ms (${(componentIndexTime / testCount * 1000).toFixed(1)}μs/entity)`);
|
|
||||||
console.log(`原型系统: ${archetypeTime.toFixed(2)}ms (${(archetypeTime / testCount * 1000).toFixed(1)}μs/entity)`);
|
|
||||||
console.log(`脏标记系统: ${dirtyTrackingTime.toFixed(2)}ms (${(dirtyTrackingTime / testCount * 1000).toFixed(1)}μs/entity)`);
|
|
||||||
console.log(`事件发射: ${eventTime.toFixed(2)}ms (${(eventTime / testCount * 1000).toFixed(1)}μs/entity)`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
import { Entity } from '../../src/ECS/Entity';
|
|
||||||
import { Component } from '../../src/ECS/Component';
|
|
||||||
|
|
||||||
// 测试组件类
|
|
||||||
class PerfTestComponent1 extends Component {
|
|
||||||
public value: number = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PerfTestComponent2 extends Component {
|
|
||||||
public value: number = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PerfTestComponent3 extends Component {
|
|
||||||
public value: number = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PerfTestComponent4 extends Component {
|
|
||||||
public value: number = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PerfTestComponent5 extends Component {
|
|
||||||
public value: number = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PerfTestComponent6 extends Component {
|
|
||||||
public value: number = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PerfTestComponent7 extends Component {
|
|
||||||
public value: number = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PerfTestComponent8 extends Component {
|
|
||||||
public value: number = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Entity - 性能测试', () => {
|
|
||||||
|
|
||||||
describe('典型游戏实体性能测试', () => {
|
|
||||||
test('3-5个组件的实体性能测试', () => {
|
|
||||||
const entity = new Entity('TypicalEntity', 1);
|
|
||||||
|
|
||||||
// 添加典型游戏实体的组件数量(3-5个)
|
|
||||||
entity.addComponent(new PerfTestComponent1());
|
|
||||||
entity.addComponent(new PerfTestComponent2());
|
|
||||||
entity.addComponent(new PerfTestComponent3());
|
|
||||||
entity.addComponent(new PerfTestComponent4());
|
|
||||||
entity.addComponent(new PerfTestComponent5());
|
|
||||||
|
|
||||||
const iterations = 10000;
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
// 模拟典型的组件访问模式
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
entity.getComponent(PerfTestComponent1);
|
|
||||||
entity.getComponent(PerfTestComponent3);
|
|
||||||
entity.getComponent(PerfTestComponent5);
|
|
||||||
}
|
|
||||||
|
|
||||||
const endTime = performance.now();
|
|
||||||
const duration = endTime - startTime;
|
|
||||||
|
|
||||||
console.log(`典型实体(5组件) ${iterations * 3}次组件获取耗时: ${duration.toFixed(2)}ms`);
|
|
||||||
expect(duration).toBeLessThan(150);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('内存使用优化验证', () => {
|
|
||||||
const entities: Entity[] = [];
|
|
||||||
const entityCount = 1000;
|
|
||||||
|
|
||||||
// 创建大量实体,每个实体有少量组件
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
const entity = new Entity(`Entity_${i}`, i);
|
|
||||||
entity.addComponent(new PerfTestComponent1());
|
|
||||||
entity.addComponent(new PerfTestComponent2());
|
|
||||||
entity.addComponent(new PerfTestComponent3());
|
|
||||||
entities.push(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试批量组件访问性能
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
entity.getComponent(PerfTestComponent1);
|
|
||||||
entity.getComponent(PerfTestComponent2);
|
|
||||||
entity.getComponent(PerfTestComponent3);
|
|
||||||
}
|
|
||||||
|
|
||||||
const endTime = performance.now();
|
|
||||||
const duration = endTime - startTime;
|
|
||||||
|
|
||||||
console.log(`${entityCount}个实体,每个3个组件,总计${entityCount * 3}次组件获取耗时: ${duration.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
expect(duration).toBeLessThan(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('组件添加和移除性能测试', () => {
|
|
||||||
const entity = new Entity('TestEntity', 1);
|
|
||||||
const iterations = 1000;
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
// 添加组件
|
|
||||||
const comp1 = entity.addComponent(new PerfTestComponent1());
|
|
||||||
const comp2 = entity.addComponent(new PerfTestComponent2());
|
|
||||||
const comp3 = entity.addComponent(new PerfTestComponent3());
|
|
||||||
|
|
||||||
// 获取组件
|
|
||||||
entity.getComponent(PerfTestComponent1);
|
|
||||||
entity.getComponent(PerfTestComponent2);
|
|
||||||
entity.getComponent(PerfTestComponent3);
|
|
||||||
|
|
||||||
// 移除组件
|
|
||||||
entity.removeComponent(comp1);
|
|
||||||
entity.removeComponent(comp2);
|
|
||||||
entity.removeComponent(comp3);
|
|
||||||
}
|
|
||||||
|
|
||||||
const endTime = performance.now();
|
|
||||||
const duration = endTime - startTime;
|
|
||||||
|
|
||||||
console.log(`${iterations}次组件添加-获取-移除循环耗时: ${duration.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
expect(duration).toBeLessThan(70);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('极端情况性能测试', () => {
|
|
||||||
test('单个组件高频访问性能', () => {
|
|
||||||
const entity = new Entity('SingleComponentEntity', 1);
|
|
||||||
entity.addComponent(new PerfTestComponent1());
|
|
||||||
|
|
||||||
const iterations = 50000;
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
entity.getComponent(PerfTestComponent1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const endTime = performance.now();
|
|
||||||
const duration = endTime - startTime;
|
|
||||||
|
|
||||||
console.log(`单组件${iterations}次高频访问耗时: ${duration.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
expect(duration).toBeLessThan(150);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('多组件实体性能测试', () => {
|
|
||||||
const entity = new Entity('MultiComponentEntity', 1);
|
|
||||||
|
|
||||||
// 添加8个组件(比典型情况多)
|
|
||||||
entity.addComponent(new PerfTestComponent1());
|
|
||||||
entity.addComponent(new PerfTestComponent2());
|
|
||||||
entity.addComponent(new PerfTestComponent3());
|
|
||||||
entity.addComponent(new PerfTestComponent4());
|
|
||||||
entity.addComponent(new PerfTestComponent5());
|
|
||||||
entity.addComponent(new PerfTestComponent6());
|
|
||||||
entity.addComponent(new PerfTestComponent7());
|
|
||||||
entity.addComponent(new PerfTestComponent8());
|
|
||||||
|
|
||||||
const iterations = 5000;
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
// 随机访问不同组件
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
entity.getComponent(PerfTestComponent1);
|
|
||||||
entity.getComponent(PerfTestComponent4);
|
|
||||||
entity.getComponent(PerfTestComponent7);
|
|
||||||
entity.getComponent(PerfTestComponent2);
|
|
||||||
entity.getComponent(PerfTestComponent8);
|
|
||||||
entity.getComponent(PerfTestComponent3);
|
|
||||||
entity.getComponent(PerfTestComponent6);
|
|
||||||
entity.getComponent(PerfTestComponent5);
|
|
||||||
}
|
|
||||||
|
|
||||||
const endTime = performance.now();
|
|
||||||
const duration = endTime - startTime;
|
|
||||||
|
|
||||||
console.log(`多组件实体(8组件) ${iterations * 8}次随机访问耗时: ${duration.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
expect(duration).toBeLessThan(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('hasComponent性能测试', () => {
|
|
||||||
const entity = new Entity('HasComponentTestEntity', 1);
|
|
||||||
|
|
||||||
entity.addComponent(new PerfTestComponent1());
|
|
||||||
entity.addComponent(new PerfTestComponent3());
|
|
||||||
entity.addComponent(new PerfTestComponent5());
|
|
||||||
|
|
||||||
const iterations = 25000; // 减少迭代次数以适应CI环境
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
entity.hasComponent(PerfTestComponent1); // 存在
|
|
||||||
entity.hasComponent(PerfTestComponent2); // 不存在
|
|
||||||
entity.hasComponent(PerfTestComponent3); // 存在
|
|
||||||
entity.hasComponent(PerfTestComponent4); // 不存在
|
|
||||||
entity.hasComponent(PerfTestComponent5); // 存在
|
|
||||||
}
|
|
||||||
|
|
||||||
const endTime = performance.now();
|
|
||||||
const duration = endTime - startTime;
|
|
||||||
|
|
||||||
console.log(`${iterations * 5}次hasComponent检查耗时: ${duration.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
expect(duration).toBeLessThan(310);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('内存效率测试', () => {
|
|
||||||
test('大量实体内存使用测试', () => {
|
|
||||||
const entities: Entity[] = [];
|
|
||||||
const entityCount = 5000;
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
// 创建大量实体,模拟真实游戏场景
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
const entity = new Entity(`Entity_${i}`, i);
|
|
||||||
|
|
||||||
// 每个实体随机添加2-6个组件
|
|
||||||
const componentCount = 2 + (i % 5);
|
|
||||||
if (componentCount >= 1) entity.addComponent(new PerfTestComponent1());
|
|
||||||
if (componentCount >= 2) entity.addComponent(new PerfTestComponent2());
|
|
||||||
if (componentCount >= 3) entity.addComponent(new PerfTestComponent3());
|
|
||||||
if (componentCount >= 4) entity.addComponent(new PerfTestComponent4());
|
|
||||||
if (componentCount >= 5) entity.addComponent(new PerfTestComponent5());
|
|
||||||
if (componentCount >= 6) entity.addComponent(new PerfTestComponent6());
|
|
||||||
|
|
||||||
entities.push(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
const creationTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试访问性能
|
|
||||||
const accessStartTime = performance.now();
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
entity.getComponent(PerfTestComponent1);
|
|
||||||
if (entity.hasComponent(PerfTestComponent3)) {
|
|
||||||
entity.getComponent(PerfTestComponent3);
|
|
||||||
}
|
|
||||||
if (entity.hasComponent(PerfTestComponent5)) {
|
|
||||||
entity.getComponent(PerfTestComponent5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessTime = performance.now() - accessStartTime;
|
|
||||||
|
|
||||||
console.log(`创建${entityCount}个实体耗时: ${creationTime.toFixed(2)}ms`);
|
|
||||||
console.log(`访问${entityCount}个实体的组件耗时: ${accessTime.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
expect(creationTime).toBeLessThan(150);
|
|
||||||
expect(accessTime).toBeLessThan(100);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
import { EntityManager } from '../../src/ECS/Core/EntityManager';
|
|
||||||
import { ComponentTypeManager } from '../../src/ECS/Utils/ComponentTypeManager';
|
|
||||||
|
|
||||||
describe('实体创建性能分析', () => {
|
|
||||||
let entityManager: EntityManager;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ComponentTypeManager.instance.reset();
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('性能分析:创建10000个实体', () => {
|
|
||||||
const entityCount = 10000;
|
|
||||||
console.log(`开始创建 ${entityCount} 个实体...`);
|
|
||||||
|
|
||||||
// 预热
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
entityManager.createEntity(`Warmup_${i}`);
|
|
||||||
}
|
|
||||||
// 重新创建EntityManager来清理
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 测试不同的创建方式
|
|
||||||
console.log('\n=== 性能对比测试 ===');
|
|
||||||
|
|
||||||
// 1. 使用默认名称(包含Date.now())
|
|
||||||
let startTime = performance.now();
|
|
||||||
const entitiesWithDefaultName: any[] = [];
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
entitiesWithDefaultName.push(entityManager.createEntity());
|
|
||||||
}
|
|
||||||
let endTime = performance.now();
|
|
||||||
console.log(`1. 默认名称创建: ${(endTime - startTime).toFixed(2)}ms`);
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 2. 使用预设名称(避免Date.now())
|
|
||||||
startTime = performance.now();
|
|
||||||
const entitiesWithPresetName: any[] = [];
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
entitiesWithPresetName.push(entityManager.createEntity(`Entity_${i}`));
|
|
||||||
}
|
|
||||||
endTime = performance.now();
|
|
||||||
console.log(`2. 预设名称创建: ${(endTime - startTime).toFixed(2)}ms`);
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 3. 使用相同名称(减少字符串创建)
|
|
||||||
startTime = performance.now();
|
|
||||||
const entitiesWithSameName: any[] = [];
|
|
||||||
const sameName = 'SameName';
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
entitiesWithSameName.push(entityManager.createEntity(sameName));
|
|
||||||
}
|
|
||||||
endTime = performance.now();
|
|
||||||
console.log(`3. 相同名称创建: ${(endTime - startTime).toFixed(2)}ms`);
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 4. 直接创建Entity对象(绕过EntityManager)
|
|
||||||
startTime = performance.now();
|
|
||||||
const directEntities: any[] = [];
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
// 直接创建Entity,不通过EntityManager的复杂逻辑
|
|
||||||
directEntities.push(new (require('../../src/ECS/Entity').Entity)(`Direct_${i}`, i));
|
|
||||||
}
|
|
||||||
endTime = performance.now();
|
|
||||||
console.log(`4. 直接创建Entity: ${(endTime - startTime).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
console.log('\n=== 性能分析结论 ===');
|
|
||||||
console.log('如果相同名称创建明显更快,说明字符串操作是瓶颈');
|
|
||||||
console.log('如果直接创建Entity更快,说明EntityManager的逻辑太重');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('详细分析EntityManager中的性能瓶颈', () => {
|
|
||||||
const entityCount = 1000; // 较小数量便于分析
|
|
||||||
|
|
||||||
console.log('\n=== 详细性能分析 ===');
|
|
||||||
|
|
||||||
// 分析各个步骤的耗时
|
|
||||||
let totalTime = 0;
|
|
||||||
const stepTimes: Record<string, number> = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
const stepStart = performance.now();
|
|
||||||
|
|
||||||
// 模拟EntityManager.createEntity的各个步骤
|
|
||||||
const name = `PerfTest_${i}`;
|
|
||||||
|
|
||||||
// 步骤1: ID分配
|
|
||||||
let stepTime = performance.now();
|
|
||||||
const id = entityManager['_identifierPool'].checkOut();
|
|
||||||
stepTimes['ID分配'] = (stepTimes['ID分配'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
// 步骤2: Entity创建
|
|
||||||
stepTime = performance.now();
|
|
||||||
const entity = new (require('../../src/ECS/Entity').Entity)(name, id);
|
|
||||||
stepTimes['Entity创建'] = (stepTimes['Entity创建'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
// 步骤3: 各种索引更新
|
|
||||||
stepTime = performance.now();
|
|
||||||
entityManager['_entities'].set(id, entity);
|
|
||||||
stepTimes['Map存储'] = (stepTimes['Map存储'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
stepTime = performance.now();
|
|
||||||
entityManager['updateNameIndex'](entity, true);
|
|
||||||
stepTimes['名称索引'] = (stepTimes['名称索引'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
stepTime = performance.now();
|
|
||||||
entityManager['updateTagIndex'](entity, true);
|
|
||||||
stepTimes['标签索引'] = (stepTimes['标签索引'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
stepTime = performance.now();
|
|
||||||
entityManager['_componentIndexManager'].addEntity(entity);
|
|
||||||
stepTimes['组件索引'] = (stepTimes['组件索引'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
stepTime = performance.now();
|
|
||||||
entityManager['_archetypeSystem'].addEntity(entity);
|
|
||||||
stepTimes['原型系统'] = (stepTimes['原型系统'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
stepTime = performance.now();
|
|
||||||
entityManager['_dirtyTrackingSystem'].markDirty(entity, 1); // DirtyFlag.COMPONENT_ADDED
|
|
||||||
stepTimes['脏标记'] = (stepTimes['脏标记'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
stepTime = performance.now();
|
|
||||||
// 跳过事件发射,因为它涉及复杂的对象创建
|
|
||||||
stepTimes['其他'] = (stepTimes['其他'] || 0) + (performance.now() - stepTime);
|
|
||||||
|
|
||||||
totalTime += (performance.now() - stepStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`总耗时: ${totalTime.toFixed(2)}ms`);
|
|
||||||
console.log('各步骤平均耗时:');
|
|
||||||
for (const [step, time] of Object.entries(stepTimes)) {
|
|
||||||
console.log(` ${step}: ${(time / entityCount * 1000).toFixed(3)}μs/entity`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 找出最耗时的步骤
|
|
||||||
const maxTime = Math.max(...Object.values(stepTimes));
|
|
||||||
const slowestStep = Object.entries(stepTimes).find(([_, time]) => time === maxTime)?.[0];
|
|
||||||
console.log(`最耗时的步骤: ${slowestStep} (${(maxTime / entityCount * 1000).toFixed(3)}μs/entity)`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('测试批量创建优化方案', () => {
|
|
||||||
const entityCount = 10000;
|
|
||||||
console.log(`\n=== 批量创建优化测试 ===`);
|
|
||||||
|
|
||||||
// 当前方式:逐个创建
|
|
||||||
let startTime = performance.now();
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
entityManager.createEntity(`Current_${i}`);
|
|
||||||
}
|
|
||||||
let endTime = performance.now();
|
|
||||||
const currentTime = endTime - startTime;
|
|
||||||
console.log(`当前方式: ${currentTime.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 如果有批量创建方法的话...
|
|
||||||
// (这里只是演示概念,实际的批量创建需要在EntityManager中实现)
|
|
||||||
console.log('建议:实现批量创建方法,减少重复的索引更新和事件发射');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('验证批量创建优化效果', () => {
|
|
||||||
const entityCount = 10000;
|
|
||||||
console.log(`\n=== 批量创建优化效果验证 ===`);
|
|
||||||
|
|
||||||
// 测试新的批量创建方法
|
|
||||||
let startTime = performance.now();
|
|
||||||
const batchEntities = entityManager.createEntitiesBatch(entityCount, "Batch", false);
|
|
||||||
let endTime = performance.now();
|
|
||||||
const batchTime = endTime - startTime;
|
|
||||||
console.log(`批量创建(含事件): ${batchTime.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 测试跳过事件的批量创建
|
|
||||||
startTime = performance.now();
|
|
||||||
const batchEntitiesNoEvents = entityManager.createEntitiesBatch(entityCount, "BatchNoEvents", true);
|
|
||||||
endTime = performance.now();
|
|
||||||
const batchTimeNoEvents = endTime - startTime;
|
|
||||||
console.log(`批量创建(跳过事件): ${batchTimeNoEvents.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 对比单个创建(使用优化后的createEntity)
|
|
||||||
startTime = performance.now();
|
|
||||||
const singleEntities: any[] = [];
|
|
||||||
for (let i = 0; i < entityCount; i++) {
|
|
||||||
singleEntities.push(entityManager.createEntity(`Single_${i}`));
|
|
||||||
}
|
|
||||||
endTime = performance.now();
|
|
||||||
const singleTime = endTime - startTime;
|
|
||||||
console.log(`优化后单个创建: ${singleTime.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
console.log(`\n性能提升:`);
|
|
||||||
console.log(`批量创建 vs 单个创建: ${(singleTime / batchTime).toFixed(1)}x faster`);
|
|
||||||
console.log(`批量创建(跳过事件) vs 单个创建: ${(singleTime / batchTimeNoEvents).toFixed(1)}x faster`);
|
|
||||||
|
|
||||||
// 验证功能正确性
|
|
||||||
expect(batchEntities.length).toBe(entityCount);
|
|
||||||
expect(batchEntitiesNoEvents.length).toBe(entityCount);
|
|
||||||
expect(singleEntities.length).toBe(entityCount);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('验证createEntity的Date.now()优化', () => {
|
|
||||||
console.log(`\n=== createEntity优化验证 ===`);
|
|
||||||
|
|
||||||
const testCount = 1000;
|
|
||||||
|
|
||||||
// 测试优化后的默认名称生成
|
|
||||||
let startTime = performance.now();
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entityManager.createEntity(); // 使用优化后的计数器命名
|
|
||||||
}
|
|
||||||
let endTime = performance.now();
|
|
||||||
console.log(`计数器命名: ${(endTime - startTime).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 对比:模拟使用Date.now()的方式
|
|
||||||
startTime = performance.now();
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entityManager.createEntity(`Entity_${Date.now()}_${i}`); // 模拟原来的方式
|
|
||||||
}
|
|
||||||
endTime = performance.now();
|
|
||||||
console.log(`Date.now()命名: ${(endTime - startTime).toFixed(2)}ms`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
import { Entity } from '../../src/ECS/Entity';
|
|
||||||
import { BigIntFactory } from '../../src/ECS/Utils/BigIntCompatibility';
|
|
||||||
import { ComponentType } from '../../src/ECS/Core/ComponentStorage';
|
|
||||||
|
|
||||||
describe('初始化方式性能对比', () => {
|
|
||||||
test('对比不同初始化方式的性能', () => {
|
|
||||||
const testCount = 10000;
|
|
||||||
console.log(`\n=== 初始化方式对比 (${testCount}个实体) ===`);
|
|
||||||
|
|
||||||
// 方式1:字段直接初始化(原来的方式)
|
|
||||||
class EntityWithFieldInit {
|
|
||||||
public name: string;
|
|
||||||
public id: number;
|
|
||||||
private _componentMask = BigIntFactory.zero();
|
|
||||||
private _componentTypeToIndex = new Map<ComponentType, number>();
|
|
||||||
|
|
||||||
constructor(name: string, id: number) {
|
|
||||||
this.name = name;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方式2:构造函数初始化(新方式)
|
|
||||||
class EntityWithConstructorInit {
|
|
||||||
public name: string;
|
|
||||||
public id: number;
|
|
||||||
private _componentMask: any;
|
|
||||||
private _componentTypeToIndex: Map<ComponentType, number>;
|
|
||||||
|
|
||||||
constructor(name: string, id: number) {
|
|
||||||
this.name = name;
|
|
||||||
this.id = id;
|
|
||||||
this._componentMask = BigIntFactory.zero();
|
|
||||||
this._componentTypeToIndex = new Map<ComponentType, number>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方式3:完全延迟初始化
|
|
||||||
class EntityWithLazyInit {
|
|
||||||
public name: string;
|
|
||||||
public id: number;
|
|
||||||
private _componentMask: any;
|
|
||||||
private _componentTypeToIndex: Map<ComponentType, number> | undefined;
|
|
||||||
|
|
||||||
constructor(name: string, id: number) {
|
|
||||||
this.name = name;
|
|
||||||
this.id = id;
|
|
||||||
// 什么都不初始化
|
|
||||||
}
|
|
||||||
|
|
||||||
private ensureInit() {
|
|
||||||
if (!this._componentTypeToIndex) {
|
|
||||||
this._componentMask = BigIntFactory.zero();
|
|
||||||
this._componentTypeToIndex = new Map<ComponentType, number>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试方式1:字段直接初始化
|
|
||||||
let startTime = performance.now();
|
|
||||||
const entities1 = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entities1.push(new EntityWithFieldInit(`Entity_${i}`, i));
|
|
||||||
}
|
|
||||||
const fieldInitTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试方式2:构造函数初始化
|
|
||||||
startTime = performance.now();
|
|
||||||
const entities2 = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entities2.push(new EntityWithConstructorInit(`Entity_${i}`, i));
|
|
||||||
}
|
|
||||||
const constructorInitTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试方式3:延迟初始化
|
|
||||||
startTime = performance.now();
|
|
||||||
const entities3 = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entities3.push(new EntityWithLazyInit(`Entity_${i}`, i));
|
|
||||||
}
|
|
||||||
const lazyInitTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试方式4:只创建基本对象
|
|
||||||
startTime = performance.now();
|
|
||||||
const entities4 = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entities4.push({ name: `Entity_${i}`, id: i });
|
|
||||||
}
|
|
||||||
const basicObjectTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
console.log(`字段直接初始化: ${fieldInitTime.toFixed(2)}ms`);
|
|
||||||
console.log(`构造函数初始化: ${constructorInitTime.toFixed(2)}ms`);
|
|
||||||
console.log(`延迟初始化: ${lazyInitTime.toFixed(2)}ms`);
|
|
||||||
console.log(`基本对象创建: ${basicObjectTime.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
console.log(`\n性能对比:`);
|
|
||||||
console.log(`构造函数 vs 字段初始化: ${(fieldInitTime / constructorInitTime).toFixed(2)}x`);
|
|
||||||
console.log(`延迟 vs 构造函数: ${(constructorInitTime / lazyInitTime).toFixed(2)}x`);
|
|
||||||
console.log(`延迟 vs 基本对象: ${(lazyInitTime / basicObjectTime).toFixed(2)}x`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('测试BigIntFactory.zero()的性能', () => {
|
|
||||||
const testCount = 10000;
|
|
||||||
console.log(`\n=== BigIntFactory.zero()性能测试 ===`);
|
|
||||||
|
|
||||||
// 测试1:每次调用BigIntFactory.zero()
|
|
||||||
let startTime = performance.now();
|
|
||||||
const values1 = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
values1.push(BigIntFactory.zero());
|
|
||||||
}
|
|
||||||
const directCallTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试2:重复使用同一个实例
|
|
||||||
const sharedZero = BigIntFactory.zero();
|
|
||||||
startTime = performance.now();
|
|
||||||
const values2 = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
values2.push(sharedZero);
|
|
||||||
}
|
|
||||||
const sharedInstanceTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试3:使用数字0
|
|
||||||
startTime = performance.now();
|
|
||||||
const values3 = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
values3.push(0);
|
|
||||||
}
|
|
||||||
const numberZeroTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
console.log(`每次调用BigIntFactory.zero(): ${directCallTime.toFixed(2)}ms`);
|
|
||||||
console.log(`重复使用同一实例: ${sharedInstanceTime.toFixed(2)}ms`);
|
|
||||||
console.log(`使用数字0: ${numberZeroTime.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
console.log(`性能提升:`);
|
|
||||||
console.log(`共享实例 vs 每次调用: ${(directCallTime / sharedInstanceTime).toFixed(2)}x faster`);
|
|
||||||
console.log(`数字0 vs BigIntFactory: ${(directCallTime / numberZeroTime).toFixed(2)}x faster`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('测试Map创建的性能', () => {
|
|
||||||
const testCount = 10000;
|
|
||||||
console.log(`\n=== Map创建性能测试 ===`);
|
|
||||||
|
|
||||||
// 测试1:每次new Map()
|
|
||||||
let startTime = performance.now();
|
|
||||||
const maps1 = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
maps1.push(new Map());
|
|
||||||
}
|
|
||||||
const newMapTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试2:使用对象字面量
|
|
||||||
startTime = performance.now();
|
|
||||||
const objects = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
objects.push({});
|
|
||||||
}
|
|
||||||
const objectTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 测试3:延迟创建Map
|
|
||||||
startTime = performance.now();
|
|
||||||
const lazyMaps = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
lazyMaps.push(null); // 先不创建
|
|
||||||
}
|
|
||||||
const lazyTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
console.log(`每次new Map(): ${newMapTime.toFixed(2)}ms`);
|
|
||||||
console.log(`对象字面量: ${objectTime.toFixed(2)}ms`);
|
|
||||||
console.log(`延迟创建: ${lazyTime.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
console.log(`性能对比:`);
|
|
||||||
console.log(`对象 vs Map: ${(newMapTime / objectTime).toFixed(2)}x faster`);
|
|
||||||
console.log(`延迟 vs Map: ${(newMapTime / lazyTime).toFixed(2)}x faster`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,341 +0,0 @@
|
|||||||
import { Component } from '../../src/ECS/Component';
|
|
||||||
import { Matcher } from '../../src/ECS/Utils/Matcher';
|
|
||||||
|
|
||||||
class Position extends Component {
|
|
||||||
public x: number = 0;
|
|
||||||
public y: number = 0;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
if (args.length >= 1) this.x = args[0] as number;
|
|
||||||
if (args.length >= 2) this.y = args[1] as number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Velocity extends Component {
|
|
||||||
public vx: number = 0;
|
|
||||||
public vy: number = 0;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
if (args.length >= 1) this.vx = args[0] as number;
|
|
||||||
if (args.length >= 2) this.vy = args[1] as number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Health extends Component {
|
|
||||||
public hp: number = 100;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
if (args.length >= 1) this.hp = args[0] as number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Weapon extends Component {
|
|
||||||
public damage: number = 10;
|
|
||||||
|
|
||||||
constructor(...args: unknown[]) {
|
|
||||||
super();
|
|
||||||
if (args.length >= 1) this.damage = args[0] as number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Matcher 性能测试', () => {
|
|
||||||
test('大量 Matcher 创建性能', () => {
|
|
||||||
console.log('\n=== Matcher 创建性能测试 ===');
|
|
||||||
|
|
||||||
const iterationCount = 10000;
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Matcher getCondition() 性能', () => {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Matcher clone() 性能', () => {
|
|
||||||
console.log('\n=== clone() 性能测试 ===');
|
|
||||||
|
|
||||||
const originalMatcher = Matcher.all(Position, Velocity, Health)
|
|
||||||
.any(Weapon)
|
|
||||||
.none(Health)
|
|
||||||
.withTag(123)
|
|
||||||
.withName('TestEntity')
|
|
||||||
.withComponent(Position);
|
|
||||||
|
|
||||||
const iterationCount = 10000;
|
|
||||||
|
|
||||||
const start = performance.now();
|
|
||||||
for (let i = 0; i < iterationCount; i++) {
|
|
||||||
originalMatcher.clone();
|
|
||||||
}
|
|
||||||
const time = performance.now() - start;
|
|
||||||
|
|
||||||
console.log(`clone() 调用: ${time.toFixed(3)}ms (${(time/iterationCount*1000).toFixed(3)}μs/次)`);
|
|
||||||
|
|
||||||
expect(time).toBeLessThan(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Matcher toString() 性能', () => {
|
|
||||||
console.log('\n=== toString() 性能测试 ===');
|
|
||||||
|
|
||||||
const simpleMatcherStart = performance.now();
|
|
||||||
const simpleMatcher = Matcher.all(Position);
|
|
||||||
for (let i = 0; i < 10000; i++) {
|
|
||||||
simpleMatcher.toString();
|
|
||||||
}
|
|
||||||
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 complexTime = performance.now() - complexMatcherStart;
|
|
||||||
|
|
||||||
console.log(`简单 toString(): ${simpleTime.toFixed(3)}ms (${(simpleTime/10000*1000).toFixed(3)}μs/次)`);
|
|
||||||
console.log(`复杂 toString(): ${complexTime.toFixed(3)}ms (${(complexTime/10000*1000).toFixed(3)}μs/次)`);
|
|
||||||
|
|
||||||
expect(simpleTime).toBeLessThan(200);
|
|
||||||
expect(complexTime).toBeLessThan(500);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Matcher isEmpty() 性能', () => {
|
|
||||||
console.log('\n=== isEmpty() 性能测试 ===');
|
|
||||||
|
|
||||||
const emptyMatcher = Matcher.empty();
|
|
||||||
const fullMatcher = Matcher.all(Position, Velocity)
|
|
||||||
.any(Health)
|
|
||||||
.none(Weapon)
|
|
||||||
.withTag(123);
|
|
||||||
|
|
||||||
const iterationCount = 100000;
|
|
||||||
|
|
||||||
const emptyStart = performance.now();
|
|
||||||
for (let i = 0; i < iterationCount; i++) {
|
|
||||||
emptyMatcher.isEmpty();
|
|
||||||
}
|
|
||||||
const emptyTime = performance.now() - emptyStart;
|
|
||||||
|
|
||||||
const fullStart = performance.now();
|
|
||||||
for (let i = 0; i < iterationCount; i++) {
|
|
||||||
fullMatcher.isEmpty();
|
|
||||||
}
|
|
||||||
const fullTime = performance.now() - fullStart;
|
|
||||||
|
|
||||||
console.log(`空匹配器 isEmpty(): ${emptyTime.toFixed(3)}ms (${(emptyTime/iterationCount*1000).toFixed(3)}μs/次)`);
|
|
||||||
console.log(`复杂匹配器 isEmpty(): ${fullTime.toFixed(3)}ms (${(fullTime/iterationCount*1000).toFixed(3)}μs/次)`);
|
|
||||||
|
|
||||||
expect(emptyTime).toBeLessThan(100);
|
|
||||||
expect(fullTime).toBeLessThan(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Matcher reset() 性能', () => {
|
|
||||||
console.log('\n=== reset() 性能测试 ===');
|
|
||||||
|
|
||||||
const iterationCount = 50000;
|
|
||||||
|
|
||||||
const start = performance.now();
|
|
||||||
for (let i = 0; i < iterationCount; i++) {
|
|
||||||
const matcher = Matcher.all(Position, Velocity, Health)
|
|
||||||
.any(Weapon)
|
|
||||||
.none(Health)
|
|
||||||
.withTag(123)
|
|
||||||
.withName('TestEntity')
|
|
||||||
.withComponent(Position);
|
|
||||||
matcher.reset();
|
|
||||||
}
|
|
||||||
const time = performance.now() - start;
|
|
||||||
|
|
||||||
console.log(`reset() 调用: ${time.toFixed(3)}ms (${(time/iterationCount*1000).toFixed(3)}μs/次)`);
|
|
||||||
|
|
||||||
expect(time).toBeLessThan(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('大规模链式调用性能', () => {
|
|
||||||
console.log('\n=== 大规模链式调用性能测试 ===');
|
|
||||||
|
|
||||||
const iterationCount = 5000;
|
|
||||||
|
|
||||||
const start = performance.now();
|
|
||||||
for (let i = 0; i < iterationCount; i++) {
|
|
||||||
Matcher.empty()
|
|
||||||
.all(Position)
|
|
||||||
.all(Velocity)
|
|
||||||
.all(Health)
|
|
||||||
.any(Weapon)
|
|
||||||
.any(Health)
|
|
||||||
.none(Weapon)
|
|
||||||
.none(Health)
|
|
||||||
.withTag(i)
|
|
||||||
.withName(`Entity${i}`)
|
|
||||||
.withComponent(Position)
|
|
||||||
.withoutTag()
|
|
||||||
.withoutName()
|
|
||||||
.withoutComponent()
|
|
||||||
.withTag(i * 2)
|
|
||||||
.withName(`NewEntity${i}`)
|
|
||||||
.withComponent(Velocity);
|
|
||||||
}
|
|
||||||
const time = performance.now() - start;
|
|
||||||
|
|
||||||
console.log(`大规模链式调用: ${time.toFixed(3)}ms (${(time/iterationCount*1000).toFixed(3)}μs/次)`);
|
|
||||||
|
|
||||||
expect(time).toBeLessThan(2000);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('内存分配性能测试', () => {
|
|
||||||
console.log('\n=== 内存分配性能测试 ===');
|
|
||||||
|
|
||||||
const iterationCount = 10000;
|
|
||||||
const matchers: Matcher[] = [];
|
|
||||||
|
|
||||||
const start = performance.now();
|
|
||||||
for (let i = 0; i < iterationCount; i++) {
|
|
||||||
const matcher = Matcher.all(Position, Velocity)
|
|
||||||
.any(Health, Weapon)
|
|
||||||
.none(Health)
|
|
||||||
.withTag(i)
|
|
||||||
.withName(`Entity${i}`);
|
|
||||||
matchers.push(matcher);
|
|
||||||
}
|
|
||||||
const allocationTime = performance.now() - start;
|
|
||||||
|
|
||||||
const cloneStart = performance.now();
|
|
||||||
const clonedMatchers = matchers.map(m => m.clone());
|
|
||||||
const cloneTime = performance.now() - cloneStart;
|
|
||||||
|
|
||||||
const conditionStart = performance.now();
|
|
||||||
const conditions = matchers.map(m => m.getCondition());
|
|
||||||
const conditionTime = performance.now() - conditionStart;
|
|
||||||
|
|
||||||
console.log(`创建 ${iterationCount} 个 Matcher: ${allocationTime.toFixed(3)}ms`);
|
|
||||||
console.log(`克隆 ${iterationCount} 个 Matcher: ${cloneTime.toFixed(3)}ms`);
|
|
||||||
console.log(`获取 ${iterationCount} 个条件: ${conditionTime.toFixed(3)}ms`);
|
|
||||||
|
|
||||||
expect(allocationTime).toBeLessThan(1500);
|
|
||||||
expect(cloneTime).toBeLessThan(1000);
|
|
||||||
expect(conditionTime).toBeLessThan(500);
|
|
||||||
|
|
||||||
expect(matchers.length).toBe(iterationCount);
|
|
||||||
expect(clonedMatchers.length).toBe(iterationCount);
|
|
||||||
expect(conditions.length).toBe(iterationCount);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('字符串操作性能对比', () => {
|
|
||||||
console.log('\n=== 字符串操作性能对比 ===');
|
|
||||||
|
|
||||||
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,171 +0,0 @@
|
|||||||
import { EntityManager } from '../../src/ECS/Core/EntityManager';
|
|
||||||
import { ComponentTypeManager } from '../../src/ECS/Utils/ComponentTypeManager';
|
|
||||||
import { Entity } from '../../src/ECS/Entity';
|
|
||||||
|
|
||||||
describe('优化后的性能分析 - ComponentIndexManager优化', () => {
|
|
||||||
let entityManager: EntityManager;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ComponentTypeManager.instance.reset();
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('测试优化后的实体创建性能', () => {
|
|
||||||
const testCount = 10000;
|
|
||||||
console.log(`\n=== 优化后的实体创建性能测试 (${testCount}个实体) ===`);
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
const entities = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entities.push(entityManager.createEntity(`Entity_${i}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
const avgTime = totalTime / testCount;
|
|
||||||
|
|
||||||
console.log(`总耗时: ${totalTime.toFixed(2)}ms`);
|
|
||||||
console.log(`平均每个实体: ${avgTime.toFixed(3)}ms`);
|
|
||||||
console.log(`每秒创建实体数: ${Math.round(1000 / avgTime)}`);
|
|
||||||
|
|
||||||
if (totalTime < 140) {
|
|
||||||
console.log(`✅ 性能优化成功!实际耗时 ${totalTime.toFixed(2)}ms < 140ms 目标`);
|
|
||||||
} else {
|
|
||||||
console.log(`❌ 仍需进一步优化,实际耗时 ${totalTime.toFixed(2)}ms >= 140ms 目标`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 性能基准:应该在140ms以下
|
|
||||||
expect(totalTime).toBeLessThan(200); // 放宽一些给CI环境
|
|
||||||
});
|
|
||||||
|
|
||||||
test('对比批量创建与逐个创建的性能', () => {
|
|
||||||
const testCount = 5000;
|
|
||||||
console.log(`\n=== 批量创建vs逐个创建对比 (${testCount}个实体) ===`);
|
|
||||||
|
|
||||||
// 逐个创建
|
|
||||||
let startTime = performance.now();
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entityManager.createEntity(`Individual_${i}`);
|
|
||||||
}
|
|
||||||
const individualTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 重置管理器
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 批量创建
|
|
||||||
startTime = performance.now();
|
|
||||||
entityManager.createEntitiesBatch(testCount, "Batch", false);
|
|
||||||
const batchTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 重置管理器
|
|
||||||
entityManager = new EntityManager();
|
|
||||||
|
|
||||||
// 批量创建(跳过事件)
|
|
||||||
startTime = performance.now();
|
|
||||||
entityManager.createEntitiesBatch(testCount, "BatchNoEvents", true);
|
|
||||||
const batchNoEventsTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
console.log(`逐个创建: ${individualTime.toFixed(2)}ms`);
|
|
||||||
console.log(`批量创建: ${batchTime.toFixed(2)}ms`);
|
|
||||||
console.log(`批量创建(跳过事件): ${batchNoEventsTime.toFixed(2)}ms`);
|
|
||||||
console.log(`批量优化倍数: ${(individualTime / batchTime).toFixed(2)}x`);
|
|
||||||
console.log(`跳过事件优化倍数: ${(individualTime / batchNoEventsTime).toFixed(2)}x`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('测试组件索引管理器对空实体的优化效果', () => {
|
|
||||||
const testCount = 10000;
|
|
||||||
console.log(`\n=== 空实体优化效果测试 (${testCount}个空实体) ===`);
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
const entities = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
const entity = entityManager.createEntity(`EmptyEntity_${i}`);
|
|
||||||
entities.push(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
// 验证前几个实体确实没有组件
|
|
||||||
for (let i = 0; i < Math.min(5, entities.length); i++) {
|
|
||||||
expect(entities[i].components.length).toBe(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`空实体创建总耗时: ${totalTime.toFixed(2)}ms`);
|
|
||||||
console.log(`平均每个空实体: ${(totalTime / testCount).toFixed(3)}ms`);
|
|
||||||
|
|
||||||
// 获取优化统计信息
|
|
||||||
const stats = entityManager.getOptimizationStats();
|
|
||||||
console.log(`组件索引统计:`, stats.componentIndex);
|
|
||||||
|
|
||||||
// 空实体创建应该非常快,放宽限制以适应CI环境
|
|
||||||
expect(totalTime).toBeLessThan(150);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('测试Set对象池的效果', () => {
|
|
||||||
const testCount = 1000;
|
|
||||||
console.log(`\n=== Set对象池效果测试 (${testCount}次添加/删除) ===`);
|
|
||||||
|
|
||||||
// 创建实体
|
|
||||||
const entities = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entities.push(entityManager.createEntity(`PoolTest_${i}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试删除和重新创建的性能
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
// 删除一半实体
|
|
||||||
for (let i = 0; i < testCount / 2; i++) {
|
|
||||||
entityManager.destroyEntity(entities[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新创建实体
|
|
||||||
for (let i = 0; i < testCount / 2; i++) {
|
|
||||||
entityManager.createEntity(`RecycledEntity_${i}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
console.log(`删除+重新创建耗时: ${totalTime.toFixed(2)}ms`);
|
|
||||||
console.log(`平均每次操作: ${(totalTime / testCount).toFixed(3)}ms`);
|
|
||||||
|
|
||||||
// 对象池优化应该让重复操作更快,放宽限制适应不同环境
|
|
||||||
expect(totalTime).toBeLessThan(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('内存使用量分析', () => {
|
|
||||||
const testCount = 5000;
|
|
||||||
console.log(`\n=== 内存使用量分析 (${testCount}个实体) ===`);
|
|
||||||
|
|
||||||
// 获取初始内存使用情况
|
|
||||||
const initialStats = entityManager.getOptimizationStats();
|
|
||||||
const initialMemory = initialStats.componentIndex.memoryUsage;
|
|
||||||
|
|
||||||
// 创建实体
|
|
||||||
const entities = [];
|
|
||||||
for (let i = 0; i < testCount; i++) {
|
|
||||||
entities.push(entityManager.createEntity(`MemoryTest_${i}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取创建后的内存使用情况
|
|
||||||
const afterStats = entityManager.getOptimizationStats();
|
|
||||||
const afterMemory = afterStats.componentIndex.memoryUsage;
|
|
||||||
|
|
||||||
console.log(`初始内存使用: ${initialMemory} 字节`);
|
|
||||||
console.log(`创建后内存使用: ${afterMemory} 字节`);
|
|
||||||
console.log(`增加的内存: ${afterMemory - initialMemory} 字节`);
|
|
||||||
console.log(`平均每个实体内存: ${((afterMemory - initialMemory) / testCount).toFixed(2)} 字节`);
|
|
||||||
|
|
||||||
// 清理并观察内存回收
|
|
||||||
for (const entity of entities) {
|
|
||||||
entityManager.destroyEntity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanupStats = entityManager.getOptimizationStats();
|
|
||||||
const cleanupMemory = cleanupStats.componentIndex.memoryUsage;
|
|
||||||
|
|
||||||
console.log(`清理后内存使用: ${cleanupMemory} 字节`);
|
|
||||||
console.log(`内存回收率: ${(((afterMemory - cleanupMemory) / (afterMemory - initialMemory)) * 100).toFixed(1)}%`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user