重新整理网络架构,tsrpc/syncvar并行
This commit is contained in:
431
packages/network/src/transport/TsrpcClient.ts
Normal file
431
packages/network/src/transport/TsrpcClient.ts
Normal file
@@ -0,0 +1,431 @@
|
||||
/**
|
||||
* TSRPC 客户端传输层
|
||||
*
|
||||
* 封装TSRPC客户端功能,提供服务端连接和消息收发
|
||||
*/
|
||||
|
||||
import { WsClient } from 'tsrpc';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { serviceProto, ServiceType } from './protocols/serviceProto';
|
||||
import { NetworkConfig, NetworkMessage } from '../types/NetworkTypes';
|
||||
import {
|
||||
ReqJoinRoom, ResJoinRoom,
|
||||
ReqServerStatus, ResServerStatus,
|
||||
ReqPing, ResPing,
|
||||
MsgNetworkMessage,
|
||||
MsgSyncVar,
|
||||
MsgRpcCall,
|
||||
MsgNetworkObjectSpawn,
|
||||
MsgNetworkObjectDespawn,
|
||||
MsgClientDisconnected,
|
||||
MsgAuthorityChange
|
||||
} from './protocols/NetworkProtocols';
|
||||
|
||||
const logger = createLogger('TsrpcClient');
|
||||
|
||||
/**
|
||||
* 连接状态
|
||||
*/
|
||||
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
|
||||
|
||||
/**
|
||||
* TSRPC客户端包装器
|
||||
*/
|
||||
export class TsrpcClient {
|
||||
private client: WsClient<ServiceType> | null = null;
|
||||
private config: NetworkConfig;
|
||||
private connectionState: ConnectionState = 'disconnected';
|
||||
private clientId: number = 0;
|
||||
private roomId: string = '';
|
||||
private reconnectAttempts: number = 0;
|
||||
private maxReconnectAttempts: number = 5;
|
||||
private reconnectInterval: number = 2000;
|
||||
private heartbeatInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
/** 统计信息 */
|
||||
private stats = {
|
||||
messagesSent: 0,
|
||||
messagesReceived: 0,
|
||||
bytesSent: 0,
|
||||
bytesReceived: 0,
|
||||
latency: 0
|
||||
};
|
||||
|
||||
/** 事件处理器 */
|
||||
public onConnected?: () => void;
|
||||
public onDisconnected?: (reason?: string) => void;
|
||||
public onReconnecting?: () => void;
|
||||
public onMessage?: (message: NetworkMessage) => void;
|
||||
public onError?: (error: Error) => void;
|
||||
|
||||
constructor(config: NetworkConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到服务端
|
||||
*/
|
||||
public async connect(): Promise<void> {
|
||||
if (this.connectionState !== 'disconnected') {
|
||||
throw new Error('客户端已连接或正在连接中');
|
||||
}
|
||||
|
||||
this.connectionState = 'connecting';
|
||||
|
||||
try {
|
||||
// 创建WebSocket客户端
|
||||
this.client = new WsClient(serviceProto, {
|
||||
server: `ws://${this.config.host}:${this.config.port}`,
|
||||
// 自动重连配置
|
||||
heartbeat: {
|
||||
interval: 30000,
|
||||
timeout: 5000
|
||||
}
|
||||
});
|
||||
|
||||
this.setupEventHandlers();
|
||||
|
||||
// 连接到服务端
|
||||
const connectResult = await this.client.connect();
|
||||
if (!connectResult.isSucc) {
|
||||
throw new Error(`连接失败: ${connectResult.errMsg}`);
|
||||
}
|
||||
|
||||
// 加入房间
|
||||
const joinResult = await this.client.callApi('network/JoinRoom', {
|
||||
roomId: this.config.roomId,
|
||||
clientInfo: {
|
||||
version: '1.0.0',
|
||||
platform: typeof window !== 'undefined' ? 'browser' : 'node'
|
||||
}
|
||||
});
|
||||
|
||||
if (!joinResult.isSucc) {
|
||||
throw new Error(`加入房间失败: ${joinResult.err.message}`);
|
||||
}
|
||||
|
||||
this.clientId = joinResult.res.clientId;
|
||||
this.roomId = joinResult.res.roomId;
|
||||
this.connectionState = 'connected';
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// 启动心跳
|
||||
this.startHeartbeat();
|
||||
|
||||
logger.info(`连接成功,客户端ID: ${this.clientId}, 房间: ${this.roomId}`);
|
||||
this.onConnected?.();
|
||||
|
||||
} catch (error) {
|
||||
this.connectionState = 'disconnected';
|
||||
logger.error('连接服务端失败:', error);
|
||||
this.onError?.(error as Error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
public async disconnect(): Promise<void> {
|
||||
if (this.connectionState === 'disconnected') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.connectionState = 'disconnected';
|
||||
this.stopHeartbeat();
|
||||
|
||||
if (this.client) {
|
||||
await this.client.disconnect();
|
||||
this.client = null;
|
||||
}
|
||||
|
||||
logger.info('客户端已断开连接');
|
||||
this.onDisconnected?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息到服务端
|
||||
*/
|
||||
public async sendMessage(message: NetworkMessage): Promise<void> {
|
||||
if (!this.isConnected()) {
|
||||
throw new Error('客户端未连接');
|
||||
}
|
||||
|
||||
try {
|
||||
const tsrpcMessage: MsgNetworkMessage = {
|
||||
type: message.type as 'syncvar' | 'rpc',
|
||||
networkId: message.networkId,
|
||||
data: message.data,
|
||||
timestamp: message.timestamp
|
||||
};
|
||||
|
||||
await this.client!.sendMsg('network/NetworkMessage', tsrpcMessage);
|
||||
this.stats.messagesSent++;
|
||||
|
||||
logger.debug(`发送消息: ${message.type}, 网络ID: ${message.networkId}`);
|
||||
} catch (error) {
|
||||
logger.error('发送消息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送SyncVar同步消息
|
||||
*/
|
||||
public async sendSyncVar(
|
||||
networkId: number,
|
||||
componentType: string,
|
||||
propertyName: string,
|
||||
value: any
|
||||
): Promise<void> {
|
||||
if (!this.isConnected()) {
|
||||
throw new Error('客户端未连接');
|
||||
}
|
||||
|
||||
try {
|
||||
const message: MsgSyncVar = {
|
||||
networkId,
|
||||
componentType,
|
||||
propertyName,
|
||||
value,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
await this.client!.sendMsg('network/SyncVar', message);
|
||||
this.stats.messagesSent++;
|
||||
|
||||
logger.debug(`发送SyncVar: ${componentType}.${propertyName} = ${value}`);
|
||||
} catch (error) {
|
||||
logger.error('发送SyncVar失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送RPC调用消息
|
||||
*/
|
||||
public async sendRpcCall(
|
||||
networkId: number,
|
||||
componentType: string,
|
||||
methodName: string,
|
||||
args: any[],
|
||||
isClientRpc: boolean
|
||||
): Promise<void> {
|
||||
if (!this.isConnected()) {
|
||||
throw new Error('客户端未连接');
|
||||
}
|
||||
|
||||
try {
|
||||
const message: MsgRpcCall = {
|
||||
networkId,
|
||||
componentType,
|
||||
methodName,
|
||||
args,
|
||||
isClientRpc,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
await this.client!.sendMsg('network/RpcCall', message);
|
||||
this.stats.messagesSent++;
|
||||
|
||||
logger.debug(`发送RPC: ${componentType}.${methodName}(${isClientRpc ? 'ClientRpc' : 'Command'})`);
|
||||
} catch (error) {
|
||||
logger.error('发送RPC失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询服务端状态
|
||||
*/
|
||||
public async getServerStatus(): Promise<ResServerStatus> {
|
||||
if (!this.isConnected()) {
|
||||
throw new Error('客户端未连接');
|
||||
}
|
||||
|
||||
const result = await this.client!.callApi('network/ServerStatus', {});
|
||||
if (!result.isSucc) {
|
||||
throw new Error(`查询服务端状态失败: ${result.err.message}`);
|
||||
}
|
||||
|
||||
return result.res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送心跳
|
||||
*/
|
||||
public async ping(): Promise<number> {
|
||||
if (!this.isConnected()) {
|
||||
throw new Error('客户端未连接');
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await this.client!.callApi('network/Ping', {
|
||||
timestamp: startTime
|
||||
});
|
||||
|
||||
if (!result.isSucc) {
|
||||
throw new Error(`心跳失败: ${result.err.message}`);
|
||||
}
|
||||
|
||||
const latency = Date.now() - startTime;
|
||||
this.stats.latency = latency;
|
||||
return latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
public getConnectionState(): ConnectionState {
|
||||
return this.connectionState;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已连接
|
||||
*/
|
||||
public isConnected(): boolean {
|
||||
return this.connectionState === 'connected' && (this.client?.isConnected || false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端ID
|
||||
*/
|
||||
public getClientId(): number {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats() {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
private setupEventHandlers(): void {
|
||||
if (!this.client) return;
|
||||
|
||||
// 连接断开处理
|
||||
this.client.flows.postDisconnectFlow.push((v) => {
|
||||
if (this.connectionState !== 'disconnected') {
|
||||
logger.warn('连接意外断开,尝试重连...');
|
||||
this.connectionState = 'reconnecting';
|
||||
this.onReconnecting?.();
|
||||
this.attemptReconnect();
|
||||
}
|
||||
return v;
|
||||
});
|
||||
|
||||
// 消息监听
|
||||
this.client.listenMsg('network/NetworkMessage', msg => {
|
||||
this.stats.messagesReceived++;
|
||||
|
||||
const networkMessage: NetworkMessage = {
|
||||
type: msg.type,
|
||||
networkId: msg.networkId,
|
||||
data: msg.data,
|
||||
timestamp: msg.timestamp
|
||||
};
|
||||
|
||||
this.onMessage?.(networkMessage);
|
||||
});
|
||||
|
||||
// SyncVar消息监听
|
||||
this.client.listenMsg('network/SyncVar', msg => {
|
||||
this.stats.messagesReceived++;
|
||||
|
||||
const networkMessage: NetworkMessage = {
|
||||
type: 'syncvar',
|
||||
networkId: msg.networkId,
|
||||
data: {
|
||||
componentType: msg.componentType,
|
||||
propertyName: msg.propertyName,
|
||||
value: msg.value
|
||||
},
|
||||
timestamp: msg.timestamp
|
||||
};
|
||||
|
||||
this.onMessage?.(networkMessage);
|
||||
});
|
||||
|
||||
// RPC消息监听
|
||||
this.client.listenMsg('network/RpcCall', msg => {
|
||||
this.stats.messagesReceived++;
|
||||
|
||||
const networkMessage: NetworkMessage = {
|
||||
type: 'rpc',
|
||||
networkId: msg.networkId,
|
||||
data: {
|
||||
componentType: msg.componentType,
|
||||
methodName: msg.methodName,
|
||||
args: msg.args
|
||||
},
|
||||
timestamp: msg.timestamp
|
||||
};
|
||||
|
||||
this.onMessage?.(networkMessage);
|
||||
});
|
||||
|
||||
// 客户端断开通知
|
||||
this.client.listenMsg('network/ClientDisconnected', msg => {
|
||||
logger.info(`客户端 ${msg.clientId} 断开连接: ${msg.reason}`);
|
||||
});
|
||||
|
||||
// 权威转移通知
|
||||
this.client.listenMsg('network/AuthorityChange', msg => {
|
||||
logger.info(`网络对象 ${msg.networkId} 权威转移给客户端 ${msg.newOwnerId}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试重连
|
||||
*/
|
||||
private async attemptReconnect(): Promise<void> {
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
logger.error('重连次数已达上限,停止重连');
|
||||
this.connectionState = 'disconnected';
|
||||
this.onDisconnected?.('max_reconnect_attempts_reached');
|
||||
return;
|
||||
}
|
||||
|
||||
this.reconnectAttempts++;
|
||||
logger.info(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
||||
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, this.reconnectInterval));
|
||||
|
||||
// 重新连接
|
||||
await this.connect();
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`重连失败:`, error);
|
||||
// 继续尝试重连
|
||||
this.attemptReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳
|
||||
*/
|
||||
private startHeartbeat(): void {
|
||||
this.heartbeatInterval = setInterval(async () => {
|
||||
try {
|
||||
await this.ping();
|
||||
} catch (error) {
|
||||
logger.warn('心跳失败:', error);
|
||||
}
|
||||
}, 30000); // 30秒心跳
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳
|
||||
*/
|
||||
private stopHeartbeat(): void {
|
||||
if (this.heartbeatInterval) {
|
||||
clearInterval(this.heartbeatInterval);
|
||||
this.heartbeatInterval = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
364
packages/network/src/transport/TsrpcServer.ts
Normal file
364
packages/network/src/transport/TsrpcServer.ts
Normal file
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* TSRPC 服务端传输层
|
||||
*
|
||||
* 封装TSRPC服务端功能,提供网络消息处理和客户端管理
|
||||
*/
|
||||
|
||||
import { WsServer } from 'tsrpc';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { serviceProto, ServiceType } from './protocols/serviceProto';
|
||||
import { NetworkConfig, NetworkMessage } from '../types/NetworkTypes';
|
||||
import {
|
||||
ReqJoinRoom, ResJoinRoom,
|
||||
ReqServerStatus, ResServerStatus,
|
||||
ReqPing, ResPing,
|
||||
MsgNetworkMessage,
|
||||
MsgSyncVar,
|
||||
MsgRpcCall,
|
||||
MsgNetworkObjectSpawn,
|
||||
MsgNetworkObjectDespawn,
|
||||
MsgClientDisconnected,
|
||||
MsgAuthorityChange
|
||||
} from './protocols/NetworkProtocols';
|
||||
|
||||
const logger = createLogger('TsrpcServer');
|
||||
|
||||
/**
|
||||
* 客户端连接信息
|
||||
*/
|
||||
interface ClientConnection {
|
||||
/** 客户端ID */
|
||||
id: number;
|
||||
/** 连接对象 */
|
||||
connection: any;
|
||||
/** 连接时间 */
|
||||
connectTime: number;
|
||||
/** 最后活跃时间 */
|
||||
lastActivity: number;
|
||||
/** 客户端信息 */
|
||||
clientInfo?: {
|
||||
version: string;
|
||||
platform: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TSRPC服务端包装器
|
||||
*/
|
||||
export class TsrpcServer {
|
||||
private server: WsServer<ServiceType> | null = null;
|
||||
private clients: Map<number, ClientConnection> = new Map();
|
||||
private nextClientId: number = 1;
|
||||
private config: NetworkConfig;
|
||||
private startTime: number = 0;
|
||||
|
||||
/** 统计信息 */
|
||||
private stats = {
|
||||
messagesSent: 0,
|
||||
messagesReceived: 0,
|
||||
bytesSent: 0,
|
||||
bytesReceived: 0
|
||||
};
|
||||
|
||||
/** 事件处理器 */
|
||||
public onClientConnected?: (clientId: number) => void;
|
||||
public onClientDisconnected?: (clientId: number, reason?: string) => void;
|
||||
public onMessage?: (message: NetworkMessage, fromClientId: number) => void;
|
||||
|
||||
constructor(config: NetworkConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动服务端
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
if (this.server) {
|
||||
throw new Error('服务端已经在运行中');
|
||||
}
|
||||
|
||||
// 创建TSRPC WebSocket服务端
|
||||
this.server = new WsServer(serviceProto, {
|
||||
port: this.config.port || 7777
|
||||
});
|
||||
|
||||
this.startTime = Date.now();
|
||||
this.setupApiHandlers();
|
||||
this.setupMessageHandlers();
|
||||
this.setupConnectionHandlers();
|
||||
|
||||
// 启动服务端
|
||||
await this.server.start();
|
||||
logger.info(`TSRPC服务端已启动,端口: ${this.config.port}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止服务端
|
||||
*/
|
||||
public async stop(): Promise<void> {
|
||||
if (this.server) {
|
||||
await this.server.stop();
|
||||
this.server = null;
|
||||
this.clients.clear();
|
||||
logger.info('TSRPC服务端已停止');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定客户端发送消息
|
||||
*/
|
||||
public sendToClient(clientId: number, message: any): void {
|
||||
const client = this.clients.get(clientId);
|
||||
if (!client) {
|
||||
logger.warn(`客户端不存在: ${clientId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 发送消息给指定连接
|
||||
this.server?.broadcastMsg(message.type, message.data || message, [client.connection]);
|
||||
this.stats.messagesSent++;
|
||||
logger.debug(`向客户端 ${clientId} 发送消息: ${message.type || 'unknown'}`);
|
||||
} catch (error) {
|
||||
logger.error(`向客户端 ${clientId} 发送消息失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向所有客户端广播消息
|
||||
*/
|
||||
public broadcast(message: any, excludeClientId?: number): void {
|
||||
if (!this.server) {
|
||||
logger.warn('服务端未启动,无法广播消息');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (excludeClientId) {
|
||||
// 排除指定客户端
|
||||
const targetConnections = Array.from(this.clients.entries())
|
||||
.filter(([clientId, client]) => clientId !== excludeClientId)
|
||||
.map(([clientId, client]) => client.connection);
|
||||
|
||||
this.server.broadcastMsg(message.type, message.data || message, targetConnections);
|
||||
this.stats.messagesSent += targetConnections.length;
|
||||
} else {
|
||||
// 广播给所有客户端
|
||||
this.server.broadcastMsg(message.type, message.data || message);
|
||||
this.stats.messagesSent += this.clients.size;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('广播消息失败:', error);
|
||||
}
|
||||
|
||||
logger.debug(`广播消息给 ${this.clients.size} 个客户端: ${message.type || 'unknown'}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接的客户端列表
|
||||
*/
|
||||
public getConnectedClients(): number[] {
|
||||
return Array.from(this.clients.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端数量
|
||||
*/
|
||||
public getClientCount(): number {
|
||||
return this.clients.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务端统计信息
|
||||
*/
|
||||
public getStats() {
|
||||
return {
|
||||
...this.stats,
|
||||
clientCount: this.clients.size,
|
||||
uptime: Date.now() - this.startTime
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置API处理器
|
||||
*/
|
||||
private setupApiHandlers(): void {
|
||||
if (!this.server) return;
|
||||
|
||||
// 客户端加入房间
|
||||
this.server.implementApi('network/JoinRoom', call => {
|
||||
const clientId = this.nextClientId++;
|
||||
const client: ClientConnection = {
|
||||
id: clientId,
|
||||
connection: call.conn,
|
||||
connectTime: Date.now(),
|
||||
lastActivity: Date.now(),
|
||||
clientInfo: call.req.clientInfo
|
||||
};
|
||||
|
||||
this.clients.set(clientId, client);
|
||||
logger.info(`客户端 ${clientId} 连接成功`);
|
||||
|
||||
// 通知上层
|
||||
this.onClientConnected?.(clientId);
|
||||
|
||||
// 返回响应
|
||||
call.succ({
|
||||
clientId,
|
||||
roomId: call.req.roomId || 'default',
|
||||
serverInfo: {
|
||||
version: '1.0.0',
|
||||
syncRate: this.config.syncRate || 20
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 服务端状态查询
|
||||
this.server.implementApi('network/ServerStatus', call => {
|
||||
const stats = this.getStats();
|
||||
call.succ({
|
||||
clientCount: stats.clientCount,
|
||||
networkObjectCount: 0, // 这里需要从NetworkRegistry获取
|
||||
uptime: stats.uptime,
|
||||
networkStats: {
|
||||
messagesSent: stats.messagesSent,
|
||||
messagesReceived: stats.messagesReceived,
|
||||
bytesSent: stats.bytesSent,
|
||||
bytesReceived: stats.bytesReceived
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 心跳检测
|
||||
this.server.implementApi('network/Ping', call => {
|
||||
call.succ({
|
||||
serverTimestamp: Date.now(),
|
||||
clientTimestamp: call.req.timestamp
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置消息处理器
|
||||
*/
|
||||
private setupMessageHandlers(): void {
|
||||
if (!this.server) return;
|
||||
|
||||
// 网络消息处理
|
||||
this.server.listenMsg('network/NetworkMessage', msg => {
|
||||
const clientId = this.getClientIdByConnection(msg.conn);
|
||||
if (clientId) {
|
||||
this.stats.messagesReceived++;
|
||||
this.updateClientActivity(clientId);
|
||||
|
||||
// 转换为内部消息格式
|
||||
const networkMessage: NetworkMessage = {
|
||||
type: msg.msg.type,
|
||||
networkId: msg.msg.networkId,
|
||||
data: msg.msg.data,
|
||||
timestamp: msg.msg.timestamp
|
||||
};
|
||||
|
||||
this.onMessage?.(networkMessage, clientId);
|
||||
}
|
||||
});
|
||||
|
||||
// SyncVar消息处理
|
||||
this.server.listenMsg('network/SyncVar', msg => {
|
||||
const clientId = this.getClientIdByConnection(msg.conn);
|
||||
if (clientId) {
|
||||
this.stats.messagesReceived++;
|
||||
this.updateClientActivity(clientId);
|
||||
|
||||
// 转换并广播给其他客户端
|
||||
const syncVarMessage: MsgSyncVar = msg.msg;
|
||||
this.broadcast({
|
||||
type: 'network/SyncVar',
|
||||
data: syncVarMessage
|
||||
}, clientId);
|
||||
}
|
||||
});
|
||||
|
||||
// RPC调用消息处理
|
||||
this.server.listenMsg('network/RpcCall', msg => {
|
||||
const clientId = this.getClientIdByConnection(msg.conn);
|
||||
if (clientId) {
|
||||
this.stats.messagesReceived++;
|
||||
this.updateClientActivity(clientId);
|
||||
|
||||
const rpcMessage: MsgRpcCall = msg.msg;
|
||||
|
||||
if (rpcMessage.isClientRpc) {
|
||||
// 服务端到客户端的RPC,广播给所有客户端
|
||||
this.broadcast({
|
||||
type: 'network/RpcCall',
|
||||
data: rpcMessage
|
||||
});
|
||||
} else {
|
||||
// 客户端到服务端的Command,只在服务端处理
|
||||
const networkMessage: NetworkMessage = {
|
||||
type: 'rpc',
|
||||
networkId: rpcMessage.networkId,
|
||||
data: {
|
||||
componentType: rpcMessage.componentType,
|
||||
methodName: rpcMessage.methodName,
|
||||
args: rpcMessage.args
|
||||
},
|
||||
timestamp: rpcMessage.timestamp
|
||||
};
|
||||
|
||||
this.onMessage?.(networkMessage, clientId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接处理器
|
||||
*/
|
||||
private setupConnectionHandlers(): void {
|
||||
if (!this.server) return;
|
||||
|
||||
// 连接断开处理
|
||||
this.server.flows.postDisconnectFlow.push(conn => {
|
||||
const clientId = this.getClientIdByConnection(conn);
|
||||
if (clientId) {
|
||||
this.clients.delete(clientId);
|
||||
logger.info(`客户端 ${clientId} 断开连接`);
|
||||
|
||||
// 通知其他客户端
|
||||
this.broadcast({
|
||||
type: 'network/ClientDisconnected',
|
||||
data: { clientId, reason: 'disconnected' }
|
||||
});
|
||||
|
||||
// 通知上层
|
||||
this.onClientDisconnected?.(clientId, 'disconnected');
|
||||
}
|
||||
|
||||
return conn;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据连接对象获取客户端ID
|
||||
*/
|
||||
private getClientIdByConnection(conn: any): number | null {
|
||||
for (const [clientId, client] of this.clients) {
|
||||
if (client.connection === conn) {
|
||||
return clientId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客户端活跃时间
|
||||
*/
|
||||
private updateClientActivity(clientId: number): void {
|
||||
const client = this.clients.get(clientId);
|
||||
if (client) {
|
||||
client.lastActivity = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
338
packages/network/src/transport/TsrpcTransport.ts
Normal file
338
packages/network/src/transport/TsrpcTransport.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* TSRPC 传输管理器
|
||||
*
|
||||
* 统一管理TSRPC服务端和客户端,提供通用的传输接口
|
||||
*/
|
||||
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { TsrpcServer } from './TsrpcServer';
|
||||
import { TsrpcClient } from './TsrpcClient';
|
||||
import { NetworkConfig, NetworkMessage, NetworkSide } from '../types/NetworkTypes';
|
||||
|
||||
const logger = createLogger('TsrpcTransport');
|
||||
|
||||
/**
|
||||
* 传输事件处理器
|
||||
*/
|
||||
export interface TransportEventHandlers {
|
||||
/** 连接建立 */
|
||||
onConnected?: () => void;
|
||||
/** 连接断开 */
|
||||
onDisconnected?: (reason?: string) => void;
|
||||
/** 客户端连接(仅服务端) */
|
||||
onClientConnected?: (clientId: number) => void;
|
||||
/** 客户端断开(仅服务端) */
|
||||
onClientDisconnected?: (clientId: number, reason?: string) => void;
|
||||
/** 收到消息 */
|
||||
onMessage?: (message: NetworkMessage, fromClientId?: number) => void;
|
||||
/** 发生错误 */
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* TSRPC传输层管理器
|
||||
*/
|
||||
export class TsrpcTransport {
|
||||
private server: TsrpcServer | null = null;
|
||||
private client: TsrpcClient | null = null;
|
||||
private networkSide: NetworkSide = 'client';
|
||||
private config: NetworkConfig;
|
||||
private eventHandlers: TransportEventHandlers = {};
|
||||
|
||||
constructor(config: NetworkConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动服务端
|
||||
*/
|
||||
public async startServer(): Promise<void> {
|
||||
if (this.server) {
|
||||
throw new Error('服务端已经在运行中');
|
||||
}
|
||||
|
||||
this.networkSide = 'server';
|
||||
this.server = new TsrpcServer(this.config);
|
||||
|
||||
// 设置服务端事件处理器
|
||||
this.server.onClientConnected = (clientId) => {
|
||||
logger.info(`客户端 ${clientId} 已连接`);
|
||||
this.eventHandlers.onClientConnected?.(clientId);
|
||||
};
|
||||
|
||||
this.server.onClientDisconnected = (clientId, reason) => {
|
||||
logger.info(`客户端 ${clientId} 已断开: ${reason}`);
|
||||
this.eventHandlers.onClientDisconnected?.(clientId, reason);
|
||||
};
|
||||
|
||||
this.server.onMessage = (message, fromClientId) => {
|
||||
this.eventHandlers.onMessage?.(message, fromClientId);
|
||||
};
|
||||
|
||||
await this.server.start();
|
||||
logger.info('TSRPC服务端已启动');
|
||||
this.eventHandlers.onConnected?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到服务端
|
||||
*/
|
||||
public async connectToServer(): Promise<void> {
|
||||
if (this.client) {
|
||||
throw new Error('客户端已经连接或正在连接中');
|
||||
}
|
||||
|
||||
this.networkSide = 'client';
|
||||
this.client = new TsrpcClient(this.config);
|
||||
|
||||
// 设置客户端事件处理器
|
||||
this.client.onConnected = () => {
|
||||
logger.info('已连接到服务端');
|
||||
this.eventHandlers.onConnected?.();
|
||||
};
|
||||
|
||||
this.client.onDisconnected = (reason) => {
|
||||
logger.info(`已断开连接: ${reason}`);
|
||||
this.eventHandlers.onDisconnected?.(reason);
|
||||
};
|
||||
|
||||
this.client.onMessage = (message) => {
|
||||
this.eventHandlers.onMessage?.(message);
|
||||
};
|
||||
|
||||
this.client.onError = (error) => {
|
||||
logger.error('客户端错误:', error);
|
||||
this.eventHandlers.onError?.(error);
|
||||
};
|
||||
|
||||
await this.client.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接/停止服务端
|
||||
*/
|
||||
public async disconnect(): Promise<void> {
|
||||
if (this.server) {
|
||||
await this.server.stop();
|
||||
this.server = null;
|
||||
logger.info('TSRPC服务端已停止');
|
||||
}
|
||||
|
||||
if (this.client) {
|
||||
await this.client.disconnect();
|
||||
this.client = null;
|
||||
logger.info('TSRPC客户端已断开');
|
||||
}
|
||||
|
||||
this.eventHandlers.onDisconnected?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
public async sendMessage(message: NetworkMessage, targetClientId?: number): Promise<void> {
|
||||
if (this.networkSide === 'server' && this.server) {
|
||||
// 服务端模式:发送给指定客户端或广播
|
||||
if (targetClientId) {
|
||||
this.server.sendToClient(targetClientId, {
|
||||
type: 'network/NetworkMessage',
|
||||
data: message
|
||||
});
|
||||
} else {
|
||||
this.server.broadcast({
|
||||
type: 'network/NetworkMessage',
|
||||
data: message
|
||||
});
|
||||
}
|
||||
} else if (this.networkSide === 'client' && this.client) {
|
||||
// 客户端模式:发送给服务端
|
||||
await this.client.sendMessage(message);
|
||||
} else {
|
||||
throw new Error('传输层未初始化或状态错误');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送SyncVar消息
|
||||
*/
|
||||
public async sendSyncVar(
|
||||
networkId: number,
|
||||
componentType: string,
|
||||
propertyName: string,
|
||||
value: any,
|
||||
targetClientId?: number
|
||||
): Promise<void> {
|
||||
if (this.networkSide === 'server' && this.server) {
|
||||
const message = {
|
||||
type: 'network/SyncVar',
|
||||
data: {
|
||||
networkId,
|
||||
componentType,
|
||||
propertyName,
|
||||
value,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
if (targetClientId) {
|
||||
this.server.sendToClient(targetClientId, message);
|
||||
} else {
|
||||
this.server.broadcast(message);
|
||||
}
|
||||
} else if (this.networkSide === 'client' && this.client) {
|
||||
await this.client.sendSyncVar(networkId, componentType, propertyName, value);
|
||||
} else {
|
||||
throw new Error('传输层未初始化或状态错误');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送RPC消息
|
||||
*/
|
||||
public async sendRpcCall(
|
||||
networkId: number,
|
||||
componentType: string,
|
||||
methodName: string,
|
||||
args: any[],
|
||||
isClientRpc: boolean,
|
||||
targetClientId?: number
|
||||
): Promise<void> {
|
||||
if (this.networkSide === 'server' && this.server) {
|
||||
const message = {
|
||||
type: 'network/RpcCall',
|
||||
data: {
|
||||
networkId,
|
||||
componentType,
|
||||
methodName,
|
||||
args,
|
||||
isClientRpc,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
if (targetClientId) {
|
||||
this.server.sendToClient(targetClientId, message);
|
||||
} else {
|
||||
this.server.broadcast(message);
|
||||
}
|
||||
} else if (this.networkSide === 'client' && this.client) {
|
||||
await this.client.sendRpcCall(networkId, componentType, methodName, args, isClientRpc);
|
||||
} else {
|
||||
throw new Error('传输层未初始化或状态错误');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
public isConnected(): boolean {
|
||||
if (this.networkSide === 'server' && this.server) {
|
||||
return true; // 服务端启动即为连接状态
|
||||
} else if (this.networkSide === 'client' && this.client) {
|
||||
return this.client.isConnected();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网络端类型
|
||||
*/
|
||||
public getNetworkSide(): NetworkSide {
|
||||
return this.networkSide;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端ID(仅客户端模式)
|
||||
*/
|
||||
public getClientId(): number {
|
||||
if (this.networkSide === 'client' && this.client) {
|
||||
return this.client.getClientId();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接的客户端列表(仅服务端模式)
|
||||
*/
|
||||
public getConnectedClients(): number[] {
|
||||
if (this.networkSide === 'server' && this.server) {
|
||||
return this.server.getConnectedClients();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端数量(仅服务端模式)
|
||||
*/
|
||||
public getClientCount(): number {
|
||||
if (this.networkSide === 'server' && this.server) {
|
||||
return this.server.getClientCount();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats() {
|
||||
if (this.networkSide === 'server' && this.server) {
|
||||
return this.server.getStats();
|
||||
} else if (this.networkSide === 'client' && this.client) {
|
||||
const clientStats = this.client.getStats();
|
||||
return {
|
||||
messagesSent: clientStats.messagesSent,
|
||||
messagesReceived: clientStats.messagesReceived,
|
||||
bytesSent: clientStats.bytesSent,
|
||||
bytesReceived: clientStats.bytesReceived,
|
||||
clientCount: 0,
|
||||
uptime: 0
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
messagesSent: 0,
|
||||
messagesReceived: 0,
|
||||
bytesSent: 0,
|
||||
bytesReceived: 0,
|
||||
clientCount: 0,
|
||||
uptime: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询服务端状态(仅客户端模式)
|
||||
*/
|
||||
public async getServerStatus() {
|
||||
if (this.networkSide === 'client' && this.client) {
|
||||
return await this.client.getServerStatus();
|
||||
}
|
||||
throw new Error('只能在客户端模式下查询服务端状态');
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送心跳(仅客户端模式)
|
||||
*/
|
||||
public async ping(): Promise<number> {
|
||||
if (this.networkSide === 'client' && this.client) {
|
||||
return await this.client.ping();
|
||||
}
|
||||
throw new Error('只能在客户端模式下发送心跳');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
public setEventHandlers(handlers: TransportEventHandlers): void {
|
||||
this.eventHandlers = { ...this.eventHandlers, ...handlers };
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单个事件处理器
|
||||
*/
|
||||
public on<K extends keyof TransportEventHandlers>(
|
||||
event: K,
|
||||
handler: TransportEventHandlers[K]
|
||||
): void {
|
||||
this.eventHandlers[event] = handler;
|
||||
}
|
||||
}
|
||||
9
packages/network/src/transport/index.ts
Normal file
9
packages/network/src/transport/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 传输层模块导出
|
||||
*/
|
||||
|
||||
export { TsrpcTransport } from './TsrpcTransport';
|
||||
export { TsrpcServer } from './TsrpcServer';
|
||||
export { TsrpcClient } from './TsrpcClient';
|
||||
export * from './protocols/NetworkProtocols';
|
||||
export { serviceProto, ServiceType } from './protocols/serviceProto';
|
||||
184
packages/network/src/transport/protocols/NetworkProtocols.ts
Normal file
184
packages/network/src/transport/protocols/NetworkProtocols.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* 网络库 TSRPC 协议定义
|
||||
* 定义所有网络消息的类型和结构
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 客户端连接请求
|
||||
*/
|
||||
export interface ReqJoinRoom {
|
||||
/** 房间ID,可选 */
|
||||
roomId?: string;
|
||||
/** 客户端信息 */
|
||||
clientInfo?: {
|
||||
/** 客户端版本 */
|
||||
version: string;
|
||||
/** 客户端平台 */
|
||||
platform: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端连接响应
|
||||
*/
|
||||
export interface ResJoinRoom {
|
||||
/** 分配的客户端ID */
|
||||
clientId: number;
|
||||
/** 房间ID */
|
||||
roomId: string;
|
||||
/** 服务端信息 */
|
||||
serverInfo: {
|
||||
/** 服务端版本 */
|
||||
version: string;
|
||||
/** 同步频率 */
|
||||
syncRate: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络消息广播
|
||||
*/
|
||||
export interface MsgNetworkMessage {
|
||||
/** 消息类型 */
|
||||
type: 'syncvar' | 'rpc';
|
||||
/** 网络对象ID */
|
||||
networkId: number;
|
||||
/** 消息数据 */
|
||||
data: any;
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 发送者客户端ID */
|
||||
senderId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar 同步消息
|
||||
*/
|
||||
export interface MsgSyncVar {
|
||||
/** 网络对象ID */
|
||||
networkId: number;
|
||||
/** 组件类型名 */
|
||||
componentType: string;
|
||||
/** 属性名 */
|
||||
propertyName: string;
|
||||
/** 新的属性值 */
|
||||
value: any;
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPC 调用消息
|
||||
*/
|
||||
export interface MsgRpcCall {
|
||||
/** 网络对象ID */
|
||||
networkId: number;
|
||||
/** 组件类型名 */
|
||||
componentType: string;
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** 参数 */
|
||||
args: any[];
|
||||
/** 是否为客户端RPC */
|
||||
isClientRpc: boolean;
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络对象生成通知
|
||||
*/
|
||||
export interface MsgNetworkObjectSpawn {
|
||||
/** 网络对象ID */
|
||||
networkId: number;
|
||||
/** 实体名称 */
|
||||
entityName: string;
|
||||
/** 所有者客户端ID */
|
||||
ownerId: number;
|
||||
/** 是否拥有权威 */
|
||||
hasAuthority: boolean;
|
||||
/** 初始组件数据 */
|
||||
components: Array<{
|
||||
/** 组件类型 */
|
||||
type: string;
|
||||
/** 组件数据 */
|
||||
data: any;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络对象销毁通知
|
||||
*/
|
||||
export interface MsgNetworkObjectDespawn {
|
||||
/** 网络对象ID */
|
||||
networkId: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端断开连接通知
|
||||
*/
|
||||
export interface MsgClientDisconnected {
|
||||
/** 断开连接的客户端ID */
|
||||
clientId: number;
|
||||
/** 断开原因 */
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权威转移通知
|
||||
*/
|
||||
export interface MsgAuthorityChange {
|
||||
/** 网络对象ID */
|
||||
networkId: number;
|
||||
/** 新的权威所有者ID */
|
||||
newOwnerId: number;
|
||||
/** 是否拥有权威 */
|
||||
hasAuthority: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端状态查询请求
|
||||
*/
|
||||
export interface ReqServerStatus {}
|
||||
|
||||
/**
|
||||
* 服务端状态响应
|
||||
*/
|
||||
export interface ResServerStatus {
|
||||
/** 连接的客户端数量 */
|
||||
clientCount: number;
|
||||
/** 网络对象数量 */
|
||||
networkObjectCount: number;
|
||||
/** 服务器运行时间(毫秒) */
|
||||
uptime: number;
|
||||
/** 网络统计 */
|
||||
networkStats: {
|
||||
/** 发送的消息数 */
|
||||
messagesSent: number;
|
||||
/** 接收的消息数 */
|
||||
messagesReceived: number;
|
||||
/** 发送的字节数 */
|
||||
bytesSent: number;
|
||||
/** 接收的字节数 */
|
||||
bytesReceived: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳请求
|
||||
*/
|
||||
export interface ReqPing {
|
||||
/** 客户端时间戳 */
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳响应
|
||||
*/
|
||||
export interface ResPing {
|
||||
/** 服务端时间戳 */
|
||||
serverTimestamp: number;
|
||||
/** 客户端时间戳(回传) */
|
||||
clientTimestamp: number;
|
||||
}
|
||||
108
packages/network/src/transport/protocols/serviceProto.ts
Normal file
108
packages/network/src/transport/protocols/serviceProto.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* TSRPC 服务协议定义
|
||||
* 定义API调用和消息类型
|
||||
*/
|
||||
|
||||
import { ServiceProto } from 'tsrpc';
|
||||
import {
|
||||
ReqJoinRoom, ResJoinRoom,
|
||||
ReqServerStatus, ResServerStatus,
|
||||
ReqPing, ResPing,
|
||||
MsgNetworkMessage,
|
||||
MsgSyncVar,
|
||||
MsgRpcCall,
|
||||
MsgNetworkObjectSpawn,
|
||||
MsgNetworkObjectDespawn,
|
||||
MsgClientDisconnected,
|
||||
MsgAuthorityChange
|
||||
} from './NetworkProtocols';
|
||||
|
||||
/**
|
||||
* 网络服务协议
|
||||
* 定义所有可用的API和消息类型
|
||||
*/
|
||||
export const serviceProto: ServiceProto<ServiceType> = {
|
||||
"services": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "network/JoinRoom",
|
||||
"type": "api"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "network/ServerStatus",
|
||||
"type": "api"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "network/Ping",
|
||||
"type": "api"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "network/NetworkMessage",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "network/SyncVar",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "network/RpcCall",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "network/NetworkObjectSpawn",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "network/NetworkObjectDespawn",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "network/ClientDisconnected",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "network/AuthorityChange",
|
||||
"type": "msg"
|
||||
}
|
||||
],
|
||||
"types": {}
|
||||
};
|
||||
|
||||
/**
|
||||
* 服务类型定义
|
||||
* 用于类型安全的API调用和消息发送
|
||||
*/
|
||||
export interface ServiceType {
|
||||
api: {
|
||||
"network/JoinRoom": {
|
||||
req: ReqJoinRoom;
|
||||
res: ResJoinRoom;
|
||||
};
|
||||
"network/ServerStatus": {
|
||||
req: ReqServerStatus;
|
||||
res: ResServerStatus;
|
||||
};
|
||||
"network/Ping": {
|
||||
req: ReqPing;
|
||||
res: ResPing;
|
||||
};
|
||||
};
|
||||
msg: {
|
||||
"network/NetworkMessage": MsgNetworkMessage;
|
||||
"network/SyncVar": MsgSyncVar;
|
||||
"network/RpcCall": MsgRpcCall;
|
||||
"network/NetworkObjectSpawn": MsgNetworkObjectSpawn;
|
||||
"network/NetworkObjectDespawn": MsgNetworkObjectDespawn;
|
||||
"network/ClientDisconnected": MsgClientDisconnected;
|
||||
"network/AuthorityChange": MsgAuthorityChange;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user