冗余测试合并

This commit is contained in:
YHH
2025-10-08 12:04:13 +08:00
parent bd6ba84087
commit f41c1a3ca3
9 changed files with 1143 additions and 1660 deletions

View File

@@ -1,253 +0,0 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
import { EnableSoA, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '../../../src/ECS/Core/SoAStorage';
// 测试组件:使用集合类型装饰器
@EnableSoA
class CollectionsComponent extends Component {
// 序列化Map存储
@SerializeMap
public playerStats: Map<string, number> = new Map();
// 序列化Set存储
@SerializeSet
public achievements: Set<string> = new Set();
// 序列化Array存储
@SerializeArray
public inventory: string[] = [];
// 深拷贝对象存储
@DeepCopy
public config: { settings: { volume: number } } = { settings: { volume: 0.5 } };
// 普通对象(引用存储)
public metadata: any = null;
constructor() {
super();
}
}
describe('SoA集合类型装饰器测试', () => {
let manager: ComponentStorageManager;
beforeEach(() => {
manager = new ComponentStorageManager();
});
test('验证Map序列化存储', () => {
console.log('\\n=== 测试Map序列化存储 ===');
const component = new CollectionsComponent();
// 设置Map数据
component.playerStats.set('health', 100);
component.playerStats.set('mana', 50);
component.playerStats.set('experience', 1250);
console.log('原始Map数据:', {
size: component.playerStats.size,
entries: Array.from(component.playerStats.entries())
});
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionsComponent);
console.log('取回Map数据:', {
size: retrieved?.playerStats.size,
entries: Array.from(retrieved?.playerStats.entries() || [])
});
// 验证Map数据完整性
expect(retrieved?.playerStats).toBeInstanceOf(Map);
expect(retrieved?.playerStats.size).toBe(3);
expect(retrieved?.playerStats.get('health')).toBe(100);
expect(retrieved?.playerStats.get('mana')).toBe(50);
expect(retrieved?.playerStats.get('experience')).toBe(1250);
console.log('✅ Map序列化存储验证通过');
});
test('验证Set序列化存储', () => {
console.log('\\n=== 测试Set序列化存储 ===');
const component = new CollectionsComponent();
// 设置Set数据
component.achievements.add('first_kill');
component.achievements.add('level_10');
component.achievements.add('boss_defeated');
console.log('原始Set数据:', {
size: component.achievements.size,
values: Array.from(component.achievements)
});
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionsComponent);
console.log('取回Set数据:', {
size: retrieved?.achievements.size,
values: Array.from(retrieved?.achievements || [])
});
// 验证Set数据完整性
expect(retrieved?.achievements).toBeInstanceOf(Set);
expect(retrieved?.achievements.size).toBe(3);
expect(retrieved?.achievements.has('first_kill')).toBe(true);
expect(retrieved?.achievements.has('level_10')).toBe(true);
expect(retrieved?.achievements.has('boss_defeated')).toBe(true);
console.log('✅ Set序列化存储验证通过');
});
test('验证Array序列化存储', () => {
console.log('\\n=== 测试Array序列化存储 ===');
const component = new CollectionsComponent();
// 设置Array数据
component.inventory.push('sword', 'shield', 'potion');
console.log('原始Array数据:', component.inventory);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionsComponent);
console.log('取回Array数据:', retrieved?.inventory);
// 验证Array数据完整性
expect(Array.isArray(retrieved?.inventory)).toBe(true);
expect(retrieved?.inventory.length).toBe(3);
expect(retrieved?.inventory).toEqual(['sword', 'shield', 'potion']);
console.log('✅ Array序列化存储验证通过');
});
test('验证深拷贝对象存储', () => {
console.log('\\n=== 测试深拷贝对象存储 ===');
const component = new CollectionsComponent();
const originalConfig = component.config;
// 修改配置
component.config.settings.volume = 0.8;
console.log('原始配置:', component.config);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionsComponent);
console.log('取回配置:', retrieved?.config);
// 验证深拷贝
expect(retrieved?.config).toEqual(component.config);
expect(retrieved?.config).not.toBe(originalConfig); // 不是同一个引用
expect(retrieved?.config.settings.volume).toBe(0.8);
// 修改原始对象不应该影响取回的对象
component.config.settings.volume = 0.3;
expect(retrieved?.config.settings.volume).toBe(0.8); // 保持不变
console.log('✅ 深拷贝对象存储验证通过');
});
test('对比普通对象存储(引用存储)', () => {
console.log('\\n=== 测试普通对象存储(引用存储)===');
const component = new CollectionsComponent();
const sharedObject = { data: 'shared' };
component.metadata = sharedObject;
console.log('原始metadata:', component.metadata);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionsComponent);
console.log('取回metadata:', retrieved?.metadata);
// 验证引用存储
expect(retrieved?.metadata).toBe(sharedObject); // 是同一个引用
expect(retrieved?.metadata.data).toBe('shared');
console.log('✅ 普通对象存储验证通过');
});
test('复杂场景:多种类型混合使用', () => {
console.log('\\n=== 测试复杂场景 ===');
const component = new CollectionsComponent();
// 设置复杂数据
component.playerStats.set('level', 25);
component.playerStats.set('gold', 5000);
component.achievements.add('explorer');
component.achievements.add('warrior');
component.inventory.push('legendary_sword', 'magic_potion');
component.config = {
settings: {
volume: 0.75
}
};
component.metadata = { timestamp: Date.now() };
console.log('复杂数据设置完成');
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionsComponent);
// 全面验证
expect(retrieved?.playerStats.get('level')).toBe(25);
expect(retrieved?.achievements.has('explorer')).toBe(true);
expect(retrieved?.inventory).toContain('legendary_sword');
expect(retrieved?.config.settings.volume).toBe(0.75);
expect(retrieved?.metadata).toBeDefined();
console.log('✅ 复杂场景验证通过');
});
test('性能测试:序列化 vs 深拷贝', () => {
console.log('\\n=== 性能对比测试 ===');
const entityCount = 100;
// 准备测试数据
const startTime = performance.now();
for (let i = 0; i < entityCount; i++) {
const component = new CollectionsComponent();
// 设置数据
component.playerStats.set('id', i);
component.playerStats.set('score', i * 100);
component.achievements.add(`achievement_${i}`);
component.inventory.push(`item_${i}`);
component.config = { settings: { volume: i / entityCount } };
manager.addComponent(i, component);
}
const createTime = performance.now() - startTime;
// 读取测试
const readStartTime = performance.now();
for (let i = 0; i < entityCount; i++) {
const component = manager.getComponent(i, CollectionsComponent);
expect(component?.playerStats.get('id')).toBe(i);
}
const readTime = performance.now() - readStartTime;
console.log(`创建${entityCount}个复杂组件: ${createTime.toFixed(2)}ms`);
console.log(`读取${entityCount}个复杂组件: ${readTime.toFixed(2)}ms`);
console.log(`平均每个组件: ${((createTime + readTime) / entityCount).toFixed(4)}ms`);
console.log('✅ 性能测试完成');
});
});

View File

