使用Lerna 和 monorepo管理项目结构

This commit is contained in:
YHH
2025-08-07 13:29:12 +08:00
parent 4479f0fab0
commit ea8523be35
135 changed files with 7058 additions and 372 deletions

View File

@@ -0,0 +1,47 @@
/**
* 网络同步接口
*
* 为帧同步框架提供网络状态管理
*/
export interface INetworkSyncable {
/**
* 获取网络同步状态
*
* @returns 序列化的网络状态数据
*/
getNetworkState(): Uint8Array;
/**
* 应用网络状态
*
* @param data - 网络状态数据
*/
applyNetworkState(data: Uint8Array): void;
/**
* 获取变化的字段编号列表
*
* @returns 变化字段的编号数组
*/
getDirtyFields(): number[];
/**
* 标记所有字段为干净状态
*/
markClean(): void;
/**
* 标记字段为脏状态
*
* @param fieldNumber - 字段编号
*/
markFieldDirty(fieldNumber: number): void;
/**
* 检查字段是否为脏状态
*
* @param fieldNumber - 字段编号
* @returns 是否为脏状态
*/
isFieldDirty(fieldNumber: number): boolean;
}

View File

@@ -0,0 +1,161 @@
import { Component } from '@esengine/ecs-framework';
import { INetworkSyncable } from './INetworkSyncable';
/**
* 网络组件基类
*
* 继承核心ECS的Component类添加网络同步功能。
* 用于需要网络同步的组件,提供帧同步框架所需的网络状态管理。
*
* @example
* ```typescript
* import { NetworkComponent } from '@esengine/ecs-framework-network';
* import { ProtoSerializable, ProtoFloat } from '@esengine/ecs-framework-network';
*
* @ProtoSerializable('Position')
* class PositionComponent extends NetworkComponent {
* @ProtoFloat(1)
* public x: number = 0;
*
* @ProtoFloat(2)
* public y: number = 0;
*
* constructor(x: number = 0, y: number = 0) {
* super();
* this.x = x;
* this.y = y;
* }
* }
* ```
*/
export abstract class NetworkComponent extends Component implements INetworkSyncable {
/**
* 脏字段标记集合
*
* 记录已修改但尚未同步的字段编号
*/
private _dirtyFields: Set<number> = new Set();
/**
* 字段变化时间戳
*
* 记录每个字段最后修改的时间
*/
private _fieldTimestamps: Map<number, number> = new Map();
/**
* 获取网络同步状态
*
* 序列化当前组件状态为网络传输格式
* @returns 序列化的网络状态数据
*/
public getNetworkState(): Uint8Array {
const { isProtoSerializable } = require('./Serialization/ProtobufDecorators');
const { ProtobufSerializer } = require('./Serialization/ProtobufSerializer');
if (!isProtoSerializable(this)) {
throw new Error(`组件 ${this.constructor.name} 不支持网络同步,请添加@ProtoSerializable装饰器`);
}
try {
const serializer = ProtobufSerializer.getInstance();
const serializedData = serializer.serialize(this);
return serializedData.data;
} catch (error) {
throw new Error(`获取网络状态失败: ${error}`);
}
}
/**
* 应用网络状态
*
* 从网络数据恢复组件状态
* @param data - 网络状态数据
*/
public applyNetworkState(data: Uint8Array): void {
const { isProtoSerializable } = require('./Serialization/ProtobufDecorators');
const { ProtobufSerializer } = require('./Serialization/ProtobufSerializer');
if (!isProtoSerializable(this)) {
throw new Error(`组件 ${this.constructor.name} 不支持网络同步,请添加@ProtoSerializable装饰器`);
}
try {
const serializer = ProtobufSerializer.getInstance();
const serializedData = {
type: 'protobuf' as const,
componentType: this.constructor.name,
data: data,
size: data.length
};
serializer.deserialize(this, serializedData);
// 应用后清理脏字段标记
this.markClean();
} catch (error) {
throw new Error(`应用网络状态失败: ${error}`);
}
}
/**
* 获取变化的字段编号列表
*
* @returns 变化字段的编号数组
*/
public getDirtyFields(): number[] {
return Array.from(this._dirtyFields);
}
/**
* 标记所有字段为干净状态
*
* 清除所有脏字段标记
*/
public markClean(): void {
this._dirtyFields.clear();
}
/**
* 标记字段为脏状态
*
* 用于标记字段已修改,需要网络同步
* @param fieldNumber - 字段编号
*/
public markFieldDirty(fieldNumber: number): void {
this._dirtyFields.add(fieldNumber);
this._fieldTimestamps.set(fieldNumber, Date.now());
}
/**
* 检查字段是否为脏状态
*
* @param fieldNumber - 字段编号
* @returns 是否为脏状态
*/
public isFieldDirty(fieldNumber: number): boolean {
return this._dirtyFields.has(fieldNumber);
}
/**
* 获取字段最后修改时间
*
* @param fieldNumber - 字段编号
* @returns 最后修改时间戳如果字段从未修改则返回0
*/
public getFieldTimestamp(fieldNumber: number): number {
return this._fieldTimestamps.get(fieldNumber) || 0;
}
/**
* 获取所有脏字段及其时间戳
*
* @returns 脏字段和时间戳的映射
*/
public getDirtyFieldsWithTimestamps(): Map<number, number> {
const result = new Map<number, number>();
for (const fieldNumber of this._dirtyFields) {
result.set(fieldNumber, this._fieldTimestamps.get(fieldNumber) || 0);
}
return result;
}
}

View File

