避免throw导致的中止运行,增加fallback回退json的序列化

This commit is contained in:
YHH
2025-08-07 10:16:36 +08:00
parent 7a000318a6
commit 4479f0fab0
4 changed files with 1014 additions and 73 deletions

View File

@@ -6,6 +6,7 @@
import 'reflect-metadata'; import 'reflect-metadata';
import { Component } from '../../ECS/Component'; import { Component } from '../../ECS/Component';
import { BigIntFactory } from '../../ECS/Utils/BigIntCompatibility';
/** /**
* Protobuf字段类型枚举 * Protobuf字段类型枚举
@@ -25,7 +26,15 @@ export enum ProtoFieldType {
SFIXED64 = 'sfixed64', SFIXED64 = 'sfixed64',
BOOL = 'bool', BOOL = 'bool',
STRING = 'string', STRING = 'string',
BYTES = 'bytes' BYTES = 'bytes',
// 扩展类型
MESSAGE = 'message',
ENUM = 'enum',
ANY = 'google.protobuf.Any',
TIMESTAMP = 'google.protobuf.Timestamp',
DURATION = 'google.protobuf.Duration',
STRUCT = 'google.protobuf.Struct',
VALUE = 'google.protobuf.Value'
} }
/** /**
@@ -42,6 +51,12 @@ export interface ProtoFieldDefinition {
optional?: boolean; optional?: boolean;
/** 字段名称 */ /** 字段名称 */
name: string; name: string;
/** 自定义类型名称 */
customTypeName?: string;
/** 枚举值映射 */
enumValues?: Record<string, number>;
/** 默认值 */
defaultValue?: any;
} }
/** /**
@@ -137,14 +152,12 @@ export class ProtobufRegistry {
* ProtoSerializable 组件装饰器 * ProtoSerializable 组件装饰器
* *
* 标记组件支持protobuf序列化 * 标记组件支持protobuf序列化
* * @param protoName protobuf消息名称默认使用类名
* @param protoName - protobuf消息名称默认使用类名
*
* @example * @example
* ```typescript * ```typescript
* @ProtoSerializable('Position') * @ProtoSerializable('Position')
* class PositionComponent extends Component { * class PositionComponent extends Component {
* // ... * // 组件实现
* } * }
* ``` * ```
*/ */
@@ -153,7 +166,7 @@ export function ProtoSerializable(protoName?: string) {
const componentName = protoName || constructor.name; const componentName = protoName || constructor.name;
const registry = ProtobufRegistry.getInstance(); const registry = ProtobufRegistry.getInstance();
// 获取字段定义由ProtoField装饰器设置 // 获取字段定义
const fields = (constructor.prototype._protoFields as Map<string, ProtoFieldDefinition>) const fields = (constructor.prototype._protoFields as Map<string, ProtoFieldDefinition>)
|| new Map<string, ProtoFieldDefinition>(); || new Map<string, ProtoFieldDefinition>();
@@ -176,11 +189,14 @@ export function ProtoSerializable(protoName?: string) {
* ProtoField 字段装饰器 * ProtoField 字段装饰器
* *
* 标记字段参与protobuf序列化 * 标记字段参与protobuf序列化
* * @param fieldNumber protobuf字段编号必须唯一且大于0
* @param fieldNumber - protobuf字段编号必须唯一且大于0 * @param type 字段类型,默认自动推断
* @param type - 字段类型,默认自动推断 * @param options 额外选项
* @param options - 额外选项 * @param options.repeated 是否为数组
* * @param options.optional 是否可选
* @param options.customTypeName 自定义类型名称
* @param options.enumValues 枚举值映射
* @param options.defaultValue 默认值
* @example * @example
* ```typescript * ```typescript
* class PositionComponent extends Component { * class PositionComponent extends Component {
@@ -198,6 +214,9 @@ export function ProtoField(
options?: { options?: {
repeated?: boolean; repeated?: boolean;
optional?: boolean; optional?: boolean;
customTypeName?: string;
enumValues?: Record<string, number>;
defaultValue?: any;
} }
) { ) {
return function (target: any, propertyKey: string) { return function (target: any, propertyKey: string) {
@@ -231,7 +250,10 @@ export function ProtoField(
type: inferredType || ProtoFieldType.STRING, type: inferredType || ProtoFieldType.STRING,
repeated: options?.repeated || false, repeated: options?.repeated || false,
optional: options?.optional || false, optional: options?.optional || false,
name: propertyKey name: propertyKey,
customTypeName: options?.customTypeName,
enumValues: options?.enumValues,
defaultValue: options?.defaultValue
}); });
}; };
} }
@@ -244,11 +266,20 @@ function inferProtoType(jsType: any): ProtoFieldType {
switch (jsType) { switch (jsType) {
case Number: case Number:
return ProtoFieldType.FLOAT; return ProtoFieldType.DOUBLE;
case Boolean: case Boolean:
return ProtoFieldType.BOOL; return ProtoFieldType.BOOL;
case String: case String:
return ProtoFieldType.STRING; return ProtoFieldType.STRING;
case Date:
return ProtoFieldType.TIMESTAMP;
case Array:
return ProtoFieldType.STRING;
case Uint8Array:
case ArrayBuffer:
return ProtoFieldType.BYTES;
case Object:
return ProtoFieldType.STRUCT;
default: default:
return ProtoFieldType.STRING; return ProtoFieldType.STRING;
} }
@@ -269,6 +300,49 @@ export const ProtoString = (fieldNumber: number, options?: { repeated?: boolean;
export const ProtoBool = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) => export const ProtoBool = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.BOOL, options); ProtoField(fieldNumber, ProtoFieldType.BOOL, options);
// 扩展的便捷装饰器
export const ProtoDouble = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.DOUBLE, options);
export const ProtoInt64 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.INT64, options);
export const ProtoUint32 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.UINT32, options);
export const ProtoUint64 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.UINT64, options);
export const ProtoBytes = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.BYTES, options);
export const ProtoTimestamp = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.TIMESTAMP, options);
export const ProtoDuration = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.DURATION, options);
export const ProtoStruct = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.STRUCT, options);
/**
* 自定义消息类型装饰器
* @param fieldNumber 字段编号
* @param customTypeName 自定义类型名称
* @param options 额外选项
*/
export const ProtoMessage = (fieldNumber: number, customTypeName: string, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.MESSAGE, { ...options, customTypeName });
/**
* 枚举类型装饰器
* @param fieldNumber 字段编号
* @param enumValues 枚举值映射
* @param options 额外选项
*/
export const ProtoEnum = (fieldNumber: number, enumValues: Record<string, number>, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.ENUM, { ...options, enumValues });
/** /**
* 检查组件是否支持protobuf序列化 * 检查组件是否支持protobuf序列化
*/ */