@@ -0,0 +1,631 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
import {
EnableSoA,
HighPrecision,
Float64,
Int32,
SerializeMap,
SerializeSet,
SerializeArray,
DeepCopy,
SoAStorage
} from '../../../src/ECS/Core/SoAStorage';
/**
* SoA存储完整测试套件
*/
// 测试组件定义
@EnableSoA
class BasicTypesComponent extends Component {
public intNumber: number;
public floatNumber: number;
public boolValue: boolean;
public stringValue: string;
public nullValue: null;
public undefinedValue: undefined;
constructor(...args: unknown[]) {
super();
const [
intNumber = 42,
floatNumber = 3.14,
boolValue = true,
stringValue = 'test',
nullValue = null,
undefinedValue = undefined
] = args as [number?, number?, boolean?, string?, null?, undefined?];
this.intNumber = intNumber;
this.floatNumber = floatNumber;
this.boolValue = boolValue;
this.stringValue = stringValue;
this.nullValue = nullValue;
this.undefinedValue = undefinedValue;
}
}
@EnableSoA
class DecoratedNumberComponent extends Component {
public normalFloat: number;
@HighPrecision
public highPrecisionNumber: number;
@Float64
public preciseFloat: number;
@Int32
public integerValue: number;
constructor(...args: unknown[]) {
super();
const [
normalFloat = 3.14,
highPrecisionNumber = Number.MAX_SAFE_INTEGER,
preciseFloat = Math.PI,
integerValue = 42
] = args as [number?, number?, number?, number?];
this.normalFloat = normalFloat;
this.highPrecisionNumber = highPrecisionNumber;
this.preciseFloat = preciseFloat;
this.integerValue = integerValue;
}
}
@EnableSoA
class CollectionComponent extends Component {
@SerializeMap
public mapData: Map<string, any>;
@SerializeSet
public setData: Set<any>;
@SerializeArray
public arrayData: any[];
@DeepCopy
public deepCopyData: any;
constructor(...args: unknown[]) {
super();
const [
mapData = new Map(),
setData = new Set(),
arrayData = [],
deepCopyData = null
] = args as [Map<string, any>?, Set<any>?, any[]?, any?];
this.mapData = mapData;
this.setData = setData;
this.arrayData = arrayData;
this.deepCopyData = deepCopyData;
}
}
class MockNode {
public name: string;
public active: boolean;
constructor(name: string) {
this.name = name;
this.active = true;
}
}
@EnableSoA
class ComplexObjectComponent extends Component {
public x: number;
public y: number;
public node: MockNode | null;
public callback: Function | null;
public data: any;
constructor(...args: unknown[]) {
super();
const [
x = 0,
y = 0,
node = null as MockNode | null,
callback = null as Function | null,
data = null as any
] = args as [number?, number?, (MockNode | null)?, (Function | null)?, any?];
this.x = x;
this.y = y;
this.node = node;
this.callback = callback;
this.data = data;
}
}
@EnableSoA
class MixedComponent extends Component {
@HighPrecision
public bigIntId: number;
@Float64
public preciseValue: number;
@Int32
public intValue: number;
@SerializeMap
public gameMap: Map<string, any>;
@SerializeSet
public flags: Set<number>;
@SerializeArray
public items: any[];
@DeepCopy
public config: any;
public normalFloat: number;
public boolFlag: boolean;
public text: string;
constructor(...args: unknown[]) {
super();
const [
bigIntId = 0,
preciseValue = 0,
intValue = 0,
normalFloat = 0,
boolFlag = false,
text = ''
] = args as [number?, number?, number?, number?, boolean?, string?];
this.bigIntId = bigIntId;
this.preciseValue = preciseValue;
this.intValue = intValue;
this.gameMap = new Map();
this.flags = new Set();
this.items = [];
this.config = null;
this.normalFloat = normalFloat;
this.boolFlag = boolFlag;
this.text = text;
}
}
describe('SoAStorage - SoA存储测试', () => {
let manager: ComponentStorageManager;
beforeEach(() => {
manager = new ComponentStorageManager();
});
describe('基础数据类型', () => {
test('应该正确存储和检索number类型', () => {
const component = new BasicTypesComponent(999, 2.718);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BasicTypesComponent);
expect(retrieved?.intNumber).toBe(999);
expect(retrieved?.floatNumber).toBeCloseTo(2.718);
});
test('应该正确存储和检索boolean类型', () => {
const component = new BasicTypesComponent(0, 0, false);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BasicTypesComponent);
expect(retrieved?.boolValue).toBe(false);
});
test('应该正确存储和检索string类型', () => {
const testString = '测试中文字符串 with emoji 🎉';
const component = new BasicTypesComponent(0, 0, true, testString);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BasicTypesComponent);
expect(retrieved?.stringValue).toBe(testString);
});
test('应该正确处理null和undefined', () => {
const component = new BasicTypesComponent();
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BasicTypesComponent);
expect(retrieved?.nullValue).toBe(null);
// undefined在SoA存储中保持为undefined不会序列化
expect(retrieved?.undefinedValue).toBeUndefined();
});
test('应该正确处理空字符串', () => {
const component = new BasicTypesComponent(0, 0, true, '');
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BasicTypesComponent);
expect(retrieved?.stringValue).toBe('');
});
test('应该正确处理数值边界值', () => {
// Float32可精确表示的最大整数约为2^24 (16777216)
// Float32最小正值约为1.4e-45Number.MIN_VALUE (5e-324)会被截断为0
const maxFloat32Int = 16777216;
const minFloat32 = 1.401298464324817e-45;
const component = new BasicTypesComponent(
maxFloat32Int,
minFloat32
);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BasicTypesComponent);
expect(retrieved?.intNumber).toBe(maxFloat32Int);
expect(retrieved?.floatNumber).toBeCloseTo(minFloat32, 45);
});
test('应该正确处理特殊字符串', () => {
const specialString = '\n\t\r"\'\\\\';
const component = new BasicTypesComponent(0, 0, true, specialString);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BasicTypesComponent);
expect(retrieved?.stringValue).toBe(specialString);
});
test('应该正确处理长字符串', () => {
const longString = 'a'.repeat(1000);
const component = new BasicTypesComponent(0, 0, true, longString);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BasicTypesComponent);
expect(retrieved?.stringValue).toBe(longString);
});
});
describe('数值类型装饰器', () => {
test('@HighPrecision应该保持高精度数值', () => {
const component = new DecoratedNumberComponent(
0,
Number.MAX_SAFE_INTEGER
);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, DecoratedNumberComponent);
expect(retrieved?.highPrecisionNumber).toBe(Number.MAX_SAFE_INTEGER);
});
test('@Float64应该使用双精度浮点存储', () => {
const component = new DecoratedNumberComponent(0, 0, Math.PI);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, DecoratedNumberComponent);
expect(retrieved?.preciseFloat).toBeCloseTo(Math.PI, 15);
});
test('@Int32应该使用32位整数存储', () => {
const component = new DecoratedNumberComponent(0, 0, 0, -2147483648);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, DecoratedNumberComponent);
expect(retrieved?.integerValue).toBe(-2147483648);
});
test('默认应该使用Float32存储', () => {
const component = new DecoratedNumberComponent(3.14159);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, DecoratedNumberComponent);
expect(retrieved?.normalFloat).toBeCloseTo(3.14159, 5);
});
test('应该使用正确的TypedArray类型', () => {
const component = new DecoratedNumberComponent();
manager.addComponent(1, component);
const storage = manager.getStorage(DecoratedNumberComponent) as SoAStorage<DecoratedNumberComponent>;
expect(storage.getFieldArray('normalFloat')).toBeInstanceOf(Float32Array);
expect(storage.getFieldArray('preciseFloat')).toBeInstanceOf(Float64Array);
expect(storage.getFieldArray('integerValue')).toBeInstanceOf(Int32Array);
expect(storage.getFieldArray('highPrecisionNumber')).toBeNull();
});
});
describe('集合类型序列化', () => {
test('@SerializeMap应该正确序列化Map', () => {
const component = new CollectionComponent();
component.mapData.set('key1', 'value1');
component.mapData.set('key2', 123);
component.mapData.set('key3', { nested: 'object' });
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionComponent);
expect(retrieved?.mapData).toBeInstanceOf(Map);
expect(retrieved?.mapData.size).toBe(3);
expect(retrieved?.mapData.get('key1')).toBe('value1');
expect(retrieved?.mapData.get('key2')).toBe(123);
expect(retrieved?.mapData.get('key3')).toEqual({ nested: 'object' });
});
test('@SerializeSet应该正确序列化Set', () => {
const component = new CollectionComponent();
component.setData.add('item1');
component.setData.add('item2');
component.setData.add(123);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionComponent);
expect(retrieved?.setData).toBeInstanceOf(Set);
expect(retrieved?.setData.size).toBe(3);
expect(retrieved?.setData.has('item1')).toBe(true);
expect(retrieved?.setData.has('item2')).toBe(true);
expect(retrieved?.setData.has(123)).toBe(true);
});
test('@SerializeArray应该正确序列化Array', () => {
const component = new CollectionComponent();
component.arrayData = ['item1', 'item2', 123, { nested: 'object' }];
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionComponent);
expect(Array.isArray(retrieved?.arrayData)).toBe(true);
expect(retrieved?.arrayData).toEqual(['item1', 'item2', 123, { nested: 'object' }]);
});
test('@DeepCopy应该创建深拷贝', () => {
const component = new CollectionComponent();
component.deepCopyData = { level1: { level2: { value: 42 } } };
const originalRef = component.deepCopyData;
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionComponent);
expect(retrieved?.deepCopyData).toEqual(component.deepCopyData);
expect(retrieved?.deepCopyData).not.toBe(originalRef);
component.deepCopyData.level1.level2.value = 100;
expect(retrieved?.deepCopyData.level1.level2.value).toBe(42);
});
test('Map应该正确处理边界值', () => {
const component = new CollectionComponent();
component.mapData.set('null', null);
component.mapData.set('undefined', undefined);
component.mapData.set('empty', '');
component.mapData.set('zero', 0);
component.mapData.set('false', false);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionComponent);
expect(retrieved?.mapData.get('null')).toBe(null);
expect(retrieved?.mapData.get('undefined')).toBe(null);
expect(retrieved?.mapData.get('empty')).toBe('');
expect(retrieved?.mapData.get('zero')).toBe(0);
expect(retrieved?.mapData.get('false')).toBe(false);
});
test('Set应该支持数值0', () => {
const component = new CollectionComponent();
component.setData.add(0);
component.setData.add(1);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionComponent);
expect(retrieved?.setData.has(0)).toBe(true);
expect(retrieved?.setData.has(1)).toBe(true);
});
test('Array应该正确处理null和undefined', () => {
const component = new CollectionComponent();
component.arrayData = [null, undefined, '', 0, false];
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, CollectionComponent);
expect(retrieved?.arrayData).toEqual([null, null, '', 0, false]);
});
});
describe('复杂对象处理', () => {
test('应该正确保存复杂对象引用', () => {
const node = new MockNode('testNode');
const callback = () => console.log('test');
const data = { complex: 'object' };
const component = new ComplexObjectComponent(100, 200, node, callback, data);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, ComplexObjectComponent);
expect(retrieved?.x).toBe(100);
expect(retrieved?.y).toBe(200);
expect(retrieved?.node?.name).toBe('testNode');
expect(retrieved?.node?.active).toBe(true);
expect(retrieved?.callback).toBe(callback);
expect(retrieved?.data).toEqual(data);
});
test('应该正确处理null对象', () => {
const component = new ComplexObjectComponent(0, 0, null, null, null);
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, ComplexObjectComponent);
expect(retrieved?.node).toBe(null);
expect(retrieved?.callback).toBe(null);
expect(retrieved?.data).toBe(null);
});
});
describe('混合装饰器使用', () => {
test('应该支持多种装饰器混合使用', () => {
const component = new MixedComponent(
Number.MAX_SAFE_INTEGER,
Math.PI,
-2147483648,
1.23,
true,
'test'
);
component.gameMap.set('player1', { level: 10 });
component.flags.add(1);
component.flags.add(2);
component.items.push('item1');
component.config = { settings: { volume: 0.8 } };
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, MixedComponent);
expect(retrieved?.bigIntId).toBe(Number.MAX_SAFE_INTEGER);
expect(retrieved?.preciseValue).toBeCloseTo(Math.PI, 15);
expect(retrieved?.intValue).toBe(-2147483648);
expect(retrieved?.normalFloat).toBeCloseTo(1.23, 5);
expect(retrieved?.boolFlag).toBe(true);
expect(retrieved?.text).toBe('test');
expect(retrieved?.gameMap.get('player1')).toEqual({ level: 10 });
expect(retrieved?.flags.has(1)).toBe(true);
expect(retrieved?.flags.has(2)).toBe(true);
expect(retrieved?.items).toContain('item1');
expect(retrieved?.config.settings.volume).toBe(0.8);
});
});
describe('存储管理', () => {
test('应该正确统计存储信息', () => {
const storage = manager.getStorage(MixedComponent) as SoAStorage<MixedComponent>;
for (let i = 1; i <= 5; i++) {
const component = new MixedComponent(i, i * Math.PI, i * 10);
manager.addComponent(i, component);
}
const stats = storage.getStats();
expect(stats.size).toBe(5);
expect(stats.capacity).toBeGreaterThanOrEqual(5);
expect(stats.memoryUsage).toBeGreaterThan(0);
});
test('应该支持压缩操作', () => {
const storage = manager.getStorage(MixedComponent) as SoAStorage<MixedComponent>;
for (let i = 1; i <= 5; i++) {
const component = new MixedComponent();
manager.addComponent(i, component);
}
storage.removeComponent(2);
storage.removeComponent(4);
const statsBefore = storage.getStats();
storage.compact();
const statsAfter = storage.getStats();
expect(statsAfter.size).toBe(3);
expect(statsAfter.size).toBeLessThan(statsBefore.capacity);
});
test('应该正确处理循环引用', () => {
const component = new MixedComponent();
const cyclicObject: any = { name: 'test' };
cyclicObject.self = cyclicObject;
component.items.push(cyclicObject);
expect(() => {
manager.addComponent(1, component);
}).not.toThrow();
const retrieved = manager.getComponent(1, MixedComponent);
expect(retrieved).toBeDefined();
});
});
describe('性能测试', () => {
test('大容量创建性能应该可接受', () => {
const entityCount = 2000;
const startTime = performance.now();
for (let i = 1; i <= entityCount; i++) {
const component = new MixedComponent(i, i * 0.1, i * 10);
component.gameMap.set(`key${i}`, i);
manager.addComponent(i, component);
}
const createTime = performance.now() - startTime;
expect(createTime).toBeLessThan(1000);
const storage = manager.getStorage(MixedComponent) as SoAStorage<MixedComponent>;
const stats = storage.getStats();
expect(stats.size).toBe(entityCount);
});
test('随机访问性能应该可接受', () => {
const entityCount = 2000;
for (let i = 1; i <= entityCount; i++) {
const component = new MixedComponent(i);
manager.addComponent(i, component);
}
const startTime = performance.now();
for (let i = 0; i < 100; i++) {
const randomId = Math.floor(Math.random() * entityCount) + 1;
const component = manager.getComponent(randomId, MixedComponent);
expect(component?.bigIntId).toBe(randomId);
}
const readTime = performance.now() - startTime;
expect(readTime).toBeLessThan(100);
});
test('向量化批量操作应该正确执行', () => {
const storage = manager.getStorage(MixedComponent) as SoAStorage<MixedComponent>;
for (let i = 1; i <= 10; i++) {
const component = new MixedComponent(0, 0, i * 10, i);
manager.addComponent(i, component);
}
let operationExecuted = false;
storage.performVectorizedOperation((fieldArrays, activeIndices) => {
operationExecuted = true;
const normalFloatArray = fieldArrays.get('normalFloat') as Float32Array;
const intArray = fieldArrays.get('intValue') as Int32Array;
expect(normalFloatArray).toBeInstanceOf(Float32Array);
expect(intArray).toBeInstanceOf(Int32Array);
expect(activeIndices.length).toBe(10);
for (let i = 0; i < activeIndices.length; i++) {
const idx = activeIndices[i];
normalFloatArray[idx] *= 2;
intArray[idx] += 5;
}
});
expect(operationExecuted).toBe(true);
const component = manager.getComponent(5, MixedComponent);
expect(component?.normalFloat).toBe(10);
expect(component?.intValue).toBe(55);
});
});
});