@@ -0,0 +1,459 @@
/**
* Protobuf序列化装饰器
*
* 提供装饰器语法来标记组件和字段进行protobuf序列化
*/
import 'reflect-metadata';
import { Component } from '@esengine/ecs-framework';
/**
* Protobuf字段类型枚举
*/
export enum ProtoFieldType {
DOUBLE = 'double',
FLOAT = 'float',
INT32 = 'int32',
INT64 = 'int64',
UINT32 = 'uint32',
UINT64 = 'uint64',
SINT32 = 'sint32',
SINT64 = 'sint64',
FIXED32 = 'fixed32',
FIXED64 = 'fixed64',
SFIXED32 = 'sfixed32',
SFIXED64 = 'sfixed64',
BOOL = 'bool',
STRING = 'string',
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'
}
/**
* 字段同步优先级
*/
export enum FieldSyncPriority {
/** 关键字段 - 每帧必须同步 */
CRITICAL = 'critical',
/** 高优先级 - 高频同步 */
HIGH = 'high',
/** 中等优先级 - 中频同步 */
MEDIUM = 'medium',
/** 低优先级 - 低频同步 */
LOW = 'low'
}
/**
* Protobuf字段定义接口
*/
export interface ProtoFieldDefinition {
/** 字段编号 */
fieldNumber: number;
/** 字段类型 */
type: ProtoFieldType;
/** 是否为数组 */
repeated?: boolean;
/** 是否可选 */
optional?: boolean;
/** 字段名称 */
name: string;
/** 自定义类型名称 */
customTypeName?: string;
/** 枚举值映射 */
enumValues?: Record<string, number>;
/** 默认值 */
defaultValue?: any;
// 帧同步特定选项
/** 同步优先级 */
syncPriority?: FieldSyncPriority;
/** 数值精度(用于量化压缩) */
precision?: number;
/** 是否支持插值 */
interpolation?: boolean;
/** 量化位数 */
quantizationBits?: number;
/** 变化阈值(小于此值不同步) */
changeThreshold?: number;
}
/**
* 组件同步模式
*/
export enum ComponentSyncMode {
/** 完整同步 - 传输所有字段 */
FULL = 'full',
/** 增量同步 - 只传输变化字段 */
DELTA = 'delta',
/** 自适应 - 根据变化量自动选择 */
ADAPTIVE = 'adaptive'
}
/**
* Protobuf组件定义接口
*/
export interface ProtoComponentDefinition {
/** 组件名称 */
name: string;
/** 字段定义列表 */
fields: Map<string, ProtoFieldDefinition>;
/** 构造函数 */
constructor: any;
// 帧同步特定选项
/** 同步模式 */
syncMode?: ComponentSyncMode;
/** 同步频率(每秒同步次数) */
syncFrequency?: number;
/** 是否启用压缩 */
enableCompression?: boolean;
/** 网络优先级 */
networkPriority?: number;
}
/**
* Protobuf注册表
*/
export class ProtobufRegistry {
private static instance: ProtobufRegistry;
private components = new Map<string, ProtoComponentDefinition>();
public static getInstance(): ProtobufRegistry {
if (!ProtobufRegistry.instance) {
ProtobufRegistry.instance = new ProtobufRegistry();
}
return ProtobufRegistry.instance;
}
/**
* 注册组件定义
*/
public registerComponent(componentName: string, definition: ProtoComponentDefinition): void {
this.components.set(componentName, definition);
}
/**
* 获取组件定义
*/
public getComponentDefinition(componentName: string): ProtoComponentDefinition | undefined {
return this.components.get(componentName);
}
/**
* 检查组件是否支持protobuf
*/
public hasProtoDefinition(componentName: string): boolean {
return this.components.has(componentName);
}
/**
* 获取所有注册的组件
*/
public getAllComponents(): Map<string, ProtoComponentDefinition> {
return new Map(this.components);
}
/**
* 生成proto文件定义
*/
public generateProtoDefinition(): string {
let protoContent = 'syntax = "proto3";\n\n';
protoContent += 'package ecs;\n\n';
// 生成消息定义
for (const [name, definition] of this.components) {
protoContent += `message ${name} {\n`;
// 按字段编号排序
const sortedFields = Array.from(definition.fields.values())
.sort((a, b) => a.fieldNumber - b.fieldNumber);
for (const field of sortedFields) {
let fieldDef = ' ';
if (field.repeated) {
fieldDef += 'repeated ';
} else if (field.optional) {
fieldDef += 'optional ';
}
fieldDef += `${field.type} ${field.name} = ${field.fieldNumber};\n`;
protoContent += fieldDef;
}
protoContent += '}\n\n';
}
return protoContent;
}
}
/**
* 组件同步选项接口
*/
export interface ComponentSyncOptions {
/** 同步模式 */
syncMode?: ComponentSyncMode;
/** 同步频率(每秒同步次数) */
syncFrequency?: number;
/** 是否启用压缩 */
enableCompression?: boolean;
/** 网络优先级1-10数字越大优先级越高 */
networkPriority?: number;
}
/**
* ProtoSerializable 组件装饰器
*
* 标记组件支持protobuf序列化专为帧同步框架优化
* @param protoName protobuf消息名称默认使用类名
* @param options 同步选项
* @example
* ```typescript
* @ProtoSerializable('Position', {
* syncMode: ComponentSyncMode.DELTA,
* syncFrequency: 30,
* networkPriority: 8
* })
* class PositionComponent extends Component {
* // 组件实现
* }
* ```
*/
export function ProtoSerializable(protoName?: string, options?: ComponentSyncOptions) {
return function <T extends { new(...args: any[]): Component }>(constructor: T) {
const componentName = protoName || constructor.name;
const registry = ProtobufRegistry.getInstance();
// 获取字段定义
const fields = (constructor.prototype._protoFields as Map<string, ProtoFieldDefinition>)
|| new Map<string, ProtoFieldDefinition>();
// 注册组件定义
registry.registerComponent(componentName, {
name: componentName,
fields: fields,
constructor: constructor,
syncMode: options?.syncMode || ComponentSyncMode.FULL,
syncFrequency: options?.syncFrequency || 30,
enableCompression: options?.enableCompression || true,
networkPriority: options?.networkPriority || 5
});
// 标记组件支持protobuf
(constructor.prototype._isProtoSerializable = true);
(constructor.prototype._protoName = componentName);
return constructor;
};
}
/**
* 字段同步选项接口
*/
export interface FieldSyncOptions {
/** 是否为数组 */
repeated?: boolean;
/** 是否可选 */
optional?: boolean;
/** 自定义类型名称 */
customTypeName?: string;
/** 枚举值映射 */
enumValues?: Record<string, number>;
/** 默认值 */
defaultValue?: any;
// 帧同步特定选项
/** 同步优先级 */
syncPriority?: FieldSyncPriority;
/** 数值精度(用于量化压缩) */
precision?: number;
/** 是否支持插值 */
interpolation?: boolean;
/** 量化位数 */
quantizationBits?: number;
/** 变化阈值(小于此值不同步) */
changeThreshold?: number;
}
/**
* ProtoField 字段装饰器
*
* 标记字段参与protobuf序列化针对帧同步进行优化
* @param fieldNumber protobuf字段编号必须唯一且大于0
* @param type 字段类型,默认自动推断
* @param options 字段选项
* @example
* ```typescript
* class PositionComponent extends Component {
* @ProtoField(1, ProtoFieldType.FLOAT, {
* syncPriority: FieldSyncPriority.CRITICAL,
* precision: 0.01,
* interpolation: true
* })
* public x: number = 0;
*
* @ProtoField(2, ProtoFieldType.FLOAT, {
* syncPriority: FieldSyncPriority.CRITICAL,
* precision: 0.01,
* interpolation: true
* })
* public y: number = 0;
* }
* ```
*/
export function ProtoField(
fieldNumber: number,
type?: ProtoFieldType,
options?: FieldSyncOptions
) {
return function (target: any, propertyKey: string) {
// 验证字段编号
if (fieldNumber <= 0) {
throw new Error(`ProtoField: 字段编号必须大于0当前值: ${fieldNumber}`);
}
// 初始化字段集合
if (!target._protoFields) {
target._protoFields = new Map<string, ProtoFieldDefinition>();
}
// 自动推断类型
let inferredType = type;
if (!inferredType) {
const designType = Reflect.getMetadata?.('design:type', target, propertyKey);
inferredType = inferProtoType(designType);
}
// 检查字段编号冲突
for (const [key, field] of target._protoFields) {
if (field.fieldNumber === fieldNumber && key !== propertyKey) {
throw new Error(`ProtoField: 字段编号 ${fieldNumber} 已被字段 ${key} 使用`);
}
}
// 添加字段定义
target._protoFields.set(propertyKey, {
fieldNumber,
type: inferredType || ProtoFieldType.STRING,
repeated: options?.repeated || false,
optional: options?.optional || false,
name: propertyKey,
customTypeName: options?.customTypeName,
enumValues: options?.enumValues,
defaultValue: options?.defaultValue,
// 帧同步特定选项
syncPriority: options?.syncPriority || FieldSyncPriority.MEDIUM,
precision: options?.precision,
interpolation: options?.interpolation || false,
quantizationBits: options?.quantizationBits,
changeThreshold: options?.changeThreshold || 0
});
};
}
/**
* 自动推断protobuf类型
*/
function inferProtoType(jsType: any): ProtoFieldType {
if (!jsType) return ProtoFieldType.STRING;
switch (jsType) {
case Number:
return ProtoFieldType.DOUBLE;
case Boolean:
return ProtoFieldType.BOOL;
case 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:
return ProtoFieldType.STRING;
}
}
/**
* 便捷装饰器 - 常用类型
*/
export const ProtoInt32 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.INT32, options);
export const ProtoFloat = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.FLOAT, options);
export const ProtoString = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
ProtoField(fieldNumber, ProtoFieldType.STRING, options);
export const ProtoBool = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
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序列化
*/
export function isProtoSerializable(component: Component): boolean {
return !!(component as any)._isProtoSerializable;
}
/**
* 获取组件的protobuf名称
*/
export function getProtoName(component: Component): string | undefined {
return (component as any)._protoName;
}

