扩展typedarray存储系统,允许自动类型推断@AutoTyped
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import { Component } from '../Component';
|
||||
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 { getComponentTypeName } from '../Decorators';
|
||||
import { ComponentRegistry, ComponentType } from './ComponentStorage/ComponentRegistry';
|
||||
|
||||
// 重新导出装饰器和核心类型
|
||||
export { EnableSoA, HighPrecision, Float64, Int32, SerializeMap, SerializeSet, SerializeArray, DeepCopy };
|
||||
// 导出核心类型
|
||||
export { ComponentRegistry, ComponentType };
|
||||
|
||||
|
||||
@@ -205,7 +204,7 @@ export class ComponentStorageManager {
|
||||
public getFieldArray<T extends Component>(
|
||||
componentType: ComponentType<T>,
|
||||
fieldName: string
|
||||
): Float32Array | Float64Array | Int32Array | null {
|
||||
): SupportedTypedArray | null {
|
||||
const soaStorage = this.getSoAStorage(componentType);
|
||||
return soaStorage ? soaStorage.getFieldArray(fieldName) : null;
|
||||
}
|
||||
@@ -219,7 +218,7 @@ export class ComponentStorageManager {
|
||||
public getTypedFieldArray<T extends Component, K extends keyof T>(
|
||||
componentType: ComponentType<T>,
|
||||
fieldName: K
|
||||
): Float32Array | Float64Array | Int32Array | null {
|
||||
): SupportedTypedArray | null {
|
||||
const soaStorage = this.getSoAStorage(componentType);
|
||||
return soaStorage ? soaStorage.getTypedFieldArray(fieldName) : null;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,78 @@ export function Int32(target: any, propertyKey: string | symbol): void {
|
||||
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装饰器
|
||||
@@ -109,13 +181,161 @@ export function DeepCopy(target: any, propertyKey: string | symbol): void {
|
||||
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存储器(需要装饰器启用)
|
||||
* 使用Structure of Arrays存储模式,在大规模批量操作时提供优异性能
|
||||
*/
|
||||
export class SoAStorage<T extends Component> {
|
||||
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 serializedFields = new Map<string, string[]>(); // 序列化存储Map/Set/Array
|
||||
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 float32Fields = (componentType as any).__float32Fields || 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 serializeSetFields = (componentType as any).__serializeSetFields || new Set();
|
||||
const serializeArrayFields = (componentType as any).__serializeArrayFields || new Set();
|
||||
@@ -151,22 +378,52 @@ export class SoAStorage<T extends Component> {
|
||||
if (highPrecisionFields.has(key)) {
|
||||
// 标记为高精度,作为复杂对象处理
|
||||
// 不添加到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)) {
|
||||
// 使用Float64Array存储
|
||||
// 使用Float64Array存储(高精度浮点数)
|
||||
this.fields.set(key, new Float64Array(this._capacity));
|
||||
} else if (int32Fields.has(key)) {
|
||||
// 使用Int32Array存储
|
||||
// 使用Int32Array存储(32位有符号整数)
|
||||
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)) {
|
||||
// 使用Float32Array存储
|
||||
// 使用Float32Array存储(32位浮点数)
|
||||
this.fields.set(key, new Float32Array(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));
|
||||
// 布尔值默认使用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));
|
||||
}
|
||||
} else if (type === 'string') {
|
||||
// 字符串专门处理
|
||||
this.stringFields.set(key, new Array(this._capacity));
|
||||
@@ -430,14 +687,30 @@ export class SoAStorage<T extends Component> {
|
||||
private resize(newCapacity: number): void {
|
||||
// 调整数值字段的TypedArray
|
||||
for (const [fieldName, oldArray] of this.fields.entries()) {
|
||||
let newArray: Float32Array | Float64Array | Int32Array;
|
||||
let newArray: SupportedTypedArray;
|
||||
|
||||
if (oldArray instanceof Float32Array) {
|
||||
newArray = new Float32Array(newCapacity);
|
||||
} else if (oldArray instanceof Float64Array) {
|
||||
newArray = new Float64Array(newCapacity);
|
||||
} else {
|
||||
} else if (oldArray instanceof Int32Array) {
|
||||
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);
|
||||
@@ -469,11 +742,11 @@ export class SoAStorage<T extends Component> {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -573,9 +846,31 @@ export class SoAStorage<T extends Component> {
|
||||
} else if (array instanceof Float64Array) {
|
||||
bytesPerElement = 8;
|
||||
typeName = 'float64';
|
||||
} else {
|
||||
} else if (array instanceof Int32Array) {
|
||||
bytesPerElement = 4;
|
||||
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;
|
||||
@@ -603,7 +898,7 @@ export class SoAStorage<T extends Component> {
|
||||
* 执行向量化批量操作
|
||||
* @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();
|
||||
operation(this.fields, activeIndices);
|
||||
}
|
||||
|
||||
49
packages/core/src/ECS/Core/StorageDecorators.ts
Normal file
49
packages/core/src/ECS/Core/StorageDecorators.ts
Normal 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';
|
||||
@@ -11,3 +11,4 @@ export { WorldManager, IWorldManagerConfig } from './WorldManager';
|
||||
export * from './Core/Events';
|
||||
export * from './Core/Query';
|
||||
export * from './Core/Storage';
|
||||
export * from './Core/StorageDecorators';
|
||||
219
packages/core/tests/ECS/Core/AutoTypeInference.test.ts
Normal file
219
packages/core/tests/ECS/Core/AutoTypeInference.test.ts
Normal 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%的内存
|
||||
});
|
||||
});
|
||||
214
packages/core/tests/ECS/Core/EnhancedTypedArraySupport.test.ts
Normal file
214
packages/core/tests/ECS/Core/EnhancedTypedArraySupport.test.ts
Normal 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%的内存
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user