View File

@@ -1,308 +0,0 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
import { EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '../../../src/ECS/Core/SoAStorage';
import { SoAStorage } from '../../../src/ECS/Core/SoAStorage';
// 综合测试组件,覆盖所有装饰器
@EnableSoA
class ComprehensiveComponent extends Component {
@HighPrecision
public bigIntId: number = BigInt(Number.MAX_SAFE_INTEGER + 1) as any;
@Float64
public preciseValue: number = Math.PI;
@Int32
public intValue: number = -2147483648;
@SerializeMap
public gameMap: Map<string, any> = new Map();
@SerializeSet
public flags: Set<number> = new Set();
@SerializeArray
public items: any[] = [];
@DeepCopy
public nestedConfig: any = { deep: { nested: { value: 42 } } };
// 未装饰的字段
public normalFloat: number = 1.23;
public flag: boolean = true;
public text: string = 'default';
public complexObject: any = null;
constructor() {
super();
}
}
describe('SoA存储综合测试覆盖', () => {
let manager: ComponentStorageManager;
beforeEach(() => {
manager = new ComponentStorageManager();
});
test('验证所有装饰器类型的存储和检索', () => {
console.log('\\n=== 综合装饰器测试 ===');
const component = new ComprehensiveComponent();
// 设置复杂数据
component.gameMap.set('player1', { level: 10, gold: 500 });
component.gameMap.set('player2', { level: 15, gold: 1200 });
component.flags.add(1);
component.flags.add(2);
component.flags.add(4);
component.items.push({ type: 'weapon', name: 'sword' });
component.items.push({ type: 'armor', name: 'shield' });
component.nestedConfig.deep.nested.value = 999;
component.complexObject = { reference: 'shared' };
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, ComprehensiveComponent);
// 验证所有类型
expect(retrieved?.bigIntId).toBe(component.bigIntId);
expect(retrieved?.preciseValue).toBeCloseTo(Math.PI, 15);
expect(retrieved?.intValue).toBe(-2147483648);
expect(retrieved?.gameMap).toBeInstanceOf(Map);
expect(retrieved?.gameMap.get('player1')).toEqual({ level: 10, gold: 500 });
expect(retrieved?.flags).toBeInstanceOf(Set);
expect(retrieved?.flags.has(2)).toBe(true);
expect(retrieved?.items).toEqual(component.items);
expect(retrieved?.nestedConfig.deep.nested.value).toBe(999);
// 深拷贝验证
expect(retrieved?.nestedConfig).not.toBe(component.nestedConfig);
console.log('✅ 综合装饰器测试通过');
});
test('测试存储器内存统计和容量管理', () => {
console.log('\\n=== 存储器管理测试 ===');
const storage = manager.getStorage(ComprehensiveComponent) as SoAStorage<ComprehensiveComponent>;
// 添加多个组件
for (let i = 1; i <= 5; i++) {
const component = new ComprehensiveComponent();
component.intValue = i * 100;
component.preciseValue = i * Math.PI;
manager.addComponent(i, component);
}
// 检查统计信息
const stats = storage.getStats();
console.log('存储统计:', {
size: stats.size,
capacity: stats.capacity,
memoryUsage: stats.memoryUsage,
fieldCount: stats.fieldStats.size
});
expect(stats.size).toBe(5);
expect(stats.capacity).toBeGreaterThanOrEqual(5);
expect(stats.memoryUsage).toBeGreaterThan(0);
// 测试压缩
storage.removeComponent(2);
storage.removeComponent(4);
const statsBeforeCompact = storage.getStats();
storage.compact();
const statsAfterCompact = storage.getStats();
expect(statsAfterCompact.size).toBe(3);
console.log('压缩前后对比:', {
before: statsBeforeCompact.size,
after: statsAfterCompact.size
});
console.log('✅ 存储器管理测试通过');
});
test('测试序列化错误处理', () => {
console.log('\\n=== 序列化错误处理测试 ===');
// 创建包含循环引用的对象
const component = new ComprehensiveComponent();
const cyclicObject: any = { name: 'test' };
cyclicObject.self = cyclicObject; // 循环引用
// 这应该不会崩溃,而是优雅处理
component.items.push(cyclicObject);
expect(() => {
manager.addComponent(1, component);
}).not.toThrow();
const retrieved = manager.getComponent(1, ComprehensiveComponent);
expect(retrieved).toBeDefined();
console.log('✅ 序列化错误处理测试通过');
});
test('测试大容量扩展和性能', () => {
console.log('\\n=== 大容量性能测试 ===');
const startTime = performance.now();
const entityCount = 2000;
// 创建大量实体
for (let i = 1; i <= entityCount; i++) {
const component = new ComprehensiveComponent();
component.intValue = i;
component.preciseValue = i * 0.1;
component.gameMap.set(`key${i}`, i);
component.flags.add(i % 10);
component.items.push(`item${i}`);
manager.addComponent(i, component);
}
const createTime = performance.now() - startTime;
// 随机访问测试
const readStartTime = performance.now();
for (let i = 0; i < 100; i++) {
const randomId = Math.floor(Math.random() * entityCount) + 1;
const component = manager.getComponent(randomId, ComprehensiveComponent);
expect(component?.intValue).toBe(randomId);
}
const readTime = performance.now() - readStartTime;
console.log(`创建${entityCount}个组件: ${createTime.toFixed(2)}ms`);
console.log(`随机读取100次: ${readTime.toFixed(2)}ms`);
console.log(`平均创建时间: ${(createTime / entityCount).toFixed(4)}ms/组件`);
// 验证存储统计
const storage = manager.getStorage(ComprehensiveComponent) as SoAStorage<ComprehensiveComponent>;
const stats = storage.getStats();
expect(stats.size).toBe(entityCount);
expect(stats.capacity).toBeGreaterThanOrEqual(entityCount);
console.log('✅ 大容量性能测试通过');
});
test('测试空值和边界处理', () => {
console.log('\\n=== 空值边界测试 ===');
const component = new ComprehensiveComponent();
// 设置各种边界值
component.gameMap.set('null', null);
component.gameMap.set('undefined', undefined);
component.gameMap.set('empty', '');
component.gameMap.set('zero', 0);
component.gameMap.set('false', false);
component.flags.add(0);
component.items.push(null, undefined, '', 0, false);
component.nestedConfig = null;
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, ComprehensiveComponent);
// 验证边界值处理
expect(retrieved?.gameMap.get('null')).toBe(null);
expect(retrieved?.gameMap.get('undefined')).toBe(null); // JSON序列化会将undefined转为null
expect(retrieved?.gameMap.get('empty')).toBe('');
expect(retrieved?.gameMap.get('zero')).toBe(0);
expect(retrieved?.gameMap.get('false')).toBe(false);
expect(retrieved?.flags.has(0)).toBe(true);
expect(retrieved?.items).toEqual([null, null, '', 0, false]); // undefined序列化为null
expect(retrieved?.nestedConfig).toBe(null);
console.log('✅ 空值边界测试通过');
});
test('测试不同TypedArray类型的字段访问', () => {
console.log('\\n=== TypedArray字段测试 ===');
const storage = manager.getStorage(ComprehensiveComponent) as SoAStorage<ComprehensiveComponent>;
// 添加测试数据
const component = new ComprehensiveComponent();
manager.addComponent(1, component);
// 检查不同类型的TypedArray
const preciseArray = storage.getFieldArray('preciseValue');
const intArray = storage.getFieldArray('intValue');
const normalArray = storage.getFieldArray('normalFloat');
const flagArray = storage.getFieldArray('flag');
expect(preciseArray).toBeInstanceOf(Float64Array);
expect(intArray).toBeInstanceOf(Int32Array);
expect(normalArray).toBeInstanceOf(Float32Array);
expect(flagArray).toBeInstanceOf(Uint8Array);
// 高精度字段不应该在TypedArray中
const bigIntArray = storage.getFieldArray('bigIntId');
expect(bigIntArray).toBeNull();
console.log('TypedArray类型验证:', {
preciseValue: preciseArray?.constructor.name,
intValue: intArray?.constructor.name,
normalFloat: normalArray?.constructor.name,
flag: flagArray?.constructor.name,
bigIntId: bigIntArray ? 'Found' : 'null (正确)'
});
console.log('✅ TypedArray字段测试通过');
});
test('测试向量化批量操作', () => {
console.log('\\n=== 向量化操作测试 ===');
const storage = manager.getStorage(ComprehensiveComponent) as SoAStorage<ComprehensiveComponent>;
// 添加测试数据
for (let i = 1; i <= 10; i++) {
const component = new ComprehensiveComponent();
component.normalFloat = i;
component.intValue = i * 10;
manager.addComponent(i, component);
}
// 执行向量化操作
let operationExecuted = false;
storage.performVectorizedOperation((fieldArrays, activeIndices) => {
operationExecuted = true;
const normalFloatArray = fieldArrays.get('normalFloat') as Float32Array;
const intArray = fieldArrays.get('intValue') as Int32Array;
expect(normalFloatArray).toBeInstanceOf(Float32Array);
expect(intArray).toBeInstanceOf(Int32Array);
expect(activeIndices.length).toBe(10);
// 批量修改数据
for (let i = 0; i < activeIndices.length; i++) {
const idx = activeIndices[i];
normalFloatArray[idx] *= 2; // 乘以2
intArray[idx] += 5; // 加5
}
});
expect(operationExecuted).toBe(true);
// 验证批量操作结果
const component = manager.getComponent(5, ComprehensiveComponent);
expect(component?.normalFloat).toBe(10); // 5 * 2
expect(component?.intValue).toBe(55); // 50 + 5
console.log('✅ 向量化操作测试通过');
});
});

