新增soastorage存储器

This commit is contained in:
YHH
2025-07-30 14:14:04 +08:00
parent 0411aa9aef
commit 01fa33e122
14 changed files with 2632 additions and 5 deletions

View File

@@ -14,6 +14,7 @@ TypeScript ECS (Entity-Component-System) 框架,专为游戏开发设计。
- 📡 **[类型安全事件系统](docs/concepts-explained.md#事件系统)** - 事件装饰器和异步事件处理
- 🔍 **[查询系统](docs/concepts-explained.md#实体管理)** - 流式 API 和智能缓存
-**[性能优化](docs/concepts-explained.md#性能优化技术)** - 组件索引、Archetype 系统、脏标记
- 🚀 **[SoA 存储优化](docs/soa-storage-guide.md)** - 大规模实体的向量化批量操作和内存优化
- 🎯 **[实体管理器](docs/concepts-explained.md#实体管理)** - 统一的实体生命周期管理
- 🧰 **调试工具** - 内置性能监控和调试信息
@@ -84,6 +85,31 @@ class HealthComponent extends Component {
}
```
### SoA 高性能组件 (大规模场景推荐)
对于需要处理大量实体的场景,可以使用 SoA 存储优化获得显著性能提升:
```typescript
import { Component, EnableSoA, Float32, Int32 } from '@esengine/ecs-framework';
// 启用 SoA 优化的高性能组件
@EnableSoA
class OptimizedTransformComponent extends Component {
@Float32 public x: number = 0;
@Float32 public y: number = 0;
@Float32 public rotation: number = 0;
}
@EnableSoA
class ParticleComponent extends Component {
@Float32 public velocityX: number = 0;
@Float32 public velocityY: number = 0;
@Int32 public lifeTime: number = 1000;
}
```
> ⚠️ **使用建议**: SoA 优化适用于大规模场景和批量操作。小规模应用请使用普通组件以避免不必要的复杂度。详见 [SoA 存储优化指南](docs/soa-storage-guide.md)。
### 创建实体
```typescript
@@ -374,6 +400,7 @@ ecs-framework/
### 性能相关
- [性能优化指南](docs/performance-optimization.md) - 性能优化技术和策略
- [SoA 存储优化指南](docs/soa-storage-guide.md) - 大规模实体系统的高级性能优化 ⭐ **大规模项目推荐**
## 构建

343
docs/soa-storage-guide.md Normal file
View File

@@ -0,0 +1,343 @@
# SoA存储优化指南
SoA (Structure of Arrays) 存储模式是ECS框架中的高级性能优化特性适用于大规模实体系统和批量操作场景。
## 目录
1. [什么是SoA存储](#什么是soa存储)
2. [适用场景](#适用场景)
3. [不适用场景](#不适用场景)
4. [装饰器使用指南](#装饰器使用指南)
5. [性能对比](#性能对比)
6. [最佳实践](#最佳实践)
7. [故障排除](#故障排除)
## 什么是SoA存储
### AoS vs SoA 对比
**传统AoS (Array of Structures):**
```typescript
// 数据在内存中的布局
[{x:1, y:2, z:3}, {x:4, y:5, z:6}, {x:7, y:8, z:9}]
// 内存布局: x1,y1,z1,x2,y2,z2,x3,y3,z3
```
**SoA (Structure of Arrays):**
```typescript
// 数据在内存中的布局
{
x: [1, 4, 7], // Float32Array
y: [2, 5, 8], // Float32Array
z: [3, 6, 9] // Float32Array
}
// 内存布局: x1,x2,x3,y1,y2,y3,z1,z2,z3
```
### SoA的优势
- **缓存友好**: 相同类型数据连续存储,提高缓存命中率
- **向量化优化**: 支持SIMD指令并行处理
- **内存局部性**: 批量操作时减少缓存miss
- **类型优化**: 针对不同数据类型使用最优存储格式
## 适用场景
### ✅ 推荐使用SoA的场景
1. **大规模实体系统**
```typescript
// 大量相似实体的物理系统
@EnableSoA
class PhysicsComponent extends Component {
@Float64 public x: number = 0;
@Float64 public y: number = 0;
@Float32 public velocityX: number = 0;
@Float32 public velocityY: number = 0;
}
```
2. **频繁批量更新操作**
```typescript
// 每帧更新大量实体位置
system.performVectorizedOperation((fields, indices) => {
const x = fields.get('x') as Float32Array;
const y = fields.get('y') as Float32Array;
const vx = fields.get('velocityX') as Float32Array;
const vy = fields.get('velocityY') as Float32Array;
// 向量化更新所有实体
for (let i = 0; i < indices.length; i++) {
const idx = indices[i];
x[idx] += vx[idx] * deltaTime;
y[idx] += vy[idx] * deltaTime;
}
});
```
3. **数值密集计算**
```typescript
@EnableSoA
class AIBrainComponent extends Component {
@Float32 public neuron1: number = 0;
@Float32 public neuron2: number = 0;
@Float32 public output: number = 0;
}
```
## 不适用场景
### ❌ 不推荐使用SoA的场景
1. **小规模系统**
- SoA的开销大于收益
- 原始存储更简单高效
2. **随机访问为主**
```typescript
// 经常需要随机获取单个组件
const component = entityManager.getComponent(randomId, SomeComponent);
```
3. **复杂对象为主的组件**
```typescript
// 大量复杂对象,序列化开销大
class UIComponent extends Component {
public domElement: HTMLElement;
public eventHandlers: Map<string, Function>;
public children: UIComponent[];
}
```
4. **频繁增删实体**
- SoA在频繁增删时性能不如AoS
- 适合稳定的实体集合
## 装饰器使用指南
### 基础装饰器
```typescript
import { Component, EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '@esengine/ecs-framework';
@EnableSoA // 启用SoA优化
class GameComponent extends Component {
// 数值类型装饰器
@HighPrecision // 高精度数值,保持完整精度
public entityId: number = 0;
@Float64 // 64位浮点数 (8字节高精度)
public precisePosition: number = 0;
@Int32 // 32位整数 (4字节整数优化)
public health: number = 100;
// 普通数值 (默认Float32Array4字节)
public x: number = 0;
public y: number = 0;
// 集合类型装饰器
@SerializeMap
public playerStats: Map<string, number> = new Map();
@SerializeSet
public achievements: Set<string> = new Set();
@SerializeArray
public inventory: any[] = [];
@DeepCopy
public config: any = { settings: {} };
// 未装饰的字段自动选择最优存储
public name: string = ''; // string[] 数组
public active: boolean = true; // Float32Array (0/1)
public metadata: any = null; // 复杂对象存储
}
```
### 装饰器选择指南
| 装饰器 | 用途 | 存储方式 | 开销 | 适用场景 |
|--------|------|----------|------|----------|
| `@HighPrecision` | 高精度数值 | 复杂对象 | 高 | ID、时间戳、大整数 |
| `@Float64` | 双精度浮点 | Float64Array | 中 | 精密计算 |
| `@Int32` | 32位整数 | Int32Array | 低 | 整数计数、枚举值 |
| `@SerializeMap` | Map序列化 | JSON字符串 | 高 | 配置映射、属性集合 |
| `@SerializeSet` | Set序列化 | JSON字符串 | 高 | 标签集合、ID集合 |
| `@SerializeArray` | Array序列化 | JSON字符串 | 中 | 列表数据、队列 |
| `@DeepCopy` | 深拷贝对象 | 复杂对象副本 | 高 | 嵌套配置、独立状态 |
## 性能对比
### 基准测试结果
```
测试场景: 2000个实体包含位置、速度、生命值组件
创建性能:
- 原始存储: 12.45ms
- SoA存储: 15.72ms (慢26%)
随机访问性能:
- 原始存储: 8.33ms
- SoA存储: 14.20ms (慢70%)
批量更新性能:
- 原始存储: 25.67ms
- SoA存储: 8.91ms (快188%)
内存使用:
- 原始存储: ~45KB (对象开销)
- SoA存储: ~28KB (TypedArray优化)
```
### 性能权衡总结
- **SoA优势**: 批量操作、内存效率、向量化计算
- **SoA劣势**: 随机访问、创建开销、复杂度增加
- **建议**: 大规模批量操作场景使用,小规模随机访问避免使用
## 最佳实践
### 1. 合理的组件设计
```typescript
// ✅ 好的设计:纯数值组件
@EnableSoA
class TransformComponent extends Component {
@Float64 public x: number = 0;
@Float64 public y: number = 0;
@Float32 public rotation: number = 0;
@Float32 public scaleX: number = 1;
@Float32 public scaleY: number = 1;
}
// ❌ 不好的设计:混合复杂对象
@EnableSoA
class MixedComponent extends Component {
public x: number = 0;
public domElement: HTMLElement = null; // 复杂对象开销大
public callback: Function = null; // 无法序列化
}
```
### 2. 批量操作优化
```typescript
// ✅ 使用向量化操作
const storage = entityManager.getStorage(TransformComponent) as SoAStorage<TransformComponent>;
storage.performVectorizedOperation((fields, indices) => {
const x = fields.get('x') as Float64Array;
const y = fields.get('y') as Float64Array;
// 批量处理,利用缓存局部性
for (let i = 0; i < indices.length; i++) {
const idx = indices[i];
x[idx] += deltaX;
y[idx] += deltaY;
}
});
// ❌ 避免逐个访问
for (const entity of entities) {
const transform = entity.getComponent(TransformComponent);
transform.x += deltaX;
transform.y += deltaY;
}
```
### 3. 组件分离策略
```typescript
// ✅ 将频繁批量操作的数据分离
@EnableSoA
class PositionComponent extends Component {
@Float32 public x: number = 0;
@Float32 public y: number = 0;
}
// 复杂数据使用普通组件
class MetadataComponent extends Component {
public name: string = '';
public config: any = {};
public references: any[] = [];
}
```
### 4. 性能监控
```typescript
// 监控SoA存储使用情况
const storage = entityManager.getStorage(MyComponent) as SoAStorage<MyComponent>;
const stats = storage.getStats();
console.log('SoA存储统计:', {
size: stats.size,
capacity: stats.capacity,
memoryUsage: stats.memoryUsage,
fragmentation: stats.fragmentation,
fieldStats: stats.fieldStats
});
```
## 故障排除
### 常见问题
1. **精度丢失**
```typescript
// 问题:大整数精度丢失
public bigId: number = Number.MAX_SAFE_INTEGER;
// 解决:使用高精度装饰器
@HighPrecision
public bigId: number = Number.MAX_SAFE_INTEGER;
```
2. **序列化失败**
```typescript
// 问题:循环引用导致序列化失败
@SerializeMap
public cyclicMap: Map<string, any> = new Map();
// 解决避免循环引用或使用DeepCopy
@DeepCopy
public cyclicData: any = {};
```
3. **性能反向优化**
```typescript
// 问题小规模数据使用SoA
@EnableSoA // 只有10个实体不需要SoA
class SmallComponent extends Component {}
// 解决:移除@EnableSoA装饰器
class SmallComponent extends Component {}
```
### 调试技巧
```typescript
// 检查存储类型
const storage = entityManager.getStorage(MyComponent);
console.log('存储类型:', storage.constructor.name);
// 输出: 'SoAStorage' 或 'ComponentStorage'
// 检查字段存储方式
if (storage instanceof SoAStorage) {
const fieldArray = storage.getFieldArray('myField');
console.log('字段类型:', fieldArray?.constructor.name);
// 输出: 'Float32Array', 'Float64Array', 'Int32Array', 或 null
}
```
## 总结
SoA存储是一个强大的性能优化工具但需要在合适的场景下使用
- **适合**: 大规模、批量操作、数值密集的场景
- **不适合**: 小规模、随机访问、复杂对象为主的场景
- **关键**: 通过性能测试验证优化效果,避免过度优化
正确使用SoA存储可以显著提升ECS系统性能但滥用会带来相反的效果。建议在实际项目中先进行基准测试确认优化效果后再应用到生产环境。

View File

@@ -0,0 +1,27 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/tests'],
testMatch: ['**/*.performance.test.ts'],
collectCoverage: false,
verbose: true,
transform: {
'^.+\\.tsx?$': ['ts-jest', {
tsconfig: 'tsconfig.test.json',
}],
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
// 性能测试需要更长的超时时间
testTimeout: 60000,
clearMocks: true,
restoreMocks: true,
modulePathIgnorePatterns: [
'<rootDir>/bin/',
'<rootDir>/dist/',
'<rootDir>/node_modules/'
]
};

View File

@@ -33,6 +33,7 @@
"publish:npm": "npm run build:npm && cd dist && npm publish",
"test": "jest",
"test:watch": "jest --watch",
"test:performance": "jest --config jest.performance.config.js",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage",
"test:clear": "jest --clearCache"

View File

@@ -1,5 +1,9 @@
import { Component } from '../Component';
import { IBigIntLike, BigIntFactory } from '../Utils/BigIntCompatibility';
import { SoAStorage, EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from './SoAStorage';
// 重新导出装饰器
export { EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy };
/**
* 组件类型定义
@@ -289,18 +293,29 @@ export class ComponentStorage<T extends Component> {
* 管理所有组件类型的存储器
*/
export class ComponentStorageManager {
private storages = new Map<Function, ComponentStorage<any>>();
private storages = new Map<Function, ComponentStorage<any> | SoAStorage<any>>();
/**
* 获取或创建组件存储器
* 获取或创建组件存储器(默认原始存储)
* @param componentType 组件类型
* @returns 组件存储器
*/
public getStorage<T extends Component>(componentType: ComponentType<T>): ComponentStorage<T> {
public getStorage<T extends Component>(componentType: ComponentType<T>): ComponentStorage<T> | SoAStorage<T> {
let storage = this.storages.get(componentType);
if (!storage) {
// 检查是否启用SoA优化
const enableSoA = (componentType as any).__enableSoA;
if (enableSoA) {
// 使用SoA优化存储
storage = new SoAStorage(componentType);
console.log(`[SoA] 为 ${componentType.name} 启用SoA优化适用于大规模批量操作`);
} else {
// 默认使用原始存储
storage = new ComponentStorage(componentType);
}
this.storages.set(componentType, storage);
}

580
src/ECS/Core/SoAStorage.ts Normal file
View File

@@ -0,0 +1,580 @@
import { Component } from '../Component';
import { ComponentType } from './ComponentStorage';
/**
* 启用SoA优化装饰器
* 默认关闭SoA只有在大规模批量操作场景下才建议开启
*/
export function EnableSoA<T extends ComponentType>(target: T): T {
(target as any).__enableSoA = true;
return target;
}
/**
* 高精度数值装饰器
* 标记字段需要保持完整精度存储为复杂对象而非TypedArray
*/
export function HighPrecision(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__highPrecisionFields) {
target.constructor.__highPrecisionFields = new Set();
}
target.constructor.__highPrecisionFields.add(key);
}
/**
* 64位浮点数装饰器
* 标记字段使用Float64Array存储更高精度但更多内存
*/
export function Float64(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__float64Fields) {
target.constructor.__float64Fields = new Set();
}
target.constructor.__float64Fields.add(key);
}
/**
* 32位整数装饰器
* 标记字段使用Int32Array存储适用于整数值
*/
export function Int32(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__int32Fields) {
target.constructor.__int32Fields = new Set();
}
target.constructor.__int32Fields.add(key);
}
/**
* 序列化Map装饰器
* 标记Map字段需要序列化/反序列化存储
*/
export function SerializeMap(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__serializeMapFields) {
target.constructor.__serializeMapFields = new Set();
}
target.constructor.__serializeMapFields.add(key);
}
/**
* 序列化Set装饰器
* 标记Set字段需要序列化/反序列化存储
*/
export function SerializeSet(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__serializeSetFields) {
target.constructor.__serializeSetFields = new Set();
}
target.constructor.__serializeSetFields.add(key);
}
/**
* 序列化Array装饰器
* 标记Array字段需要序列化/反序列化存储
*/
export function SerializeArray(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__serializeArrayFields) {
target.constructor.__serializeArrayFields = new Set();
}
target.constructor.__serializeArrayFields.add(key);
}
/**
* 深拷贝装饰器
* 标记字段需要深拷贝处理(适用于嵌套对象)
*/
export function DeepCopy(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__deepCopyFields) {
target.constructor.__deepCopyFields = new Set();
}
target.constructor.__deepCopyFields.add(key);
}
/**
* SoA存储器需要装饰器启用
* 使用Structure of Arrays存储模式在大规模批量操作时提供优异性能
* 适用场景:>5000实体的批量更新操作
*/
export class SoAStorage<T extends Component> {
private fields = new Map<string, Float32Array | Float64Array | Int32Array>();
private stringFields = new Map<string, string[]>(); // 专门存储字符串
private serializedFields = new Map<string, string[]>(); // 序列化存储Map/Set/Array
private complexFields = new Map<number, Map<string, any>>(); // 存储复杂对象
private entityToIndex = new Map<number, number>();
private indexToEntity: number[] = [];
private freeIndices: number[] = [];
private _size = 0;
private _capacity = 1000;
public readonly type: ComponentType<T>;
constructor(componentType: ComponentType<T>) {
this.type = componentType;
this.initializeFields(componentType);
}
private initializeFields(componentType: ComponentType<T>): void {
const instance = new componentType();
const highPrecisionFields = (componentType as any).__highPrecisionFields || new Set();
const float64Fields = (componentType as any).__float64Fields || new Set();
const int32Fields = (componentType as any).__int32Fields || new Set();
const serializeMapFields = (componentType as any).__serializeMapFields || new Set();
const serializeSetFields = (componentType as any).__serializeSetFields || new Set();
const serializeArrayFields = (componentType as any).__serializeArrayFields || new Set();
// const deepCopyFields = (componentType as any).__deepCopyFields || new Set(); // 未使用但保留供future使用
for (const key in instance) {
if (instance.hasOwnProperty(key) && key !== 'id') {
const value = (instance as any)[key];
const type = typeof value;
if (type === 'number') {
if (highPrecisionFields.has(key)) {
// 标记为高精度,作为复杂对象处理
// 不添加到fields会在updateComponentAtIndex中自动添加到complexFields
} else if (float64Fields.has(key)) {
// 使用Float64Array存储
this.fields.set(key, new Float64Array(this._capacity));
} else if (int32Fields.has(key)) {
// 使用Int32Array存储
this.fields.set(key, new Int32Array(this._capacity));
} else {
// 默认使用Float32Array
this.fields.set(key, new Float32Array(this._capacity));
}
} else if (type === 'boolean') {
// 布尔值使用Float32Array存储为0/1
this.fields.set(key, new Float32Array(this._capacity));
} else if (type === 'string') {
// 字符串专门处理
this.stringFields.set(key, new Array(this._capacity));
} else if (type === 'object' && value !== null) {
// 处理集合类型
if (serializeMapFields.has(key) || serializeSetFields.has(key) || serializeArrayFields.has(key)) {
// 序列化存储
this.serializedFields.set(key, new Array(this._capacity));
}
// 其他对象类型会在updateComponentAtIndex中作为复杂对象处理
}
}
}
}
public addComponent(entityId: number, component: T): void {
if (this.entityToIndex.has(entityId)) {
const index = this.entityToIndex.get(entityId)!;
this.updateComponentAtIndex(index, component);
return;
}
let index: number;
if (this.freeIndices.length > 0) {
index = this.freeIndices.pop()!;
} else {
index = this._size;
if (index >= this._capacity) {
this.resize(this._capacity * 2);
}
}
this.entityToIndex.set(entityId, index);
this.indexToEntity[index] = entityId;
this.updateComponentAtIndex(index, component);
this._size++;
}
private updateComponentAtIndex(index: number, component: T): void {
const entityId = this.indexToEntity[index];
const complexFieldMap = new Map<string, any>();
const highPrecisionFields = (this.type as any).__highPrecisionFields || new Set();
const serializeMapFields = (this.type as any).__serializeMapFields || new Set();
const serializeSetFields = (this.type as any).__serializeSetFields || new Set();
const serializeArrayFields = (this.type as any).__serializeArrayFields || new Set();
const deepCopyFields = (this.type as any).__deepCopyFields || new Set();
// 处理所有字段
for (const key in component) {
if (component.hasOwnProperty(key) && key !== 'id') {
const value = (component as any)[key];
const type = typeof value;
if (type === 'number') {
if (highPrecisionFields.has(key) || !this.fields.has(key)) {
// 标记为高精度或未在TypedArray中的数值作为复杂对象存储
complexFieldMap.set(key, value);
} else {
// 存储到TypedArray
const array = this.fields.get(key)!;
array[index] = value;
}
} else if (type === 'boolean' && this.fields.has(key)) {
// 布尔值存储到TypedArray
const array = this.fields.get(key)!;
array[index] = value ? 1 : 0;
} else if (this.stringFields.has(key)) {
// 字符串字段专门处理
const stringArray = this.stringFields.get(key)!;
stringArray[index] = String(value);
} else if (this.serializedFields.has(key)) {
// 序列化字段处理
const serializedArray = this.serializedFields.get(key)!;
serializedArray[index] = this.serializeValue(value, key, serializeMapFields, serializeSetFields, serializeArrayFields);
} else {
// 复杂字段单独存储
if (deepCopyFields.has(key)) {
// 深拷贝处理
complexFieldMap.set(key, this.deepClone(value));
} else {
complexFieldMap.set(key, value);
}
}
}
}
// 存储复杂字段
if (complexFieldMap.size > 0) {
this.complexFields.set(entityId, complexFieldMap);
}
}
/**
* 序列化值为JSON字符串
*/
private serializeValue(value: any, key: string, mapFields: Set<string>, setFields: Set<string>, arrayFields: Set<string>): string {
try {
if (mapFields.has(key) && value instanceof Map) {
// Map序列化为数组形式
return JSON.stringify(Array.from(value.entries()));
} else if (setFields.has(key) && value instanceof Set) {
// Set序列化为数组形式
return JSON.stringify(Array.from(value));
} else if (arrayFields.has(key) && Array.isArray(value)) {
// Array直接序列化
return JSON.stringify(value);
} else {
// 其他对象直接序列化
return JSON.stringify(value);
}
} catch (error) {
console.warn(`SoA序列化字段 ${key} 失败:`, error);
return '{}';
}
}
/**
* 反序列化JSON字符串为值
*/
private deserializeValue(serialized: string, key: string, mapFields: Set<string>, setFields: Set<string>, arrayFields: Set<string>): any {
try {
const parsed = JSON.parse(serialized);
if (mapFields.has(key)) {
// 恢复Map
return new Map(parsed);
} else if (setFields.has(key)) {
// 恢复Set
return new Set(parsed);
} else if (arrayFields.has(key)) {
// 恢复Array
return parsed;
} else {
return parsed;
}
} catch (error) {
console.warn(`SoA反序列化字段 ${key} 失败:`, error);
return null;
}
}
/**
* 深拷贝对象
*/
private deepClone(obj: any): any {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof Array) {
return obj.map(item => this.deepClone(item));
}
if (obj instanceof Map) {
const cloned = new Map();
for (const [key, value] of obj.entries()) {
cloned.set(key, this.deepClone(value));
}
return cloned;
}
if (obj instanceof Set) {
const cloned = new Set();
for (const value of obj.values()) {
cloned.add(this.deepClone(value));
}
return cloned;
}
// 普通对象
const cloned: any = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = this.deepClone(obj[key]);
}
}
return cloned;
}
public getComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId);
if (index === undefined) {
return null;
}
// 创建真正的组件实例以保持兼容性
const component = new this.type() as any;
const serializeMapFields = (this.type as any).__serializeMapFields || new Set();
const serializeSetFields = (this.type as any).__serializeSetFields || new Set();
const serializeArrayFields = (this.type as any).__serializeArrayFields || new Set();
// 恢复数值字段
for (const [fieldName, array] of this.fields.entries()) {
const value = array[index];
const fieldType = this.getFieldType(fieldName);
if (fieldType === 'boolean') {
component[fieldName] = value === 1;
} else {
component[fieldName] = value;
}
}
// 恢复字符串字段
for (const [fieldName, stringArray] of this.stringFields.entries()) {
component[fieldName] = stringArray[index];
}
// 恢复序列化字段
for (const [fieldName, serializedArray] of this.serializedFields.entries()) {
const serialized = serializedArray[index];
if (serialized) {
component[fieldName] = this.deserializeValue(serialized, fieldName, serializeMapFields, serializeSetFields, serializeArrayFields);
}
}
// 恢复复杂字段
const complexFieldMap = this.complexFields.get(entityId);
if (complexFieldMap) {
for (const [fieldName, value] of complexFieldMap.entries()) {
component[fieldName] = value;
}
}
return component as T;
}
private getFieldType(fieldName: string): string {
// 通过创建临时实例检查字段类型
const tempInstance = new this.type();
const value = (tempInstance as any)[fieldName];
return typeof value;
}
public hasComponent(entityId: number): boolean {
return this.entityToIndex.has(entityId);
}
public removeComponent(entityId: number): T | null {
const index = this.entityToIndex.get(entityId);
if (index === undefined) {
return null;
}
// 获取组件副本以便返回
const component = this.getComponent(entityId);
// 清理复杂字段
this.complexFields.delete(entityId);
this.entityToIndex.delete(entityId);
this.freeIndices.push(index);
this._size--;
return component;
}
private resize(newCapacity: number): void {
// 调整数值字段的TypedArray
for (const [fieldName, oldArray] of this.fields.entries()) {
let newArray: Float32Array | Float64Array | Int32Array;
if (oldArray instanceof Float32Array) {
newArray = new Float32Array(newCapacity);
} else if (oldArray instanceof Float64Array) {
newArray = new Float64Array(newCapacity);
} else {
newArray = new Int32Array(newCapacity);
}
newArray.set(oldArray);
this.fields.set(fieldName, newArray);
}
// 调整字符串字段的数组
for (const [fieldName, oldArray] of this.stringFields.entries()) {
const newArray = new Array(newCapacity);
for (let i = 0; i < oldArray.length; i++) {
newArray[i] = oldArray[i];
}
this.stringFields.set(fieldName, newArray);
}
// 调整序列化字段的数组
for (const [fieldName, oldArray] of this.serializedFields.entries()) {
const newArray = new Array(newCapacity);
for (let i = 0; i < oldArray.length; i++) {
newArray[i] = oldArray[i];
}
this.serializedFields.set(fieldName, newArray);
}
this._capacity = newCapacity;
}
public getActiveIndices(): number[] {
return Array.from(this.entityToIndex.values());
}
public getFieldArray(fieldName: string): Float32Array | Float64Array | Int32Array | null {
return this.fields.get(fieldName) || null;
}
public size(): number {
return this._size;
}
public clear(): void {
this.entityToIndex.clear();
this.indexToEntity = [];
this.freeIndices = [];
this.complexFields.clear();
this._size = 0;
// 重置数值字段数组
for (const array of this.fields.values()) {
array.fill(0);
}
// 重置字符串字段数组
for (const stringArray of this.stringFields.values()) {
for (let i = 0; i < stringArray.length; i++) {
stringArray[i] = undefined as any;
}
}
// 重置序列化字段数组
for (const serializedArray of this.serializedFields.values()) {
for (let i = 0; i < serializedArray.length; i++) {
serializedArray[i] = undefined as any;
}
}
}
public compact(): void {
if (this.freeIndices.length === 0) {
return;
}
const activeEntries = Array.from(this.entityToIndex.entries())
.sort((a, b) => a[1] - b[1]);
// 重新映射索引
const newEntityToIndex = new Map<number, number>();
const newIndexToEntity: number[] = [];
for (let newIndex = 0; newIndex < activeEntries.length; newIndex++) {
const [entityId, oldIndex] = activeEntries[newIndex];
newEntityToIndex.set(entityId, newIndex);
newIndexToEntity[newIndex] = entityId;
// 移动字段数据
if (newIndex !== oldIndex) {
// 移动数值字段
for (const [, array] of this.fields.entries()) {
array[newIndex] = array[oldIndex];
}
// 移动字符串字段
for (const [, stringArray] of this.stringFields.entries()) {
stringArray[newIndex] = stringArray[oldIndex];
}
// 移动序列化字段
for (const [, serializedArray] of this.serializedFields.entries()) {
serializedArray[newIndex] = serializedArray[oldIndex];
}
}
}
this.entityToIndex = newEntityToIndex;
this.indexToEntity = newIndexToEntity;
this.freeIndices = [];
this._size = activeEntries.length;
}
public getStats(): any {
let totalMemory = 0;
const fieldStats = new Map<string, any>();
for (const [fieldName, array] of this.fields.entries()) {
let bytesPerElement: number;
let typeName: string;
if (array instanceof Float32Array) {
bytesPerElement = 4;
typeName = 'float32';
} else if (array instanceof Float64Array) {
bytesPerElement = 8;
typeName = 'float64';
} else {
bytesPerElement = 4;
typeName = 'int32';
}
const memory = array.length * bytesPerElement;
totalMemory += memory;
fieldStats.set(fieldName, {
size: this._size,
capacity: array.length,
type: typeName,
memory: memory
});
}
return {
size: this._size,
capacity: this._capacity,
usedSlots: this._size, // 兼容原测试
fragmentation: this.freeIndices.length / this._capacity,
memoryUsage: totalMemory,
fieldStats: fieldStats
};
}
/**
* 执行向量化批量操作
* @param operation 操作函数,接收字段数组和活跃索引
*/
public performVectorizedOperation(operation: (fieldArrays: Map<string, Float32Array | Float64Array | Int32Array>, activeIndices: number[]) => void): void {
const activeIndices = this.getActiveIndices();
operation(this.fields, activeIndices);
}
}

View File

@@ -0,0 +1,88 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager, EnableSoA } from '../../../src/ECS/Core/ComponentStorage';
// 默认原始存储组件
class PositionComponent extends Component {
public x: number = 0;
public y: number = 0;
public z: number = 0;
}
// 启用SoA优化的组件用于大规模批量操作
@EnableSoA
class LargeScaleComponent extends Component {
public x: number = 0;
public y: number = 0;
public z: number = 0;
public vx: number = 0;
public vy: number = 0;
public vz: number = 0;
}
describe('SoA优化选择测试', () => {
let manager: ComponentStorageManager;
beforeEach(() => {
manager = new ComponentStorageManager();
});
test('默认使用原始存储', () => {
const storage = manager.getStorage(PositionComponent);
// 添加组件
manager.addComponent(1, new PositionComponent());
// 验证能正常工作
const component = manager.getComponent(1, PositionComponent);
expect(component).toBeTruthy();
expect(component?.x).toBe(0);
// 验证使用原始存储
expect(storage.constructor.name).toBe('ComponentStorage');
});
test('@EnableSoA装饰器启用优化', () => {
const storage = manager.getStorage(LargeScaleComponent);
// 添加组件
const component = new LargeScaleComponent();
component.x = 100;
component.vx = 10;
manager.addComponent(1, component);
// 验证能正常工作
const retrieved = manager.getComponent(1, LargeScaleComponent);
expect(retrieved).toBeTruthy();
expect(retrieved?.x).toBe(100);
expect(retrieved?.vx).toBe(10);
// 验证使用SoA存储
expect(storage.constructor.name).toBe('SoAStorage');
});
test('SoA存储功能验证', () => {
const entityCount = 1000;
// 创建实体使用SoA优化
for (let i = 0; i < entityCount; i++) {
const component = new LargeScaleComponent();
component.x = i;
component.y = i * 2;
component.vx = 1;
component.vy = 2;
manager.addComponent(i, component);
}
// 验证数据正确性
const testComponent = manager.getComponent(100, LargeScaleComponent);
expect(testComponent?.x).toBe(100);
expect(testComponent?.y).toBe(200);
expect(testComponent?.vx).toBe(1);
expect(testComponent?.vy).toBe(2);
// 验证存储类型
const storage = manager.getStorage(LargeScaleComponent);
expect(storage.constructor.name).toBe('SoAStorage');
console.log(`成功创建 ${entityCount} 个SoA实体数据验证通过`);
});
});

View File

@@ -0,0 +1,530 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorage, ComponentStorageManager, EnableSoA } from '../../../src/ECS/Core/ComponentStorage';
import { SoAStorage } from '../../../src/ECS/Core/SoAStorage';
// 测试用统一组件结构启用SoA
@EnableSoA
class TestPositionComponent extends Component {
public x: number = 0;
public y: number = 0;
public z: number = 0;
constructor(x = 0, y = 0, z = 0) {
super();
this.x = x;
this.y = y;
this.z = z;
}
}
@EnableSoA
class TestVelocityComponent extends Component {
public vx: number = 0;
public vy: number = 0;
public vz: number = 0;
public maxSpeed: number = 100;
constructor(vx = 0, vy = 0, vz = 0) {
super();
this.vx = vx;
this.vy = vy;
this.vz = vz;
}
}
@EnableSoA
class TestHealthComponent extends Component {
public current: number = 100;
public max: number = 100;
public regeneration: number = 1;
constructor(current = 100, max = 100) {
super();
this.current = current;
this.max = max;
}
}
// 用于原始存储测试的版本(默认原始存储)
class OriginalPositionComponent extends Component {
public x: number = 0;
public y: number = 0;
public z: number = 0;
constructor(x = 0, y = 0, z = 0) {
super();
this.x = x;
this.y = y;
this.z = z;
}
}
class OriginalVelocityComponent extends Component {
public vx: number = 0;
public vy: number = 0;
public vz: number = 0;
public maxSpeed: number = 100;
constructor(vx = 0, vy = 0, vz = 0) {
super();
this.vx = vx;
this.vy = vy;
this.vz = vz;
}
}
class OriginalHealthComponent extends Component {
public current: number = 100;
public max: number = 100;
public regeneration: number = 1;
constructor(current = 100, max = 100) {
super();
this.current = current;
this.max = max;
}
}
interface PerformanceResult {
name: string;
storageType: 'Original' | 'SoA';
entityCount: number;
operations: number;
totalTime: number;
averageTime: number;
operationsPerSecond: number;
}
describe('ComponentStorage 严谨性能对比测试', () => {
const entityCounts = [1000, 5000, 20000];
let results: PerformanceResult[] = [];
afterAll(() => {
generateDetailedReport();
});
describe('存储器创建和初始化', () => {
test('验证SoA和原始存储使用相同接口', () => {
const originalManager = new ComponentStorageManager();
const soaManager = new ComponentStorageManager();
const originalStorage = originalManager.getStorage(OriginalPositionComponent);
const soaStorage = soaManager.getStorage(TestPositionComponent);
// 验证都实现了相同的接口
expect(typeof originalStorage.addComponent).toBe('function');
expect(typeof originalStorage.getComponent).toBe('function');
expect(typeof originalStorage.hasComponent).toBe('function');
expect(typeof originalStorage.removeComponent).toBe('function');
expect(typeof soaStorage.addComponent).toBe('function');
expect(typeof soaStorage.getComponent).toBe('function');
expect(typeof soaStorage.hasComponent).toBe('function');
expect(typeof soaStorage.removeComponent).toBe('function');
// 验证存储器类型
expect(originalStorage).toBeInstanceOf(ComponentStorage);
expect(soaStorage).toBeInstanceOf(SoAStorage);
});
});
describe('实体创建性能对比', () => {
entityCounts.forEach(entityCount => {
test(`创建 ${entityCount} 个完整实体`, () => {
console.log(`\\n=== 实体创建性能测试: ${entityCount} 个实体 ===`);
// 原始存储测试
const originalResult = measureOriginalEntityCreation(entityCount);
results.push(originalResult);
// SoA存储测试
const soaResult = measureSoAEntityCreation(entityCount);
results.push(soaResult);
// 输出对比结果
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
const speedup = originalResult.totalTime / soaResult.totalTime;
const improvement = ((speedup - 1) * 100);
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
// 验证功能正确性
expect(originalResult.operations).toBe(soaResult.operations);
expect(originalResult.totalTime).toBeGreaterThan(0);
expect(soaResult.totalTime).toBeGreaterThan(0);
});
});
});
describe('组件访问性能对比', () => {
entityCounts.forEach(entityCount => {
test(`随机访问 ${entityCount} 个实体组件`, () => {
console.log(`\\n=== 组件访问性能测试: ${entityCount} 个实体 ===`);
// 原始存储测试
const originalResult = measureOriginalComponentAccess(entityCount, 100);
results.push(originalResult);
// SoA存储测试
const soaResult = measureSoAComponentAccess(entityCount, 100);
results.push(soaResult);
// 输出对比结果
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
const speedup = originalResult.totalTime / soaResult.totalTime;
const improvement = ((speedup - 1) * 100);
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
expect(originalResult.operations).toBe(soaResult.operations);
expect(originalResult.totalTime).toBeGreaterThan(0);
expect(soaResult.totalTime).toBeGreaterThan(0);
});
});
});
describe('批量更新性能对比SoA优势场景', () => {
entityCounts.forEach(entityCount => {
test(`批量更新 ${entityCount} 个实体`, () => {
console.log(`\\n=== 批量更新性能测试: ${entityCount} 个实体 ===`);
// 原始存储测试
const originalResult = measureOriginalBatchUpdate(entityCount, 50);
results.push(originalResult);
// SoA存储测试向量化操作
const soaResult = measureSoABatchUpdate(entityCount, 50);
results.push(soaResult);
// 输出对比结果
console.log(`原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
console.log(`SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
const speedup = originalResult.totalTime / soaResult.totalTime;
const improvement = ((speedup - 1) * 100);
console.log(`性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
// 这是SoA的优势场景应该有性能提升
if (entityCount > 5000) {
expect(speedup).toBeGreaterThan(1.0); // SoA应该更快
}
expect(originalResult.operations).toBe(soaResult.operations);
expect(originalResult.totalTime).toBeGreaterThan(0);
expect(soaResult.totalTime).toBeGreaterThan(0);
});
});
});
// 测试辅助函数
function measureOriginalEntityCreation(entityCount: number): PerformanceResult {
const manager = new ComponentStorageManager();
const startTime = performance.now();
for (let i = 0; i < entityCount; i++) {
manager.addComponent(i, new OriginalPositionComponent(
Math.random() * 1000,
Math.random() * 1000,
Math.random() * 100
));
manager.addComponent(i, new OriginalVelocityComponent(
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 10
));
if (i % 2 === 0) {
manager.addComponent(i, new OriginalHealthComponent(
80 + Math.random() * 20,
100
));
}
}
const totalTime = performance.now() - startTime;
const operations = entityCount * 2.5; // 平均每个实体2.5个组件
return {
name: 'Entity Creation',
storageType: 'Original',
entityCount,
operations,
totalTime,
averageTime: totalTime / operations,
operationsPerSecond: operations / (totalTime / 1000)
};
}
function measureSoAEntityCreation(entityCount: number): PerformanceResult {
const manager = new ComponentStorageManager();
const startTime = performance.now();
for (let i = 0; i < entityCount; i++) {
manager.addComponent(i, new TestPositionComponent(
Math.random() * 1000,
Math.random() * 1000,
Math.random() * 100
));
manager.addComponent(i, new TestVelocityComponent(
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 10
));
if (i % 2 === 0) {
manager.addComponent(i, new TestHealthComponent(
80 + Math.random() * 20,
100
));
}
}
const totalTime = performance.now() - startTime;
const operations = entityCount * 2.5;
return {
name: 'Entity Creation',
storageType: 'SoA',
entityCount,
operations,
totalTime,
averageTime: totalTime / operations,
operationsPerSecond: operations / (totalTime / 1000)
};
}
function measureOriginalComponentAccess(entityCount: number, iterations: number): PerformanceResult {
const manager = new ComponentStorageManager();
// 预创建实体
for (let i = 0; i < entityCount; i++) {
manager.addComponent(i, new OriginalPositionComponent(i, i, i));
manager.addComponent(i, new OriginalVelocityComponent(1, 1, 1));
if (i % 2 === 0) {
manager.addComponent(i, new OriginalHealthComponent(100, 100));
}
}
const startTime = performance.now();
for (let iter = 0; iter < iterations; iter++) {
for (let i = 0; i < entityCount; i++) {
const pos = manager.getComponent(i, OriginalPositionComponent);
const vel = manager.getComponent(i, OriginalVelocityComponent);
if (pos && vel) {
// 模拟简单的读取操作
const sum = pos.x + pos.y + pos.z + vel.vx + vel.vy + vel.vz;
if (sum < 0) continue; // 防止优化
}
}
}
const totalTime = performance.now() - startTime;
const operations = entityCount * iterations;
return {
name: 'Component Access',
storageType: 'Original',
entityCount,
operations,
totalTime,
averageTime: totalTime / operations,
operationsPerSecond: operations / (totalTime / 1000)
};
}
function measureSoAComponentAccess(entityCount: number, iterations: number): PerformanceResult {
const manager = new ComponentStorageManager();
// 预创建实体
for (let i = 0; i < entityCount; i++) {
manager.addComponent(i, new TestPositionComponent(i, i, i));
manager.addComponent(i, new TestVelocityComponent(1, 1, 1));
if (i % 2 === 0) {
manager.addComponent(i, new TestHealthComponent(100, 100));
}
}
const startTime = performance.now();
for (let iter = 0; iter < iterations; iter++) {
for (let i = 0; i < entityCount; i++) {
const pos = manager.getComponent(i, TestPositionComponent);
const vel = manager.getComponent(i, TestVelocityComponent);
if (pos && vel) {
// 模拟简单的读取操作
const sum = pos.x + pos.y + pos.z + vel.vx + vel.vy + vel.vz;
if (sum < 0) continue; // 防止优化
}
}
}
const totalTime = performance.now() - startTime;
const operations = entityCount * iterations;
return {
name: 'Component Access',
storageType: 'SoA',
entityCount,
operations,
totalTime,
averageTime: totalTime / operations,
operationsPerSecond: operations / (totalTime / 1000)
};
}
function measureOriginalBatchUpdate(entityCount: number, iterations: number): PerformanceResult {
const manager = new ComponentStorageManager();
// 预创建实体
for (let i = 0; i < entityCount; i++) {
manager.addComponent(i, new OriginalPositionComponent(i, i, 0));
manager.addComponent(i, new OriginalVelocityComponent(1, 1, 0));
}
const startTime = performance.now();
const deltaTime = 0.016;
for (let iter = 0; iter < iterations; iter++) {
for (let i = 0; i < entityCount; i++) {
const pos = manager.getComponent(i, OriginalPositionComponent);
const vel = manager.getComponent(i, OriginalVelocityComponent);
if (pos && vel) {
// 物理更新
pos.x += vel.vx * deltaTime;
pos.y += vel.vy * deltaTime;
pos.z += vel.vz * deltaTime;
}
}
}
const totalTime = performance.now() - startTime;
const operations = entityCount * iterations;
return {
name: 'Batch Update',
storageType: 'Original',
entityCount,
operations,
totalTime,
averageTime: totalTime / operations,
operationsPerSecond: operations / (totalTime / 1000)
};
}
function measureSoABatchUpdate(entityCount: number, iterations: number): PerformanceResult {
const manager = new ComponentStorageManager();
// 预创建实体
for (let i = 0; i < entityCount; i++) {
manager.addComponent(i, new TestPositionComponent(i, i, 0));
manager.addComponent(i, new TestVelocityComponent(1, 1, 0));
}
const startTime = performance.now();
const deltaTime = 0.016;
// 获取SoA存储器进行向量化操作
const posStorage = manager.getStorage(TestPositionComponent) as SoAStorage<TestPositionComponent>;
const velStorage = manager.getStorage(TestVelocityComponent) as SoAStorage<TestVelocityComponent>;
for (let iter = 0; iter < iterations; iter++) {
// 使用向量化操作
posStorage.performVectorizedOperation((posFields, activeIndices) => {
const velFields = velStorage.getFieldArray('vx') ?
new Map([
['vx', velStorage.getFieldArray('vx')!],
['vy', velStorage.getFieldArray('vy')!],
['vz', velStorage.getFieldArray('vz')!]
]) : new Map();
const posX = posFields.get('x') as Float32Array;
const posY = posFields.get('y') as Float32Array;
const posZ = posFields.get('z') as Float32Array;
const velX = velFields.get('vx') as Float32Array;
const velY = velFields.get('vy') as Float32Array;
const velZ = velFields.get('vz') as Float32Array;
// 向量化物理更新
for (let j = 0; j < activeIndices.length; j++) {
const idx = activeIndices[j];
posX[idx] += velX[idx] * deltaTime;
posY[idx] += velY[idx] * deltaTime;
posZ[idx] += velZ[idx] * deltaTime;
}
});
}
const totalTime = performance.now() - startTime;
const operations = entityCount * iterations;
return {
name: 'Batch Update',
storageType: 'SoA',
entityCount,
operations,
totalTime,
averageTime: totalTime / operations,
operationsPerSecond: operations / (totalTime / 1000)
};
}
function generateDetailedReport(): void {
console.log('\\n' + '='.repeat(80));
console.log('ComponentStorage 严谨性能对比报告');
console.log('='.repeat(80));
// 按测试类型分组
const groupedResults = new Map<string, PerformanceResult[]>();
for (const result of results) {
const key = `${result.name}-${result.entityCount}`;
if (!groupedResults.has(key)) {
groupedResults.set(key, []);
}
groupedResults.get(key)!.push(result);
}
let totalOriginalTime = 0;
let totalSoATime = 0;
let testCount = 0;
for (const [key, testResults] of groupedResults.entries()) {
console.log(`\\n${key}:`);
const originalResult = testResults.find(r => r.storageType === 'Original');
const soaResult = testResults.find(r => r.storageType === 'SoA');
if (originalResult && soaResult) {
const speedup = originalResult.totalTime / soaResult.totalTime;
const improvement = ((speedup - 1) * 100);
console.log(` 原始存储: ${originalResult.totalTime.toFixed(2)}ms (${originalResult.operationsPerSecond.toFixed(0)} ops/sec)`);
console.log(` SoA存储: ${soaResult.totalTime.toFixed(2)}ms (${soaResult.operationsPerSecond.toFixed(0)} ops/sec)`);
console.log(` 性能对比: ${speedup.toFixed(2)}x ${improvement > 0 ? '提升' : '下降'} ${Math.abs(improvement).toFixed(1)}%`);
totalOriginalTime += originalResult.totalTime;
totalSoATime += soaResult.totalTime;
testCount++;
}
}
if (testCount > 0) {
const overallSpeedup = totalOriginalTime / totalSoATime;
const overallImprovement = ((overallSpeedup - 1) * 100);
console.log('\\n' + '='.repeat(80));
console.log('总体性能对比:');
console.log(` 原始存储总耗时: ${totalOriginalTime.toFixed(2)}ms`);
console.log(` SoA存储总耗时: ${totalSoATime.toFixed(2)}ms`);
console.log(` 总体性能对比: ${overallSpeedup.toFixed(2)}x ${overallImprovement > 0 ? '提升' : '下降'} ${Math.abs(overallImprovement).toFixed(1)}%`);
console.log('\\n结论: SoA优化在批量操作场景中表现优异在小规模随机访问场景中有轻微开销。');
console.log('建议: 对于大规模游戏实体和批量系统更新SoA优化能带来显著性能提升。');
console.log('='.repeat(80));
}
}
});

View File

@@ -7,7 +7,7 @@ import {
import { Component } from '../../../src/ECS/Component';
import { BigIntFactory } from '../../../src/ECS/Utils/BigIntCompatibility';
// 测试组件类
// 测试组件类(默认使用原始存储)
class TestComponent extends Component {
constructor(public value: number = 0) {
super();

View File

@@ -0,0 +1,252 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager, EnableSoA, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '../../../src/ECS/Core/ComponentStorage';
// 测试组件:使用集合类型装饰器
@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,307 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager, EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from '../../../src/ECS/Core/ComponentStorage';
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(Float32Array);
// 高精度字段不应该在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

@@ -0,0 +1,171 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager, EnableSoA, HighPrecision, Float64, Int32 } from '../../../src/ECS/Core/ComponentStorage';
import { SoAStorage } 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(Float32Array);
// 高精度字段不应该在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

@@ -0,0 +1,128 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager, EnableSoA } from '../../../src/ECS/Core/ComponentStorage';
// 模拟复杂对象如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

@@ -0,0 +1,158 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager, EnableSoA, HighPrecision, Float64 } from '../../../src/ECS/Core/ComponentStorage';
// 包含所有基础类型的组件
@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('✅ 边界情况测试通过');
});
});