From f41c1a3ca34a3175f883660c0f055bd21d809ba8 Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Wed, 8 Oct 2025 12:04:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=86=97=E4=BD=99=E6=B5=8B=E8=AF=95=E5=90=88?= =?UTF-8?q?=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ECS/Core/SoAStorage.collections.test.ts | 253 ------- .../ECS/Core/SoAStorage.complete.test.ts | 631 ++++++++++++++++++ .../ECS/Core/SoAStorage.comprehensive.test.ts | 308 --------- .../ECS/Core/SoAStorage.decorators.test.ts | 171 ----- .../ECS/Core/SoAStorage.edge-case.test.ts | 129 ---- .../tests/ECS/Core/SoAStorage.types.test.ts | 159 ----- .../ECS/Core/SystemInitialization.test.ts | 512 ++++++++++++++ .../ECS/Core/SystemInitializeIssue.test.ts | 500 -------------- .../ECS/Core/SystemMultipleInitialize.test.ts | 140 ---- 9 files changed, 1143 insertions(+), 1660 deletions(-) delete mode 100644 packages/core/tests/ECS/Core/SoAStorage.collections.test.ts create mode 100644 packages/core/tests/ECS/Core/SoAStorage.complete.test.ts delete mode 100644 packages/core/tests/ECS/Core/SoAStorage.comprehensive.test.ts delete mode 100644 packages/core/tests/ECS/Core/SoAStorage.decorators.test.ts delete mode 100644 packages/core/tests/ECS/Core/SoAStorage.edge-case.test.ts delete mode 100644 packages/core/tests/ECS/Core/SoAStorage.types.test.ts create mode 100644 packages/core/tests/ECS/Core/SystemInitialization.test.ts delete mode 100644 packages/core/tests/ECS/Core/SystemInitializeIssue.test.ts delete mode 100644 packages/core/tests/ECS/Core/SystemMultipleInitialize.test.ts diff --git a/packages/core/tests/ECS/Core/SoAStorage.collections.test.ts b/packages/core/tests/ECS/Core/SoAStorage.collections.test.ts deleted file mode 100644 index 14a1091c..00000000 --- a/packages/core/tests/ECS/Core/SoAStorage.collections.test.ts +++ /dev/null @@ -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 = new Map(); - - // 序列化Set存储 - @SerializeSet - public achievements: Set = 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('✅ 性能测试完成'); - }); -}); \ No newline at end of file diff --git a/packages/core/tests/ECS/Core/SoAStorage.complete.test.ts b/packages/core/tests/ECS/Core/SoAStorage.complete.test.ts new file mode 100644 index 00000000..e6dd301a --- /dev/null +++ b/packages/core/tests/ECS/Core/SoAStorage.complete.test.ts @@ -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; + + @SerializeSet + public setData: Set; + + @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?, Set?, 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; + + @SerializeSet + public flags: Set; + + @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-45,Number.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; + + 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; + + 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; + + 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; + 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; + + 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); + }); + }); +}); diff --git a/packages/core/tests/ECS/Core/SoAStorage.comprehensive.test.ts b/packages/core/tests/ECS/Core/SoAStorage.comprehensive.test.ts deleted file mode 100644 index 6c178104..00000000 --- a/packages/core/tests/ECS/Core/SoAStorage.comprehensive.test.ts +++ /dev/null @@ -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 = new Map(); - - @SerializeSet - public flags: Set = 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; - - // 添加多个组件 - 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; - 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; - - // 添加测试数据 - 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; - - // 添加测试数据 - 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('✅ 向量化操作测试通过'); - }); -}); \ No newline at end of file diff --git a/packages/core/tests/ECS/Core/SoAStorage.decorators.test.ts b/packages/core/tests/ECS/Core/SoAStorage.decorators.test.ts deleted file mode 100644 index 51279fdc..00000000 --- a/packages/core/tests/ECS/Core/SoAStorage.decorators.test.ts +++ /dev/null @@ -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; - - // 检查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('✅ 性能测试完成,数据完整性验证通过'); - }); -}); \ No newline at end of file diff --git a/packages/core/tests/ECS/Core/SoAStorage.edge-case.test.ts b/packages/core/tests/ECS/Core/SoAStorage.edge-case.test.ts deleted file mode 100644 index cb2fa178..00000000 --- a/packages/core/tests/ECS/Core/SoAStorage.edge-case.test.ts +++ /dev/null @@ -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'); - }); -}); \ No newline at end of file diff --git a/packages/core/tests/ECS/Core/SoAStorage.types.test.ts b/packages/core/tests/ECS/Core/SoAStorage.types.test.ts deleted file mode 100644 index c89bbf84..00000000 --- a/packages/core/tests/ECS/Core/SoAStorage.types.test.ts +++ /dev/null @@ -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('✅ 边界情况测试通过'); - }); -}); \ No newline at end of file diff --git a/packages/core/tests/ECS/Core/SystemInitialization.test.ts b/packages/core/tests/ECS/Core/SystemInitialization.test.ts new file mode 100644 index 00000000..e156fb58 --- /dev/null +++ b/packages/core/tests/ECS/Core/SystemInitialization.test.ts @@ -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); + }); + }); +}); diff --git a/packages/core/tests/ECS/Core/SystemInitializeIssue.test.ts b/packages/core/tests/ECS/Core/SystemInitializeIssue.test.ts deleted file mode 100644 index d6dd6520..00000000 --- a/packages/core/tests/ECS/Core/SystemInitializeIssue.test.ts +++ /dev/null @@ -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)); - }); -}); \ No newline at end of file diff --git a/packages/core/tests/ECS/Core/SystemMultipleInitialize.test.ts b/packages/core/tests/ECS/Core/SystemMultipleInitialize.test.ts deleted file mode 100644 index a48676da..00000000 --- a/packages/core/tests/ECS/Core/SystemMultipleInitialize.test.ts +++ /dev/null @@ -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); - }); -}); \ No newline at end of file