View File

@@ -1,171 +0,0 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
import { SoAStorage, EnableSoA, HighPrecision, Float64, Int32 } from '../../../src/ECS/Core/SoAStorage';
// 测试组件:使用不同的数值类型装饰器
@EnableSoA
class DecoratedComponent extends Component {
// 默认Float32Array存储
public normalFloat: number = 3.14;
// 高精度存储(作为复杂对象)
@HighPrecision
public highPrecisionNumber: number = Number.MAX_SAFE_INTEGER;
// Float64Array存储
@Float64
public preciseFloat: number = Math.PI;
// Int32Array存储
@Int32
public integerValue: number = 42;
// 布尔值默认Float32Array
public flag: boolean = true;
// 字符串(专门数组)
public text: string = 'hello';
constructor() {
super();
}
}
describe('SoA数值类型装饰器测试', () => {
let manager: ComponentStorageManager;
beforeEach(() => {
manager = new ComponentStorageManager();
});
test('验证不同装饰器的存储类型', () => {
console.log('\\n=== 测试装饰器存储类型 ===');
const component = new DecoratedComponent();
component.highPrecisionNumber = Number.MAX_SAFE_INTEGER;
component.preciseFloat = Math.PI;
component.integerValue = 999999;
component.normalFloat = 2.718;
console.log('原始数据:', {
normalFloat: component.normalFloat,
highPrecisionNumber: component.highPrecisionNumber,
preciseFloat: component.preciseFloat,
integerValue: component.integerValue,
flag: component.flag,
text: component.text
});
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, DecoratedComponent);
console.log('\\n取回数据:', {
normalFloat: retrieved?.normalFloat,
highPrecisionNumber: retrieved?.highPrecisionNumber,
preciseFloat: retrieved?.preciseFloat,
integerValue: retrieved?.integerValue,
flag: retrieved?.flag,
text: retrieved?.text
});
// 验证精度保持
expect(retrieved?.normalFloat).toBeCloseTo(2.718, 5); // Float32精度
expect(retrieved?.highPrecisionNumber).toBe(Number.MAX_SAFE_INTEGER); // 高精度保持
expect(retrieved?.preciseFloat).toBeCloseTo(Math.PI, 15); // Float64精度
expect(retrieved?.integerValue).toBe(999999); // 整数保持
expect(retrieved?.flag).toBe(true);
expect(retrieved?.text).toBe('hello');
console.log('✅ 所有装饰器类型验证通过');
});
test('验证存储器内部结构', () => {
console.log('\\n=== 测试存储器内部结构 ===');
const component = new DecoratedComponent();
manager.addComponent(1, component);
const storage = manager.getStorage(DecoratedComponent) as SoAStorage<DecoratedComponent>;
// 检查TypedArray字段
const normalFloatArray = storage.getFieldArray('normalFloat');
const preciseFloatArray = storage.getFieldArray('preciseFloat');
const integerArray = storage.getFieldArray('integerValue');
const flagArray = storage.getFieldArray('flag');
console.log('存储类型:', {
normalFloat: normalFloatArray?.constructor.name,
preciseFloat: preciseFloatArray?.constructor.name,
integerValue: integerArray?.constructor.name,
flag: flagArray?.constructor.name
});
// 验证存储类型
expect(normalFloatArray).toBeInstanceOf(Float32Array);
expect(preciseFloatArray).toBeInstanceOf(Float64Array);
expect(integerArray).toBeInstanceOf(Int32Array);
expect(flagArray).toBeInstanceOf(Uint8Array);
// 高精度字段不应该在TypedArray中
const highPrecisionArray = storage.getFieldArray('highPrecisionNumber');
expect(highPrecisionArray).toBeNull();
console.log('✅ 存储器内部结构验证通过');
});
test('测试边界值精度', () => {
console.log('\\n=== 测试边界值精度 ===');
const component = new DecoratedComponent();
// 测试极限值
component.highPrecisionNumber = Number.MAX_SAFE_INTEGER;
component.preciseFloat = Number.MIN_VALUE;
component.normalFloat = 16777217; // 超出Float32精度
component.integerValue = -2147483648; // Int32最小值
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, DecoratedComponent);
console.log('边界值测试结果:', {
highPrecision: retrieved?.highPrecisionNumber === Number.MAX_SAFE_INTEGER,
preciseFloat: retrieved?.preciseFloat === Number.MIN_VALUE,
normalFloat: retrieved?.normalFloat, // 可能有精度损失
integerValue: retrieved?.integerValue === -2147483648
});
// 验证高精度保持
expect(retrieved?.highPrecisionNumber).toBe(Number.MAX_SAFE_INTEGER);
expect(retrieved?.preciseFloat).toBe(Number.MIN_VALUE);
expect(retrieved?.integerValue).toBe(-2147483648);
console.log('✅ 边界值精度测试通过');
});
test('性能对比:装饰器 vs 自动检测', () => {
console.log('\\n=== 性能对比测试 ===');
const entityCount = 1000;
// 使用装饰器的组件
const startTime = performance.now();
for (let i = 0; i < entityCount; i++) {
const component = new DecoratedComponent();
component.highPrecisionNumber = Number.MAX_SAFE_INTEGER;
component.preciseFloat = Math.PI * i;
component.integerValue = i;
manager.addComponent(i, component);
}
const decoratorTime = performance.now() - startTime;
console.log(`装饰器方式: ${decoratorTime.toFixed(2)}ms`);
console.log(`平均每个组件: ${(decoratorTime / entityCount).toFixed(4)}ms`);
// 验证数据完整性
const sample = manager.getComponent(500, DecoratedComponent);
expect(sample?.highPrecisionNumber).toBe(Number.MAX_SAFE_INTEGER);
expect(sample?.integerValue).toBe(500);
console.log('✅ 性能测试完成,数据完整性验证通过');
});
});

