新增syncvar高级特性,使用protobuf定义
This commit is contained in:
303
packages/network/src/Messaging/MessageHandler.ts
Normal file
303
packages/network/src/Messaging/MessageHandler.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
import { NetworkMessage } from './NetworkMessage';
|
||||
import { NetworkConnection } from '../Core/NetworkConnection';
|
||||
import { INetworkMessage, MessageData } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 消息处理器接口
|
||||
*/
|
||||
export interface IMessageHandler<T extends INetworkMessage = INetworkMessage> {
|
||||
/**
|
||||
* 处理消息
|
||||
*
|
||||
* @param message - 网络消息
|
||||
* @param connection - 发送消息的连接(服务端有效)
|
||||
*/
|
||||
handle(message: T, connection?: NetworkConnection): Promise<void> | void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息处理器注册信息
|
||||
*/
|
||||
interface MessageHandlerInfo<T extends MessageData = MessageData> {
|
||||
handler: IMessageHandler<INetworkMessage<T>>;
|
||||
messageClass: new (...args: any[]) => INetworkMessage<T>;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息处理器管理器
|
||||
*
|
||||
* 负责注册、查找和调用消息处理器
|
||||
* 支持消息优先级和类型匹配
|
||||
*/
|
||||
export class MessageHandler {
|
||||
private static _instance: MessageHandler | null = null;
|
||||
private _handlers: Map<number, MessageHandlerInfo[]> = new Map();
|
||||
private _messageClasses: Map<number, new (...args: any[]) => INetworkMessage> = new Map();
|
||||
|
||||
/**
|
||||
* 获取消息处理器单例
|
||||
*/
|
||||
public static get Instance(): MessageHandler {
|
||||
if (!MessageHandler._instance) {
|
||||
MessageHandler._instance = new MessageHandler();
|
||||
}
|
||||
return MessageHandler._instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 注册消息处理器
|
||||
*
|
||||
* @param messageType - 消息类型ID
|
||||
* @param messageClass - 消息类构造函数
|
||||
* @param handler - 消息处理器
|
||||
* @param priority - 处理优先级(数字越小优先级越高)
|
||||
*/
|
||||
public registerHandler<TData extends MessageData, T extends INetworkMessage<TData>>(
|
||||
messageType: number,
|
||||
messageClass: new (...args: any[]) => T,
|
||||
handler: IMessageHandler<T>,
|
||||
priority: number = 0
|
||||
): void {
|
||||
// 注册消息类
|
||||
this._messageClasses.set(messageType, messageClass);
|
||||
|
||||
// 获取或创建处理器列表
|
||||
if (!this._handlers.has(messageType)) {
|
||||
this._handlers.set(messageType, []);
|
||||
}
|
||||
|
||||
const handlers = this._handlers.get(messageType)!;
|
||||
|
||||
// 检查是否已经注册了相同的处理器
|
||||
const existingIndex = handlers.findIndex(h => h.handler === handler);
|
||||
if (existingIndex !== -1) {
|
||||
console.warn(`[MessageHandler] 消息类型 ${messageType} 的处理器已存在,将替换优先级`);
|
||||
handlers[existingIndex].priority = priority;
|
||||
} else {
|
||||
// 添加新处理器
|
||||
handlers.push({
|
||||
handler: handler as IMessageHandler<INetworkMessage>,
|
||||
messageClass: messageClass as new (...args: any[]) => INetworkMessage,
|
||||
priority
|
||||
});
|
||||
}
|
||||
|
||||
// 按优先级排序(数字越小优先级越高)
|
||||
handlers.sort((a, b) => a.priority - b.priority);
|
||||
|
||||
console.log(`[MessageHandler] 注册消息处理器: 类型=${messageType}, 优先级=${priority}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销消息处理器
|
||||
*
|
||||
* @param messageType - 消息类型ID
|
||||
* @param handler - 消息处理器
|
||||
*/
|
||||
public unregisterHandler(messageType: number, handler: IMessageHandler): void {
|
||||
const handlers = this._handlers.get(messageType);
|
||||
if (!handlers) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = handlers.findIndex(h => h.handler === handler);
|
||||
if (index !== -1) {
|
||||
handlers.splice(index, 1);
|
||||
console.log(`[MessageHandler] 注销消息处理器: 类型=${messageType}`);
|
||||
}
|
||||
|
||||
// 如果没有处理器了,清理映射
|
||||
if (handlers.length === 0) {
|
||||
this._handlers.delete(messageType);
|
||||
this._messageClasses.delete(messageType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理原始消息数据
|
||||
*
|
||||
* @param data - 原始消息数据
|
||||
* @param connection - 发送消息的连接(服务端有效)
|
||||
* @returns 是否成功处理
|
||||
*/
|
||||
public async handleRawMessage(data: Uint8Array, connection?: NetworkConnection): Promise<boolean> {
|
||||
if (data.length < 4) {
|
||||
console.error('[MessageHandler] 消息数据长度不足,至少需要4字节消息类型');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 读取消息类型(前4字节)
|
||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
||||
const messageType = view.getUint32(0, true);
|
||||
|
||||
// 查找消息类
|
||||
const MessageClass = this._messageClasses.get(messageType);
|
||||
if (!MessageClass) {
|
||||
console.warn(`[MessageHandler] 未知的消息类型: ${messageType}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建消息实例并反序列化
|
||||
try {
|
||||
const message = new MessageClass();
|
||||
message.deserialize(data);
|
||||
|
||||
return await this.handleMessage(message, connection);
|
||||
} catch (error) {
|
||||
console.error(`[MessageHandler] 消息反序列化失败 (类型=${messageType}):`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理网络消息
|
||||
*
|
||||
* @param message - 网络消息
|
||||
* @param connection - 发送消息的连接(服务端有效)
|
||||
* @returns 是否成功处理
|
||||
*/
|
||||
public async handleMessage(message: INetworkMessage, connection?: NetworkConnection): Promise<boolean> {
|
||||
const messageType = message.messageType;
|
||||
const handlers = this._handlers.get(messageType);
|
||||
|
||||
if (!handlers || handlers.length === 0) {
|
||||
console.warn(`[MessageHandler] 没有找到消息类型 ${messageType} 的处理器`);
|
||||
return false;
|
||||
}
|
||||
|
||||
let handledCount = 0;
|
||||
|
||||
// 按优先级顺序执行所有处理器
|
||||
for (const handlerInfo of handlers) {
|
||||
try {
|
||||
const result = handlerInfo.handler.handle(message, connection);
|
||||
|
||||
// 支持异步处理器
|
||||
if (result instanceof Promise) {
|
||||
await result;
|
||||
}
|
||||
|
||||
handledCount++;
|
||||
} catch (error) {
|
||||
console.error(`[MessageHandler] 处理器执行错误 (类型=${messageType}, 优先级=${handlerInfo.priority}):`, error);
|
||||
// 继续执行其他处理器
|
||||
}
|
||||
}
|
||||
|
||||
return handledCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的消息类型列表
|
||||
*
|
||||
* @returns 消息类型数组
|
||||
*/
|
||||
public getRegisteredMessageTypes(): number[] {
|
||||
return Array.from(this._messageClasses.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查消息类型是否已注册
|
||||
*
|
||||
* @param messageType - 消息类型ID
|
||||
* @returns 是否已注册
|
||||
*/
|
||||
public isMessageTypeRegistered(messageType: number): boolean {
|
||||
return this._messageClasses.has(messageType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息类型的处理器数量
|
||||
*
|
||||
* @param messageType - 消息类型ID
|
||||
* @returns 处理器数量
|
||||
*/
|
||||
public getHandlerCount(messageType: number): number {
|
||||
const handlers = this._handlers.get(messageType);
|
||||
return handlers ? handlers.length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有处理器
|
||||
*/
|
||||
public clear(): void {
|
||||
this._handlers.clear();
|
||||
this._messageClasses.clear();
|
||||
console.log('[MessageHandler] 已清除所有消息处理器');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息处理器统计信息
|
||||
*
|
||||
* @returns 统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
totalMessageTypes: number;
|
||||
totalHandlers: number;
|
||||
messageTypes: Array<{
|
||||
type: number;
|
||||
handlerCount: number;
|
||||
className: string;
|
||||
}>;
|
||||
} {
|
||||
let totalHandlers = 0;
|
||||
const messageTypes: Array<{ type: number; handlerCount: number; className: string }> = [];
|
||||
|
||||
for (const [type, handlers] of this._handlers) {
|
||||
const handlerCount = handlers.length;
|
||||
totalHandlers += handlerCount;
|
||||
|
||||
const MessageClass = this._messageClasses.get(type);
|
||||
const className = MessageClass ? MessageClass.name : 'Unknown';
|
||||
|
||||
messageTypes.push({
|
||||
type,
|
||||
handlerCount,
|
||||
className
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
totalMessageTypes: this._messageClasses.size,
|
||||
totalHandlers,
|
||||
messageTypes
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息处理器装饰器
|
||||
*
|
||||
* 用于自动注册消息处理器
|
||||
*
|
||||
* @param messageType - 消息类型ID
|
||||
* @param messageClass - 消息类构造函数
|
||||
* @param priority - 处理优先级
|
||||
*/
|
||||
export function MessageHandlerDecorator<TData extends MessageData, T extends INetworkMessage<TData>>(
|
||||
messageType: number,
|
||||
messageClass: new (...args: any[]) => T,
|
||||
priority: number = 0
|
||||
) {
|
||||
return function(target: unknown, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
if (typeof originalMethod !== 'function') {
|
||||
throw new Error(`[MessageHandlerDecorator] ${propertyKey} is not a function`);
|
||||
}
|
||||
|
||||
// 注册处理器
|
||||
const handler: IMessageHandler<T> = {
|
||||
handle: async (message: T, connection?: NetworkConnection) => {
|
||||
return await originalMethod.call(target, message, connection);
|
||||
}
|
||||
};
|
||||
|
||||
MessageHandler.Instance.registerHandler(messageType, messageClass, handler, priority);
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
511
packages/network/src/Messaging/MessageTypes.ts
Normal file
511
packages/network/src/Messaging/MessageTypes.ts
Normal file
@@ -0,0 +1,511 @@
|
||||
import { NetworkMessage, JsonMessage } from './NetworkMessage';
|
||||
|
||||
/**
|
||||
* 内置消息类型枚举
|
||||
*/
|
||||
export enum MessageType {
|
||||
// 基础消息类型 (0-99)
|
||||
RAW = 0,
|
||||
JSON = 1,
|
||||
PROTOBUF = 2,
|
||||
|
||||
// 连接管理消息 (100-199)
|
||||
CONNECT_REQUEST = 100,
|
||||
CONNECT_RESPONSE = 101,
|
||||
DISCONNECT = 102,
|
||||
PING = 103,
|
||||
PONG = 104,
|
||||
|
||||
// 身份验证消息 (200-299)
|
||||
AUTH_REQUEST = 200,
|
||||
AUTH_RESPONSE = 201,
|
||||
|
||||
// 网络对象管理 (300-399)
|
||||
SPAWN_OBJECT = 300,
|
||||
DESTROY_OBJECT = 301,
|
||||
TRANSFER_AUTHORITY = 302,
|
||||
|
||||
// 组件同步消息 (400-499)
|
||||
SYNC_VAR_UPDATE = 400,
|
||||
COMPONENT_STATE = 401,
|
||||
BATCH_UPDATE = 402,
|
||||
|
||||
// RPC调用消息 (500-599)
|
||||
CLIENT_RPC = 500,
|
||||
SERVER_RPC = 501,
|
||||
RPC_RESPONSE = 502,
|
||||
|
||||
// 场景管理消息 (600-699)
|
||||
SCENE_LOAD = 600,
|
||||
SCENE_LOADED = 601,
|
||||
SCENE_UNLOAD = 602,
|
||||
|
||||
// 自定义消息 (1000+)
|
||||
CUSTOM = 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接请求消息
|
||||
*/
|
||||
export class ConnectRequestMessage extends JsonMessage<{
|
||||
clientVersion: string;
|
||||
protocolVersion: number;
|
||||
clientId?: string;
|
||||
}> {
|
||||
public override readonly messageType: number = MessageType.CONNECT_REQUEST;
|
||||
|
||||
constructor(clientVersion: string = '1.0.0', protocolVersion: number = 1, clientId?: string) {
|
||||
super({
|
||||
clientVersion,
|
||||
protocolVersion,
|
||||
clientId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接响应消息
|
||||
*/
|
||||
export class ConnectResponseMessage extends JsonMessage<{
|
||||
success: boolean;
|
||||
clientId: string;
|
||||
serverVersion: string;
|
||||
protocolVersion: number;
|
||||
errorMessage?: string;
|
||||
}> {
|
||||
public override readonly messageType: number = MessageType.CONNECT_RESPONSE;
|
||||
|
||||
constructor(
|
||||
success: boolean,
|
||||
clientId: string,
|
||||
serverVersion: string = '1.0.0',
|
||||
protocolVersion: number = 1,
|
||||
errorMessage?: string
|
||||
) {
|
||||
super({
|
||||
success,
|
||||
clientId,
|
||||
serverVersion,
|
||||
protocolVersion,
|
||||
errorMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接消息
|
||||
*/
|
||||
export class DisconnectMessage extends JsonMessage<{
|
||||
reason: string;
|
||||
code?: number;
|
||||
}> {
|
||||
public override readonly messageType: number = MessageType.DISCONNECT;
|
||||
|
||||
constructor(reason: string, code?: number) {
|
||||
super({
|
||||
reason,
|
||||
code
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳消息
|
||||
*/
|
||||
export class PingMessage extends NetworkMessage<{ pingId: number }> {
|
||||
public readonly messageType: number = MessageType.PING;
|
||||
private _data: { pingId: number };
|
||||
|
||||
public get data(): { pingId: number } {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
public get pingId(): number {
|
||||
return this._data.pingId;
|
||||
}
|
||||
|
||||
public set pingId(value: number) {
|
||||
this._data.pingId = value;
|
||||
}
|
||||
|
||||
constructor(pingId: number = Date.now()) {
|
||||
super();
|
||||
this._data = { pingId };
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
const buffer = new ArrayBuffer(12); // 4字节时间戳 + 4字节pingId + 4字节消息类型
|
||||
const view = new DataView(buffer);
|
||||
|
||||
view.setUint32(0, this.messageType, true);
|
||||
view.setUint32(4, this.timestamp, true);
|
||||
view.setUint32(8, this._data.pingId, true);
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
if (data.length < 12) {
|
||||
throw new Error('Ping消息数据长度不足');
|
||||
}
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
||||
// messageType在第0-3字节已经被外部处理
|
||||
this.timestamp = view.getUint32(4, true);
|
||||
this._data.pingId = view.getUint32(8, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳响应消息
|
||||
*/
|
||||
export class PongMessage extends NetworkMessage<{ pingId: number; serverTime: number }> {
|
||||
public readonly messageType: number = MessageType.PONG;
|
||||
private _data: { pingId: number; serverTime: number };
|
||||
|
||||
public get data(): { pingId: number; serverTime: number } {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
public get pingId(): number {
|
||||
return this._data.pingId;
|
||||
}
|
||||
|
||||
public set pingId(value: number) {
|
||||
this._data.pingId = value;
|
||||
}
|
||||
|
||||
public get serverTime(): number {
|
||||
return this._data.serverTime;
|
||||
}
|
||||
|
||||
public set serverTime(value: number) {
|
||||
this._data.serverTime = value;
|
||||
}
|
||||
|
||||
constructor(pingId: number = 0, serverTime: number = Date.now()) {
|
||||
super();
|
||||
this._data = { pingId, serverTime };
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
const buffer = new ArrayBuffer(16); // 4字节消息类型 + 4字节时间戳 + 4字节pingId + 4字节服务器时间
|
||||
const view = new DataView(buffer);
|
||||
|
||||
view.setUint32(0, this.messageType, true);
|
||||
view.setUint32(4, this.timestamp, true);
|
||||
view.setUint32(8, this._data.pingId, true);
|
||||
view.setUint32(12, this._data.serverTime, true);
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
if (data.length < 16) {
|
||||
throw new Error('Pong消息数据长度不足');
|
||||
}
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
||||
// messageType在第0-3字节已经被外部处理
|
||||
this.timestamp = view.getUint32(4, true);
|
||||
this._data.pingId = view.getUint32(8, true);
|
||||
this._data.serverTime = view.getUint32(12, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络对象生成消息
|
||||
*/
|
||||
export class SpawnObjectMessage extends JsonMessage<{
|
||||
networkId: string;
|
||||
prefabName: string;
|
||||
position: { x: number; y: number; z?: number };
|
||||
rotation?: { x: number; y: number; z: number; w: number };
|
||||
ownerId: string;
|
||||
hasAuthority: boolean;
|
||||
components?: Array<{
|
||||
type: string;
|
||||
data: string; // base64编码的protobuf数据
|
||||
}>;
|
||||
}> {
|
||||
public override readonly messageType: number = MessageType.SPAWN_OBJECT;
|
||||
|
||||
constructor(
|
||||
networkId: string,
|
||||
prefabName: string,
|
||||
position: { x: number; y: number; z?: number },
|
||||
ownerId: string,
|
||||
hasAuthority: boolean = false,
|
||||
rotation?: { x: number; y: number; z: number; w: number },
|
||||
components?: Array<{ type: string; data: string }>
|
||||
) {
|
||||
super({
|
||||
networkId,
|
||||
prefabName,
|
||||
position,
|
||||
rotation,
|
||||
ownerId,
|
||||
hasAuthority,
|
||||
components
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络对象销毁消息
|
||||
*/
|
||||
export class DestroyObjectMessage extends JsonMessage<{
|
||||
networkId: string;
|
||||
reason?: string;
|
||||
}> {
|
||||
public override readonly messageType: number = MessageType.DESTROY_OBJECT;
|
||||
|
||||
constructor(networkId: string, reason?: string) {
|
||||
super({
|
||||
networkId,
|
||||
reason
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限转移消息
|
||||
*/
|
||||
export class TransferAuthorityMessage extends JsonMessage<{
|
||||
networkId: string;
|
||||
newOwnerId: string;
|
||||
previousOwnerId: string;
|
||||
}> {
|
||||
public override readonly messageType: number = MessageType.TRANSFER_AUTHORITY;
|
||||
|
||||
constructor(networkId: string, newOwnerId: string, previousOwnerId: string) {
|
||||
super({
|
||||
networkId,
|
||||
newOwnerId,
|
||||
previousOwnerId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar字段更新数据
|
||||
*/
|
||||
export interface SyncVarFieldUpdate {
|
||||
/** 字段编号 */
|
||||
fieldNumber: number;
|
||||
/** 字段名称(用于调试) */
|
||||
propertyKey: string;
|
||||
/** 序列化后的新值 */
|
||||
newValue: string | number | boolean | null | undefined | Date | Uint8Array | Record<string, unknown> | unknown[];
|
||||
/** 序列化后的旧值(用于回滚或调试) */
|
||||
oldValue?: string | number | boolean | null | undefined | Date | Uint8Array | Record<string, unknown> | unknown[];
|
||||
/** 字段变化时间戳 */
|
||||
timestamp: number;
|
||||
/** 是否是权威字段(只有权威端可以修改) */
|
||||
authorityOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar更新消息数据结构
|
||||
*/
|
||||
export interface SyncVarUpdateData extends Record<string, unknown> {
|
||||
/** 网络对象ID */
|
||||
networkId: string;
|
||||
/** 组件类型名称 */
|
||||
componentType: string;
|
||||
/** 字段更新列表 */
|
||||
fieldUpdates: SyncVarFieldUpdate[];
|
||||
/** 是否是完整状态同步(而非增量更新) */
|
||||
isFullSync: boolean;
|
||||
/** 发送者ID */
|
||||
senderId: string;
|
||||
/** 同步序号(用于确保顺序) */
|
||||
syncSequence: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar更新消息
|
||||
*
|
||||
* 支持增量同步和批处理
|
||||
*/
|
||||
export class SyncVarUpdateMessage extends JsonMessage<SyncVarUpdateData> {
|
||||
public override readonly messageType: number = MessageType.SYNC_VAR_UPDATE;
|
||||
|
||||
/** 网络对象ID */
|
||||
public get networkId(): string {
|
||||
return this.payload.networkId;
|
||||
}
|
||||
|
||||
public set networkId(value: string) {
|
||||
this.payload.networkId = value;
|
||||
}
|
||||
|
||||
/** 组件类型名称 */
|
||||
public get componentType(): string {
|
||||
return this.payload.componentType;
|
||||
}
|
||||
|
||||
public set componentType(value: string) {
|
||||
this.payload.componentType = value;
|
||||
}
|
||||
|
||||
/** 字段更新列表 */
|
||||
public get fieldUpdates(): SyncVarFieldUpdate[] {
|
||||
return this.payload.fieldUpdates;
|
||||
}
|
||||
|
||||
public set fieldUpdates(value: SyncVarFieldUpdate[]) {
|
||||
this.payload.fieldUpdates = value;
|
||||
}
|
||||
|
||||
/** 是否是完整状态同步(而非增量更新) */
|
||||
public get isFullSync(): boolean {
|
||||
return this.payload.isFullSync;
|
||||
}
|
||||
|
||||
public set isFullSync(value: boolean) {
|
||||
this.payload.isFullSync = value;
|
||||
}
|
||||
|
||||
/** 同步序号(用于确保顺序) */
|
||||
public get syncSequence(): number {
|
||||
return this.payload.syncSequence;
|
||||
}
|
||||
|
||||
public set syncSequence(value: number) {
|
||||
this.payload.syncSequence = value;
|
||||
}
|
||||
|
||||
constructor(
|
||||
networkId: string = '',
|
||||
componentType: string = '',
|
||||
fieldUpdates: SyncVarFieldUpdate[] = [],
|
||||
isFullSync: boolean = false,
|
||||
senderId: string = '',
|
||||
syncSequence: number = 0
|
||||
) {
|
||||
const data: SyncVarUpdateData = {
|
||||
networkId,
|
||||
componentType,
|
||||
fieldUpdates,
|
||||
isFullSync,
|
||||
senderId,
|
||||
syncSequence
|
||||
};
|
||||
super(data, senderId, syncSequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加字段更新
|
||||
*/
|
||||
public addFieldUpdate(update: SyncVarFieldUpdate): void {
|
||||
this.payload.fieldUpdates.push(update);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定字段的更新
|
||||
*/
|
||||
public getFieldUpdate(fieldNumber: number): SyncVarFieldUpdate | undefined {
|
||||
return this.payload.fieldUpdates.find(update => update.fieldNumber === fieldNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定字段的更新
|
||||
*/
|
||||
public removeFieldUpdate(fieldNumber: number): boolean {
|
||||
const index = this.payload.fieldUpdates.findIndex(update => update.fieldNumber === fieldNumber);
|
||||
if (index !== -1) {
|
||||
this.payload.fieldUpdates.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有字段更新
|
||||
*/
|
||||
public clearFieldUpdates(): void {
|
||||
this.payload.fieldUpdates = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取更新的字段数量
|
||||
*/
|
||||
public getUpdateCount(): number {
|
||||
return this.payload.fieldUpdates.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有字段更新
|
||||
*/
|
||||
public hasUpdates(): boolean {
|
||||
return this.payload.fieldUpdates.length > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 创建消息副本
|
||||
*/
|
||||
public override clone(): SyncVarUpdateMessage {
|
||||
return new SyncVarUpdateMessage(
|
||||
this.payload.networkId,
|
||||
this.payload.componentType,
|
||||
[...this.payload.fieldUpdates], // 深拷贝字段更新数组
|
||||
this.payload.isFullSync,
|
||||
this.payload.senderId,
|
||||
this.payload.syncSequence
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
updateCount: number;
|
||||
estimatedSize: number;
|
||||
hasAuthorityOnlyFields: boolean;
|
||||
oldestUpdateTime: number;
|
||||
newestUpdateTime: number;
|
||||
} {
|
||||
if (this.payload.fieldUpdates.length === 0) {
|
||||
return {
|
||||
updateCount: 0,
|
||||
estimatedSize: this.getSize(),
|
||||
hasAuthorityOnlyFields: false,
|
||||
oldestUpdateTime: 0,
|
||||
newestUpdateTime: 0
|
||||
};
|
||||
}
|
||||
|
||||
const timestamps = this.payload.fieldUpdates.map(u => u.timestamp);
|
||||
const hasAuthorityOnlyFields = this.payload.fieldUpdates.some(u => u.authorityOnly);
|
||||
|
||||
return {
|
||||
updateCount: this.payload.fieldUpdates.length,
|
||||
estimatedSize: this.getSize(),
|
||||
hasAuthorityOnlyFields,
|
||||
oldestUpdateTime: Math.min(...timestamps),
|
||||
newestUpdateTime: Math.max(...timestamps)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新消息
|
||||
*
|
||||
* 用于一次性发送多个对象的状态更新
|
||||
*/
|
||||
export class BatchUpdateMessage extends JsonMessage<{
|
||||
updates: Array<{
|
||||
networkId: string;
|
||||
componentType: string;
|
||||
data: string; // base64编码的完整组件状态
|
||||
}>;
|
||||
}> {
|
||||
public override readonly messageType: number = MessageType.BATCH_UPDATE;
|
||||
|
||||
constructor(updates: Array<{ networkId: string; componentType: string; data: string }>) {
|
||||
super({ updates });
|
||||
}
|
||||
}
|
||||
316
packages/network/src/Messaging/NetworkMessage.ts
Normal file
316
packages/network/src/Messaging/NetworkMessage.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import { INetworkMessage, MessageData } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 网络消息基类
|
||||
*
|
||||
* 所有网络消息都应该继承此类
|
||||
* 提供消息的序列化和反序列化功能
|
||||
*/
|
||||
export abstract class NetworkMessage<TData extends MessageData = MessageData> implements INetworkMessage<TData> {
|
||||
/**
|
||||
* 消息类型ID
|
||||
* 每个消息类型都应该有唯一的ID
|
||||
*/
|
||||
public abstract readonly messageType: number;
|
||||
|
||||
/**
|
||||
* 消息数据
|
||||
*/
|
||||
public abstract readonly data: TData;
|
||||
|
||||
/**
|
||||
* 消息时间戳
|
||||
*/
|
||||
public timestamp: number = Date.now();
|
||||
|
||||
/**
|
||||
* 发送者ID
|
||||
*/
|
||||
public senderId?: string;
|
||||
|
||||
/**
|
||||
* 消息序列号
|
||||
*/
|
||||
public sequence?: number;
|
||||
|
||||
/**
|
||||
* 序列化消息为二进制数据
|
||||
*
|
||||
* @returns 序列化后的数据
|
||||
*/
|
||||
public abstract serialize(): Uint8Array;
|
||||
|
||||
/**
|
||||
* 从二进制数据反序列化消息
|
||||
*
|
||||
* @param data - 二进制数据
|
||||
*/
|
||||
public abstract deserialize(data: Uint8Array): void;
|
||||
|
||||
/**
|
||||
* 创建消息实例
|
||||
*/
|
||||
protected constructor(
|
||||
senderId?: string,
|
||||
sequence?: number
|
||||
) {
|
||||
this.senderId = senderId;
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息大小(字节)
|
||||
*
|
||||
* @returns 消息大小
|
||||
*/
|
||||
public getSize(): number {
|
||||
return this.serialize().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建消息副本
|
||||
*
|
||||
* @returns 消息副本
|
||||
*/
|
||||
public clone(): NetworkMessage<TData> {
|
||||
const Constructor = this.constructor as new (senderId?: string, sequence?: number) => NetworkMessage<TData>;
|
||||
const cloned = new Constructor(this.senderId, this.sequence);
|
||||
const data = this.serialize();
|
||||
cloned.deserialize(data);
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 原始二进制消息
|
||||
*
|
||||
* 用于传输原始二进制数据,不进行额外的序列化处理
|
||||
*/
|
||||
export class RawMessage extends NetworkMessage<Uint8Array> {
|
||||
public readonly messageType: number = 0;
|
||||
private _data: Uint8Array;
|
||||
|
||||
public get data(): Uint8Array {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
constructor(
|
||||
data: Uint8Array = new Uint8Array(0),
|
||||
senderId?: string,
|
||||
sequence?: number
|
||||
) {
|
||||
super(senderId, sequence);
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
// 创建包含消息类型的完整消息格式:[4字节消息类型][原始数据]
|
||||
const buffer = new ArrayBuffer(4 + this._data.length);
|
||||
const view = new DataView(buffer);
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
|
||||
// 写入消息类型
|
||||
view.setUint32(0, this.messageType, true);
|
||||
|
||||
// 写入原始数据
|
||||
uint8Array.set(this._data, 4);
|
||||
|
||||
return uint8Array;
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
// 原始数据从第4字节开始(前4字节是消息类型)
|
||||
this._data = data.subarray(4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON消息
|
||||
*
|
||||
* 用于传输JSON数据,自动进行JSON序列化和反序列化
|
||||
*/
|
||||
export class JsonMessage<T = Record<string, unknown>> extends NetworkMessage<Record<string, unknown>> {
|
||||
public readonly messageType: number = 1;
|
||||
private _data: Record<string, unknown>;
|
||||
|
||||
public get data(): Record<string, unknown> {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
constructor(
|
||||
payload: T = {} as T,
|
||||
senderId?: string,
|
||||
sequence?: number
|
||||
) {
|
||||
super(senderId, sequence);
|
||||
this._data = { payload };
|
||||
}
|
||||
|
||||
public get payload(): T {
|
||||
return this._data.payload as T;
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
const payloadBytes = this.serializePayload(this._data.payload);
|
||||
const senderIdBytes = new TextEncoder().encode(this.senderId || '');
|
||||
|
||||
const buffer = new ArrayBuffer(
|
||||
4 + // messageType
|
||||
8 + // timestamp
|
||||
4 + // sequence
|
||||
4 + // senderId length
|
||||
senderIdBytes.length + // senderId
|
||||
payloadBytes.length
|
||||
);
|
||||
|
||||
const view = new DataView(buffer);
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
let offset = 0;
|
||||
|
||||
view.setUint32(offset, this.messageType, true);
|
||||
offset += 4;
|
||||
|
||||
view.setBigUint64(offset, BigInt(this.timestamp), true);
|
||||
offset += 8;
|
||||
|
||||
view.setUint32(offset, this.sequence || 0, true);
|
||||
offset += 4;
|
||||
|
||||
view.setUint32(offset, senderIdBytes.length, true);
|
||||
offset += 4;
|
||||
|
||||
uint8Array.set(senderIdBytes, offset);
|
||||
offset += senderIdBytes.length;
|
||||
|
||||
uint8Array.set(payloadBytes, offset);
|
||||
|
||||
return uint8Array;
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
const view = new DataView(data.buffer, data.byteOffset);
|
||||
let offset = 4; // 跳过messageType
|
||||
|
||||
this.timestamp = Number(view.getBigUint64(offset, true));
|
||||
offset += 8;
|
||||
|
||||
this.sequence = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
const senderIdLength = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
this.senderId = new TextDecoder().decode(data.subarray(offset, offset + senderIdLength));
|
||||
offset += senderIdLength;
|
||||
|
||||
const payloadBytes = data.subarray(offset);
|
||||
this._data = { payload: this.deserializePayload(payloadBytes) };
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化payload,子类可以重写以使用不同的序列化策略
|
||||
*/
|
||||
protected serializePayload(payload: unknown): Uint8Array {
|
||||
const jsonString = JSON.stringify(payload);
|
||||
return new TextEncoder().encode(jsonString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化payload,子类可以重写以使用不同的反序列化策略
|
||||
*/
|
||||
protected deserializePayload(data: Uint8Array): unknown {
|
||||
const jsonString = new TextDecoder().decode(data);
|
||||
return JSON.parse(jsonString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf消息包装器
|
||||
*
|
||||
* 用于包装已经序列化的Protobuf数据
|
||||
*/
|
||||
export class ProtobufMessage extends NetworkMessage<Uint8Array> {
|
||||
public readonly messageType: number = 2;
|
||||
private _componentType: string;
|
||||
private _data: Uint8Array;
|
||||
|
||||
public get componentType(): string {
|
||||
return this._componentType;
|
||||
}
|
||||
|
||||
public get data(): Uint8Array {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
constructor(
|
||||
componentType: string = '',
|
||||
data: Uint8Array = new Uint8Array(0),
|
||||
senderId?: string,
|
||||
sequence?: number
|
||||
) {
|
||||
super(senderId, sequence);
|
||||
this._componentType = componentType;
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
// 创建包含头部信息的消息格式:
|
||||
// [4字节消息类型][4字节时间戳][1字节组件类型长度][组件类型字符串][protobuf数据]
|
||||
const typeBytes = new TextEncoder().encode(this._componentType);
|
||||
const buffer = new ArrayBuffer(4 + 4 + 1 + typeBytes.length + this._data.length);
|
||||
const view = new DataView(buffer);
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
|
||||
let offset = 0;
|
||||
|
||||
// 写入消息类型(4字节)
|
||||
view.setUint32(offset, this.messageType, true);
|
||||
offset += 4;
|
||||
|
||||
// 写入时间戳(4字节)
|
||||
view.setUint32(offset, this.timestamp, true);
|
||||
offset += 4;
|
||||
|
||||
// 写入组件类型长度(1字节)
|
||||
view.setUint8(offset, typeBytes.length);
|
||||
offset += 1;
|
||||
|
||||
// 写入组件类型字符串
|
||||
uint8Array.set(typeBytes, offset);
|
||||
offset += typeBytes.length;
|
||||
|
||||
// 写入protobuf数据
|
||||
uint8Array.set(this._data, offset);
|
||||
|
||||
return uint8Array;
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
if (data.length < 9) { // 4+4+1 = 9字节最小长度
|
||||
throw new Error('Protobuf消息数据长度不足');
|
||||
}
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
||||
let offset = 4; // 跳过前4字节消息类型
|
||||
|
||||
// 读取时间戳(4字节)
|
||||
this.timestamp = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
// 读取组件类型长度(1字节)
|
||||
const typeLength = view.getUint8(offset);
|
||||
offset += 1;
|
||||
|
||||
if (data.length < offset + typeLength) {
|
||||
throw new Error('Protobuf消息组件类型数据不足');
|
||||
}
|
||||
|
||||
// 读取组件类型字符串
|
||||
const typeBytes = data.subarray(offset, offset + typeLength);
|
||||
this._componentType = new TextDecoder().decode(typeBytes);
|
||||
offset += typeLength;
|
||||
|
||||
// 读取protobuf数据
|
||||
this._data = data.subarray(offset);
|
||||
}
|
||||
}
|
||||
13
packages/network/src/Messaging/index.ts
Normal file
13
packages/network/src/Messaging/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 消息系统导出
|
||||
*
|
||||
* 提供网络消息的定义、处理和管理功能
|
||||
*/
|
||||
|
||||
// 消息基类和类型
|
||||
export * from './NetworkMessage';
|
||||
export * from './MessageTypes';
|
||||
export * from './MessageHandler';
|
||||
|
||||
// 导出SyncVar相关的接口和类型
|
||||
export type { SyncVarFieldUpdate } from './MessageTypes';
|
||||
Reference in New Issue
Block a user