View File

@@ -0,0 +1,693 @@
/**
* Protobuf序列化器
*
* 处理组件的protobuf序列化和反序列化
*/
import { Component } from '@esengine/ecs-framework';
import { BigIntFactory } from '@esengine/ecs-framework';
import {
ProtobufRegistry,
ProtoComponentDefinition,
ProtoFieldDefinition,
ProtoFieldType,
isProtoSerializable,
getProtoName
} from './ProtobufDecorators';
import { SerializedData } from './SerializationTypes';
/**
* Protobuf序列化器
*/
export class ProtobufSerializer {
private registry: ProtobufRegistry;
private static instance: ProtobufSerializer;
/** protobuf.js库实例 */
private protobuf: any = null;
private root: any = null;
/** MessageType缓存映射表 */
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 enableComponentDataCache: boolean = true;
private constructor() {
this.registry = ProtobufRegistry.getInstance();
this.initializeProtobuf();
}
/**
* 设置性能选项
* @param options 性能配置选项
* @param options.enableValidation 是否启用数据验证
* @param options.enableComponentDataCache 是否启用组件数据缓存
* @param options.maxCacheSize 最大缓存大小
* @param options.clearCache 是否清空消息类型缓存
* @param options.clearAllCaches 是否清空所有缓存
*/
public setPerformanceOptions(options: {
enableValidation?: boolean;
enableComponentDataCache?: boolean;
maxCacheSize?: number;
clearCache?: boolean;
clearAllCaches?: boolean;
}): void {
if (options.enableValidation !== undefined) {
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) {
this.messageTypeCache.clear();
}
if (options.clearAllCaches) {
this.clearAllCaches();
}
}
/**
* 清空所有缓存
*/
public clearAllCaches(): void {
this.messageTypeCache.clear();
this.componentDataCache.clear();
this.cacheAccessCount.clear();
}
/**
* 自动初始化protobuf支持
*/
private async initializeProtobuf(): Promise<void> {
try {
// 动态导入protobufjs
this.protobuf = await import('protobufjs');
this.buildProtoDefinitions();
console.log('[ProtobufSerializer] Protobuf支持已启用');
} catch (error) {
throw new Error('[ProtobufSerializer] 无法加载protobufjs: ' + error);
}
}
public static getInstance(): ProtobufSerializer {
if (!ProtobufSerializer.instance) {
ProtobufSerializer.instance = new ProtobufSerializer();
}
return ProtobufSerializer.instance;
}
/**
* 手动初始化protobuf.js库
* @param protobufJs protobuf.js库实例
*/
public initialize(protobufJs: any): void {
this.protobuf = protobufJs;
this.buildProtoDefinitions();
console.log('[ProtobufSerializer] Protobuf支持已手动启用');
}
/**
* 序列化组件
* @param component 要序列化的组件
* @returns 序列化数据
*/
public serialize(component: Component): SerializedData {
const componentType = component.constructor.name;
// 检查是否支持protobuf序列化
if (!isProtoSerializable(component)) {
throw new Error(`组件 ${componentType} 不支持protobuf序列化请添加@ProtoSerializable装饰器`);
}
const protoName = getProtoName(component);
if (!protoName) {
throw new Error(`组件 ${componentType} 未设置protobuf名称`);
}
const definition = this.registry.getComponentDefinition(protoName);
if (!definition) {
throw new Error(`未找到组件定义: ${protoName}`);
}
// 获取protobuf消息类型
const MessageType = this.getMessageType(protoName);
if (!MessageType) {
throw new Error(`未找到消息类型: ${protoName}`);
}
// 构建protobuf数据对象
const protoData = this.buildProtoData(component, definition);
// 数据验证
if (this.enableValidation) {
const error = MessageType.verify(protoData);
if (error) {
throw new Error(`数据验证失败: ${error}`);
}
}
// 创建消息并编码
const message = MessageType.create(protoData);
const buffer = MessageType.encode(message).finish();
return {
type: 'protobuf',
componentType: componentType,
data: buffer,
size: buffer.length
};
}
/**
* 反序列化组件
* @param component 目标组件实例
* @param serializedData 序列化数据
*/
public deserialize(component: Component, serializedData: SerializedData): void {
if (serializedData.type !== 'protobuf') {
throw new Error(`不支持的序列化类型: ${serializedData.type}`);
}
const protoName = getProtoName(component);
if (!protoName) {
throw new Error(`组件 ${component.constructor.name} 未设置protobuf名称`);
}
const MessageType = this.getMessageType(protoName);
if (!MessageType) {
throw new Error(`未找到消息类型: ${protoName}`);
}
// 解码消息
const message = MessageType.decode(serializedData.data);
const data = MessageType.toObject(message);
// 应用数据到组件
this.applyDataToComponent(component, data);
}
/**
* 检查组件是否支持protobuf序列化
*/
public canSerialize(component: Component): boolean {
if (!this.protobuf) return false;
return isProtoSerializable(component);
}
/**
* 批量序列化组件
* @param components 要序列化的组件数组
* @param options 批量序列化选项
* @param options.continueOnError 遇到错误时是否继续处理
* @param options.maxBatchSize 最大批次大小
* @returns 序列化结果数组
*/
public serializeBatch(
components: Component[],
options?: {
continueOnError?: boolean;
maxBatchSize?: number;
}
): 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 = this.groupComponentsByType(components, continueOnError, errors);
// 按组分别序列化
for (const [protoName, groupComponents] of componentGroups) {
const definition = this.registry.getComponentDefinition(protoName);
const MessageType = this.getMessageType(protoName);
if (!definition || !MessageType) {
const error = new Error(`[ProtobufSerializer] 组件类型 ${protoName} 未正确注册`);
if (continueOnError) {
errors.push(error);
continue;
} else {
throw error;
}
}
// 预编译消息类型和字段定义
const compiledType = this.getCompiledMessageType(protoName, definition, MessageType);
for (const component of groupComponents) {
try {
const result = this.serializeSingleComponent(component, definition, compiledType);
results.push(result);
} catch (error) {
if (continueOnError) {
errors.push(error instanceof Error ? error : new Error(String(error)));
} else {
throw error;
}
}
}
}
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;
}
/**
* 获取序列化统计信息
*/
public getStats(): {
registeredComponents: number;
protobufAvailable: boolean;
messageTypeCacheSize: number;
componentDataCacheSize: number;
enableComponentDataCache: boolean;
maxCacheSize: number;
} {
return {
registeredComponents: this.registry.getAllComponents().size,
protobufAvailable: !!this.protobuf,
messageTypeCacheSize: this.messageTypeCache.size,
componentDataCacheSize: this.componentDataCache.size,
enableComponentDataCache: this.enableComponentDataCache,
maxCacheSize: this.maxCacheSize
};
}
/**
* 构建protobuf数据对象
*/
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 = {};
for (const [propertyName, fieldDef] of definition.fields) {
const value = (component as any)[propertyName];
if (value !== undefined && value !== null) {
data[fieldDef.name] = this.convertValueToProtoType(value, fieldDef);
}
}
// 缓存结果,仅在启用且数据较小时缓存
if (this.enableComponentDataCache && JSON.stringify(data).length < 1000) {
this.setCacheWithLRU(cacheKey, data);
}
return data;
}
/**
* 转换值到protobuf类型
*/
private convertValueToProtoType(value: any, fieldDef: ProtoFieldDefinition): any {
if (fieldDef.repeated && Array.isArray(value)) {
return value.map(v => this.convertSingleValue(v, fieldDef.type));
}
return this.convertSingleValue(value, fieldDef.type);
}
/**
* 转换单个值为protobuf类型
*/
private convertSingleValue(value: any, type: ProtoFieldType): any {
switch (type) {
case ProtoFieldType.INT32:
case ProtoFieldType.UINT32:
case ProtoFieldType.SINT32:
case ProtoFieldType.FIXED32:
case ProtoFieldType.SFIXED32:
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.DOUBLE:
return typeof value === 'number' ? value : (parseFloat(value) || 0);
case ProtoFieldType.BOOL:
return typeof value === 'boolean' ? value : Boolean(value);
case ProtoFieldType.STRING:
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:
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) };
}
/**
* 应用数据到组件
*/
private applyDataToComponent(component: Component, data: any): void {
const protoName = getProtoName(component);
if (!protoName) return;
const definition = this.registry.getComponentDefinition(protoName);
if (!definition) return;
for (const [propertyName, fieldDef] of definition.fields) {
const value = data[fieldDef.name];
if (value !== undefined) {
(component as any)[propertyName] = value;
}
}
}
/**
* 构建protobuf定义
*/
private buildProtoDefinitions(): void {
if (!this.protobuf) return;
try {
const protoDefinition = this.registry.generateProtoDefinition();
this.root = this.protobuf.parse(protoDefinition).root;
// 清空缓存schema已更新
this.messageTypeCache.clear();
} catch (error) {
console.error('[ProtobufSerializer] 构建protobuf定义失败:', error);
}
}
/**
* 获取消息类型并缓存结果
*/
private getMessageType(typeName: string): any {
if (!this.root) return null;
// 检查缓存
const fullTypeName = `ecs.${typeName}`;
if (this.messageTypeCache.has(fullTypeName)) {
return this.messageTypeCache.get(fullTypeName);
}
try {
const messageType = this.root.lookupType(fullTypeName);
// 缓存结果
this.messageTypeCache.set(fullTypeName, messageType);
return messageType;
} catch (error) {
console.warn(`[ProtobufSerializer] 未找到消息类型: ${fullTypeName}`);
// 缓存null结果以避免重复查找
this.messageTypeCache.set(fullTypeName, null);
return null;
}
}
/**
* 生成组件缓存键
*/
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

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