View File

@@ -1,129 +0,0 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
import { EnableSoA } from '../../../src/ECS/Core/SoAStorage';
// 模拟复杂对象如cocos的node节点
class MockNode {
public name: string;
public active: boolean;
constructor(name: string) {
this.name = name;
this.active = true;
}
public toString() {
return `Node(${this.name})`;
}
}
// 包含复杂属性的组件
@EnableSoA
class ProblematicComponent extends Component {
public x: number = 0;
public y: number = 0;
public node: MockNode | null = null;
public callback: Function | null = null;
public data: any = null;
constructor() {
super();
this.node = new MockNode('test');
this.callback = () => console.log('test');
this.data = { complex: 'object' };
}
}
// 安全的数值组件
@EnableSoA
class SafeComponent extends Component {
public x: number = 0;
public y: number = 0;
public active: boolean = true;
}
describe('SoA边界情况和复杂属性测试', () => {
let manager: ComponentStorageManager;
beforeEach(() => {
manager = new ComponentStorageManager();
});
test('包含复杂对象的组件会有什么问题', () => {
console.log('\\n=== 测试复杂对象处理 ===');
// 创建包含复杂属性的组件
const originalComponent = new ProblematicComponent();
console.log('原始组件:', {
x: originalComponent.x,
y: originalComponent.y,
node: originalComponent.node?.name,
callback: typeof originalComponent.callback,
data: originalComponent.data
});
// 添加到SoA存储
manager.addComponent(1, originalComponent);
// 获取组件看看发生了什么
const retrievedComponent = manager.getComponent(1, ProblematicComponent);
console.log('取回的组件:', {
x: retrievedComponent?.x,
y: retrievedComponent?.y,
node: retrievedComponent?.node,
callback: retrievedComponent?.callback,
data: retrievedComponent?.data
});
// 验证数据完整性
expect(retrievedComponent?.x).toBe(0);
expect(retrievedComponent?.y).toBe(0);
// 复杂对象的问题
console.log('\\n⚠ 问题发现:');
console.log('- node对象:', retrievedComponent?.node);
console.log('- callback函数:', retrievedComponent?.callback);
console.log('- data对象:', retrievedComponent?.data);
// 复杂属性现在应该正确保存
expect(retrievedComponent?.node?.name).toBe('test'); // 应该保持原始值
expect(retrievedComponent?.callback).toBe(originalComponent.callback); // 应该是同一个函数
expect(retrievedComponent?.data).toEqual({ complex: 'object' }); // 应该保持原始数据
console.log('✅ 修复成功:复杂对象现在能正确处理!');
});
test('纯数值组件工作正常', () => {
console.log('\\n=== 测试纯数值组件 ===');
const safeComponent = new SafeComponent();
safeComponent.x = 100;
safeComponent.y = 200;
safeComponent.active = false;
manager.addComponent(1, safeComponent);
const retrieved = manager.getComponent(1, SafeComponent);
console.log('纯数值组件正常工作:', {
x: retrieved?.x,
y: retrieved?.y,
active: retrieved?.active
});
expect(retrieved?.x).toBe(100);
expect(retrieved?.y).toBe(200);
expect(retrieved?.active).toBe(false);
});
test('SoA是否能检测到不适合的组件类型', () => {
console.log('\\n=== 测试类型检测 ===');
// 当前实现会静默忽略复杂字段
// 这是一个潜在的问题!
const storage = manager.getStorage(ProblematicComponent);
console.log('存储类型:', storage.constructor.name);
// SoA存储应该能警告或拒绝不适合的组件
expect(storage.constructor.name).toBe('SoAStorage');
});
});

View File

@@ -1,159 +0,0 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
import { EnableSoA, HighPrecision, Float64 } from '../../../src/ECS/Core/SoAStorage';
// 包含所有基础类型的组件
@EnableSoA
class AllTypesComponent extends Component {
// 数值类型
public intNumber: number = 42;
public floatNumber: number = 3.14;
public zeroNumber: number = 0;
// 布尔类型
public trueBoolean: boolean = true;
public falseBoolean: boolean = false;
// 字符串类型
public emptyString: string = '';
public normalString: string = 'hello';
public longString: string = 'this is a long string with spaces and 123 numbers!';
// 其他基础类型
public nullValue: null = null;
public undefinedValue: undefined = undefined;
// 复杂类型
public arrayValue: number[] = [1, 2, 3];
public objectValue: { name: string } = { name: 'test' };
constructor() {
super();
}
}
// 边界测试专用组件
@EnableSoA
class BoundaryTestComponent extends Component {
// 高精度大整数
@HighPrecision
public maxInt: number = 0;
// 高精度小浮点数
@Float64
public minFloat: number = 0;
// 普通数值
public normalNumber: number = 0;
// 字符串测试
public testString: string = '';
public longString: string = '';
constructor() {
super();
}
}
describe('SoA所有数据类型处理测试', () => {
let manager: ComponentStorageManager;
beforeEach(() => {
manager = new ComponentStorageManager();
});
test('验证所有基础类型的处理', () => {
console.log('\\n=== 测试所有数据类型 ===');
// 创建包含各种类型的组件
const originalComponent = new AllTypesComponent();
originalComponent.normalString = 'modified string';
originalComponent.longString = '测试中文字符串 with emoji 🎉';
originalComponent.intNumber = 999;
originalComponent.floatNumber = 2.718;
originalComponent.trueBoolean = false;
originalComponent.falseBoolean = true;
console.log('原始组件数据:', {
intNumber: originalComponent.intNumber,
floatNumber: originalComponent.floatNumber,
trueBoolean: originalComponent.trueBoolean,
falseBoolean: originalComponent.falseBoolean,
emptyString: `"${originalComponent.emptyString}"`,
normalString: `"${originalComponent.normalString}"`,
longString: `"${originalComponent.longString}"`,
arrayValue: originalComponent.arrayValue,
objectValue: originalComponent.objectValue
});
// 存储到SoA
manager.addComponent(1, originalComponent);
// 获取并验证
const retrievedComponent = manager.getComponent(1, AllTypesComponent);
console.log('\\n取回的组件数据:', {
intNumber: retrievedComponent?.intNumber,
floatNumber: retrievedComponent?.floatNumber,
trueBoolean: retrievedComponent?.trueBoolean,
falseBoolean: retrievedComponent?.falseBoolean,
emptyString: `"${retrievedComponent?.emptyString}"`,
normalString: `"${retrievedComponent?.normalString}"`,
longString: `"${retrievedComponent?.longString}"`,
arrayValue: retrievedComponent?.arrayValue,
objectValue: retrievedComponent?.objectValue
});
// 验证数值类型
expect(retrievedComponent?.intNumber).toBe(999);
expect(retrievedComponent?.floatNumber).toBeCloseTo(2.718);
// 验证布尔类型
expect(retrievedComponent?.trueBoolean).toBe(false);
expect(retrievedComponent?.falseBoolean).toBe(true);
// 验证字符串类型
expect(retrievedComponent?.emptyString).toBe('');
expect(retrievedComponent?.normalString).toBe('modified string');
expect(retrievedComponent?.longString).toBe('测试中文字符串 with emoji 🎉');
// 验证复杂类型
expect(retrievedComponent?.arrayValue).toEqual([1, 2, 3]);
expect(retrievedComponent?.objectValue).toEqual({ name: 'test' });
console.log('\\n✅ 所有类型验证完成');
});
test('边界情况测试', () => {
console.log('\\n=== 边界情况测试 ===');
const component = new BoundaryTestComponent();
// 特殊数值
component.maxInt = Number.MAX_SAFE_INTEGER;
component.minFloat = Number.MIN_VALUE;
component.normalNumber = -0;
// 特殊字符串
component.testString = '\\n\\t\\r"\'\\\\'; // 转义字符
component.longString = 'a'.repeat(1000); // 长字符串
manager.addComponent(1, component);
const retrieved = manager.getComponent(1, BoundaryTestComponent);
console.log('边界情况结果:', {
maxInt: retrieved?.maxInt,
minFloat: retrieved?.minFloat,
negativeZero: retrieved?.normalNumber,
escapeStr: retrieved?.testString,
longStr: retrieved?.longString?.length
});
expect(retrieved?.maxInt).toBe(Number.MAX_SAFE_INTEGER);
expect(retrieved?.minFloat).toBe(Number.MIN_VALUE);
expect(retrieved?.testString).toBe('\\n\\t\\r"\'\\\\');
expect(retrieved?.longString).toBe('a'.repeat(1000));
console.log('✅ 边界情况测试通过');
});
});

