扩展typedarray存储系统,允许自动类型推断@AutoTyped

This commit is contained in:
YHH
2025-09-30 11:00:05 +08:00
parent d0cb7d5359
commit aa33cad4fa
6 changed files with 801 additions and 24 deletions

View File

@@ -1,12 +1,11 @@
import { Component } from '../Component'; import { Component } from '../Component';
import { BitMask64Utils, BitMask64Data } from '../Utils/BigIntCompatibility'; import { BitMask64Utils, BitMask64Data } from '../Utils/BigIntCompatibility';
import { SoAStorage, EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy } from './SoAStorage'; import { SoAStorage, SupportedTypedArray } from './SoAStorage';
import { createLogger } from '../../Utils/Logger'; import { createLogger } from '../../Utils/Logger';
import { getComponentTypeName } from '../Decorators'; import { getComponentTypeName } from '../Decorators';
import { ComponentRegistry, ComponentType } from './ComponentStorage/ComponentRegistry'; import { ComponentRegistry, ComponentType } from './ComponentStorage/ComponentRegistry';
// 重新导出装饰器和核心类型 // 导出核心类型
export { EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy };
export { ComponentRegistry, ComponentType }; export { ComponentRegistry, ComponentType };
@@ -205,7 +204,7 @@ export class ComponentStorageManager {
public getFieldArray<T extends Component>( public getFieldArray<T extends Component>(
componentType: ComponentType<T>, componentType: ComponentType<T>,
fieldName: string fieldName: string
): Float32Array | Float64Array | Int32Array | null { ): SupportedTypedArray | null {
const soaStorage = this.getSoAStorage(componentType); const soaStorage = this.getSoAStorage(componentType);
return soaStorage ? soaStorage.getFieldArray(fieldName) : null; return soaStorage ? soaStorage.getFieldArray(fieldName) : null;
} }
@@ -219,7 +218,7 @@ export class ComponentStorageManager {
public getTypedFieldArray<T extends Component, K extends keyof T>( public getTypedFieldArray<T extends Component, K extends keyof T>(
componentType: ComponentType<T>, componentType: ComponentType<T>,
fieldName: K fieldName: K
): Float32Array | Float64Array | Int32Array | null { ): SupportedTypedArray | null {
const soaStorage = this.getSoAStorage(componentType); const soaStorage = this.getSoAStorage(componentType);
return soaStorage ? soaStorage.getTypedFieldArray(fieldName) : null; return soaStorage ? soaStorage.getTypedFieldArray(fieldName) : null;
} }

View File

@@ -60,6 +60,78 @@ export function Int32(target: any, propertyKey: string | symbol): void {
target.constructor.__int32Fields.add(key); target.constructor.__int32Fields.add(key);
} }
/**
* 32位无符号整数装饰器
* 标记字段使用Uint32Array存储适用于无符号整数如ID、标志位等
*/
export function Uint32(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__uint32Fields) {
target.constructor.__uint32Fields = new Set();
}
target.constructor.__uint32Fields.add(key);
}
/**
* 16位整数装饰器
* 标记字段使用Int16Array存储适用于小范围整数
*/
export function Int16(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__int16Fields) {
target.constructor.__int16Fields = new Set();
}
target.constructor.__int16Fields.add(key);
}
/**
* 16位无符号整数装饰器
* 标记字段使用Uint16Array存储适用于小范围无符号整数
*/
export function Uint16(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__uint16Fields) {
target.constructor.__uint16Fields = new Set();
}
target.constructor.__uint16Fields.add(key);
}
/**
* 8位整数装饰器
* 标记字段使用Int8Array存储适用于很小的整数值
*/
export function Int8(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__int8Fields) {
target.constructor.__int8Fields = new Set();
}
target.constructor.__int8Fields.add(key);
}
/**
* 8位无符号整数装饰器
* 标记字段使用Uint8Array存储适用于字节值、布尔标志等
*/
export function Uint8(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__uint8Fields) {
target.constructor.__uint8Fields = new Set();
}
target.constructor.__uint8Fields.add(key);
}
/**
* 8位夹紧整数装饰器
* 标记字段使用Uint8ClampedArray存储适用于颜色值等需要夹紧的数据
*/
export function Uint8Clamped(target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__uint8ClampedFields) {
target.constructor.__uint8ClampedFields = new Set();
}
target.constructor.__uint8ClampedFields.add(key);
}
/** /**
* 序列化Map装饰器 * 序列化Map装饰器
@@ -109,13 +181,161 @@ export function DeepCopy(target: any, propertyKey: string | symbol): void {
target.constructor.__deepCopyFields.add(key); target.constructor.__deepCopyFields.add(key);
} }
/**
* 自动类型推断装饰器
* 根据字段的默认值和数值范围自动选择最优的TypedArray类型
*
* @param options 类型推断选项
* @param options.minValue 数值的最小值(用于范围优化)
* @param options.maxValue 数值的最大值(用于范围优化)
* @param options.precision 是否需要浮点精度true: 使用浮点数组, false: 使用整数数组)
* @param options.signed 是否需要符号位(仅在整数模式下有效)
*/
export function AutoTyped(options?: {
minValue?: number;
maxValue?: number;
precision?: boolean;
signed?: boolean;
}) {
return function (target: any, propertyKey: string | symbol): void {
const key = String(propertyKey);
if (!target.constructor.__autoTypedFields) {
target.constructor.__autoTypedFields = new Map();
}
target.constructor.__autoTypedFields.set(key, options || {});
};
}
/**
* 自动类型推断器
* 根据数值类型和范围自动选择最优的TypedArray类型
*/
export class TypeInference {
/**
* 根据数值范围推断最优的TypedArray类型
*/
public static inferOptimalType(value: any, options: {
minValue?: number;
maxValue?: number;
precision?: boolean;
signed?: boolean;
} = {}): string {
const type = typeof value;
if (type === 'boolean') {
return 'uint8'; // 布尔值使用最小的无符号整数
}
if (type !== 'number') {
return 'float32'; // 非数值类型默认使用Float32
}
const { minValue, maxValue, precision, signed } = options;
// 如果显式要求精度,使用浮点数
if (precision === true) {
// 检查是否需要双精度
if (Math.abs(value) > 3.4028235e+38 || (minValue !== undefined && Math.abs(minValue) > 3.4028235e+38) || (maxValue !== undefined && Math.abs(maxValue) > 3.4028235e+38)) {
return 'float64';
}
return 'float32';
}
// 如果显式禁用精度,或者是整数值,尝试使用整数数组
if (precision === false || Number.isInteger(value)) {
const actualMin = minValue !== undefined ? minValue : value;
const actualMax = maxValue !== undefined ? maxValue : value;
const needsSigned = signed !== false && (actualMin < 0 || value < 0);
// 根据范围选择最小的整数类型
if (needsSigned) {
// 有符号整数
if (actualMin >= -128 && actualMax <= 127) {
return 'int8';
} else if (actualMin >= -32768 && actualMax <= 32767) {
return 'int16';
} else if (actualMin >= -2147483648 && actualMax <= 2147483647) {
return 'int32';
} else {
return 'float64'; // 超出int32范围使用双精度浮点
}
} else {
// 无符号整数
if (actualMax <= 255) {
return 'uint8';
} else if (actualMax <= 65535) {
return 'uint16';
} else if (actualMax <= 4294967295) {
return 'uint32';
} else {
return 'float64'; // 超出uint32范围使用双精度浮点
}
}
}
// 默认情况:检查是否为小数
if (!Number.isInteger(value)) {
return 'float32';
}
// 整数值,但没有指定范围,根据值的大小选择
if (value >= 0 && value <= 255) {
return 'uint8';
} else if (value >= -128 && value <= 127) {
return 'int8';
} else if (value >= 0 && value <= 65535) {
return 'uint16';
} else if (value >= -32768 && value <= 32767) {
return 'int16';
} else if (value >= 0 && value <= 4294967295) {
return 'uint32';
} else if (value >= -2147483648 && value <= 2147483647) {
return 'int32';
} else {
return 'float64';
}
}
/**
* 根据推断的类型名创建对应的TypedArray构造函数
*/
public static getTypedArrayConstructor(typeName: string): typeof Float32Array | typeof Float64Array | typeof Int32Array | typeof Uint32Array | typeof Int16Array | typeof Uint16Array | typeof Int8Array | typeof Uint8Array | typeof Uint8ClampedArray {
switch (typeName) {
case 'float32': return Float32Array;
case 'float64': return Float64Array;
case 'int32': return Int32Array;
case 'uint32': return Uint32Array;
case 'int16': return Int16Array;
case 'uint16': return Uint16Array;
case 'int8': return Int8Array;
case 'uint8': return Uint8Array;
case 'uint8clamped': return Uint8ClampedArray;
default: return Float32Array;
}
}
}
/**
* SoA存储器支持的TypedArray类型
*/
export type SupportedTypedArray =
| Float32Array
| Float64Array
| Int32Array
| Uint32Array
| Int16Array
| Uint16Array
| Int8Array
| Uint8Array
| Uint8ClampedArray;
/** /**
* SoA存储器需要装饰器启用 * SoA存储器需要装饰器启用
* 使用Structure of Arrays存储模式在大规模批量操作时提供优异性能 * 使用Structure of Arrays存储模式在大规模批量操作时提供优异性能
*/ */
export class SoAStorage<T extends Component> { export class SoAStorage<T extends Component> {
private static readonly _logger = createLogger('SoAStorage'); private static readonly _logger = createLogger('SoAStorage');
private fields = new Map<string, Float32Array | Float64Array | Int32Array>(); private fields = new Map<string, SupportedTypedArray>();
private stringFields = new Map<string, string[]>(); // 专门存储字符串 private stringFields = new Map<string, string[]>(); // 专门存储字符串
private serializedFields = new Map<string, string[]>(); // 序列化存储Map/Set/Array private serializedFields = new Map<string, string[]>(); // 序列化存储Map/Set/Array
private complexFields = new Map<number, Map<string, any>>(); // 存储复杂对象 private complexFields = new Map<number, Map<string, any>>(); // 存储复杂对象
@@ -137,6 +357,13 @@ export class SoAStorage<T extends Component> {
const float64Fields = (componentType as any).__float64Fields || new Set(); const float64Fields = (componentType as any).__float64Fields || new Set();
const float32Fields = (componentType as any).__float32Fields || new Set(); const float32Fields = (componentType as any).__float32Fields || new Set();
const int32Fields = (componentType as any).__int32Fields || new Set(); const int32Fields = (componentType as any).__int32Fields || new Set();
const uint32Fields = (componentType as any).__uint32Fields || new Set();
const int16Fields = (componentType as any).__int16Fields || new Set();
const uint16Fields = (componentType as any).__uint16Fields || new Set();
const int8Fields = (componentType as any).__int8Fields || new Set();
const uint8Fields = (componentType as any).__uint8Fields || new Set();
const uint8ClampedFields = (componentType as any).__uint8ClampedFields || new Set();
const autoTypedFields = (componentType as any).__autoTypedFields || new Map();
const serializeMapFields = (componentType as any).__serializeMapFields || new Set(); const serializeMapFields = (componentType as any).__serializeMapFields || new Set();
const serializeSetFields = (componentType as any).__serializeSetFields || new Set(); const serializeSetFields = (componentType as any).__serializeSetFields || new Set();
const serializeArrayFields = (componentType as any).__serializeArrayFields || new Set(); const serializeArrayFields = (componentType as any).__serializeArrayFields || new Set();
@@ -151,22 +378,52 @@ export class SoAStorage<T extends Component> {
if (highPrecisionFields.has(key)) { if (highPrecisionFields.has(key)) {
// 标记为高精度,作为复杂对象处理 // 标记为高精度,作为复杂对象处理
// 不添加到fields会在updateComponentAtIndex中自动添加到complexFields // 不添加到fields会在updateComponentAtIndex中自动添加到complexFields
} else if (autoTypedFields.has(key)) {
// 使用自动类型推断
const options = autoTypedFields.get(key);
const inferredType = TypeInference.inferOptimalType(value, options);
const ArrayConstructor = TypeInference.getTypedArrayConstructor(inferredType);
this.fields.set(key, new ArrayConstructor(this._capacity));
SoAStorage._logger.info(`字段 ${key} 自动推断为 ${inferredType} 类型,值: ${value}, 选项:`, options);
} else if (float64Fields.has(key)) { } else if (float64Fields.has(key)) {
// 使用Float64Array存储 // 使用Float64Array存储(高精度浮点数)
this.fields.set(key, new Float64Array(this._capacity)); this.fields.set(key, new Float64Array(this._capacity));
} else if (int32Fields.has(key)) { } else if (int32Fields.has(key)) {
// 使用Int32Array存储 // 使用Int32Array存储32位有符号整数
this.fields.set(key, new Int32Array(this._capacity)); this.fields.set(key, new Int32Array(this._capacity));
} else if (uint32Fields.has(key)) {
// 使用Uint32Array存储32位无符号整数
this.fields.set(key, new Uint32Array(this._capacity));
} else if (int16Fields.has(key)) {
// 使用Int16Array存储16位有符号整数
this.fields.set(key, new Int16Array(this._capacity));
} else if (uint16Fields.has(key)) {
// 使用Uint16Array存储16位无符号整数
this.fields.set(key, new Uint16Array(this._capacity));
} else if (int8Fields.has(key)) {
// 使用Int8Array存储8位有符号整数
this.fields.set(key, new Int8Array(this._capacity));
} else if (uint8Fields.has(key)) {
// 使用Uint8Array存储8位无符号整数
this.fields.set(key, new Uint8Array(this._capacity));
} else if (uint8ClampedFields.has(key)) {
// 使用Uint8ClampedArray存储8位夹紧无符号整数
this.fields.set(key, new Uint8ClampedArray(this._capacity));
} else if (float32Fields.has(key)) { } else if (float32Fields.has(key)) {
// 使用Float32Array存储 // 使用Float32Array存储32位浮点数
this.fields.set(key, new Float32Array(this._capacity)); this.fields.set(key, new Float32Array(this._capacity));
} else { } else {
// 默认使用Float32Array // 默认使用Float32Array
this.fields.set(key, new Float32Array(this._capacity)); this.fields.set(key, new Float32Array(this._capacity));
} }
} else if (type === 'boolean') { } else if (type === 'boolean') {
// 布尔值使用Float32Array存储为0/1 // 布尔值默认使用Uint8Array存储为0/1(更节省内存)
if (uint8Fields.has(key) || (!float32Fields.has(key) && !float64Fields.has(key))) {
this.fields.set(key, new Uint8Array(this._capacity));
} else {
// 兼容性:如果显式指定浮点类型则使用原有方式
this.fields.set(key, new Float32Array(this._capacity)); this.fields.set(key, new Float32Array(this._capacity));
}
} else if (type === 'string') { } else if (type === 'string') {
// 字符串专门处理 // 字符串专门处理
this.stringFields.set(key, new Array(this._capacity)); this.stringFields.set(key, new Array(this._capacity));
@@ -430,14 +687,30 @@ export class SoAStorage<T extends Component> {
private resize(newCapacity: number): void { private resize(newCapacity: number): void {
// 调整数值字段的TypedArray // 调整数值字段的TypedArray
for (const [fieldName, oldArray] of this.fields.entries()) { for (const [fieldName, oldArray] of this.fields.entries()) {
let newArray: Float32Array | Float64Array | Int32Array; let newArray: SupportedTypedArray;
if (oldArray instanceof Float32Array) { if (oldArray instanceof Float32Array) {
newArray = new Float32Array(newCapacity); newArray = new Float32Array(newCapacity);
} else if (oldArray instanceof Float64Array) { } else if (oldArray instanceof Float64Array) {
newArray = new Float64Array(newCapacity); newArray = new Float64Array(newCapacity);
} else { } else if (oldArray instanceof Int32Array) {
newArray = new Int32Array(newCapacity); newArray = new Int32Array(newCapacity);
} else if (oldArray instanceof Uint32Array) {
newArray = new Uint32Array(newCapacity);
} else if (oldArray instanceof Int16Array) {
newArray = new Int16Array(newCapacity);
} else if (oldArray instanceof Uint16Array) {
newArray = new Uint16Array(newCapacity);
} else if (oldArray instanceof Int8Array) {
newArray = new Int8Array(newCapacity);
} else if (oldArray instanceof Uint8Array) {
newArray = new Uint8Array(newCapacity);
} else if (oldArray instanceof Uint8ClampedArray) {
newArray = new Uint8ClampedArray(newCapacity);
} else {
// 默认回退到Float32Array
newArray = new Float32Array(newCapacity);
SoAStorage._logger.warn(`未知的TypedArray类型用于字段 ${fieldName}回退到Float32Array`);
} }
newArray.set(oldArray); newArray.set(oldArray);
@@ -469,11 +742,11 @@ export class SoAStorage<T extends Component> {
return Array.from(this.entityToIndex.values()); return Array.from(this.entityToIndex.values());
} }
public getFieldArray(fieldName: string): Float32Array | Float64Array | Int32Array | null { public getFieldArray(fieldName: string): SupportedTypedArray | null {
return this.fields.get(fieldName) || null; return this.fields.get(fieldName) || null;
} }
public getTypedFieldArray<K extends keyof T>(fieldName: K): Float32Array | Float64Array | Int32Array | null { public getTypedFieldArray<K extends keyof T>(fieldName: K): SupportedTypedArray | null {
return this.fields.get(String(fieldName)) || null; return this.fields.get(String(fieldName)) || null;
} }
@@ -573,9 +846,31 @@ export class SoAStorage<T extends Component> {
} else if (array instanceof Float64Array) { } else if (array instanceof Float64Array) {
bytesPerElement = 8; bytesPerElement = 8;
typeName = 'float64'; typeName = 'float64';
} else { } else if (array instanceof Int32Array) {
bytesPerElement = 4; bytesPerElement = 4;
typeName = 'int32'; typeName = 'int32';
} else if (array instanceof Uint32Array) {
bytesPerElement = 4;
typeName = 'uint32';
} else if (array instanceof Int16Array) {
bytesPerElement = 2;
typeName = 'int16';
} else if (array instanceof Uint16Array) {
bytesPerElement = 2;
typeName = 'uint16';
} else if (array instanceof Int8Array) {
bytesPerElement = 1;
typeName = 'int8';
} else if (array instanceof Uint8Array) {
bytesPerElement = 1;
typeName = 'uint8';
} else if (array instanceof Uint8ClampedArray) {
bytesPerElement = 1;
typeName = 'uint8clamped';
} else {
// 默认回退
bytesPerElement = 4;
typeName = 'unknown';
} }
const memory = array.length * bytesPerElement; const memory = array.length * bytesPerElement;
@@ -603,7 +898,7 @@ export class SoAStorage<T extends Component> {
* 执行向量化批量操作 * 执行向量化批量操作
* @param operation 操作函数,接收字段数组和活跃索引 * @param operation 操作函数,接收字段数组和活跃索引
*/ */
public performVectorizedOperation(operation: (fieldArrays: Map<string, Float32Array | Float64Array | Int32Array>, activeIndices: number[]) => void): void { public performVectorizedOperation(operation: (fieldArrays: Map<string, SupportedTypedArray>, activeIndices: number[]) => void): void {
const activeIndices = this.getActiveIndices(); const activeIndices = this.getActiveIndices();
operation(this.fields, activeIndices); operation(this.fields, activeIndices);
} }

View File

@@ -0,0 +1,49 @@
/**
* 统一的存储装饰器导出文件
*
* 用户可以从这里导入所有的SoA存储装饰器而不需要知道内部实现细节
*
* @example
* ```typescript
* import { EnableSoA, Float32, Int16, Uint8 } from './ECS/Core/StorageDecorators';
*
* @EnableSoA
* class TransformComponent extends Component {
* @Float32 x: number = 0;
* @Float32 y: number = 0;
* @Int16 layer: number = 0;
* @Uint8 visible: boolean = true;
* }
* ```
*/
// 从SoAStorage导入所有装饰器和类型
export {
// 启用装饰器
EnableSoA,
// 数值类型装饰器
HighPrecision,
Float64,
Float32,
Int32,
Uint32,
Int16,
Uint16,
Int8,
Uint8,
Uint8Clamped,
// 自动类型推断
AutoTyped,
TypeInference,
// 序列化装饰器
SerializeMap,
SerializeSet,
SerializeArray,
DeepCopy,
// 类型定义
SupportedTypedArray
} from './SoAStorage';

View File

@@ -11,3 +11,4 @@ export { WorldManager, IWorldManagerConfig } from './WorldManager';
export * from './Core/Events'; export * from './Core/Events';
export * from './Core/Query'; export * from './Core/Query';
export * from './Core/Storage'; export * from './Core/Storage';
export * from './Core/StorageDecorators';

View File

@@ -0,0 +1,219 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
import { EnableSoA, AutoTyped, TypeInference } from '../../../src/ECS/Core/StorageDecorators';
// 测试自动类型推断
@EnableSoA
class AutoInferredComponent extends Component {
// 基于默认值的自动推断
@AutoTyped() health: number = 100; // 应该推断为uint8
@AutoTyped() score: number = 1000000; // 应该推断为uint32
@AutoTyped() position: number = 3.14; // 应该推断为float32
@AutoTyped() temperature: number = -40; // 应该推断为int8
// 基于范围的精确推断
@AutoTyped({ minValue: 0, maxValue: 255 })
level: number = 1; // 应该推断为uint8
@AutoTyped({ minValue: -1000, maxValue: 1000 })
velocity: number = 0; // 应该推断为int16
@AutoTyped({ minValue: 0, maxValue: 100000, precision: false })
experience: number = 0; // 应该推断为uint32禁用精度
@AutoTyped({ precision: true })
exactValue: number = 42; // 应该推断为float32强制精度
@AutoTyped({ minValue: 0, maxValue: 10, signed: false })
difficulty: number = 5; // 应该推断为uint8无符号
}
// 传统手动指定类型的组件(用于对比)
@EnableSoA
class ManualTypedComponent extends Component {
health: number = 100; // 默认Float32Array
score: number = 1000000; // 默认Float32Array
position: number = 3.14; // 默认Float32Array
temperature: number = -40; // 默认Float32Array
level: number = 1; // 默认Float32Array
velocity: number = 0; // 默认Float32Array
experience: number = 0; // 默认Float32Array
exactValue: number = 42; // 默认Float32Array
difficulty: number = 5; // 默认Float32Array
}
describe('AutoType Inference', () => {
let storageManager: ComponentStorageManager;
beforeEach(() => {
storageManager = new ComponentStorageManager();
});
test('TypeInference.inferOptimalType 应该正确推断基础类型', () => {
// 布尔值
expect(TypeInference.inferOptimalType(true)).toBe('uint8');
expect(TypeInference.inferOptimalType(false)).toBe('uint8');
// 小整数
expect(TypeInference.inferOptimalType(100)).toBe('uint8');
expect(TypeInference.inferOptimalType(-50)).toBe('int8');
// 大整数
expect(TypeInference.inferOptimalType(1000)).toBe('uint16');
expect(TypeInference.inferOptimalType(-1000)).toBe('int16');
// 浮点数
expect(TypeInference.inferOptimalType(3.14)).toBe('float32');
expect(TypeInference.inferOptimalType(-3.14)).toBe('float32');
// 非数值
expect(TypeInference.inferOptimalType('string')).toBe('float32');
expect(TypeInference.inferOptimalType(null)).toBe('float32');
});
test('TypeInference.inferOptimalType 应该根据范围推断类型', () => {
// 指定范围的无符号整数
expect(TypeInference.inferOptimalType(100, { minValue: 0, maxValue: 255 })).toBe('uint8');
expect(TypeInference.inferOptimalType(1000, { minValue: 0, maxValue: 65535 })).toBe('uint16');
// 指定范围的有符号整数
expect(TypeInference.inferOptimalType(-50, { minValue: -128, maxValue: 127 })).toBe('int8');
expect(TypeInference.inferOptimalType(-1000, { minValue: -32768, maxValue: 32767 })).toBe('int16');
// 强制使用浮点精度
expect(TypeInference.inferOptimalType(42, { precision: true })).toBe('float32');
expect(TypeInference.inferOptimalType(42, { precision: true, maxValue: 1e40 })).toBe('float64');
// 强制禁用精度
expect(TypeInference.inferOptimalType(42.5, { precision: false, minValue: 0, maxValue: 255 })).toBe('uint8');
});
test('应该为自动推断的字段创建正确的TypedArray', () => {
const storage = storageManager.getSoAStorage(AutoInferredComponent);
expect(storage).not.toBeNull();
// 验证推断的类型
expect(storage!.getFieldArray('health')).toBeInstanceOf(Uint8Array);
expect(storage!.getFieldArray('score')).toBeInstanceOf(Uint32Array);
expect(storage!.getFieldArray('position')).toBeInstanceOf(Float32Array);
expect(storage!.getFieldArray('temperature')).toBeInstanceOf(Int8Array);
expect(storage!.getFieldArray('level')).toBeInstanceOf(Uint8Array);
expect(storage!.getFieldArray('velocity')).toBeInstanceOf(Int16Array);
expect(storage!.getFieldArray('experience')).toBeInstanceOf(Uint32Array);
expect(storage!.getFieldArray('exactValue')).toBeInstanceOf(Float32Array);
expect(storage!.getFieldArray('difficulty')).toBeInstanceOf(Uint8Array);
});
test('应该正确存储和检索自动推断类型的值', () => {
const entityId = 1;
const component = new AutoInferredComponent();
// 设置测试值
component.health = 255; // Uint8 最大值
component.score = 4000000000; // 大的Uint32值
component.position = 3.14159;
component.temperature = -128; // Int8 最小值
component.level = 200;
component.velocity = -500;
component.experience = 50000;
component.exactValue = Math.PI;
component.difficulty = 10;
storageManager.addComponent(entityId, component);
const retrieved = storageManager.getComponent(entityId, AutoInferredComponent);
expect(retrieved).not.toBeNull();
expect(retrieved!.health).toBe(255);
expect(retrieved!.score).toBe(4000000000);
expect(retrieved!.position).toBeCloseTo(3.14159, 5);
expect(retrieved!.temperature).toBe(-128);
expect(retrieved!.level).toBe(200);
expect(retrieved!.velocity).toBe(-500);
expect(retrieved!.experience).toBe(50000);
expect(retrieved!.exactValue).toBeCloseTo(Math.PI, 5);
expect(retrieved!.difficulty).toBe(10);
});
test('自动推断应该比手动类型使用更少内存', () => {
const autoStorage = storageManager.getSoAStorage(AutoInferredComponent);
const manualStorage = storageManager.getSoAStorage(ManualTypedComponent);
const autoStats = autoStorage!.getStats();
const manualStats = manualStorage!.getStats();
// 自动推断应该使用更少的内存
expect(autoStats.memoryUsage).toBeLessThan(manualStats.memoryUsage);
// 验证特定字段的内存使用
expect(autoStats.fieldStats.get('health').memory).toBe(1000); // Uint8: 1 byte per element
expect(manualStats.fieldStats.get('health').memory).toBe(4000); // Float32: 4 bytes per element
expect(autoStats.fieldStats.get('temperature').memory).toBe(1000); // Int8: 1 byte per element
expect(manualStats.fieldStats.get('temperature').memory).toBe(4000); // Float32: 4 bytes per element
});
test('应该处理边界值', () => {
@EnableSoA
class BoundaryTestComponent extends Component {
@AutoTyped({ minValue: 0, maxValue: 255 })
maxUint8: number = 255;
@AutoTyped({ minValue: -128, maxValue: 127 })
minInt8: number = -128;
@AutoTyped({ minValue: 0, maxValue: 65535 })
maxUint16: number = 65535;
@AutoTyped({ minValue: -32768, maxValue: 32767 })
minInt16: number = -32768;
}
const storage = storageManager.getSoAStorage(BoundaryTestComponent);
expect(storage!.getFieldArray('maxUint8')).toBeInstanceOf(Uint8Array);
expect(storage!.getFieldArray('minInt8')).toBeInstanceOf(Int8Array);
expect(storage!.getFieldArray('maxUint16')).toBeInstanceOf(Uint16Array);
expect(storage!.getFieldArray('minInt16')).toBeInstanceOf(Int16Array);
const entityId = 1;
const component = new BoundaryTestComponent();
storageManager.addComponent(entityId, component);
const retrieved = storageManager.getComponent(entityId, BoundaryTestComponent);
expect(retrieved!.maxUint8).toBe(255);
expect(retrieved!.minInt8).toBe(-128);
expect(retrieved!.maxUint16).toBe(65535);
expect(retrieved!.minInt16).toBe(-32768);
});
test('应该输出推断日志信息', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
// 创建新的存储以触发推断
const newStorageManager = new ComponentStorageManager();
newStorageManager.getSoAStorage(AutoInferredComponent);
// 验证日志输出注意实际的日志可能通过logger系统输出
// 这里主要验证不会出错
expect(() => {
const component = new AutoInferredComponent();
newStorageManager.addComponent(999, component);
}).not.toThrow();
consoleSpy.mockRestore();
});
test('计算内存节省百分比', () => {
const autoStorage = storageManager.getSoAStorage(AutoInferredComponent);
const manualStorage = storageManager.getSoAStorage(ManualTypedComponent);
const autoStats = autoStorage!.getStats();
const manualStats = manualStorage!.getStats();
const memorySaved = ((manualStats.memoryUsage - autoStats.memoryUsage) / manualStats.memoryUsage) * 100;
// 自动推断应该节省显著的内存
expect(memorySaved).toBeGreaterThan(20); // 至少节省20%的内存
});
});

View File

@@ -0,0 +1,214 @@
import { Component } from '../../../src/ECS/Component';
import { ComponentStorageManager } from '../../../src/ECS/Core/ComponentStorage';
import {
EnableSoA,
Float32,
Float64,
Int32,
Uint32,
Int16,
Uint16,
Int8,
Uint8,
Uint8Clamped
} from '../../../src/ECS/Core/StorageDecorators';
// 测试组件 - 展示所有TypedArray类型
@EnableSoA
class EnhancedTestComponent extends Component {
@Float32 x: number = 0;
@Float32 y: number = 0;
@Float64 preciseX: number = 0;
@Int32 count: number = 0;
@Uint32 entityId: number = 0; // 避免与基类id冲突
@Int16 layer: number = 0;
@Uint16 priority: number = 0;
@Int8 direction: number = 0;
@Uint8 flags: number = 0;
@Uint8Clamped alpha: number = 255;
}
// 简单组件用于对比
class SimpleComponent extends Component {
value: number = 42;
}
describe('Enhanced TypedArray Support', () => {
let storageManager: ComponentStorageManager;
beforeEach(() => {
storageManager = new ComponentStorageManager();
});
test('应该为所有TypedArray类型创建正确的存储', () => {
const storage = storageManager.getSoAStorage(EnhancedTestComponent);
expect(storage).not.toBeNull();
// 测试不同类型的字段数组
const xArray = storage!.getFieldArray('x'); // Float32Array
const preciseXArray = storage!.getFieldArray('preciseX'); // Float64Array
const countArray = storage!.getFieldArray('count'); // Int32Array
const entityIdArray = storage!.getFieldArray('entityId'); // Uint32Array
const layerArray = storage!.getFieldArray('layer'); // Int16Array
const priorityArray = storage!.getFieldArray('priority'); // Uint16Array
const directionArray = storage!.getFieldArray('direction'); // Int8Array
const flagsArray = storage!.getFieldArray('flags'); // Uint8Array
const alphaArray = storage!.getFieldArray('alpha'); // Uint8ClampedArray
expect(xArray).toBeInstanceOf(Float32Array);
expect(preciseXArray).toBeInstanceOf(Float64Array);
expect(countArray).toBeInstanceOf(Int32Array);
expect(entityIdArray).toBeInstanceOf(Uint32Array);
expect(layerArray).toBeInstanceOf(Int16Array);
expect(priorityArray).toBeInstanceOf(Uint16Array);
expect(directionArray).toBeInstanceOf(Int8Array);
expect(flagsArray).toBeInstanceOf(Uint8Array);
expect(alphaArray).toBeInstanceOf(Uint8ClampedArray);
});
test('应该正确存储和检索不同类型的数值', () => {
const entityId = 1;
const component = new EnhancedTestComponent();
// 设置测试值
component.x = 3.14159;
component.preciseX = Math.PI;
component.count = -123456;
component.entityId = 4294967295; // 最大Uint32值
component.layer = -32768; // 最小Int16值
component.priority = 65535; // 最大Uint16值
component.direction = -128; // 最小Int8值
component.flags = 255; // 最大Uint8值
component.alpha = 300; // 会被Clamped到255
storageManager.addComponent(entityId, component);
const retrieved = storageManager.getComponent(entityId, EnhancedTestComponent);
expect(retrieved).not.toBeNull();
expect(retrieved!.x).toBeCloseTo(3.14159, 5);
expect(retrieved!.preciseX).toBeCloseTo(Math.PI, 10);
expect(retrieved!.count).toBe(-123456);
expect(retrieved!.entityId).toBe(4294967295);
expect(retrieved!.layer).toBe(-32768);
expect(retrieved!.priority).toBe(65535);
expect(retrieved!.direction).toBe(-128);
expect(retrieved!.flags).toBe(255);
expect(retrieved!.alpha).toBe(255); // Clamped
});
test('应该正确计算内存使用量', () => {
const storage = storageManager.getSoAStorage(EnhancedTestComponent);
const stats = storage!.getStats();
expect(stats.fieldStats.get('x').type).toBe('float32');
expect(stats.fieldStats.get('x').memory).toBe(4000); // 1000 * 4 bytes
expect(stats.fieldStats.get('preciseX').type).toBe('float64');
expect(stats.fieldStats.get('preciseX').memory).toBe(8000); // 1000 * 8 bytes
expect(stats.fieldStats.get('count').type).toBe('int32');
expect(stats.fieldStats.get('count').memory).toBe(4000); // 1000 * 4 bytes
expect(stats.fieldStats.get('entityId').type).toBe('uint32');
expect(stats.fieldStats.get('entityId').memory).toBe(4000); // 1000 * 4 bytes
expect(stats.fieldStats.get('layer').type).toBe('int16');
expect(stats.fieldStats.get('layer').memory).toBe(2000); // 1000 * 2 bytes
expect(stats.fieldStats.get('priority').type).toBe('uint16');
expect(stats.fieldStats.get('priority').memory).toBe(2000); // 1000 * 2 bytes
expect(stats.fieldStats.get('direction').type).toBe('int8');
expect(stats.fieldStats.get('direction').memory).toBe(1000); // 1000 * 1 byte
expect(stats.fieldStats.get('flags').type).toBe('uint8');
expect(stats.fieldStats.get('flags').memory).toBe(1000); // 1000 * 1 byte
expect(stats.fieldStats.get('alpha').type).toBe('uint8clamped');
expect(stats.fieldStats.get('alpha').memory).toBe(1000); // 1000 * 1 byte
});
test('应该支持向量化批量操作', () => {
const storage = storageManager.getSoAStorage(EnhancedTestComponent);
// 添加测试数据
for (let i = 0; i < 100; i++) {
const component = new EnhancedTestComponent();
component.x = i;
component.y = i * 2;
storageManager.addComponent(i, component);
}
// 执行向量化操作
let processedCount = 0;
storage!.performVectorizedOperation((fieldArrays, activeIndices) => {
const xArray = fieldArrays.get('x') as Float32Array;
const yArray = fieldArrays.get('y') as Float32Array;
// 批量处理x = x + y
for (const index of activeIndices) {
xArray[index] = xArray[index] + yArray[index];
processedCount++;
}
});
expect(processedCount).toBe(100);
// 验证结果
for (let i = 0; i < 100; i++) {
const component = storageManager.getComponent(i, EnhancedTestComponent);
expect(component!.x).toBe(i + i * 2); // x + y
}
});
test('布尔值应该默认使用Uint8Array', () => {
@EnableSoA
class BooleanTestComponent extends Component {
visible: boolean = true;
@Float32 enabledFloat: boolean = true; // 显式指定Float32
}
const storage = storageManager.getSoAStorage(BooleanTestComponent);
const visibleArray = storage!.getFieldArray('visible');
const enabledFloatArray = storage!.getFieldArray('enabledFloat');
expect(visibleArray).toBeInstanceOf(Uint8Array);
expect(enabledFloatArray).toBeInstanceOf(Float32Array);
});
test('内存使用量对比:优化后应该更节省内存', () => {
@EnableSoA
class OptimizedComponent extends Component {
@Uint8 flag1: number = 0;
@Uint8 flag2: number = 0;
@Uint8 flag3: number = 0;
@Uint8 flag4: number = 0;
}
@EnableSoA
class UnoptimizedComponent extends Component {
@Float32 flag1: number = 0;
@Float32 flag2: number = 0;
@Float32 flag3: number = 0;
@Float32 flag4: number = 0;
}
const optimizedStorage = storageManager.getSoAStorage(OptimizedComponent);
const unoptimizedStorage = storageManager.getSoAStorage(UnoptimizedComponent);
const optimizedStats = optimizedStorage!.getStats();
const unoptimizedStats = unoptimizedStorage!.getStats();
// 优化版本应该使用更少的内存4个Uint8 vs 4个Float32
expect(optimizedStats.memoryUsage).toBeLessThan(unoptimizedStats.memoryUsage);
// 验证优化后的单个字段确实更小
expect(optimizedStats.fieldStats.get('flag1').memory).toBe(1000); // Uint8: 1000 bytes
expect(unoptimizedStats.fieldStats.get('flag1').memory).toBe(4000); // Float32: 4000 bytes
// 计算节省的内存百分比
const memorySaved = ((unoptimizedStats.memoryUsage - optimizedStats.memoryUsage) / unoptimizedStats.memoryUsage) * 100;
expect(memorySaved).toBeGreaterThan(50); // 应该节省超过50%的内存
});
});