View File

@@ -0,0 +1,41 @@
/**
* 网络插件序列化系统导出
*
* 统一导出所有protobuf序列化相关的类型、装饰器和工具函数
*/
// Protobuf序列化系统用于帧同步框架
export {
ProtobufSerializer
} from './ProtobufSerializer';
export {
ProtoSerializable,
ProtoField,
ProtoFloat,
ProtoInt32,
ProtoString,
ProtoBool,
ProtoBytes,
ProtoTimestamp,
ProtoDouble,
ProtoInt64,
ProtoStruct,
ProtoMessage,
ProtoEnum,
isProtoSerializable,
getProtoName,
ProtobufRegistry,
ProtoComponentDefinition,
ProtoFieldDefinition,
ProtoFieldType,
FieldSyncPriority,
ComponentSyncMode,
ComponentSyncOptions,
FieldSyncOptions
} from './ProtobufDecorators';
export {
SerializedData
} from './SerializationTypes';

View File

@@ -0,0 +1,92 @@
/**
* 可序列化接口
*
* 实现此接口的类可以被快照系统序列化和反序列化
*/
export interface ISnapshotable {
/**
* 序列化对象到可传输的数据格式
*
* @returns 序列化后的数据
*/
serialize(): any;
/**
* 从序列化数据恢复对象状态
*
* @param data - 序列化数据
*/
deserialize(data: any): void;
}
/**
* 快照配置接口
*/
export interface SnapshotConfig {
/** 是否包含在快照中 */
includeInSnapshot: boolean;
/** 压缩级别 (0-9) */
compressionLevel: number;
/** 同步优先级 (数字越大优先级越高) */
syncPriority: number;
/** 是否启用增量快照 */
enableIncremental: boolean;
}
/**
* 组件快照数据
*/
export interface ComponentSnapshot {
/** 组件类型名称 */
type: string;
/** 组件ID */
id: number;
/** 序列化数据 */
data: any;
/** 是否启用 */
enabled: boolean;
/** 快照配置 */
config?: SnapshotConfig;
}
/**
* 实体快照数据
*/
export interface EntitySnapshot {
/** 实体ID */
id: number;
/** 实体名称 */
name: string;
/** 是否启用 */
enabled: boolean;
/** 是否激活 */
active: boolean;
/** 标签 */
tag: number;
/** 更新顺序 */
updateOrder: number;
/** 组件快照列表 */
components: ComponentSnapshot[];
/** 子实体ID列表 */
children: number[];
/** 父实体ID */
parent?: number;
/** 快照时间戳 */
timestamp: number;
}
/**
* 场景快照数据
*/
export interface SceneSnapshot {
/** 实体快照列表 */
entities: EntitySnapshot[];
/** 快照时间戳 */
timestamp: number;
/** 框架版本 */
version: string;
/** 快照类型 */
type: 'full' | 'incremental';
/** 基础快照ID增量快照使用 */
baseSnapshotId?: string;
}