View File

@@ -0,0 +1,512 @@
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
/**
* System初始化测试套件
*
* 测试覆盖:
* - 系统初始化时序问题(先添加实体 vs 先添加系统)
* - 系统重复初始化防护
* - 动态组件修改响应
* - 系统生命周期管理
*/
// 测试组件
class PositionComponent extends Component {
public x: number;
public y: number;
constructor(...args: unknown[]) {
super();
const [x = 0, y = 0] = args as [number?, number?];
this.x = x;
this.y = y;
}
}
class VelocityComponent extends Component {
public vx: number;
public vy: number;
constructor(...args: unknown[]) {
super();
const [vx = 0, vy = 0] = args as [number?, number?];
this.vx = vx;
this.vy = vy;
}
}
class HealthComponent extends Component {
public health: number;
constructor(...args: unknown[]) {
super();
const [health = 100] = args as [number?];
this.health = health;
}
}
class TagComponent extends Component {
public tag: string;
constructor(...args: unknown[]) {
super();
const [tag = ''] = args as [string?];
this.tag = tag;
}
}
class TestComponent extends Component {
public value: number;
constructor(...args: unknown[]) {
super();
const [value = 0] = args as [number?];
this.value = value;
}
}
// 测试系统
class MovementSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public initializeCalled = false;
public onAddedEntities: Entity[] = [];
public onRemovedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent, VelocityComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override onAdded(entity: Entity): void {
this.onAddedEntities.push(entity);
}
protected override onRemoved(entity: Entity): void {
this.onRemovedEntities.push(entity);
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
for (const entity of entities) {
const position = entity.getComponent(PositionComponent)!;
const velocity = entity.getComponent(VelocityComponent)!;
position.x += velocity.vx;
position.y += velocity.vy;
}
}
}
class HealthSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public initializeCalled = false;
public onAddedEntities: Entity[] = [];
public onRemovedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(HealthComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override onAdded(entity: Entity): void {
this.onAddedEntities.push(entity);
}
protected override onRemoved(entity: Entity): void {
this.onRemovedEntities.push(entity);
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
for (const entity of entities) {
const health = entity.getComponent(HealthComponent)!;
if (health.health <= 0) {
entity.enabled = false;
}
}
}
}
class MultiComponentSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public initializeCalled = false;
public onAddedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent, HealthComponent, TagComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override onAdded(entity: Entity): void {
this.onAddedEntities.push(entity);
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
}
}
class TrackingSystem extends EntitySystem {
public initializeCallCount = 0;
public onChangedCallCount = 0;
public trackedEntities: Entity[] = [];
public override initialize(): void {
const wasInitialized = (this as any)._initialized;
super.initialize();
if (!wasInitialized) {
this.initializeCallCount++;
if (this.scene) {
for (const entity of this.scene.entities.buffer) {
this.onChanged(entity);
}
}
}
}
public onChanged(entity: Entity): void {
this.onChangedCallCount++;
if (this.isInterestedEntity(entity)) {
if (!this.trackedEntities.includes(entity)) {
this.trackedEntities.push(entity);
}
} else {
const index = this.trackedEntities.indexOf(entity);
if (index !== -1) {
this.trackedEntities.splice(index, 1);
}
}
}
public isInterestedEntity(entity: Entity): boolean {
return entity.hasComponent(TestComponent);
}
}
describe('SystemInitialization - 系统初始化测试', () => {
let scene: Scene;
beforeEach(() => {
ComponentTypeManager.instance.reset();
scene = new Scene();
scene.name = 'InitializationTestScene';
});
describe('初始化时序', () => {
test('先添加实体再添加系统 - 系统应该正确初始化', () => {
const player = scene.createEntity('Player');
player.addComponent(new PositionComponent(10, 20));
player.addComponent(new VelocityComponent(1, 1));
const system = new MovementSystem();
scene.addEntityProcessor(system);
expect(system.initializeCalled).toBe(true);
expect(system.onAddedEntities).toHaveLength(1);
expect(system.onAddedEntities[0]).toBe(player);
});
test('先添加系统再添加实体 - 系统应该正确响应', () => {
const system = new MovementSystem();
scene.addEntityProcessor(system);
expect(system.initializeCalled).toBe(true);
expect(system.onAddedEntities).toHaveLength(0);
const player = scene.createEntity('Player');
player.addComponent(new PositionComponent(10, 20));
player.addComponent(new VelocityComponent(1, 1));
scene.update(); // 触发系统查询
expect(system.onAddedEntities).toHaveLength(1);
expect(system.onAddedEntities[0]).toBe(player);
});
test('先添加部分实体,再添加系统,再添加更多实体', () => {
const entity1 = scene.createEntity('Entity1');
entity1.addComponent(new PositionComponent(0, 0));
entity1.addComponent(new VelocityComponent(1, 0));
const system = new MovementSystem();
scene.addEntityProcessor(system);
expect(system.onAddedEntities).toHaveLength(1);
const entity2 = scene.createEntity('Entity2');
entity2.addComponent(new PositionComponent(0, 0));
entity2.addComponent(new VelocityComponent(0, 1));
scene.update(); // 触发系统查询
expect(system.onAddedEntities).toHaveLength(2);
expect(system.onAddedEntities[1]).toBe(entity2);
});
test('批量实体创建后系统初始化应该正确', () => {
const entities: Entity[] = [];
for (let i = 0; i < 5; i++) {
const entity = scene.createEntity(`Entity_${i}`);
entity.addComponent(new PositionComponent(i, i));
entity.addComponent(new VelocityComponent(1, 1));
entities.push(entity);
}
const system = new MovementSystem();
scene.addEntityProcessor(system);
expect(system.onAddedEntities).toHaveLength(5);
for (let i = 0; i < 5; i++) {
expect(system.onAddedEntities).toContain(entities[i]);
}
});
});
describe('重复初始化防护', () => {
test('系统被多次添加到场景 - 应该防止重复初始化', () => {
const entity = scene.createEntity('TestEntity');
entity.addComponent(new TestComponent(10));
const system = new TrackingSystem();
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1);
expect(system.trackedEntities).toHaveLength(1);
expect(system.onChangedCallCount).toBe(1);
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1);
expect(system.trackedEntities).toHaveLength(1);
expect(system.onChangedCallCount).toBe(1);
});
test('手动多次调用initialize - 应该防止重复处理', () => {
const entity = scene.createEntity('TestEntity');
entity.addComponent(new TestComponent(10));
const system = new TrackingSystem();
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1);
expect(system.trackedEntities).toHaveLength(1);
expect(system.onChangedCallCount).toBe(1);
system.initialize();
expect(system.initializeCallCount).toBe(1);
expect(system.onChangedCallCount).toBe(1);
expect(system.trackedEntities).toHaveLength(1);
});
test('系统被移除后重新添加 - 应该重新初始化', () => {
const system = new TrackingSystem();
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1);
scene.removeEntityProcessor(system);
const entity = scene.createEntity('TestEntity');
entity.addComponent(new TestComponent(10));
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(2);
expect(system.trackedEntities).toHaveLength(1);
});
});
describe('动态组件修改响应', () => {
test('运行时添加组件 - 系统应该自动响应', () => {
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(0, 0));
const system = new MovementSystem();
scene.addEntityProcessor(system);
expect(system.onAddedEntities).toHaveLength(0);
entity.addComponent(new VelocityComponent(1, 1));
scene.update(); // 触发系统查询
expect(system.onAddedEntities).toHaveLength(1);
expect(system.onAddedEntities[0]).toBe(entity);
});
test('运行时移除组件 - 系统应该自动响应', () => {
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(0, 0));
const velocity = entity.addComponent(new VelocityComponent(1, 1));
const system = new MovementSystem();
scene.addEntityProcessor(system);
expect(system.onAddedEntities).toHaveLength(1);
entity.removeComponent(velocity);
scene.update(); // 触发系统查询
expect(system.onRemovedEntities).toHaveLength(1);
expect(system.onRemovedEntities[0]).toBe(entity);
});
test('复杂的组件添加移除序列', () => {
const entity = scene.createEntity('Entity');
const system = new MovementSystem();
scene.addEntityProcessor(system);
entity.addComponent(new PositionComponent(0, 0));
scene.update();
expect(system.onAddedEntities).toHaveLength(0);
const velocity1 = entity.addComponent(new VelocityComponent(1, 1));
scene.update();
expect(system.onAddedEntities).toHaveLength(1);
entity.removeComponent(velocity1);
scene.update();
expect(system.onRemovedEntities).toHaveLength(1);
entity.addComponent(new VelocityComponent(2, 2));
scene.update();
expect(system.onAddedEntities).toHaveLength(2);
});
test('多个组件同时满足条件', () => {
const entity = scene.createEntity('Entity');
const system = new MultiComponentSystem();
scene.addEntityProcessor(system);
entity.addComponent(new PositionComponent(0, 0));
scene.update();
expect(system.onAddedEntities).toHaveLength(0);
entity.addComponent(new HealthComponent(100));
scene.update();
expect(system.onAddedEntities).toHaveLength(0);
entity.addComponent(new TagComponent('player'));
scene.update();
expect(system.onAddedEntities).toHaveLength(1);
});
});
describe('多系统协同', () => {
test('多个系统同时响应同一实体', () => {
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
entity.addComponent(new HealthComponent(100));
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
expect(movementSystem.onAddedEntities).toHaveLength(1);
expect(healthSystem.onAddedEntities).toHaveLength(1);
expect(movementSystem.onAddedEntities[0]).toBe(entity);
expect(healthSystem.onAddedEntities[0]).toBe(entity);
});
test('不同系统匹配不同实体', () => {
const movingEntity = scene.createEntity('Moving');
movingEntity.addComponent(new PositionComponent(0, 0));
movingEntity.addComponent(new VelocityComponent(1, 1));
const healthEntity = scene.createEntity('Health');
healthEntity.addComponent(new HealthComponent(100));
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
expect(movementSystem.onAddedEntities).toHaveLength(1);
expect(movementSystem.onAddedEntities[0]).toBe(movingEntity);
expect(healthSystem.onAddedEntities).toHaveLength(1);
expect(healthSystem.onAddedEntities[0]).toBe(healthEntity);
});
test('组件变化影响多个系统', () => {
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
entity.addComponent(new HealthComponent(100));
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
const multiSystem = new MultiComponentSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
scene.addEntityProcessor(multiSystem);
entity.addComponent(new TagComponent('player'));
scene.update(); // 触发系统查询
expect(multiSystem.onAddedEntities).toHaveLength(1);
expect(multiSystem.onAddedEntities[0]).toBe(entity);
});
});
describe('边界情况', () => {
test('空场景添加系统', () => {
const system = new MovementSystem();
scene.addEntityProcessor(system);
expect(system.initializeCalled).toBe(true);
expect(system.onAddedEntities).toHaveLength(0);
});
test('实体禁用状态不影响系统初始化', () => {
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
entity.enabled = false;
const system = new MovementSystem();
scene.addEntityProcessor(system);
// 禁用的实体仍然被系统跟踪但在process时会被过滤
expect(system.onAddedEntities).toHaveLength(1);
});
test('系统初始化时实体被销毁', () => {
const entity = scene.createEntity('Entity');
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
const system = new MovementSystem();
scene.addEntityProcessor(system);
entity.destroy();
scene.update(); // 触发系统查询检测移除
expect(system.onRemovedEntities).toHaveLength(1);
});
});
});

