feat: 集成Rust WASM渲染引擎与TypeScript ECS框架 (#228)
* feat: 集成Rust WASM渲染引擎与TypeScript ECS框架 * feat: 增强编辑器UI功能与跨平台支持 * fix: 修复CI测试和类型检查问题 * fix: 修复CI问题并提高测试覆盖率 * fix: 修复CI问题并提高测试覆盖率
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { IComponent } from '../Types';
|
||||
import { Int32 } from './Core/SoAStorage';
|
||||
|
||||
/**
|
||||
* 游戏组件基类
|
||||
@@ -50,6 +51,7 @@ export abstract class Component implements IComponent {
|
||||
*
|
||||
* 存储实体ID而非引用,避免循环引用,符合ECS数据导向设计。
|
||||
*/
|
||||
@Int32
|
||||
public entityId: number | null = null;
|
||||
|
||||
/**
|
||||
|
||||
108
packages/core/src/ECS/Core/SoASerializer.ts
Normal file
108
packages/core/src/ECS/Core/SoASerializer.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
|
||||
/**
|
||||
* SoA 序列化器
|
||||
* 负责复杂类型的序列化/反序列化和深拷贝
|
||||
*/
|
||||
export class SoASerializer {
|
||||
private static readonly _logger = createLogger('SoASerializer');
|
||||
|
||||
/**
|
||||
* 序列化值为 JSON 字符串
|
||||
*/
|
||||
public static serialize(
|
||||
value: unknown,
|
||||
fieldName: string,
|
||||
options: {
|
||||
isMap?: boolean;
|
||||
isSet?: boolean;
|
||||
isArray?: boolean;
|
||||
} = {}
|
||||
): string {
|
||||
try {
|
||||
if (options.isMap && value instanceof Map) {
|
||||
return JSON.stringify(Array.from(value.entries()));
|
||||
}
|
||||
if (options.isSet && value instanceof Set) {
|
||||
return JSON.stringify(Array.from(value));
|
||||
}
|
||||
if (options.isArray && Array.isArray(value)) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
} catch (error) {
|
||||
this._logger.warn(`SoA序列化字段 ${fieldName} 失败:`, error);
|
||||
return '{}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化 JSON 字符串为值
|
||||
*/
|
||||
public static deserialize(
|
||||
serialized: string,
|
||||
fieldName: string,
|
||||
options: {
|
||||
isMap?: boolean;
|
||||
isSet?: boolean;
|
||||
isArray?: boolean;
|
||||
} = {}
|
||||
): unknown {
|
||||
try {
|
||||
const parsed = JSON.parse(serialized);
|
||||
|
||||
if (options.isMap) {
|
||||
return new Map(parsed);
|
||||
}
|
||||
if (options.isSet) {
|
||||
return new Set(parsed);
|
||||
}
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
this._logger.warn(`SoA反序列化字段 ${fieldName} 失败:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝对象
|
||||
*/
|
||||
public static deepClone<T>(obj: T): T {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (obj instanceof Date) {
|
||||
return new Date(obj.getTime()) as T;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => this.deepClone(item)) as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Map) {
|
||||
const cloned = new Map();
|
||||
for (const [key, value] of obj.entries()) {
|
||||
cloned.set(key, this.deepClone(value));
|
||||
}
|
||||
return cloned as T;
|
||||
}
|
||||
|
||||
if (obj instanceof Set) {
|
||||
const cloned = new Set();
|
||||
for (const value of obj.values()) {
|
||||
cloned.add(this.deepClone(value));
|
||||
}
|
||||
return cloned as T;
|
||||
}
|
||||
|
||||
// 普通对象
|
||||
const cloned = {} as Record<string, unknown>;
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
cloned[key] = this.deepClone((obj as Record<string, unknown>)[key]);
|
||||
}
|
||||
}
|
||||
return cloned as T;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,16 @@
|
||||
import { Component } from '../Component';
|
||||
import { ComponentType } from './ComponentStorage';
|
||||
import { createLogger } from '../../Utils/Logger';
|
||||
import {
|
||||
SoATypeRegistry,
|
||||
SupportedTypedArray,
|
||||
TypedArrayTypeName
|
||||
} from './SoATypeRegistry';
|
||||
import { SoASerializer } from './SoASerializer';
|
||||
|
||||
// 重新导出类型,保持向后兼容
|
||||
export { SupportedTypedArray, TypedArrayTypeName } from './SoATypeRegistry';
|
||||
export { SoATypeRegistry } from './SoATypeRegistry';
|
||||
export { SoASerializer } from './SoASerializer';
|
||||
|
||||
/**
|
||||
* 启用SoA优化装饰器
|
||||
@@ -12,18 +22,6 @@ export function EnableSoA<T extends ComponentType>(target: T): T {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 高精度数值装饰器
|
||||
* 标记字段需要保持完整精度,存储为复杂对象而非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存储(更高精度但更多内存)
|
||||
@@ -181,164 +179,16 @@ 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, SupportedTypedArray>();
|
||||
private stringFields = new Map<string, string[]>(); // 专门存储字符串
|
||||
private serializedFields = new Map<string, string[]>(); // 序列化存储Map/Set/Array
|
||||
private complexFields = new Map<number, Map<string, any>>(); // 存储复杂对象
|
||||
private stringFields = new Map<string, string[]>();
|
||||
private serializedFields = new Map<string, string[]>();
|
||||
private complexFields = new Map<number, Map<string, unknown>>();
|
||||
private entityToIndex = new Map<number, number>();
|
||||
private indexToEntity: number[] = [];
|
||||
private freeIndices: number[] = [];
|
||||
@@ -346,6 +196,13 @@ export class SoAStorage<T extends Component> {
|
||||
private _capacity = 1000;
|
||||
public readonly type: ComponentType<T>;
|
||||
|
||||
// 缓存字段类型信息,避免重复创建实例
|
||||
private fieldTypes = new Map<string, string>();
|
||||
// 缓存装饰器元数据
|
||||
private serializeMapFields: Set<string> = new Set();
|
||||
private serializeSetFields: Set<string> = new Set();
|
||||
private serializeArrayFields: Set<string> = new Set();
|
||||
|
||||
constructor(componentType: ComponentType<T>) {
|
||||
this.type = componentType;
|
||||
this.initializeFields(componentType);
|
||||
@@ -353,88 +210,85 @@ export class SoAStorage<T extends Component> {
|
||||
|
||||
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 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();
|
||||
// const deepCopyFields = (componentType as any).__deepCopyFields || new Set(); // 未使用,但保留供future使用
|
||||
const typeWithMeta = componentType as ComponentType<T> & {
|
||||
__float64Fields?: Set<string>;
|
||||
__float32Fields?: Set<string>;
|
||||
__int32Fields?: Set<string>;
|
||||
__uint32Fields?: Set<string>;
|
||||
__int16Fields?: Set<string>;
|
||||
__uint16Fields?: Set<string>;
|
||||
__int8Fields?: Set<string>;
|
||||
__uint8Fields?: Set<string>;
|
||||
__uint8ClampedFields?: Set<string>;
|
||||
__serializeMapFields?: Set<string>;
|
||||
__serializeSetFields?: Set<string>;
|
||||
__serializeArrayFields?: Set<string>;
|
||||
};
|
||||
|
||||
for (const key in instance) {
|
||||
if (instance.hasOwnProperty(key) && key !== 'id') {
|
||||
const value = (instance as any)[key];
|
||||
const type = typeof value;
|
||||
const float64Fields = typeWithMeta.__float64Fields || new Set<string>();
|
||||
const float32Fields = typeWithMeta.__float32Fields || new Set<string>();
|
||||
const int32Fields = typeWithMeta.__int32Fields || new Set<string>();
|
||||
const uint32Fields = typeWithMeta.__uint32Fields || new Set<string>();
|
||||
const int16Fields = typeWithMeta.__int16Fields || new Set<string>();
|
||||
const uint16Fields = typeWithMeta.__uint16Fields || new Set<string>();
|
||||
const int8Fields = typeWithMeta.__int8Fields || new Set<string>();
|
||||
const uint8Fields = typeWithMeta.__uint8Fields || new Set<string>();
|
||||
const uint8ClampedFields = typeWithMeta.__uint8ClampedFields || new Set<string>();
|
||||
|
||||
if (type === 'number') {
|
||||
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存储(高精度浮点数)
|
||||
this.fields.set(key, new Float64Array(this._capacity));
|
||||
} else if (int32Fields.has(key)) {
|
||||
// 使用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存储(32位浮点数)
|
||||
this.fields.set(key, new Float32Array(this._capacity));
|
||||
} else {
|
||||
// 默认使用Float32Array
|
||||
this.fields.set(key, new Float32Array(this._capacity));
|
||||
}
|
||||
// 缓存装饰器元数据
|
||||
this.serializeMapFields = typeWithMeta.__serializeMapFields || new Set<string>();
|
||||
this.serializeSetFields = typeWithMeta.__serializeSetFields || new Set<string>();
|
||||
this.serializeArrayFields = typeWithMeta.__serializeArrayFields || new Set<string>();
|
||||
|
||||
// 先收集所有有装饰器的字段,避免重复遍历
|
||||
const decoratedFields = new Map<string, string>(); // fieldName -> arrayType
|
||||
|
||||
// 处理各类型装饰器标记的字段
|
||||
for (const key of float64Fields) decoratedFields.set(key, 'float64');
|
||||
for (const key of float32Fields) decoratedFields.set(key, 'float32');
|
||||
for (const key of int32Fields) decoratedFields.set(key, 'int32');
|
||||
for (const key of uint32Fields) decoratedFields.set(key, 'uint32');
|
||||
for (const key of int16Fields) decoratedFields.set(key, 'int16');
|
||||
for (const key of uint16Fields) decoratedFields.set(key, 'uint16');
|
||||
for (const key of int8Fields) decoratedFields.set(key, 'int8');
|
||||
for (const key of uint8Fields) decoratedFields.set(key, 'uint8');
|
||||
for (const key of uint8ClampedFields) decoratedFields.set(key, 'uint8clamped');
|
||||
|
||||
// 只遍历实例自身的属性(不包括原型链),跳过 id
|
||||
const instanceKeys = Object.keys(instance).filter(key => key !== 'id');
|
||||
|
||||
for (const key of instanceKeys) {
|
||||
const value = (instance as Record<string, unknown>)[key];
|
||||
const type = typeof value;
|
||||
|
||||
// 跳过函数(通常不会出现在 Object.keys 中,但以防万一)
|
||||
if (type === 'function') continue;
|
||||
|
||||
// 检查装饰器类型
|
||||
const decoratorType = decoratedFields.get(key);
|
||||
const effectiveType = decoratorType ? 'number' : type;
|
||||
this.fieldTypes.set(key, effectiveType);
|
||||
|
||||
if (decoratorType) {
|
||||
// 有装饰器标记的数字字段
|
||||
const ArrayConstructor = SoATypeRegistry.getConstructor(decoratorType as TypedArrayTypeName);
|
||||
this.fields.set(key, new ArrayConstructor(this._capacity));
|
||||
} else if (type === 'number') {
|
||||
// 无装饰器的数字字段,默认使用 Float32Array
|
||||
this.fields.set(key, new Float32Array(this._capacity));
|
||||
} else if (type === 'boolean') {
|
||||
// 布尔值默认使用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));
|
||||
}
|
||||
// 布尔值使用 Uint8Array 存储为 0/1
|
||||
this.fields.set(key, new Uint8Array(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)) {
|
||||
if (this.serializeMapFields.has(key) || this.serializeSetFields.has(key) || this.serializeArrayFields.has(key)) {
|
||||
// 序列化存储
|
||||
this.serializedFields.set(key, new Array(this._capacity));
|
||||
}
|
||||
// 其他对象类型会在updateComponentAtIndex中作为复杂对象处理
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -497,12 +351,16 @@ export class SoAStorage<T extends Component> {
|
||||
} else if (this.serializedFields.has(key)) {
|
||||
// 序列化字段处理
|
||||
const serializedArray = this.serializedFields.get(key)!;
|
||||
serializedArray[index] = this.serializeValue(value, key, serializeMapFields, serializeSetFields, serializeArrayFields);
|
||||
serializedArray[index] = SoASerializer.serialize(value, key, {
|
||||
isMap: serializeMapFields.has(key),
|
||||
isSet: serializeSetFields.has(key),
|
||||
isArray: serializeArrayFields.has(key)
|
||||
});
|
||||
} else {
|
||||
// 复杂字段单独存储
|
||||
if (deepCopyFields.has(key)) {
|
||||
// 深拷贝处理
|
||||
complexFieldMap.set(key, this.deepClone(value));
|
||||
complexFieldMap.set(key, SoASerializer.deepClone(value));
|
||||
} else {
|
||||
complexFieldMap.set(key, value);
|
||||
}
|
||||
@@ -516,96 +374,6 @@ export class SoAStorage<T extends Component> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化值为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) {
|
||||
SoAStorage._logger.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) {
|
||||
SoAStorage._logger.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);
|
||||
@@ -613,11 +381,162 @@ export class SoAStorage<T extends Component> {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建真正的组件实例以保持兼容性
|
||||
// 返回 Proxy,直接操作底层 TypedArray
|
||||
return this.createProxyView(entityId, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建组件的 Proxy 视图
|
||||
* 读写操作直接映射到底层 TypedArray,无数据复制
|
||||
*/
|
||||
private createProxyView(entityId: number, index: number): T {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
|
||||
// Proxy handler 类型定义
|
||||
const handler: ProxyHandler<Record<string, unknown>> = {
|
||||
get(_, prop: string | symbol) {
|
||||
const propStr = String(prop);
|
||||
|
||||
// TypedArray 字段
|
||||
const array = self.fields.get(propStr);
|
||||
if (array) {
|
||||
const fieldType = self.getFieldType(propStr);
|
||||
if (fieldType === 'boolean') {
|
||||
return array[index] === 1;
|
||||
}
|
||||
return array[index];
|
||||
}
|
||||
|
||||
// 字符串字段
|
||||
const stringArray = self.stringFields.get(propStr);
|
||||
if (stringArray) {
|
||||
return stringArray[index];
|
||||
}
|
||||
|
||||
// 序列化字段
|
||||
const serializedArray = self.serializedFields.get(propStr);
|
||||
if (serializedArray) {
|
||||
const serialized = serializedArray[index];
|
||||
if (serialized) {
|
||||
return SoASerializer.deserialize(serialized, propStr, {
|
||||
isMap: self.serializeMapFields.has(propStr),
|
||||
isSet: self.serializeSetFields.has(propStr),
|
||||
isArray: self.serializeArrayFields.has(propStr)
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 复杂字段
|
||||
const complexFieldMap = self.complexFields.get(entityId);
|
||||
if (complexFieldMap?.has(propStr)) {
|
||||
return complexFieldMap.get(propStr);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
set(_, prop: string | symbol, value) {
|
||||
const propStr = String(prop);
|
||||
|
||||
// entityId 是只读的
|
||||
if (propStr === 'entityId') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TypedArray 字段
|
||||
const array = self.fields.get(propStr);
|
||||
if (array) {
|
||||
const fieldType = self.getFieldType(propStr);
|
||||
if (fieldType === 'boolean') {
|
||||
array[index] = value ? 1 : 0;
|
||||
} else {
|
||||
array[index] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 字符串字段
|
||||
const stringArray = self.stringFields.get(propStr);
|
||||
if (stringArray) {
|
||||
stringArray[index] = String(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 序列化字段
|
||||
if (self.serializedFields.has(propStr)) {
|
||||
const serializedArray = self.serializedFields.get(propStr)!;
|
||||
serializedArray[index] = SoASerializer.serialize(value, propStr, {
|
||||
isMap: self.serializeMapFields.has(propStr),
|
||||
isSet: self.serializeSetFields.has(propStr),
|
||||
isArray: self.serializeArrayFields.has(propStr)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// 复杂字段
|
||||
let complexFieldMap = self.complexFields.get(entityId);
|
||||
if (!complexFieldMap) {
|
||||
complexFieldMap = new Map();
|
||||
self.complexFields.set(entityId, complexFieldMap);
|
||||
}
|
||||
complexFieldMap.set(propStr, value);
|
||||
return true;
|
||||
},
|
||||
|
||||
has(_, prop) {
|
||||
const propStr = String(prop);
|
||||
return self.fields.has(propStr) ||
|
||||
self.stringFields.has(propStr) ||
|
||||
self.serializedFields.has(propStr) ||
|
||||
self.complexFields.get(entityId)?.has(propStr) || false;
|
||||
},
|
||||
|
||||
ownKeys() {
|
||||
const keys: string[] = [];
|
||||
for (const key of self.fields.keys()) keys.push(key);
|
||||
for (const key of self.stringFields.keys()) keys.push(key);
|
||||
for (const key of self.serializedFields.keys()) keys.push(key);
|
||||
const complexFieldMap = self.complexFields.get(entityId);
|
||||
if (complexFieldMap) {
|
||||
for (const key of complexFieldMap.keys()) keys.push(key);
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
|
||||
getOwnPropertyDescriptor(_, prop) {
|
||||
const propStr = String(prop);
|
||||
if (self.fields.has(propStr) ||
|
||||
self.stringFields.has(propStr) ||
|
||||
self.serializedFields.has(propStr) ||
|
||||
self.complexFields.get(entityId)?.has(propStr)) {
|
||||
return {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
// entityId 是只读的
|
||||
writable: propStr !== 'entityId',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
return new Proxy({} as Record<string, unknown>, handler) as unknown as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件的快照副本(用于序列化等需要独立副本的场景)
|
||||
*/
|
||||
public getComponentSnapshot(entityId: number): T | null {
|
||||
const index = this.entityToIndex.get(entityId);
|
||||
if (index === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 需要 any 因为要动态写入泛型 T 的属性
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
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()) {
|
||||
@@ -640,7 +559,11 @@ export class SoAStorage<T extends Component> {
|
||||
for (const [fieldName, serializedArray] of this.serializedFields.entries()) {
|
||||
const serialized = serializedArray[index];
|
||||
if (serialized) {
|
||||
component[fieldName] = this.deserializeValue(serialized, fieldName, serializeMapFields, serializeSetFields, serializeArrayFields);
|
||||
component[fieldName] = SoASerializer.deserialize(serialized, fieldName, {
|
||||
isMap: this.serializeMapFields.has(fieldName),
|
||||
isSet: this.serializeSetFields.has(fieldName),
|
||||
isArray: this.serializeArrayFields.has(fieldName)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,10 +579,8 @@ export class SoAStorage<T extends Component> {
|
||||
}
|
||||
|
||||
private getFieldType(fieldName: string): string {
|
||||
// 通过创建临时实例检查字段类型
|
||||
const tempInstance = new this.type();
|
||||
const value = (tempInstance as any)[fieldName];
|
||||
return typeof value;
|
||||
// 使用缓存的字段类型
|
||||
return this.fieldTypes.get(fieldName) || 'unknown';
|
||||
}
|
||||
|
||||
public hasComponent(entityId: number): boolean {
|
||||
@@ -687,32 +608,7 @@ export class SoAStorage<T extends Component> {
|
||||
private resize(newCapacity: number): void {
|
||||
// 调整数值字段的TypedArray
|
||||
for (const [fieldName, oldArray] of this.fields.entries()) {
|
||||
let newArray: SupportedTypedArray;
|
||||
|
||||
if (oldArray instanceof Float32Array) {
|
||||
newArray = new Float32Array(newCapacity);
|
||||
} else if (oldArray instanceof Float64Array) {
|
||||
newArray = new Float64Array(newCapacity);
|
||||
} 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`);
|
||||
}
|
||||
|
||||
const newArray = SoATypeRegistry.createSameType(oldArray, newCapacity);
|
||||
newArray.set(oldArray);
|
||||
this.fields.set(fieldName, newArray);
|
||||
}
|
||||
@@ -849,42 +745,8 @@ export class SoAStorage<T extends Component> {
|
||||
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 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 typeName = SoATypeRegistry.getTypeName(array);
|
||||
const bytesPerElement = SoATypeRegistry.getBytesPerElement(typeName);
|
||||
const memory = array.length * bytesPerElement;
|
||||
totalMemory += memory;
|
||||
|
||||
@@ -914,4 +776,5 @@ export class SoAStorage<T extends Component> {
|
||||
const activeIndices = this.getActiveIndices();
|
||||
operation(this.fields, activeIndices);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
208
packages/core/src/ECS/Core/SoATypeRegistry.ts
Normal file
208
packages/core/src/ECS/Core/SoATypeRegistry.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* SoA存储器支持的TypedArray类型
|
||||
*/
|
||||
export type SupportedTypedArray =
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray;
|
||||
|
||||
export type TypedArrayConstructor =
|
||||
| typeof Float32Array
|
||||
| typeof Float64Array
|
||||
| typeof Int32Array
|
||||
| typeof Uint32Array
|
||||
| typeof Int16Array
|
||||
| typeof Uint16Array
|
||||
| typeof Int8Array
|
||||
| typeof Uint8Array
|
||||
| typeof Uint8ClampedArray;
|
||||
|
||||
/**
|
||||
* TypedArray 类型名称
|
||||
*/
|
||||
export type TypedArrayTypeName =
|
||||
| 'float32'
|
||||
| 'float64'
|
||||
| 'int32'
|
||||
| 'uint32'
|
||||
| 'int16'
|
||||
| 'uint16'
|
||||
| 'int8'
|
||||
| 'uint8'
|
||||
| 'uint8clamped';
|
||||
|
||||
/**
|
||||
* 字段元数据
|
||||
*/
|
||||
export interface FieldMetadata {
|
||||
name: string;
|
||||
type: 'number' | 'boolean' | 'string' | 'object';
|
||||
arrayType?: TypedArrayTypeName;
|
||||
isSerializedMap?: boolean;
|
||||
isSerializedSet?: boolean;
|
||||
isSerializedArray?: boolean;
|
||||
isDeepCopy?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SoA 类型注册器
|
||||
* 负责类型推断、TypedArray 创建和元数据管理
|
||||
*/
|
||||
export class SoATypeRegistry {
|
||||
private static readonly TYPE_CONSTRUCTORS: Record<TypedArrayTypeName, TypedArrayConstructor> = {
|
||||
float32: Float32Array,
|
||||
float64: Float64Array,
|
||||
int32: Int32Array,
|
||||
uint32: Uint32Array,
|
||||
int16: Int16Array,
|
||||
uint16: Uint16Array,
|
||||
int8: Int8Array,
|
||||
uint8: Uint8Array,
|
||||
uint8clamped: Uint8ClampedArray
|
||||
};
|
||||
|
||||
private static readonly TYPE_BYTES: Record<TypedArrayTypeName, number> = {
|
||||
float32: 4,
|
||||
float64: 8,
|
||||
int32: 4,
|
||||
uint32: 4,
|
||||
int16: 2,
|
||||
uint16: 2,
|
||||
int8: 1,
|
||||
uint8: 1,
|
||||
uint8clamped: 1
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取 TypedArray 构造函数
|
||||
*/
|
||||
public static getConstructor(typeName: TypedArrayTypeName): TypedArrayConstructor {
|
||||
return this.TYPE_CONSTRUCTORS[typeName] || Float32Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每个元素的字节数
|
||||
*/
|
||||
public static getBytesPerElement(typeName: TypedArrayTypeName): number {
|
||||
return this.TYPE_BYTES[typeName] || 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 TypedArray 实例获取类型名称
|
||||
*/
|
||||
public static getTypeName(array: SupportedTypedArray): TypedArrayTypeName {
|
||||
if (array instanceof Float32Array) return 'float32';
|
||||
if (array instanceof Float64Array) return 'float64';
|
||||
if (array instanceof Int32Array) return 'int32';
|
||||
if (array instanceof Uint32Array) return 'uint32';
|
||||
if (array instanceof Int16Array) return 'int16';
|
||||
if (array instanceof Uint16Array) return 'uint16';
|
||||
if (array instanceof Int8Array) return 'int8';
|
||||
if (array instanceof Uint8Array) return 'uint8';
|
||||
if (array instanceof Uint8ClampedArray) return 'uint8clamped';
|
||||
return 'float32';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的 TypedArray(与原数组同类型)
|
||||
*/
|
||||
public static createSameType(source: SupportedTypedArray, capacity: number): SupportedTypedArray {
|
||||
const typeName = this.getTypeName(source);
|
||||
const Constructor = this.getConstructor(typeName);
|
||||
return new Constructor(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从组件类型提取字段元数据
|
||||
*/
|
||||
public static extractFieldMetadata<T>(
|
||||
componentType: new () => T
|
||||
): Map<string, FieldMetadata> {
|
||||
const instance = new componentType();
|
||||
const metadata = new Map<string, FieldMetadata>();
|
||||
|
||||
const typeWithMeta = componentType as typeof componentType & {
|
||||
__float64Fields?: Set<string>;
|
||||
__float32Fields?: Set<string>;
|
||||
__int32Fields?: Set<string>;
|
||||
__uint32Fields?: Set<string>;
|
||||
__int16Fields?: Set<string>;
|
||||
__uint16Fields?: Set<string>;
|
||||
__int8Fields?: Set<string>;
|
||||
__uint8Fields?: Set<string>;
|
||||
__uint8ClampedFields?: Set<string>;
|
||||
__serializeMapFields?: Set<string>;
|
||||
__serializeSetFields?: Set<string>;
|
||||
__serializeArrayFields?: Set<string>;
|
||||
__deepCopyFields?: Set<string>;
|
||||
};
|
||||
|
||||
// 收集装饰器标记
|
||||
const decoratorMap = new Map<string, TypedArrayTypeName>();
|
||||
|
||||
const addDecorators = (fields: Set<string> | undefined, type: TypedArrayTypeName) => {
|
||||
if (fields) {
|
||||
for (const key of fields) decoratorMap.set(key, type);
|
||||
}
|
||||
};
|
||||
|
||||
addDecorators(typeWithMeta.__float64Fields, 'float64');
|
||||
addDecorators(typeWithMeta.__float32Fields, 'float32');
|
||||
addDecorators(typeWithMeta.__int32Fields, 'int32');
|
||||
addDecorators(typeWithMeta.__uint32Fields, 'uint32');
|
||||
addDecorators(typeWithMeta.__int16Fields, 'int16');
|
||||
addDecorators(typeWithMeta.__uint16Fields, 'uint16');
|
||||
addDecorators(typeWithMeta.__int8Fields, 'int8');
|
||||
addDecorators(typeWithMeta.__uint8Fields, 'uint8');
|
||||
addDecorators(typeWithMeta.__uint8ClampedFields, 'uint8clamped');
|
||||
|
||||
// 遍历实例属性
|
||||
const instanceKeys = Object.keys(instance as object).filter((key) => key !== 'id');
|
||||
|
||||
for (const key of instanceKeys) {
|
||||
const value = (instance as Record<string, unknown>)[key];
|
||||
const type = typeof value;
|
||||
|
||||
if (type === 'function') continue;
|
||||
|
||||
const fieldMeta: FieldMetadata = {
|
||||
name: key,
|
||||
type: type as 'number' | 'boolean' | 'string' | 'object'
|
||||
};
|
||||
|
||||
const decoratorType = decoratorMap.get(key);
|
||||
|
||||
if (decoratorType) {
|
||||
fieldMeta.arrayType = decoratorType;
|
||||
} else if (type === 'number') {
|
||||
fieldMeta.arrayType = 'float32';
|
||||
} else if (type === 'boolean') {
|
||||
fieldMeta.arrayType = 'uint8';
|
||||
}
|
||||
|
||||
// 序列化标记
|
||||
if (typeWithMeta.__serializeMapFields?.has(key)) {
|
||||
fieldMeta.isSerializedMap = true;
|
||||
}
|
||||
if (typeWithMeta.__serializeSetFields?.has(key)) {
|
||||
fieldMeta.isSerializedSet = true;
|
||||
}
|
||||
if (typeWithMeta.__serializeArrayFields?.has(key)) {
|
||||
fieldMeta.isSerializedArray = true;
|
||||
}
|
||||
if (typeWithMeta.__deepCopyFields?.has(key)) {
|
||||
fieldMeta.isDeepCopy = true;
|
||||
}
|
||||
|
||||
metadata.set(key, fieldMeta);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export { ComponentPool, ComponentPoolManager } from '../ComponentPool';
|
||||
export { ComponentStorage, ComponentRegistry } from '../ComponentStorage';
|
||||
export { EnableSoA, HighPrecision, Float64, Float32, Int32, SerializeMap, SoAStorage } from '../SoAStorage';
|
||||
export { EnableSoA, Float64, Float32, Int32, SerializeMap, SoAStorage } from '../SoAStorage';
|
||||
|
||||
@@ -23,7 +23,6 @@ export {
|
||||
EnableSoA,
|
||||
|
||||
// 数值类型装饰器
|
||||
HighPrecision,
|
||||
Float64,
|
||||
Float32,
|
||||
Int32,
|
||||
@@ -34,10 +33,6 @@ export {
|
||||
Uint8,
|
||||
Uint8Clamped,
|
||||
|
||||
// 自动类型推断
|
||||
AutoTyped,
|
||||
TypeInference,
|
||||
|
||||
// 序列化装饰器
|
||||
SerializeMap,
|
||||
SerializeSet,
|
||||
|
||||
Reference in New Issue
Block a user