View File

@@ -0,0 +1,255 @@
import { Component } from '@esengine/ecs-framework';
import { ISnapshotable, SnapshotConfig } from './ISnapshotable';
/**
* 快照扩展接口
*
* 为Component基类提供快照功能的扩展接口
*/
export interface ISnapshotExtension {
/** 快照配置 */
snapshotConfig?: SnapshotConfig;
/** 序列化方法 */
serialize?(): any;
/** 反序列化方法 */
deserialize?(data: any): void;
/** 变化检测方法 */
hasChanged?(baseData: any): boolean;
}
/**
* 快照装饰器
*
* 用于标记组件属性为可序列化
*/
export function Serializable(config?: Partial<SnapshotConfig>) {
return function (target: any, propertyKey: string) {
// 确保组件有快照配置
if (!target.snapshotConfig) {
target.snapshotConfig = {
includeInSnapshot: true,
compressionLevel: 0,
syncPriority: 5,
enableIncremental: true
};
}
// 标记属性为可序列化
if (!target._serializableProperties) {
target._serializableProperties = new Set<string>();
}
target._serializableProperties.add(propertyKey);
// 应用配置
if (config) {
Object.assign(target.snapshotConfig, config);
}
};
}
/**
* 快照配置装饰器
*
* 用于配置组件的快照行为
*/
export function SnapshotConfigDecorator(config: SnapshotConfig) {
return function (target: any) {
target.prototype.snapshotConfig = config;
};
}
/**
* 快照扩展工具类
*/
export class SnapshotExtension {
/**
* 为组件添加快照支持
*
* @param component - 目标组件
* @param config - 快照配置
*/
public static enableSnapshot(component: Component, config?: Partial<SnapshotConfig>): void {
const defaultConfig: SnapshotConfig = {
includeInSnapshot: true,
compressionLevel: 0,
syncPriority: 5,
enableIncremental: true
};
(component as any).snapshotConfig = { ...defaultConfig, ...config };
}
/**
* 禁用组件的快照功能
*
* @param component - 目标组件
*/
public static disableSnapshot(component: Component): void {
if ((component as any).snapshotConfig) {
(component as any).snapshotConfig.includeInSnapshot = false;
}
}
/**
* 检查组件是否支持快照
*
* @param component - 目标组件
* @returns 是否支持快照
*/
public static isSnapshotable(component: Component): boolean {
const config = (component as any).snapshotConfig;
return config && config.includeInSnapshot;
}
/**
* 获取组件的可序列化属性
*
* @param component - 目标组件
* @returns 可序列化属性列表
*/
public static getSerializableProperties(component: Component): string[] {
const properties = (component as any)._serializableProperties;
if (properties) {
return Array.from(properties);
}
// 如果没有标记,返回所有公共属性
const publicProperties: string[] = [];
for (const key in component) {
if (component.hasOwnProperty(key) &&
typeof (component as any)[key] !== 'function' &&
key !== 'id' &&
key !== 'entity' &&
key !== '_enabled' &&
key !== '_updateOrder') {
publicProperties.push(key);
}
}
return publicProperties;
}
/**
* 创建组件的默认序列化方法
*
* @param component - 目标组件
* @returns 序列化数据
*/
public static createDefaultSerializer(component: Component): () => any {
return function() {
const data: any = {};
const properties = SnapshotExtension.getSerializableProperties(component);
for (const prop of properties) {
const value = (component as any)[prop];
if (value !== undefined && value !== null) {
data[prop] = value;
}
}
return data;
};
}
/**
* 创建组件的默认反序列化方法
*
* @param component - 目标组件
* @returns 反序列化函数
*/
public static createDefaultDeserializer(component: Component): (data: any) => void {
return function(data: any) {
const properties = SnapshotExtension.getSerializableProperties(component);
for (const prop of properties) {
if (data.hasOwnProperty(prop)) {
(component as any)[prop] = data[prop];
}
}
};
}
/**
* 创建简单的变化检测方法
*
* @param component - 目标组件
* @returns 变化检测函数
*/
public static createSimpleChangeDetector(component: Component): (baseData: any) => boolean {
return function(baseData: any) {
const properties = SnapshotExtension.getSerializableProperties(component);
for (const prop of properties) {
const currentValue = (component as any)[prop];
const baseValue = baseData[prop];
if (currentValue !== baseValue) {
return true;
}
}
return false;
};
}
/**
* 创建深度变化检测方法
*
* @param component - 目标组件
* @returns 变化检测函数
*/
public static createDeepChangeDetector(component: Component): (baseData: any) => boolean {
return function(baseData: any) {
const properties = SnapshotExtension.getSerializableProperties(component);
for (const prop of properties) {
const currentValue = (component as any)[prop];
const baseValue = baseData[prop];
if (SnapshotExtension.deepCompare(currentValue, baseValue)) {
return true;
}
}
return false;
};
}
/**
* 深度比较两个值
*/
private static deepCompare(value1: any, value2: any): boolean {
if (value1 === value2) return false;
if (typeof value1 !== typeof value2) return true;
if (value1 === null || value2 === null) return value1 !== value2;
if (typeof value1 !== 'object') return value1 !== value2;
if (Array.isArray(value1) !== Array.isArray(value2)) return true;
if (Array.isArray(value1)) {
if (value1.length !== value2.length) return true;
for (let i = 0; i < value1.length; i++) {
if (this.deepCompare(value1[i], value2[i])) return true;
}
return false;
}
const keys1 = Object.keys(value1);
const keys2 = Object.keys(value2);
if (keys1.length !== keys2.length) return true;
for (const key of keys1) {
if (!keys2.includes(key)) return true;
if (this.deepCompare(value1[key], value2[key])) return true;
}
return false;
}
}