View File

@@ -1,500 +0,0 @@
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { Matcher } from '../../../src/ECS/Utils/Matcher';
// 测试组件
class PositionComponent extends Component {
public x: number;
public y: number;
constructor(...args: unknown[]) {
super();
this.x = (args[0] as number) ?? 0;
this.y = (args[1] as number) ?? 0;
}
}
class VelocityComponent extends Component {
public vx: number;
public vy: number;
constructor(...args: unknown[]) {
super();
this.vx = (args[0] as number) ?? 0;
this.vy = (args[1] as number) ?? 0;
}
}
class HealthComponent extends Component {
public health: number;
constructor(...args: unknown[]) {
super();
this.health = (args[0] as number) ?? 100;
}
}
class TagComponent extends Component {
public tag: string;
constructor(...args: unknown[]) {
super();
this.tag = (args[0] as string) ?? '';
}
}
// 测试系统
class MovementSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public initializeCalled = false;
public onAddedEntities: Entity[] = [];
public onRemovedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(PositionComponent, VelocityComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override onAdded(entity: Entity): void {
this.onAddedEntities.push(entity);
}
protected override onRemoved(entity: Entity): void {
this.onRemovedEntities.push(entity);
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
for (const entity of entities) {
const position = entity.getComponent(PositionComponent)!;
const velocity = entity.getComponent(VelocityComponent)!;
position.x += velocity.vx;
position.y += velocity.vy;
}
}
}
class HealthSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public initializeCalled = false;
public onAddedEntities: Entity[] = [];
public onRemovedEntities: Entity[] = [];
constructor() {
super(Matcher.empty().all(HealthComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override onAdded(entity: Entity): void {
this.onAddedEntities.push(entity);
}
protected override onRemoved(entity: Entity): void {
this.onRemovedEntities.push(entity);
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
for (const entity of entities) {
const health = entity.getComponent(HealthComponent)!;
if (health.health <= 0) {
entity.enabled = false;
}
}
}
}
class MultiComponentSystem extends EntitySystem {
public processedEntities: Entity[] = [];
public initializeCalled = false;
constructor() {
super(Matcher.empty().all(PositionComponent, HealthComponent, TagComponent));
}
public override initialize(): void {
this.initializeCalled = true;
super.initialize();
}
protected override process(entities: Entity[]): void {
this.processedEntities = [...entities];
}
}
describe('ECS系统初始化时序问题深度测试', () => {
let scene: Scene;
beforeEach(() => {
scene = new Scene();
scene.name = "InitializeTestScene";
});
describe('基础时序问题测试', () => {
test('先添加实体再添加系统 - 系统应该正确初始化', () => {
// 创建实体并添加组件
const player = scene.createEntity("Player");
player.addComponent(new PositionComponent(10, 20));
player.addComponent(new VelocityComponent(1, 1));
player.addComponent(new HealthComponent(100));
const enemy = scene.createEntity("Enemy");
enemy.addComponent(new PositionComponent(50, 60));
enemy.addComponent(new VelocityComponent(-1, 0));
enemy.addComponent(new HealthComponent(80));
// 验证实体已创建
expect(scene.entities.count).toBe(2);
// 添加系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证initialize方法被调用
expect(movementSystem.initializeCalled).toBe(true);
expect(healthSystem.initializeCalled).toBe(true);
// 验证系统正确识别已存在的实体
expect(movementSystem.entities.length).toBe(2);
expect(healthSystem.entities.length).toBe(2);
// 验证onAdded回调被正确调用
expect(movementSystem.onAddedEntities.length).toBe(2);
expect(movementSystem.onAddedEntities).toContain(player);
expect(movementSystem.onAddedEntities).toContain(enemy);
// 运行更新确认处理
scene.update();
expect(movementSystem.processedEntities.length).toBe(2);
expect(healthSystem.processedEntities.length).toBe(2);
// 检查移动逻辑是否生效
const playerPos = player.getComponent(PositionComponent)!;
expect(playerPos.x).toBe(11);
expect(playerPos.y).toBe(21);
});
test('先添加系统再添加实体 - 正常工作', () => {
// 先添加系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证initialize被调用但没有发现实体
expect(movementSystem.initializeCalled).toBe(true);
expect(healthSystem.initializeCalled).toBe(true);
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
// 创建实体
const player = scene.createEntity("Player");
player.addComponent(new PositionComponent(10, 20));
player.addComponent(new VelocityComponent(1, 1));
player.addComponent(new HealthComponent(100));
// 系统应该自动识别新实体
expect(movementSystem.entities.length).toBe(1);
expect(healthSystem.entities.length).toBe(1);
expect(movementSystem.onAddedEntities.length).toBe(1);
});
});
describe('复杂场景的时序测试', () => {
test('部分匹配实体的初始化', () => {
// 创建不同类型的实体
const fullEntity = scene.createEntity("FullEntity");
fullEntity.addComponent(new PositionComponent(0, 0));
fullEntity.addComponent(new VelocityComponent(1, 1));
fullEntity.addComponent(new HealthComponent(100));
const partialEntity1 = scene.createEntity("PartialEntity1");
partialEntity1.addComponent(new PositionComponent(10, 10));
partialEntity1.addComponent(new HealthComponent(50));
// 缺少VelocityComponent
const partialEntity2 = scene.createEntity("PartialEntity2");
partialEntity2.addComponent(new PositionComponent(20, 20));
partialEntity2.addComponent(new VelocityComponent(2, 2));
// 缺少HealthComponent
// 添加系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证选择性匹配
expect(movementSystem.entities).toContain(fullEntity);
expect(movementSystem.entities).not.toContain(partialEntity1);
expect(movementSystem.entities).toContain(partialEntity2);
expect(movementSystem.entities.length).toBe(2);
expect(healthSystem.entities).toContain(fullEntity);
expect(healthSystem.entities).toContain(partialEntity1);
expect(healthSystem.entities).not.toContain(partialEntity2);
expect(healthSystem.entities.length).toBe(2);
});
test('多组件要求系统的初始化', () => {
// 创建具有不同组件组合的实体
const entity1 = scene.createEntity("Entity1");
entity1.addComponent(new PositionComponent(0, 0));
entity1.addComponent(new HealthComponent(100));
entity1.addComponent(new TagComponent("player"));
const entity2 = scene.createEntity("Entity2");
entity2.addComponent(new PositionComponent(10, 10));
entity2.addComponent(new HealthComponent(80));
// 缺少TagComponent
const entity3 = scene.createEntity("Entity3");
entity3.addComponent(new PositionComponent(20, 20));
entity3.addComponent(new TagComponent("enemy"));
// 缺少HealthComponent
// 添加要求三个组件的系统
const multiSystem = new MultiComponentSystem();
scene.addEntityProcessor(multiSystem);
// 只有entity1应该匹配
expect(multiSystem.entities.length).toBe(1);
expect(multiSystem.entities).toContain(entity1);
expect(multiSystem.entities).not.toContain(entity2);
expect(multiSystem.entities).not.toContain(entity3);
});
test('批量实体创建后的系统初始化', () => {
// 批量创建实体
const entities = scene.createEntities(10, "BatchEntity");
// 为所有实体添加组件
entities.forEach((entity, index) => {
entity.addComponent(new PositionComponent(index * 10, index * 10));
entity.addComponent(new VelocityComponent(index, index));
if (index % 2 === 0) {
entity.addComponent(new HealthComponent(100 - index * 10));
}
});
// 添加系统
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
// 验证系统正确处理批量实体
expect(movementSystem.entities.length).toBe(10); // 所有实体都有Position+Velocity
expect(healthSystem.entities.length).toBe(5); // 只有偶数索引的实体有Health
// 验证onAdded回调被正确调用
expect(movementSystem.onAddedEntities.length).toBe(10);
expect(healthSystem.onAddedEntities.length).toBe(5);
});
});
describe('动态组件修改后的系统响应', () => {
test('运行时添加组件 - 系统自动响应', () => {
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
// 创建只有位置组件的实体
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(0, 0));
// 系统不应该匹配
expect(movementSystem.entities.length).toBe(0);
// 添加速度组件
entity.addComponent(new VelocityComponent(5, 5));
// 系统应该立即识别
expect(movementSystem.entities.length).toBe(1);
expect(movementSystem.entities).toContain(entity);
expect(movementSystem.onAddedEntities).toContain(entity);
});
test('运行时移除组件 - 系统自动响应', () => {
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
// 创建完整的可移动实体
const entity = scene.createEntity("MovableEntity");
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(5, 5));
// 系统应该识别
expect(movementSystem.entities.length).toBe(1);
// 移除速度组件
const velocityComponent = entity.getComponent(VelocityComponent);
if (velocityComponent) {
entity.removeComponent(velocityComponent);
}
// 系统应该移除实体
expect(movementSystem.entities.length).toBe(0);
expect(movementSystem.onRemovedEntities).toContain(entity);
});
test('复杂的组件添加移除序列', () => {
const movementSystem = new MovementSystem();
const healthSystem = new HealthSystem();
scene.addEntityProcessor(movementSystem);
scene.addEntityProcessor(healthSystem);
const entity = scene.createEntity("ComplexEntity");
// 初始状态:无组件
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
// 添加位置组件
entity.addComponent(new PositionComponent(0, 0));
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
// 添加健康组件
entity.addComponent(new HealthComponent(100));
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(1);
// 添加速度组件
entity.addComponent(new VelocityComponent(1, 1));
expect(movementSystem.entities.length).toBe(1);
expect(healthSystem.entities.length).toBe(1);
// 移除健康组件
const healthComponent = entity.getComponent(HealthComponent);
if (healthComponent) {
entity.removeComponent(healthComponent);
}
expect(movementSystem.entities.length).toBe(1);
expect(healthSystem.entities.length).toBe(0);
// 移除位置组件
const positionComponent = entity.getComponent(PositionComponent);
if (positionComponent) {
entity.removeComponent(positionComponent);
}
expect(movementSystem.entities.length).toBe(0);
expect(healthSystem.entities.length).toBe(0);
});
});
describe('系统重复添加和移除测试', () => {
test('重复添加同一个系统 - 应该忽略', () => {
const movementSystem = new MovementSystem();
// 第一次添加
scene.addEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(1);
expect(movementSystem.initializeCalled).toBe(true);
// 重置标志
movementSystem.initializeCalled = false;
// 第二次添加同一个系统
scene.addEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(1); // 没有增加
expect(movementSystem.initializeCalled).toBe(false); // initialize不应该再次调用
});
test('添加后移除再添加 - 应该重新初始化', () => {
const entity = scene.createEntity("TestEntity");
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
const movementSystem = new MovementSystem();
// 第一次添加
scene.addEntityProcessor(movementSystem);
expect(movementSystem.entities.length).toBe(1);
expect(movementSystem.initializeCalled).toBe(true);
// 移除系统
scene.removeEntityProcessor(movementSystem);
expect(scene.entityProcessors.count).toBe(0);
// 重置状态
movementSystem.initializeCalled = false;
movementSystem.onAddedEntities = [];
// 重新添加
scene.addEntityProcessor(movementSystem);
expect(movementSystem.entities.length).toBe(1);
expect(movementSystem.initializeCalled).toBe(true);
expect(movementSystem.onAddedEntities.length).toBe(1);
});
});
describe('空场景和空系统的边界情况', () => {
test('空场景添加系统 - 不应该出错', () => {
const movementSystem = new MovementSystem();
expect(() => {
scene.addEntityProcessor(movementSystem);
}).not.toThrow();
expect(movementSystem.initializeCalled).toBe(true);
expect(movementSystem.entities.length).toBe(0);
});
test('有实体但没有匹配组件 - 系统应该为空', () => {
// 创建只有健康组件的实体
const entity = scene.createEntity("HealthOnlyEntity");
entity.addComponent(new HealthComponent(100));
// 添加移动系统需要Position+Velocity
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
expect(movementSystem.entities.length).toBe(0);
expect(movementSystem.onAddedEntities.length).toBe(0);
});
test('实体被禁用 - 系统仍应包含但不处理', () => {
const entity = scene.createEntity("DisabledEntity");
entity.addComponent(new PositionComponent(0, 0));
entity.addComponent(new VelocityComponent(1, 1));
const movementSystem = new MovementSystem();
scene.addEntityProcessor(movementSystem);
expect(movementSystem.entities.length).toBe(1);
// 禁用实体
entity.enabled = false;
// 系统仍然包含实体,但处理时应该跳过
expect(movementSystem.entities.length).toBe(1);
scene.update();
// 处理逻辑中应该检查enabled状态
// 由于实体被禁用,位置不应该改变(这取决于系统实现)
});
});
afterEach(() => {
scene.destroyAllEntities();
const processors = [...scene.entityProcessors.processors];
processors.forEach(processor => scene.removeEntityProcessor(processor));
});
});