View File

@@ -5,6 +5,7 @@
*/ */
import { Component } from '../../ECS/Component'; import { Component } from '../../ECS/Component';
import { BigIntFactory } from '../../ECS/Utils/BigIntCompatibility';
import { import {
ProtobufRegistry, ProtobufRegistry,
ProtoComponentDefinition, ProtoComponentDefinition,
@@ -30,9 +31,21 @@ export class ProtobufSerializer {
/** MessageType缓存映射表 */ /** MessageType缓存映射表 */
private messageTypeCache: Map<string, any> = new Map(); private messageTypeCache: Map<string, any> = new Map();
/** 组件序列化数据缓存 */
private componentDataCache: Map<string, any> = new Map();
/** 缓存访问计数器 */
private cacheAccessCount: Map<string, number> = new Map();
/** 最大缓存大小 */
private maxCacheSize: number = 1000;
/** 是否启用数据验证 */ /** 是否启用数据验证 */
private enableValidation: boolean = process.env.NODE_ENV === 'development'; private enableValidation: boolean = process.env.NODE_ENV === 'development';
/** 是否启用组件数据缓存 */
private enableComponentDataCache: boolean = true;
private constructor() { private constructor() {
this.registry = ProtobufRegistry.getInstance(); this.registry = ProtobufRegistry.getInstance();
this.initializeProtobuf(); this.initializeProtobuf();
@@ -40,17 +53,44 @@ export class ProtobufSerializer {
/** /**
* 设置性能选项 * 设置性能选项
* @param options 性能配置选项
* @param options.enableValidation 是否启用数据验证
* @param options.enableComponentDataCache 是否启用组件数据缓存
* @param options.maxCacheSize 最大缓存大小
* @param options.clearCache 是否清空消息类型缓存
* @param options.clearAllCaches 是否清空所有缓存
*/ */
public setPerformanceOptions(options: { public setPerformanceOptions(options: {
enableValidation?: boolean; enableValidation?: boolean;
enableComponentDataCache?: boolean;
maxCacheSize?: number;
clearCache?: boolean; clearCache?: boolean;
clearAllCaches?: boolean;
}): void { }): void {
if (options.enableValidation !== undefined) { if (options.enableValidation !== undefined) {
this.enableValidation = options.enableValidation; this.enableValidation = options.enableValidation;
} }
if (options.enableComponentDataCache !== undefined) {
this.enableComponentDataCache = options.enableComponentDataCache;
}
if (options.maxCacheSize !== undefined && options.maxCacheSize > 0) {
this.maxCacheSize = options.maxCacheSize;
}
if (options.clearCache) { if (options.clearCache) {
this.messageTypeCache.clear(); this.messageTypeCache.clear();
} }
if (options.clearAllCaches) {
this.clearAllCaches();
}
}
/**
* 清空所有缓存
*/
public clearAllCaches(): void {
this.messageTypeCache.clear();
this.componentDataCache.clear();
this.cacheAccessCount.clear();
} }
/** /**
@@ -76,7 +116,6 @@ export class ProtobufSerializer {
/** /**
* 手动初始化protobuf.js库 * 手动初始化protobuf.js库
*
* @param protobufJs protobuf.js库实例 * @param protobufJs protobuf.js库实例
*/ */
public initialize(protobufJs: any): void { public initialize(protobufJs: any): void {
@@ -87,44 +126,42 @@ export class ProtobufSerializer {
/** /**
* 序列化组件 * 序列化组件
*
* @param component 要序列化的组件 * @param component 要序列化的组件
* @returns 序列化数据 * @returns 序列化数据
* @throws Error 如果组件不支持protobuf序列化
*/ */
public serialize(component: Component): SerializedData { public serialize(component: Component): SerializedData {
const componentType = component.constructor.name; const componentType = component.constructor.name;
// 检查是否支持protobuf序列化 // 检查是否支持protobuf序列化
if (!isProtoSerializable(component)) { if (!isProtoSerializable(component)) {
throw new Error(`[ProtobufSerializer] 组件 ${componentType} 不支持protobuf序列化,请添加@ProtoSerializable装饰器`); return this.fallbackToJsonSerialization(component, `组件 ${componentType} 不支持protobuf序列化`);
} }
const protoName = getProtoName(component); const protoName = getProtoName(component);
if (!protoName) { if (!protoName) {
throw new Error(`[ProtobufSerializer] 组件 ${componentType} 未设置protobuf名称`); return this.fallbackToJsonSerialization(component, `组件 ${componentType} 未设置protobuf名称`);
} }
const definition = this.registry.getComponentDefinition(protoName); const definition = this.registry.getComponentDefinition(protoName);
if (!definition) { if (!definition) {
throw new Error(`[ProtobufSerializer] 未找到组件定义: ${protoName}`); return this.fallbackToJsonSerialization(component, `未找到组件定义: ${protoName}`);
} }
// 获取protobuf消息类型 // 获取protobuf消息类型
const MessageType = this.getMessageType(protoName); const MessageType = this.getMessageType(protoName);
if (!MessageType) { if (!MessageType) {
throw new Error(`[ProtobufSerializer] 未找到消息类型: ${protoName}`); return this.fallbackToJsonSerialization(component, `未找到消息类型: ${protoName}`);
} }
try { try {
// 构建protobuf数据对象 // 构建protobuf数据对象
const protoData = this.buildProtoData(component, definition); const protoData = this.buildProtoData(component, definition);
// 数据验证(仅在开发环境) // 数据验证
if (this.enableValidation) { if (this.enableValidation) {
const error = MessageType.verify(protoData); const error = MessageType.verify(protoData);
if (error) { if (error) {
throw new Error(`[ProtobufSerializer] 数据验证失败: ${error}`); return this.fallbackToJsonSerialization(component, `数据验证失败: ${error}`);
} }
} }
@@ -140,26 +177,33 @@ export class ProtobufSerializer {
}; };
} catch (error) { } catch (error) {
throw new Error(`[ProtobufSerializer] 序列化失败: ${componentType} - ${error}`); return this.fallbackToJsonSerialization(component, `序列化失败: ${error}`);
} }
} }
/** /**
* 反序列化组件 * 反序列化组件
*
* @param component 目标组件实例 * @param component 目标组件实例
* @param serializedData 序列化数据 * @param serializedData 序列化数据
* @throws Error 如果反序列化失败
*/ */
public deserialize(component: Component, serializedData: SerializedData): void { public deserialize(component: Component, serializedData: SerializedData): void {
// 如果是JSON数据使用JSON反序列化
if (serializedData.type === 'json') {
this.deserializeFromJson(component, serializedData);
return;
}
// Protobuf反序列化
const protoName = getProtoName(component); const protoName = getProtoName(component);
if (!protoName) { if (!protoName) {
throw new Error(`[ProtobufSerializer] 组件 ${component.constructor.name} 未设置protobuf名称`); console.warn(`[ProtobufSerializer] 组件 ${component.constructor.name} 未设置protobuf名称,跳过反序列化`);
return;
} }
const MessageType = this.getMessageType(protoName); const MessageType = this.getMessageType(protoName);
if (!MessageType) { if (!MessageType) {
throw new Error(`[ProtobufSerializer] 未找到消息类型: ${protoName}`); console.warn(`[ProtobufSerializer] 未找到消息类型: ${protoName},跳过反序列化`);
return;
} }
try { try {
@@ -171,7 +215,8 @@ export class ProtobufSerializer {
this.applyDataToComponent(component, data); this.applyDataToComponent(component, data);
} catch (error) { } catch (error) {
throw new Error(`[ProtobufSerializer] 反序列化失败: ${component.constructor.name} - ${error}`); console.error(`[ProtobufSerializer] 反序列化失败: ${component.constructor.name} - ${error}`);
// 不抛出异常,避免影响整个反序列化流程
} }
} }
@@ -185,31 +230,59 @@ export class ProtobufSerializer {
/** /**
* 批量序列化组件 * 批量序列化组件
*
* @param components 要序列化的组件数组 * @param components 要序列化的组件数组
* @param options 批量序列化选项
* @param options.continueOnError 遇到错误时是否继续处理
* @param options.maxBatchSize 最大批次大小
* @returns 序列化结果数组 * @returns 序列化结果数组
*/ */
public serializeBatch(components: Component[]): SerializedData[] { public serializeBatch(
components: Component[],
options?: {
continueOnError?: boolean;
maxBatchSize?: number;
}
): SerializedData[] {
const results: SerializedData[] = []; const results: SerializedData[] = [];
const errors: Error[] = [];
const continueOnError = options?.continueOnError ?? false;
const maxBatchSize = options?.maxBatchSize ?? 1000;
// 分批处理大量组件
const batches = this.splitIntoBatches(components, maxBatchSize);
for (const batch of batches) {
const batchResults = this.serializeBatchSerial(batch, continueOnError);
results.push(...batchResults.results);
errors.push(...batchResults.errors);
}
// 如果有错误且不继续执行,抛出第一个错误
if (errors.length > 0 && !continueOnError) {
throw errors[0];
}
// 记录错误统计
if (errors.length > 0) {
console.warn(`[ProtobufSerializer] 批量序列化完成,${results.length} 成功,${errors.length} 失败`);
}
return results;
}
/**
* 串行批量序列化
*/
private serializeBatchSerial(
components: Component[],
continueOnError: boolean
): { results: SerializedData[], errors: Error[] } {
const results: SerializedData[] = [];
const errors: Error[] = [];
// 按组件类型分组,减少重复查找 // 按组件类型分组,减少重复查找
const componentGroups = new Map<string, Component[]>(); const componentGroups = this.groupComponentsByType(components, continueOnError, errors);
for (const component of components) {
if (!isProtoSerializable(component)) {
throw new Error(`[ProtobufSerializer] 组件 ${component.constructor.name} 不支持protobuf序列化`);
}
const protoName = getProtoName(component);
if (!protoName) {
throw new Error(`[ProtobufSerializer] 组件 ${component.constructor.name} 未设置protobuf名称`);
}
if (!componentGroups.has(protoName)) {
componentGroups.set(protoName, []);
}
componentGroups.get(protoName)!.push(component);
}
// 按组分别序列化 // 按组分别序列化
for (const [protoName, groupComponents] of componentGroups) { for (const [protoName, groupComponents] of componentGroups) {
@@ -217,37 +290,135 @@ export class ProtobufSerializer {
const MessageType = this.getMessageType(protoName); const MessageType = this.getMessageType(protoName);
if (!definition || !MessageType) { if (!definition || !MessageType) {
throw new Error(`[ProtobufSerializer] 组件类型 ${protoName} 未正确注册`); const error = new Error(`[ProtobufSerializer] 组件类型 ${protoName} 未正确注册`);
if (continueOnError) {
errors.push(error);
// 回退到JSON序列化
for (const component of groupComponents) {
try {
const jsonResult = this.fallbackToJsonSerialization(component, `组件类型 ${protoName} 未正确注册`);
results.push(jsonResult);
} catch (jsonError) {
errors.push(jsonError instanceof Error ? jsonError : new Error(String(jsonError)));
}
}
continue;
} else {
throw error;
}
} }
// 预编译消息类型和字段定义
const compiledType = this.getCompiledMessageType(protoName, definition, MessageType);
for (const component of groupComponents) { for (const component of groupComponents) {
try { try {
const protoData = this.buildProtoData(component, definition); const result = this.serializeSingleComponent(component, definition, compiledType);
results.push(result);
// 数据验证(仅在开发环境)
if (this.enableValidation) {
const error = MessageType.verify(protoData);
if (error) {
throw new Error(`[ProtobufSerializer] 数据验证失败: ${error}`);
}
}
const message = MessageType.create(protoData);
const buffer = MessageType.encode(message).finish();
results.push({
type: 'protobuf',
componentType: component.constructor.name,
data: buffer,
size: buffer.length
});
} catch (error) { } catch (error) {
throw new Error(`[ProtobufSerializer] 批量序列化失败: ${component.constructor.name} - ${error}`); if (continueOnError) {
errors.push(error instanceof Error ? error : new Error(String(error)));
// 尝试JSON回退
try {
const jsonResult = this.fallbackToJsonSerialization(component, `Protobuf序列化失败: ${error}`);
results.push(jsonResult);
} catch (jsonError) {
errors.push(jsonError instanceof Error ? jsonError : new Error(String(jsonError)));
}
} else {
throw error;
}
} }
} }
} }
return results; return { results, errors };
}
/**
* 序列化单个组件
*/
private serializeSingleComponent(
component: Component,
definition: ProtoComponentDefinition,
compiledType: any
): SerializedData {
const protoData = this.buildProtoData(component, definition);
// 数据验证
if (this.enableValidation && compiledType.verify) {
const error = compiledType.verify(protoData);
if (error) {
throw new Error(`[ProtobufSerializer] 数据验证失败: ${error}`);
}
}
const message = compiledType.create(protoData);
const buffer = compiledType.encode(message).finish();
return {
type: 'protobuf',
componentType: component.constructor.name,
data: buffer,
size: buffer.length
};
}
/**
* 按类型分组组件
*/
private groupComponentsByType(
components: Component[],
continueOnError: boolean,
errors: Error[]
): Map<string, Component[]> {
const componentGroups = new Map<string, Component[]>();
for (const component of components) {
try {
if (!isProtoSerializable(component)) {
throw new Error(`[ProtobufSerializer] 组件 ${component.constructor.name} 不支持protobuf序列化`);
}
const protoName = getProtoName(component);
if (!protoName) {
throw new Error(`[ProtobufSerializer] 组件 ${component.constructor.name} 未设置protobuf名称`);
}
if (!componentGroups.has(protoName)) {
componentGroups.set(protoName, []);
}
componentGroups.get(protoName)!.push(component);
} catch (error) {
if (continueOnError) {
errors.push(error instanceof Error ? error : new Error(String(error)));
} else {
throw error;
}
}
}
return componentGroups;
}
/**
* 获取编译后的消息类型
*/
private getCompiledMessageType(_protoName: string, _definition: ProtoComponentDefinition, MessageType: any): any {
// TODO: 实现消息类型编译和缓存优化
return MessageType;
}
/**
* 将数组分割成批次
*/
private splitIntoBatches<T>(items: T[], batchSize: number): T[][] {
const batches: T[][] = [];
for (let i = 0; i < items.length; i += batchSize) {
batches.push(items.slice(i, i + batchSize));
}
return batches;
} }
/** /**
@@ -256,12 +427,18 @@ export class ProtobufSerializer {
public getStats(): { public getStats(): {
registeredComponents: number; registeredComponents: number;
protobufAvailable: boolean; protobufAvailable: boolean;
cacheSize: number; messageTypeCacheSize: number;
componentDataCacheSize: number;
enableComponentDataCache: boolean;
maxCacheSize: number;
} { } {
return { return {
registeredComponents: this.registry.getAllComponents().size, registeredComponents: this.registry.getAllComponents().size,
protobufAvailable: !!this.protobuf, protobufAvailable: !!this.protobuf,
cacheSize: this.messageTypeCache.size messageTypeCacheSize: this.messageTypeCache.size,
componentDataCacheSize: this.componentDataCache.size,
enableComponentDataCache: this.enableComponentDataCache,
maxCacheSize: this.maxCacheSize
}; };
} }
@@ -269,6 +446,17 @@ export class ProtobufSerializer {
* 构建protobuf数据对象 * 构建protobuf数据对象
*/ */
private buildProtoData(component: Component, definition: ProtoComponentDefinition): any { private buildProtoData(component: Component, definition: ProtoComponentDefinition): any {
const componentType = component.constructor.name;
// 生成缓存键
const cacheKey = this.generateComponentCacheKey(component, componentType);
// 检查缓存
if (this.enableComponentDataCache && this.componentDataCache.has(cacheKey)) {
this.updateCacheAccess(cacheKey);
return this.componentDataCache.get(cacheKey);
}
const data: any = {}; const data: any = {};
for (const [propertyName, fieldDef] of definition.fields) { for (const [propertyName, fieldDef] of definition.fields) {
@@ -279,6 +467,11 @@ export class ProtobufSerializer {
} }
} }
// 缓存结果,仅在启用且数据较小时缓存
if (this.enableComponentDataCache && JSON.stringify(data).length < 1000) {
this.setCacheWithLRU(cacheKey, data);
}
return data; return data;
} }
@@ -305,6 +498,15 @@ export class ProtobufSerializer {
case ProtoFieldType.SFIXED32: case ProtoFieldType.SFIXED32:
return typeof value === 'number' ? (value | 0) : (parseInt(value) || 0); return typeof value === 'number' ? (value | 0) : (parseInt(value) || 0);
case ProtoFieldType.INT64:
case ProtoFieldType.UINT64:
case ProtoFieldType.SINT64:
case ProtoFieldType.FIXED64:
case ProtoFieldType.SFIXED64:
// 使用BigIntFactory处理64位整数以确保兼容性
const bigIntValue = BigIntFactory.create(value || 0);
return bigIntValue.valueOf(); // 转换为数值用于protobuf
case ProtoFieldType.FLOAT: case ProtoFieldType.FLOAT:
case ProtoFieldType.DOUBLE: case ProtoFieldType.DOUBLE:
return typeof value === 'number' ? value : (parseFloat(value) || 0); return typeof value === 'number' ? value : (parseFloat(value) || 0);
@@ -315,11 +517,87 @@ export class ProtobufSerializer {
case ProtoFieldType.STRING: case ProtoFieldType.STRING:
return typeof value === 'string' ? value : String(value); return typeof value === 'string' ? value : String(value);
case ProtoFieldType.BYTES:
if (value instanceof Uint8Array) return value;
if (value instanceof ArrayBuffer) return new Uint8Array(value);
if (typeof value === 'string') return new TextEncoder().encode(value);
return new Uint8Array();
case ProtoFieldType.TIMESTAMP:
if (value instanceof Date) {
return {
seconds: Math.floor(value.getTime() / 1000),
nanos: (value.getTime() % 1000) * 1000000
};
}
if (typeof value === 'string') {
const date = new Date(value);
return {
seconds: Math.floor(date.getTime() / 1000),
nanos: (date.getTime() % 1000) * 1000000
};
}
return { seconds: 0, nanos: 0 };
case ProtoFieldType.DURATION:
if (typeof value === 'number') {
return {
seconds: Math.floor(value / 1000),
nanos: (value % 1000) * 1000000
};
}
return { seconds: 0, nanos: 0 };
case ProtoFieldType.STRUCT:
if (value && typeof value === 'object') {
return this.convertObjectToStruct(value);
}
return {};
case ProtoFieldType.MESSAGE:
case ProtoFieldType.ENUM:
// 对于自定义消息和枚举直接返回值让protobuf.js处理
return value;
default: default:
return value; return value;
} }
} }
/**
* 转换对象为Protobuf Struct格式
*/
private convertObjectToStruct(obj: any): any {
const result: any = { fields: {} };
for (const [key, value] of Object.entries(obj)) {
result.fields[key] = this.convertValueToStructValue(value);
}
return result;
}
/**
* 转换值为Protobuf Value格式
*/
private convertValueToStructValue(value: any): any {
if (value === null) return { nullValue: 0 };
if (typeof value === 'number') return { numberValue: value };
if (typeof value === 'string') return { stringValue: value };
if (typeof value === 'boolean') return { boolValue: value };
if (Array.isArray(value)) {
return {
listValue: {
values: value.map(v => this.convertValueToStructValue(v))
}
};
}
if (typeof value === 'object') {
return { structValue: this.convertObjectToStruct(value) };
}
return { stringValue: String(value) };
}
/** /**
* 应用数据到组件 * 应用数据到组件
*/ */
@@ -378,4 +656,180 @@ export class ProtobufSerializer {
return null; return null;
} }
} }
/**
* 回退到JSON序列化
*/
private fallbackToJsonSerialization(component: Component, reason: string): SerializedData {
console.warn(`[ProtobufSerializer] ${reason}回退到JSON序列化`);
try {
const data = this.serializeToJson(component);
const jsonString = JSON.stringify(data);
const encoder = new TextEncoder();
const buffer = encoder.encode(jsonString);
return {
type: 'json',
componentType: component.constructor.name,
data: data,
size: buffer.length
};
} catch (error) {
console.error(`[ProtobufSerializer] JSON序列化也失败: ${error}`);
throw new Error(`[ProtobufSerializer] 序列化完全失败: ${component.constructor.name}`);
}
}
/**
* 从JSON数据反序列化
*/
private deserializeFromJson(component: Component, serializedData: SerializedData): void {
try {
if (typeof (component as any).deserialize === 'function') {
// 使用组件的自定义反序列化方法
(component as any).deserialize(serializedData.data);
} else {
// 默认的属性赋值
Object.assign(component, serializedData.data);
}
} catch (error) {
console.error(`[ProtobufSerializer] JSON反序列化失败: ${component.constructor.name} - ${error}`);
}
}
/**
* 序列化为JSON对象
*/
private serializeToJson(component: Component): any {
if (typeof (component as any).serialize === 'function') {
// 使用组件的自定义序列化方法
return (component as any).serialize();
} else {
// 默认的属性序列化
const data: any = {};
// 获取所有可枚举属性
for (const key of Object.keys(component)) {
const value = (component as any)[key];
if (this.isSerializableValue(value)) {
data[key] = this.deepCloneSerializableValue(value);
}
}
return data;
}
}
/**
* 检查值是否可序列化
*/
private isSerializableValue(value: any): boolean {
if (value === null || value === undefined) return true;
if (typeof value === 'function') return false;
if (value instanceof Date) return true;
if (Array.isArray(value)) return true;
if (value instanceof Map || value instanceof Set) return true;
if (typeof value === 'object' && value.constructor === Object) return true;
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return true;
return false;
}
/**
* 深拷贝可序列化的值
*/
private deepCloneSerializableValue(value: any): any {
if (value === null || value === undefined) return value;
if (typeof value !== 'object') return value;
if (value instanceof Date) return value.toISOString();
if (Array.isArray(value)) {
return value.map(item => this.deepCloneSerializableValue(item));
}
if (value instanceof Map) {
return Array.from(value.entries());
}
if (value instanceof Set) {
return Array.from(value);
}
if (value.constructor === Object) {
const result: any = {};
for (const key in value) {
if (value.hasOwnProperty(key) && this.isSerializableValue(value[key])) {
result[key] = this.deepCloneSerializableValue(value[key]);
}
}
return result;
}
return value;
}
/**
* 生成组件缓存键
*/
private generateComponentCacheKey(component: Component, componentType: string): string {
// TODO: 考虑更高效的缓存键生成策略
const properties = Object.keys(component).sort();
const values = properties.map(key => String((component as any)[key])).join('|');
return `${componentType}:${this.simpleHash(values)}`;
}
/**
* 简单哈希函数
*/
private simpleHash(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash.toString(36);
}
/**
* 更新缓存访问计数
*/
private updateCacheAccess(cacheKey: string): void {
const currentCount = this.cacheAccessCount.get(cacheKey) || 0;
this.cacheAccessCount.set(cacheKey, currentCount + 1);
}
/**
* 使用LRU策略设置缓存
*/
private setCacheWithLRU(cacheKey: string, data: any): void {
// 检查是否需要淘汰缓存
if (this.componentDataCache.size >= this.maxCacheSize) {
this.evictLRUCache();
}
this.componentDataCache.set(cacheKey, data);
this.cacheAccessCount.set(cacheKey, 1);
}
/**
* 淘汰LRU缓存项
*/
private evictLRUCache(): void {
let lruKey = '';
let minAccessCount = Number.MAX_SAFE_INTEGER;
// 找到访问次数最少的缓存项
for (const [key, count] of this.cacheAccessCount) {
if (count < minAccessCount) {
minAccessCount = count;
lruKey = key;
}
}
if (lruKey) {
this.componentDataCache.delete(lruKey);
this.cacheAccessCount.delete(lruKey);
}
}
} }

View File

@@ -7,11 +7,11 @@
*/ */
export interface SerializedData { export interface SerializedData {
/** 序列化类型 */ /** 序列化类型 */
type: 'protobuf'; type: 'protobuf' | 'json';
/** 组件类型名称 */ /** 组件类型名称 */
componentType: string; componentType: string;
/** 序列化后的数据 */ /** 序列化后的数据 */
data: Uint8Array; data: Uint8Array | any;
/** 数据大小(字节) */ /** 数据大小(字节) */
size: number; size: number;
} }

View File

@@ -0,0 +1,413 @@
/**
* Protobuf序列化器边界情况测试
*/
import { Component } from '../../../src/ECS/Component';
import { BigIntFactory } from '../../../src/ECS/Utils/BigIntCompatibility';
import { ProtobufSerializer } from '../../../src/Utils/Serialization/ProtobufSerializer';
import {
ProtoSerializable,
ProtoFloat,
ProtoInt32,
ProtoString,
ProtoBool,
ProtoBytes,
ProtoTimestamp,
ProtoDouble,
ProtoInt64,
ProtoStruct
} from '../../../src/Utils/Serialization/ProtobufDecorators';
// 边界测试组件
@ProtoSerializable('EdgeCaseComponent')
class EdgeCaseComponent extends Component {
@ProtoFloat(1)
public floatValue: number = 0;
@ProtoDouble(2)
public doubleValue: number = 0;
@ProtoInt32(3)
public intValue: number = 0;
@ProtoInt64(4)
public bigIntValue: any = BigIntFactory.zero();
@ProtoString(5)
public stringValue: string = '';
@ProtoBool(6)
public boolValue: boolean = false;
@ProtoBytes(7)
public bytesValue: Uint8Array = new Uint8Array();
@ProtoTimestamp(8)
public timestampValue: Date = new Date();
@ProtoStruct(9)
public structValue: any = {};
@ProtoFloat(10, { repeated: true })
public arrayValue: number[] = [];
constructor() {
super();
}
}
// 不完整的组件(缺少字段)
@ProtoSerializable('IncompleteComponent')
class IncompleteComponent extends Component {
@ProtoString(1)
public name: string = '';
// 故意添加没有装饰器的字段
public undecoratedField: number = 42;
constructor(name: string = '') {
super();
this.name = name;
}
}
// 有循环引用的组件
@ProtoSerializable('CircularComponent')
class CircularComponent extends Component {
@ProtoString(1)
public name: string = '';
@ProtoStruct(2)
public circular: any = null;
constructor(name: string = '') {
super();
this.name = name;
// 创建循环引用
this.circular = this;
}
}
// 没有protobuf装饰器的组件
class NonSerializableComponent extends Component {
public data: string = 'test';
serialize(): any {
return { data: this.data };
}
deserialize(data: any): void {
this.data = data.data || this.data;
}
}
// Mock protobuf.js
const mockProtobuf = {
parse: jest.fn().mockReturnValue({
root: {
lookupType: jest.fn().mockImplementation((typeName: string) => {
return {
verify: jest.fn().mockReturnValue(null),
create: jest.fn().mockImplementation((data) => data),
encode: jest.fn().mockReturnValue({
finish: jest.fn().mockReturnValue(new Uint8Array([1, 2, 3, 4]))
}),
decode: jest.fn().mockImplementation(() => ({
floatValue: 3.14,
doubleValue: 2.718,
intValue: 42,
bigIntValue: BigIntFactory.create(999),
stringValue: 'test',
boolValue: true,
bytesValue: new Uint8Array([65, 66, 67]),
timestampValue: { seconds: 1609459200, nanos: 0 },
structValue: { fields: { key: { stringValue: 'value' } } },
arrayValue: [1.1, 2.2, 3.3],
name: 'TestComponent'
})),
toObject: jest.fn().mockImplementation((message) => message)
};
})
}
})
};
describe('ProtobufSerializer边界情况测试', () => {
let serializer: ProtobufSerializer;
beforeEach(() => {
serializer = ProtobufSerializer.getInstance();
serializer.initialize(mockProtobuf);
jest.clearAllMocks();
});
describe('极值测试', () => {
it('应该处理极大值', () => {
const component = new EdgeCaseComponent();
component.floatValue = Number.MAX_VALUE;
component.doubleValue = Number.MAX_VALUE;
component.intValue = Number.MAX_SAFE_INTEGER;
component.bigIntValue = BigIntFactory.create(Number.MAX_SAFE_INTEGER);
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
expect(result.size).toBeGreaterThan(0);
});
it('应该处理极小值', () => {
const component = new EdgeCaseComponent();
component.floatValue = Number.MIN_VALUE;
component.doubleValue = Number.MIN_VALUE;
component.intValue = Number.MIN_SAFE_INTEGER;
component.bigIntValue = BigIntFactory.create(Number.MIN_SAFE_INTEGER);
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
it('应该处理特殊数值', () => {
const component = new EdgeCaseComponent();
component.floatValue = NaN;
component.doubleValue = Infinity;
component.intValue = 0;
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
});
describe('空值和undefined测试', () => {
it('应该处理null值', () => {
const component = new EdgeCaseComponent();
(component as any).stringValue = null;
(component as any).structValue = null;
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
it('应该处理undefined值', () => {
const component = new EdgeCaseComponent();
(component as any).stringValue = undefined;
(component as any).floatValue = undefined;
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
it('应该处理空数组', () => {
const component = new EdgeCaseComponent();
component.arrayValue = [];
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
});
describe('复杂数据类型测试', () => {
it('应该处理复杂对象结构', () => {
const component = new EdgeCaseComponent();
component.structValue = {
nested: {
array: [1, 2, 3],
object: { key: 'value' },
date: new Date(),
null: null,
undefined: undefined
}
};
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
it('应该处理Date对象', () => {
const component = new EdgeCaseComponent();
component.timestampValue = new Date('2021-01-01T00:00:00Z');
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
it('应该处理Uint8Array', () => {
const component = new EdgeCaseComponent();
component.bytesValue = new Uint8Array([0, 255, 128, 64]);
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
});
describe('循环引用测试', () => {
it('应该处理循环引用对象', () => {
const component = new CircularComponent('circular');
// 应该回退到JSON序列化
const result = serializer.serialize(component);
expect(result).toBeDefined();
});
});
describe('不完整组件测试', () => {
it('应该处理缺少装饰器的字段', () => {
const component = new IncompleteComponent('test');
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
expect(result.componentType).toBe('IncompleteComponent');
});
});
describe('非序列化组件测试', () => {
it('应该回退到JSON序列化', () => {
const component = new NonSerializableComponent();
const result = serializer.serialize(component);
expect(result.type).toBe('json');
expect(result.componentType).toBe('NonSerializableComponent');
expect(result.data).toEqual({ data: 'test' });
});
});
describe('批量序列化边界测试', () => {
it('应该处理空数组', () => {
const results = serializer.serializeBatch([]);
expect(results).toEqual([]);
});
it('应该处理混合组件类型', () => {
const components = [
new EdgeCaseComponent(),
new NonSerializableComponent(),
new IncompleteComponent('mixed'),
];
const results = serializer.serializeBatch(components, { continueOnError: true });
expect(results.length).toBeGreaterThanOrEqual(2);
expect(results.some(r => r.type === 'protobuf')).toBe(true);
expect(results.some(r => r.type === 'json')).toBe(true);
});
it('应该处理批量数据', () => {
const components = Array.from({ length: 50 }, () => new EdgeCaseComponent());
const results = serializer.serializeBatch(components);
expect(results).toHaveLength(50);
expect(results.every(r => r.type === 'protobuf')).toBe(true);
});
it('应该处理序列化错误', () => {
// 模拟序列化失败
const mockType = mockProtobuf.parse().root.lookupType('ecs.EdgeCaseComponent');
mockType.verify.mockReturnValue('Validation error');
const components = [new EdgeCaseComponent()];
// continueOnError = false 应该抛出异常
expect(() => {
serializer.serializeBatch(components, { continueOnError: false });
}).toThrow();
// continueOnError = true 应该回退到JSON
const results = serializer.serializeBatch(components, { continueOnError: true });
expect(results).toHaveLength(1);
expect(results[0].type).toBe('json');
});
});
describe('反序列化边界测试', () => {
it('应该处理JSON类型的反序列化', () => {
const component = new NonSerializableComponent();
const serializedData = {
type: 'json' as const,
componentType: 'NonSerializableComponent',
data: { data: 'deserialized' },
size: 100
};
serializer.deserialize(component, serializedData);
expect(component.data).toBe('deserialized');
});
it('应该优雅处理反序列化错误', () => {
const component = new EdgeCaseComponent();
const invalidData = {
type: 'protobuf' as const,
componentType: 'EdgeCaseComponent',
data: new Uint8Array([255, 255, 255, 255]),
size: 4
};
// 模拟解码失败
const mockType = mockProtobuf.parse().root.lookupType('ecs.EdgeCaseComponent');
mockType.decode.mockImplementation(() => {
throw new Error('Decode failed');
});
// 不应该抛出异常
expect(() => {
serializer.deserialize(component, invalidData);
}).not.toThrow();
});
it('应该处理缺失的proto定义', () => {
const component = new EdgeCaseComponent();
// 清除proto名称以模拟缺失情况
(component as any)._protoName = undefined;
const serializedData = {
type: 'protobuf' as const,
componentType: 'EdgeCaseComponent',
data: new Uint8Array([1, 2, 3, 4]),
size: 4
};
// 不应该抛出异常
expect(() => {
serializer.deserialize(component, serializedData);
}).not.toThrow();
});
});
describe('缓存测试', () => {
it('应该能清空所有缓存', () => {
serializer.clearAllCaches();
const stats = serializer.getStats();
expect(stats.messageTypeCacheSize).toBe(0);
expect(stats.componentDataCacheSize).toBe(0);
});
});
describe('性能选项测试', () => {
it('应该能禁用数据验证', () => {
serializer.setPerformanceOptions({ enableValidation: false });
const component = new EdgeCaseComponent();
const result = serializer.serialize(component);
expect(result.type).toBe('protobuf');
});
it('应该能禁用组件数据缓存', () => {
serializer.setPerformanceOptions({ enableComponentDataCache: false });
const component = new EdgeCaseComponent();
serializer.serialize(component);
const stats = serializer.getStats();
expect(stats.componentDataCacheSize).toBe(0);
});
});
describe('统计信息测试', () => {
it('应该返回正确的统计信息', () => {
const stats = serializer.getStats();
expect(typeof stats.registeredComponents).toBe('number');
expect(typeof stats.protobufAvailable).toBe('boolean');
expect(typeof stats.messageTypeCacheSize).toBe('number');
expect(typeof stats.componentDataCacheSize).toBe('number');
expect(typeof stats.enableComponentDataCache).toBe('boolean');
expect(typeof stats.maxCacheSize).toBe('number');
});
});
});