Files
esengine/packages/network/src/NetworkComponent.ts

161 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}