View File

@@ -0,0 +1,608 @@
import { Entity, Component } from '@esengine/ecs-framework';
import { ISnapshotable, SceneSnapshot, EntitySnapshot, ComponentSnapshot, SnapshotConfig } from './ISnapshotable';
import { ProtobufSerializer } from '../Serialization/ProtobufSerializer';
import { SerializedData } from '../Serialization/SerializationTypes';
import { isProtoSerializable } from '../Serialization/ProtobufDecorators';
/**
* 快照管理器
*
* 负责创建和管理ECS系统的快照支持完整快照和增量快照
* 使用protobuf序列化
*/
export class SnapshotManager {
/** 默认快照配置 */
private static readonly DEFAULT_CONFIG: SnapshotConfig = {
includeInSnapshot: true,
compressionLevel: 0,
syncPriority: 5,
enableIncremental: true
};
/** 框架版本 */
private readonly version: string = '1.0.0';
/** 最后快照时间戳 */
private lastSnapshotTime: number = 0;
/** 快照缓存 */
private snapshotCache = new Map<string, SceneSnapshot>();
/** 最大缓存数量 */
private maxCacheSize: number = 10;
/** Protobuf序列化器 */
private protobufSerializer: ProtobufSerializer;
/**
* 构造函数
*/
constructor() {
this.protobufSerializer = ProtobufSerializer.getInstance();
}
/**
* 创建场景快照
*
* @param entities - 实体列表
* @param type - 快照类型
* @returns 场景快照
*/
public createSceneSnapshot(entities: Entity[], type: 'full' | 'incremental' = 'full'): SceneSnapshot {
const entitySnapshots: EntitySnapshot[] = [];
const sortedEntities = entities.sort((a, b) => a.id - b.id);
for (const entity of sortedEntities) {
if (entity.isDestroyed) continue;
const entitySnapshot = this.createEntitySnapshot(entity);
if (entitySnapshot) {
entitySnapshots.push(entitySnapshot);
}
}
const snapshot: SceneSnapshot = {
entities: entitySnapshots,
timestamp: Date.now(),
version: this.version,
type: type
};
this.lastSnapshotTime = snapshot.timestamp;
return snapshot;
}
/**
* 从快照恢复场景
*
* @param snapshot - 场景快照
* @param targetEntities - 目标实体列表(用于增量恢复)
* @param createMissingEntities - 是否创建缺失的实体
*/
public restoreFromSnapshot(snapshot: SceneSnapshot, targetEntities?: Entity[], createMissingEntities: boolean = false): Entity[] {
if (snapshot.type === 'incremental' && targetEntities) {
return this.restoreIncrementalSnapshot(snapshot, targetEntities);
} else {
return this.restoreFullSnapshot(snapshot, targetEntities, createMissingEntities);
}
}
/**
* 从快照恢复实体列表
*
* @param snapshot - 场景快照
* @param targetEntities - 目标实体列表
* @param createMissingEntities - 是否创建缺失的实体
*/
public restoreEntitiesFromSnapshot(snapshot: SceneSnapshot, targetEntities: Entity[], createMissingEntities: boolean = false): Entity[] {
const restoredEntities: Entity[] = [];
const targetEntityMap = new Map<number, Entity>();
for (const entity of targetEntities) {
targetEntityMap.set(entity.id, entity);
}
for (const entitySnapshot of snapshot.entities) {
let targetEntity = targetEntityMap.get(entitySnapshot.id);
if (!targetEntity && createMissingEntities) {
// 创建缺失的实体
const newEntity = this.createEntityFromSnapshot(entitySnapshot);
if (newEntity) {
restoredEntities.push(newEntity);
}
} else if (targetEntity) {
// 恢复现有实体
this.restoreEntityFromSnapshot(targetEntity, entitySnapshot);
restoredEntities.push(targetEntity);
}
}
return restoredEntities;
}
/**
* 从快照创建实体
*/
private createEntityFromSnapshot(entitySnapshot: EntitySnapshot): Entity | null {
try {
const entity = new Entity(entitySnapshot.name, entitySnapshot.id);
// 设置基本属性
entity.enabled = entitySnapshot.enabled;
entity.active = entitySnapshot.active;
entity.tag = entitySnapshot.tag;
entity.updateOrder = entitySnapshot.updateOrder;
// 创建组件
for (const componentSnapshot of entitySnapshot.components) {
this.createComponentFromSnapshot(entity, componentSnapshot);
}
return entity;
} catch (error) {
console.error(`[SnapshotManager] 创建实体失败: ${entitySnapshot.name}`, error);
return null;
}
}
/**
* 从快照创建组件
*/
private createComponentFromSnapshot(entity: Entity, componentSnapshot: ComponentSnapshot): void {
try {
// 尝试获取组件构造函数
const componentType = this.getComponentType(componentSnapshot.type);
if (!componentType) {
console.warn(`[SnapshotManager] 未知组件类型: ${componentSnapshot.type}`);
return;
}
// 创建组件实例
const component = entity.createComponent(componentType);
// 恢复组件启用状态
component.enabled = componentSnapshot.enabled;
// 恢复组件数据
const serializedData = componentSnapshot.data as SerializedData;
if (!isProtoSerializable(component)) {
throw new Error(`[SnapshotManager] 组件 ${component.constructor.name} 不支持protobuf反序列化`);
}
this.protobufSerializer.deserialize(component, serializedData);
} catch (error) {
console.error(`[SnapshotManager] 创建组件失败: ${componentSnapshot.type}`, error);
}
}
/**
* 获取组件类型
*/
private getComponentType(typeName: string): any {
// TODO: 实现组件类型注册表或者使用其他方式获取组件类型
return null;
}
/**
* 创建快速快照(跳过变化检测)
*
* @param entities - 实体列表
* @returns 场景快照
*/
public createQuickSnapshot(entities: Entity[]): SceneSnapshot {
return this.createSceneSnapshot(entities, 'full');
}
/**
* 创建增量快照
*
* @param entities - 实体列表
* @param baseSnapshot - 基础快照
* @param enableChangeDetection - 是否启用变化检测
* @returns 增量快照
*/
public createIncrementalSnapshot(entities: Entity[], baseSnapshot: SceneSnapshot, enableChangeDetection: boolean = true): SceneSnapshot {
const incrementalEntities: EntitySnapshot[] = [];
const baseEntityMap = new Map<number, EntitySnapshot>();
for (const entity of baseSnapshot.entities) {
baseEntityMap.set(entity.id, entity);
}
for (const entity of entities) {
if (entity.isDestroyed) continue;
const baseEntity = baseEntityMap.get(entity.id);
if (!baseEntity) {
const entitySnapshot = this.createEntitySnapshot(entity);
if (entitySnapshot) {
incrementalEntities.push(entitySnapshot);
}
} else if (enableChangeDetection) {
const changedComponents = this.getChangedComponents(entity, baseEntity);
if (this.hasEntityStructureChanged(entity, baseEntity) || changedComponents.length > 0) {
const incrementalEntitySnapshot = this.createIncrementalEntitySnapshot(entity, baseEntity, changedComponents);
if (incrementalEntitySnapshot) {
incrementalEntities.push(incrementalEntitySnapshot);
}
}
}
}
return {
entities: incrementalEntities,
timestamp: Date.now(),
version: this.version,
type: 'incremental',
baseSnapshotId: this.generateSnapshotId(baseSnapshot)
};
}
/**
* 缓存快照
*
* @param id - 快照ID
* @param snapshot - 快照数据
*/
public cacheSnapshot(id: string, snapshot: SceneSnapshot): void {
// 清理过期缓存
if (this.snapshotCache.size >= this.maxCacheSize) {
const oldestKey = this.snapshotCache.keys().next().value;
if (oldestKey) {
this.snapshotCache.delete(oldestKey);
}
}
this.snapshotCache.set(id, snapshot);
}
/**
* 获取缓存的快照
*
* @param id - 快照ID
* @returns 快照数据或undefined
*/
public getCachedSnapshot(id: string): SceneSnapshot | undefined {
return this.snapshotCache.get(id);
}
/**
* 清空快照缓存
*/
public clearCache(): void {
this.snapshotCache.clear();
}
/**
* 清空所有缓存
*/
public clearAllCaches(): void {
this.snapshotCache.clear();
}
/**
* 获取缓存统计信息
*/
public getCacheStats(): {
snapshotCacheSize: number;
protobufStats?: {
registeredComponents: number;
protobufAvailable: boolean;
};
} {
const stats: any = {
snapshotCacheSize: this.snapshotCache.size
};
if (this.protobufSerializer) {
stats.protobufStats = this.protobufSerializer.getStats();
}
return stats;
}
/**
* 手动初始化protobuf支持可选通常会自动初始化
*
* @param protobufJs - protobuf.js库实例
*/
public initializeProtobuf(protobufJs: any): void {
if (this.protobufSerializer) {
this.protobufSerializer.initialize(protobufJs);
console.log('[SnapshotManager] Protobuf支持已手动启用');
}
}
/**
* 创建实体快照
*/
private createEntitySnapshot(entity: Entity): EntitySnapshot | null {
const componentSnapshots: ComponentSnapshot[] = [];
for (const component of entity.components) {
const componentSnapshot = this.createComponentSnapshot(component);
if (componentSnapshot) {
componentSnapshots.push(componentSnapshot);
}
}
return {
id: entity.id,
name: entity.name,
enabled: entity.enabled,
active: entity.active,
tag: entity.tag,
updateOrder: entity.updateOrder,
components: componentSnapshots,
children: entity.children.map(child => child.id),
parent: entity.parent?.id || undefined,
timestamp: Date.now()
};
}
/**
* 创建组件快照
*
* 使用protobuf序列化
*/
private createComponentSnapshot(component: Component): ComponentSnapshot | null {
if (!this.isComponentSnapshotable(component)) {
return null;
}
if (!isProtoSerializable(component)) {
throw new Error(`[SnapshotManager] 组件 ${component.constructor.name} 不支持protobuf序列化请添加@ProtoSerializable装饰器`);
}
const serializedData = this.protobufSerializer.serialize(component);
return {
type: component.constructor.name,
id: component.id,
data: serializedData,
enabled: component.enabled,
config: this.getComponentSnapshotConfig(component)
};
}
/**
* 检查组件是否支持快照
*/
private isComponentSnapshotable(component: Component): boolean {
// 检查是否有快照配置
const config = this.getComponentSnapshotConfig(component);
return config.includeInSnapshot;
}
/**
* 获取组件快照配置
*/
private getComponentSnapshotConfig(component: Component): SnapshotConfig {
// 检查组件是否有自定义配置
if ((component as any).snapshotConfig) {
return { ...SnapshotManager.DEFAULT_CONFIG, ...(component as any).snapshotConfig };
}
return SnapshotManager.DEFAULT_CONFIG;
}
/**
* 恢复完整快照
*/
private restoreFullSnapshot(snapshot: SceneSnapshot, targetEntities?: Entity[], createMissingEntities: boolean = false): Entity[] {
if (targetEntities && createMissingEntities) {
return this.restoreEntitiesFromSnapshot(snapshot, targetEntities, true);
} else if (targetEntities) {
return this.restoreEntitiesFromSnapshot(snapshot, targetEntities, false);
} else {
const restoredEntities: Entity[] = [];
for (const entitySnapshot of snapshot.entities) {
const entity = this.createEntityFromSnapshot(entitySnapshot);
if (entity) {
restoredEntities.push(entity);
}
}
return restoredEntities;
}
}
/**
* 恢复增量快照
*/
private restoreIncrementalSnapshot(snapshot: SceneSnapshot, targetEntities: Entity[]): Entity[] {
const restoredEntities: Entity[] = [];
const targetEntityMap = new Map<number, Entity>();
for (const entity of targetEntities) {
targetEntityMap.set(entity.id, entity);
}
for (const entitySnapshot of snapshot.entities) {
const targetEntity = targetEntityMap.get(entitySnapshot.id);
if (targetEntity) {
this.restoreEntityFromSnapshot(targetEntity, entitySnapshot);
restoredEntities.push(targetEntity);
}
}
return restoredEntities;
}
/**
* 从快照恢复实体
*/
private restoreEntityFromSnapshot(entity: Entity, entitySnapshot: EntitySnapshot): void {
// 恢复实体基本属性
entity.enabled = entitySnapshot.enabled;
entity.active = entitySnapshot.active;
entity.tag = entitySnapshot.tag;
entity.updateOrder = entitySnapshot.updateOrder;
// 恢复组件
for (const componentSnapshot of entitySnapshot.components) {
this.restoreComponentFromSnapshot(entity, componentSnapshot);
}
}
/**
* 从快照恢复组件
*
* 使用protobuf反序列化
*/
private restoreComponentFromSnapshot(entity: Entity, componentSnapshot: ComponentSnapshot): void {
// 查找现有组件
let component = entity.getComponent(componentSnapshot.type as any);
if (!component) {
// 组件不存在,需要创建
console.warn(`[SnapshotManager] 组件 ${componentSnapshot.type} 不存在于实体 ${entity.name},无法恢复`);
return;
}
// 恢复组件启用状态
component.enabled = componentSnapshot.enabled;
// 恢复组件数据
const serializedData = componentSnapshot.data as SerializedData;
if (!isProtoSerializable(component)) {
throw new Error(`[SnapshotManager] 组件 ${component.constructor.name} 不支持protobuf反序列化`);
}
this.protobufSerializer.deserialize(component, serializedData);
}
/**
* 检查实体结构是否发生变化(组件数量、类型等)
*/
private hasEntityStructureChanged(entity: Entity, baseSnapshot: EntitySnapshot): boolean {
// 检查基本属性变化
if (entity.enabled !== baseSnapshot.enabled ||
entity.active !== baseSnapshot.active ||
entity.tag !== baseSnapshot.tag ||
entity.updateOrder !== baseSnapshot.updateOrder) {
return true;
}
// 检查组件数量变化
if (entity.components.length !== baseSnapshot.components.length) {
return true;
}
// 检查组件类型变化
const currentComponentTypes = new Set(entity.components.map(c => c.constructor.name));
const baseComponentTypes = new Set(baseSnapshot.components.map(c => c.type));
if (currentComponentTypes.size !== baseComponentTypes.size) {
return true;
}
for (const type of currentComponentTypes) {
if (!baseComponentTypes.has(type)) {
return true;
}
}
return false;
}
/**
* 获取发生变化的组件列表
*/
private getChangedComponents(entity: Entity, baseSnapshot: EntitySnapshot): ComponentSnapshot[] {
const changedComponents: ComponentSnapshot[] = [];
const baseComponentMap = new Map<string, ComponentSnapshot>();
for (const comp of baseSnapshot.components) {
baseComponentMap.set(comp.type, comp);
}
for (const component of entity.components) {
const baseComponent = baseComponentMap.get(component.constructor.name);
if (!baseComponent) {
const componentSnapshot = this.createComponentSnapshot(component);
if (componentSnapshot) {
changedComponents.push(componentSnapshot);
}
} else {
if (this.hasComponentDataChanged(component, baseComponent)) {
const componentSnapshot = this.createComponentSnapshot(component);
if (componentSnapshot) {
changedComponents.push(componentSnapshot);
}
}
}
}
return changedComponents;
}
/**
* 检查组件数据是否发生变化
*/
private hasComponentDataChanged(component: Component, baseComponent: ComponentSnapshot): boolean {
if (component.enabled !== baseComponent.enabled) {
return true;
}
if (this.hasChangeDetectionMethod(component)) {
try {
return (component as any).hasChanged(baseComponent.data);
} catch {
return true;
}
}
return true;
}
/**
* 检查组件是否有变化检测方法
*/
private hasChangeDetectionMethod(component: Component): boolean {
return typeof (component as any).hasChanged === 'function';
}
/**
* 创建增量实体快照(只包含变化的组件)
*/
private createIncrementalEntitySnapshot(entity: Entity, baseSnapshot: EntitySnapshot, changedComponents: ComponentSnapshot[]): EntitySnapshot | null {
// 检查实体基本属性是否变化
const hasBasicChanges = entity.enabled !== baseSnapshot.enabled ||
entity.active !== baseSnapshot.active ||
entity.tag !== baseSnapshot.tag ||
entity.updateOrder !== baseSnapshot.updateOrder;
// 如果没有基本变化且没有组件变化返回null
if (!hasBasicChanges && changedComponents.length === 0) {
return null;
}
return {
id: entity.id,
name: entity.name,
enabled: entity.enabled,
active: entity.active,
tag: entity.tag,
updateOrder: entity.updateOrder,
components: changedComponents, // 只包含变化的组件
children: entity.children.map(child => child.id),
parent: entity.parent?.id || undefined,
timestamp: Date.now()
};
}
/**
* 生成快照ID
*/
private generateSnapshotId(snapshot: SceneSnapshot): string {
return `${snapshot.timestamp}_${snapshot.entities.length}`;
}
}

View File

@@ -0,0 +1,19 @@
/**
* 快照系统模块
*
* 提供ECS系统的快照功能支持实体和组件的序列化与反序列化
*/
// 核心接口
export * from './ISnapshotable';
// 快照管理器
export { SnapshotManager } from './SnapshotManager';
// 快照扩展
export {
ISnapshotExtension,
Serializable,
SnapshotConfigDecorator,
SnapshotExtension
} from './SnapshotExtension';

View File

@@ -0,0 +1,15 @@
/**
* ECS Framework Network Plugin - 网络插件
*
* 为ECS框架提供网络同步和帧同步功能
*/
// 网络组件基类
export { NetworkComponent } from './NetworkComponent';
export { INetworkSyncable } from './INetworkSyncable';
// Protobuf序列化系统
export * from './Serialization';
// 快照系统(帧同步)
export * from './Snapshot';