View File

@@ -1,140 +0,0 @@
import { Scene } from '../../../src/ECS/Scene';
import { Entity } from '../../../src/ECS/Entity';
import { Component } from '../../../src/ECS/Component';
import { EntitySystem } from '../../../src/ECS/Systems/EntitySystem';
import { ComponentTypeManager } from '../../../src/ECS/Utils/ComponentTypeManager';
class TestComponent extends Component {
public value: number = 0;
constructor(...args: unknown[]) {
super();
const [value = 0] = args as [number?];
this.value = value;
}
}
class TrackingSystem extends EntitySystem {
public initializeCallCount = 0;
public onChangedCallCount = 0;
public trackedEntities: Entity[] = [];
public override initialize(): void {
// 必须先调用父类的initialize来检查防重复逻辑
const wasInitialized = (this as any)._initialized;
super.initialize();
// 只有在真正执行初始化时才增加计数和处理实体
if (!wasInitialized) {
this.initializeCallCount++;
// 处理所有现有实体
if (this.scene) {
for (const entity of this.scene.entities.buffer) {
this.onChanged(entity);
}
}
}
}
public onChanged(entity: Entity): void {
this.onChangedCallCount++;
if (this.isInterestedEntity(entity)) {
if (!this.trackedEntities.includes(entity)) {
this.trackedEntities.push(entity);
}
} else {
const index = this.trackedEntities.indexOf(entity);
if (index !== -1) {
this.trackedEntities.splice(index, 1);
}
}
}
public isInterestedEntity(entity: Entity): boolean {
return entity.hasComponent(TestComponent);
}
}
describe('系统多次初始化问题测试', () => {
let scene: Scene;
let system: TrackingSystem;
beforeEach(() => {
ComponentTypeManager.instance.reset();
scene = new Scene();
system = new TrackingSystem();
});
test('系统被多次添加到场景 - 应该防止重复初始化', () => {
const entity = scene.createEntity('TestEntity');
entity.addComponent(new TestComponent(10));
// 第一次添加系统
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1);
expect(system.trackedEntities.length).toBe(1);
expect(system.onChangedCallCount).toBe(1);
// 再次添加同一个系统 - 应该被忽略
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1); // 不应该增加
expect(system.trackedEntities.length).toBe(1); // 实体不应该重复
expect(system.onChangedCallCount).toBe(1); // onChanged不应该重复调用
});
test('手动多次调用initialize - 应该防止重复处理', () => {
const entity = scene.createEntity('TestEntity');
entity.addComponent(new TestComponent(10));
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1);
expect(system.trackedEntities.length).toBe(1);
expect(system.onChangedCallCount).toBe(1);
// 手动再次调用initialize - 应该被防止
system.initialize();
expect(system.initializeCallCount).toBe(1); // 不应该增加
expect(system.onChangedCallCount).toBe(1); // onChanged不应该重复调用
expect(system.trackedEntities.length).toBe(1);
});
test('系统被移除后重新添加 - 应该重新初始化', () => {
const entity = scene.createEntity('TestEntity');
entity.addComponent(new TestComponent(10));
// 添加系统
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1);
expect(system.trackedEntities.length).toBe(1);
// 移除系统
scene.removeEntityProcessor(system);
// 重新添加系统 - 应该重新初始化
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(2); // 应该重新初始化
expect(system.trackedEntities.length).toBe(1);
});
test('多个实体的重复初始化应该被防止', () => {
// 创建多个实体
const entities = [];
for (let i = 0; i < 5; i++) {
const entity = scene.createEntity(`Entity${i}`);
entity.addComponent(new TestComponent(i));
entities.push(entity);
}
scene.addEntityProcessor(system);
expect(system.initializeCallCount).toBe(1);
expect(system.trackedEntities.length).toBe(5);
expect(system.onChangedCallCount).toBe(5);
// 手动再次初始化 - 应该被防止
system.initialize();
expect(system.initializeCallCount).toBe(1); // 不应该增加
expect(system.onChangedCallCount).toBe(5); // 不应该重复处理
expect(system.trackedEntities.length).toBe(5);
});
});