重构network库(mvp版本)搭建基础设施和核心接口
定义ITransport/ISerializer/INetworkMessage接口 NetworkIdentity组件 基础事件定义
This commit is contained in:
@@ -1,179 +0,0 @@
|
||||
/**
|
||||
* 客户端网络行为基类
|
||||
*
|
||||
* 类似Unity Mirror的NetworkBehaviour,提供网络功能
|
||||
*/
|
||||
|
||||
import { Component, Entity } from '@esengine/ecs-framework';
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
import { NetworkClient } from './NetworkClient';
|
||||
import { NetworkIdentity } from './NetworkIdentity';
|
||||
|
||||
/**
|
||||
* 客户端网络行为基类
|
||||
*/
|
||||
export abstract class ClientNetworkBehaviour extends Component {
|
||||
/** 网络标识组件 */
|
||||
protected networkIdentity: NetworkIdentity | null = null;
|
||||
/** 网络客户端实例 */
|
||||
protected networkClient: NetworkClient | null = null;
|
||||
|
||||
/**
|
||||
* 组件初始化
|
||||
*/
|
||||
initialize(): void {
|
||||
|
||||
// 获取网络标识组件
|
||||
this.networkIdentity = this.entity.getComponent(NetworkIdentity);
|
||||
if (!this.networkIdentity) {
|
||||
throw new Error('NetworkBehaviour requires NetworkIdentity component');
|
||||
}
|
||||
|
||||
// 从全局获取网络客户端实例
|
||||
this.networkClient = this.getNetworkClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网络客户端实例
|
||||
*/
|
||||
protected getNetworkClient(): NetworkClient | null {
|
||||
// 这里需要实现从全局管理器获取客户端实例的逻辑
|
||||
// 暂时返回null,在实际使用时需要通过单例模式或依赖注入获取
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为本地玩家
|
||||
*/
|
||||
get isLocalPlayer(): boolean {
|
||||
return this.networkIdentity?.isLocalPlayer ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为服务器权威
|
||||
*/
|
||||
get hasAuthority(): boolean {
|
||||
return this.networkIdentity?.hasAuthority ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络ID
|
||||
*/
|
||||
get networkId(): string {
|
||||
return this.networkIdentity?.networkId ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已连接
|
||||
*/
|
||||
get isConnected(): boolean {
|
||||
return this.networkClient?.isInRoom() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送RPC到服务器
|
||||
*/
|
||||
protected async sendServerRpc(methodName: string, ...args: NetworkValue[]): Promise<NetworkValue> {
|
||||
if (!this.networkClient || !this.networkIdentity) {
|
||||
throw new Error('Network client or identity not available');
|
||||
}
|
||||
|
||||
return this.networkClient.sendRpc(this.networkIdentity.networkId, methodName, args, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送不可靠RPC到服务器
|
||||
*/
|
||||
protected async sendServerRpcUnreliable(methodName: string, ...args: NetworkValue[]): Promise<void> {
|
||||
if (!this.networkClient || !this.networkIdentity) {
|
||||
throw new Error('Network client or identity not available');
|
||||
}
|
||||
|
||||
await this.networkClient.sendRpc(this.networkIdentity.networkId, methodName, args, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新SyncVar
|
||||
*/
|
||||
protected async updateSyncVar(fieldName: string, value: NetworkValue): Promise<void> {
|
||||
if (!this.networkClient || !this.networkIdentity) {
|
||||
throw new Error('Network client or identity not available');
|
||||
}
|
||||
|
||||
await this.networkClient.updateSyncVar(this.networkIdentity.networkId, fieldName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当收到RPC调用时
|
||||
*/
|
||||
onRpcReceived(methodName: string, args: NetworkValue[]): void {
|
||||
// 尝试调用对应的方法
|
||||
const method = (this as any)[methodName];
|
||||
if (typeof method === 'function') {
|
||||
try {
|
||||
method.apply(this, args);
|
||||
} catch (error) {
|
||||
console.error(`Error calling RPC method ${methodName}:`, error);
|
||||
}
|
||||
} else {
|
||||
console.warn(`RPC method ${methodName} not found on ${this.constructor.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当SyncVar更新时
|
||||
*/
|
||||
onSyncVarChanged(fieldName: string, oldValue: NetworkValue, newValue: NetworkValue): void {
|
||||
// 子类可以重写此方法来处理SyncVar变化
|
||||
}
|
||||
|
||||
/**
|
||||
* 当获得权威时
|
||||
*/
|
||||
onStartAuthority(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 当失去权威时
|
||||
*/
|
||||
onStopAuthority(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 当成为本地玩家时
|
||||
*/
|
||||
onStartLocalPlayer(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 当不再是本地玩家时
|
||||
*/
|
||||
onStopLocalPlayer(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络启动时调用
|
||||
*/
|
||||
onNetworkStart(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络停止时调用
|
||||
*/
|
||||
onNetworkStop(): void {
|
||||
// 子类可以重写此方法
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件销毁
|
||||
*/
|
||||
onDestroy(): void {
|
||||
this.networkIdentity = null;
|
||||
this.networkClient = null;
|
||||
}
|
||||
}
|
||||
@@ -1,638 +0,0 @@
|
||||
/**
|
||||
* 网络客户端主类
|
||||
*
|
||||
* 管理连接、认证、房间加入等功能
|
||||
*/
|
||||
|
||||
import { Scene, EntityManager, Emitter, ITimer, Core } from '@esengine/ecs-framework';
|
||||
import {
|
||||
NetworkIdentity as SharedNetworkIdentity,
|
||||
NetworkValue,
|
||||
RpcMessage,
|
||||
SyncVarMessage
|
||||
} from '@esengine/ecs-framework-network-shared';
|
||||
import {
|
||||
ClientTransport,
|
||||
WebSocketClientTransport,
|
||||
HttpClientTransport,
|
||||
ConnectionState,
|
||||
ClientMessage,
|
||||
ClientTransportConfig,
|
||||
WebSocketClientConfig,
|
||||
HttpClientConfig
|
||||
} from '../transport';
|
||||
|
||||
/**
|
||||
* 网络客户端配置
|
||||
*/
|
||||
export interface NetworkClientConfig {
|
||||
/** 传输类型 */
|
||||
transport: 'websocket' | 'http';
|
||||
/** 传输配置 */
|
||||
transportConfig: WebSocketClientConfig | HttpClientConfig;
|
||||
/** 是否启用预测 */
|
||||
enablePrediction?: boolean;
|
||||
/** 预测缓冲区大小 */
|
||||
predictionBuffer?: number;
|
||||
/** 是否启用插值 */
|
||||
enableInterpolation?: boolean;
|
||||
/** 插值延迟(毫秒) */
|
||||
interpolationDelay?: number;
|
||||
/** 网络对象同步间隔(毫秒) */
|
||||
syncInterval?: number;
|
||||
/** 是否启用调试 */
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/** 用户ID */
|
||||
userId: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 用户数据 */
|
||||
data?: NetworkValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间信息
|
||||
*/
|
||||
export interface RoomInfo {
|
||||
/** 房间ID */
|
||||
roomId: string;
|
||||
/** 房间名称 */
|
||||
name: string;
|
||||
/** 当前人数 */
|
||||
playerCount: number;
|
||||
/** 最大人数 */
|
||||
maxPlayers: number;
|
||||
/** 房间元数据 */
|
||||
metadata?: NetworkValue;
|
||||
/** 是否私有房间 */
|
||||
isPrivate?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证消息
|
||||
*/
|
||||
export interface AuthMessage {
|
||||
action: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
userData?: NetworkValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间消息
|
||||
*/
|
||||
export interface RoomMessage {
|
||||
action: string;
|
||||
roomId?: string;
|
||||
name?: string;
|
||||
maxPlayers?: number;
|
||||
metadata?: NetworkValue;
|
||||
isPrivate?: boolean;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络客户端事件
|
||||
*/
|
||||
export interface NetworkClientEvents {
|
||||
/** 连接建立 */
|
||||
'connected': () => void;
|
||||
/** 连接断开 */
|
||||
'disconnected': (reason: string) => void;
|
||||
/** 认证成功 */
|
||||
'authenticated': (userInfo: UserInfo) => void;
|
||||
/** 加入房间成功 */
|
||||
'joined-room': (roomInfo: RoomInfo) => void;
|
||||
/** 离开房间 */
|
||||
'left-room': (roomId: string) => void;
|
||||
/** 房间列表更新 */
|
||||
'room-list-updated': (rooms: RoomInfo[]) => void;
|
||||
/** 玩家加入房间 */
|
||||
'player-joined': (userId: string, userInfo: UserInfo) => void;
|
||||
/** 玩家离开房间 */
|
||||
'player-left': (userId: string) => void;
|
||||
/** 网络对象创建 */
|
||||
'network-object-created': (networkId: string, data: NetworkValue) => void;
|
||||
/** 网络对象销毁 */
|
||||
'network-object-destroyed': (networkId: string) => void;
|
||||
/** SyncVar 更新 */
|
||||
'syncvar-updated': (networkId: string, fieldName: string, value: NetworkValue) => void;
|
||||
/** RPC 调用 */
|
||||
'rpc-received': (networkId: string, methodName: string, args: NetworkValue[]) => void;
|
||||
/** 错误发生 */
|
||||
'error': (error: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络客户端主类
|
||||
*/
|
||||
export class NetworkClient {
|
||||
private transport: ClientTransport;
|
||||
private config: NetworkClientConfig;
|
||||
private currentUser: UserInfo | null = null;
|
||||
private currentRoom: RoomInfo | null = null;
|
||||
private availableRooms: Map<string, RoomInfo> = new Map();
|
||||
private networkObjects: Map<string, SharedNetworkIdentity> = new Map();
|
||||
private pendingRpcs: Map<string, { resolve: Function; reject: Function; timeout: ITimer<any> }> = new Map();
|
||||
private scene: Scene | null = null;
|
||||
private eventEmitter: Emitter<keyof NetworkClientEvents, any>;
|
||||
|
||||
constructor(config: NetworkClientConfig) {
|
||||
this.eventEmitter = new Emitter();
|
||||
|
||||
this.config = {
|
||||
enablePrediction: true,
|
||||
predictionBuffer: 64,
|
||||
enableInterpolation: true,
|
||||
interpolationDelay: 100,
|
||||
syncInterval: 50,
|
||||
debug: false,
|
||||
...config
|
||||
};
|
||||
|
||||
this.transport = this.createTransport();
|
||||
this.setupTransportEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建传输层
|
||||
*/
|
||||
private createTransport(): ClientTransport {
|
||||
switch (this.config.transport) {
|
||||
case 'websocket':
|
||||
return new WebSocketClientTransport(this.config.transportConfig as WebSocketClientConfig);
|
||||
case 'http':
|
||||
return new HttpClientTransport(this.config.transportConfig as HttpClientConfig);
|
||||
default:
|
||||
throw new Error(`Unsupported transport type: ${this.config.transport}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置传输层事件监听
|
||||
*/
|
||||
private setupTransportEvents(): void {
|
||||
this.transport.on('connected', () => {
|
||||
this.eventEmitter.emit('connected');
|
||||
});
|
||||
|
||||
this.transport.on('disconnected', (reason) => {
|
||||
this.handleDisconnected(reason);
|
||||
});
|
||||
|
||||
this.transport.on('message', (message) => {
|
||||
this.handleMessage(message);
|
||||
});
|
||||
|
||||
this.transport.on('error', (error) => {
|
||||
this.eventEmitter.emit('error', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到服务器
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
return this.transport.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
async disconnect(): Promise<void> {
|
||||
await this.transport.disconnect();
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户认证
|
||||
*/
|
||||
async authenticate(username: string, password?: string, userData?: NetworkValue): Promise<UserInfo> {
|
||||
if (!this.transport.isConnected()) {
|
||||
throw new Error('Not connected to server');
|
||||
}
|
||||
|
||||
const authMessage: AuthMessage = {
|
||||
action: 'login',
|
||||
username,
|
||||
password,
|
||||
userData
|
||||
};
|
||||
|
||||
const response = await this.sendRequestWithResponse('system', authMessage as any);
|
||||
|
||||
if (response.success && response.userInfo) {
|
||||
this.currentUser = response.userInfo as UserInfo;
|
||||
this.eventEmitter.emit('authenticated', this.currentUser);
|
||||
return this.currentUser;
|
||||
} else {
|
||||
throw new Error(response.error || 'Authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间列表
|
||||
*/
|
||||
async getRoomList(): Promise<RoomInfo[]> {
|
||||
if (!this.isAuthenticated()) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const roomMessage: RoomMessage = {
|
||||
action: 'list-rooms'
|
||||
};
|
||||
|
||||
const response = await this.sendRequestWithResponse('system', roomMessage as any);
|
||||
|
||||
if (response.success && response.rooms) {
|
||||
this.availableRooms.clear();
|
||||
response.rooms.forEach((room: RoomInfo) => {
|
||||
this.availableRooms.set(room.roomId, room);
|
||||
});
|
||||
|
||||
this.eventEmitter.emit('room-list-updated', response.rooms);
|
||||
return response.rooms;
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to get room list');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建房间
|
||||
*/
|
||||
async createRoom(name: string, maxPlayers: number = 8, metadata?: NetworkValue, isPrivate = false): Promise<RoomInfo> {
|
||||
if (!this.isAuthenticated()) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const roomMessage: RoomMessage = {
|
||||
action: 'create-room',
|
||||
name,
|
||||
maxPlayers,
|
||||
metadata,
|
||||
isPrivate
|
||||
};
|
||||
|
||||
const response = await this.sendRequestWithResponse('system', roomMessage as any);
|
||||
|
||||
if (response.success && response.room) {
|
||||
this.currentRoom = response.room as RoomInfo;
|
||||
this.eventEmitter.emit('joined-room', this.currentRoom);
|
||||
return this.currentRoom;
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to create room');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入房间
|
||||
*/
|
||||
async joinRoom(roomId: string, password?: string): Promise<RoomInfo> {
|
||||
if (!this.isAuthenticated()) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const roomMessage: RoomMessage = {
|
||||
action: 'join-room',
|
||||
roomId,
|
||||
password
|
||||
};
|
||||
|
||||
const response = await this.sendRequestWithResponse('system', roomMessage as any);
|
||||
|
||||
if (response.success && response.room) {
|
||||
this.currentRoom = response.room as RoomInfo;
|
||||
this.eventEmitter.emit('joined-room', this.currentRoom);
|
||||
return this.currentRoom;
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to join room');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 离开房间
|
||||
*/
|
||||
async leaveRoom(): Promise<void> {
|
||||
if (!this.currentRoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const roomMessage: RoomMessage = {
|
||||
action: 'leave-room',
|
||||
roomId: this.currentRoom.roomId
|
||||
};
|
||||
|
||||
try {
|
||||
await this.sendRequestWithResponse('system', roomMessage as any);
|
||||
} finally {
|
||||
const roomId = this.currentRoom.roomId;
|
||||
this.currentRoom = null;
|
||||
this.networkObjects.clear();
|
||||
this.eventEmitter.emit('left-room', roomId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送RPC调用
|
||||
*/
|
||||
async sendRpc(networkId: string, methodName: string, args: NetworkValue[] = [], reliable = true): Promise<NetworkValue> {
|
||||
if (!this.isInRoom()) {
|
||||
throw new Error('Not in a room');
|
||||
}
|
||||
|
||||
const rpcMessage: any = {
|
||||
networkId,
|
||||
methodName,
|
||||
args,
|
||||
isServer: false,
|
||||
messageId: this.generateMessageId()
|
||||
};
|
||||
|
||||
if (reliable) {
|
||||
return this.sendRequestWithResponse('rpc', rpcMessage);
|
||||
} else {
|
||||
await this.transport.sendMessage({
|
||||
type: 'rpc',
|
||||
data: rpcMessage as NetworkValue,
|
||||
reliable: false
|
||||
});
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新SyncVar
|
||||
*/
|
||||
async updateSyncVar(networkId: string, fieldName: string, value: NetworkValue): Promise<void> {
|
||||
if (!this.isInRoom()) {
|
||||
throw new Error('Not in a room');
|
||||
}
|
||||
|
||||
const syncMessage: any = {
|
||||
networkId,
|
||||
propertyName: fieldName,
|
||||
value,
|
||||
isServer: false
|
||||
};
|
||||
|
||||
await this.transport.sendMessage({
|
||||
type: 'syncvar',
|
||||
data: syncMessage as NetworkValue,
|
||||
reliable: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置ECS场景
|
||||
*/
|
||||
setScene(scene: Scene): void {
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
getCurrentUser(): UserInfo | null {
|
||||
return this.currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前房间信息
|
||||
*/
|
||||
getCurrentRoom(): RoomInfo | null {
|
||||
return this.currentRoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
getConnectionState(): ConnectionState {
|
||||
return this.transport.getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已认证
|
||||
*/
|
||||
isAuthenticated(): boolean {
|
||||
return this.currentUser !== null && this.transport.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在房间中
|
||||
*/
|
||||
isInRoom(): boolean {
|
||||
return this.isAuthenticated() && this.currentRoom !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网络对象
|
||||
*/
|
||||
getNetworkObject(networkId: string): SharedNetworkIdentity | null {
|
||||
return this.networkObjects.get(networkId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有网络对象
|
||||
*/
|
||||
getAllNetworkObjects(): SharedNetworkIdentity[] {
|
||||
return Array.from(this.networkObjects.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理断开连接
|
||||
*/
|
||||
private handleDisconnected(reason: string): void {
|
||||
this.cleanup();
|
||||
this.eventEmitter.emit('disconnected', reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理接收到的消息
|
||||
*/
|
||||
private handleMessage(message: ClientMessage): void {
|
||||
try {
|
||||
switch (message.type) {
|
||||
case 'system':
|
||||
this.handleSystemMessage(message);
|
||||
break;
|
||||
case 'rpc':
|
||||
this.handleRpcMessage(message);
|
||||
break;
|
||||
case 'syncvar':
|
||||
this.handleSyncVarMessage(message);
|
||||
break;
|
||||
case 'custom':
|
||||
this.handleCustomMessage(message);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling message:', error);
|
||||
this.eventEmitter.emit('error', error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理系统消息
|
||||
*/
|
||||
private handleSystemMessage(message: ClientMessage): void {
|
||||
const data = message.data as any;
|
||||
|
||||
// 处理响应消息
|
||||
if (message.messageId && this.pendingRpcs.has(message.messageId)) {
|
||||
const pending = this.pendingRpcs.get(message.messageId)!;
|
||||
pending.timeout.stop();
|
||||
this.pendingRpcs.delete(message.messageId);
|
||||
|
||||
if (data.success) {
|
||||
pending.resolve(data);
|
||||
} else {
|
||||
pending.reject(new Error(data.error || 'Request failed'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理广播消息
|
||||
switch (data.action) {
|
||||
case 'player-joined':
|
||||
this.eventEmitter.emit('player-joined', data.userId, data.userInfo);
|
||||
break;
|
||||
case 'player-left':
|
||||
this.eventEmitter.emit('player-left', data.userId);
|
||||
break;
|
||||
case 'network-object-created':
|
||||
this.handleNetworkObjectCreated(data);
|
||||
break;
|
||||
case 'network-object-destroyed':
|
||||
this.handleNetworkObjectDestroyed(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理RPC消息
|
||||
*/
|
||||
private handleRpcMessage(message: ClientMessage): void {
|
||||
const rpcData = message.data as any;
|
||||
this.eventEmitter.emit('rpc-received', rpcData.networkId, rpcData.methodName, rpcData.args || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理SyncVar消息
|
||||
*/
|
||||
private handleSyncVarMessage(message: ClientMessage): void {
|
||||
const syncData = message.data as any;
|
||||
this.eventEmitter.emit('syncvar-updated', syncData.networkId, syncData.propertyName, syncData.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义消息
|
||||
*/
|
||||
private handleCustomMessage(message: ClientMessage): void {
|
||||
// 可扩展的自定义消息处理
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理网络对象创建
|
||||
*/
|
||||
private handleNetworkObjectCreated(data: any): void {
|
||||
const networkObject = new SharedNetworkIdentity();
|
||||
this.networkObjects.set(data.networkId, networkObject);
|
||||
this.eventEmitter.emit('network-object-created', data.networkId, data.data || {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理网络对象销毁
|
||||
*/
|
||||
private handleNetworkObjectDestroyed(data: any): void {
|
||||
this.networkObjects.delete(data.networkId);
|
||||
this.eventEmitter.emit('network-object-destroyed', data.networkId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求并等待响应
|
||||
*/
|
||||
private sendRequestWithResponse(type: ClientMessage['type'], data: NetworkValue, timeout = 30000): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const messageId = this.generateMessageId();
|
||||
|
||||
const timeoutTimer = Core.schedule(timeout / 1000, false, this, () => {
|
||||
this.pendingRpcs.delete(messageId);
|
||||
reject(new Error('Request timeout'));
|
||||
});
|
||||
|
||||
this.pendingRpcs.set(messageId, {
|
||||
resolve,
|
||||
reject,
|
||||
timeout: timeoutTimer
|
||||
});
|
||||
|
||||
this.transport.sendMessage({
|
||||
type,
|
||||
data,
|
||||
messageId,
|
||||
reliable: true
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成消息ID
|
||||
*/
|
||||
private generateMessageId(): string {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
private cleanup(): void {
|
||||
this.currentUser = null;
|
||||
this.currentRoom = null;
|
||||
this.availableRooms.clear();
|
||||
this.networkObjects.clear();
|
||||
|
||||
// 取消所有待处理的RPC
|
||||
this.pendingRpcs.forEach(pending => {
|
||||
pending.timeout.stop();
|
||||
pending.reject(new Error('Connection closed'));
|
||||
});
|
||||
this.pendingRpcs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁客户端
|
||||
*/
|
||||
destroy(): void {
|
||||
this.disconnect();
|
||||
this.transport.destroy();
|
||||
// 清理事件监听器,由于Emitter没有clear方法,我们重新创建一个
|
||||
this.eventEmitter = new Emitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型安全的事件监听
|
||||
*/
|
||||
on<K extends keyof NetworkClientEvents>(event: K, listener: NetworkClientEvents[K]): void {
|
||||
this.eventEmitter.addObserver(event, listener, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
*/
|
||||
off<K extends keyof NetworkClientEvents>(event: K, listener: NetworkClientEvents[K]): void {
|
||||
this.eventEmitter.removeObserver(event, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型安全的事件触发
|
||||
*/
|
||||
emit<K extends keyof NetworkClientEvents>(event: K, ...args: Parameters<NetworkClientEvents[K]>): void {
|
||||
this.eventEmitter.emit(event, ...args);
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
/**
|
||||
* 客户端网络标识组件
|
||||
*
|
||||
* 标识网络对象并管理其状态
|
||||
*/
|
||||
|
||||
import { Component, Entity } from '@esengine/ecs-framework';
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
import { ClientNetworkBehaviour } from './ClientNetworkBehaviour';
|
||||
|
||||
/**
|
||||
* 网络权威类型
|
||||
*/
|
||||
export enum NetworkAuthority {
|
||||
/** 服务器权威 */
|
||||
SERVER = 'server',
|
||||
/** 客户端权威 */
|
||||
CLIENT = 'client',
|
||||
/** 所有者权威 */
|
||||
OWNER = 'owner'
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar信息
|
||||
*/
|
||||
export interface SyncVarInfo {
|
||||
/** 字段名 */
|
||||
fieldName: string;
|
||||
/** 当前值 */
|
||||
currentValue: NetworkValue;
|
||||
/** 上一个值 */
|
||||
previousValue: NetworkValue;
|
||||
/** 最后更新时间 */
|
||||
lastUpdateTime: number;
|
||||
/** 是否已变更 */
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络标识组件
|
||||
*/
|
||||
export class NetworkIdentity extends Component {
|
||||
/** 网络ID */
|
||||
private _networkId: string = '';
|
||||
/** 所有者用户ID */
|
||||
private _ownerId: string = '';
|
||||
/** 是否为本地玩家 */
|
||||
private _isLocalPlayer: boolean = false;
|
||||
/** 权威类型 */
|
||||
private _authority: NetworkAuthority = NetworkAuthority.SERVER;
|
||||
/** 是否有权威 */
|
||||
private _hasAuthority: boolean = false;
|
||||
/** 网络行为组件列表 */
|
||||
private networkBehaviours: ClientNetworkBehaviour[] = [];
|
||||
/** SyncVar信息映射 */
|
||||
private syncVars: Map<string, SyncVarInfo> = new Map();
|
||||
/** 预测状态 */
|
||||
private predictionEnabled: boolean = false;
|
||||
/** 插值状态 */
|
||||
private interpolationEnabled: boolean = true;
|
||||
|
||||
/**
|
||||
* 网络ID
|
||||
*/
|
||||
get networkId(): string {
|
||||
return this._networkId;
|
||||
}
|
||||
|
||||
set networkId(value: string) {
|
||||
this._networkId = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有者用户ID
|
||||
*/
|
||||
get ownerId(): string {
|
||||
return this._ownerId;
|
||||
}
|
||||
|
||||
set ownerId(value: string) {
|
||||
this._ownerId = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为本地玩家
|
||||
*/
|
||||
get isLocalPlayer(): boolean {
|
||||
return this._isLocalPlayer;
|
||||
}
|
||||
|
||||
set isLocalPlayer(value: boolean) {
|
||||
if (this._isLocalPlayer !== value) {
|
||||
this._isLocalPlayer = value;
|
||||
this.notifyLocalPlayerChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权威类型
|
||||
*/
|
||||
get authority(): NetworkAuthority {
|
||||
return this._authority;
|
||||
}
|
||||
|
||||
set authority(value: NetworkAuthority) {
|
||||
if (this._authority !== value) {
|
||||
this._authority = value;
|
||||
this.updateAuthorityStatus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有权威
|
||||
*/
|
||||
get hasAuthority(): boolean {
|
||||
return this._hasAuthority;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用预测
|
||||
*/
|
||||
get isPredictionEnabled(): boolean {
|
||||
return this.predictionEnabled;
|
||||
}
|
||||
|
||||
set isPredictionEnabled(value: boolean) {
|
||||
this.predictionEnabled = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用插值
|
||||
*/
|
||||
get isInterpolationEnabled(): boolean {
|
||||
return this.interpolationEnabled;
|
||||
}
|
||||
|
||||
set isInterpolationEnabled(value: boolean) {
|
||||
this.interpolationEnabled = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件初始化
|
||||
*/
|
||||
initialize(): void {
|
||||
this.collectNetworkBehaviours();
|
||||
this.notifyNetworkStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集网络行为组件
|
||||
*/
|
||||
private collectNetworkBehaviours(): void {
|
||||
// 暂时留空,等待实际集成时实现
|
||||
this.networkBehaviours = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新权威状态
|
||||
*/
|
||||
private updateAuthorityStatus(): void {
|
||||
const oldHasAuthority = this._hasAuthority;
|
||||
|
||||
// 根据权威类型计算是否有权威
|
||||
switch (this._authority) {
|
||||
case NetworkAuthority.SERVER:
|
||||
this._hasAuthority = false; // 客户端永远没有服务器权威
|
||||
break;
|
||||
case NetworkAuthority.CLIENT:
|
||||
this._hasAuthority = true; // 客户端权威
|
||||
break;
|
||||
case NetworkAuthority.OWNER:
|
||||
this._hasAuthority = this._isLocalPlayer; // 本地玩家才有权威
|
||||
break;
|
||||
}
|
||||
|
||||
// 通知权威变化
|
||||
if (oldHasAuthority !== this._hasAuthority) {
|
||||
this.notifyAuthorityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知权威变化
|
||||
*/
|
||||
private notifyAuthorityChanged(): void {
|
||||
this.networkBehaviours.forEach(behaviour => {
|
||||
if (this._hasAuthority) {
|
||||
behaviour.onStartAuthority();
|
||||
} else {
|
||||
behaviour.onStopAuthority();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知本地玩家状态变化
|
||||
*/
|
||||
private notifyLocalPlayerChanged(): void {
|
||||
this.updateAuthorityStatus(); // 本地玩家状态影响权威
|
||||
|
||||
this.networkBehaviours.forEach(behaviour => {
|
||||
if (this._isLocalPlayer) {
|
||||
behaviour.onStartLocalPlayer();
|
||||
} else {
|
||||
behaviour.onStopLocalPlayer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知网络启动
|
||||
*/
|
||||
private notifyNetworkStart(): void {
|
||||
this.networkBehaviours.forEach(behaviour => {
|
||||
behaviour.onNetworkStart();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知网络停止
|
||||
*/
|
||||
private notifyNetworkStop(): void {
|
||||
this.networkBehaviours.forEach(behaviour => {
|
||||
behaviour.onNetworkStop();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理RPC调用
|
||||
*/
|
||||
handleRpcCall(methodName: string, args: NetworkValue[]): void {
|
||||
// 将RPC调用分发给所有网络行为组件
|
||||
this.networkBehaviours.forEach(behaviour => {
|
||||
behaviour.onRpcReceived(methodName, args);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册SyncVar
|
||||
*/
|
||||
registerSyncVar(fieldName: string, initialValue: NetworkValue): void {
|
||||
this.syncVars.set(fieldName, {
|
||||
fieldName,
|
||||
currentValue: initialValue,
|
||||
previousValue: initialValue,
|
||||
lastUpdateTime: Date.now(),
|
||||
isDirty: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新SyncVar
|
||||
*/
|
||||
updateSyncVar(fieldName: string, newValue: NetworkValue): void {
|
||||
const syncVar = this.syncVars.get(fieldName);
|
||||
if (!syncVar) {
|
||||
console.warn(`SyncVar ${fieldName} not registered on ${this._networkId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const oldValue = syncVar.currentValue;
|
||||
syncVar.previousValue = oldValue;
|
||||
syncVar.currentValue = newValue;
|
||||
syncVar.lastUpdateTime = Date.now();
|
||||
syncVar.isDirty = true;
|
||||
|
||||
// 通知所有网络行为组件
|
||||
this.networkBehaviours.forEach(behaviour => {
|
||||
behaviour.onSyncVarChanged(fieldName, oldValue, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SyncVar值
|
||||
*/
|
||||
getSyncVar(fieldName: string): NetworkValue | undefined {
|
||||
return this.syncVars.get(fieldName)?.currentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有SyncVar
|
||||
*/
|
||||
getAllSyncVars(): Map<string, SyncVarInfo> {
|
||||
return new Map(this.syncVars);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取脏SyncVar
|
||||
*/
|
||||
getDirtySyncVars(): SyncVarInfo[] {
|
||||
return Array.from(this.syncVars.values()).filter(syncVar => syncVar.isDirty);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除脏标记
|
||||
*/
|
||||
clearDirtyFlags(): void {
|
||||
this.syncVars.forEach(syncVar => {
|
||||
syncVar.isDirty = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化网络状态
|
||||
*/
|
||||
serializeState(): NetworkValue {
|
||||
const state: any = {
|
||||
networkId: this._networkId,
|
||||
ownerId: this._ownerId,
|
||||
isLocalPlayer: this._isLocalPlayer,
|
||||
authority: this._authority,
|
||||
syncVars: {}
|
||||
};
|
||||
|
||||
// 序列化SyncVar
|
||||
this.syncVars.forEach((syncVar, fieldName) => {
|
||||
state.syncVars[fieldName] = syncVar.currentValue;
|
||||
});
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化网络状态
|
||||
*/
|
||||
deserializeState(state: any): void {
|
||||
if (state.networkId) this._networkId = state.networkId;
|
||||
if (state.ownerId) this._ownerId = state.ownerId;
|
||||
if (typeof state.isLocalPlayer === 'boolean') this.isLocalPlayer = state.isLocalPlayer;
|
||||
if (state.authority) this.authority = state.authority;
|
||||
|
||||
// 反序列化SyncVar
|
||||
if (state.syncVars) {
|
||||
Object.entries(state.syncVars).forEach(([fieldName, value]) => {
|
||||
if (this.syncVars.has(fieldName)) {
|
||||
this.updateSyncVar(fieldName, value as NetworkValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置预测状态
|
||||
*/
|
||||
setPredictionState(enabled: boolean): void {
|
||||
this.predictionEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置插值状态
|
||||
*/
|
||||
setInterpolationState(enabled: boolean): void {
|
||||
this.interpolationEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以发送RPC
|
||||
*/
|
||||
canSendRpc(): boolean {
|
||||
return this._hasAuthority || this._isLocalPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以更新SyncVar
|
||||
*/
|
||||
canUpdateSyncVar(): boolean {
|
||||
return this._hasAuthority;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件销毁
|
||||
*/
|
||||
onDestroy(): void {
|
||||
this.notifyNetworkStop();
|
||||
this.networkBehaviours = [];
|
||||
this.syncVars.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* 核心模块导出
|
||||
*/
|
||||
|
||||
export * from './NetworkClient';
|
||||
export * from './ClientNetworkBehaviour';
|
||||
export * from './NetworkIdentity';
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
* ClientRpc装饰器 - 客户端版本
|
||||
*
|
||||
* 用于标记可以从服务器调用的客户端方法
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
|
||||
/**
|
||||
* ClientRpc配置选项
|
||||
*/
|
||||
export interface ClientRpcOptions {
|
||||
/** 是否可靠传输 */
|
||||
reliable?: boolean;
|
||||
/** 超时时间(毫秒) */
|
||||
timeout?: number;
|
||||
/** 是否仅发送给所有者 */
|
||||
ownerOnly?: boolean;
|
||||
/** 是否包含发送者 */
|
||||
includeSender?: boolean;
|
||||
/** 权限要求 */
|
||||
requireAuthority?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ClientRpc元数据键
|
||||
*/
|
||||
export const CLIENT_RPC_METADATA_KEY = Symbol('client_rpc');
|
||||
|
||||
/**
|
||||
* ClientRpc元数据
|
||||
*/
|
||||
export interface ClientRpcMetadata {
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** 配置选项 */
|
||||
options: ClientRpcOptions;
|
||||
/** 原始方法 */
|
||||
originalMethod: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* ClientRpc装饰器
|
||||
*/
|
||||
export function ClientRpc(options: ClientRpcOptions = {}): MethodDecorator {
|
||||
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||
const methodName = propertyKey as string;
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// 获取已有的ClientRpc元数据
|
||||
const existingMetadata: ClientRpcMetadata[] = Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target.constructor) || [];
|
||||
|
||||
// 添加新的ClientRpc元数据
|
||||
existingMetadata.push({
|
||||
methodName,
|
||||
options: {
|
||||
reliable: true,
|
||||
timeout: 30000,
|
||||
ownerOnly: false,
|
||||
includeSender: false,
|
||||
requireAuthority: false,
|
||||
...options
|
||||
},
|
||||
originalMethod
|
||||
});
|
||||
|
||||
// 设置元数据
|
||||
Reflect.defineMetadata(CLIENT_RPC_METADATA_KEY, existingMetadata, target.constructor);
|
||||
|
||||
// 包装原方法(客户端接收RPC调用时执行)
|
||||
descriptor.value = function (this: any, ...args: NetworkValue[]) {
|
||||
try {
|
||||
// 直接调用原方法,客户端接收RPC调用
|
||||
return originalMethod.apply(this, args);
|
||||
} catch (error) {
|
||||
console.error(`Error executing ClientRpc ${methodName}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的所有ClientRpc元数据
|
||||
*/
|
||||
export function getClientRpcMetadata(target: any): ClientRpcMetadata[] {
|
||||
return Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法是否为ClientRpc
|
||||
*/
|
||||
export function isClientRpc(target: any, methodName: string): boolean {
|
||||
const metadata = getClientRpcMetadata(target);
|
||||
return metadata.some(meta => meta.methodName === methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定方法的ClientRpc选项
|
||||
*/
|
||||
export function getClientRpcOptions(target: any, methodName: string): ClientRpcOptions | null {
|
||||
const metadata = getClientRpcMetadata(target);
|
||||
const rpc = metadata.find(meta => meta.methodName === methodName);
|
||||
return rpc ? rpc.options : null;
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* ServerRpc装饰器 - 客户端版本
|
||||
*
|
||||
* 用于标记可以向服务器发送的RPC方法
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
import { ClientNetworkBehaviour } from '../core/ClientNetworkBehaviour';
|
||||
|
||||
/**
|
||||
* ServerRpc配置选项
|
||||
*/
|
||||
export interface ServerRpcOptions {
|
||||
/** 是否可靠传输 */
|
||||
reliable?: boolean;
|
||||
/** 超时时间(毫秒) */
|
||||
timeout?: number;
|
||||
/** 是否需要权威 */
|
||||
requireAuthority?: boolean;
|
||||
/** 是否需要是本地玩家 */
|
||||
requireLocalPlayer?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServerRpc元数据键
|
||||
*/
|
||||
export const SERVER_RPC_METADATA_KEY = Symbol('server_rpc');
|
||||
|
||||
/**
|
||||
* ServerRpc元数据
|
||||
*/
|
||||
export interface ServerRpcMetadata {
|
||||
/** 方法名 */
|
||||
methodName: string;
|
||||
/** 配置选项 */
|
||||
options: ServerRpcOptions;
|
||||
/** 原始方法 */
|
||||
originalMethod: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* ServerRpc装饰器
|
||||
*/
|
||||
export function ServerRpc(options: ServerRpcOptions = {}): MethodDecorator {
|
||||
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||
const methodName = propertyKey as string;
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
// 获取已有的ServerRpc元数据
|
||||
const existingMetadata: ServerRpcMetadata[] = Reflect.getMetadata(SERVER_RPC_METADATA_KEY, target.constructor) || [];
|
||||
|
||||
// 添加新的ServerRpc元数据
|
||||
existingMetadata.push({
|
||||
methodName,
|
||||
options: {
|
||||
reliable: true,
|
||||
timeout: 30000,
|
||||
requireAuthority: false,
|
||||
requireLocalPlayer: false,
|
||||
...options
|
||||
},
|
||||
originalMethod
|
||||
});
|
||||
|
||||
// 设置元数据
|
||||
Reflect.defineMetadata(SERVER_RPC_METADATA_KEY, existingMetadata, target.constructor);
|
||||
|
||||
// 替换方法实现为发送RPC调用
|
||||
descriptor.value = async function (this: ClientNetworkBehaviour, ...args: NetworkValue[]) {
|
||||
try {
|
||||
// 获取NetworkIdentity
|
||||
const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any);
|
||||
if (!networkIdentity) {
|
||||
throw new Error('NetworkIdentity component not found');
|
||||
}
|
||||
|
||||
// 检查权限要求
|
||||
if (options.requireAuthority && !(networkIdentity as any).hasAuthority) {
|
||||
throw new Error(`ServerRpc ${methodName} requires authority`);
|
||||
}
|
||||
|
||||
if (options.requireLocalPlayer && !(networkIdentity as any).isLocalPlayer) {
|
||||
throw new Error(`ServerRpc ${methodName} requires local player`);
|
||||
}
|
||||
|
||||
// 发送RPC到服务器
|
||||
if (options.reliable) {
|
||||
const result = await this.sendServerRpc(methodName, ...args);
|
||||
return result;
|
||||
} else {
|
||||
await this.sendServerRpcUnreliable(methodName, ...args);
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error sending ServerRpc ${methodName}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存原方法到特殊属性,用于本地预测或调试
|
||||
(descriptor.value as any).__originalMethod = originalMethod;
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的所有ServerRpc元数据
|
||||
*/
|
||||
export function getServerRpcMetadata(target: any): ServerRpcMetadata[] {
|
||||
return Reflect.getMetadata(SERVER_RPC_METADATA_KEY, target) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查方法是否为ServerRpc
|
||||
*/
|
||||
export function isServerRpc(target: any, methodName: string): boolean {
|
||||
const metadata = getServerRpcMetadata(target);
|
||||
return metadata.some(meta => meta.methodName === methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定方法的ServerRpc选项
|
||||
*/
|
||||
export function getServerRpcOptions(target: any, methodName: string): ServerRpcOptions | null {
|
||||
const metadata = getServerRpcMetadata(target);
|
||||
const rpc = metadata.find(meta => meta.methodName === methodName);
|
||||
return rpc ? rpc.options : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取方法的原始实现(未被装饰器修改的版本)
|
||||
*/
|
||||
export function getOriginalMethod(method: Function): Function | null {
|
||||
return (method as any).__originalMethod || null;
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/**
|
||||
* SyncVar装饰器 - 客户端版本
|
||||
*
|
||||
* 用于标记需要同步的变量
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
import { ClientNetworkBehaviour } from '../core/ClientNetworkBehaviour';
|
||||
|
||||
/**
|
||||
* SyncVar配置选项
|
||||
*/
|
||||
export interface SyncVarOptions {
|
||||
/** 是否可从客户端修改 */
|
||||
clientCanModify?: boolean;
|
||||
/** 同步间隔(毫秒),0表示立即同步 */
|
||||
syncInterval?: number;
|
||||
/** 是否仅同步给所有者 */
|
||||
ownerOnly?: boolean;
|
||||
/** 自定义序列化器 */
|
||||
serializer?: (value: any) => NetworkValue;
|
||||
/** 自定义反序列化器 */
|
||||
deserializer?: (value: NetworkValue) => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar元数据键
|
||||
*/
|
||||
export const SYNCVAR_METADATA_KEY = Symbol('syncvar');
|
||||
|
||||
/**
|
||||
* SyncVar元数据
|
||||
*/
|
||||
export interface SyncVarMetadata {
|
||||
/** 属性名 */
|
||||
propertyKey: string;
|
||||
/** 配置选项 */
|
||||
options: SyncVarOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar装饰器
|
||||
*/
|
||||
export function SyncVar(options: SyncVarOptions = {}): PropertyDecorator {
|
||||
return function (target: any, propertyKey: string | symbol) {
|
||||
const key = propertyKey as string;
|
||||
|
||||
// 获取已有的SyncVar元数据
|
||||
const existingMetadata: SyncVarMetadata[] = Reflect.getMetadata(SYNCVAR_METADATA_KEY, target.constructor) || [];
|
||||
|
||||
// 添加新的SyncVar元数据
|
||||
existingMetadata.push({
|
||||
propertyKey: key,
|
||||
options: {
|
||||
clientCanModify: false,
|
||||
syncInterval: 0,
|
||||
ownerOnly: false,
|
||||
...options
|
||||
}
|
||||
});
|
||||
|
||||
// 设置元数据
|
||||
Reflect.defineMetadata(SYNCVAR_METADATA_KEY, existingMetadata, target.constructor);
|
||||
|
||||
// 存储原始属性名(用于内部存储)
|
||||
const privateKey = `_syncvar_${key}`;
|
||||
|
||||
// 创建属性访问器
|
||||
Object.defineProperty(target, key, {
|
||||
get: function (this: ClientNetworkBehaviour) {
|
||||
// 从NetworkIdentity获取SyncVar值
|
||||
const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any);
|
||||
if (networkIdentity) {
|
||||
const syncVarValue = (networkIdentity as any).getSyncVar(key);
|
||||
if (syncVarValue !== undefined) {
|
||||
return options.deserializer ? options.deserializer(syncVarValue) : syncVarValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果网络值不存在,返回本地存储的值
|
||||
return (this as any)[privateKey];
|
||||
},
|
||||
|
||||
set: function (this: ClientNetworkBehaviour, value: any) {
|
||||
const oldValue = (this as any)[privateKey];
|
||||
const newValue = options.serializer ? options.serializer(value) : value;
|
||||
|
||||
// 存储到本地
|
||||
(this as any)[privateKey] = value;
|
||||
|
||||
// 获取NetworkIdentity
|
||||
const networkIdentity = this.entity?.getComponent('NetworkIdentity' as any);
|
||||
if (!networkIdentity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否可以修改
|
||||
if (!options.clientCanModify && !(networkIdentity as any).hasAuthority) {
|
||||
console.warn(`Cannot modify SyncVar ${key} without authority`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册SyncVar(如果尚未注册)
|
||||
(networkIdentity as any).registerSyncVar(key, newValue);
|
||||
|
||||
// 更新NetworkIdentity中的值
|
||||
(networkIdentity as any).updateSyncVar(key, newValue);
|
||||
|
||||
// 如果有权威且值发生变化,发送到服务器
|
||||
if ((networkIdentity as any).hasAuthority && oldValue !== value) {
|
||||
this.updateSyncVar(key, newValue).catch(error => {
|
||||
console.error(`Failed to sync variable ${key}:`, error);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的所有SyncVar元数据
|
||||
*/
|
||||
export function getSyncVarMetadata(target: any): SyncVarMetadata[] {
|
||||
return Reflect.getMetadata(SYNCVAR_METADATA_KEY, target) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性是否为SyncVar
|
||||
*/
|
||||
export function isSyncVar(target: any, propertyKey: string): boolean {
|
||||
const metadata = getSyncVarMetadata(target);
|
||||
return metadata.some(meta => meta.propertyKey === propertyKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定属性的SyncVar选项
|
||||
*/
|
||||
export function getSyncVarOptions(target: any, propertyKey: string): SyncVarOptions | null {
|
||||
const metadata = getSyncVarMetadata(target);
|
||||
const syncVar = metadata.find(meta => meta.propertyKey === propertyKey);
|
||||
return syncVar ? syncVar.options : null;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* 装饰器导出
|
||||
*/
|
||||
|
||||
export * from './SyncVar';
|
||||
export * from './ClientRpc';
|
||||
export * from './ServerRpc';
|
||||
@@ -1,23 +1,24 @@
|
||||
/**
|
||||
* ECS Framework 网络库 - 客户端
|
||||
*
|
||||
* 提供网络客户端功能,包括连接管理、预测、插值等
|
||||
* @esengine/network-client
|
||||
* ECS Framework网络层 - 客户端实现
|
||||
*/
|
||||
|
||||
// 核心模块
|
||||
export * from './core';
|
||||
// 核心客户端 (待实现)
|
||||
// export * from './core/NetworkClient';
|
||||
// export * from './core/ServerConnection';
|
||||
|
||||
// 传输层
|
||||
export * from './transport';
|
||||
// 传输层 (待实现)
|
||||
// export * from './transport/WebSocketClient';
|
||||
// export * from './transport/HttpClient';
|
||||
|
||||
// 装饰器
|
||||
export * from './decorators';
|
||||
// 系统层 (待实现)
|
||||
// export * from './systems/ClientSyncSystem';
|
||||
// export * from './systems/ClientRpcSystem';
|
||||
// export * from './systems/InterpolationSystem';
|
||||
|
||||
// 系统
|
||||
export * from './systems';
|
||||
// 平台适配器 (待实现)
|
||||
// export * from './adapters/BrowserAdapter';
|
||||
// export * from './adapters/CocosAdapter';
|
||||
|
||||
// 接口
|
||||
export * from './interfaces';
|
||||
|
||||
// 版本信息
|
||||
export const VERSION = '1.0.11';
|
||||
// 重新导出shared包的类型
|
||||
export * from '@esengine/network-shared';
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* 网络系统相关接口
|
||||
*/
|
||||
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
|
||||
/**
|
||||
* 可预测组件接口
|
||||
*
|
||||
* 实现此接口的组件可以参与客户端预测系统
|
||||
*/
|
||||
export interface IPredictable {
|
||||
/**
|
||||
* 预测更新
|
||||
*
|
||||
* @param inputs 输入数据
|
||||
* @param timestamp 时间戳
|
||||
*/
|
||||
predictUpdate(inputs: NetworkValue, timestamp: number): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可插值组件接口
|
||||
*
|
||||
* 实现此接口的组件可以参与插值系统
|
||||
*/
|
||||
export interface IInterpolatable {
|
||||
/**
|
||||
* 应用插值状态
|
||||
*
|
||||
* @param state 插值后的状态数据
|
||||
*/
|
||||
applyInterpolatedState(state: NetworkValue): void;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* 接口导出
|
||||
*/
|
||||
|
||||
export * from './NetworkInterfaces';
|
||||
@@ -1,520 +0,0 @@
|
||||
/**
|
||||
* 客户端插值系统
|
||||
*
|
||||
* 实现网络对象的平滑插值
|
||||
*/
|
||||
|
||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
import { NetworkIdentity } from '../core/NetworkIdentity';
|
||||
import { IInterpolatable } from '../interfaces/NetworkInterfaces';
|
||||
|
||||
/**
|
||||
* 插值状态快照
|
||||
*/
|
||||
export interface InterpolationSnapshot {
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 网络ID */
|
||||
networkId: string;
|
||||
/** 状态数据 */
|
||||
state: NetworkValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插值目标
|
||||
*/
|
||||
export interface InterpolationTarget {
|
||||
/** 网络ID */
|
||||
networkId: string;
|
||||
/** 起始状态 */
|
||||
fromState: NetworkValue;
|
||||
/** 目标状态 */
|
||||
toState: NetworkValue;
|
||||
/** 起始时间 */
|
||||
fromTime: number;
|
||||
/** 结束时间 */
|
||||
toTime: number;
|
||||
/** 当前插值进度 (0-1) */
|
||||
progress: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插值配置
|
||||
*/
|
||||
export interface InterpolationConfig {
|
||||
/** 插值延迟(毫秒) */
|
||||
delay: number;
|
||||
/** 最大插值时间(毫秒) */
|
||||
maxTime: number;
|
||||
/** 插值缓冲区大小 */
|
||||
bufferSize: number;
|
||||
/** 外推是否启用 */
|
||||
enableExtrapolation: boolean;
|
||||
/** 最大外推时间(毫秒) */
|
||||
maxExtrapolationTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插值算法类型
|
||||
*/
|
||||
export enum InterpolationType {
|
||||
/** 线性插值 */
|
||||
LINEAR = 'linear',
|
||||
/** 平滑插值 */
|
||||
SMOOTHSTEP = 'smoothstep',
|
||||
/** 三次贝塞尔插值 */
|
||||
CUBIC = 'cubic'
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端插值系统
|
||||
*/
|
||||
export class InterpolationSystem extends EntitySystem {
|
||||
/** 插值状态缓冲区 */
|
||||
private stateBuffer: Map<string, InterpolationSnapshot[]> = new Map();
|
||||
/** 当前插值目标 */
|
||||
private interpolationTargets: Map<string, InterpolationTarget> = new Map();
|
||||
/** 插值配置 */
|
||||
private config: InterpolationConfig;
|
||||
/** 当前时间 */
|
||||
private currentTime: number = 0;
|
||||
|
||||
constructor(config?: Partial<InterpolationConfig>) {
|
||||
// 使用Matcher查询具有NetworkIdentity的实体
|
||||
super(Matcher.all(NetworkIdentity));
|
||||
|
||||
this.config = {
|
||||
delay: 100,
|
||||
maxTime: 500,
|
||||
bufferSize: 32,
|
||||
enableExtrapolation: false,
|
||||
maxExtrapolationTime: 50,
|
||||
...config
|
||||
};
|
||||
|
||||
this.currentTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统初始化
|
||||
*/
|
||||
override initialize(): void {
|
||||
super.initialize();
|
||||
this.currentTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统更新
|
||||
*/
|
||||
override update(): void {
|
||||
this.currentTime = Date.now();
|
||||
this.cleanupOldStates();
|
||||
|
||||
// 调用父类update,会自动调用process方法处理匹配的实体
|
||||
super.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理匹配的实体
|
||||
*/
|
||||
protected override process(entities: Entity[]): void {
|
||||
const interpolationTime = this.currentTime - this.config.delay;
|
||||
|
||||
for (const entity of entities) {
|
||||
const networkIdentity = entity.getComponent(NetworkIdentity);
|
||||
|
||||
if (networkIdentity && networkIdentity.isInterpolationEnabled) {
|
||||
const networkId = networkIdentity.networkId;
|
||||
const target = this.interpolationTargets.get(networkId);
|
||||
|
||||
if (target) {
|
||||
// 计算插值进度
|
||||
const duration = target.toTime - target.fromTime;
|
||||
if (duration > 0) {
|
||||
const elapsed = interpolationTime - target.fromTime;
|
||||
target.progress = Math.max(0, Math.min(1, elapsed / duration));
|
||||
|
||||
// 执行插值
|
||||
const interpolatedState = this.interpolateStates(
|
||||
target.fromState,
|
||||
target.toState,
|
||||
target.progress,
|
||||
InterpolationType.LINEAR
|
||||
);
|
||||
|
||||
// 应用插值状态
|
||||
this.applyInterpolatedState(entity, interpolatedState);
|
||||
|
||||
// 检查是否需要外推
|
||||
if (target.progress >= 1 && this.config.enableExtrapolation) {
|
||||
this.performExtrapolation(entity, target, interpolationTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加网络状态快照
|
||||
*/
|
||||
addStateSnapshot(networkId: string, state: NetworkValue, timestamp: number): void {
|
||||
// 获取或创建缓冲区
|
||||
if (!this.stateBuffer.has(networkId)) {
|
||||
this.stateBuffer.set(networkId, []);
|
||||
}
|
||||
|
||||
const buffer = this.stateBuffer.get(networkId)!;
|
||||
|
||||
const snapshot: InterpolationSnapshot = {
|
||||
timestamp,
|
||||
networkId,
|
||||
state
|
||||
};
|
||||
|
||||
// 插入到正确的位置(按时间戳排序)
|
||||
const insertIndex = this.findInsertIndex(buffer, timestamp);
|
||||
buffer.splice(insertIndex, 0, snapshot);
|
||||
|
||||
// 保持缓冲区大小
|
||||
if (buffer.length > this.config.bufferSize) {
|
||||
buffer.shift();
|
||||
}
|
||||
|
||||
// 更新插值目标
|
||||
this.updateInterpolationTarget(networkId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新插值目标
|
||||
*/
|
||||
private updateInterpolationTarget(networkId: string): void {
|
||||
const buffer = this.stateBuffer.get(networkId);
|
||||
if (!buffer || buffer.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interpolationTime = this.currentTime - this.config.delay;
|
||||
|
||||
// 查找插值区间
|
||||
const { from, to } = this.findInterpolationRange(buffer, interpolationTime);
|
||||
|
||||
if (!from || !to) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新或创建插值目标
|
||||
this.interpolationTargets.set(networkId, {
|
||||
networkId,
|
||||
fromState: from.state,
|
||||
toState: to.state,
|
||||
fromTime: from.timestamp,
|
||||
toTime: to.timestamp,
|
||||
progress: 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找插值区间
|
||||
*/
|
||||
private findInterpolationRange(buffer: InterpolationSnapshot[], time: number): {
|
||||
from: InterpolationSnapshot | null;
|
||||
to: InterpolationSnapshot | null;
|
||||
} {
|
||||
let from: InterpolationSnapshot | null = null;
|
||||
let to: InterpolationSnapshot | null = null;
|
||||
|
||||
for (let i = 0; i < buffer.length - 1; i++) {
|
||||
const current = buffer[i];
|
||||
const next = buffer[i + 1];
|
||||
|
||||
if (time >= current.timestamp && time <= next.timestamp) {
|
||||
from = current;
|
||||
to = next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到区间,使用最近的两个状态
|
||||
if (!from && !to && buffer.length >= 2) {
|
||||
if (time < buffer[0].timestamp) {
|
||||
// 时间过早,使用前两个状态
|
||||
from = buffer[0];
|
||||
to = buffer[1];
|
||||
} else if (time > buffer[buffer.length - 1].timestamp) {
|
||||
// 时间过晚,使用后两个状态
|
||||
from = buffer[buffer.length - 2];
|
||||
to = buffer[buffer.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return { from, to };
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态插值
|
||||
*/
|
||||
private interpolateStates(
|
||||
fromState: NetworkValue,
|
||||
toState: NetworkValue,
|
||||
progress: number,
|
||||
type: InterpolationType
|
||||
): NetworkValue {
|
||||
// 调整插值进度曲线
|
||||
const adjustedProgress = this.adjustProgress(progress, type);
|
||||
|
||||
try {
|
||||
return this.interpolateValue(fromState, toState, adjustedProgress);
|
||||
} catch (error) {
|
||||
console.error('Error interpolating states:', error);
|
||||
return toState; // 出错时返回目标状态
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归插值值
|
||||
*/
|
||||
private interpolateValue(from: NetworkValue, to: NetworkValue, progress: number): NetworkValue {
|
||||
// 如果类型不同,直接返回目标值
|
||||
if (typeof from !== typeof to) {
|
||||
return to;
|
||||
}
|
||||
|
||||
// 数字插值
|
||||
if (typeof from === 'number' && typeof to === 'number') {
|
||||
return from + (to - from) * progress;
|
||||
}
|
||||
|
||||
// 字符串插值(直接切换)
|
||||
if (typeof from === 'string' && typeof to === 'string') {
|
||||
return progress < 0.5 ? from : to;
|
||||
}
|
||||
|
||||
// 布尔插值(直接切换)
|
||||
if (typeof from === 'boolean' && typeof to === 'boolean') {
|
||||
return progress < 0.5 ? from : to;
|
||||
}
|
||||
|
||||
// 数组插值
|
||||
if (Array.isArray(from) && Array.isArray(to)) {
|
||||
const result: NetworkValue[] = [];
|
||||
const maxLength = Math.max(from.length, to.length);
|
||||
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
const fromValue = i < from.length ? from[i] : to[i];
|
||||
const toValue = i < to.length ? to[i] : from[i];
|
||||
result[i] = this.interpolateValue(fromValue, toValue, progress);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 对象插值
|
||||
if (from && to && typeof from === 'object' && typeof to === 'object') {
|
||||
const result: any = {};
|
||||
const allKeys = new Set([...Object.keys(from), ...Object.keys(to)]);
|
||||
|
||||
for (const key of allKeys) {
|
||||
const fromValue = (from as any)[key];
|
||||
const toValue = (to as any)[key];
|
||||
|
||||
if (fromValue !== undefined && toValue !== undefined) {
|
||||
result[key] = this.interpolateValue(fromValue, toValue, progress);
|
||||
} else {
|
||||
result[key] = toValue !== undefined ? toValue : fromValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 其他类型直接返回目标值
|
||||
return to;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整插值进度曲线
|
||||
*/
|
||||
private adjustProgress(progress: number, type: InterpolationType): number {
|
||||
switch (type) {
|
||||
case InterpolationType.LINEAR:
|
||||
return progress;
|
||||
|
||||
case InterpolationType.SMOOTHSTEP:
|
||||
return progress * progress * (3 - 2 * progress);
|
||||
|
||||
case InterpolationType.CUBIC:
|
||||
return progress < 0.5
|
||||
? 4 * progress * progress * progress
|
||||
: 1 - Math.pow(-2 * progress + 2, 3) / 2;
|
||||
|
||||
default:
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用插值状态到实体
|
||||
*/
|
||||
private applyInterpolatedState(entity: Entity, state: NetworkValue): void {
|
||||
// 获取所有可插值的组件
|
||||
const components: any[] = [];
|
||||
for (const component of components) {
|
||||
if (this.isInterpolatable(component)) {
|
||||
try {
|
||||
(component as IInterpolatable).applyInterpolatedState(state);
|
||||
} catch (error) {
|
||||
console.error('Error applying interpolated state:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新NetworkIdentity中的状态
|
||||
const networkIdentity = entity.getComponent(NetworkIdentity);
|
||||
if (networkIdentity && typeof networkIdentity.deserializeState === 'function') {
|
||||
try {
|
||||
networkIdentity.deserializeState(state);
|
||||
} catch (error) {
|
||||
console.error('Error deserializing interpolated state:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件是否实现了IInterpolatable接口
|
||||
*/
|
||||
private isInterpolatable(component: any): component is IInterpolatable {
|
||||
return component && typeof component.applyInterpolatedState === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行外推
|
||||
*/
|
||||
private performExtrapolation(entity: Entity, target: InterpolationTarget, currentTime: number): void {
|
||||
if (!this.config.enableExtrapolation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extrapolationTime = currentTime - target.toTime;
|
||||
if (extrapolationTime > this.config.maxExtrapolationTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算外推状态
|
||||
const extrapolationProgress = extrapolationTime / (target.toTime - target.fromTime);
|
||||
const extrapolatedState = this.extrapolateState(
|
||||
target.fromState,
|
||||
target.toState,
|
||||
1 + extrapolationProgress
|
||||
);
|
||||
|
||||
// 应用外推状态
|
||||
this.applyInterpolatedState(entity, extrapolatedState);
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态外推
|
||||
*/
|
||||
private extrapolateState(fromState: NetworkValue, toState: NetworkValue, progress: number): NetworkValue {
|
||||
// 简单的线性外推
|
||||
return this.interpolateValue(fromState, toState, progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找插入位置
|
||||
*/
|
||||
private findInsertIndex(buffer: InterpolationSnapshot[], timestamp: number): number {
|
||||
let left = 0;
|
||||
let right = buffer.length;
|
||||
|
||||
while (left < right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
if (buffer[mid].timestamp < timestamp) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期状态
|
||||
*/
|
||||
private cleanupOldStates(): void {
|
||||
const cutoffTime = this.currentTime - this.config.maxTime;
|
||||
|
||||
this.stateBuffer.forEach((buffer, networkId) => {
|
||||
// 移除过期的状态
|
||||
const validStates = buffer.filter(snapshot => snapshot.timestamp > cutoffTime);
|
||||
|
||||
if (validStates.length !== buffer.length) {
|
||||
this.stateBuffer.set(networkId, validStates);
|
||||
}
|
||||
|
||||
// 如果缓冲区为空,移除它
|
||||
if (validStates.length === 0) {
|
||||
this.stateBuffer.delete(networkId);
|
||||
this.interpolationTargets.delete(networkId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据网络ID查找实体
|
||||
*/
|
||||
private findEntityByNetworkId(networkId: string): Entity | null {
|
||||
// 使用系统的entities属性来查找
|
||||
for (const entity of this.entities) {
|
||||
const networkIdentity = entity.getComponent(NetworkIdentity);
|
||||
if (networkIdentity && networkIdentity.networkId === networkId) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置插值配置
|
||||
*/
|
||||
setInterpolationConfig(config: Partial<InterpolationConfig>): void {
|
||||
this.config = { ...this.config, ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插值统计信息
|
||||
*/
|
||||
getInterpolationStats(): { [networkId: string]: { bufferSize: number; progress: number } } {
|
||||
const stats: { [networkId: string]: { bufferSize: number; progress: number } } = {};
|
||||
|
||||
this.stateBuffer.forEach((buffer, networkId) => {
|
||||
const target = this.interpolationTargets.get(networkId);
|
||||
stats[networkId] = {
|
||||
bufferSize: buffer.length,
|
||||
progress: target ? target.progress : 0
|
||||
};
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有插值数据
|
||||
*/
|
||||
clearInterpolationData(): void {
|
||||
this.stateBuffer.clear();
|
||||
this.interpolationTargets.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统销毁
|
||||
*/
|
||||
onDestroy(): void {
|
||||
this.clearInterpolationData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,362 +0,0 @@
|
||||
/**
|
||||
* 客户端预测系统
|
||||
*
|
||||
* 实现客户端预测和服务器和解
|
||||
*/
|
||||
|
||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
import { NetworkIdentity } from '../core/NetworkIdentity';
|
||||
import { IPredictable } from '../interfaces/NetworkInterfaces';
|
||||
|
||||
/**
|
||||
* 预测状态快照
|
||||
*/
|
||||
export interface PredictionSnapshot {
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 网络ID */
|
||||
networkId: string;
|
||||
/** 状态数据 */
|
||||
state: NetworkValue;
|
||||
/** 输入数据 */
|
||||
inputs?: NetworkValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预测输入
|
||||
*/
|
||||
export interface PredictionInput {
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 输入数据 */
|
||||
data: NetworkValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端预测系统
|
||||
*/
|
||||
export class PredictionSystem extends EntitySystem {
|
||||
/** 预测状态缓冲区 */
|
||||
private predictionBuffer: Map<string, PredictionSnapshot[]> = new Map();
|
||||
/** 输入缓冲区 */
|
||||
private inputBuffer: PredictionInput[] = [];
|
||||
/** 最大缓冲区大小 */
|
||||
private maxBufferSize: number = 64;
|
||||
/** 预测时间窗口(毫秒) */
|
||||
private predictionWindow: number = 500;
|
||||
/** 当前预测时间戳 */
|
||||
private currentPredictionTime: number = 0;
|
||||
|
||||
constructor(maxBufferSize = 64, predictionWindow = 500) {
|
||||
// 使用Matcher查询具有NetworkIdentity的实体
|
||||
super(Matcher.all(NetworkIdentity));
|
||||
|
||||
this.maxBufferSize = maxBufferSize;
|
||||
this.predictionWindow = predictionWindow;
|
||||
this.currentPredictionTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统初始化
|
||||
*/
|
||||
override initialize(): void {
|
||||
super.initialize();
|
||||
this.currentPredictionTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统更新
|
||||
*/
|
||||
override update(): void {
|
||||
this.currentPredictionTime = Date.now();
|
||||
this.cleanupOldSnapshots();
|
||||
|
||||
// 调用父类update,会自动调用process方法处理匹配的实体
|
||||
super.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理匹配的实体
|
||||
*/
|
||||
protected override process(entities: Entity[]): void {
|
||||
for (const entity of entities) {
|
||||
const networkIdentity = entity.getComponent(NetworkIdentity);
|
||||
|
||||
if (networkIdentity &&
|
||||
networkIdentity.isPredictionEnabled &&
|
||||
networkIdentity.isLocalPlayer) {
|
||||
|
||||
// 保存当前状态快照
|
||||
this.saveSnapshot(entity);
|
||||
|
||||
// 应用当前输入进行预测
|
||||
const currentInputs = this.getCurrentInputs();
|
||||
if (currentInputs) {
|
||||
this.applyInputs(entity, currentInputs, this.currentPredictionTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加预测输入
|
||||
*/
|
||||
addInput(input: PredictionInput): void {
|
||||
this.inputBuffer.push(input);
|
||||
|
||||
// 保持输入缓冲区大小
|
||||
if (this.inputBuffer.length > this.maxBufferSize) {
|
||||
this.inputBuffer.shift();
|
||||
}
|
||||
|
||||
// 按时间戳排序
|
||||
this.inputBuffer.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存预测状态快照
|
||||
*/
|
||||
saveSnapshot(entity: Entity): void {
|
||||
const networkIdentity = entity.getComponent(NetworkIdentity);
|
||||
if (!networkIdentity || !networkIdentity.isPredictionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const networkId = networkIdentity.networkId;
|
||||
const snapshot: PredictionSnapshot = {
|
||||
timestamp: this.currentPredictionTime,
|
||||
networkId,
|
||||
state: networkIdentity.serializeState(),
|
||||
inputs: this.getCurrentInputs() || undefined
|
||||
};
|
||||
|
||||
// 获取或创建缓冲区
|
||||
if (!this.predictionBuffer.has(networkId)) {
|
||||
this.predictionBuffer.set(networkId, []);
|
||||
}
|
||||
|
||||
const buffer = this.predictionBuffer.get(networkId)!;
|
||||
buffer.push(snapshot);
|
||||
|
||||
// 保持缓冲区大小
|
||||
if (buffer.length > this.maxBufferSize) {
|
||||
buffer.shift();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务器接收权威状态进行和解
|
||||
*/
|
||||
reconcileWithServer(networkId: string, serverState: NetworkValue, serverTimestamp: number): void {
|
||||
const buffer = this.predictionBuffer.get(networkId);
|
||||
if (!buffer || buffer.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找对应时间戳的预测状态
|
||||
const predictionSnapshot = this.findSnapshot(buffer, serverTimestamp);
|
||||
if (!predictionSnapshot) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 比较预测状态和服务器状态
|
||||
if (this.statesMatch(predictionSnapshot.state, serverState)) {
|
||||
// 预测正确,移除已确认的快照
|
||||
this.removeSnapshotsBeforeTimestamp(buffer, serverTimestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
// 预测错误,需要进行和解
|
||||
this.performReconciliation(networkId, serverState, serverTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行预测和解
|
||||
*/
|
||||
private performReconciliation(networkId: string, serverState: NetworkValue, serverTimestamp: number): void {
|
||||
const entity = this.findEntityByNetworkId(networkId);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
const networkIdentity = entity.getComponent(NetworkIdentity);
|
||||
if (!networkIdentity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 回滚到服务器状态
|
||||
if (typeof networkIdentity.deserializeState === 'function') {
|
||||
networkIdentity.deserializeState(serverState);
|
||||
}
|
||||
|
||||
// 重新应用服务器时间戳之后的输入
|
||||
const buffer = this.predictionBuffer.get(networkId)!;
|
||||
const snapshotsToReplay = buffer.filter(snapshot => snapshot.timestamp > serverTimestamp);
|
||||
|
||||
for (const snapshot of snapshotsToReplay) {
|
||||
if (snapshot.inputs) {
|
||||
this.applyInputs(entity, snapshot.inputs, snapshot.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理已和解的快照
|
||||
this.removeSnapshotsBeforeTimestamp(buffer, serverTimestamp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 应用输入进行预测计算
|
||||
*/
|
||||
private applyInputs(entity: Entity, inputs: NetworkValue, timestamp: number): void {
|
||||
const networkIdentity = entity.getComponent(NetworkIdentity);
|
||||
if (!networkIdentity) return;
|
||||
|
||||
// 获取实体的所有组件并检查是否实现了IPredictable接口
|
||||
const components: any[] = [];
|
||||
for (const component of components) {
|
||||
if (this.isPredictable(component)) {
|
||||
try {
|
||||
(component as IPredictable).predictUpdate(inputs, timestamp);
|
||||
} catch (error) {
|
||||
console.error('Error applying prediction:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件是否实现了IPredictable接口
|
||||
*/
|
||||
private isPredictable(component: any): component is IPredictable {
|
||||
return component && typeof component.predictUpdate === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前输入
|
||||
*/
|
||||
private getCurrentInputs(): NetworkValue | null {
|
||||
if (this.inputBuffer.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取最新的输入
|
||||
return this.inputBuffer[this.inputBuffer.length - 1].data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找指定时间戳的快照
|
||||
*/
|
||||
private findSnapshot(buffer: PredictionSnapshot[], timestamp: number): PredictionSnapshot | null {
|
||||
// 查找最接近的快照
|
||||
let closest: PredictionSnapshot | null = null;
|
||||
let minDiff = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
for (const snapshot of buffer) {
|
||||
const diff = Math.abs(snapshot.timestamp - timestamp);
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff;
|
||||
closest = snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个状态是否匹配
|
||||
*/
|
||||
private statesMatch(predictedState: NetworkValue, serverState: NetworkValue): boolean {
|
||||
try {
|
||||
// 简单的JSON比较,实际应用中可能需要更精确的比较
|
||||
return JSON.stringify(predictedState) === JSON.stringify(serverState);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定时间戳之前的快照
|
||||
*/
|
||||
private removeSnapshotsBeforeTimestamp(buffer: PredictionSnapshot[], timestamp: number): void {
|
||||
for (let i = buffer.length - 1; i >= 0; i--) {
|
||||
if (buffer[i].timestamp < timestamp) {
|
||||
buffer.splice(0, i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的快照
|
||||
*/
|
||||
private cleanupOldSnapshots(): void {
|
||||
const cutoffTime = this.currentPredictionTime - this.predictionWindow;
|
||||
|
||||
this.predictionBuffer.forEach((buffer, networkId) => {
|
||||
this.removeSnapshotsBeforeTimestamp(buffer, cutoffTime);
|
||||
|
||||
// 如果缓冲区为空,移除它
|
||||
if (buffer.length === 0) {
|
||||
this.predictionBuffer.delete(networkId);
|
||||
}
|
||||
});
|
||||
|
||||
// 清理过期的输入
|
||||
this.inputBuffer = this.inputBuffer.filter(input =>
|
||||
input.timestamp > cutoffTime
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据网络ID查找实体
|
||||
*/
|
||||
private findEntityByNetworkId(networkId: string): Entity | null {
|
||||
// 使用系统的entities属性来查找
|
||||
for (const entity of this.entities) {
|
||||
const networkIdentity = entity.getComponent(NetworkIdentity);
|
||||
if (networkIdentity && networkIdentity.networkId === networkId) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置预测配置
|
||||
*/
|
||||
setPredictionConfig(maxBufferSize: number, predictionWindow: number): void {
|
||||
this.maxBufferSize = maxBufferSize;
|
||||
this.predictionWindow = predictionWindow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预测统计信息
|
||||
*/
|
||||
getPredictionStats(): { [networkId: string]: number } {
|
||||
const stats: { [networkId: string]: number } = {};
|
||||
|
||||
this.predictionBuffer.forEach((buffer, networkId) => {
|
||||
stats[networkId] = buffer.length;
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有预测数据
|
||||
*/
|
||||
clearPredictionData(): void {
|
||||
this.predictionBuffer.clear();
|
||||
this.inputBuffer = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统销毁
|
||||
*/
|
||||
onDestroy(): void {
|
||||
this.clearPredictionData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 系统导出
|
||||
*/
|
||||
|
||||
export * from './PredictionSystem';
|
||||
export * from './InterpolationSystem';
|
||||
@@ -1,445 +0,0 @@
|
||||
/**
|
||||
* 客户端传输层抽象接口
|
||||
*/
|
||||
|
||||
import { Emitter, ITimer, Core } from '@esengine/ecs-framework';
|
||||
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
|
||||
|
||||
/**
|
||||
* 客户端传输配置
|
||||
*/
|
||||
export interface ClientTransportConfig {
|
||||
/** 服务器地址 */
|
||||
host: string;
|
||||
/** 服务器端口 */
|
||||
port: number;
|
||||
/** 是否使用安全连接 */
|
||||
secure?: boolean;
|
||||
/** 连接超时时间(毫秒) */
|
||||
connectionTimeout?: number;
|
||||
/** 重连间隔(毫秒) */
|
||||
reconnectInterval?: number;
|
||||
/** 最大重连次数 */
|
||||
maxReconnectAttempts?: number;
|
||||
/** 心跳间隔(毫秒) */
|
||||
heartbeatInterval?: number;
|
||||
/** 消息队列最大大小 */
|
||||
maxQueueSize?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接状态
|
||||
*/
|
||||
export enum ConnectionState {
|
||||
/** 断开连接 */
|
||||
DISCONNECTED = 'disconnected',
|
||||
/** 连接中 */
|
||||
CONNECTING = 'connecting',
|
||||
/** 已连接 */
|
||||
CONNECTED = 'connected',
|
||||
/** 认证中 */
|
||||
AUTHENTICATING = 'authenticating',
|
||||
/** 已认证 */
|
||||
AUTHENTICATED = 'authenticated',
|
||||
/** 重连中 */
|
||||
RECONNECTING = 'reconnecting',
|
||||
/** 连接错误 */
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端消息
|
||||
*/
|
||||
export interface ClientMessage {
|
||||
/** 消息类型 */
|
||||
type: 'rpc' | 'syncvar' | 'system' | 'custom';
|
||||
/** 消息数据 */
|
||||
data: NetworkValue;
|
||||
/** 消息ID(用于响应匹配) */
|
||||
messageId?: string;
|
||||
/** 是否可靠传输 */
|
||||
reliable?: boolean;
|
||||
/** 时间戳 */
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接统计信息
|
||||
*/
|
||||
export interface ConnectionStats {
|
||||
/** 连接时间 */
|
||||
connectedAt: Date | null;
|
||||
/** 连接持续时间(毫秒) */
|
||||
connectionDuration: number;
|
||||
/** 发送消息数 */
|
||||
messagesSent: number;
|
||||
/** 接收消息数 */
|
||||
messagesReceived: number;
|
||||
/** 发送字节数 */
|
||||
bytesSent: number;
|
||||
/** 接收字节数 */
|
||||
bytesReceived: number;
|
||||
/** 重连次数 */
|
||||
reconnectCount: number;
|
||||
/** 丢失消息数 */
|
||||
messagesLost: number;
|
||||
/** 平均延迟(毫秒) */
|
||||
averageLatency: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端传输事件
|
||||
*/
|
||||
export interface ClientTransportEvents {
|
||||
/** 连接建立 */
|
||||
'connected': () => void;
|
||||
/** 连接断开 */
|
||||
'disconnected': (reason: string) => void;
|
||||
/** 连接状态变化 */
|
||||
'state-changed': (oldState: ConnectionState, newState: ConnectionState) => void;
|
||||
/** 收到消息 */
|
||||
'message': (message: ClientMessage) => void;
|
||||
/** 连接错误 */
|
||||
'error': (error: Error) => void;
|
||||
/** 重连开始 */
|
||||
'reconnecting': (attempt: number, maxAttempts: number) => void;
|
||||
/** 重连成功 */
|
||||
'reconnected': () => void;
|
||||
/** 重连失败 */
|
||||
'reconnect-failed': () => void;
|
||||
/** 延迟更新 */
|
||||
'latency-updated': (latency: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端传输层抽象类
|
||||
*/
|
||||
export abstract class ClientTransport {
|
||||
protected config: ClientTransportConfig;
|
||||
protected state: ConnectionState = ConnectionState.DISCONNECTED;
|
||||
protected stats: ConnectionStats;
|
||||
protected messageQueue: ClientMessage[] = [];
|
||||
protected reconnectAttempts = 0;
|
||||
protected reconnectTimer: ITimer<any> | null = null;
|
||||
protected heartbeatTimer: ITimer<any> | null = null;
|
||||
private latencyMeasurements: number[] = [];
|
||||
private eventEmitter: Emitter<keyof ClientTransportEvents, any>;
|
||||
|
||||
constructor(config: ClientTransportConfig) {
|
||||
this.eventEmitter = new Emitter<keyof ClientTransportEvents, any>();
|
||||
|
||||
this.config = {
|
||||
secure: false,
|
||||
connectionTimeout: 10000, // 10秒
|
||||
reconnectInterval: 3000, // 3秒
|
||||
maxReconnectAttempts: 10,
|
||||
heartbeatInterval: 30000, // 30秒
|
||||
maxQueueSize: 1000,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
connectedAt: null,
|
||||
connectionDuration: 0,
|
||||
messagesSent: 0,
|
||||
messagesReceived: 0,
|
||||
bytesSent: 0,
|
||||
bytesReceived: 0,
|
||||
reconnectCount: 0,
|
||||
messagesLost: 0,
|
||||
averageLatency: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到服务器
|
||||
*/
|
||||
abstract connect(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
abstract disconnect(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
abstract sendMessage(message: ClientMessage): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* 获取当前连接状态
|
||||
*/
|
||||
getState(): ConnectionState {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已连接
|
||||
*/
|
||||
isConnected(): boolean {
|
||||
return this.state === ConnectionState.CONNECTED ||
|
||||
this.state === ConnectionState.AUTHENTICATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接统计信息
|
||||
*/
|
||||
getStats(): ConnectionStats {
|
||||
if (this.stats.connectedAt) {
|
||||
this.stats.connectionDuration = Date.now() - this.stats.connectedAt.getTime();
|
||||
}
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
*/
|
||||
getConfig(): Readonly<ClientTransportConfig> {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态
|
||||
*/
|
||||
protected setState(newState: ConnectionState): void {
|
||||
if (this.state !== newState) {
|
||||
const oldState = this.state;
|
||||
this.state = newState;
|
||||
this.eventEmitter.emit('state-changed', oldState, newState);
|
||||
|
||||
// 特殊状态处理
|
||||
if (newState === ConnectionState.CONNECTED) {
|
||||
this.stats.connectedAt = new Date();
|
||||
this.reconnectAttempts = 0;
|
||||
this.startHeartbeat();
|
||||
this.processMessageQueue();
|
||||
this.eventEmitter.emit('connected');
|
||||
|
||||
if (oldState === ConnectionState.RECONNECTING) {
|
||||
this.eventEmitter.emit('reconnected');
|
||||
}
|
||||
} else if (newState === ConnectionState.DISCONNECTED) {
|
||||
this.stats.connectedAt = null;
|
||||
this.stopHeartbeat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理接收到的消息
|
||||
*/
|
||||
protected handleMessage(message: ClientMessage): void {
|
||||
this.stats.messagesReceived++;
|
||||
|
||||
if (message.data) {
|
||||
try {
|
||||
const messageSize = JSON.stringify(message.data).length;
|
||||
this.stats.bytesReceived += messageSize;
|
||||
} catch (error) {
|
||||
// 忽略序列化错误
|
||||
}
|
||||
}
|
||||
|
||||
// 处理系统消息
|
||||
if (message.type === 'system') {
|
||||
this.handleSystemMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventEmitter.emit('message', message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理系统消息
|
||||
*/
|
||||
protected handleSystemMessage(message: ClientMessage): void {
|
||||
const data = message.data as any;
|
||||
|
||||
switch (data.action) {
|
||||
case 'ping':
|
||||
// 响应ping
|
||||
this.sendMessage({
|
||||
type: 'system',
|
||||
data: { action: 'pong', timestamp: data.timestamp }
|
||||
});
|
||||
break;
|
||||
|
||||
case 'pong':
|
||||
// 计算延迟
|
||||
if (data.timestamp) {
|
||||
const latency = Date.now() - data.timestamp;
|
||||
this.updateLatency(latency);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理连接错误
|
||||
*/
|
||||
protected handleError(error: Error): void {
|
||||
console.error('Transport error:', error.message);
|
||||
this.eventEmitter.emit('error', error);
|
||||
|
||||
if (this.isConnected()) {
|
||||
this.setState(ConnectionState.ERROR);
|
||||
this.startReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始重连
|
||||
*/
|
||||
protected startReconnect(): void {
|
||||
if (this.reconnectAttempts >= this.config.maxReconnectAttempts!) {
|
||||
this.eventEmitter.emit('reconnect-failed');
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(ConnectionState.RECONNECTING);
|
||||
this.reconnectAttempts++;
|
||||
this.stats.reconnectCount++;
|
||||
|
||||
this.eventEmitter.emit('reconnecting', this.reconnectAttempts, this.config.maxReconnectAttempts!);
|
||||
|
||||
this.reconnectTimer = Core.schedule(this.config.reconnectInterval! / 1000, false, this, async () => {
|
||||
try {
|
||||
await this.connect();
|
||||
} catch (error) {
|
||||
this.startReconnect(); // 继续重连
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止重连
|
||||
*/
|
||||
protected stopReconnect(): void {
|
||||
if (this.reconnectTimer) {
|
||||
this.reconnectTimer.stop();
|
||||
this.reconnectTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将消息加入队列
|
||||
*/
|
||||
protected queueMessage(message: ClientMessage): boolean {
|
||||
if (this.messageQueue.length >= this.config.maxQueueSize!) {
|
||||
this.stats.messagesLost++;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.messageQueue.push(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息队列
|
||||
*/
|
||||
protected async processMessageQueue(): Promise<void> {
|
||||
while (this.messageQueue.length > 0 && this.isConnected()) {
|
||||
const message = this.messageQueue.shift()!;
|
||||
await this.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始心跳
|
||||
*/
|
||||
protected startHeartbeat(): void {
|
||||
if (this.config.heartbeatInterval && this.config.heartbeatInterval > 0) {
|
||||
this.heartbeatTimer = Core.schedule(this.config.heartbeatInterval / 1000, true, this, () => {
|
||||
this.sendHeartbeat();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳
|
||||
*/
|
||||
protected stopHeartbeat(): void {
|
||||
if (this.heartbeatTimer) {
|
||||
this.heartbeatTimer.stop();
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送心跳
|
||||
*/
|
||||
protected sendHeartbeat(): void {
|
||||
this.sendMessage({
|
||||
type: 'system',
|
||||
data: { action: 'ping', timestamp: Date.now() }
|
||||
}).catch(() => {
|
||||
// 心跳发送失败,可能连接有问题
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新延迟统计
|
||||
*/
|
||||
protected updateLatency(latency: number): void {
|
||||
this.latencyMeasurements.push(latency);
|
||||
|
||||
// 只保留最近的10个测量值
|
||||
if (this.latencyMeasurements.length > 10) {
|
||||
this.latencyMeasurements.shift();
|
||||
}
|
||||
|
||||
// 计算平均延迟
|
||||
const sum = this.latencyMeasurements.reduce((a, b) => a + b, 0);
|
||||
this.stats.averageLatency = sum / this.latencyMeasurements.length;
|
||||
|
||||
this.eventEmitter.emit('latency-updated', latency);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新发送统计
|
||||
*/
|
||||
protected updateSendStats(message: ClientMessage): void {
|
||||
this.stats.messagesSent++;
|
||||
|
||||
if (message.data) {
|
||||
try {
|
||||
const messageSize = JSON.stringify(message.data).length;
|
||||
this.stats.bytesSent += messageSize;
|
||||
} catch (error) {
|
||||
// 忽略序列化错误
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁传输层
|
||||
*/
|
||||
destroy(): void {
|
||||
this.stopReconnect();
|
||||
this.stopHeartbeat();
|
||||
this.messageQueue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型安全的事件监听
|
||||
*/
|
||||
on<K extends keyof ClientTransportEvents>(event: K, listener: ClientTransportEvents[K]): this {
|
||||
this.eventEmitter.addObserver(event, listener, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
*/
|
||||
off<K extends keyof ClientTransportEvents>(event: K, listener: ClientTransportEvents[K]): this {
|
||||
this.eventEmitter.removeObserver(event, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型安全的事件触发
|
||||
*/
|
||||
emit<K extends keyof ClientTransportEvents>(event: K, ...args: Parameters<ClientTransportEvents[K]>): void {
|
||||
this.eventEmitter.emit(event, ...args);
|
||||
}
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
/**
|
||||
* HTTP 客户端传输实现
|
||||
*
|
||||
* 支持 REST API 和长轮询
|
||||
*/
|
||||
|
||||
import { Core, ITimer } from '@esengine/ecs-framework';
|
||||
import {
|
||||
ClientTransport,
|
||||
ClientTransportConfig,
|
||||
ConnectionState,
|
||||
ClientMessage
|
||||
} from './ClientTransport';
|
||||
|
||||
/**
|
||||
* HTTP 客户端配置
|
||||
*/
|
||||
export interface HttpClientConfig extends ClientTransportConfig {
|
||||
/** API 路径前缀 */
|
||||
apiPrefix?: string;
|
||||
/** 请求超时时间(毫秒) */
|
||||
requestTimeout?: number;
|
||||
/** 长轮询超时时间(毫秒) */
|
||||
longPollTimeout?: number;
|
||||
/** 是否启用长轮询 */
|
||||
enableLongPolling?: boolean;
|
||||
/** 额外的请求头 */
|
||||
headers?: Record<string, string>;
|
||||
/** 认证令牌 */
|
||||
authToken?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 响应接口
|
||||
*/
|
||||
interface HttpResponse {
|
||||
success: boolean;
|
||||
data?: any;
|
||||
error?: string;
|
||||
messages?: ClientMessage[];
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 客户端传输
|
||||
*/
|
||||
export class HttpClientTransport extends ClientTransport {
|
||||
private connectionId: string | null = null;
|
||||
private longPollController: AbortController | null = null;
|
||||
private longPollRunning = false;
|
||||
private connectPromise: Promise<void> | null = null;
|
||||
private requestTimers: Set<ITimer<any>> = new Set();
|
||||
|
||||
protected override config: HttpClientConfig;
|
||||
|
||||
constructor(config: HttpClientConfig) {
|
||||
super(config);
|
||||
|
||||
this.config = {
|
||||
apiPrefix: '/api',
|
||||
requestTimeout: 30000, // 30秒
|
||||
longPollTimeout: 25000, // 25秒
|
||||
enableLongPolling: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到服务器
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
if (this.state === ConnectionState.CONNECTING ||
|
||||
this.state === ConnectionState.CONNECTED) {
|
||||
return this.connectPromise || Promise.resolve();
|
||||
}
|
||||
|
||||
this.setState(ConnectionState.CONNECTING);
|
||||
this.stopReconnect();
|
||||
|
||||
this.connectPromise = this.performConnect();
|
||||
return this.connectPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行连接
|
||||
*/
|
||||
private async performConnect(): Promise<void> {
|
||||
try {
|
||||
// 发送连接请求
|
||||
const response = await this.makeRequest('/connect', 'POST', {});
|
||||
|
||||
if (response.success && response.data.connectionId) {
|
||||
this.connectionId = response.data.connectionId;
|
||||
this.setState(ConnectionState.CONNECTED);
|
||||
|
||||
// 启动长轮询
|
||||
if (this.config.enableLongPolling) {
|
||||
this.startLongPolling();
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.error || 'Connection failed');
|
||||
}
|
||||
} catch (error) {
|
||||
this.setState(ConnectionState.ERROR);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
async disconnect(): Promise<void> {
|
||||
this.stopReconnect();
|
||||
this.stopLongPolling();
|
||||
|
||||
if (this.connectionId) {
|
||||
try {
|
||||
await this.makeRequest('/disconnect', 'POST', {
|
||||
connectionId: this.connectionId
|
||||
});
|
||||
} catch (error) {
|
||||
// 忽略断开连接时的错误
|
||||
}
|
||||
|
||||
this.connectionId = null;
|
||||
}
|
||||
|
||||
this.setState(ConnectionState.DISCONNECTED);
|
||||
this.connectPromise = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
async sendMessage(message: ClientMessage): Promise<boolean> {
|
||||
if (!this.connectionId) {
|
||||
// 如果未连接,将消息加入队列
|
||||
if (this.state === ConnectionState.CONNECTING ||
|
||||
this.state === ConnectionState.RECONNECTING) {
|
||||
return this.queueMessage(message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.makeRequest('/send', 'POST', {
|
||||
connectionId: this.connectionId,
|
||||
message: {
|
||||
...message,
|
||||
timestamp: message.timestamp || Date.now()
|
||||
}
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
this.updateSendStats(message);
|
||||
return true;
|
||||
} else {
|
||||
console.error('Send message failed:', response.error);
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.handleError(error as Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动长轮询
|
||||
*/
|
||||
private startLongPolling(): void {
|
||||
if (this.longPollRunning || !this.connectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.longPollRunning = true;
|
||||
this.performLongPoll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止长轮询
|
||||
*/
|
||||
private stopLongPolling(): void {
|
||||
this.longPollRunning = false;
|
||||
|
||||
if (this.longPollController) {
|
||||
this.longPollController.abort();
|
||||
this.longPollController = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行长轮询
|
||||
*/
|
||||
private async performLongPoll(): Promise<void> {
|
||||
while (this.longPollRunning && this.connectionId) {
|
||||
try {
|
||||
this.longPollController = new AbortController();
|
||||
|
||||
const response = await this.makeRequest('/poll', 'GET', {
|
||||
connectionId: this.connectionId
|
||||
}, {
|
||||
signal: this.longPollController.signal,
|
||||
timeout: this.config.longPollTimeout
|
||||
});
|
||||
|
||||
if (response.success && response.messages && response.messages.length > 0) {
|
||||
// 处理接收到的消息
|
||||
for (const message of response.messages) {
|
||||
this.handleMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果服务器指示断开连接
|
||||
if (response.data && response.data.disconnected) {
|
||||
this.handleServerDisconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if ((error as any).name === 'AbortError') {
|
||||
// 被主动取消,正常情况
|
||||
break;
|
||||
}
|
||||
|
||||
console.warn('Long polling error:', (error as Error).message);
|
||||
|
||||
// 如果是网络错误,尝试重连
|
||||
if (this.isNetworkError(error as Error)) {
|
||||
this.handleError(error as Error);
|
||||
break;
|
||||
}
|
||||
|
||||
// 短暂等待后重试
|
||||
await this.delay(1000);
|
||||
}
|
||||
|
||||
this.longPollController = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理服务器主动断开连接
|
||||
*/
|
||||
private handleServerDisconnect(): void {
|
||||
this.connectionId = null;
|
||||
this.stopLongPolling();
|
||||
this.emit('disconnected', 'Server disconnect');
|
||||
|
||||
if (this.reconnectAttempts < this.config.maxReconnectAttempts!) {
|
||||
this.startReconnect();
|
||||
} else {
|
||||
this.setState(ConnectionState.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 HTTP 请求
|
||||
*/
|
||||
private async makeRequest(
|
||||
path: string,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
||||
data?: any,
|
||||
options: {
|
||||
signal?: AbortSignal;
|
||||
timeout?: number;
|
||||
} = {}
|
||||
): Promise<HttpResponse> {
|
||||
const url = this.buildUrl(path);
|
||||
const headers = this.buildHeaders();
|
||||
|
||||
const requestOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
signal: options.signal
|
||||
};
|
||||
|
||||
// 添加请求体
|
||||
if (method !== 'GET' && data) {
|
||||
requestOptions.body = JSON.stringify(data);
|
||||
} else if (method === 'GET' && data) {
|
||||
// GET 请求将数据作为查询参数
|
||||
const params = new URLSearchParams();
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
params.append(key, String(value));
|
||||
});
|
||||
const separator = url.includes('?') ? '&' : '?';
|
||||
return this.fetchWithTimeout(`${url}${separator}${params}`, requestOptions, options.timeout);
|
||||
}
|
||||
|
||||
return this.fetchWithTimeout(url, requestOptions, options.timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带超时的 fetch 请求
|
||||
*/
|
||||
private async fetchWithTimeout(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
timeout?: number
|
||||
): Promise<HttpResponse> {
|
||||
const actualTimeout = timeout || this.config.requestTimeout!;
|
||||
|
||||
const controller = new AbortController();
|
||||
let timeoutTimer: ITimer<any> | null = null;
|
||||
|
||||
// 创建超时定时器
|
||||
timeoutTimer = Core.schedule(actualTimeout / 1000, false, this, () => {
|
||||
controller.abort();
|
||||
if (timeoutTimer) {
|
||||
this.requestTimers.delete(timeoutTimer);
|
||||
}
|
||||
});
|
||||
|
||||
this.requestTimers.add(timeoutTimer);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
signal: options.signal || controller.signal
|
||||
});
|
||||
|
||||
// 清理定时器
|
||||
if (timeoutTimer) {
|
||||
timeoutTimer.stop();
|
||||
this.requestTimers.delete(timeoutTimer);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result as HttpResponse;
|
||||
|
||||
} catch (error) {
|
||||
// 清理定时器
|
||||
if (timeoutTimer) {
|
||||
timeoutTimer.stop();
|
||||
this.requestTimers.delete(timeoutTimer);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求URL
|
||||
*/
|
||||
private buildUrl(path: string): string {
|
||||
const protocol = this.config.secure ? 'https' : 'http';
|
||||
const basePath = this.config.apiPrefix || '';
|
||||
const cleanPath = path.startsWith('/') ? path : `/${path}`;
|
||||
|
||||
return `${protocol}://${this.config.host}:${this.config.port}${basePath}${cleanPath}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求头
|
||||
*/
|
||||
private buildHeaders(): Record<string, string> {
|
||||
const headers = { ...this.config.headers };
|
||||
|
||||
if (this.config.authToken) {
|
||||
headers['Authorization'] = `Bearer ${this.config.authToken}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为网络错误
|
||||
*/
|
||||
private isNetworkError(error: Error): boolean {
|
||||
return error.message.includes('fetch') ||
|
||||
error.message.includes('network') ||
|
||||
error.message.includes('timeout') ||
|
||||
error.name === 'TypeError';
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟函数
|
||||
*/
|
||||
private delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const timer = Core.schedule(ms / 1000, false, this, () => {
|
||||
this.requestTimers.delete(timer);
|
||||
resolve();
|
||||
});
|
||||
this.requestTimers.add(timer);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置认证令牌
|
||||
*/
|
||||
setAuthToken(token: string): void {
|
||||
this.config.authToken = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接ID
|
||||
*/
|
||||
getConnectionId(): string | null {
|
||||
return this.connectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否支持 Fetch API
|
||||
*/
|
||||
static isSupported(): boolean {
|
||||
return typeof fetch !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁传输层
|
||||
*/
|
||||
override destroy(): void {
|
||||
// 清理所有请求定时器
|
||||
this.requestTimers.forEach(timer => timer.stop());
|
||||
this.requestTimers.clear();
|
||||
|
||||
this.disconnect();
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
/**
|
||||
* WebSocket 客户端传输实现
|
||||
*/
|
||||
|
||||
import { Core, ITimer } from '@esengine/ecs-framework';
|
||||
import {
|
||||
ClientTransport,
|
||||
ClientTransportConfig,
|
||||
ConnectionState,
|
||||
ClientMessage
|
||||
} from './ClientTransport';
|
||||
|
||||
/**
|
||||
* WebSocket 客户端配置
|
||||
*/
|
||||
export interface WebSocketClientConfig extends ClientTransportConfig {
|
||||
/** WebSocket 路径 */
|
||||
path?: string;
|
||||
/** 协议列表 */
|
||||
protocols?: string | string[];
|
||||
/** 额外的请求头 */
|
||||
headers?: Record<string, string>;
|
||||
/** 是否启用二进制消息 */
|
||||
binaryType?: 'blob' | 'arraybuffer';
|
||||
/** WebSocket 扩展 */
|
||||
extensions?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 客户端传输
|
||||
*/
|
||||
export class WebSocketClientTransport extends ClientTransport {
|
||||
private websocket: WebSocket | null = null;
|
||||
private connectionPromise: Promise<void> | null = null;
|
||||
private connectionTimeoutTimer: ITimer<any> | null = null;
|
||||
|
||||
protected override config: WebSocketClientConfig;
|
||||
|
||||
constructor(config: WebSocketClientConfig) {
|
||||
super(config);
|
||||
|
||||
this.config = {
|
||||
path: '/ws',
|
||||
protocols: [],
|
||||
headers: {},
|
||||
binaryType: 'arraybuffer',
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到服务器
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
if (this.state === ConnectionState.CONNECTING ||
|
||||
this.state === ConnectionState.CONNECTED) {
|
||||
return this.connectionPromise || Promise.resolve();
|
||||
}
|
||||
|
||||
this.setState(ConnectionState.CONNECTING);
|
||||
this.stopReconnect(); // 停止任何正在进行的重连
|
||||
|
||||
this.connectionPromise = new Promise((resolve, reject) => {
|
||||
try {
|
||||
// 构建WebSocket URL
|
||||
const protocol = this.config.secure ? 'wss' : 'ws';
|
||||
const url = `${protocol}://${this.config.host}:${this.config.port}${this.config.path}`;
|
||||
|
||||
// 创建WebSocket连接
|
||||
this.websocket = new WebSocket(url, this.config.protocols);
|
||||
|
||||
if (this.config.binaryType) {
|
||||
this.websocket.binaryType = this.config.binaryType;
|
||||
}
|
||||
|
||||
// 设置连接超时
|
||||
this.connectionTimeoutTimer = Core.schedule(this.config.connectionTimeout! / 1000, false, this, () => {
|
||||
if (this.websocket && this.websocket.readyState === WebSocket.CONNECTING) {
|
||||
this.websocket.close();
|
||||
reject(new Error('Connection timeout'));
|
||||
}
|
||||
});
|
||||
|
||||
// WebSocket 事件处理
|
||||
this.websocket.onopen = () => {
|
||||
if (this.connectionTimeoutTimer) {
|
||||
this.connectionTimeoutTimer.stop();
|
||||
this.connectionTimeoutTimer = null;
|
||||
}
|
||||
this.setState(ConnectionState.CONNECTED);
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.websocket.onclose = (event) => {
|
||||
if (this.connectionTimeoutTimer) {
|
||||
this.connectionTimeoutTimer.stop();
|
||||
this.connectionTimeoutTimer = null;
|
||||
}
|
||||
this.handleClose(event.code, event.reason);
|
||||
|
||||
if (this.state === ConnectionState.CONNECTING) {
|
||||
reject(new Error(`Connection failed: ${event.reason || 'Unknown error'}`));
|
||||
}
|
||||
};
|
||||
|
||||
this.websocket.onerror = (event) => {
|
||||
if (this.connectionTimeoutTimer) {
|
||||
this.connectionTimeoutTimer.stop();
|
||||
this.connectionTimeoutTimer = null;
|
||||
}
|
||||
const error = new Error('WebSocket error');
|
||||
this.handleError(error);
|
||||
|
||||
if (this.state === ConnectionState.CONNECTING) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
this.websocket.onmessage = (event) => {
|
||||
this.handleWebSocketMessage(event);
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.setState(ConnectionState.ERROR);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
return this.connectionPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
async disconnect(): Promise<void> {
|
||||
this.stopReconnect();
|
||||
|
||||
if (this.websocket) {
|
||||
// 设置状态为断开连接,避免触发重连
|
||||
this.setState(ConnectionState.DISCONNECTED);
|
||||
|
||||
if (this.websocket.readyState === WebSocket.OPEN ||
|
||||
this.websocket.readyState === WebSocket.CONNECTING) {
|
||||
this.websocket.close(1000, 'Client disconnect');
|
||||
}
|
||||
|
||||
this.websocket = null;
|
||||
}
|
||||
|
||||
this.connectionPromise = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
async sendMessage(message: ClientMessage): Promise<boolean> {
|
||||
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
|
||||
// 如果未连接,将消息加入队列
|
||||
if (this.state === ConnectionState.CONNECTING ||
|
||||
this.state === ConnectionState.RECONNECTING) {
|
||||
return this.queueMessage(message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 序列化消息
|
||||
const serialized = JSON.stringify({
|
||||
...message,
|
||||
timestamp: message.timestamp || Date.now()
|
||||
});
|
||||
|
||||
// 发送消息
|
||||
this.websocket.send(serialized);
|
||||
this.updateSendStats(message);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
this.handleError(error as Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 WebSocket 消息
|
||||
*/
|
||||
private handleWebSocketMessage(event: MessageEvent): void {
|
||||
try {
|
||||
let data: string;
|
||||
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
// 处理二进制数据
|
||||
data = new TextDecoder().decode(event.data);
|
||||
} else if (event.data instanceof Blob) {
|
||||
// Blob 需要异步处理
|
||||
event.data.text().then(text => {
|
||||
this.processMessage(text);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
// 字符串数据
|
||||
data = event.data;
|
||||
}
|
||||
|
||||
this.processMessage(data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing WebSocket message:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息内容
|
||||
*/
|
||||
private processMessage(data: string): void {
|
||||
try {
|
||||
const message: ClientMessage = JSON.parse(data);
|
||||
this.handleMessage(message);
|
||||
} catch (error) {
|
||||
console.error('Error parsing message:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理连接关闭
|
||||
*/
|
||||
private handleClose(code: number, reason: string): void {
|
||||
this.websocket = null;
|
||||
this.connectionPromise = null;
|
||||
|
||||
const wasConnected = this.isConnected();
|
||||
|
||||
// 根据关闭代码决定是否重连
|
||||
if (code === 1000) {
|
||||
// 正常关闭,不重连
|
||||
this.setState(ConnectionState.DISCONNECTED);
|
||||
this.emit('disconnected', reason || 'Normal closure');
|
||||
} else if (wasConnected && this.reconnectAttempts < this.config.maxReconnectAttempts!) {
|
||||
// 异常关闭,尝试重连
|
||||
this.emit('disconnected', reason || `Abnormal closure (${code})`);
|
||||
this.startReconnect();
|
||||
} else {
|
||||
// 达到最大重连次数或其他情况
|
||||
this.setState(ConnectionState.DISCONNECTED);
|
||||
this.emit('disconnected', reason || `Connection lost (${code})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 WebSocket 就绪状态
|
||||
*/
|
||||
getReadyState(): number {
|
||||
return this.websocket?.readyState ?? WebSocket.CLOSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 WebSocket 实例
|
||||
*/
|
||||
getWebSocket(): WebSocket | null {
|
||||
return this.websocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否支持 WebSocket
|
||||
*/
|
||||
static isSupported(): boolean {
|
||||
return typeof WebSocket !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁传输层
|
||||
*/
|
||||
override destroy(): void {
|
||||
if (this.connectionTimeoutTimer) {
|
||||
this.connectionTimeoutTimer.stop();
|
||||
this.connectionTimeoutTimer = null;
|
||||
}
|
||||
this.disconnect();
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* 传输层导出
|
||||
*/
|
||||
|
||||
export * from './ClientTransport';
|
||||
export * from './WebSocketClientTransport';
|
||||
export * from './HttpClientTransport';
|
||||
Reference in New Issue
Block a user