集成tsrpc代替protobuf
This commit is contained in:
478
packages/network/src/Config/NetworkConfigManager.ts
Normal file
478
packages/network/src/Config/NetworkConfigManager.ts
Normal file
@@ -0,0 +1,478 @@
|
||||
/**
|
||||
* 网络配置管理器
|
||||
*/
|
||||
|
||||
import {
|
||||
NETWORK_CONFIG,
|
||||
SYNCVAR_CONFIG,
|
||||
MESSAGE_CONFIG,
|
||||
SERIALIZATION_CONFIG,
|
||||
TSRPC_CONFIG,
|
||||
AUTHORITY_CONFIG,
|
||||
PERFORMANCE_CONFIG
|
||||
} from '../constants/NetworkConstants';
|
||||
|
||||
/**
|
||||
* 网络配置接口
|
||||
*/
|
||||
export interface INetworkConfig {
|
||||
[key: string]: unknown;
|
||||
/** 连接配置 */
|
||||
connection: {
|
||||
timeout: number;
|
||||
maxReconnectAttempts: number;
|
||||
reconnectDelay: number;
|
||||
};
|
||||
|
||||
/** 心跳配置 */
|
||||
heartbeat: {
|
||||
interval: number;
|
||||
timeout: number;
|
||||
maxConsecutiveLoss: number;
|
||||
packetSize: number;
|
||||
enableAdaptiveInterval: boolean;
|
||||
rttHistorySize: number;
|
||||
};
|
||||
|
||||
/** SyncVar配置 */
|
||||
syncVar: {
|
||||
cacheTimeout: number;
|
||||
defaultThrottleMs: number;
|
||||
maxFieldNumber: number;
|
||||
minFieldNumber: number;
|
||||
};
|
||||
|
||||
/** 消息配置 */
|
||||
message: {
|
||||
maxSequenceNumber: number;
|
||||
maxHeaderSize: number;
|
||||
maxPayloadSize: number;
|
||||
defaultTimeout: number;
|
||||
maxBatchSize: number;
|
||||
};
|
||||
|
||||
/** 序列化配置 */
|
||||
serialization: {
|
||||
defaultCompressionLevel: number;
|
||||
minCompressionSize: number;
|
||||
initialBufferSize: number;
|
||||
maxBufferSize: number;
|
||||
};
|
||||
|
||||
/** TSRPC配置 */
|
||||
tsrpc: {
|
||||
defaultServerUrl: string;
|
||||
defaultTimeout: number;
|
||||
heartbeatInterval: number;
|
||||
heartbeatTimeout: number;
|
||||
poolConfig: {
|
||||
minConnections: number;
|
||||
maxConnections: number;
|
||||
idleTimeout: number;
|
||||
};
|
||||
};
|
||||
|
||||
/** 权限配置 */
|
||||
authority: {
|
||||
minPriority: number;
|
||||
maxPriority: number;
|
||||
defaultRulePriority: number;
|
||||
};
|
||||
|
||||
/** 性能监控配置 */
|
||||
performance: {
|
||||
statsCollectionInterval: number;
|
||||
statsRetentionTime: number;
|
||||
warningThresholds: {
|
||||
rtt: number;
|
||||
packetLoss: number;
|
||||
jitter: number;
|
||||
cpuUsage: number;
|
||||
memoryUsage: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置更新事件接口
|
||||
*/
|
||||
export interface IConfigUpdateEvent<T = unknown> {
|
||||
path: string;
|
||||
oldValue: T;
|
||||
newValue: T;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置管理器
|
||||
*
|
||||
* 提供类型安全的配置管理,支持配置更新监听和验证
|
||||
*/
|
||||
export class NetworkConfigManager {
|
||||
private static _instance: NetworkConfigManager | null = null;
|
||||
private _config: INetworkConfig;
|
||||
private _updateListeners: Map<string, Array<(event: IConfigUpdateEvent) => void>> = new Map();
|
||||
|
||||
private constructor() {
|
||||
this._config = this.createDefaultConfig();
|
||||
}
|
||||
|
||||
public static get Instance(): NetworkConfigManager {
|
||||
if (!NetworkConfigManager._instance) {
|
||||
NetworkConfigManager._instance = new NetworkConfigManager();
|
||||
}
|
||||
return NetworkConfigManager._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认配置
|
||||
*/
|
||||
private createDefaultConfig(): INetworkConfig {
|
||||
return {
|
||||
connection: {
|
||||
timeout: NETWORK_CONFIG.DEFAULT_CONNECTION_TIMEOUT,
|
||||
maxReconnectAttempts: NETWORK_CONFIG.DEFAULT_MAX_RECONNECT_ATTEMPTS,
|
||||
reconnectDelay: NETWORK_CONFIG.DEFAULT_RECONNECT_DELAY
|
||||
},
|
||||
heartbeat: {
|
||||
interval: NETWORK_CONFIG.DEFAULT_HEARTBEAT_INTERVAL,
|
||||
timeout: NETWORK_CONFIG.DEFAULT_HEARTBEAT_TIMEOUT,
|
||||
maxConsecutiveLoss: NETWORK_CONFIG.DEFAULT_MAX_CONSECUTIVE_LOSS,
|
||||
packetSize: NETWORK_CONFIG.DEFAULT_HEARTBEAT_PACKET_SIZE,
|
||||
enableAdaptiveInterval: true,
|
||||
rttHistorySize: NETWORK_CONFIG.DEFAULT_RTT_HISTORY_SIZE
|
||||
},
|
||||
syncVar: {
|
||||
cacheTimeout: SYNCVAR_CONFIG.DEFAULT_CACHE_TIMEOUT,
|
||||
defaultThrottleMs: SYNCVAR_CONFIG.DEFAULT_THROTTLE_MS,
|
||||
maxFieldNumber: SYNCVAR_CONFIG.MAX_FIELD_NUMBER,
|
||||
minFieldNumber: SYNCVAR_CONFIG.MIN_FIELD_NUMBER
|
||||
},
|
||||
message: {
|
||||
maxSequenceNumber: MESSAGE_CONFIG.MAX_SEQUENCE_NUMBER,
|
||||
maxHeaderSize: MESSAGE_CONFIG.MAX_HEADER_SIZE,
|
||||
maxPayloadSize: MESSAGE_CONFIG.MAX_PAYLOAD_SIZE,
|
||||
defaultTimeout: MESSAGE_CONFIG.DEFAULT_MESSAGE_TIMEOUT,
|
||||
maxBatchSize: MESSAGE_CONFIG.MAX_BATCH_SIZE
|
||||
},
|
||||
serialization: {
|
||||
defaultCompressionLevel: SERIALIZATION_CONFIG.DEFAULT_COMPRESSION_LEVEL,
|
||||
minCompressionSize: SERIALIZATION_CONFIG.MIN_COMPRESSION_SIZE,
|
||||
initialBufferSize: SERIALIZATION_CONFIG.INITIAL_BUFFER_SIZE,
|
||||
maxBufferSize: SERIALIZATION_CONFIG.MAX_BUFFER_SIZE
|
||||
},
|
||||
tsrpc: {
|
||||
defaultServerUrl: TSRPC_CONFIG.DEFAULT_SERVER_URL,
|
||||
defaultTimeout: TSRPC_CONFIG.DEFAULT_TIMEOUT,
|
||||
heartbeatInterval: TSRPC_CONFIG.DEFAULT_HEARTBEAT.interval,
|
||||
heartbeatTimeout: TSRPC_CONFIG.DEFAULT_HEARTBEAT.timeout,
|
||||
poolConfig: {
|
||||
minConnections: TSRPC_CONFIG.DEFAULT_POOL_CONFIG.minConnections,
|
||||
maxConnections: TSRPC_CONFIG.DEFAULT_POOL_CONFIG.maxConnections,
|
||||
idleTimeout: TSRPC_CONFIG.DEFAULT_POOL_CONFIG.idleTimeout
|
||||
}
|
||||
},
|
||||
authority: {
|
||||
minPriority: AUTHORITY_CONFIG.MIN_PRIORITY,
|
||||
maxPriority: AUTHORITY_CONFIG.MAX_PRIORITY,
|
||||
defaultRulePriority: AUTHORITY_CONFIG.DEFAULT_RULE_PRIORITY
|
||||
},
|
||||
performance: {
|
||||
statsCollectionInterval: PERFORMANCE_CONFIG.STATS_COLLECTION_INTERVAL,
|
||||
statsRetentionTime: PERFORMANCE_CONFIG.STATS_RETENTION_TIME,
|
||||
warningThresholds: {
|
||||
rtt: PERFORMANCE_CONFIG.WARNING_THRESHOLDS.RTT,
|
||||
packetLoss: PERFORMANCE_CONFIG.WARNING_THRESHOLDS.PACKET_LOSS,
|
||||
jitter: PERFORMANCE_CONFIG.WARNING_THRESHOLDS.JITTER,
|
||||
cpuUsage: PERFORMANCE_CONFIG.WARNING_THRESHOLDS.CPU_USAGE,
|
||||
memoryUsage: PERFORMANCE_CONFIG.WARNING_THRESHOLDS.MEMORY_USAGE
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
*/
|
||||
public getConfig(): Readonly<INetworkConfig> {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定路径的配置值
|
||||
*
|
||||
* @param path - 配置路径,使用点号分隔
|
||||
* @returns 配置值
|
||||
*/
|
||||
public get<T = unknown>(path: string): T {
|
||||
const keys = path.split('.');
|
||||
let current: unknown = this._config;
|
||||
|
||||
for (const key of keys) {
|
||||
if (typeof current !== 'object' || current === null || !(key in current)) {
|
||||
throw new Error(`配置路径不存在: ${path}`);
|
||||
}
|
||||
current = (current as Record<string, unknown>)[key];
|
||||
}
|
||||
|
||||
return current as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定路径的配置值
|
||||
*
|
||||
* @param path - 配置路径
|
||||
* @param value - 新值
|
||||
*/
|
||||
public set<T = unknown>(path: string, value: T): void {
|
||||
const keys = path.split('.');
|
||||
const lastKey = keys.pop()!;
|
||||
let current = this._config as Record<string, unknown>;
|
||||
|
||||
// 导航到父对象
|
||||
for (const key of keys) {
|
||||
if (typeof current[key] !== 'object' || current[key] === null) {
|
||||
throw new Error(`配置路径无效: ${path}`);
|
||||
}
|
||||
current = current[key] as Record<string, unknown>;
|
||||
}
|
||||
|
||||
const oldValue = current[lastKey];
|
||||
|
||||
// 验证新值
|
||||
this.validateConfigValue(path, value);
|
||||
|
||||
// 设置新值
|
||||
current[lastKey] = value;
|
||||
|
||||
// 触发更新事件
|
||||
this.emitConfigUpdate({
|
||||
path,
|
||||
oldValue,
|
||||
newValue: value,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新配置
|
||||
*
|
||||
* @param updates - 配置更新对象
|
||||
*/
|
||||
public update(updates: Partial<INetworkConfig>): void {
|
||||
const flatUpdates = this.flattenObject(updates as unknown as Record<string, unknown>);
|
||||
|
||||
for (const [path, value] of Object.entries(flatUpdates)) {
|
||||
this.set(path, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置配置为默认值
|
||||
*/
|
||||
public reset(): void {
|
||||
const oldConfig = { ...this._config };
|
||||
this._config = this.createDefaultConfig();
|
||||
|
||||
this.emitConfigUpdate({
|
||||
path: '',
|
||||
oldValue: oldConfig,
|
||||
newValue: this._config,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加配置更新监听器
|
||||
*
|
||||
* @param path - 监听的配置路径(空字符串监听所有)
|
||||
* @param listener - 监听器函数
|
||||
*/
|
||||
public addUpdateListener(path: string, listener: (event: IConfigUpdateEvent) => void): void {
|
||||
if (!this._updateListeners.has(path)) {
|
||||
this._updateListeners.set(path, []);
|
||||
}
|
||||
this._updateListeners.get(path)!.push(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除配置更新监听器
|
||||
*
|
||||
* @param path - 配置路径
|
||||
* @param listener - 监听器函数
|
||||
*/
|
||||
public removeUpdateListener(path: string, listener: (event: IConfigUpdateEvent) => void): void {
|
||||
const listeners = this._updateListeners.get(path);
|
||||
if (listeners) {
|
||||
const index = listeners.indexOf(listener);
|
||||
if (index !== -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从环境变量加载配置
|
||||
*/
|
||||
public loadFromEnv(): void {
|
||||
const envConfig = this.parseEnvConfig();
|
||||
if (Object.keys(envConfig).length > 0) {
|
||||
this.update(envConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JSON对象加载配置
|
||||
*
|
||||
* @param config - 配置对象
|
||||
*/
|
||||
public loadFromObject(config: Partial<INetworkConfig>): void {
|
||||
this.validateConfig(config);
|
||||
this.update(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出配置为JSON
|
||||
*/
|
||||
public exportConfig(): INetworkConfig {
|
||||
return JSON.parse(JSON.stringify(this._config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证配置值
|
||||
*/
|
||||
private validateConfigValue(path: string, value: unknown): void {
|
||||
// 基本类型检查
|
||||
if (path.includes('timeout') || path.includes('interval') || path.includes('delay')) {
|
||||
if (typeof value !== 'number' || value < 0) {
|
||||
throw new Error(`配置值必须是非负数: ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (path.includes('size') && typeof value === 'number' && value <= 0) {
|
||||
throw new Error(`大小配置必须是正数: ${path}`);
|
||||
}
|
||||
|
||||
if (path.includes('url') && typeof value !== 'string') {
|
||||
throw new Error(`URL配置必须是字符串: ${path}`);
|
||||
}
|
||||
|
||||
// 特定路径验证
|
||||
if (path === 'syncVar.maxFieldNumber' && typeof value === 'number' && value > SYNCVAR_CONFIG.MAX_FIELD_NUMBER) {
|
||||
throw new Error(`字段编号不能超过 ${SYNCVAR_CONFIG.MAX_FIELD_NUMBER}`);
|
||||
}
|
||||
|
||||
if (path === 'syncVar.minFieldNumber' && typeof value === 'number' && value < SYNCVAR_CONFIG.MIN_FIELD_NUMBER) {
|
||||
throw new Error(`字段编号不能小于 ${SYNCVAR_CONFIG.MIN_FIELD_NUMBER}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证整个配置对象
|
||||
*/
|
||||
private validateConfig(config: Partial<INetworkConfig>): void {
|
||||
// 递归验证配置结构
|
||||
const flatConfig = this.flattenObject(config);
|
||||
for (const [path, value] of Object.entries(flatConfig)) {
|
||||
this.validateConfigValue(path, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扁平化对象
|
||||
*/
|
||||
private flattenObject(obj: Record<string, unknown>, prefix = ''): Record<string, unknown> {
|
||||
const flattened: Record<string, unknown> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const newKey = prefix ? `${prefix}.${key}` : key;
|
||||
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
Object.assign(flattened, this.flattenObject(value as Record<string, unknown>, newKey));
|
||||
} else {
|
||||
flattened[newKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return flattened;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析环境变量配置
|
||||
*/
|
||||
private parseEnvConfig(): Partial<INetworkConfig> {
|
||||
const config: Partial<INetworkConfig> = {};
|
||||
|
||||
// 连接配置
|
||||
if (process.env.NETWORK_CONNECTION_TIMEOUT) {
|
||||
config.connection = {
|
||||
timeout: parseInt(process.env.NETWORK_CONNECTION_TIMEOUT, 10),
|
||||
maxReconnectAttempts: NETWORK_CONFIG.DEFAULT_MAX_RECONNECT_ATTEMPTS,
|
||||
reconnectDelay: NETWORK_CONFIG.DEFAULT_RECONNECT_DELAY
|
||||
};
|
||||
}
|
||||
|
||||
// 心跳配置
|
||||
if (process.env.NETWORK_HEARTBEAT_INTERVAL) {
|
||||
config.heartbeat = {
|
||||
interval: parseInt(process.env.NETWORK_HEARTBEAT_INTERVAL, 10),
|
||||
timeout: NETWORK_CONFIG.DEFAULT_HEARTBEAT_TIMEOUT,
|
||||
maxConsecutiveLoss: NETWORK_CONFIG.DEFAULT_MAX_CONSECUTIVE_LOSS,
|
||||
packetSize: NETWORK_CONFIG.DEFAULT_HEARTBEAT_PACKET_SIZE,
|
||||
enableAdaptiveInterval: true,
|
||||
rttHistorySize: NETWORK_CONFIG.DEFAULT_RTT_HISTORY_SIZE
|
||||
};
|
||||
}
|
||||
|
||||
// TSRPC配置
|
||||
if (process.env.TSRPC_SERVER_URL) {
|
||||
config.tsrpc = {
|
||||
defaultServerUrl: process.env.TSRPC_SERVER_URL,
|
||||
defaultTimeout: TSRPC_CONFIG.DEFAULT_TIMEOUT,
|
||||
heartbeatInterval: TSRPC_CONFIG.DEFAULT_HEARTBEAT.interval,
|
||||
heartbeatTimeout: TSRPC_CONFIG.DEFAULT_HEARTBEAT.timeout,
|
||||
poolConfig: {
|
||||
minConnections: TSRPC_CONFIG.DEFAULT_POOL_CONFIG.minConnections,
|
||||
maxConnections: TSRPC_CONFIG.DEFAULT_POOL_CONFIG.maxConnections,
|
||||
idleTimeout: TSRPC_CONFIG.DEFAULT_POOL_CONFIG.idleTimeout
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发配置更新事件
|
||||
*/
|
||||
private emitConfigUpdate(event: IConfigUpdateEvent): void {
|
||||
// 触发具体路径的监听器
|
||||
const listeners = this._updateListeners.get(event.path);
|
||||
if (listeners) {
|
||||
listeners.forEach(listener => {
|
||||
try {
|
||||
listener(event);
|
||||
} catch (error) {
|
||||
console.error(`配置更新监听器错误 (${event.path}):`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 触发全局监听器
|
||||
const globalListeners = this._updateListeners.get('');
|
||||
if (globalListeners) {
|
||||
globalListeners.forEach(listener => {
|
||||
try {
|
||||
listener(event);
|
||||
} catch (error) {
|
||||
console.error('全局配置更新监听器错误:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置管理器单例实例
|
||||
*/
|
||||
export const NetworkConfig = NetworkConfigManager.Instance;
|
||||
477
packages/network/src/Core/HeartbeatManager.ts
Normal file
477
packages/network/src/Core/HeartbeatManager.ts
Normal file
@@ -0,0 +1,477 @@
|
||||
import { NetworkConnection } from './NetworkConnection';
|
||||
import { MessageData } from '../types/NetworkTypes';
|
||||
import { NETWORK_CONFIG } from '../constants/NetworkConstants';
|
||||
|
||||
// Logger akan diimport dari framework logger
|
||||
const logger = {
|
||||
info: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
};
|
||||
|
||||
/**
|
||||
* 心跳包数据
|
||||
*/
|
||||
export interface HeartbeatPacket {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
sequenceNumber: number;
|
||||
type: 'ping' | 'pong';
|
||||
payload?: MessageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳统计信息
|
||||
*/
|
||||
export interface HeartbeatStats {
|
||||
/** 总发送数 */
|
||||
totalSent: number;
|
||||
/** 总接收数 */
|
||||
totalReceived: number;
|
||||
/** 丢包数 */
|
||||
lostPackets: number;
|
||||
/** 丢包率 */
|
||||
packetLossRate: number;
|
||||
/** 平均RTT */
|
||||
averageRtt: number;
|
||||
/** 最小RTT */
|
||||
minRtt: number;
|
||||
/** 最大RTT */
|
||||
maxRtt: number;
|
||||
/** 抖动 */
|
||||
jitter: number;
|
||||
/** 连续丢包数 */
|
||||
consecutiveLoss: number;
|
||||
/** 最后心跳时间 */
|
||||
lastHeartbeat: number;
|
||||
/** 连接状态 */
|
||||
isAlive: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳配置
|
||||
*/
|
||||
export interface HeartbeatConfig {
|
||||
/** 心跳间隔(毫秒) */
|
||||
interval: number;
|
||||
/** 超时时间(毫秒) */
|
||||
timeout: number;
|
||||
/** 最大连续丢包数 */
|
||||
maxConsecutiveLoss: number;
|
||||
/** 心跳包大小(字节) */
|
||||
packetSize: number;
|
||||
/** 是否启用自适应间隔 */
|
||||
enableAdaptiveInterval: boolean;
|
||||
/** RTT历史记录数量 */
|
||||
rttHistorySize: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳管理器
|
||||
*
|
||||
* 提供精确的包丢失检测和连接质量监控
|
||||
*/
|
||||
export class HeartbeatManager {
|
||||
private static readonly DEFAULT_CONFIG: HeartbeatConfig = {
|
||||
interval: NETWORK_CONFIG.DEFAULT_HEARTBEAT_INTERVAL,
|
||||
timeout: NETWORK_CONFIG.DEFAULT_HEARTBEAT_TIMEOUT,
|
||||
maxConsecutiveLoss: NETWORK_CONFIG.DEFAULT_MAX_CONSECUTIVE_LOSS,
|
||||
packetSize: NETWORK_CONFIG.DEFAULT_HEARTBEAT_PACKET_SIZE,
|
||||
enableAdaptiveInterval: true,
|
||||
rttHistorySize: NETWORK_CONFIG.DEFAULT_RTT_HISTORY_SIZE
|
||||
};
|
||||
|
||||
private _config: HeartbeatConfig;
|
||||
private _connection: NetworkConnection;
|
||||
private _isRunning: boolean = false;
|
||||
private _intervalId: NodeJS.Timeout | null = null;
|
||||
private _sequenceNumber: number = 0;
|
||||
|
||||
// 统计数据
|
||||
private _stats: HeartbeatStats = {
|
||||
totalSent: 0,
|
||||
totalReceived: 0,
|
||||
lostPackets: 0,
|
||||
packetLossRate: 0,
|
||||
averageRtt: 0,
|
||||
minRtt: Number.MAX_VALUE,
|
||||
maxRtt: 0,
|
||||
jitter: 0,
|
||||
consecutiveLoss: 0,
|
||||
lastHeartbeat: 0,
|
||||
isAlive: true
|
||||
};
|
||||
|
||||
// 心跳记录
|
||||
private _pendingPings: Map<string, { packet: HeartbeatPacket; sentAt: number }> = new Map();
|
||||
private _rttHistory: number[] = [];
|
||||
private _lastRtt: number = 0;
|
||||
|
||||
constructor(connection: NetworkConnection, config?: Partial<HeartbeatConfig>) {
|
||||
this._connection = connection;
|
||||
this._config = { ...HeartbeatManager.DEFAULT_CONFIG, ...config };
|
||||
this.setupConnectionHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接处理器
|
||||
*/
|
||||
private setupConnectionHandlers(): void {
|
||||
this._connection.on('message', (data) => {
|
||||
this.handleMessage(data);
|
||||
});
|
||||
|
||||
this._connection.on('disconnected', () => {
|
||||
this.stop();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始心跳监控
|
||||
*/
|
||||
public start(): void {
|
||||
if (this._isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isRunning = true;
|
||||
this.scheduleNextHeartbeat();
|
||||
logger.info('心跳管理器已启动');
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳监控
|
||||
*/
|
||||
public stop(): void {
|
||||
if (!this._isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isRunning = false;
|
||||
|
||||
if (this._intervalId) {
|
||||
clearTimeout(this._intervalId);
|
||||
this._intervalId = null;
|
||||
}
|
||||
|
||||
this._pendingPings.clear();
|
||||
logger.info('心跳管理器已停止');
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排下次心跳
|
||||
*/
|
||||
private scheduleNextHeartbeat(): void {
|
||||
if (!this._isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
let interval = this._config.interval;
|
||||
|
||||
// 自适应间隔调整
|
||||
if (this._config.enableAdaptiveInterval) {
|
||||
interval = this.calculateAdaptiveInterval();
|
||||
}
|
||||
|
||||
this._intervalId = setTimeout(() => {
|
||||
this.sendHeartbeat();
|
||||
this.scheduleNextHeartbeat();
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算自适应间隔
|
||||
*/
|
||||
private calculateAdaptiveInterval(): number {
|
||||
const baseInterval = this._config.interval;
|
||||
|
||||
// 根据网络质量调整间隔
|
||||
if (this._stats.packetLossRate > 0.05) {
|
||||
// 丢包率高时增加心跳频率
|
||||
return Math.max(baseInterval * 0.5, 1000);
|
||||
} else if (this._stats.packetLossRate < 0.01 && this._stats.averageRtt < 50) {
|
||||
// 网络质量好时减少心跳频率
|
||||
return Math.min(baseInterval * 1.5, 15000);
|
||||
}
|
||||
|
||||
return baseInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送心跳包
|
||||
*/
|
||||
private sendHeartbeat(): void {
|
||||
const packet: HeartbeatPacket = {
|
||||
id: this.generatePacketId(),
|
||||
timestamp: Date.now(),
|
||||
sequenceNumber: ++this._sequenceNumber,
|
||||
type: 'ping',
|
||||
payload: this.generateHeartbeatPayload()
|
||||
};
|
||||
|
||||
const data = this.serializePacket(packet);
|
||||
const success = this._connection.send(data);
|
||||
|
||||
if (success) {
|
||||
this._pendingPings.set(packet.id, {
|
||||
packet,
|
||||
sentAt: Date.now()
|
||||
});
|
||||
this._stats.totalSent++;
|
||||
|
||||
// 清理超时的心跳包
|
||||
this.cleanupTimeoutPings();
|
||||
} else {
|
||||
logger.warn('心跳包发送失败');
|
||||
this._stats.consecutiveLoss++;
|
||||
this.updateConnectionStatus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理接收到的消息
|
||||
*/
|
||||
private handleMessage(data: Uint8Array): void {
|
||||
try {
|
||||
const packet = this.deserializePacket(data);
|
||||
if (!packet || !this.isHeartbeatPacket(packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.type === 'ping') {
|
||||
this.handlePingPacket(packet);
|
||||
} else if (packet.type === 'pong') {
|
||||
this.handlePongPacket(packet);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.debug('处理心跳包时出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Ping包
|
||||
*/
|
||||
private handlePingPacket(packet: HeartbeatPacket): void {
|
||||
const pongPacket: HeartbeatPacket = {
|
||||
id: packet.id,
|
||||
timestamp: Date.now(),
|
||||
sequenceNumber: packet.sequenceNumber,
|
||||
type: 'pong',
|
||||
payload: packet.payload
|
||||
};
|
||||
|
||||
const data = this.serializePacket(pongPacket);
|
||||
this._connection.send(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Pong包
|
||||
*/
|
||||
private handlePongPacket(packet: HeartbeatPacket): void {
|
||||
const pendingPing = this._pendingPings.get(packet.id);
|
||||
if (!pendingPing) {
|
||||
return; // 可能是超时的包
|
||||
}
|
||||
|
||||
// 计算RTT
|
||||
const now = Date.now();
|
||||
const rtt = now - pendingPing.sentAt;
|
||||
|
||||
// 更新统计信息
|
||||
this._stats.totalReceived++;
|
||||
this._stats.lastHeartbeat = now;
|
||||
this._stats.consecutiveLoss = 0;
|
||||
this._stats.isAlive = true;
|
||||
|
||||
// 更新RTT统计
|
||||
this.updateRttStats(rtt);
|
||||
|
||||
// 移除已确认的ping
|
||||
this._pendingPings.delete(packet.id);
|
||||
|
||||
// 更新丢包统计
|
||||
this.updatePacketLossStats();
|
||||
|
||||
logger.debug(`心跳RTT: ${rtt}ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新RTT统计
|
||||
*/
|
||||
private updateRttStats(rtt: number): void {
|
||||
this._rttHistory.push(rtt);
|
||||
if (this._rttHistory.length > this._config.rttHistorySize) {
|
||||
this._rttHistory.shift();
|
||||
}
|
||||
|
||||
// 计算平均RTT
|
||||
this._stats.averageRtt = this._rttHistory.reduce((sum, r) => sum + r, 0) / this._rttHistory.length;
|
||||
|
||||
// 更新最小/最大RTT
|
||||
this._stats.minRtt = Math.min(this._stats.minRtt, rtt);
|
||||
this._stats.maxRtt = Math.max(this._stats.maxRtt, rtt);
|
||||
|
||||
// 计算抖动
|
||||
if (this._lastRtt > 0) {
|
||||
const jitterSample = Math.abs(rtt - this._lastRtt);
|
||||
this._stats.jitter = (this._stats.jitter * 0.9) + (jitterSample * 0.1);
|
||||
}
|
||||
this._lastRtt = rtt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新丢包统计
|
||||
*/
|
||||
private updatePacketLossStats(): void {
|
||||
const now = Date.now();
|
||||
let lostPackets = 0;
|
||||
|
||||
// 计算超时的包数量
|
||||
for (const [id, pingInfo] of this._pendingPings) {
|
||||
if (now - pingInfo.sentAt > this._config.timeout) {
|
||||
lostPackets++;
|
||||
this._pendingPings.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
this._stats.lostPackets += lostPackets;
|
||||
|
||||
if (this._stats.totalSent > 0) {
|
||||
this._stats.packetLossRate = this._stats.lostPackets / this._stats.totalSent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理超时的ping包
|
||||
*/
|
||||
private cleanupTimeoutPings(): void {
|
||||
const now = Date.now();
|
||||
const timeoutIds: string[] = [];
|
||||
|
||||
for (const [id, pingInfo] of this._pendingPings) {
|
||||
if (now - pingInfo.sentAt > this._config.timeout) {
|
||||
timeoutIds.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (timeoutIds.length > 0) {
|
||||
this._stats.consecutiveLoss += timeoutIds.length;
|
||||
this._stats.lostPackets += timeoutIds.length;
|
||||
|
||||
for (const id of timeoutIds) {
|
||||
this._pendingPings.delete(id);
|
||||
}
|
||||
|
||||
this.updateConnectionStatus();
|
||||
logger.debug(`清理了 ${timeoutIds.length} 个超时心跳包`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新连接状态
|
||||
*/
|
||||
private updateConnectionStatus(): void {
|
||||
const now = Date.now();
|
||||
const timeSinceLastHeartbeat = now - this._stats.lastHeartbeat;
|
||||
|
||||
// 检查连接是否还活着
|
||||
this._stats.isAlive =
|
||||
this._stats.consecutiveLoss < this._config.maxConsecutiveLoss &&
|
||||
timeSinceLastHeartbeat < this._config.timeout * 2;
|
||||
|
||||
if (!this._stats.isAlive) {
|
||||
logger.warn('连接被判定为不活跃', {
|
||||
consecutiveLoss: this._stats.consecutiveLoss,
|
||||
timeSinceLastHeartbeat
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成包ID
|
||||
*/
|
||||
private generatePacketId(): string {
|
||||
return `heartbeat_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成心跳载荷
|
||||
*/
|
||||
private generateHeartbeatPayload(): MessageData {
|
||||
const payloadSize = this._config.packetSize - 50; // 减去头部大小
|
||||
return {
|
||||
data: 'x'.repeat(Math.max(0, payloadSize)),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化包
|
||||
*/
|
||||
private serializePacket(packet: HeartbeatPacket): Uint8Array {
|
||||
const jsonString = JSON.stringify(packet);
|
||||
return new TextEncoder().encode(jsonString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化包
|
||||
*/
|
||||
private deserializePacket(data: Uint8Array): HeartbeatPacket | null {
|
||||
try {
|
||||
const jsonString = new TextDecoder().decode(data);
|
||||
return JSON.parse(jsonString) as HeartbeatPacket;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为心跳包
|
||||
*/
|
||||
private isHeartbeatPacket(packet: unknown): packet is HeartbeatPacket {
|
||||
return typeof packet === 'object' &&
|
||||
packet !== null &&
|
||||
typeof (packet as Record<string, unknown>).id === 'string' &&
|
||||
typeof (packet as Record<string, unknown>).timestamp === 'number' &&
|
||||
typeof (packet as Record<string, unknown>).sequenceNumber === 'number' &&
|
||||
((packet as Record<string, unknown>).type === 'ping' || (packet as Record<string, unknown>).type === 'pong');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): HeartbeatStats {
|
||||
return { ...this._stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this._stats = {
|
||||
totalSent: 0,
|
||||
totalReceived: 0,
|
||||
lostPackets: 0,
|
||||
packetLossRate: 0,
|
||||
averageRtt: 0,
|
||||
minRtt: Number.MAX_VALUE,
|
||||
maxRtt: 0,
|
||||
jitter: 0,
|
||||
consecutiveLoss: 0,
|
||||
lastHeartbeat: 0,
|
||||
isAlive: true
|
||||
};
|
||||
this._rttHistory = [];
|
||||
this._pendingPings.clear();
|
||||
logger.debug('心跳统计信息已重置');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<HeartbeatConfig>): void {
|
||||
this._config = { ...this._config, ...newConfig };
|
||||
logger.debug('心跳配置已更新');
|
||||
}
|
||||
}
|
||||
@@ -150,8 +150,7 @@ export class NetworkClient {
|
||||
this.emit('message', data);
|
||||
|
||||
// 自动处理消息
|
||||
const { MessageHandler } = require('../Messaging/MessageHandler');
|
||||
await MessageHandler.Instance.handleRawMessage(data);
|
||||
await this._messageHandler.handleRawMessage(data);
|
||||
});
|
||||
|
||||
this._connection.on('error', (error) => {
|
||||
|
||||
@@ -16,9 +16,12 @@ export enum ConnectionState {
|
||||
*/
|
||||
export interface NetworkConnectionEvents {
|
||||
connected: () => void;
|
||||
disconnected: (reason?: string) => void;
|
||||
disconnected: (reason?: string, code?: number) => void;
|
||||
message: (data: Uint8Array) => void;
|
||||
error: (error: Error) => void;
|
||||
reconnecting: (attempt: number) => void;
|
||||
reconnected: () => void;
|
||||
reconnectFailed: (error: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,23 +41,55 @@ export class NetworkConnection {
|
||||
private _pingInterval: NodeJS.Timeout | null = null;
|
||||
private _eventHandlers: Map<keyof NetworkConnectionEvents, Function[]> = new Map();
|
||||
|
||||
// 重连相关属性
|
||||
private _reconnectAttempts: number = 0;
|
||||
private _maxReconnectAttempts: number = 5;
|
||||
private _reconnectInterval: number = 1000; // 1秒
|
||||
private _maxReconnectInterval: number = 30000; // 30秒
|
||||
private _reconnectTimer: NodeJS.Timeout | null = null;
|
||||
private _enableAutoReconnect: boolean = true;
|
||||
private _originalUrl: string = '';
|
||||
|
||||
// 心跳配置
|
||||
private static readonly PING_INTERVAL = 30000; // 30秒
|
||||
private static readonly PING_TIMEOUT = 5000; // 5秒超时
|
||||
|
||||
// 错误分类
|
||||
private static readonly RECOVERABLE_CODES = [1006, 1011, 1012, 1013, 1014];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param ws - WebSocket实例
|
||||
* @param connectionId - 连接ID
|
||||
* @param address - 连接地址
|
||||
* @param options - 连接选项
|
||||
*/
|
||||
constructor(ws: WebSocket, connectionId: string, address: string = '') {
|
||||
constructor(
|
||||
ws: WebSocket,
|
||||
connectionId: string,
|
||||
address: string = '',
|
||||
options?: {
|
||||
enableAutoReconnect?: boolean;
|
||||
maxReconnectAttempts?: number;
|
||||
reconnectInterval?: number;
|
||||
maxReconnectInterval?: number;
|
||||
}
|
||||
) {
|
||||
this._ws = ws;
|
||||
this._connectionId = connectionId;
|
||||
this._address = address;
|
||||
this._originalUrl = address;
|
||||
this._connectedTime = Date.now();
|
||||
|
||||
// 设置重连选项
|
||||
if (options) {
|
||||
this._enableAutoReconnect = options.enableAutoReconnect ?? true;
|
||||
this._maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
||||
this._reconnectInterval = options.reconnectInterval ?? 1000;
|
||||
this._maxReconnectInterval = options.maxReconnectInterval ?? 30000;
|
||||
}
|
||||
|
||||
this.setupWebSocket();
|
||||
this.startPingInterval();
|
||||
}
|
||||
@@ -67,13 +102,31 @@ export class NetworkConnection {
|
||||
|
||||
this._ws.onopen = () => {
|
||||
this._state = ConnectionState.Connected;
|
||||
|
||||
// 重连成功,重置重连状态
|
||||
if (this._reconnectAttempts > 0) {
|
||||
NetworkConnection.logger.info(`重连成功 (第 ${this._reconnectAttempts} 次尝试)`);
|
||||
this.resetReconnectState();
|
||||
this.emit('reconnected');
|
||||
}
|
||||
|
||||
this.emit('connected');
|
||||
};
|
||||
|
||||
this._ws.onclose = (event) => {
|
||||
this._state = ConnectionState.Disconnected;
|
||||
this.stopPingInterval();
|
||||
this.emit('disconnected', event.reason);
|
||||
|
||||
const reason = event.reason || '连接已关闭';
|
||||
const code = event.code;
|
||||
|
||||
NetworkConnection.logger.warn(`连接断开: ${reason} (code: ${code})`);
|
||||
this.emit('disconnected', reason, code);
|
||||
|
||||
// 判断是否需要自动重连
|
||||
if (this._enableAutoReconnect && this.shouldReconnect(code)) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
};
|
||||
|
||||
this._ws.onerror = (event) => {
|
||||
@@ -164,6 +217,119 @@ export class NetworkConnection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该重连
|
||||
*/
|
||||
private shouldReconnect(code: number): boolean {
|
||||
// 正常关闭不需要重连
|
||||
if (code === 1000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否是可恢复的错误代码
|
||||
return NetworkConnection.RECOVERABLE_CODES.includes(code) || code === 1006;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排重连
|
||||
*/
|
||||
private scheduleReconnect(): void {
|
||||
if (this._reconnectAttempts >= this._maxReconnectAttempts) {
|
||||
NetworkConnection.logger.error(`重连失败,已达到最大重试次数 ${this._maxReconnectAttempts}`);
|
||||
this.emit('reconnectFailed', new Error(`重连失败,已达到最大重试次数`));
|
||||
return;
|
||||
}
|
||||
|
||||
this._reconnectAttempts++;
|
||||
const delay = Math.min(
|
||||
this._reconnectInterval * Math.pow(2, this._reconnectAttempts - 1),
|
||||
this._maxReconnectInterval
|
||||
);
|
||||
|
||||
NetworkConnection.logger.info(`将在 ${delay}ms 后尝试第 ${this._reconnectAttempts} 次重连`);
|
||||
this.emit('reconnecting', this._reconnectAttempts);
|
||||
|
||||
this._reconnectTimer = setTimeout(() => {
|
||||
this.attemptReconnect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试重连
|
||||
*/
|
||||
private attemptReconnect(): void {
|
||||
if (this._state === ConnectionState.Connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
NetworkConnection.logger.info(`正在尝试重连到 ${this._originalUrl}`);
|
||||
|
||||
// 创建新的WebSocket连接
|
||||
this._ws = new WebSocket(this._originalUrl);
|
||||
this._state = ConnectionState.Connecting;
|
||||
|
||||
// 重新设置事件处理
|
||||
this.setupWebSocket();
|
||||
|
||||
} catch (error) {
|
||||
NetworkConnection.logger.error(`重连失败:`, error);
|
||||
|
||||
// 继续下一次重连尝试
|
||||
setTimeout(() => {
|
||||
this.scheduleReconnect();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置重连状态
|
||||
*/
|
||||
private resetReconnectState(): void {
|
||||
this._reconnectAttempts = 0;
|
||||
|
||||
if (this._reconnectTimer) {
|
||||
clearTimeout(this._reconnectTimer);
|
||||
this._reconnectTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置重连配置
|
||||
*/
|
||||
public setReconnectOptions(options: {
|
||||
enableAutoReconnect?: boolean;
|
||||
maxReconnectAttempts?: number;
|
||||
reconnectInterval?: number;
|
||||
maxReconnectInterval?: number;
|
||||
}): void {
|
||||
if (options.enableAutoReconnect !== undefined) {
|
||||
this._enableAutoReconnect = options.enableAutoReconnect;
|
||||
}
|
||||
if (options.maxReconnectAttempts !== undefined) {
|
||||
this._maxReconnectAttempts = options.maxReconnectAttempts;
|
||||
}
|
||||
if (options.reconnectInterval !== undefined) {
|
||||
this._reconnectInterval = options.reconnectInterval;
|
||||
}
|
||||
if (options.maxReconnectInterval !== undefined) {
|
||||
this._maxReconnectInterval = options.maxReconnectInterval;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动重连
|
||||
*/
|
||||
public reconnect(): void {
|
||||
if (this._state === ConnectionState.Connected) {
|
||||
NetworkConnection.logger.warn('连接已存在,无需重连');
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetReconnectState();
|
||||
this.attemptReconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭连接
|
||||
*
|
||||
@@ -175,6 +341,11 @@ export class NetworkConnection {
|
||||
}
|
||||
|
||||
this._state = ConnectionState.Disconnecting;
|
||||
|
||||
// 主动关闭时禁用自动重连
|
||||
this._enableAutoReconnect = false;
|
||||
this.resetReconnectState();
|
||||
|
||||
this.stopPingInterval();
|
||||
|
||||
if (this._ws) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* 监控网络连接的性能指标,包括延迟、吞吐量、包丢失率等
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { HeartbeatManager, HeartbeatStats } from './HeartbeatManager';
|
||||
import { NetworkConnection } from './NetworkConnection';
|
||||
|
||||
export interface NetworkMetrics {
|
||||
/** 往返时延 (ms) */
|
||||
@@ -103,6 +105,9 @@ export class NetworkPerformanceMonitor {
|
||||
/** 事件监听器 */
|
||||
private _eventListeners: Map<string, Function[]> = new Map();
|
||||
|
||||
/** 心跳管理器映射 */
|
||||
private _heartbeatManagers: Map<string, HeartbeatManager> = new Map();
|
||||
|
||||
public static get Instance(): NetworkPerformanceMonitor {
|
||||
if (!NetworkPerformanceMonitor._instance) {
|
||||
NetworkPerformanceMonitor._instance = new NetworkPerformanceMonitor();
|
||||
@@ -200,8 +205,8 @@ export class NetworkPerformanceMonitor {
|
||||
// 计算带宽
|
||||
const { uploadBandwidth, downloadBandwidth } = this.calculateBandwidth();
|
||||
|
||||
// 模拟包丢失率(实际应用中需要通过心跳包检测)
|
||||
const packetLoss = this.estimatePacketLoss();
|
||||
// 获取真实的包丢失率(优先使用心跳管理器数据)
|
||||
const packetLoss = this.getAccuratePacketLoss();
|
||||
|
||||
// 计算连接质量评分
|
||||
const connectionQuality = this.calculateConnectionQuality(avgRtt, jitter, packetLoss);
|
||||
@@ -257,19 +262,43 @@ export class NetworkPerformanceMonitor {
|
||||
|
||||
/**
|
||||
* 估算包丢失率
|
||||
* 使用多种指标进行更精确的丢包检测
|
||||
*/
|
||||
private estimatePacketLoss(): number {
|
||||
// 实际实现中应该通过心跳包或消息确认机制来检测包丢失
|
||||
// 这里提供一个简单的估算
|
||||
const recentRtt = this._rttHistory.slice(-5);
|
||||
if (recentRtt.length === 0) return 0;
|
||||
const recentRtt = this._rttHistory.slice(-10); // 增加样本数量
|
||||
if (recentRtt.length < 3) return 0; // 需要足够的样本
|
||||
|
||||
// 1. 基于RTT标准差的检测
|
||||
const avgRtt = recentRtt.reduce((a, b) => a + b, 0) / recentRtt.length;
|
||||
const maxRtt = Math.max(...recentRtt);
|
||||
const variance = recentRtt.reduce((sum, rtt) => sum + Math.pow(rtt - avgRtt, 2), 0) / recentRtt.length;
|
||||
const stdDev = Math.sqrt(variance);
|
||||
const coefficientOfVariation = stdDev / avgRtt;
|
||||
|
||||
// 基于RTT变化估算丢包率
|
||||
const rttVariation = maxRtt / avgRtt - 1;
|
||||
return Math.min(rttVariation * 0.1, 0.1); // 最大10%丢包率
|
||||
// 2. 异常RTT值检测(可能是重传导致)
|
||||
const threshold = avgRtt + 2 * stdDev;
|
||||
const abnormalRttCount = recentRtt.filter(rtt => rtt > threshold).length;
|
||||
const abnormalRttRatio = abnormalRttCount / recentRtt.length;
|
||||
|
||||
// 3. 基于连续超时的检测
|
||||
let consecutiveHighRtt = 0;
|
||||
let maxConsecutive = 0;
|
||||
for (const rtt of recentRtt) {
|
||||
if (rtt > avgRtt * 1.5) {
|
||||
consecutiveHighRtt++;
|
||||
maxConsecutive = Math.max(maxConsecutive, consecutiveHighRtt);
|
||||
} else {
|
||||
consecutiveHighRtt = 0;
|
||||
}
|
||||
}
|
||||
const consecutiveImpact = Math.min(maxConsecutive / recentRtt.length * 2, 1);
|
||||
|
||||
// 综合评估丢包率
|
||||
const basePacketLoss = Math.min(coefficientOfVariation * 0.3, 0.15);
|
||||
const abnormalAdjustment = abnormalRttRatio * 0.1;
|
||||
const consecutiveAdjustment = consecutiveImpact * 0.05;
|
||||
|
||||
const totalPacketLoss = basePacketLoss + abnormalAdjustment + consecutiveAdjustment;
|
||||
return Math.min(totalPacketLoss, 0.2); // 最大20%丢包率
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,22 +325,87 @@ export class NetworkPerformanceMonitor {
|
||||
|
||||
/**
|
||||
* 获取SyncVar统计信息
|
||||
* 改进异常处理,提供更详细的错误信息和降级策略
|
||||
* 使用懒加载避免循环依赖问题
|
||||
*/
|
||||
private getSyncVarStatistics(): PerformanceSnapshot['syncVarStats'] {
|
||||
try {
|
||||
const { SyncVarSyncScheduler } = require('../SyncVar/SyncVarSyncScheduler');
|
||||
const scheduler = SyncVarSyncScheduler.Instance;
|
||||
// 使用懒加载获取SyncVarSyncScheduler,避免循环依赖
|
||||
const scheduler = this.getSyncVarScheduler();
|
||||
|
||||
if (!scheduler) {
|
||||
NetworkPerformanceMonitor.logger.debug('SyncVarSyncScheduler实例不存在,可能尚未初始化');
|
||||
return {
|
||||
syncedComponents: 0,
|
||||
syncedFields: 0,
|
||||
averageSyncRate: 0,
|
||||
syncDataSize: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 检查getStats方法是否存在
|
||||
if (typeof scheduler.getStats !== 'function') {
|
||||
NetworkPerformanceMonitor.logger.warn('SyncVarSyncScheduler缺少getStats方法');
|
||||
return {
|
||||
syncedComponents: 0,
|
||||
syncedFields: 0,
|
||||
averageSyncRate: 0,
|
||||
syncDataSize: 0
|
||||
};
|
||||
}
|
||||
|
||||
const stats = scheduler.getStats();
|
||||
|
||||
return {
|
||||
syncedComponents: stats.totalComponents || 0,
|
||||
syncedFields: stats.totalFields || 0,
|
||||
averageSyncRate: stats.averageFrequency || 0,
|
||||
syncDataSize: stats.totalDataSize || 0
|
||||
// 验证统计数据的有效性
|
||||
const validatedStats = {
|
||||
syncedComponents: (typeof stats.totalComponents === 'number') ? Math.max(0, stats.totalComponents) : 0,
|
||||
syncedFields: (typeof stats.totalFields === 'number') ? Math.max(0, stats.totalFields) : 0,
|
||||
averageSyncRate: (typeof stats.averageFrequency === 'number') ? Math.max(0, stats.averageFrequency) : 0,
|
||||
syncDataSize: (typeof stats.totalDataSize === 'number') ? Math.max(0, stats.totalDataSize) : 0
|
||||
};
|
||||
|
||||
return validatedStats;
|
||||
|
||||
} catch (error) {
|
||||
NetworkPerformanceMonitor.logger.warn('获取SyncVar统计失败:', error);
|
||||
return undefined;
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
NetworkPerformanceMonitor.logger.warn(`获取SyncVar统计失败: ${errorMsg}, 返回默认统计数据`);
|
||||
|
||||
// 返回安全的默认值而不是undefined
|
||||
return {
|
||||
syncedComponents: 0,
|
||||
syncedFields: 0,
|
||||
averageSyncRate: 0,
|
||||
syncDataSize: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 懒加载获取SyncVarSyncScheduler实例,避免循环依赖
|
||||
*/
|
||||
private getSyncVarScheduler(): any | null {
|
||||
try {
|
||||
// 检查全局对象中是否已有实例
|
||||
const globalObj = (globalThis as any);
|
||||
if (globalObj.SyncVarSyncScheduler && globalObj.SyncVarSyncScheduler.Instance) {
|
||||
return globalObj.SyncVarSyncScheduler.Instance;
|
||||
}
|
||||
|
||||
// 尝试动态导入(仅在必要时)
|
||||
try {
|
||||
const SyncVarModule = require('../SyncVar/SyncVarSyncScheduler');
|
||||
if (SyncVarModule.SyncVarSyncScheduler && SyncVarModule.SyncVarSyncScheduler.Instance) {
|
||||
return SyncVarModule.SyncVarSyncScheduler.Instance;
|
||||
}
|
||||
} catch (requireError) {
|
||||
// 如果动态require失败,返回null而不是抛出错误
|
||||
NetworkPerformanceMonitor.logger.debug('无法动态加载SyncVarSyncScheduler模块');
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
NetworkPerformanceMonitor.logger.debug('获取SyncVarScheduler实例失败');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,4 +647,212 @@ export class NetworkPerformanceMonitor {
|
||||
bandwidthWindowSize: this._bandwidthWindow.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络质量自适应机制
|
||||
* 根据网络状况动态调整同步策略
|
||||
*/
|
||||
public adaptNetworkStrategy(): {
|
||||
suggestedSyncInterval: number;
|
||||
suggestedBatchSize: number;
|
||||
suggestedCompressionLevel: number;
|
||||
prioritizeUpdate: boolean;
|
||||
} {
|
||||
const metrics = this.getCurrentMetrics();
|
||||
|
||||
// 基础设置
|
||||
let syncInterval = 50; // 默认50ms
|
||||
let batchSize = 10; // 默认批大小
|
||||
let compressionLevel = 1; // 默认压缩级别
|
||||
let prioritizeUpdate = false;
|
||||
|
||||
// 根据连接质量调整
|
||||
if (metrics.connectionQuality >= 80) {
|
||||
// 高质量网络: 高频更新,小批次
|
||||
syncInterval = 33; // 30fps
|
||||
batchSize = 5;
|
||||
compressionLevel = 0; // 不压缩
|
||||
} else if (metrics.connectionQuality >= 60) {
|
||||
// 中等质量网络: 标准设置
|
||||
syncInterval = 50; // 20fps
|
||||
batchSize = 10;
|
||||
compressionLevel = 1;
|
||||
} else if (metrics.connectionQuality >= 40) {
|
||||
// 低质量网络: 降低频率,增加批处理
|
||||
syncInterval = 100; // 10fps
|
||||
batchSize = 20;
|
||||
compressionLevel = 2;
|
||||
prioritizeUpdate = true;
|
||||
} else {
|
||||
// 极低质量网络: 最保守设置
|
||||
syncInterval = 200; // 5fps
|
||||
batchSize = 50;
|
||||
compressionLevel = 3;
|
||||
prioritizeUpdate = true;
|
||||
}
|
||||
|
||||
// 根据RTT进一步调整
|
||||
if (metrics.rtt > 300) {
|
||||
syncInterval = Math.max(syncInterval * 1.5, 200);
|
||||
batchSize = Math.min(batchSize * 2, 100);
|
||||
}
|
||||
|
||||
// 根据丢包率调整
|
||||
if (metrics.packetLoss > 0.1) {
|
||||
syncInterval = Math.max(syncInterval * 1.2, 150);
|
||||
compressionLevel = Math.min(compressionLevel + 1, 3);
|
||||
prioritizeUpdate = true;
|
||||
}
|
||||
|
||||
return {
|
||||
suggestedSyncInterval: Math.round(syncInterval),
|
||||
suggestedBatchSize: Math.round(batchSize),
|
||||
suggestedCompressionLevel: compressionLevel,
|
||||
prioritizeUpdate
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测网络拥塞状态
|
||||
*/
|
||||
public detectNetworkCongestion(): {
|
||||
isCongested: boolean;
|
||||
congestionLevel: 'none' | 'light' | 'moderate' | 'severe';
|
||||
suggestedAction: string;
|
||||
} {
|
||||
const recentSnapshots = this._snapshots.slice(-5);
|
||||
if (recentSnapshots.length < 3) {
|
||||
return {
|
||||
isCongested: false,
|
||||
congestionLevel: 'none',
|
||||
suggestedAction: '数据不足,继续监控'
|
||||
};
|
||||
}
|
||||
|
||||
// 计算趋势
|
||||
const rttTrend = this.calculateTrend(recentSnapshots.map(s => s.metrics.rtt));
|
||||
const packetLossTrend = this.calculateTrend(recentSnapshots.map(s => s.metrics.packetLoss));
|
||||
const qualityTrend = this.calculateTrend(recentSnapshots.map(s => s.metrics.connectionQuality));
|
||||
|
||||
// 检测拥塞指标
|
||||
const avgRtt = recentSnapshots.reduce((sum, s) => sum + s.metrics.rtt, 0) / recentSnapshots.length;
|
||||
const avgPacketLoss = recentSnapshots.reduce((sum, s) => sum + s.metrics.packetLoss, 0) / recentSnapshots.length;
|
||||
const avgQuality = recentSnapshots.reduce((sum, s) => sum + s.metrics.connectionQuality, 0) / recentSnapshots.length;
|
||||
|
||||
// 拥塞判定
|
||||
let congestionLevel: 'none' | 'light' | 'moderate' | 'severe' = 'none';
|
||||
let suggestedAction = '网络状况良好';
|
||||
|
||||
if (avgRtt > 500 || avgPacketLoss > 0.15 || avgQuality < 30) {
|
||||
congestionLevel = 'severe';
|
||||
suggestedAction = '严重拥塞,建议降低同步频率至最低,启用高压缩';
|
||||
} else if (avgRtt > 300 || avgPacketLoss > 0.08 || avgQuality < 50) {
|
||||
congestionLevel = 'moderate';
|
||||
suggestedAction = '中等拥塞,建议减少同步频率,启用压缩';
|
||||
} else if (avgRtt > 150 || avgPacketLoss > 0.03 || avgQuality < 70) {
|
||||
congestionLevel = 'light';
|
||||
suggestedAction = '轻微拥塞,建议适度降低同步频率';
|
||||
}
|
||||
|
||||
const isCongested = congestionLevel !== 'none';
|
||||
|
||||
return {
|
||||
isCongested,
|
||||
congestionLevel,
|
||||
suggestedAction
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算数据趋势(斜率)
|
||||
*/
|
||||
private calculateTrend(values: number[]): number {
|
||||
if (values.length < 2) return 0;
|
||||
|
||||
const n = values.length;
|
||||
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
sumX += i;
|
||||
sumY += values[i];
|
||||
sumXY += i * values[i];
|
||||
sumX2 += i * i;
|
||||
}
|
||||
|
||||
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
|
||||
return slope;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为连接添加心跳监控
|
||||
*/
|
||||
public addHeartbeatMonitoring(connectionId: string, connection: NetworkConnection): void {
|
||||
if (!this._heartbeatManagers.has(connectionId)) {
|
||||
const heartbeatManager = new HeartbeatManager(connection);
|
||||
this._heartbeatManagers.set(connectionId, heartbeatManager);
|
||||
heartbeatManager.start();
|
||||
NetworkPerformanceMonitor.logger.info(`为连接 ${connectionId} 启动心跳监控`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除连接的心跳监控
|
||||
*/
|
||||
public removeHeartbeatMonitoring(connectionId: string): void {
|
||||
const heartbeatManager = this._heartbeatManagers.get(connectionId);
|
||||
if (heartbeatManager) {
|
||||
heartbeatManager.stop();
|
||||
this._heartbeatManagers.delete(connectionId);
|
||||
NetworkPerformanceMonitor.logger.info(`移除连接 ${connectionId} 的心跳监控`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取精确的包丢失率(优先使用心跳数据)
|
||||
*/
|
||||
private getAccuratePacketLoss(): number {
|
||||
let totalPacketLoss = 0;
|
||||
let count = 0;
|
||||
|
||||
// 从心跳管理器获取真实丢包率
|
||||
for (const heartbeatManager of this._heartbeatManagers.values()) {
|
||||
const stats = heartbeatManager.getStats();
|
||||
totalPacketLoss += stats.packetLossRate;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
return totalPacketLoss / count;
|
||||
}
|
||||
|
||||
// 回退到估算方法
|
||||
return this.estimatePacketLoss();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取心跳统计信息
|
||||
*/
|
||||
public getHeartbeatStats(): Map<string, HeartbeatStats> {
|
||||
const stats = new Map<string, HeartbeatStats>();
|
||||
|
||||
for (const [connectionId, manager] of this._heartbeatManagers) {
|
||||
stats.set(connectionId, manager.getStats());
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有连接的健康状态
|
||||
*/
|
||||
public getConnectionHealth(): Map<string, boolean> {
|
||||
const health = new Map<string, boolean>();
|
||||
|
||||
for (const [connectionId, manager] of this._heartbeatManagers) {
|
||||
const stats = manager.getStats();
|
||||
health.set(connectionId, stats.isAlive);
|
||||
}
|
||||
|
||||
return health;
|
||||
}
|
||||
}
|
||||
@@ -202,8 +202,7 @@ export class NetworkServer {
|
||||
this.emit('clientMessage', connection, data);
|
||||
|
||||
// 自动处理消息
|
||||
const { MessageHandler } = require('../Messaging/MessageHandler');
|
||||
await MessageHandler.Instance.handleRawMessage(data, connection);
|
||||
await this._messageHandler.handleRawMessage(data, connection);
|
||||
});
|
||||
|
||||
connection.on('error', (error) => {
|
||||
|
||||
409
packages/network/src/Error/NetworkErrorHandler.ts
Normal file
409
packages/network/src/Error/NetworkErrorHandler.ts
Normal file
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* 网络错误处理系统
|
||||
*/
|
||||
|
||||
import { ERROR_CODES, LOG_LEVELS } from '../constants/NetworkConstants';
|
||||
import { NetworkErrorType, INetworkError, createNetworkError } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 错误处理器接口
|
||||
*/
|
||||
export interface IErrorHandler {
|
||||
/** 处理器名称 */
|
||||
readonly name: string;
|
||||
/** 是否可以处理该错误 */
|
||||
canHandle(error: INetworkError): boolean;
|
||||
/** 处理错误 */
|
||||
handle(error: INetworkError): Promise<boolean> | boolean;
|
||||
/** 优先级(数值越大优先级越高) */
|
||||
readonly priority: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理选项
|
||||
*/
|
||||
export interface ErrorHandlingOptions {
|
||||
/** 是否自动重试 */
|
||||
autoRetry?: boolean;
|
||||
/** 最大重试次数 */
|
||||
maxRetries?: number;
|
||||
/** 重试延迟(毫秒) */
|
||||
retryDelay?: number;
|
||||
/** 是否记录错误日志 */
|
||||
logError?: boolean;
|
||||
/** 日志级别 */
|
||||
logLevel?: 'error' | 'warn' | 'info' | 'debug';
|
||||
/** 是否通知用户 */
|
||||
notifyUser?: boolean;
|
||||
/** 自定义处理函数 */
|
||||
customHandler?: ((error: INetworkError) => Promise<boolean> | boolean) | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误统计信息
|
||||
*/
|
||||
export interface ErrorStats {
|
||||
/** 总错误数 */
|
||||
totalErrors: number;
|
||||
/** 按类型分组的错误数 */
|
||||
errorsByType: Map<NetworkErrorType, number>;
|
||||
/** 按错误代码分组的错误数 */
|
||||
errorsByCode: Map<string | number, number>;
|
||||
/** 最近的错误 */
|
||||
recentErrors: INetworkError[];
|
||||
/** 错误趋势(每小时) */
|
||||
hourlyTrend: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络错误处理器
|
||||
*
|
||||
* 提供统一的错误处理、重试机制和错误统计功能
|
||||
*/
|
||||
export class NetworkErrorHandler {
|
||||
private static _instance: NetworkErrorHandler | null = null;
|
||||
private _handlers: IErrorHandler[] = [];
|
||||
private _stats: ErrorStats;
|
||||
private _options: Omit<Required<ErrorHandlingOptions>, 'customHandler'> & { customHandler?: (error: INetworkError) => Promise<boolean> | boolean };
|
||||
|
||||
private readonly logger = {
|
||||
info: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
};
|
||||
|
||||
private constructor() {
|
||||
this._stats = {
|
||||
totalErrors: 0,
|
||||
errorsByType: new Map(),
|
||||
errorsByCode: new Map(),
|
||||
recentErrors: [],
|
||||
hourlyTrend: new Array(24).fill(0)
|
||||
};
|
||||
|
||||
this._options = {
|
||||
autoRetry: true,
|
||||
maxRetries: 3,
|
||||
retryDelay: 1000,
|
||||
logError: true,
|
||||
logLevel: 'error' as const,
|
||||
notifyUser: false,
|
||||
customHandler: undefined
|
||||
};
|
||||
|
||||
this.initializeDefaultHandlers();
|
||||
}
|
||||
|
||||
public static get Instance(): NetworkErrorHandler {
|
||||
if (!NetworkErrorHandler._instance) {
|
||||
NetworkErrorHandler._instance = new NetworkErrorHandler();
|
||||
}
|
||||
return NetworkErrorHandler._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理网络错误
|
||||
*/
|
||||
public async handleError(
|
||||
error: Error | INetworkError,
|
||||
options?: Partial<ErrorHandlingOptions>
|
||||
): Promise<boolean> {
|
||||
const networkError = this.ensureNetworkError(error);
|
||||
const handlingOptions = { ...this._options, ...options };
|
||||
|
||||
// 更新统计信息
|
||||
this.updateStats(networkError);
|
||||
|
||||
// 记录错误日志
|
||||
if (handlingOptions.logError) {
|
||||
this.logError(networkError, handlingOptions.logLevel);
|
||||
}
|
||||
|
||||
// 执行自定义处理器
|
||||
if (handlingOptions.customHandler) {
|
||||
try {
|
||||
const handled = await handlingOptions.customHandler(networkError);
|
||||
if (handled) {
|
||||
return true;
|
||||
}
|
||||
} catch (customError) {
|
||||
this.logger.error('自定义错误处理器失败:', customError);
|
||||
}
|
||||
}
|
||||
|
||||
// 查找合适的处理器
|
||||
const handlers = this._handlers
|
||||
.filter(handler => handler.canHandle(networkError))
|
||||
.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
for (const handler of handlers) {
|
||||
try {
|
||||
const handled = await handler.handle(networkError);
|
||||
if (handled) {
|
||||
this.logger.debug(`错误已被处理器 "${handler.name}" 处理`);
|
||||
return true;
|
||||
}
|
||||
} catch (handlerError) {
|
||||
this.logger.error(`处理器 "${handler.name}" 执行失败:`, handlerError);
|
||||
}
|
||||
}
|
||||
|
||||
// 自动重试机制
|
||||
if (handlingOptions.autoRetry && this.shouldRetry(networkError)) {
|
||||
return this.attemptRetry(networkError, handlingOptions);
|
||||
}
|
||||
|
||||
// 通知用户
|
||||
if (handlingOptions.notifyUser) {
|
||||
this.notifyUser(networkError);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加错误处理器
|
||||
*/
|
||||
public addHandler(handler: IErrorHandler): void {
|
||||
this._handlers.push(handler);
|
||||
this._handlers.sort((a, b) => b.priority - a.priority);
|
||||
this.logger.debug(`添加错误处理器: ${handler.name} (优先级: ${handler.priority})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除错误处理器
|
||||
*/
|
||||
public removeHandler(name: string): boolean {
|
||||
const index = this._handlers.findIndex(handler => handler.name === name);
|
||||
if (index !== -1) {
|
||||
this._handlers.splice(index, 1);
|
||||
this.logger.debug(`移除错误处理器: ${name}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认处理选项
|
||||
*/
|
||||
public setDefaultOptions(options: Partial<ErrorHandlingOptions>): void {
|
||||
this._options = { ...this._options, ...options };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误统计信息
|
||||
*/
|
||||
public getStats(): ErrorStats {
|
||||
return {
|
||||
totalErrors: this._stats.totalErrors,
|
||||
errorsByType: new Map(this._stats.errorsByType),
|
||||
errorsByCode: new Map(this._stats.errorsByCode),
|
||||
recentErrors: [...this._stats.recentErrors],
|
||||
hourlyTrend: [...this._stats.hourlyTrend]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this._stats = {
|
||||
totalErrors: 0,
|
||||
errorsByType: new Map(),
|
||||
errorsByCode: new Map(),
|
||||
recentErrors: [],
|
||||
hourlyTrend: new Array(24).fill(0)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建网络错误
|
||||
*/
|
||||
public createError(
|
||||
type: NetworkErrorType,
|
||||
message: string,
|
||||
context?: Record<string, unknown>
|
||||
): INetworkError {
|
||||
return createNetworkError(type, message, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化默认错误处理器
|
||||
*/
|
||||
private initializeDefaultHandlers(): void {
|
||||
// 连接错误处理器
|
||||
this.addHandler({
|
||||
name: 'connection-error-handler',
|
||||
priority: 80,
|
||||
canHandle: (error) => error.type === NetworkErrorType.CONNECTION_FAILED,
|
||||
handle: (error) => {
|
||||
this.logger.warn('连接失败,尝试重新连接:', error.message);
|
||||
return false; // 让重试机制处理
|
||||
}
|
||||
});
|
||||
|
||||
// 序列化错误处理器
|
||||
this.addHandler({
|
||||
name: 'serialization-error-handler',
|
||||
priority: 70,
|
||||
canHandle: (error) =>
|
||||
error.type === NetworkErrorType.SERIALIZATION_FAILED ||
|
||||
error.type === NetworkErrorType.DESERIALIZATION_FAILED,
|
||||
handle: (error) => {
|
||||
this.logger.error('序列化/反序列化失败:', error.message);
|
||||
// 序列化错误通常不需要重试
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// 权限错误处理器
|
||||
this.addHandler({
|
||||
name: 'permission-error-handler',
|
||||
priority: 60,
|
||||
canHandle: (error) => error.type === NetworkErrorType.PERMISSION_DENIED,
|
||||
handle: (error) => {
|
||||
this.logger.warn('权限被拒绝:', error.message);
|
||||
// 权限错误不应该重试
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// 默认错误处理器
|
||||
this.addHandler({
|
||||
name: 'default-error-handler',
|
||||
priority: 0,
|
||||
canHandle: () => true,
|
||||
handle: (error) => {
|
||||
this.logger.error('未处理的网络错误:', error.message, error.context);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保错误是NetworkError类型
|
||||
*/
|
||||
private ensureNetworkError(error: Error | INetworkError): INetworkError {
|
||||
if ('type' in error && 'timestamp' in error) {
|
||||
return error as INetworkError;
|
||||
}
|
||||
|
||||
// 转换普通错误为网络错误
|
||||
return createNetworkError(
|
||||
NetworkErrorType.INVALID_DATA,
|
||||
error.message,
|
||||
{ originalError: error.name }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新错误统计
|
||||
*/
|
||||
private updateStats(error: INetworkError): void {
|
||||
this._stats.totalErrors++;
|
||||
|
||||
// 按类型统计
|
||||
const typeCount = this._stats.errorsByType.get(error.type) || 0;
|
||||
this._stats.errorsByType.set(error.type, typeCount + 1);
|
||||
|
||||
// 按错误代码统计
|
||||
if (error.code !== undefined) {
|
||||
const codeCount = this._stats.errorsByCode.get(error.code) || 0;
|
||||
this._stats.errorsByCode.set(error.code, codeCount + 1);
|
||||
}
|
||||
|
||||
// 记录最近错误(保留最近100个)
|
||||
this._stats.recentErrors.push(error);
|
||||
if (this._stats.recentErrors.length > 100) {
|
||||
this._stats.recentErrors.shift();
|
||||
}
|
||||
|
||||
// 更新小时趋势
|
||||
const currentHour = new Date().getHours();
|
||||
this._stats.hourlyTrend[currentHour]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录错误日志
|
||||
*/
|
||||
private logError(error: INetworkError, level: 'error' | 'warn' | 'info' | 'debug'): void {
|
||||
const logMessage = `[${error.type}] ${error.message}`;
|
||||
const logData = {
|
||||
error: error.message,
|
||||
type: error.type,
|
||||
code: error.code,
|
||||
context: error.context,
|
||||
timestamp: new Date(error.timestamp).toISOString()
|
||||
};
|
||||
|
||||
switch (level) {
|
||||
case 'error':
|
||||
this.logger.error(logMessage, logData);
|
||||
break;
|
||||
case 'warn':
|
||||
this.logger.warn(logMessage, logData);
|
||||
break;
|
||||
case 'info':
|
||||
this.logger.info(logMessage, logData);
|
||||
break;
|
||||
case 'debug':
|
||||
this.logger.debug(logMessage, logData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该重试
|
||||
*/
|
||||
private shouldRetry(error: INetworkError): boolean {
|
||||
// 这些错误类型不应该重试
|
||||
const noRetryTypes = [
|
||||
NetworkErrorType.PERMISSION_DENIED,
|
||||
NetworkErrorType.SERIALIZATION_FAILED,
|
||||
NetworkErrorType.DESERIALIZATION_FAILED
|
||||
];
|
||||
|
||||
return !noRetryTypes.includes(error.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试重试
|
||||
*/
|
||||
private async attemptRetry(
|
||||
error: INetworkError,
|
||||
options: Omit<Required<ErrorHandlingOptions>, 'customHandler'> & { customHandler?: (error: INetworkError) => Promise<boolean> | boolean }
|
||||
): Promise<boolean> {
|
||||
for (let attempt = 1; attempt <= options.maxRetries; attempt++) {
|
||||
this.logger.info(`重试处理错误 (${attempt}/${options.maxRetries}):`, error.message);
|
||||
|
||||
// 等待重试延迟
|
||||
await new Promise(resolve => setTimeout(resolve, options.retryDelay * attempt));
|
||||
|
||||
try {
|
||||
// 这里应该有具体的重试逻辑,取决于错误类型
|
||||
// 目前返回false表示重试失败
|
||||
return false;
|
||||
} catch (retryError) {
|
||||
this.logger.error(`重试失败 (${attempt}):`, retryError);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.error(`重试${options.maxRetries}次后仍然失败:`, error.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知用户
|
||||
*/
|
||||
private notifyUser(error: INetworkError): void {
|
||||
// 这里可以实现用户通知逻辑
|
||||
// 例如显示弹窗、发送邮件等
|
||||
console.warn(`用户通知: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理器单例实例
|
||||
*/
|
||||
export const ErrorHandler = NetworkErrorHandler.Instance;
|
||||
@@ -1,12 +1,12 @@
|
||||
import { NetworkMessage } from './NetworkMessage';
|
||||
import { NetworkConnection } from '../Core/NetworkConnection';
|
||||
import { INetworkMessage, MessageData } from '../types/NetworkTypes';
|
||||
import { IBasicNetworkMessage, MessageData } from '../types/NetworkTypes';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 消息处理器接口
|
||||
*/
|
||||
export interface IMessageHandler<T extends INetworkMessage = INetworkMessage> {
|
||||
export interface IMessageHandler<T extends IBasicNetworkMessage = IBasicNetworkMessage> {
|
||||
/**
|
||||
* 处理消息
|
||||
*
|
||||
@@ -20,8 +20,8 @@ export interface IMessageHandler<T extends INetworkMessage = INetworkMessage> {
|
||||
* 消息处理器注册信息
|
||||
*/
|
||||
interface MessageHandlerInfo<T extends MessageData = MessageData> {
|
||||
handler: IMessageHandler<INetworkMessage<T>>;
|
||||
messageClass: new (...args: any[]) => INetworkMessage<T>;
|
||||
handler: IMessageHandler<IBasicNetworkMessage<T>>;
|
||||
messageClass: new (...args: any[]) => IBasicNetworkMessage<T>;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export class MessageHandler {
|
||||
private static readonly logger = createLogger('MessageHandler');
|
||||
private static _instance: MessageHandler | null = null;
|
||||
private _handlers: Map<number, MessageHandlerInfo[]> = new Map();
|
||||
private _messageClasses: Map<number, new (...args: any[]) => INetworkMessage> = new Map();
|
||||
private _messageClasses: Map<number, new (...args: any[]) => IBasicNetworkMessage> = new Map();
|
||||
|
||||
/**
|
||||
* 获取消息处理器单例
|
||||
@@ -57,7 +57,7 @@ export class MessageHandler {
|
||||
* @param handler - 消息处理器
|
||||
* @param priority - 处理优先级(数字越小优先级越高)
|
||||
*/
|
||||
public registerHandler<TData extends MessageData, T extends INetworkMessage<TData>>(
|
||||
public registerHandler<TData extends MessageData, T extends IBasicNetworkMessage<TData>>(
|
||||
messageType: number,
|
||||
messageClass: new (...args: any[]) => T,
|
||||
handler: IMessageHandler<T>,
|
||||
@@ -81,8 +81,8 @@ export class MessageHandler {
|
||||
} else {
|
||||
// 添加新处理器
|
||||
handlers.push({
|
||||
handler: handler as IMessageHandler<INetworkMessage>,
|
||||
messageClass: messageClass as new (...args: any[]) => INetworkMessage,
|
||||
handler: handler as IMessageHandler<IBasicNetworkMessage>,
|
||||
messageClass: messageClass as new (...args: any[]) => IBasicNetworkMessage,
|
||||
priority
|
||||
});
|
||||
}
|
||||
@@ -161,7 +161,7 @@ export class MessageHandler {
|
||||
* @param connection - 发送消息的连接(服务端有效)
|
||||
* @returns 是否成功处理
|
||||
*/
|
||||
public async handleMessage(message: INetworkMessage, connection?: NetworkConnection): Promise<boolean> {
|
||||
public async handleMessage(message: IBasicNetworkMessage, connection?: NetworkConnection): Promise<boolean> {
|
||||
const messageType = message.messageType;
|
||||
const handlers = this._handlers.get(messageType);
|
||||
|
||||
@@ -279,7 +279,7 @@ export class MessageHandler {
|
||||
* @param messageClass - 消息类构造函数
|
||||
* @param priority - 处理优先级
|
||||
*/
|
||||
export function MessageHandlerDecorator<TData extends MessageData, T extends INetworkMessage<TData>>(
|
||||
export function MessageHandlerDecorator<TData extends MessageData, T extends IBasicNetworkMessage<TData>>(
|
||||
messageType: number,
|
||||
messageClass: new (...args: any[]) => T,
|
||||
priority: number = 0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { NetworkMessage, JsonMessage } from './NetworkMessage';
|
||||
import { MessageType as CoreMessageType } from '../types/MessageTypes';
|
||||
import { MESSAGE_CONFIG } from '../constants/NetworkConstants';
|
||||
|
||||
/**
|
||||
* 内置消息类型枚举
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { INetworkMessage, MessageData } from '../types/NetworkTypes';
|
||||
import { IBasicNetworkMessage, MessageData } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 网络消息基类
|
||||
@@ -6,7 +6,7 @@ import { INetworkMessage, MessageData } from '../types/NetworkTypes';
|
||||
* 所有网络消息都应该继承此类
|
||||
* 提供消息的序列化和反序列化功能
|
||||
*/
|
||||
export abstract class NetworkMessage<TData extends MessageData = MessageData> implements INetworkMessage<TData> {
|
||||
export abstract class NetworkMessage<TData extends MessageData = MessageData> implements IBasicNetworkMessage<TData> {
|
||||
/**
|
||||
* 消息类型ID
|
||||
* 每个消息类型都应该有唯一的ID
|
||||
@@ -313,4 +313,363 @@ export class ProtobufMessage extends NetworkMessage<Uint8Array> {
|
||||
// 读取protobuf数据
|
||||
this._data = data.subarray(offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳消息
|
||||
* 用于维持连接和检测网络延迟
|
||||
*/
|
||||
export class HeartbeatMessage extends NetworkMessage<{ ping: boolean; timestamp: number }> {
|
||||
public readonly messageType: number = 4;
|
||||
private _isPing: boolean;
|
||||
private _pingTimestamp: number;
|
||||
|
||||
public get data(): { ping: boolean; timestamp: number } {
|
||||
return {
|
||||
ping: this._isPing,
|
||||
timestamp: this._pingTimestamp
|
||||
};
|
||||
}
|
||||
|
||||
constructor(isPing: boolean = true, timestamp: number = Date.now(), senderId?: string, sequence?: number) {
|
||||
super(senderId, sequence);
|
||||
this._isPing = isPing;
|
||||
this._pingTimestamp = timestamp;
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
const buffer = new ArrayBuffer(13); // 4(type) + 1(ping) + 8(timestamp)
|
||||
const view = new DataView(buffer);
|
||||
|
||||
view.setUint32(0, this.messageType, true);
|
||||
view.setUint8(4, this._isPing ? 1 : 0);
|
||||
// 使用BigUint64将timestamp存储为64位整数
|
||||
view.setBigUint64(5, BigInt(this._pingTimestamp), true);
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
if (data.length < 13) {
|
||||
throw new Error('心跳消息数据长度不足');
|
||||
}
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset);
|
||||
this._isPing = view.getUint8(4) === 1;
|
||||
this._pingTimestamp = Number(view.getBigUint64(5, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建ping消息
|
||||
*/
|
||||
public static createPing(senderId?: string): HeartbeatMessage {
|
||||
return new HeartbeatMessage(true, Date.now(), senderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建pong消息
|
||||
*/
|
||||
public static createPong(originalTimestamp: number, senderId?: string): HeartbeatMessage {
|
||||
return new HeartbeatMessage(false, originalTimestamp, senderId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
* 用于传递错误信息
|
||||
*/
|
||||
export class ErrorMessage extends NetworkMessage<{ code: number; message: string; details?: any }> {
|
||||
public readonly messageType: number = 5;
|
||||
private _errorCode: number;
|
||||
private _errorMessage: string;
|
||||
private _details?: any;
|
||||
|
||||
public get data(): { code: number; message: string; details?: any } {
|
||||
return {
|
||||
code: this._errorCode,
|
||||
message: this._errorMessage,
|
||||
details: this._details
|
||||
};
|
||||
}
|
||||
|
||||
constructor(code: number, message: string, details?: any, senderId?: string, sequence?: number) {
|
||||
super(senderId, sequence);
|
||||
this._errorCode = code;
|
||||
this._errorMessage = message;
|
||||
this._details = details;
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
const messageBytes = new TextEncoder().encode(this._errorMessage);
|
||||
const detailsBytes = this._details ? new TextEncoder().encode(JSON.stringify(this._details)) : new Uint8Array(0);
|
||||
|
||||
const buffer = new ArrayBuffer(4 + 4 + 4 + messageBytes.length + 4 + detailsBytes.length);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
let offset = 0;
|
||||
view.setUint32(offset, this.messageType, true);
|
||||
offset += 4;
|
||||
|
||||
view.setUint32(offset, this._errorCode, true);
|
||||
offset += 4;
|
||||
|
||||
view.setUint32(offset, messageBytes.length, true);
|
||||
offset += 4;
|
||||
|
||||
new Uint8Array(buffer, offset, messageBytes.length).set(messageBytes);
|
||||
offset += messageBytes.length;
|
||||
|
||||
view.setUint32(offset, detailsBytes.length, true);
|
||||
offset += 4;
|
||||
|
||||
if (detailsBytes.length > 0) {
|
||||
new Uint8Array(buffer, offset, detailsBytes.length).set(detailsBytes);
|
||||
}
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
if (data.length < 16) { // 至少4+4+4+4字节
|
||||
throw new Error('错误消息数据长度不足');
|
||||
}
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset);
|
||||
let offset = 4; // 跳过消息类型
|
||||
|
||||
this._errorCode = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
const messageLength = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
if (data.length < offset + messageLength + 4) {
|
||||
throw new Error('错误消息数据不足');
|
||||
}
|
||||
|
||||
const messageBytes = data.subarray(offset, offset + messageLength);
|
||||
this._errorMessage = new TextDecoder().decode(messageBytes);
|
||||
offset += messageLength;
|
||||
|
||||
const detailsLength = view.getUint32(offset, true);
|
||||
offset += 4;
|
||||
|
||||
if (detailsLength > 0) {
|
||||
if (data.length < offset + detailsLength) {
|
||||
throw new Error('错误消息详情数据不足');
|
||||
}
|
||||
const detailsBytes = data.subarray(offset, offset + detailsLength);
|
||||
try {
|
||||
this._details = JSON.parse(new TextDecoder().decode(detailsBytes));
|
||||
} catch {
|
||||
this._details = new TextDecoder().decode(detailsBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态同步消息
|
||||
* 用于同步游戏对象状态
|
||||
*/
|
||||
export interface StateData extends Record<string, unknown> {
|
||||
entityId: number;
|
||||
position?: { x: number; y: number; z?: number };
|
||||
rotation?: { x: number; y: number; z: number; w: number };
|
||||
velocity?: { x: number; y: number; z?: number };
|
||||
health?: number;
|
||||
customData?: Record<string, any>;
|
||||
}
|
||||
|
||||
export class StateSyncMessage extends NetworkMessage<StateData> {
|
||||
public readonly messageType: number = 6;
|
||||
private _stateData: StateData;
|
||||
|
||||
public get data(): StateData {
|
||||
return this._stateData;
|
||||
}
|
||||
|
||||
constructor(stateData: StateData, senderId?: string, sequence?: number) {
|
||||
super(senderId, sequence);
|
||||
this._stateData = stateData;
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
// 使用JSON序列化(可以替换为更高效的二进制协议)
|
||||
const jsonString = JSON.stringify(this._stateData);
|
||||
const jsonBytes = new TextEncoder().encode(jsonString);
|
||||
|
||||
const buffer = new ArrayBuffer(8 + jsonBytes.length);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
view.setUint32(0, this.messageType, true);
|
||||
view.setUint32(4, jsonBytes.length, true);
|
||||
|
||||
new Uint8Array(buffer, 8, jsonBytes.length).set(jsonBytes);
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
if (data.length < 8) {
|
||||
throw new Error('状态同步消息数据长度不足');
|
||||
}
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset);
|
||||
const jsonLength = view.getUint32(4, true);
|
||||
|
||||
if (data.length < 8 + jsonLength) {
|
||||
throw new Error('状态同步消息JSON数据不足');
|
||||
}
|
||||
|
||||
const jsonBytes = data.subarray(8, 8 + jsonLength);
|
||||
const jsonString = new TextDecoder().decode(jsonBytes);
|
||||
|
||||
try {
|
||||
this._stateData = JSON.parse(jsonString);
|
||||
} catch (error) {
|
||||
throw new Error(`状态同步消息JSON解析失败: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间消息
|
||||
* 用于房间相关的操作和通知
|
||||
*/
|
||||
export interface RoomMessageData extends Record<string, unknown> {
|
||||
action: 'join' | 'leave' | 'kick' | 'message' | 'update';
|
||||
roomId: string;
|
||||
playerId?: number;
|
||||
playerName?: string;
|
||||
message?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export class RoomMessage extends NetworkMessage<RoomMessageData> {
|
||||
public readonly messageType: number = 7;
|
||||
private _roomData: RoomMessageData;
|
||||
|
||||
public get data(): RoomMessageData {
|
||||
return this._roomData;
|
||||
}
|
||||
|
||||
constructor(roomData: RoomMessageData, senderId?: string, sequence?: number) {
|
||||
super(senderId, sequence);
|
||||
this._roomData = roomData;
|
||||
}
|
||||
|
||||
public serialize(): Uint8Array {
|
||||
const jsonString = JSON.stringify(this._roomData);
|
||||
const jsonBytes = new TextEncoder().encode(jsonString);
|
||||
|
||||
const buffer = new ArrayBuffer(8 + jsonBytes.length);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
view.setUint32(0, this.messageType, true);
|
||||
view.setUint32(4, jsonBytes.length, true);
|
||||
|
||||
new Uint8Array(buffer, 8, jsonBytes.length).set(jsonBytes);
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
public deserialize(data: Uint8Array): void {
|
||||
if (data.length < 8) {
|
||||
throw new Error('房间消息数据长度不足');
|
||||
}
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset);
|
||||
const jsonLength = view.getUint32(4, true);
|
||||
|
||||
if (data.length < 8 + jsonLength) {
|
||||
throw new Error('房间消息JSON数据不足');
|
||||
}
|
||||
|
||||
const jsonBytes = data.subarray(8, 8 + jsonLength);
|
||||
const jsonString = new TextDecoder().decode(jsonBytes);
|
||||
|
||||
try {
|
||||
this._roomData = JSON.parse(jsonString);
|
||||
} catch (error) {
|
||||
throw new Error(`房间消息JSON解析失败: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息工厂
|
||||
* 根据消息类型创建相应的消息实例
|
||||
*/
|
||||
export class NetworkMessageFactory {
|
||||
private static messageConstructors = new Map<number, new (...args: any[]) => NetworkMessage>([
|
||||
[0, RawMessage],
|
||||
[1, JsonMessage],
|
||||
[2, ProtobufMessage],
|
||||
[4, HeartbeatMessage],
|
||||
[5, ErrorMessage],
|
||||
[6, StateSyncMessage],
|
||||
[7, RoomMessage]
|
||||
]);
|
||||
|
||||
/**
|
||||
* 根据消息类型创建消息实例
|
||||
*/
|
||||
public static createMessage(messageType: number): NetworkMessage | null {
|
||||
const Constructor = this.messageConstructors.get(messageType);
|
||||
if (!Constructor) {
|
||||
return null;
|
||||
}
|
||||
// 为不同消息类型提供默认参数
|
||||
try {
|
||||
switch (messageType) {
|
||||
case 4: // HeartbeatMessage
|
||||
return new Constructor();
|
||||
case 5: // ErrorMessage
|
||||
return new Constructor(0, '');
|
||||
case 6: // StateSyncMessage
|
||||
return new Constructor({ entityId: 0 });
|
||||
case 7: // RoomMessage
|
||||
return new Constructor({ action: 'message', roomId: '' });
|
||||
default:
|
||||
return new Constructor();
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从二进制数据反序列化消息
|
||||
*/
|
||||
public static deserializeMessage(data: Uint8Array): NetworkMessage | null {
|
||||
if (data.length < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset);
|
||||
const messageType = view.getUint32(0, true);
|
||||
|
||||
const message = this.createMessage(messageType);
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
message.deserialize(data);
|
||||
return message;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册新的消息类型
|
||||
*/
|
||||
public static registerMessageType<T extends NetworkMessage>(
|
||||
messageType: number,
|
||||
constructor: new (...args: any[]) => T
|
||||
): void {
|
||||
this.messageConstructors.set(messageType, constructor as new (...args: any[]) => NetworkMessage);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,11 @@ import { Component } from '@esengine/ecs-framework';
|
||||
import { INetworkSyncable } from './INetworkSyncable';
|
||||
import { NetworkRole } from './NetworkRole';
|
||||
import { NetworkEnvironment } from './Core/NetworkEnvironment';
|
||||
import { createSyncVarProxy, isSyncVarProxied, destroySyncVarProxy } from './SyncVar/SyncVarProxy';
|
||||
import { SyncVarManager } from './SyncVar/SyncVarManager';
|
||||
import { getSyncVarMetadata } from './SyncVar/SyncVarDecorator';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { ComponentRegistry } from '@esengine/ecs-framework';
|
||||
import { isTsrpcSerializable } from './Serialization/TsrpcDecorators';
|
||||
import { TsrpcSerializer } from './Serialization/TsrpcSerializer';
|
||||
|
||||
/**
|
||||
* 网络组件基类
|
||||
@@ -16,14 +17,14 @@ import { createLogger } from '@esengine/ecs-framework';
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { NetworkComponent } from '@esengine/ecs-framework-network';
|
||||
* import { ProtoSerializable, ProtoFloat } from '@esengine/ecs-framework-network';
|
||||
* import { TsrpcSerializable, SyncField } from '@esengine/ecs-framework-network';
|
||||
*
|
||||
* @ProtoSerializable('Position')
|
||||
* @TsrpcSerializable()
|
||||
* class PositionComponent extends NetworkComponent {
|
||||
* @ProtoFloat(1)
|
||||
* @SyncField()
|
||||
* public x: number = 0;
|
||||
*
|
||||
* @ProtoFloat(2)
|
||||
* @SyncField()
|
||||
* public y: number = 0;
|
||||
*
|
||||
* constructor(x: number = 0, y: number = 0) {
|
||||
@@ -57,6 +58,13 @@ import { createLogger } from '@esengine/ecs-framework';
|
||||
* ```
|
||||
*/
|
||||
export abstract class NetworkComponent extends Component implements INetworkSyncable {
|
||||
/** SyncVar内部ID */
|
||||
_syncVarId?: string;
|
||||
/** SyncVar监听禁用标志 */
|
||||
_syncVarDisabled?: boolean;
|
||||
|
||||
/** 允许通过字符串键访问属性 */
|
||||
[propertyKey: string]: unknown;
|
||||
/**
|
||||
* 脏字段标记集合
|
||||
*
|
||||
@@ -87,17 +95,16 @@ export abstract class NetworkComponent extends Component implements INetworkSync
|
||||
*/
|
||||
private ensureComponentRegistered(): void {
|
||||
try {
|
||||
const { ComponentRegistry } = require('@esengine/ecs-framework');
|
||||
|
||||
// 检查当前组件类型是否已注册
|
||||
if (!ComponentRegistry.isRegistered(this.constructor)) {
|
||||
if (!ComponentRegistry.isRegistered(this.constructor as any)) {
|
||||
// 如果未注册,自动注册
|
||||
ComponentRegistry.register(this.constructor);
|
||||
const logger = createLogger('NetworkComponent');
|
||||
ComponentRegistry.register(this.constructor as any);
|
||||
const logger = { info: console.log, warn: console.warn, error: console.error, debug: console.debug };
|
||||
logger.debug(`自动注册组件类型: ${this.constructor.name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
const logger = createLogger('NetworkComponent');
|
||||
const logger = { info: console.log, warn: console.warn, error: console.error, debug: console.debug };
|
||||
logger.warn(`无法注册组件类型 ${this.constructor.name}:`, error);
|
||||
}
|
||||
}
|
||||
@@ -110,7 +117,7 @@ export abstract class NetworkComponent extends Component implements INetworkSync
|
||||
private initializeSyncVar(): void {
|
||||
const metadata = getSyncVarMetadata(this.constructor);
|
||||
if (metadata.length > 0) {
|
||||
const logger = createLogger('NetworkComponent');
|
||||
const logger = { info: console.log, warn: console.warn, error: console.error, debug: console.debug };
|
||||
logger.debug(`${this.constructor.name} 发现 ${metadata.length} 个SyncVar字段,将启用代理监听`);
|
||||
}
|
||||
}
|
||||
@@ -150,19 +157,20 @@ export abstract class NetworkComponent extends Component implements INetworkSync
|
||||
* @returns 序列化的网络状态数据
|
||||
*/
|
||||
public getNetworkState(): Uint8Array {
|
||||
const { isProtoSerializable } = require('./Serialization/ProtobufDecorators');
|
||||
const { ProtobufSerializer } = require('./Serialization/ProtobufSerializer');
|
||||
|
||||
if (!isProtoSerializable(this)) {
|
||||
throw new Error(`组件 ${this.constructor.name} 不支持网络同步,请添加@ProtoSerializable装饰器`);
|
||||
if (!isTsrpcSerializable(this)) {
|
||||
throw new Error(`组件 ${this.constructor.name} 不支持网络同步,请添加@TsrpcSerializable装饰器`);
|
||||
}
|
||||
|
||||
try {
|
||||
const serializer = ProtobufSerializer.getInstance();
|
||||
const serializer = TsrpcSerializer.getInstance();
|
||||
const serializedData = serializer.serialize(this);
|
||||
if (!serializedData) {
|
||||
throw new Error(`序列化失败: 组件=${this.constructor.name}`);
|
||||
}
|
||||
return serializedData.data;
|
||||
} catch (error) {
|
||||
throw new Error(`获取网络状态失败: ${error}`);
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`获取网络状态失败: 组件=${this.constructor.name}, 错误=${errorMsg}, 可能原因: 序列化字段格式错误或网络连接问题`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,27 +181,44 @@ export abstract class NetworkComponent extends Component implements INetworkSync
|
||||
* @param data - 网络状态数据
|
||||
*/
|
||||
public applyNetworkState(data: Uint8Array): void {
|
||||
const { isProtoSerializable } = require('./Serialization/ProtobufDecorators');
|
||||
const { ProtobufSerializer } = require('./Serialization/ProtobufSerializer');
|
||||
|
||||
if (!isProtoSerializable(this)) {
|
||||
throw new Error(`组件 ${this.constructor.name} 不支持网络同步,请添加@ProtoSerializable装饰器`);
|
||||
if (!isTsrpcSerializable(this)) {
|
||||
throw new Error(`组件 ${this.constructor.name} 不支持网络同步,请添加@TsrpcSerializable装饰器`);
|
||||
}
|
||||
|
||||
try {
|
||||
const serializer = ProtobufSerializer.getInstance();
|
||||
const serializer = TsrpcSerializer.getInstance();
|
||||
const serializedData = {
|
||||
type: 'protobuf' as const,
|
||||
type: 'tsrpc' as const,
|
||||
componentType: this.constructor.name,
|
||||
data: data,
|
||||
size: data.length
|
||||
};
|
||||
serializer.deserialize(this, serializedData);
|
||||
|
||||
// 反序列化并应用到当前组件实例
|
||||
const deserializedComponent = serializer.deserialize(serializedData, this.constructor as any);
|
||||
if (!deserializedComponent) {
|
||||
throw new Error(`反序列化失败: 组件=${this.constructor.name}`);
|
||||
}
|
||||
|
||||
// 将反序列化的数据应用到当前实例
|
||||
Object.assign(this, deserializedComponent);
|
||||
|
||||
// 应用后清理脏字段标记
|
||||
this.markClean();
|
||||
} catch (error) {
|
||||
throw new Error(`应用网络状态失败: ${error}`);
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
// 记录错误但不完全阻止组件运行
|
||||
console.warn(`应用网络状态失败: 组件=${this.constructor.name}, 错误=${errorMsg}, 尝试恢复到安全状态`);
|
||||
|
||||
// 尝试恢复到安全状态
|
||||
try {
|
||||
this.markClean(); // 至少清理脏字段标记
|
||||
} catch (cleanupError) {
|
||||
// 如果连清理都失败,则抛出原始错误
|
||||
throw new Error(`应用网络状态失败: 组件=${this.constructor.name}, 原始错误=${errorMsg}, 清理失败=${cleanupError}, 组件可能处于不一致状态`);
|
||||
}
|
||||
|
||||
throw new Error(`应用网络状态失败: 组件=${this.constructor.name}, 错误=${errorMsg}, 已恢复到安全状态`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,470 +0,0 @@
|
||||
/**
|
||||
* Protobuf序列化装饰器
|
||||
*
|
||||
* 提供装饰器语法来标记组件和字段进行protobuf序列化
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import * as protobuf from 'protobufjs';
|
||||
|
||||
/**
|
||||
* 使用protobufjs官方字段类型定义
|
||||
*/
|
||||
export type ProtoFieldType = keyof typeof protobuf.types.basic | keyof typeof protobuf.types.defaults | 'message' | 'enum';
|
||||
|
||||
/**
|
||||
* protobufjs官方字段类型常量
|
||||
*/
|
||||
export const ProtoTypes = {
|
||||
// 基本数值类型
|
||||
DOUBLE: 'double' as ProtoFieldType,
|
||||
FLOAT: 'float' as ProtoFieldType,
|
||||
INT32: 'int32' as ProtoFieldType,
|
||||
INT64: 'int64' as ProtoFieldType,
|
||||
UINT32: 'uint32' as ProtoFieldType,
|
||||
UINT64: 'uint64' as ProtoFieldType,
|
||||
SINT32: 'sint32' as ProtoFieldType,
|
||||
SINT64: 'sint64' as ProtoFieldType,
|
||||
FIXED32: 'fixed32' as ProtoFieldType,
|
||||
FIXED64: 'fixed64' as ProtoFieldType,
|
||||
SFIXED32: 'sfixed32' as ProtoFieldType,
|
||||
SFIXED64: 'sfixed64' as ProtoFieldType,
|
||||
BOOL: 'bool' as ProtoFieldType,
|
||||
STRING: 'string' as ProtoFieldType,
|
||||
BYTES: 'bytes' as ProtoFieldType,
|
||||
// 复合类型
|
||||
MESSAGE: 'message' as ProtoFieldType,
|
||||
ENUM: 'enum' as ProtoFieldType
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* protobufjs官方类型映射
|
||||
*/
|
||||
export const ProtobufTypes = protobuf.types;
|
||||
|
||||
/**
|
||||
* 字段同步优先级
|
||||
*/
|
||||
export enum FieldSyncPriority {
|
||||
/** 关键字段 - 每帧必须同步 */
|
||||
CRITICAL = 'critical',
|
||||
/** 高优先级 - 高频同步 */
|
||||
HIGH = 'high',
|
||||
/** 中等优先级 - 中频同步 */
|
||||
MEDIUM = 'medium',
|
||||
/** 低优先级 - 低频同步 */
|
||||
LOW = 'low'
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf字段定义接口
|
||||
*/
|
||||
export interface ProtoFieldDefinition {
|
||||
/** 字段编号 */
|
||||
fieldNumber: number;
|
||||
/** 字段类型 */
|
||||
type: ProtoFieldType;
|
||||
/** 是否为数组 */
|
||||
repeated?: boolean;
|
||||
/** 是否可选 */
|
||||
optional?: boolean;
|
||||
/** 字段名称 */
|
||||
name: string;
|
||||
/** 自定义类型名称 */
|
||||
customTypeName?: string;
|
||||
/** 枚举值映射 */
|
||||
enumValues?: Record<string, number>;
|
||||
/** 默认值 */
|
||||
defaultValue?: any;
|
||||
|
||||
// 帧同步特定选项
|
||||
/** 同步优先级 */
|
||||
syncPriority?: FieldSyncPriority;
|
||||
/** 数值精度(用于量化压缩) */
|
||||
precision?: number;
|
||||
/** 是否支持插值 */
|
||||
interpolation?: boolean;
|
||||
/** 量化位数 */
|
||||
quantizationBits?: number;
|
||||
/** 变化阈值(小于此值不同步) */
|
||||
changeThreshold?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件同步模式
|
||||
*/
|
||||
export enum ComponentSyncMode {
|
||||
/** 完整同步 - 传输所有字段 */
|
||||
FULL = 'full',
|
||||
/** 增量同步 - 只传输变化字段 */
|
||||
DELTA = 'delta',
|
||||
/** 自适应 - 根据变化量自动选择 */
|
||||
ADAPTIVE = 'adaptive'
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf组件定义接口
|
||||
*/
|
||||
export interface ProtoComponentDefinition {
|
||||
/** 组件名称 */
|
||||
name: string;
|
||||
/** 字段定义列表 */
|
||||
fields: Map<string, ProtoFieldDefinition>;
|
||||
/** 构造函数 */
|
||||
constructor: any;
|
||||
|
||||
// 帧同步特定选项
|
||||
/** 同步模式 */
|
||||
syncMode?: ComponentSyncMode;
|
||||
/** 同步频率(每秒同步次数) */
|
||||
syncFrequency?: number;
|
||||
/** 是否启用压缩 */
|
||||
enableCompression?: boolean;
|
||||
/** 网络优先级 */
|
||||
networkPriority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protobuf注册表
|
||||
*/
|
||||
export class ProtobufRegistry {
|
||||
private static instance: ProtobufRegistry;
|
||||
private components = new Map<string, ProtoComponentDefinition>();
|
||||
|
||||
public static getInstance(): ProtobufRegistry {
|
||||
if (!ProtobufRegistry.instance) {
|
||||
ProtobufRegistry.instance = new ProtobufRegistry();
|
||||
}
|
||||
return ProtobufRegistry.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册组件定义
|
||||
*/
|
||||
public registerComponent(componentName: string, definition: ProtoComponentDefinition): void {
|
||||
this.components.set(componentName, definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件定义
|
||||
*/
|
||||
public getComponentDefinition(componentName: string): ProtoComponentDefinition | undefined {
|
||||
return this.components.get(componentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件是否支持protobuf
|
||||
*/
|
||||
public hasProtoDefinition(componentName: string): boolean {
|
||||
return this.components.has(componentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有注册的组件
|
||||
*/
|
||||
public getAllComponents(): Map<string, ProtoComponentDefinition> {
|
||||
return new Map(this.components);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成proto文件定义
|
||||
*/
|
||||
public generateProtoDefinition(): string {
|
||||
let protoContent = 'syntax = "proto3";\n\n';
|
||||
protoContent += 'package ecs;\n\n';
|
||||
|
||||
// 生成消息定义
|
||||
for (const [name, definition] of this.components) {
|
||||
protoContent += `message ${name} {\n`;
|
||||
|
||||
// 按字段编号排序
|
||||
const sortedFields = Array.from(definition.fields.values())
|
||||
.sort((a, b) => a.fieldNumber - b.fieldNumber);
|
||||
|
||||
for (const field of sortedFields) {
|
||||
let fieldDef = ' ';
|
||||
|
||||
if (field.repeated) {
|
||||
fieldDef += 'repeated ';
|
||||
} else if (field.optional) {
|
||||
fieldDef += 'optional ';
|
||||
}
|
||||
|
||||
fieldDef += `${field.type} ${field.name} = ${field.fieldNumber};\n`;
|
||||
protoContent += fieldDef;
|
||||
}
|
||||
|
||||
protoContent += '}\n\n';
|
||||
}
|
||||
|
||||
return protoContent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件同步选项接口
|
||||
*/
|
||||
export interface ComponentSyncOptions {
|
||||
/** 同步模式 */
|
||||
syncMode?: ComponentSyncMode;
|
||||
/** 同步频率(每秒同步次数) */
|
||||
syncFrequency?: number;
|
||||
/** 是否启用压缩 */
|
||||
enableCompression?: boolean;
|
||||
/** 网络优先级(1-10,数字越大优先级越高) */
|
||||
networkPriority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProtoSerializable 组件装饰器
|
||||
*
|
||||
* 标记组件支持protobuf序列化,专为帧同步框架优化
|
||||
* @param protoName protobuf消息名称,默认使用类名
|
||||
* @param options 同步选项
|
||||
* @example
|
||||
* ```typescript
|
||||
* @ProtoSerializable('Position', {
|
||||
* syncMode: ComponentSyncMode.DELTA,
|
||||
* syncFrequency: 30,
|
||||
* networkPriority: 8
|
||||
* })
|
||||
* class PositionComponent extends Component {
|
||||
* // 组件实现
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function ProtoSerializable(protoName?: string, options?: ComponentSyncOptions) {
|
||||
return function <T extends { new(...args: any[]): Component }>(constructor: T) {
|
||||
const componentName = protoName || constructor.name;
|
||||
const registry = ProtobufRegistry.getInstance();
|
||||
|
||||
// 获取字段定义
|
||||
const fields = (constructor.prototype._protoFields as Map<string, ProtoFieldDefinition>)
|
||||
|| new Map<string, ProtoFieldDefinition>();
|
||||
|
||||
// 注册组件定义
|
||||
registry.registerComponent(componentName, {
|
||||
name: componentName,
|
||||
fields: fields,
|
||||
constructor: constructor,
|
||||
syncMode: options?.syncMode || ComponentSyncMode.FULL,
|
||||
syncFrequency: options?.syncFrequency || 30,
|
||||
enableCompression: options?.enableCompression || true,
|
||||
networkPriority: options?.networkPriority || 5
|
||||
});
|
||||
|
||||
// 标记组件支持protobuf
|
||||
(constructor.prototype._isProtoSerializable = true);
|
||||
(constructor.prototype._protoName = componentName);
|
||||
|
||||
return constructor;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段同步选项接口
|
||||
*/
|
||||
export interface FieldSyncOptions {
|
||||
/** 是否为数组 */
|
||||
repeated?: boolean;
|
||||
/** 是否可选 */
|
||||
optional?: boolean;
|
||||
/** 自定义类型名称 */
|
||||
customTypeName?: string;
|
||||
/** 枚举值映射 */
|
||||
enumValues?: Record<string, number>;
|
||||
/** 默认值 */
|
||||
defaultValue?: any;
|
||||
|
||||
// 帧同步特定选项
|
||||
/** 同步优先级 */
|
||||
syncPriority?: FieldSyncPriority;
|
||||
/** 数值精度(用于量化压缩) */
|
||||
precision?: number;
|
||||
/** 是否支持插值 */
|
||||
interpolation?: boolean;
|
||||
/** 量化位数 */
|
||||
quantizationBits?: number;
|
||||
/** 变化阈值(小于此值不同步) */
|
||||
changeThreshold?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* ProtoField 字段装饰器
|
||||
*
|
||||
* 标记字段参与protobuf序列化,针对帧同步进行优化
|
||||
* @param fieldNumber protobuf字段编号,必须唯一且大于0
|
||||
* @param type 字段类型,默认自动推断
|
||||
* @param options 字段选项
|
||||
* @example
|
||||
* ```typescript
|
||||
* class PositionComponent extends Component {
|
||||
* @ProtoField(1, ProtoFieldType.FLOAT, {
|
||||
* syncPriority: FieldSyncPriority.CRITICAL,
|
||||
* precision: 0.01,
|
||||
* interpolation: true
|
||||
* })
|
||||
* public x: number = 0;
|
||||
*
|
||||
* @ProtoField(2, ProtoFieldType.FLOAT, {
|
||||
* syncPriority: FieldSyncPriority.CRITICAL,
|
||||
* precision: 0.01,
|
||||
* interpolation: true
|
||||
* })
|
||||
* public y: number = 0;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function ProtoField(
|
||||
fieldNumber: number,
|
||||
type?: ProtoFieldType,
|
||||
options?: FieldSyncOptions
|
||||
) {
|
||||
return function (target: any, propertyKey: string) {
|
||||
// 验证字段编号
|
||||
if (fieldNumber <= 0) {
|
||||
throw new Error(`ProtoField: 字段编号必须大于0,当前值: ${fieldNumber}`);
|
||||
}
|
||||
|
||||
// 初始化字段集合
|
||||
if (!target._protoFields) {
|
||||
target._protoFields = new Map<string, ProtoFieldDefinition>();
|
||||
}
|
||||
|
||||
// 自动推断类型
|
||||
let inferredType = type;
|
||||
if (!inferredType) {
|
||||
const designType = Reflect.getMetadata?.('design:type', target, propertyKey);
|
||||
inferredType = inferProtoType(designType);
|
||||
}
|
||||
|
||||
// 检查字段编号冲突
|
||||
for (const [key, field] of target._protoFields) {
|
||||
if (field.fieldNumber === fieldNumber && key !== propertyKey) {
|
||||
throw new Error(`ProtoField: 字段编号 ${fieldNumber} 已被字段 ${key} 使用`);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加字段定义
|
||||
target._protoFields.set(propertyKey, {
|
||||
fieldNumber,
|
||||
type: inferredType || ProtoTypes.STRING,
|
||||
repeated: options?.repeated || false,
|
||||
optional: options?.optional || false,
|
||||
name: propertyKey,
|
||||
customTypeName: options?.customTypeName,
|
||||
enumValues: options?.enumValues,
|
||||
defaultValue: options?.defaultValue,
|
||||
// 帧同步特定选项
|
||||
syncPriority: options?.syncPriority || FieldSyncPriority.MEDIUM,
|
||||
precision: options?.precision,
|
||||
interpolation: options?.interpolation || false,
|
||||
quantizationBits: options?.quantizationBits,
|
||||
changeThreshold: options?.changeThreshold || 0
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动推断protobuf类型
|
||||
*/
|
||||
function inferProtoType(jsType: any): ProtoFieldType {
|
||||
if (!jsType) return ProtoTypes.STRING;
|
||||
|
||||
switch (jsType) {
|
||||
case Number:
|
||||
return ProtoTypes.DOUBLE;
|
||||
case Boolean:
|
||||
return ProtoTypes.BOOL;
|
||||
case String:
|
||||
return ProtoTypes.STRING;
|
||||
case Date:
|
||||
// 对于Date类型,使用int64存储时间戳或者使用message类型
|
||||
return ProtoTypes.INT64;
|
||||
case Array:
|
||||
return ProtoTypes.STRING;
|
||||
case Uint8Array:
|
||||
case ArrayBuffer:
|
||||
return ProtoTypes.BYTES;
|
||||
case Object:
|
||||
return ProtoTypes.MESSAGE;
|
||||
default:
|
||||
return ProtoTypes.STRING;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷装饰器 - 常用类型
|
||||
*/
|
||||
export const ProtoInt32 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.INT32, options);
|
||||
|
||||
export const ProtoFloat = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.FLOAT, options);
|
||||
|
||||
export const ProtoString = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.STRING, options);
|
||||
|
||||
export const ProtoBool = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.BOOL, options);
|
||||
|
||||
// 扩展的便捷装饰器
|
||||
export const ProtoDouble = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.DOUBLE, options);
|
||||
|
||||
export const ProtoInt64 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.INT64, options);
|
||||
|
||||
export const ProtoUint32 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.UINT32, options);
|
||||
|
||||
export const ProtoUint64 = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.UINT64, options);
|
||||
|
||||
export const ProtoBytes = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.BYTES, options);
|
||||
|
||||
// 对于时间戳,使用int64存储毫秒时间戳
|
||||
export const ProtoTimestamp = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.INT64, options);
|
||||
|
||||
// 对于持续时间,使用int32存储毫秒数
|
||||
export const ProtoDuration = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.INT32, options);
|
||||
|
||||
// 对于结构体,使用message类型
|
||||
export const ProtoStruct = (fieldNumber: number, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.MESSAGE, options);
|
||||
|
||||
/**
|
||||
* 自定义消息类型装饰器
|
||||
* @param fieldNumber 字段编号
|
||||
* @param customTypeName 自定义类型名称
|
||||
* @param options 额外选项
|
||||
*/
|
||||
export const ProtoMessage = (fieldNumber: number, customTypeName: string, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.MESSAGE, { ...options, customTypeName });
|
||||
|
||||
/**
|
||||
* 枚举类型装饰器
|
||||
* @param fieldNumber 字段编号
|
||||
* @param enumValues 枚举值映射
|
||||
* @param options 额外选项
|
||||
*/
|
||||
export const ProtoEnum = (fieldNumber: number, enumValues: Record<string, number>, options?: { repeated?: boolean; optional?: boolean }) =>
|
||||
ProtoField(fieldNumber, ProtoTypes.ENUM, { ...options, enumValues });
|
||||
|
||||
/**
|
||||
* 检查组件是否支持protobuf序列化
|
||||
*/
|
||||
export function isProtoSerializable(component: Component): boolean {
|
||||
return !!(component as any)._isProtoSerializable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件的protobuf名称
|
||||
*/
|
||||
export function getProtoName(component: Component): string | undefined {
|
||||
return (component as any)._protoName;
|
||||
}
|
||||
@@ -1,479 +0,0 @@
|
||||
/**
|
||||
* Protobuf序列化器
|
||||
*
|
||||
* 处理组件的protobuf序列化和反序列化
|
||||
*/
|
||||
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import * as protobuf from 'protobufjs';
|
||||
import {
|
||||
ProtobufRegistry,
|
||||
isProtoSerializable,
|
||||
getProtoName
|
||||
} from './ProtobufDecorators';
|
||||
import { SerializedData } from './SerializationTypes';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 可序列化组件接口
|
||||
*/
|
||||
interface SerializableComponent extends Component {
|
||||
readonly constructor: { name: string };
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Protobuf序列化器
|
||||
*/
|
||||
export class ProtobufSerializer {
|
||||
private registry: ProtobufRegistry;
|
||||
private static instance: ProtobufSerializer;
|
||||
private static readonly logger = createLogger('ProtobufSerializer');
|
||||
|
||||
/** protobuf.js根对象 */
|
||||
private root: protobuf.Root | null = null;
|
||||
|
||||
/** MessageType缓存映射表 */
|
||||
private messageTypeCache: Map<string, protobuf.Type> = new Map();
|
||||
|
||||
/** 组件序列化数据缓存 */
|
||||
private componentDataCache: Map<string, Record<string, any>> = new Map();
|
||||
|
||||
/** 缓存访问计数器 */
|
||||
private cacheAccessCount: Map<string, number> = new Map();
|
||||
|
||||
/** 最大缓存大小 */
|
||||
private maxCacheSize: number = 1000;
|
||||
|
||||
/** 是否启用数据验证 */
|
||||
private enableValidation: boolean = process.env.NODE_ENV === 'development';
|
||||
|
||||
/** 是否启用组件数据缓存 */
|
||||
private enableComponentDataCache: boolean = true;
|
||||
|
||||
private constructor() {
|
||||
this.registry = ProtobufRegistry.getInstance();
|
||||
this.initializeProtobuf();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置性能选项
|
||||
* @param options 性能配置选项
|
||||
* @param options.enableValidation 是否启用数据验证
|
||||
* @param options.enableComponentDataCache 是否启用组件数据缓存
|
||||
* @param options.maxCacheSize 最大缓存大小
|
||||
* @param options.clearCache 是否清空消息类型缓存
|
||||
* @param options.clearAllCaches 是否清空所有缓存
|
||||
*/
|
||||
public setPerformanceOptions(options: {
|
||||
enableValidation?: boolean;
|
||||
enableComponentDataCache?: boolean;
|
||||
maxCacheSize?: number;
|
||||
clearCache?: boolean;
|
||||
clearAllCaches?: boolean;
|
||||
}): void {
|
||||
if (options.enableValidation !== undefined) {
|
||||
this.enableValidation = options.enableValidation;
|
||||
}
|
||||
if (options.enableComponentDataCache !== undefined) {
|
||||
this.enableComponentDataCache = options.enableComponentDataCache;
|
||||
}
|
||||
if (options.maxCacheSize !== undefined && options.maxCacheSize > 0) {
|
||||
this.maxCacheSize = options.maxCacheSize;
|
||||
}
|
||||
if (options.clearCache) {
|
||||
this.messageTypeCache.clear();
|
||||
this.cacheAccessCount.clear();
|
||||
}
|
||||
if (options.clearAllCaches) {
|
||||
this.clearAllCaches();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有缓存
|
||||
*/
|
||||
public clearAllCaches(): void {
|
||||
this.messageTypeCache.clear();
|
||||
this.componentDataCache.clear();
|
||||
this.cacheAccessCount.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动初始化protobuf支持
|
||||
*/
|
||||
private async initializeProtobuf(): Promise<void> {
|
||||
try {
|
||||
this.buildProtoDefinitions();
|
||||
ProtobufSerializer.logger.info('Protobuf支持已启用');
|
||||
} catch (error) {
|
||||
throw new Error('[ProtobufSerializer] 初始化protobuf失败: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
public static getInstance(): ProtobufSerializer {
|
||||
if (!ProtobufSerializer.instance) {
|
||||
ProtobufSerializer.instance = new ProtobufSerializer();
|
||||
}
|
||||
return ProtobufSerializer.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动初始化protobuf.js库
|
||||
* @param protobufRoot protobuf根对象
|
||||
*/
|
||||
public initialize(protobufRoot?: protobuf.Root): void {
|
||||
if (protobufRoot) {
|
||||
this.root = protobufRoot;
|
||||
} else {
|
||||
this.buildProtoDefinitions();
|
||||
}
|
||||
ProtobufSerializer.logger.info('Protobuf支持已手动启用');
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化组件
|
||||
* @param component 要序列化的组件
|
||||
* @returns 序列化数据
|
||||
*/
|
||||
public serialize(component: SerializableComponent): SerializedData {
|
||||
const componentType = component.constructor.name;
|
||||
|
||||
// 检查是否支持protobuf序列化
|
||||
if (!isProtoSerializable(component)) {
|
||||
throw new Error(`组件 ${componentType} 不支持protobuf序列化,请添加@ProtoSerializable装饰器`);
|
||||
}
|
||||
|
||||
const protoName = getProtoName(component);
|
||||
if (!protoName) {
|
||||
throw new Error(`组件 ${componentType} 未设置protobuf名称`);
|
||||
}
|
||||
|
||||
// 获取protobuf消息类型
|
||||
const MessageType = this.getMessageType(protoName);
|
||||
if (!MessageType) {
|
||||
throw new Error(`未找到消息类型: ${protoName}`);
|
||||
}
|
||||
|
||||
// 数据验证(可选)
|
||||
if (this.enableValidation && MessageType.verify) {
|
||||
const error = MessageType.verify(component);
|
||||
if (error) {
|
||||
throw new Error(`数据验证失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 直接让protobufjs处理序列化
|
||||
const message = MessageType.create(component);
|
||||
const buffer = MessageType.encode(message).finish();
|
||||
|
||||
return {
|
||||
type: 'protobuf',
|
||||
componentType: componentType,
|
||||
data: buffer,
|
||||
size: buffer.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化组件
|
||||
* @param component 目标组件实例
|
||||
* @param serializedData 序列化数据
|
||||
*/
|
||||
public deserialize(component: SerializableComponent, serializedData: SerializedData): void {
|
||||
if (serializedData.type !== 'protobuf') {
|
||||
throw new Error(`不支持的序列化类型: ${serializedData.type}`);
|
||||
}
|
||||
|
||||
const protoName = getProtoName(component);
|
||||
if (!protoName) {
|
||||
throw new Error(`组件 ${component.constructor.name} 未设置protobuf名称`);
|
||||
}
|
||||
|
||||
const MessageType = this.getMessageType(protoName);
|
||||
if (!MessageType) {
|
||||
throw new Error(`未找到消息类型: ${protoName}`);
|
||||
}
|
||||
|
||||
// 解码消息并直接应用到组件
|
||||
const message = MessageType.decode(serializedData.data);
|
||||
const decoded = MessageType.toObject(message);
|
||||
|
||||
// 直接应用解码后的数据到组件
|
||||
Object.assign(component, decoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件是否支持protobuf序列化
|
||||
*/
|
||||
public canSerialize(component: SerializableComponent): boolean {
|
||||
if (!this.root) return false;
|
||||
return isProtoSerializable(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量序列化组件
|
||||
* @param components 要序列化的组件数组
|
||||
* @param options 批量序列化选项
|
||||
* @param options.continueOnError 遇到错误时是否继续处理
|
||||
* @param options.maxBatchSize 最大批次大小
|
||||
* @returns 序列化结果数组
|
||||
*/
|
||||
public serializeBatch(
|
||||
components: SerializableComponent[],
|
||||
options?: {
|
||||
continueOnError?: boolean;
|
||||
maxBatchSize?: number;
|
||||
}
|
||||
): SerializedData[] {
|
||||
const results: SerializedData[] = [];
|
||||
const errors: Error[] = [];
|
||||
|
||||
const continueOnError = options?.continueOnError ?? false;
|
||||
const maxBatchSize = options?.maxBatchSize ?? 1000;
|
||||
|
||||
// 分批处理大量组件
|
||||
const batches = this.splitIntoBatches(components, maxBatchSize);
|
||||
|
||||
for (const batch of batches) {
|
||||
const batchResults = this.serializeBatchSerial(batch, continueOnError);
|
||||
results.push(...batchResults.results);
|
||||
errors.push(...batchResults.errors);
|
||||
}
|
||||
|
||||
// 如果有错误且不继续执行,抛出第一个错误
|
||||
if (errors.length > 0 && !continueOnError) {
|
||||
throw errors[0];
|
||||
}
|
||||
|
||||
// 记录错误统计
|
||||
if (errors.length > 0) {
|
||||
ProtobufSerializer.logger.warn(`批量序列化完成,${results.length} 成功,${errors.length} 失败`);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 串行批量序列化
|
||||
*/
|
||||
private serializeBatchSerial(
|
||||
components: Component[],
|
||||
continueOnError: boolean
|
||||
): { results: SerializedData[], errors: Error[] } {
|
||||
const results: SerializedData[] = [];
|
||||
const errors: Error[] = [];
|
||||
|
||||
// 按组件类型分组,减少重复查找
|
||||
const componentGroups = this.groupComponentsByType(components, continueOnError, errors);
|
||||
|
||||
// 按组分别序列化
|
||||
for (const [protoName, groupComponents] of componentGroups) {
|
||||
const definition = this.registry.getComponentDefinition(protoName);
|
||||
const MessageType = this.getMessageType(protoName);
|
||||
|
||||
if (!definition || !MessageType) {
|
||||
const error = new Error(`[ProtobufSerializer] 组件类型 ${protoName} 未正确注册`);
|
||||
if (continueOnError) {
|
||||
errors.push(error);
|
||||
continue;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
for (const component of groupComponents) {
|
||||
try {
|
||||
const result = this.serializeSingleComponent(component, MessageType);
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
if (continueOnError) {
|
||||
errors.push(error instanceof Error ? error : new Error(String(error)));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { results, errors };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 序列化单个组件
|
||||
*/
|
||||
private serializeSingleComponent(
|
||||
component: SerializableComponent,
|
||||
MessageType: protobuf.Type
|
||||
): SerializedData {
|
||||
// 数据验证
|
||||
if (this.enableValidation && MessageType.verify) {
|
||||
const error = MessageType.verify(component);
|
||||
if (error) {
|
||||
throw new Error(`[ProtobufSerializer] 数据验证失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
const message = MessageType.create(component);
|
||||
const buffer = MessageType.encode(message).finish();
|
||||
|
||||
return {
|
||||
type: 'protobuf',
|
||||
componentType: component.constructor.name,
|
||||
data: buffer,
|
||||
size: buffer.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类型分组组件
|
||||
*/
|
||||
private groupComponentsByType(
|
||||
components: SerializableComponent[],
|
||||
continueOnError: boolean,
|
||||
errors: Error[]
|
||||
): Map<string, SerializableComponent[]> {
|
||||
const componentGroups = new Map<string, SerializableComponent[]>();
|
||||
|
||||
for (const component of components) {
|
||||
try {
|
||||
if (!isProtoSerializable(component)) {
|
||||
throw new Error(`[ProtobufSerializer] 组件 ${component.constructor.name} 不支持protobuf序列化`);
|
||||
}
|
||||
|
||||
const protoName = getProtoName(component);
|
||||
if (!protoName) {
|
||||
throw new Error(`[ProtobufSerializer] 组件 ${component.constructor.name} 未设置protobuf名称`);
|
||||
}
|
||||
|
||||
if (!componentGroups.has(protoName)) {
|
||||
componentGroups.set(protoName, []);
|
||||
}
|
||||
componentGroups.get(protoName)!.push(component);
|
||||
} catch (error) {
|
||||
if (continueOnError) {
|
||||
errors.push(error instanceof Error ? error : new Error(String(error)));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return componentGroups;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据需要清理缓存
|
||||
*/
|
||||
private cleanupCacheIfNeeded(): void {
|
||||
if (this.messageTypeCache.size <= this.maxCacheSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = Array.from(this.cacheAccessCount.entries())
|
||||
.sort((a, b) => a[1] - b[1])
|
||||
.slice(0, Math.floor(this.maxCacheSize * 0.2));
|
||||
|
||||
for (const [key] of entries) {
|
||||
this.messageTypeCache.delete(key);
|
||||
this.cacheAccessCount.delete(key);
|
||||
this.componentDataCache.delete(key);
|
||||
}
|
||||
|
||||
ProtobufSerializer.logger.debug(`清理了 ${entries.length} 个缓存项`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数组分割成批次
|
||||
*/
|
||||
private splitIntoBatches<T extends SerializableComponent>(items: T[], batchSize: number): T[][] {
|
||||
const batches: T[][] = [];
|
||||
for (let i = 0; i < items.length; i += batchSize) {
|
||||
batches.push(items.slice(i, i + batchSize));
|
||||
}
|
||||
return batches;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取序列化统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
registeredComponents: number;
|
||||
protobufAvailable: boolean;
|
||||
messageTypeCacheSize: number;
|
||||
componentDataCacheSize: number;
|
||||
enableComponentDataCache: boolean;
|
||||
maxCacheSize: number;
|
||||
} {
|
||||
return {
|
||||
registeredComponents: this.registry.getAllComponents().size,
|
||||
protobufAvailable: !!this.root,
|
||||
messageTypeCacheSize: this.messageTypeCache.size,
|
||||
componentDataCacheSize: this.componentDataCache.size,
|
||||
enableComponentDataCache: this.enableComponentDataCache,
|
||||
maxCacheSize: this.maxCacheSize
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 构建protobuf定义
|
||||
*/
|
||||
private buildProtoDefinitions(): void {
|
||||
try {
|
||||
const protoDefinition = this.registry.generateProtoDefinition();
|
||||
this.root = protobuf.parse(protoDefinition).root;
|
||||
// 清空缓存,schema已更新
|
||||
this.messageTypeCache.clear();
|
||||
this.cacheAccessCount.clear();
|
||||
} catch (error) {
|
||||
ProtobufSerializer.logger.error('构建protobuf定义失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息类型并缓存结果
|
||||
*/
|
||||
private getMessageType(typeName: string): protobuf.Type | null {
|
||||
if (!this.root) return null;
|
||||
|
||||
// 检查缓存
|
||||
const fullTypeName = `ecs.${typeName}`;
|
||||
if (this.messageTypeCache.has(fullTypeName)) {
|
||||
this.cacheAccessCount.set(fullTypeName, (this.cacheAccessCount.get(fullTypeName) || 0) + 1);
|
||||
return this.messageTypeCache.get(fullTypeName)!;
|
||||
}
|
||||
|
||||
try {
|
||||
const messageType = this.root.lookupType(fullTypeName);
|
||||
if (messageType) {
|
||||
// 缓存MessageType
|
||||
this.messageTypeCache.set(fullTypeName, messageType);
|
||||
this.cacheAccessCount.set(fullTypeName, 1);
|
||||
|
||||
this.cleanupCacheIfNeeded();
|
||||
return messageType;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
ProtobufSerializer.logger.warn(`未找到消息类型: ${fullTypeName}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,17 +1,8 @@
|
||||
/**
|
||||
* 序列化类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 序列化数据接口
|
||||
*/
|
||||
export interface SerializedData {
|
||||
/** 序列化类型 */
|
||||
type: 'protobuf' | 'json';
|
||||
/** 组件类型名称 */
|
||||
type: 'tsrpc' | 'json';
|
||||
componentType: string;
|
||||
/** 序列化后的数据 */
|
||||
data: Uint8Array | any;
|
||||
/** 数据大小(字节) */
|
||||
size: number;
|
||||
schema?: string;
|
||||
version?: number;
|
||||
}
|
||||
181
packages/network/src/Serialization/TsrpcDecorators.ts
Normal file
181
packages/network/src/Serialization/TsrpcDecorators.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import 'reflect-metadata';
|
||||
import { Component, createLogger } from '@esengine/ecs-framework';
|
||||
import {
|
||||
SyncFieldOptions,
|
||||
TsrpcSerializableOptions,
|
||||
TsrpcFieldMetadata,
|
||||
TsrpcComponentMetadata,
|
||||
TsrpcSupportedTypes
|
||||
} from './TsrpcTypes';
|
||||
|
||||
const logger = createLogger('TsrpcDecorators');
|
||||
|
||||
const TSRPC_COMPONENT_KEY = Symbol('tsrpc:component');
|
||||
const TSRPC_FIELDS_KEY = Symbol('tsrpc:fields');
|
||||
const TSRPC_SERIALIZABLE_KEY = Symbol('tsrpc:serializable');
|
||||
|
||||
export class TsrpcRegistry {
|
||||
private static _instance: TsrpcRegistry | null = null;
|
||||
private _components: Map<string, TsrpcComponentMetadata> = new Map();
|
||||
private _constructors: Map<Function, TsrpcComponentMetadata> = new Map();
|
||||
|
||||
public static getInstance(): TsrpcRegistry {
|
||||
if (!TsrpcRegistry._instance) {
|
||||
TsrpcRegistry._instance = new TsrpcRegistry();
|
||||
}
|
||||
return TsrpcRegistry._instance;
|
||||
}
|
||||
|
||||
public register(metadata: TsrpcComponentMetadata): void {
|
||||
this._components.set(metadata.componentType, metadata);
|
||||
this._constructors.set(metadata.constructor, metadata);
|
||||
}
|
||||
|
||||
public getByName(componentType: string): TsrpcComponentMetadata | undefined {
|
||||
return this._components.get(componentType);
|
||||
}
|
||||
|
||||
public getByConstructor(constructor: Function): TsrpcComponentMetadata | undefined {
|
||||
return this._constructors.get(constructor);
|
||||
}
|
||||
|
||||
public getAllComponents(): TsrpcComponentMetadata[] {
|
||||
return Array.from(this._components.values());
|
||||
}
|
||||
|
||||
public isRegistered(constructor: Function): boolean {
|
||||
return this._constructors.has(constructor);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._components.clear();
|
||||
this._constructors.clear();
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeInfo(target: any, propertyKey: string) {
|
||||
const designType = Reflect.getMetadata('design:type', target, propertyKey);
|
||||
|
||||
if (!designType) {
|
||||
return { typeName: 'unknown', isArray: false, isOptional: false, isUnion: false };
|
||||
}
|
||||
|
||||
const typeName = designType.name || designType.toString();
|
||||
|
||||
return {
|
||||
typeName: typeName.toLowerCase(),
|
||||
isArray: designType === Array || typeName === 'Array',
|
||||
isOptional: false,
|
||||
isUnion: false,
|
||||
unionTypes: [],
|
||||
genericTypes: []
|
||||
};
|
||||
}
|
||||
|
||||
function createTypeChecker(typeInfo: any): (value: any) => boolean {
|
||||
return (value: any): boolean => {
|
||||
if (value === null || value === undefined) {
|
||||
return typeInfo.isOptional;
|
||||
}
|
||||
|
||||
switch (typeInfo.typeName) {
|
||||
case 'boolean': return typeof value === 'boolean';
|
||||
case 'number': return typeof value === 'number' && !isNaN(value);
|
||||
case 'string': return typeof value === 'string';
|
||||
case 'array': return Array.isArray(value);
|
||||
case 'object': return typeof value === 'object' && !Array.isArray(value);
|
||||
case 'date': return value instanceof Date;
|
||||
default: return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function SyncField(options: SyncFieldOptions = {}): PropertyDecorator {
|
||||
return (target: any, propertyKey: string | symbol) => {
|
||||
if (typeof propertyKey !== 'string') {
|
||||
throw new Error('SyncField只支持字符串属性名');
|
||||
}
|
||||
|
||||
const existingFields: Map<string, TsrpcFieldMetadata> =
|
||||
Reflect.getMetadata(TSRPC_FIELDS_KEY, target.constructor) || new Map();
|
||||
|
||||
const typeInfo = getTypeInfo(target, propertyKey);
|
||||
|
||||
const fieldMetadata: TsrpcFieldMetadata = {
|
||||
propertyKey,
|
||||
options: { priority: 'normal', authorityOnly: false, throttle: 0, delta: false, ...options },
|
||||
typeInfo,
|
||||
typeChecker: createTypeChecker(typeInfo),
|
||||
fieldIndex: existingFields.size
|
||||
};
|
||||
|
||||
existingFields.set(propertyKey, fieldMetadata);
|
||||
Reflect.defineMetadata(TSRPC_FIELDS_KEY, existingFields, target.constructor);
|
||||
};
|
||||
}
|
||||
|
||||
export function TsrpcSerializable(options: TsrpcSerializableOptions = {}): ClassDecorator {
|
||||
return (constructor: any) => {
|
||||
Reflect.defineMetadata(TSRPC_SERIALIZABLE_KEY, true, constructor);
|
||||
|
||||
const fields: Map<string, TsrpcFieldMetadata> =
|
||||
Reflect.getMetadata(TSRPC_FIELDS_KEY, constructor) || new Map();
|
||||
|
||||
const componentMetadata: TsrpcComponentMetadata = {
|
||||
componentType: options.name || constructor.name,
|
||||
options: { version: 1, validation: false, compression: false, strategy: 'auto', ...options },
|
||||
fields,
|
||||
constructor,
|
||||
version: options.version || 1,
|
||||
createdAt: Date.now()
|
||||
};
|
||||
|
||||
Reflect.defineMetadata(TSRPC_COMPONENT_KEY, componentMetadata, constructor);
|
||||
|
||||
const registry = TsrpcRegistry.getInstance();
|
||||
registry.register(componentMetadata);
|
||||
};
|
||||
}
|
||||
|
||||
export function isTsrpcSerializable(component: any): boolean {
|
||||
return Reflect.getMetadata(TSRPC_SERIALIZABLE_KEY, component.constructor) === true;
|
||||
}
|
||||
|
||||
export function getTsrpcMetadata(constructor: Function): TsrpcComponentMetadata | undefined {
|
||||
return Reflect.getMetadata(TSRPC_COMPONENT_KEY, constructor);
|
||||
}
|
||||
|
||||
export function getTsrpcFields(constructor: Function): Map<string, TsrpcFieldMetadata> {
|
||||
return Reflect.getMetadata(TSRPC_FIELDS_KEY, constructor) || new Map();
|
||||
}
|
||||
|
||||
export function getTsrpcName(component: any): string | undefined {
|
||||
const metadata = getTsrpcMetadata(component.constructor);
|
||||
return metadata?.componentType;
|
||||
}
|
||||
|
||||
export const TsrpcString = (options: SyncFieldOptions = {}) => SyncField(options);
|
||||
export const TsrpcNumber = (options: SyncFieldOptions = {}) => SyncField(options);
|
||||
export const TsrpcBoolean = (options: SyncFieldOptions = {}) => SyncField(options);
|
||||
export const TsrpcDate = (options: SyncFieldOptions = {}) => SyncField(options);
|
||||
export const TsrpcArray = (options: SyncFieldOptions = {}) => SyncField({ ...options, delta: true });
|
||||
export const TsrpcObject = (options: SyncFieldOptions = {}) => SyncField({ ...options, delta: true });
|
||||
export const TsrpcCritical = (options: SyncFieldOptions = {}) => SyncField({ ...options, priority: 'critical' });
|
||||
export const TsrpcAuthority = (options: SyncFieldOptions = {}) => SyncField({ ...options, authorityOnly: true });
|
||||
|
||||
export function validateTsrpcComponent(component: Component): boolean {
|
||||
const metadata = getTsrpcMetadata(component.constructor);
|
||||
if (!metadata) return false;
|
||||
|
||||
for (const [fieldName, fieldMetadata] of metadata.fields) {
|
||||
const value = (component as any)[fieldName];
|
||||
if (fieldMetadata.typeChecker && !fieldMetadata.typeChecker(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function AutoSync(target: any): any {
|
||||
return target;
|
||||
}
|
||||
179
packages/network/src/Serialization/TsrpcSerializer.ts
Normal file
179
packages/network/src/Serialization/TsrpcSerializer.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { Component, createLogger } from '@esengine/ecs-framework';
|
||||
import { SerializedData } from './SerializationTypes';
|
||||
import {
|
||||
TsrpcComponentMetadata,
|
||||
TsrpcFieldMetadata,
|
||||
TsrpcSerializationStats,
|
||||
TsrpcSerializable
|
||||
} from './TsrpcTypes';
|
||||
import {
|
||||
TsrpcRegistry,
|
||||
isTsrpcSerializable,
|
||||
getTsrpcMetadata,
|
||||
validateTsrpcComponent
|
||||
} from './TsrpcDecorators';
|
||||
|
||||
const logger = createLogger('TsrpcSerializer');
|
||||
export class TsrpcSerializer {
|
||||
private static _instance: TsrpcSerializer | null = null;
|
||||
private _registry: TsrpcRegistry;
|
||||
private _stats: TsrpcSerializationStats;
|
||||
|
||||
constructor() {
|
||||
this._registry = TsrpcRegistry.getInstance();
|
||||
this._stats = {
|
||||
serializeCount: 0,
|
||||
deserializeCount: 0,
|
||||
totalSerializeTime: 0,
|
||||
totalDeserializeTime: 0,
|
||||
averageSerializedSize: 0,
|
||||
errorCount: 0,
|
||||
cacheHits: 0,
|
||||
cacheMisses: 0
|
||||
};
|
||||
}
|
||||
|
||||
public static getInstance(): TsrpcSerializer {
|
||||
if (!TsrpcSerializer._instance) {
|
||||
TsrpcSerializer._instance = new TsrpcSerializer();
|
||||
}
|
||||
return TsrpcSerializer._instance;
|
||||
}
|
||||
|
||||
public serialize(component: Component): SerializedData | null {
|
||||
if (!isTsrpcSerializable(component)) return null;
|
||||
|
||||
const metadata = getTsrpcMetadata(component.constructor);
|
||||
if (!metadata) return null;
|
||||
|
||||
try {
|
||||
const data = this.extractSerializableData(component, metadata);
|
||||
const jsonString = JSON.stringify(data);
|
||||
const serialized = new TextEncoder().encode(jsonString);
|
||||
|
||||
this._stats.serializeCount++;
|
||||
this._stats.averageSerializedSize =
|
||||
(this._stats.averageSerializedSize * (this._stats.serializeCount - 1) + serialized.length)
|
||||
/ this._stats.serializeCount;
|
||||
|
||||
return {
|
||||
type: 'tsrpc',
|
||||
componentType: metadata.componentType,
|
||||
data: serialized,
|
||||
size: serialized.length,
|
||||
schema: metadata.componentType,
|
||||
version: metadata.version
|
||||
};
|
||||
} catch (error) {
|
||||
this._stats.errorCount++;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public deserialize<T extends Component>(
|
||||
serializedData: SerializedData,
|
||||
ComponentClass?: new (...args: any[]) => T
|
||||
): T | null {
|
||||
if (serializedData.type !== 'tsrpc') return null;
|
||||
|
||||
let metadata: TsrpcComponentMetadata | undefined;
|
||||
if (ComponentClass) {
|
||||
metadata = getTsrpcMetadata(ComponentClass);
|
||||
} else {
|
||||
metadata = this._registry.getByName(serializedData.componentType);
|
||||
}
|
||||
|
||||
if (!metadata) return null;
|
||||
|
||||
try {
|
||||
const jsonString = new TextDecoder().decode(serializedData.data as Uint8Array);
|
||||
const data = JSON.parse(jsonString);
|
||||
|
||||
const component = new metadata.constructor();
|
||||
this.applySerializableData(component as T, data, metadata);
|
||||
this._stats.deserializeCount++;
|
||||
|
||||
return component as T;
|
||||
} catch (error) {
|
||||
this._stats.errorCount++;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private extractSerializableData(component: Component, metadata: TsrpcComponentMetadata): any {
|
||||
const data: any = {};
|
||||
for (const [fieldName, fieldMetadata] of metadata.fields) {
|
||||
const value = (component as any)[fieldName];
|
||||
if (value !== undefined || fieldMetadata.typeInfo.isOptional) {
|
||||
data[fieldName] = this.processFieldValue(value, fieldMetadata);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private applySerializableData(component: Component, data: any, metadata: TsrpcComponentMetadata): void {
|
||||
for (const [fieldName, fieldMetadata] of metadata.fields) {
|
||||
if (fieldName in data) {
|
||||
const value = this.processFieldValue(data[fieldName], fieldMetadata, true);
|
||||
(component as any)[fieldName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isTsrpcSerializableInstance(component)) {
|
||||
component.applyTsrpcData(data);
|
||||
}
|
||||
}
|
||||
|
||||
private processFieldValue(value: any, fieldMetadata: TsrpcFieldMetadata, isDeserializing = false): any {
|
||||
if (value === null || value === undefined) return value;
|
||||
|
||||
const { typeInfo } = fieldMetadata;
|
||||
|
||||
if (['boolean', 'number', 'string'].includes(typeInfo.typeName)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeInfo.typeName === 'date') {
|
||||
return isDeserializing ? new Date(value) : value.toISOString();
|
||||
}
|
||||
|
||||
if (typeInfo.isArray && Array.isArray(value)) {
|
||||
return value.map(item => this.processFieldValue(item, fieldMetadata, isDeserializing));
|
||||
}
|
||||
|
||||
if (typeInfo.typeName === 'object' && typeof value === 'object') {
|
||||
return isDeserializing ? structuredClone(value) : value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private isTsrpcSerializableInstance(component: any): component is TsrpcSerializable {
|
||||
return typeof component.getTsrpcData === 'function' &&
|
||||
typeof component.applyTsrpcData === 'function';
|
||||
}
|
||||
|
||||
public getStats(): TsrpcSerializationStats {
|
||||
return { ...this._stats };
|
||||
}
|
||||
|
||||
public resetStats(): void {
|
||||
this._stats = {
|
||||
serializeCount: 0,
|
||||
deserializeCount: 0,
|
||||
totalSerializeTime: 0,
|
||||
totalDeserializeTime: 0,
|
||||
averageSerializedSize: 0,
|
||||
errorCount: 0,
|
||||
cacheHits: 0,
|
||||
cacheMisses: 0
|
||||
};
|
||||
}
|
||||
|
||||
public getSupportedTypes(): string[] {
|
||||
return this._registry.getAllComponents().map(comp => comp.componentType);
|
||||
}
|
||||
}
|
||||
|
||||
export const tsrpcSerializer = TsrpcSerializer.getInstance();
|
||||
218
packages/network/src/Serialization/TsrpcTypes.ts
Normal file
218
packages/network/src/Serialization/TsrpcTypes.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* TSRPC序列化类型定义
|
||||
*/
|
||||
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* TSRPC同步字段配置选项
|
||||
*/
|
||||
export interface SyncFieldOptions {
|
||||
/**
|
||||
* 同步优先级
|
||||
* - 'critical': 关键字段,每帧必须同步
|
||||
* - 'high': 高优先级,频繁同步
|
||||
* - 'normal': 普通优先级,正常同步频率
|
||||
* - 'low': 低优先级,较少同步
|
||||
*/
|
||||
priority?: 'critical' | 'high' | 'normal' | 'low';
|
||||
|
||||
/**
|
||||
* 值变化时的回调函数名
|
||||
* 回调函数签名: (oldValue: T, newValue: T) => void
|
||||
*/
|
||||
hook?: string;
|
||||
|
||||
/**
|
||||
* 是否只有拥有权限的客户端才能修改
|
||||
*/
|
||||
authorityOnly?: boolean;
|
||||
|
||||
/**
|
||||
* 同步频率限制(毫秒)
|
||||
* 防止过于频繁的网络同步,默认为0(不限制)
|
||||
*/
|
||||
throttle?: number;
|
||||
|
||||
/**
|
||||
* 是否启用增量同步
|
||||
* 对于对象和数组类型,启用后只同步变化的部分
|
||||
*/
|
||||
delta?: boolean;
|
||||
|
||||
/**
|
||||
* 自定义比较函数
|
||||
* 用于判断值是否发生变化,默认使用深度比较
|
||||
*/
|
||||
compare?: (oldValue: any, newValue: any) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TSRPC序列化组件配置选项
|
||||
*/
|
||||
export interface TsrpcSerializableOptions {
|
||||
/**
|
||||
* 组件版本号,用于兼容性检查
|
||||
*/
|
||||
version?: number;
|
||||
|
||||
/**
|
||||
* 是否启用类型验证
|
||||
* 在开发模式下默认启用,生产模式下默认关闭
|
||||
*/
|
||||
validation?: boolean;
|
||||
|
||||
/**
|
||||
* 是否启用压缩
|
||||
*/
|
||||
compression?: boolean;
|
||||
|
||||
/**
|
||||
* 自定义序列化名称,默认使用类名
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* 序列化策略
|
||||
* - 'full': 完整序列化所有字段
|
||||
* - 'partial': 只序列化标记为@SyncField的字段
|
||||
* - 'auto': 自动检测,推荐使用
|
||||
*/
|
||||
strategy?: 'full' | 'partial' | 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* TSRPC字段元数据
|
||||
*/
|
||||
export interface TsrpcFieldMetadata {
|
||||
/** 属性名称 */
|
||||
propertyKey: string;
|
||||
|
||||
/** 字段选项 */
|
||||
options: SyncFieldOptions;
|
||||
|
||||
/** TypeScript类型信息 */
|
||||
typeInfo: {
|
||||
/** 基本类型名 */
|
||||
typeName: string;
|
||||
/** 是否为数组 */
|
||||
isArray: boolean;
|
||||
/** 是否可选 */
|
||||
isOptional: boolean;
|
||||
/** 是否为联合类型 */
|
||||
isUnion: boolean;
|
||||
/** 联合类型成员(如果是联合类型) */
|
||||
unionTypes?: string[];
|
||||
/** 泛型参数(如果有) */
|
||||
genericTypes?: string[];
|
||||
};
|
||||
|
||||
/** 运行时类型检查函数 */
|
||||
typeChecker?: (value: any) => boolean;
|
||||
|
||||
/** 字段索引(用于二进制序列化) */
|
||||
fieldIndex: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* TSRPC组件元数据
|
||||
*/
|
||||
export interface TsrpcComponentMetadata {
|
||||
/** 组件类型名称 */
|
||||
componentType: string;
|
||||
|
||||
/** 组件配置选项 */
|
||||
options: TsrpcSerializableOptions;
|
||||
|
||||
/** 字段元数据映射 */
|
||||
fields: Map<string, TsrpcFieldMetadata>;
|
||||
|
||||
/** 组件构造函数 */
|
||||
constructor: new (...args: any[]) => Component;
|
||||
|
||||
/** TSBuffer schema */
|
||||
schema?: any;
|
||||
|
||||
/** 序列化版本 */
|
||||
version: number;
|
||||
|
||||
/** 创建时间戳 */
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* TSRPC可序列化组件接口
|
||||
*/
|
||||
export interface TsrpcSerializable {
|
||||
/** 获取TSRPC序列化数据 */
|
||||
getTsrpcData(): Record<string, any>;
|
||||
|
||||
/** 应用TSRPC序列化数据 */
|
||||
applyTsrpcData(data: Record<string, any>): void;
|
||||
|
||||
/** 获取变化的字段 */
|
||||
getDirtyFields(): string[];
|
||||
|
||||
/** 标记字段为clean状态 */
|
||||
markClean(fieldNames?: string[]): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的TypeScript基本类型
|
||||
*/
|
||||
export const TsrpcSupportedTypes = {
|
||||
// 基本类型
|
||||
BOOLEAN: 'boolean',
|
||||
NUMBER: 'number',
|
||||
BIGINT: 'bigint',
|
||||
STRING: 'string',
|
||||
|
||||
// 对象类型
|
||||
OBJECT: 'object',
|
||||
ARRAY: 'array',
|
||||
DATE: 'Date',
|
||||
REGEXP: 'RegExp',
|
||||
|
||||
// 特殊类型
|
||||
UNDEFINED: 'undefined',
|
||||
NULL: 'null',
|
||||
|
||||
// 二进制类型
|
||||
UINT8ARRAY: 'Uint8Array',
|
||||
BUFFER: 'Buffer',
|
||||
ARRAYBUFFER: 'ArrayBuffer',
|
||||
|
||||
// 联合类型和字面量类型
|
||||
UNION: 'union',
|
||||
LITERAL: 'literal',
|
||||
ENUM: 'enum'
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* TSRPC序列化统计信息
|
||||
*/
|
||||
export interface TsrpcSerializationStats {
|
||||
/** 序列化次数 */
|
||||
serializeCount: number;
|
||||
|
||||
/** 反序列化次数 */
|
||||
deserializeCount: number;
|
||||
|
||||
/** 总序列化时间 */
|
||||
totalSerializeTime: number;
|
||||
|
||||
/** 总反序列化时间 */
|
||||
totalDeserializeTime: number;
|
||||
|
||||
/** 平均序列化大小 */
|
||||
averageSerializedSize: number;
|
||||
|
||||
/** 错误次数 */
|
||||
errorCount: number;
|
||||
|
||||
/** 缓存命中次数 */
|
||||
cacheHits: number;
|
||||
|
||||
/** 缓存未命中次数 */
|
||||
cacheMisses: number;
|
||||
}
|
||||
@@ -1,41 +1,37 @@
|
||||
/**
|
||||
* 网络插件序列化系统导出
|
||||
*
|
||||
* 统一导出所有protobuf序列化相关的类型、装饰器和工具函数
|
||||
*/
|
||||
|
||||
// Protobuf序列化系统(用于帧同步框架)
|
||||
export {
|
||||
ProtobufSerializer
|
||||
} from './ProtobufSerializer';
|
||||
export { SerializedData } from './SerializationTypes';
|
||||
|
||||
export {
|
||||
ProtoSerializable,
|
||||
ProtoField,
|
||||
ProtoFloat,
|
||||
ProtoInt32,
|
||||
ProtoString,
|
||||
ProtoBool,
|
||||
ProtoBytes,
|
||||
ProtoTimestamp,
|
||||
ProtoDouble,
|
||||
ProtoInt64,
|
||||
ProtoStruct,
|
||||
ProtoMessage,
|
||||
ProtoEnum,
|
||||
isProtoSerializable,
|
||||
getProtoName,
|
||||
ProtobufRegistry,
|
||||
ProtoComponentDefinition,
|
||||
ProtoFieldDefinition,
|
||||
ProtoFieldType,
|
||||
FieldSyncPriority,
|
||||
ComponentSyncMode,
|
||||
ComponentSyncOptions,
|
||||
FieldSyncOptions
|
||||
} from './ProtobufDecorators';
|
||||
SyncFieldOptions,
|
||||
TsrpcSerializableOptions,
|
||||
TsrpcFieldMetadata,
|
||||
TsrpcComponentMetadata,
|
||||
TsrpcSerializable,
|
||||
TsrpcSupportedTypes,
|
||||
TsrpcSerializationStats
|
||||
} from './TsrpcTypes';
|
||||
|
||||
export {
|
||||
SerializedData
|
||||
} from './SerializationTypes';
|
||||
TsrpcRegistry,
|
||||
SyncField,
|
||||
TsrpcSerializable as TsrpcSerializableDecorator,
|
||||
isTsrpcSerializable,
|
||||
getTsrpcMetadata,
|
||||
getTsrpcFields,
|
||||
getTsrpcName,
|
||||
validateTsrpcComponent,
|
||||
TsrpcString,
|
||||
TsrpcNumber,
|
||||
TsrpcBoolean,
|
||||
TsrpcDate,
|
||||
TsrpcArray,
|
||||
TsrpcObject,
|
||||
TsrpcCritical,
|
||||
TsrpcAuthority,
|
||||
AutoSync
|
||||
} from './TsrpcDecorators';
|
||||
|
||||
export {
|
||||
TsrpcSerializer,
|
||||
tsrpcSerializer
|
||||
} from './TsrpcSerializer';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Entity, Component, ComponentType, createLogger } from '@esengine/ecs-framework';
|
||||
import { ISnapshotable, SceneSnapshot, EntitySnapshot, ComponentSnapshot, SnapshotConfig } from './ISnapshotable';
|
||||
import { ProtobufSerializer } from '../Serialization/ProtobufSerializer';
|
||||
import { TsrpcSerializer } from '../Serialization/TsrpcSerializer';
|
||||
import { SerializedData } from '../Serialization/SerializationTypes';
|
||||
import { isProtoSerializable } from '../Serialization/ProtobufDecorators';
|
||||
import { isTsrpcSerializable } from '../Serialization/TsrpcDecorators';
|
||||
import {
|
||||
NetworkComponentType,
|
||||
IComponentFactory,
|
||||
@@ -125,8 +125,8 @@ export class SnapshotManager {
|
||||
/** 最大缓存数量 */
|
||||
private maxCacheSize: number = 10;
|
||||
|
||||
/** Protobuf序列化器 */
|
||||
private protobufSerializer: ProtobufSerializer;
|
||||
/** TSRPC序列化器 */
|
||||
private tsrpcSerializer: TsrpcSerializer;
|
||||
|
||||
/** 组件类型注册表 */
|
||||
private componentRegistry: ComponentTypeRegistry;
|
||||
@@ -135,7 +135,7 @@ export class SnapshotManager {
|
||||
* 构造函数
|
||||
*/
|
||||
constructor() {
|
||||
this.protobufSerializer = ProtobufSerializer.getInstance();
|
||||
this.tsrpcSerializer = TsrpcSerializer.getInstance();
|
||||
this.componentRegistry = ComponentTypeRegistry.Instance;
|
||||
}
|
||||
|
||||
@@ -268,11 +268,11 @@ export class SnapshotManager {
|
||||
// 恢复组件数据
|
||||
const serializedData = componentSnapshot.data as SerializedData;
|
||||
|
||||
if (!isProtoSerializable(component)) {
|
||||
throw new Error(`[SnapshotManager] 组件 ${component.constructor.name} 不支持protobuf反序列化`);
|
||||
if (!isTsrpcSerializable(component)) {
|
||||
throw new Error(`[SnapshotManager] 组件 ${component.constructor.name} 不支持TSRPC反序列化`);
|
||||
}
|
||||
|
||||
this.protobufSerializer.deserialize(component, serializedData);
|
||||
this.tsrpcSerializer.deserialize(serializedData);
|
||||
} catch (error) {
|
||||
SnapshotManager.logger.error(`创建组件失败: ${componentSnapshot.type}`, error);
|
||||
}
|
||||
@@ -392,33 +392,22 @@ export class SnapshotManager {
|
||||
*/
|
||||
public getCacheStats(): {
|
||||
snapshotCacheSize: number;
|
||||
protobufStats?: {
|
||||
tsrpcStats?: {
|
||||
registeredComponents: number;
|
||||
protobufAvailable: boolean;
|
||||
tsrpcAvailable: boolean;
|
||||
};
|
||||
} {
|
||||
const stats: any = {
|
||||
snapshotCacheSize: this.snapshotCache.size
|
||||
};
|
||||
|
||||
if (this.protobufSerializer) {
|
||||
stats.protobufStats = this.protobufSerializer.getStats();
|
||||
if (this.tsrpcSerializer) {
|
||||
stats.tsrpcStats = this.tsrpcSerializer.getStats();
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动初始化protobuf支持(可选,通常会自动初始化)
|
||||
*
|
||||
* @param protobufJs - protobuf.js库实例
|
||||
*/
|
||||
public initializeProtobuf(protobufJs: any): void {
|
||||
if (this.protobufSerializer) {
|
||||
this.protobufSerializer.initialize(protobufJs);
|
||||
SnapshotManager.logger.info('Protobuf支持已手动启用');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册组件类型
|
||||
@@ -487,7 +476,7 @@ export class SnapshotManager {
|
||||
/**
|
||||
* 创建组件快照
|
||||
*
|
||||
* 优先使用protobuf序列化,fallback到JSON序列化
|
||||
* 优先使用TSRPC序列化,fallback到JSON序列化
|
||||
*/
|
||||
private createComponentSnapshot(component: Component): ComponentSnapshot | null {
|
||||
if (!this.isComponentSnapshotable(component)) {
|
||||
@@ -496,12 +485,17 @@ export class SnapshotManager {
|
||||
|
||||
let serializedData: SerializedData;
|
||||
|
||||
// 优先使用protobuf序列化
|
||||
if (isProtoSerializable(component)) {
|
||||
// 优先使用TSRPC序列化
|
||||
if (isTsrpcSerializable(component)) {
|
||||
try {
|
||||
serializedData = this.protobufSerializer.serialize(component);
|
||||
const tsrpcResult = this.tsrpcSerializer.serialize(component);
|
||||
if (tsrpcResult) {
|
||||
serializedData = tsrpcResult;
|
||||
} else {
|
||||
throw new Error('TSRPC序列化返回null');
|
||||
}
|
||||
} catch (error) {
|
||||
SnapshotManager.logger.warn(`[SnapshotManager] Protobuf序列化失败,fallback到JSON: ${error}`);
|
||||
SnapshotManager.logger.warn(`[SnapshotManager] TSRPC序列化失败,fallback到JSON: ${error}`);
|
||||
serializedData = this.createJsonSerializedData(component);
|
||||
}
|
||||
} else {
|
||||
@@ -626,7 +620,7 @@ export class SnapshotManager {
|
||||
/**
|
||||
* 从快照恢复组件
|
||||
*
|
||||
* 使用protobuf反序列化
|
||||
* 使用TSRPC反序列化
|
||||
*/
|
||||
private restoreComponentFromSnapshot(entity: Entity, componentSnapshot: ComponentSnapshot): void {
|
||||
// 查找现有组件
|
||||
@@ -650,9 +644,9 @@ export class SnapshotManager {
|
||||
// 恢复组件数据
|
||||
const serializedData = componentSnapshot.data as SerializedData;
|
||||
|
||||
if (serializedData.type === 'protobuf' && isProtoSerializable(component)) {
|
||||
// 使用protobuf反序列化
|
||||
this.protobufSerializer.deserialize(component, serializedData);
|
||||
if (serializedData.type === 'tsrpc' && isTsrpcSerializable(component)) {
|
||||
// 使用TSRPC反序列化
|
||||
this.tsrpcSerializer.deserialize(serializedData);
|
||||
} else if (serializedData.type === 'json') {
|
||||
// 使用JSON反序列化
|
||||
this.deserializeFromJson(component, serializedData);
|
||||
|
||||
219
packages/network/src/SyncVar/ComponentIdGenerator.ts
Normal file
219
packages/network/src/SyncVar/ComponentIdGenerator.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import { Entity, createLogger } from '@esengine/ecs-framework';
|
||||
import { NetworkEnvironment } from '../Core/NetworkEnvironment';
|
||||
import { NetworkIdentity } from '../Core/NetworkIdentity';
|
||||
|
||||
const logger = createLogger('ComponentIdGenerator');
|
||||
|
||||
/**
|
||||
* 组件ID生成器配置
|
||||
*/
|
||||
export interface ComponentIdGeneratorConfig {
|
||||
useNetworkId: boolean;
|
||||
useEntityId: boolean;
|
||||
useTimestamp: boolean;
|
||||
useSequence: boolean;
|
||||
customPrefix?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件唯一ID生成器
|
||||
*
|
||||
* 提供多种策略生成组件的唯一标识符
|
||||
* 可以集成网络ID系统、实体ID系统等
|
||||
*/
|
||||
export class ComponentIdGenerator {
|
||||
private static readonly DEFAULT_CONFIG: ComponentIdGeneratorConfig = {
|
||||
useNetworkId: true,
|
||||
useEntityId: true,
|
||||
useTimestamp: true,
|
||||
useSequence: true
|
||||
};
|
||||
|
||||
private _config: ComponentIdGeneratorConfig;
|
||||
private _sequenceCounter: number = 0;
|
||||
private _generatedIds: Set<string> = new Set();
|
||||
|
||||
constructor(config?: Partial<ComponentIdGeneratorConfig>) {
|
||||
this._config = { ...ComponentIdGenerator.DEFAULT_CONFIG, ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* 为组件生成唯一ID
|
||||
*
|
||||
* @param component - 组件实例
|
||||
* @returns 唯一ID字符串
|
||||
*/
|
||||
public generateId(component: any): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
// 添加自定义前缀
|
||||
if (this._config.customPrefix) {
|
||||
parts.push(this._config.customPrefix);
|
||||
}
|
||||
|
||||
// 添加组件类型名称
|
||||
parts.push(component.constructor.name);
|
||||
|
||||
// 尝试使用网络ID
|
||||
if (this._config.useNetworkId) {
|
||||
const networkId = this.extractNetworkId(component);
|
||||
if (networkId) {
|
||||
parts.push(`net_${networkId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试使用实体ID
|
||||
if (this._config.useEntityId) {
|
||||
const entityId = this.extractEntityId(component);
|
||||
if (entityId !== null) {
|
||||
parts.push(`ent_${entityId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加环境前缀
|
||||
const env = NetworkEnvironment.isServer ? 's' : 'c';
|
||||
parts.push(env);
|
||||
|
||||
// 添加时间戳
|
||||
if (this._config.useTimestamp) {
|
||||
parts.push(Date.now().toString(36));
|
||||
}
|
||||
|
||||
// 添加序列号
|
||||
if (this._config.useSequence) {
|
||||
parts.push((++this._sequenceCounter).toString(36));
|
||||
}
|
||||
|
||||
// 生成基础ID
|
||||
let baseId = parts.join('_');
|
||||
|
||||
// 确保ID唯一性
|
||||
let finalId = baseId;
|
||||
let counter = 0;
|
||||
while (this._generatedIds.has(finalId)) {
|
||||
finalId = `${baseId}_${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
// 记录生成的ID
|
||||
this._generatedIds.add(finalId);
|
||||
|
||||
logger.debug(`为组件 ${component.constructor.name} 生成ID: ${finalId}`);
|
||||
return finalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从组件中提取网络ID
|
||||
*
|
||||
* @param component - 组件实例
|
||||
* @returns 网络ID或null
|
||||
*/
|
||||
private extractNetworkId(component: any): string | null {
|
||||
try {
|
||||
// 检查组件是否有网络身份
|
||||
if (component.networkIdentity && component.networkIdentity instanceof NetworkIdentity) {
|
||||
return component.networkIdentity.networkId;
|
||||
}
|
||||
|
||||
// 检查组件的实体是否有网络身份
|
||||
if (component.entity) {
|
||||
const networkIdentity = component.entity.getComponent(NetworkIdentity);
|
||||
if (networkIdentity) {
|
||||
return networkIdentity.networkId;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查组件本身是否有networkId属性
|
||||
if (component.networkId && typeof component.networkId === 'string') {
|
||||
return component.networkId;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.debug('提取网络ID时出错:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从组件中提取实体ID
|
||||
*
|
||||
* @param component - 组件实例
|
||||
* @returns 实体ID或null
|
||||
*/
|
||||
private extractEntityId(component: any): number | null {
|
||||
try {
|
||||
// 检查组件是否有实体引用
|
||||
if (component.entity && component.entity instanceof Entity) {
|
||||
return component.entity.id;
|
||||
}
|
||||
|
||||
// 检查组件本身是否有entityId属性
|
||||
if (typeof component.entityId === 'number') {
|
||||
return component.entityId;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.debug('提取实体ID时出错:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查ID是否已经生成过
|
||||
*
|
||||
* @param id - 要检查的ID
|
||||
* @returns 是否已存在
|
||||
*/
|
||||
public hasGenerated(id: string): boolean {
|
||||
return this._generatedIds.has(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理已生成的ID记录
|
||||
*
|
||||
* @param maxAge - 最大保留时间(毫秒),默认1小时
|
||||
*/
|
||||
public cleanup(maxAge: number = 3600000): void {
|
||||
// 避免未使用参数警告
|
||||
void maxAge;
|
||||
// 简单实现:清空所有记录
|
||||
// 在实际应用中,可以根据时间戳进行更精细的清理
|
||||
this._generatedIds.clear();
|
||||
this._sequenceCounter = 0;
|
||||
logger.debug('已清理ID生成器缓存');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
generatedCount: number;
|
||||
sequenceCounter: number;
|
||||
config: ComponentIdGeneratorConfig;
|
||||
} {
|
||||
return {
|
||||
generatedCount: this._generatedIds.size,
|
||||
sequenceCounter: this._sequenceCounter,
|
||||
config: { ...this._config }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置生成器状态
|
||||
*/
|
||||
public reset(): void {
|
||||
this._generatedIds.clear();
|
||||
this._sequenceCounter = 0;
|
||||
logger.debug('ID生成器已重置');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<ComponentIdGeneratorConfig>): void {
|
||||
this._config = { ...this._config, ...newConfig };
|
||||
logger.debug('ID生成器配置已更新');
|
||||
}
|
||||
}
|
||||
318
packages/network/src/SyncVar/SyncVarAuthority.ts
Normal file
318
packages/network/src/SyncVar/SyncVarAuthority.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
import { AUTHORITY_CONFIG, SYNCVAR_CONFIG } from '../constants/NetworkConstants';
|
||||
import { NetworkEnvironment } from '../Core/NetworkEnvironment';
|
||||
import { NetworkIdentity } from '../Core/NetworkIdentity';
|
||||
import { INetworkComponent, INetworkEntity, AuthorityContext as CoreAuthorityContext, AuthorityType as CoreAuthorityType, NetworkEnvironmentType } from '../types/CoreTypes';
|
||||
|
||||
const logger = {
|
||||
info: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
};
|
||||
|
||||
/** 重新导出核心权限类型 */
|
||||
export { AuthorityType } from '../types/CoreTypes';
|
||||
|
||||
/** 重新导出核心权限上下文 */
|
||||
export { AuthorityContext } from '../types/CoreTypes';
|
||||
|
||||
/**
|
||||
* 权限规则
|
||||
*/
|
||||
export interface AuthorityRule {
|
||||
/** 规则名称 */
|
||||
name: string;
|
||||
/** 规则检查函数 */
|
||||
check: <T extends INetworkComponent>(component: T, context: CoreAuthorityContext) => boolean;
|
||||
/** 优先级(数值越大优先级越高) */
|
||||
priority: number;
|
||||
/** 是否启用 */
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar权限管理器
|
||||
*
|
||||
* 提供灵活的权限检查机制,支持多种权限策略
|
||||
*/
|
||||
export class SyncVarAuthorityManager {
|
||||
private static _instance: SyncVarAuthorityManager | null = null;
|
||||
private _rules: AuthorityRule[] = [];
|
||||
private _cache: Map<string, { result: boolean; timestamp: number }> = new Map();
|
||||
private _cacheTimeout: number = SYNCVAR_CONFIG.DEFAULT_CACHE_TIMEOUT;
|
||||
|
||||
private constructor() {
|
||||
this.initializeDefaultRules();
|
||||
}
|
||||
|
||||
public static get Instance(): SyncVarAuthorityManager {
|
||||
if (!SyncVarAuthorityManager._instance) {
|
||||
SyncVarAuthorityManager._instance = new SyncVarAuthorityManager();
|
||||
}
|
||||
return SyncVarAuthorityManager._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化默认权限规则
|
||||
*/
|
||||
private initializeDefaultRules(): void {
|
||||
// 服务端权限规则
|
||||
this.addRule({
|
||||
name: 'server-full-authority',
|
||||
check: (component, context) => context.environment === 'server',
|
||||
priority: AUTHORITY_CONFIG.SERVER_AUTHORITY_PRIORITY,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
// 网络身份权限规则
|
||||
this.addRule({
|
||||
name: 'network-identity-authority',
|
||||
check: (component, context) => {
|
||||
if (context.environment !== 'client') return false;
|
||||
|
||||
const networkIdentity = this.extractNetworkIdentity(component);
|
||||
if (!networkIdentity) return false;
|
||||
|
||||
// 客户端只能控制属于自己的网络对象
|
||||
return networkIdentity.hasAuthority &&
|
||||
networkIdentity.ownerId === context.clientId;
|
||||
},
|
||||
priority: AUTHORITY_CONFIG.NETWORK_IDENTITY_PRIORITY,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
// 组件自定义权限规则
|
||||
this.addRule({
|
||||
name: 'component-custom-authority',
|
||||
check: (component: any, context) => {
|
||||
if (typeof component.hasAuthority === 'function') {
|
||||
return component.hasAuthority(context);
|
||||
}
|
||||
if (typeof component.checkAuthority === 'function') {
|
||||
return component.checkAuthority(context);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
priority: AUTHORITY_CONFIG.COMPONENT_CUSTOM_PRIORITY,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
// 实体所有者权限规则
|
||||
this.addRule({
|
||||
name: 'entity-owner-authority',
|
||||
check: (component, context) => {
|
||||
if (context.environment !== 'client') return false;
|
||||
|
||||
const entity = component.entity;
|
||||
if (!entity) return false;
|
||||
|
||||
// 检查实体的所有者信息
|
||||
if ((entity as INetworkEntity).ownerId && (entity as INetworkEntity).ownerId === context.clientId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
priority: AUTHORITY_CONFIG.ENTITY_OWNER_PRIORITY,
|
||||
enabled: true
|
||||
});
|
||||
|
||||
// 默认拒绝规则
|
||||
this.addRule({
|
||||
name: 'default-deny',
|
||||
check: () => false,
|
||||
priority: AUTHORITY_CONFIG.DEFAULT_DENY_PRIORITY,
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查组件权限
|
||||
*
|
||||
* @param component - 组件实例
|
||||
* @param clientId - 客户端ID(可选)
|
||||
* @returns 是否有权限
|
||||
*/
|
||||
public hasAuthority<T extends INetworkComponent>(component: T, clientId?: string): boolean {
|
||||
const context = this.createContext(component, clientId);
|
||||
const cacheKey = this.generateCacheKey(component, context);
|
||||
|
||||
// 检查缓存
|
||||
const cached = this._cache.get(cacheKey);
|
||||
if (cached && (Date.now() - cached.timestamp) < this._cacheTimeout) {
|
||||
return cached.result;
|
||||
}
|
||||
|
||||
// 执行权限检查
|
||||
const result = this.checkAuthority(component, context);
|
||||
|
||||
// 缓存结果
|
||||
this._cache.set(cacheKey, {
|
||||
result,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
logger.debug(`权限检查结果: ${component.constructor.name} -> ${result}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行权限检查
|
||||
*/
|
||||
private checkAuthority<T extends INetworkComponent>(component: T, context: CoreAuthorityContext): boolean {
|
||||
const enabledRules = this._rules
|
||||
.filter(rule => rule.enabled)
|
||||
.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
for (const rule of enabledRules) {
|
||||
try {
|
||||
const result = rule.check(component, context);
|
||||
if (result) {
|
||||
logger.debug(`权限规则 "${rule.name}" 通过`);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`权限规则 "${rule.name}" 执行失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug('所有权限规则都不匹配,拒绝权限');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建权限上下文
|
||||
*/
|
||||
private createContext<T extends INetworkComponent>(component: T, clientId?: string): CoreAuthorityContext {
|
||||
const networkIdentity = this.extractNetworkIdentity(component);
|
||||
const entity = component.entity;
|
||||
|
||||
return {
|
||||
environment: NetworkEnvironment.isServer ? 'server' : 'client' as NetworkEnvironmentType,
|
||||
networkId: networkIdentity?.networkId,
|
||||
entityId: entity?.id,
|
||||
clientId,
|
||||
level: CoreAuthorityType.ReadWrite,
|
||||
timestamp: Date.now(),
|
||||
metadata: {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取网络身份
|
||||
*/
|
||||
private extractNetworkIdentity<T extends INetworkComponent>(component: T): NetworkIdentity | null {
|
||||
try {
|
||||
// 直接检查组件的网络身份
|
||||
if ((component as any).networkIdentity instanceof NetworkIdentity) {
|
||||
return (component as any).networkIdentity;
|
||||
}
|
||||
|
||||
// 检查组件实体的网络身份
|
||||
if (component.entity) {
|
||||
const networkIdentity = (component.entity as any).getComponent?.(NetworkIdentity);
|
||||
if (networkIdentity) {
|
||||
return networkIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.debug('提取网络身份时出错:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缓存键
|
||||
*/
|
||||
private generateCacheKey<T extends INetworkComponent>(component: T, context: CoreAuthorityContext): string {
|
||||
const parts = [
|
||||
component.constructor.name,
|
||||
context.environment,
|
||||
context.networkId || 'no-net-id',
|
||||
context.clientId || 'no-client-id'
|
||||
];
|
||||
return parts.join('|');
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加权限规则
|
||||
*/
|
||||
public addRule(rule: AuthorityRule): void {
|
||||
this._rules.push(rule);
|
||||
this._rules.sort((a, b) => b.priority - a.priority);
|
||||
this.clearCache();
|
||||
logger.debug(`添加权限规则: ${rule.name} (优先级: ${rule.priority})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除权限规则
|
||||
*/
|
||||
public removeRule(name: string): boolean {
|
||||
const index = this._rules.findIndex(rule => rule.name === name);
|
||||
if (index !== -1) {
|
||||
this._rules.splice(index, 1);
|
||||
this.clearCache();
|
||||
logger.debug(`移除权限规则: ${name}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用权限规则
|
||||
*/
|
||||
public setRuleEnabled(name: string, enabled: boolean): boolean {
|
||||
const rule = this._rules.find(rule => rule.name === name);
|
||||
if (rule) {
|
||||
rule.enabled = enabled;
|
||||
this.clearCache();
|
||||
logger.debug(`${enabled ? '启用' : '禁用'}权限规则: ${name}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有权限规则
|
||||
*/
|
||||
public getRules(): AuthorityRule[] {
|
||||
return [...this._rules];
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除权限缓存
|
||||
*/
|
||||
public clearCache(): void {
|
||||
this._cache.clear();
|
||||
logger.debug('权限缓存已清除');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存超时时间
|
||||
*/
|
||||
public setCacheTimeout(timeout: number): void {
|
||||
this._cacheTimeout = timeout;
|
||||
logger.debug(`权限缓存超时时间设为: ${timeout}ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限检查统计
|
||||
*/
|
||||
public getStats(): {
|
||||
rulesCount: number;
|
||||
cacheSize: number;
|
||||
cacheTimeout: number;
|
||||
enabledRules: string[];
|
||||
} {
|
||||
return {
|
||||
rulesCount: this._rules.length,
|
||||
cacheSize: this._cache.size,
|
||||
cacheTimeout: this._cacheTimeout,
|
||||
enabledRules: this._rules
|
||||
.filter(rule => rule.enabled)
|
||||
.map(rule => rule.name)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,10 @@ import {
|
||||
NetworkComponentType,
|
||||
TypeGuards
|
||||
} from '../types/NetworkTypes';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { SYNCVAR_CONFIG } from '../constants/NetworkConstants';
|
||||
import { INetworkComponent } from '../types/CoreTypes';
|
||||
import { ComponentIdGenerator } from './ComponentIdGenerator';
|
||||
import { SyncVarAuthorityManager } from './SyncVarAuthority';
|
||||
|
||||
/**
|
||||
* SyncVar变化记录
|
||||
@@ -84,7 +87,12 @@ export interface SyncVarSyncData {
|
||||
*/
|
||||
export class SyncVarManager {
|
||||
private static _instance: SyncVarManager | null = null;
|
||||
private static readonly logger = createLogger('SyncVarManager');
|
||||
private static readonly logger = {
|
||||
info: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
};
|
||||
|
||||
/**
|
||||
* 组件实例的SyncVar变化监听器
|
||||
@@ -116,7 +124,7 @@ export class SyncVarManager {
|
||||
* @param component - 网络组件实例
|
||||
* @returns 是否成功初始化
|
||||
*/
|
||||
public initializeComponent(component: INetworkSyncable): boolean {
|
||||
public initializeComponent<T extends INetworkSyncable>(component: T): boolean {
|
||||
const componentId = this.getComponentId(component);
|
||||
const metadata = getSyncVarMetadata(component.constructor as NetworkComponentType);
|
||||
|
||||
@@ -152,7 +160,7 @@ export class SyncVarManager {
|
||||
*
|
||||
* @param component - 网络组件实例
|
||||
*/
|
||||
public cleanupComponent(component: INetworkSyncable): void {
|
||||
public cleanupComponent<T extends INetworkSyncable>(component: T): void {
|
||||
const componentId = this.getComponentId(component);
|
||||
this._componentChanges.delete(componentId);
|
||||
this._lastSyncTimes.delete(componentId);
|
||||
@@ -166,8 +174,8 @@ export class SyncVarManager {
|
||||
* @param oldValue - 旧值
|
||||
* @param newValue - 新值
|
||||
*/
|
||||
public recordChange(
|
||||
component: INetworkSyncable,
|
||||
public recordChange<T extends INetworkSyncable>(
|
||||
component: T,
|
||||
propertyKey: string,
|
||||
oldValue: SyncVarValue,
|
||||
newValue: SyncVarValue
|
||||
@@ -243,7 +251,7 @@ export class SyncVarManager {
|
||||
* @param component - 组件实例
|
||||
* @returns 待同步的变化数组
|
||||
*/
|
||||
public getPendingChanges(component: any): SyncVarChange[] {
|
||||
public getPendingChanges<T extends INetworkSyncable>(component: T): SyncVarChange[] {
|
||||
const componentId = this.getComponentId(component);
|
||||
const changes = this._componentChanges.get(componentId) || [];
|
||||
return changes.filter(change => change.needsSync);
|
||||
@@ -255,7 +263,7 @@ export class SyncVarManager {
|
||||
* @param component - 组件实例
|
||||
* @param propertyKeys - 要清除的属性名数组,如果不提供则清除所有
|
||||
*/
|
||||
public clearChanges(component: any, propertyKeys?: string[]): void {
|
||||
public clearChanges<T extends INetworkSyncable>(component: T, propertyKeys?: string[]): void {
|
||||
const componentId = this.getComponentId(component);
|
||||
const changes = this._componentChanges.get(componentId);
|
||||
|
||||
@@ -279,7 +287,7 @@ export class SyncVarManager {
|
||||
* @param component - 组件实例
|
||||
* @returns 同步数据
|
||||
*/
|
||||
public createSyncData(component: any): SyncVarSyncData | null {
|
||||
public createSyncData<T extends INetworkSyncable>(component: T): SyncVarSyncData | null {
|
||||
const pendingChanges = this.getPendingChanges(component);
|
||||
|
||||
if (pendingChanges.length === 0) {
|
||||
@@ -317,7 +325,7 @@ export class SyncVarManager {
|
||||
* @param component - 组件实例
|
||||
* @param syncData - 同步数据
|
||||
*/
|
||||
public applySyncData(component: any, syncData: SyncVarSyncData): void {
|
||||
public applySyncData<T extends INetworkSyncable>(component: T, syncData: SyncVarSyncData): void {
|
||||
const metadata = getSyncVarMetadata(component.constructor);
|
||||
const metadataMap = new Map(metadata.map(m => [m.fieldNumber, m]));
|
||||
|
||||
@@ -345,16 +353,31 @@ export class SyncVarManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ID生成器实例
|
||||
*/
|
||||
private static _idGenerator: ComponentIdGenerator | null = null;
|
||||
|
||||
/**
|
||||
* 获取ID生成器实例
|
||||
*/
|
||||
private static getIdGenerator(): ComponentIdGenerator {
|
||||
if (!SyncVarManager._idGenerator) {
|
||||
SyncVarManager._idGenerator = new ComponentIdGenerator();
|
||||
}
|
||||
return SyncVarManager._idGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成组件的唯一ID
|
||||
*
|
||||
* @param component - 组件实例
|
||||
* @returns 唯一ID
|
||||
*/
|
||||
private getComponentId(component: any): string {
|
||||
// 简单实现,将来可以集成网络ID系统
|
||||
private getComponentId<T extends INetworkSyncable>(component: T): string {
|
||||
if (!component._syncVarId) {
|
||||
component._syncVarId = `${component.constructor.name}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
const idGenerator = SyncVarManager.getIdGenerator();
|
||||
component._syncVarId = idGenerator.generateId(component);
|
||||
}
|
||||
return component._syncVarId;
|
||||
}
|
||||
@@ -366,7 +389,7 @@ export class SyncVarManager {
|
||||
* @param b - 值B
|
||||
* @returns 是否相等
|
||||
*/
|
||||
private isValueEqual(a: any, b: any): boolean {
|
||||
private isValueEqual(a: unknown, b: unknown): boolean {
|
||||
// 基础类型比较
|
||||
if (typeof a !== typeof b) {
|
||||
return false;
|
||||
@@ -378,39 +401,33 @@ export class SyncVarManager {
|
||||
|
||||
// 对象比较(浅比较)
|
||||
if (typeof a === 'object' && a !== null && b !== null) {
|
||||
const keysA = Object.keys(a);
|
||||
const keysB = Object.keys(b);
|
||||
const keysA = Object.keys(a as Record<string, unknown>);
|
||||
const keysB = Object.keys(b as Record<string, unknown>);
|
||||
|
||||
if (keysA.length !== keysB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return keysA.every(key => a[key] === b[key]);
|
||||
return keysA.every(key => (a as Record<string, unknown>)[key] === (b as Record<string, unknown>)[key]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限管理器实例
|
||||
*/
|
||||
private _authorityManager: SyncVarAuthorityManager = SyncVarAuthorityManager.Instance;
|
||||
|
||||
/**
|
||||
* 检查组件是否有修改权限
|
||||
*
|
||||
* @param component - 组件实例
|
||||
* @param clientId - 客户端ID(可选)
|
||||
* @returns 是否有权限
|
||||
*/
|
||||
private hasAuthority(component: any): boolean {
|
||||
// 简单实现:服务端始终有权限
|
||||
if (NetworkEnvironment.isServer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 客户端检查组件的权限设置
|
||||
// 如果组件有hasAuthority方法,使用它;否则默认客户端没有权限
|
||||
if (typeof component.hasAuthority === 'function') {
|
||||
return component.hasAuthority();
|
||||
}
|
||||
|
||||
// 默认情况下,客户端对权威字段没有权限
|
||||
return false;
|
||||
private hasAuthority<T extends INetworkSyncable>(component: T, clientId?: string): boolean {
|
||||
return this._authorityManager.hasAuthority(component as unknown as INetworkComponent, clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -420,7 +437,7 @@ export class SyncVarManager {
|
||||
* @param metadata - SyncVar元数据
|
||||
* @returns 是否应该同步
|
||||
*/
|
||||
private shouldSync(component: any, metadata: SyncVarMetadata): boolean {
|
||||
private shouldSync<T extends INetworkSyncable>(component: T, metadata: SyncVarMetadata): boolean {
|
||||
// 权限检查:权威字段只有在有权限时才同步
|
||||
if (metadata.options.authorityOnly && !this.hasAuthority(component)) {
|
||||
SyncVarManager.logger.debug(`字段 ${metadata.propertyKey} 是权威字段,但当前没有权限,跳过同步`);
|
||||
@@ -449,7 +466,7 @@ export class SyncVarManager {
|
||||
* @param oldValue - 旧值
|
||||
* @param newValue - 新值
|
||||
*/
|
||||
private triggerHook(component: any, metadata: SyncVarMetadata, oldValue: any, newValue: any): void {
|
||||
private triggerHook<T extends INetworkSyncable>(component: T, metadata: SyncVarMetadata, oldValue: unknown, newValue: unknown): void {
|
||||
if (!metadata.options.hook) {
|
||||
return;
|
||||
}
|
||||
@@ -472,7 +489,7 @@ export class SyncVarManager {
|
||||
* @param value - 值
|
||||
* @returns 序列化数据
|
||||
*/
|
||||
private serializeValue(component: any, propertyKey: string, value: any): Uint8Array {
|
||||
private serializeValue<T extends INetworkSyncable>(component: T, propertyKey: string, value: unknown): Uint8Array {
|
||||
const metadata = getSyncVarMetadataForProperty(component, propertyKey);
|
||||
|
||||
if (metadata?.options.serializer) {
|
||||
@@ -490,7 +507,7 @@ export class SyncVarManager {
|
||||
* @param data - 序列化数据
|
||||
* @returns 反序列化的值
|
||||
*/
|
||||
private deserializeValue(component: any, propertyKey: string, data: Uint8Array): any {
|
||||
private deserializeValue<T extends INetworkSyncable>(component: T, propertyKey: string, data: Uint8Array): unknown {
|
||||
const metadata = getSyncVarMetadataForProperty(component, propertyKey);
|
||||
|
||||
if (metadata?.options.deserializer) {
|
||||
@@ -503,7 +520,7 @@ export class SyncVarManager {
|
||||
/**
|
||||
* 将值序列化为二进制数据
|
||||
*/
|
||||
private serializeValueToBinary(value: any): Uint8Array {
|
||||
private serializeValueToBinary(value: unknown): Uint8Array {
|
||||
if (value === null || value === undefined) {
|
||||
return new Uint8Array([0]);
|
||||
}
|
||||
@@ -543,7 +560,7 @@ export class SyncVarManager {
|
||||
/**
|
||||
* 从二进制数据反序列化值
|
||||
*/
|
||||
private deserializeValueFromBinary(data: Uint8Array): any {
|
||||
private deserializeValueFromBinary(data: Uint8Array): unknown {
|
||||
if (data.length === 0) return null;
|
||||
|
||||
const view = new DataView(data.buffer, data.byteOffset);
|
||||
@@ -574,13 +591,13 @@ export class SyncVarManager {
|
||||
* @param propertyKey - 属性名
|
||||
* @param value - 值
|
||||
*/
|
||||
private setValueDirectly(component: any, propertyKey: string, value: any): void {
|
||||
private setValueDirectly<T extends INetworkSyncable>(component: T, propertyKey: string, value: unknown): void {
|
||||
// 临时禁用代理监听
|
||||
const originalValue = component._syncVarDisabled;
|
||||
component._syncVarDisabled = true;
|
||||
|
||||
try {
|
||||
component[propertyKey] = value;
|
||||
(component as Record<string, unknown>)[propertyKey] = value;
|
||||
} finally {
|
||||
component._syncVarDisabled = originalValue;
|
||||
}
|
||||
@@ -596,8 +613,8 @@ export class SyncVarManager {
|
||||
* @param isFullSync - 是否是完整同步
|
||||
* @returns SyncVar更新消息,如果没有待同步的变化则返回null
|
||||
*/
|
||||
public createSyncVarUpdateMessage(
|
||||
component: any,
|
||||
public createSyncVarUpdateMessage<T extends INetworkSyncable>(
|
||||
component: T,
|
||||
networkId: string = '',
|
||||
senderId: string = '',
|
||||
syncSequence: number = 0,
|
||||
@@ -653,7 +670,7 @@ export class SyncVarManager {
|
||||
* @param component - 组件实例
|
||||
* @param message - SyncVar更新消息
|
||||
*/
|
||||
public applySyncVarUpdateMessage(component: any, message: SyncVarUpdateMessage): void {
|
||||
public applySyncVarUpdateMessage<T extends INetworkSyncable>(component: T, message: SyncVarUpdateMessage): void {
|
||||
if (message.componentType !== component.constructor.name) {
|
||||
SyncVarManager.logger.warn(`组件类型不匹配: 期望 ${component.constructor.name}, 收到 ${message.componentType}`);
|
||||
return;
|
||||
@@ -705,8 +722,8 @@ export class SyncVarManager {
|
||||
* @param syncSequence - 同步序号
|
||||
* @returns SyncVar更新消息数组
|
||||
*/
|
||||
public createBatchSyncVarUpdateMessages(
|
||||
components: any[],
|
||||
public createBatchSyncVarUpdateMessages<T extends INetworkSyncable>(
|
||||
components: T[],
|
||||
networkIds: string[] = [],
|
||||
senderId: string = '',
|
||||
syncSequence: number = 0
|
||||
@@ -738,7 +755,7 @@ export class SyncVarManager {
|
||||
* @param components - 组件数组
|
||||
* @returns 有待同步变化的组件数组
|
||||
*/
|
||||
public filterComponentsWithChanges(components: any[]): any[] {
|
||||
public filterComponentsWithChanges<T extends INetworkSyncable>(components: T[]): T[] {
|
||||
return components.filter(component => {
|
||||
const pendingChanges = this.getPendingChanges(component);
|
||||
return pendingChanges.length > 0;
|
||||
@@ -751,7 +768,7 @@ export class SyncVarManager {
|
||||
* @param component - 组件实例
|
||||
* @returns 变化统计信息
|
||||
*/
|
||||
public getComponentChangeStats(component: any): {
|
||||
public getComponentChangeStats<T extends INetworkSyncable>(component: T): {
|
||||
totalChanges: number;
|
||||
pendingChanges: number;
|
||||
lastChangeTime: number;
|
||||
|
||||
@@ -475,4 +475,211 @@ export class SyncVarSyncScheduler {
|
||||
public get isRunning(): boolean {
|
||||
return this._isRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能调度配置
|
||||
* 根据网络状况动态调整同步策略
|
||||
*/
|
||||
public applyNetworkAdaptiveConfig(adaptiveConfig: {
|
||||
suggestedSyncInterval: number;
|
||||
suggestedBatchSize: number;
|
||||
prioritizeUpdate: boolean;
|
||||
}): void {
|
||||
const oldConfig = { ...this._config };
|
||||
|
||||
// 应用建议的同步间隔,但不超出安全范围
|
||||
this._config.syncInterval = Math.max(
|
||||
Math.min(adaptiveConfig.suggestedSyncInterval, 1000), // 最大1秒
|
||||
this._config.minSyncInterval // 最小间隔
|
||||
);
|
||||
|
||||
// 应用建议的批处理大小
|
||||
this._config.maxBatchSize = Math.max(
|
||||
Math.min(adaptiveConfig.suggestedBatchSize, 100), // 最大100
|
||||
1 // 最小1
|
||||
);
|
||||
|
||||
// 根据优先级更新标志调整其他参数
|
||||
if (adaptiveConfig.prioritizeUpdate) {
|
||||
// 高优先级模式:减少每帧处理对象数,确保重要更新及时处理
|
||||
this._config.maxObjectsPerFrame = Math.max(
|
||||
Math.floor(this._config.maxObjectsPerFrame * 0.7),
|
||||
10
|
||||
);
|
||||
this._config.enablePrioritySort = true;
|
||||
} else {
|
||||
// 正常模式:恢复标准设置
|
||||
this._config.maxObjectsPerFrame = 50;
|
||||
}
|
||||
|
||||
SyncVarSyncScheduler.logger.info('应用网络自适应配置:', {
|
||||
oldInterval: oldConfig.syncInterval,
|
||||
newInterval: this._config.syncInterval,
|
||||
oldBatchSize: oldConfig.maxBatchSize,
|
||||
newBatchSize: this._config.maxBatchSize,
|
||||
prioritizeMode: adaptiveConfig.prioritizeUpdate
|
||||
});
|
||||
|
||||
// 重启调度器以应用新配置
|
||||
if (this._isRunning) {
|
||||
this.stop();
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络拥塞控制
|
||||
* 根据拥塞状态调整同步行为
|
||||
*/
|
||||
public applyCongestionControl(congestionInfo: {
|
||||
isCongested: boolean;
|
||||
congestionLevel: 'none' | 'light' | 'moderate' | 'severe';
|
||||
}): void {
|
||||
if (!congestionInfo.isCongested) {
|
||||
// 无拥塞:可以恢复正常配置
|
||||
this.resetToOptimalConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
const emergencyConfig = { ...this._config };
|
||||
|
||||
switch (congestionInfo.congestionLevel) {
|
||||
case 'light':
|
||||
// 轻微拥塞:适度降低频率
|
||||
emergencyConfig.syncInterval = Math.max(this._config.syncInterval * 1.2, 80);
|
||||
emergencyConfig.maxBatchSize = Math.min(this._config.maxBatchSize * 1.5, 15);
|
||||
break;
|
||||
|
||||
case 'moderate':
|
||||
// 中度拥塞:显著降低频率,增加批处理
|
||||
emergencyConfig.syncInterval = Math.max(this._config.syncInterval * 1.5, 120);
|
||||
emergencyConfig.maxBatchSize = Math.min(this._config.maxBatchSize * 2, 25);
|
||||
emergencyConfig.maxObjectsPerFrame = Math.max(
|
||||
Math.floor(this._config.maxObjectsPerFrame * 0.6),
|
||||
15
|
||||
);
|
||||
break;
|
||||
|
||||
case 'severe':
|
||||
// 严重拥塞:最保守策略
|
||||
emergencyConfig.syncInterval = Math.max(this._config.syncInterval * 2, 200);
|
||||
emergencyConfig.maxBatchSize = Math.min(this._config.maxBatchSize * 3, 50);
|
||||
emergencyConfig.maxObjectsPerFrame = Math.max(
|
||||
Math.floor(this._config.maxObjectsPerFrame * 0.4),
|
||||
10
|
||||
);
|
||||
emergencyConfig.enablePrioritySort = true; // 强制启用优先级排序
|
||||
break;
|
||||
}
|
||||
|
||||
this._config = emergencyConfig;
|
||||
|
||||
SyncVarSyncScheduler.logger.warn(`应用拥塞控制策略 [${congestionInfo.congestionLevel}]:`, {
|
||||
syncInterval: this._config.syncInterval,
|
||||
maxBatchSize: this._config.maxBatchSize,
|
||||
maxObjectsPerFrame: this._config.maxObjectsPerFrame
|
||||
});
|
||||
|
||||
// 重启调度器以应用拥塞控制配置
|
||||
if (this._isRunning) {
|
||||
this.stop();
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置到优化配置
|
||||
* 当网络状况改善时调用
|
||||
*/
|
||||
private resetToOptimalConfig(): void {
|
||||
const optimalConfig: SyncVarSyncConfig = {
|
||||
syncInterval: 50, // 20fps
|
||||
maxBatchSize: 10,
|
||||
maxObjectsPerFrame: 50,
|
||||
enablePrioritySort: true,
|
||||
minSyncInterval: 16, // 最小16ms (60fps)
|
||||
enableIncrementalSync: true
|
||||
};
|
||||
|
||||
const configChanged = (
|
||||
this._config.syncInterval !== optimalConfig.syncInterval ||
|
||||
this._config.maxBatchSize !== optimalConfig.maxBatchSize ||
|
||||
this._config.maxObjectsPerFrame !== optimalConfig.maxObjectsPerFrame
|
||||
);
|
||||
|
||||
if (configChanged) {
|
||||
this._config = optimalConfig;
|
||||
SyncVarSyncScheduler.logger.info('恢复到优化配置');
|
||||
|
||||
if (this._isRunning) {
|
||||
this.stop();
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能批处理优化
|
||||
* 基于网络状况和组件变化频率动态调整批处理策略
|
||||
*/
|
||||
public optimizeBatching(): void {
|
||||
// 分析最近的同步统计
|
||||
const recentSuccessRate = this.calculateRecentSuccessRate();
|
||||
const avgProcessingTime = this._stats.averageCycleTime;
|
||||
|
||||
// 根据成功率调整批处理大小
|
||||
if (recentSuccessRate < 0.8) {
|
||||
// 成功率低,减少批处理大小
|
||||
this._config.maxBatchSize = Math.max(
|
||||
Math.floor(this._config.maxBatchSize * 0.8),
|
||||
2
|
||||
);
|
||||
SyncVarSyncScheduler.logger.debug(`批处理优化: 降低批大小到 ${this._config.maxBatchSize} (成功率: ${recentSuccessRate.toFixed(2)})`);
|
||||
} else if (recentSuccessRate > 0.95 && avgProcessingTime < this._config.syncInterval * 0.5) {
|
||||
// 成功率高且处理时间充足,可以增加批处理大小
|
||||
this._config.maxBatchSize = Math.min(
|
||||
Math.floor(this._config.maxBatchSize * 1.1),
|
||||
30
|
||||
);
|
||||
SyncVarSyncScheduler.logger.debug(`批处理优化: 提升批大小到 ${this._config.maxBatchSize}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算最近的成功率
|
||||
* 这里简化实现,实际可以基于更复杂的统计
|
||||
*/
|
||||
private calculateRecentSuccessRate(): number {
|
||||
// 简化实现:基于错误率计算成功率
|
||||
const totalOperations = this._stats.totalSyncCycles;
|
||||
if (totalOperations === 0) return 1.0;
|
||||
|
||||
const errorRate = this._stats.errors / totalOperations;
|
||||
return Math.max(0, 1 - errorRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态优先级调整
|
||||
* 根据组件活跃度和网络状况调整优先级计算
|
||||
*/
|
||||
public updatePriorityStrategy(networkQuality: number): void {
|
||||
if (networkQuality >= 80) {
|
||||
// 高质量网络:可以使用复杂的优先级计算
|
||||
this._priorityCalculator = new DefaultSyncPriorityCalculator();
|
||||
} else {
|
||||
// 低质量网络:使用简化的优先级计算,减少CPU开销
|
||||
this._priorityCalculator = new SimplifiedPriorityCalculator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化优先级计算器
|
||||
* 在网络状况不佳时使用,减少计算开销
|
||||
*/
|
||||
class SimplifiedPriorityCalculator implements ISyncPriorityCalculator {
|
||||
public calculatePriority(component: any, identity: NetworkIdentity): number {
|
||||
// 简化的优先级计算:仅基于权威性
|
||||
return identity.hasAuthority ? 10 : 5;
|
||||
}
|
||||
}
|
||||
@@ -38,4 +38,5 @@ export {
|
||||
} from './SyncVarOptimizer';
|
||||
export type {
|
||||
SyncVarOptimizationConfig
|
||||
} from './SyncVarOptimizer';
|
||||
} from './SyncVarOptimizer';
|
||||
|
||||
|
||||
383
packages/network/src/TSRPC/TsrpcClient.ts
Normal file
383
packages/network/src/TSRPC/TsrpcClient.ts
Normal file
@@ -0,0 +1,383 @@
|
||||
/**
|
||||
* TSRPC 客户端
|
||||
*/
|
||||
import { WsClient } from 'tsrpc';
|
||||
import { ServiceType, serviceProto } from './protocols/serviceProto';
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { getTsrpcMetadata } from '../Serialization/TsrpcDecorators';
|
||||
import { INetworkSyncable, MessageData } from '../types/NetworkTypes';
|
||||
import { ConnectionState, ITypedEventEmitter, NetworkEventHandlers } from '../types/CoreTypes';
|
||||
import { TSRPC_CONFIG, NETWORK_CONFIG } from '../constants/NetworkConstants';
|
||||
|
||||
const logger = {
|
||||
info: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
debug: console.debug
|
||||
};
|
||||
|
||||
export class TsrpcNetworkClient {
|
||||
private _client: WsClient<ServiceType>;
|
||||
private _playerId?: number;
|
||||
private _roomId?: string;
|
||||
private _componentUpdateCallbacks = new Map<string, (entityId: number, data: MessageData) => void>();
|
||||
private _connectionState: ConnectionState = 'disconnected';
|
||||
private _reconnectAttempts: number = 0;
|
||||
private _maxReconnectAttempts: number = NETWORK_CONFIG.DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
||||
private _reconnectDelay: number = NETWORK_CONFIG.DEFAULT_RECONNECT_DELAY;
|
||||
private _lastPingTime: number = 0;
|
||||
private _rtt: number = 0;
|
||||
private _eventHandlers = new Map<keyof NetworkEventHandlers, NetworkEventHandlers[keyof NetworkEventHandlers][]>();
|
||||
|
||||
constructor(serverUrl: string = TSRPC_CONFIG.DEFAULT_SERVER_URL) {
|
||||
this._client = new WsClient(serviceProto, {
|
||||
server: serverUrl,
|
||||
// JSON兼容模式
|
||||
json: false,
|
||||
// 自动重连
|
||||
heartbeat: {
|
||||
interval: TSRPC_CONFIG.DEFAULT_HEARTBEAT.interval,
|
||||
timeout: TSRPC_CONFIG.DEFAULT_HEARTBEAT.timeout
|
||||
}
|
||||
});
|
||||
|
||||
this.setupMessageHandlers();
|
||||
this.setupEvents();
|
||||
}
|
||||
|
||||
private setupMessageHandlers() {
|
||||
// 监听组件更新消息
|
||||
this._client.listenMsg('ComponentUpdate', (msg) => {
|
||||
const { entityId, componentType, componentData, timestamp } = msg;
|
||||
|
||||
logger.debug(`收到组件更新: Entity ${entityId}, Component ${componentType}`);
|
||||
|
||||
const callback = this._componentUpdateCallbacks.get(componentType);
|
||||
if (callback) {
|
||||
callback(entityId, componentData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setupEvents() {
|
||||
// 连接成功
|
||||
this._client.flows.postConnectFlow.push((conn) => {
|
||||
this._connectionState = 'connected';
|
||||
this._reconnectAttempts = 0;
|
||||
logger.info('已连接到TSRPC服务器');
|
||||
this.emit('connected');
|
||||
return conn;
|
||||
});
|
||||
|
||||
// 连接断开
|
||||
this._client.flows.postDisconnectFlow.push((data) => {
|
||||
this._connectionState = 'disconnected';
|
||||
logger.info('与TSRPC服务器断开连接');
|
||||
this.emit('disconnected', data?.reason || 'Unknown disconnect reason');
|
||||
|
||||
// 自动重连
|
||||
if (this._reconnectAttempts < this._maxReconnectAttempts) {
|
||||
this.attemptReconnect();
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到服务器
|
||||
*/
|
||||
async connect(): Promise<boolean> {
|
||||
this._connectionState = 'connecting';
|
||||
this.emit('connecting');
|
||||
|
||||
const result = await this._client.connect();
|
||||
if (result.isSucc) {
|
||||
this._connectionState = 'connected';
|
||||
return true;
|
||||
} else {
|
||||
this._connectionState = 'disconnected';
|
||||
this.emit('connectError', new Error(result.errMsg || 'Connection failed'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
disconnect() {
|
||||
this._client.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入房间
|
||||
*/
|
||||
async joinRoom(roomId: string, playerName?: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await this._client.callApi('JoinRoom', {
|
||||
roomId,
|
||||
playerName
|
||||
});
|
||||
|
||||
if (result.isSucc && result.res.success) {
|
||||
this._playerId = result.res.playerId;
|
||||
this._roomId = roomId;
|
||||
logger.info(`成功加入房间 ${roomId}, 玩家ID: ${this._playerId}`);
|
||||
return true;
|
||||
} else {
|
||||
logger.error('加入房间失败:', result.isSucc ? result.res.errorMsg : result.err);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('加入房间异常:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步组件到服务器
|
||||
*/
|
||||
async syncComponent<T extends INetworkSyncable>(entityId: number, component: T): Promise<boolean> {
|
||||
try {
|
||||
// 获取组件的TSRPC元数据
|
||||
const metadata = getTsrpcMetadata(component.constructor);
|
||||
if (!metadata) {
|
||||
logger.error(`组件 ${component.constructor.name} 不支持TSRPC同步`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 提取组件数据
|
||||
const componentData: Record<string, unknown> = {};
|
||||
for (const [fieldName] of metadata.fields) {
|
||||
componentData[fieldName] = component[fieldName];
|
||||
}
|
||||
|
||||
const result = await this._client.callApi('SyncComponent', {
|
||||
entityId,
|
||||
componentType: metadata.componentType,
|
||||
componentData,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
if (result.isSucc && result.res.success) {
|
||||
logger.debug(`组件同步成功: Entity ${entityId}, Component ${metadata.componentType}`);
|
||||
return true;
|
||||
} else {
|
||||
logger.error('组件同步失败:', result.isSucc ? result.res.errorMsg : result.err);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('同步组件异常:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册组件更新回调
|
||||
*/
|
||||
onComponentUpdate(componentType: string, callback: (entityId: number, data: MessageData) => void) {
|
||||
this._componentUpdateCallbacks.set(componentType, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消组件更新回调
|
||||
*/
|
||||
offComponentUpdate(componentType: string) {
|
||||
this._componentUpdateCallbacks.delete(componentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端状态
|
||||
*/
|
||||
get isConnected(): boolean {
|
||||
return this._client.isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取玩家ID
|
||||
*/
|
||||
get playerId(): number | undefined {
|
||||
return this._playerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间ID
|
||||
*/
|
||||
get roomId(): string | undefined {
|
||||
return this._roomId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端实例
|
||||
*/
|
||||
get client(): WsClient<ServiceType> {
|
||||
return this._client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试重连
|
||||
*/
|
||||
private async attemptReconnect(): Promise<void> {
|
||||
this._reconnectAttempts++;
|
||||
const delay = Math.min(this._reconnectDelay * Math.pow(2, this._reconnectAttempts - 1), 30000);
|
||||
|
||||
logger.info(`尝试重连 (${this._reconnectAttempts}/${this._maxReconnectAttempts}),${delay}ms后重试`);
|
||||
this.emit('reconnecting', this._reconnectAttempts);
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const success = await this.connect();
|
||||
if (success) {
|
||||
logger.info('重连成功');
|
||||
this.emit('reconnected');
|
||||
} else if (this._reconnectAttempts < this._maxReconnectAttempts) {
|
||||
await this.attemptReconnect();
|
||||
} else {
|
||||
logger.error('重连失败,已达到最大重试次数');
|
||||
this.emit('reconnectFailed');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('重连异常:', error);
|
||||
if (this._reconnectAttempts < this._maxReconnectAttempts) {
|
||||
await this.attemptReconnect();
|
||||
}
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping服务器测试连接
|
||||
*/
|
||||
public async ping(): Promise<number> {
|
||||
try {
|
||||
this._lastPingTime = Date.now();
|
||||
const result = await this._client.callApi('Ping', {
|
||||
timestamp: this._lastPingTime
|
||||
});
|
||||
|
||||
if (result.isSucc) {
|
||||
this._rtt = Date.now() - this._lastPingTime;
|
||||
return this._rtt;
|
||||
} else {
|
||||
throw new Error(result.err?.message || 'Ping失败');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Ping服务器失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取RTT
|
||||
*/
|
||||
public getRtt(): number {
|
||||
return this._rtt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
public getConnectionState(): ConnectionState {
|
||||
return this._connectionState;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置重连配置
|
||||
*/
|
||||
public setReconnectConfig(maxAttempts: number, delay: number): void {
|
||||
this._maxReconnectAttempts = maxAttempts;
|
||||
this._reconnectDelay = delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
*/
|
||||
public on<K extends keyof NetworkEventHandlers>(event: K, handler: NetworkEventHandlers[K]): void {
|
||||
if (!this._eventHandlers.has(event)) {
|
||||
this._eventHandlers.set(event, []);
|
||||
}
|
||||
this._eventHandlers.get(event)!.push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
*/
|
||||
public off<K extends keyof NetworkEventHandlers>(event: K, handler: NetworkEventHandlers[K]): void {
|
||||
const handlers = this._eventHandlers.get(event);
|
||||
if (handlers) {
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index !== -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
*/
|
||||
private emit<K extends keyof NetworkEventHandlers>(event: K, ...args: Parameters<NetworkEventHandlers[K]>): void {
|
||||
const handlers = this._eventHandlers.get(event);
|
||||
if (handlers) {
|
||||
handlers.forEach(handler => {
|
||||
try {
|
||||
(handler as (...args: Parameters<NetworkEventHandlers[K]>) => void)(...args);
|
||||
} catch (error) {
|
||||
logger.error(`事件处理器错误 (${event}):`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量同步组件
|
||||
*/
|
||||
public async syncComponents<T extends INetworkSyncable>(updates: Array<{
|
||||
entityId: number;
|
||||
component: T;
|
||||
}>): Promise<{ success: number; failed: number }> {
|
||||
let successCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
const promises = updates.map(async ({ entityId, component }) => {
|
||||
try {
|
||||
const success = await this.syncComponent(entityId, component);
|
||||
if (success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failedCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`批量同步组件失败 Entity ${entityId}:`, error);
|
||||
failedCount++;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
logger.debug(`批量同步完成: 成功 ${successCount}, 失败 ${failedCount}`);
|
||||
return { success: successCount, failed: failedCount };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
connectionState: string;
|
||||
playerId?: number;
|
||||
roomId?: string;
|
||||
rtt: number;
|
||||
reconnectAttempts: number;
|
||||
maxReconnectAttempts: number;
|
||||
componentCallbacks: number;
|
||||
} {
|
||||
return {
|
||||
connectionState: this._connectionState,
|
||||
playerId: this._playerId,
|
||||
roomId: this._roomId,
|
||||
rtt: this._rtt,
|
||||
reconnectAttempts: this._reconnectAttempts,
|
||||
maxReconnectAttempts: this._maxReconnectAttempts,
|
||||
componentCallbacks: this._componentUpdateCallbacks.size
|
||||
};
|
||||
}
|
||||
}
|
||||
129
packages/network/src/TSRPC/TsrpcManager.ts
Normal file
129
packages/network/src/TSRPC/TsrpcManager.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* TSRPC 网络管理器
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { TsrpcNetworkServer } from './TsrpcServer';
|
||||
import { TsrpcNetworkClient } from './TsrpcClient';
|
||||
|
||||
const logger = createLogger('TsrpcManager');
|
||||
|
||||
export enum NetworkMode {
|
||||
Server = 'server',
|
||||
Client = 'client'
|
||||
}
|
||||
|
||||
export class TsrpcManager {
|
||||
private static _instance: TsrpcManager | null = null;
|
||||
private _server: TsrpcNetworkServer | null = null;
|
||||
private _client: TsrpcNetworkClient | null = null;
|
||||
private _mode: NetworkMode | null = null;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): TsrpcManager {
|
||||
if (!TsrpcManager._instance) {
|
||||
TsrpcManager._instance = new TsrpcManager();
|
||||
}
|
||||
return TsrpcManager._instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化为服务器模式
|
||||
*/
|
||||
async initAsServer(port: number = 3000): Promise<void> {
|
||||
if (this._mode !== null) {
|
||||
throw new Error('TSRPC管理器已经初始化');
|
||||
}
|
||||
|
||||
this._mode = NetworkMode.Server;
|
||||
this._server = new TsrpcNetworkServer(port);
|
||||
await this._server.start();
|
||||
|
||||
logger.info(`TSRPC管理器已初始化为服务器模式,端口: ${port}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化为客户端模式
|
||||
*/
|
||||
async initAsClient(serverUrl: string = 'ws://localhost:3000'): Promise<void> {
|
||||
if (this._mode !== null) {
|
||||
throw new Error('TSRPC管理器已经初始化');
|
||||
}
|
||||
|
||||
this._mode = NetworkMode.Client;
|
||||
this._client = new TsrpcNetworkClient(serverUrl);
|
||||
const connected = await this._client.connect();
|
||||
|
||||
if (connected) {
|
||||
logger.info(`TSRPC管理器已初始化为客户端模式,服务器: ${serverUrl}`);
|
||||
} else {
|
||||
throw new Error('无法连接到TSRPC服务器');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭网络连接
|
||||
*/
|
||||
async shutdown(): Promise<void> {
|
||||
if (this._server) {
|
||||
await this._server.stop();
|
||||
this._server = null;
|
||||
}
|
||||
|
||||
if (this._client) {
|
||||
this._client.disconnect();
|
||||
this._client = null;
|
||||
}
|
||||
|
||||
this._mode = null;
|
||||
logger.info('TSRPC管理器已关闭');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器实例(仅服务器模式)
|
||||
*/
|
||||
get server(): TsrpcNetworkServer {
|
||||
if (!this._server) {
|
||||
throw new Error('未初始化为服务器模式或服务器未启动');
|
||||
}
|
||||
return this._server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端实例(仅客户端模式)
|
||||
*/
|
||||
get client(): TsrpcNetworkClient {
|
||||
if (!this._client) {
|
||||
throw new Error('未初始化为客户端模式或客户端未连接');
|
||||
}
|
||||
return this._client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前模式
|
||||
*/
|
||||
get mode(): NetworkMode | null {
|
||||
return this._mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为服务器模式
|
||||
*/
|
||||
get isServer(): boolean {
|
||||
return this._mode === NetworkMode.Server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为客户端模式
|
||||
*/
|
||||
get isClient(): boolean {
|
||||
return this._mode === NetworkMode.Client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已初始化
|
||||
*/
|
||||
get isInitialized(): boolean {
|
||||
return this._mode !== null;
|
||||
}
|
||||
}
|
||||
698
packages/network/src/TSRPC/TsrpcServer.ts
Normal file
698
packages/network/src/TSRPC/TsrpcServer.ts
Normal file
@@ -0,0 +1,698 @@
|
||||
/**
|
||||
* TSRPC 服务器
|
||||
*/
|
||||
import { WsServer } from 'tsrpc';
|
||||
import { ServiceType, serviceProto } from './protocols/serviceProto';
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
|
||||
const logger = createLogger('TsrpcServer');
|
||||
|
||||
export class TsrpcNetworkServer {
|
||||
private _server: WsServer<ServiceType>;
|
||||
private _connectedClients = new Map<string, any>();
|
||||
private _isRunning: boolean = false;
|
||||
private _startTime: number = 0;
|
||||
private _totalConnections: number = 0;
|
||||
private _maxConcurrentConnections: number = 0;
|
||||
private _eventHandlers: Map<string, Function[]> = new Map();
|
||||
private _performanceStats = {
|
||||
totalMessages: 0,
|
||||
totalBytesTransferred: 0,
|
||||
averageResponseTime: 0,
|
||||
peakConnections: 0,
|
||||
uptime: 0
|
||||
};
|
||||
|
||||
constructor(port: number = 3000) {
|
||||
this._server = new WsServer(serviceProto, {
|
||||
port: port,
|
||||
// JSON兼容模式
|
||||
json: false,
|
||||
// 连接日志
|
||||
logConnect: true
|
||||
});
|
||||
|
||||
this.setupApi();
|
||||
this.setupEvents();
|
||||
}
|
||||
|
||||
private setupApi() {
|
||||
// 实现同步组件API
|
||||
this._server.implementApi('SyncComponent', async (call) => {
|
||||
try {
|
||||
const { entityId, componentType, componentData, timestamp } = call.req;
|
||||
|
||||
logger.debug(`收到组件同步: Entity ${entityId}, Component ${componentType}`);
|
||||
|
||||
// 广播组件更新给其他客户端
|
||||
this.broadcastComponentUpdate(entityId, componentType, componentData, timestamp, call.conn.id);
|
||||
|
||||
call.succ({
|
||||
success: true,
|
||||
entityId
|
||||
});
|
||||
} catch (error) {
|
||||
call.error('同步组件失败', { error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
// 实现加入房间API
|
||||
this._server.implementApi('JoinRoom', async (call) => {
|
||||
try {
|
||||
const { roomId, playerName, password } = call.req;
|
||||
|
||||
// 检查房间是否存在,如果不存在则自动创建
|
||||
let room = this._rooms.get(roomId);
|
||||
if (!room) {
|
||||
this.createRoom(roomId, { maxPlayers: 10 });
|
||||
room = this._rooms.get(roomId)!;
|
||||
}
|
||||
|
||||
// 检查房间是否已满
|
||||
if (room.currentPlayers >= room.maxPlayers) {
|
||||
call.succ({
|
||||
success: false,
|
||||
errorMsg: '房间已满'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查密码
|
||||
if (room.password && room.password !== password) {
|
||||
call.succ({
|
||||
success: false,
|
||||
errorMsg: '密码错误'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const playerId = Date.now() + Math.floor(Math.random() * 1000); // 更好的ID生成
|
||||
|
||||
// 添加玩家到房间
|
||||
const playerInfo: PlayerInfo = {
|
||||
playerId,
|
||||
playerName,
|
||||
joinedAt: Date.now()
|
||||
};
|
||||
room.players.set(call.conn.id, playerInfo);
|
||||
room.currentPlayers++;
|
||||
|
||||
// 记录连接
|
||||
this._connectedClients.set(call.conn.id, {
|
||||
playerId,
|
||||
roomId,
|
||||
playerName,
|
||||
connId: call.conn.id
|
||||
});
|
||||
|
||||
logger.info(`玩家 ${playerName} (ID: ${playerId}) 加入房间 ${roomId} (${room.currentPlayers}/${room.maxPlayers})`);
|
||||
|
||||
// 通知房间内其他玩家
|
||||
this.broadcastToRoom(roomId, 'PlayerJoined', {
|
||||
playerId,
|
||||
playerName,
|
||||
timestamp: Date.now()
|
||||
}, call.conn.id);
|
||||
|
||||
call.succ({
|
||||
success: true,
|
||||
playerId,
|
||||
roomInfo: {
|
||||
roomId,
|
||||
playerCount: room.currentPlayers,
|
||||
maxPlayers: room.maxPlayers,
|
||||
metadata: room.metadata
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
call.error('加入房间失败', { error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setupEvents() {
|
||||
// 连接建立
|
||||
this._server.flows.postConnectFlow.push((conn) => {
|
||||
logger.info(`客户端连接: ${conn.id}`);
|
||||
return conn;
|
||||
});
|
||||
|
||||
// 连接断开
|
||||
this._server.flows.postDisconnectFlow.push((data) => {
|
||||
const client = this._connectedClients.get(data.conn.id);
|
||||
if (client) {
|
||||
logger.info(`客户端断开: ${client.playerName} (${data.conn.id})`);
|
||||
|
||||
// 从房间中移除玩家
|
||||
const room = this._rooms.get(client.roomId);
|
||||
if (room) {
|
||||
room.players.delete(data.conn.id);
|
||||
room.currentPlayers--;
|
||||
|
||||
// 通知房间内其他玩家
|
||||
this.broadcastToRoom(client.roomId, 'PlayerLeft', {
|
||||
playerId: client.playerId,
|
||||
playerName: client.playerName,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
logger.info(`玩家 ${client.playerName} 离开房间 ${client.roomId} (${room.currentPlayers}/${room.maxPlayers})`);
|
||||
|
||||
// 如果房间空了,可选择性删除房间
|
||||
if (room.currentPlayers === 0) {
|
||||
logger.info(`房间 ${client.roomId} 已空,保留等待新玩家`);
|
||||
// 可以选择删除空房间: this._rooms.delete(client.roomId);
|
||||
}
|
||||
}
|
||||
|
||||
this._connectedClients.delete(data.conn.id);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播组件更新给其他客户端
|
||||
*/
|
||||
private broadcastComponentUpdate(entityId: number, componentType: string, componentData: any, timestamp: number, excludeConnId?: string) {
|
||||
const updateMsg = {
|
||||
entityId,
|
||||
componentType,
|
||||
componentData,
|
||||
timestamp
|
||||
};
|
||||
|
||||
// 给所有其他客户端发送更新消息
|
||||
const targetConns = this._server.connections.filter(conn => conn.id !== excludeConnId);
|
||||
this._server.broadcastMsg('ComponentUpdate', updateMsg, targetConns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动服务器
|
||||
*/
|
||||
async start(): Promise<void> {
|
||||
await this._server.start();
|
||||
logger.info(`TSRPC服务器已启动,端口: ${this._server.options.port}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止服务器
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
await this._server.stop();
|
||||
logger.info('TSRPC服务器已停止');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接的客户端数量
|
||||
*/
|
||||
get clientCount(): number {
|
||||
return this._connectedClients.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器实例
|
||||
*/
|
||||
get server(): WsServer<ServiceType> {
|
||||
return this._server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高级房间管理功能
|
||||
*/
|
||||
|
||||
/**
|
||||
* 创建房间
|
||||
*/
|
||||
public createRoom(roomId: string, options?: {
|
||||
maxPlayers?: number;
|
||||
password?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}): boolean {
|
||||
if (this._rooms.has(roomId)) {
|
||||
return false; // 房间已存在
|
||||
}
|
||||
|
||||
const room: RoomInfo = {
|
||||
roomId,
|
||||
maxPlayers: options?.maxPlayers || 10,
|
||||
currentPlayers: 0,
|
||||
players: new Map(),
|
||||
created: Date.now(),
|
||||
password: options?.password,
|
||||
metadata: options?.metadata || {}
|
||||
};
|
||||
|
||||
this._rooms.set(roomId, room);
|
||||
logger.info(`创建房间: ${roomId}, 最大玩家数: ${room.maxPlayers}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除房间
|
||||
*/
|
||||
public removeRoom(roomId: string): boolean {
|
||||
const room = this._rooms.get(roomId);
|
||||
if (!room) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 通知房间内的所有玩家
|
||||
for (const [connId, playerInfo] of room.players) {
|
||||
const conn = this._server.connections.find(c => c.id === connId);
|
||||
if (conn) {
|
||||
this._server.broadcastMsg('RoomClosed', { roomId, reason: '房间已关闭' }, [conn]);
|
||||
// 从已连接客户端列表中移除
|
||||
this._connectedClients.delete(connId);
|
||||
}
|
||||
}
|
||||
|
||||
this._rooms.delete(roomId);
|
||||
logger.info(`删除房间: ${roomId}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间列表
|
||||
*/
|
||||
public getRoomList(includePassword: boolean = false): Array<{
|
||||
roomId: string;
|
||||
currentPlayers: number;
|
||||
maxPlayers: number;
|
||||
hasPassword: boolean;
|
||||
metadata: Record<string, any>;
|
||||
}> {
|
||||
const roomList: Array<{
|
||||
roomId: string;
|
||||
currentPlayers: number;
|
||||
maxPlayers: number;
|
||||
hasPassword: boolean;
|
||||
metadata: Record<string, any>;
|
||||
}> = [];
|
||||
|
||||
for (const room of this._rooms.values()) {
|
||||
roomList.push({
|
||||
roomId: room.roomId,
|
||||
currentPlayers: room.currentPlayers,
|
||||
maxPlayers: room.maxPlayers,
|
||||
hasPassword: !!room.password,
|
||||
metadata: room.metadata
|
||||
});
|
||||
}
|
||||
|
||||
return roomList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间详情
|
||||
*/
|
||||
public getRoomInfo(roomId: string): RoomInfo | null {
|
||||
return this._rooms.get(roomId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间内广播消息
|
||||
*/
|
||||
public broadcastToRoom(roomId: string, message: string, data: any, excludeConnId?: string): void {
|
||||
const room = this._rooms.get(roomId);
|
||||
if (!room) {
|
||||
logger.warn(`房间不存在: ${roomId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetConns = this._server.connections.filter(conn => {
|
||||
return room.players.has(conn.id) && conn.id !== excludeConnId;
|
||||
});
|
||||
|
||||
if (targetConns.length > 0) {
|
||||
// 使用对应的房间消息类型
|
||||
switch (message) {
|
||||
case 'PlayerJoined':
|
||||
this._server.broadcastMsg('PlayerJoined', data, targetConns);
|
||||
break;
|
||||
case 'PlayerLeft':
|
||||
this._server.broadcastMsg('PlayerLeft', data, targetConns);
|
||||
break;
|
||||
case 'PlayerKicked':
|
||||
this._server.broadcastMsg('PlayerKicked', data, targetConns);
|
||||
break;
|
||||
default:
|
||||
// 其他消息使用ComponentUpdate作为通用消息
|
||||
this._server.broadcastMsg('ComponentUpdate', {
|
||||
entityId: 0,
|
||||
componentType: message,
|
||||
componentData: data,
|
||||
timestamp: Date.now()
|
||||
}, targetConns);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出玩家
|
||||
*/
|
||||
public kickPlayer(roomId: string, playerId: number, reason?: string): boolean {
|
||||
const room = this._rooms.get(roomId);
|
||||
if (!room) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let targetConnId: string | null = null;
|
||||
for (const [connId, playerInfo] of room.players) {
|
||||
if (playerInfo.playerId === playerId) {
|
||||
targetConnId = connId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetConnId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从房间中移除玩家
|
||||
room.players.delete(targetConnId);
|
||||
room.currentPlayers--;
|
||||
|
||||
// 从已连接客户端列表中移除
|
||||
this._connectedClients.delete(targetConnId);
|
||||
|
||||
// 通知被踢出的玩家
|
||||
const conn = this._server.connections.find(c => c.id === targetConnId);
|
||||
if (conn) {
|
||||
this.broadcastToRoom(roomId, 'PlayerKicked', {
|
||||
playerId,
|
||||
reason: reason || '被踢出房间'
|
||||
});
|
||||
|
||||
// 断开连接
|
||||
conn.close(reason);
|
||||
}
|
||||
|
||||
logger.info(`踢出玩家: ${playerId} 从房间 ${roomId}, 原因: ${reason || '未指定'}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置房间元数据
|
||||
*/
|
||||
public setRoomMetadata(roomId: string, metadata: Record<string, any>): boolean {
|
||||
const room = this._rooms.get(roomId);
|
||||
if (!room) {
|
||||
return false;
|
||||
}
|
||||
|
||||
room.metadata = { ...room.metadata, ...metadata };
|
||||
logger.debug(`更新房间元数据: ${roomId}`, metadata);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间状态统计
|
||||
*/
|
||||
public getRoomStats(): {
|
||||
totalRooms: number;
|
||||
totalPlayers: number;
|
||||
averagePlayersPerRoom: number;
|
||||
roomDetails: Array<{
|
||||
roomId: string;
|
||||
players: number;
|
||||
maxPlayers: number;
|
||||
uptime: number;
|
||||
}>;
|
||||
} {
|
||||
let totalPlayers = 0;
|
||||
const roomDetails: Array<{
|
||||
roomId: string;
|
||||
players: number;
|
||||
maxPlayers: number;
|
||||
uptime: number;
|
||||
}> = [];
|
||||
|
||||
for (const room of this._rooms.values()) {
|
||||
totalPlayers += room.currentPlayers;
|
||||
roomDetails.push({
|
||||
roomId: room.roomId,
|
||||
players: room.currentPlayers,
|
||||
maxPlayers: room.maxPlayers,
|
||||
uptime: Date.now() - room.created
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
totalRooms: this._rooms.size,
|
||||
totalPlayers,
|
||||
averagePlayersPerRoom: this._rooms.size > 0 ? totalPlayers / this._rooms.size : 0,
|
||||
roomDetails
|
||||
};
|
||||
}
|
||||
|
||||
// 添加房间映射和房间信息接口
|
||||
private _rooms = new Map<string, RoomInfo>();
|
||||
|
||||
/**
|
||||
* 启动服务器
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
try {
|
||||
await this._server.start();
|
||||
this._isRunning = true;
|
||||
this._startTime = Date.now();
|
||||
logger.info(`TSRPC服务器已启动,端口: ${this._server.options.port}`);
|
||||
this.emit('started');
|
||||
} catch (error) {
|
||||
logger.error('启动TSRPC服务器失败:', error);
|
||||
this.emit('startError', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止服务器
|
||||
*/
|
||||
public async stop(): Promise<void> {
|
||||
try {
|
||||
if (this._isRunning) {
|
||||
await this._server.stop();
|
||||
this._isRunning = false;
|
||||
this._startTime = 0;
|
||||
this._connectedClients.clear();
|
||||
this._rooms.clear();
|
||||
logger.info('TSRPC服务器已停止');
|
||||
this.emit('stopped');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('停止TSRPC服务器失败:', error);
|
||||
this.emit('stopError', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
isRunning: boolean;
|
||||
uptime: number;
|
||||
connectedClients: number;
|
||||
totalConnections: number;
|
||||
maxConcurrentConnections: number;
|
||||
rooms: number;
|
||||
performance: typeof this._performanceStats;
|
||||
} {
|
||||
this._performanceStats.uptime = this._isRunning ? Date.now() - this._startTime : 0;
|
||||
this._performanceStats.peakConnections = Math.max(
|
||||
this._maxConcurrentConnections,
|
||||
this._connectedClients.size
|
||||
);
|
||||
|
||||
return {
|
||||
isRunning: this._isRunning,
|
||||
uptime: this._performanceStats.uptime,
|
||||
connectedClients: this._connectedClients.size,
|
||||
totalConnections: this._totalConnections,
|
||||
maxConcurrentConnections: this._maxConcurrentConnections,
|
||||
rooms: this._rooms.size,
|
||||
performance: { ...this._performanceStats }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播消息给所有客户端
|
||||
*/
|
||||
public broadcastToAll(msgName: string, msg: any, excludeConnId?: string): void {
|
||||
for (const [connId, clientInfo] of this._connectedClients) {
|
||||
if (excludeConnId && connId === excludeConnId) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
this._server.sendMsg(clientInfo.conn, msgName, msg);
|
||||
this._performanceStats.totalMessages++;
|
||||
} catch (error) {
|
||||
logger.error(`向客户端 ${connId} 广播消息失败:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向房间内广播消息
|
||||
*/
|
||||
public broadcastToRoom(roomId: string, msgName: string, msg: any, excludeConnId?: string): void {
|
||||
const room = this._rooms.get(roomId);
|
||||
if (!room) {
|
||||
logger.warn(`房间 ${roomId} 不存在`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [connId] of room.players) {
|
||||
if (excludeConnId && connId === excludeConnId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const clientInfo = this._connectedClients.get(connId);
|
||||
if (clientInfo) {
|
||||
try {
|
||||
this._server.sendMsg(clientInfo.conn, msgName, msg);
|
||||
this._performanceStats.totalMessages++;
|
||||
} catch (error) {
|
||||
logger.error(`向房间 ${roomId} 客户端 ${connId} 广播消息失败:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
*/
|
||||
public on(event: string, handler: Function): void {
|
||||
if (!this._eventHandlers.has(event)) {
|
||||
this._eventHandlers.set(event, []);
|
||||
}
|
||||
this._eventHandlers.get(event)!.push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
*/
|
||||
public off(event: string, handler: Function): void {
|
||||
const handlers = this._eventHandlers.get(event);
|
||||
if (handlers) {
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index !== -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
*/
|
||||
private emit(event: string, ...args: any[]): void {
|
||||
const handlers = this._eventHandlers.get(event);
|
||||
if (handlers) {
|
||||
handlers.forEach(handler => {
|
||||
try {
|
||||
handler(...args);
|
||||
} catch (error) {
|
||||
logger.error(`事件处理器错误 (${event}):`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间列表
|
||||
*/
|
||||
public getRooms(): Array<{
|
||||
roomId: string;
|
||||
currentPlayers: number;
|
||||
maxPlayers: number;
|
||||
created: number;
|
||||
hasPassword: boolean;
|
||||
}> {
|
||||
return Array.from(this._rooms.values()).map(room => ({
|
||||
roomId: room.roomId,
|
||||
currentPlayers: room.currentPlayers,
|
||||
maxPlayers: room.maxPlayers,
|
||||
created: room.created,
|
||||
hasPassword: !!room.password
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制踢出玩家
|
||||
*/
|
||||
public kickPlayer(connId: string, reason?: string): boolean {
|
||||
const clientInfo = this._connectedClients.get(connId);
|
||||
if (clientInfo) {
|
||||
try {
|
||||
// 发送踢出通知
|
||||
this._server.sendMsg(clientInfo.conn, 'PlayerKicked', {
|
||||
reason: reason || '被服务器踢出'
|
||||
});
|
||||
|
||||
// 断开连接
|
||||
clientInfo.conn.close();
|
||||
logger.info(`踢出玩家 ${connId}: ${reason || '未指定原因'}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(`踢出玩家 ${connId} 失败:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线玩家列表
|
||||
*/
|
||||
public getOnlinePlayers(): Array<{
|
||||
connId: string;
|
||||
playerId?: number;
|
||||
playerName?: string;
|
||||
joinedAt: number;
|
||||
roomId?: string;
|
||||
}> {
|
||||
const players: Array<{
|
||||
connId: string;
|
||||
playerId?: number;
|
||||
playerName?: string;
|
||||
joinedAt: number;
|
||||
roomId?: string;
|
||||
}> = [];
|
||||
|
||||
for (const [connId, clientInfo] of this._connectedClients) {
|
||||
players.push({
|
||||
connId,
|
||||
playerId: clientInfo.playerId,
|
||||
playerName: clientInfo.playerName,
|
||||
joinedAt: clientInfo.joinedAt,
|
||||
roomId: clientInfo.roomId
|
||||
});
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间信息接口
|
||||
*/
|
||||
interface RoomInfo {
|
||||
roomId: string;
|
||||
maxPlayers: number;
|
||||
currentPlayers: number;
|
||||
players: Map<string, PlayerInfo>; // connId -> PlayerInfo
|
||||
created: number;
|
||||
password?: string;
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家信息接口
|
||||
*/
|
||||
interface PlayerInfo {
|
||||
playerId: number;
|
||||
playerName?: string;
|
||||
joinedAt: number;
|
||||
}
|
||||
14
packages/network/src/TSRPC/index.ts
Normal file
14
packages/network/src/TSRPC/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* TSRPC 网络通信系统导出
|
||||
*/
|
||||
|
||||
// 协议定义
|
||||
export * from './protocols/serviceProto';
|
||||
export * from './protocols/PtlSyncComponent';
|
||||
export * from './protocols/PtlJoinRoom';
|
||||
export * from './protocols/MsgComponentUpdate';
|
||||
|
||||
// 客户端和服务器
|
||||
export { TsrpcNetworkServer } from './TsrpcServer';
|
||||
export { TsrpcNetworkClient } from './TsrpcClient';
|
||||
export { TsrpcManager, NetworkMode } from './TsrpcManager';
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 组件更新消息(服务器广播给客户端)
|
||||
*/
|
||||
export interface MsgComponentUpdate {
|
||||
entityId: number;
|
||||
componentType: string;
|
||||
componentData: any;
|
||||
timestamp: number;
|
||||
}
|
||||
20
packages/network/src/TSRPC/protocols/PtlJoinRoom.ts
Normal file
20
packages/network/src/TSRPC/protocols/PtlJoinRoom.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 加入房间协议
|
||||
*/
|
||||
export interface ReqJoinRoom {
|
||||
roomId: string;
|
||||
playerName?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface ResJoinRoom {
|
||||
success: boolean;
|
||||
playerId?: number;
|
||||
roomInfo?: {
|
||||
roomId: string;
|
||||
playerCount: number;
|
||||
maxPlayers: number;
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
errorMsg?: string;
|
||||
}
|
||||
18
packages/network/src/TSRPC/protocols/PtlSyncComponent.ts
Normal file
18
packages/network/src/TSRPC/protocols/PtlSyncComponent.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 组件同步协议定义
|
||||
*/
|
||||
|
||||
// 同步组件数据请求
|
||||
export interface ReqSyncComponent {
|
||||
entityId: number;
|
||||
componentType: string;
|
||||
componentData: any;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
// 同步组件数据响应
|
||||
export interface ResSyncComponent {
|
||||
success: boolean;
|
||||
entityId: number;
|
||||
errorMsg?: string;
|
||||
}
|
||||
78
packages/network/src/TSRPC/protocols/serviceProto.ts
Normal file
78
packages/network/src/TSRPC/protocols/serviceProto.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* TSRPC 服务协议定义
|
||||
*/
|
||||
import { ServiceProto } from 'tsrpc-proto';
|
||||
|
||||
// API 协议
|
||||
export interface ServiceType {
|
||||
api: {
|
||||
'SyncComponent': {
|
||||
req: import('./PtlSyncComponent').ReqSyncComponent;
|
||||
res: import('./PtlSyncComponent').ResSyncComponent;
|
||||
};
|
||||
'JoinRoom': {
|
||||
req: import('./PtlJoinRoom').ReqJoinRoom;
|
||||
res: import('./PtlJoinRoom').ResJoinRoom;
|
||||
};
|
||||
'Ping': {
|
||||
req: { timestamp: number };
|
||||
res: { timestamp: number; serverTime: number };
|
||||
};
|
||||
};
|
||||
msg: {
|
||||
'ComponentUpdate': import('./MsgComponentUpdate').MsgComponentUpdate;
|
||||
'RoomClosed': { roomId: string; reason: string };
|
||||
'PlayerJoined': { playerId: number; playerName?: string; timestamp: number };
|
||||
'PlayerLeft': { playerId: number; playerName?: string; timestamp: number };
|
||||
'PlayerKicked': { playerId: number; reason: string };
|
||||
};
|
||||
}
|
||||
|
||||
export const serviceProto: ServiceProto<ServiceType> = {
|
||||
"version": 1,
|
||||
"services": [
|
||||
// API 服务
|
||||
{
|
||||
"id": 0,
|
||||
"name": "SyncComponent",
|
||||
"type": "api"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "JoinRoom",
|
||||
"type": "api"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Ping",
|
||||
"type": "api"
|
||||
},
|
||||
// 消息服务
|
||||
{
|
||||
"id": 3,
|
||||
"name": "ComponentUpdate",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "RoomClosed",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "PlayerJoined",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "PlayerLeft",
|
||||
"type": "msg"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "PlayerKicked",
|
||||
"type": "msg"
|
||||
}
|
||||
],
|
||||
"types": {}
|
||||
};
|
||||
211
packages/network/src/constants/NetworkConstants.ts
Normal file
211
packages/network/src/constants/NetworkConstants.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* 网络库常量定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 网络配置常量
|
||||
*/
|
||||
export const NETWORK_CONFIG = {
|
||||
/** 默认连接超时时间 (ms) */
|
||||
DEFAULT_CONNECTION_TIMEOUT: 10000,
|
||||
/** 默认心跳间隔 (ms) */
|
||||
DEFAULT_HEARTBEAT_INTERVAL: 5000,
|
||||
/** 默认心跳超时时间 (ms) */
|
||||
DEFAULT_HEARTBEAT_TIMEOUT: 10000,
|
||||
/** 默认最大重连次数 */
|
||||
DEFAULT_MAX_RECONNECT_ATTEMPTS: 5,
|
||||
/** 默认重连延迟 (ms) */
|
||||
DEFAULT_RECONNECT_DELAY: 1000,
|
||||
/** 默认最大连续丢包数 */
|
||||
DEFAULT_MAX_CONSECUTIVE_LOSS: 3,
|
||||
/** 默认心跳包大小 (bytes) */
|
||||
DEFAULT_HEARTBEAT_PACKET_SIZE: 64,
|
||||
/** 默认RTT历史记录大小 */
|
||||
DEFAULT_RTT_HISTORY_SIZE: 50
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* SyncVar配置常量
|
||||
*/
|
||||
export const SYNCVAR_CONFIG = {
|
||||
/** 默认SyncVar缓存超时 (ms) */
|
||||
DEFAULT_CACHE_TIMEOUT: 5000,
|
||||
/** 默认同步频率限制 (ms) */
|
||||
DEFAULT_THROTTLE_MS: 50,
|
||||
/** 最大字段编号 */
|
||||
MAX_FIELD_NUMBER: 65535,
|
||||
/** 最小字段编号 */
|
||||
MIN_FIELD_NUMBER: 1
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 消息配置常量
|
||||
*/
|
||||
export const MESSAGE_CONFIG = {
|
||||
/** 默认消息序列号范围 */
|
||||
MAX_SEQUENCE_NUMBER: 4294967295, // 2^32 - 1
|
||||
/** 消息头部最大大小 (bytes) */
|
||||
MAX_HEADER_SIZE: 256,
|
||||
/** 消息体最大大小 (bytes) */
|
||||
MAX_PAYLOAD_SIZE: 1048576, // 1MB
|
||||
/** 默认消息超时时间 (ms) */
|
||||
DEFAULT_MESSAGE_TIMEOUT: 30000,
|
||||
/** 批处理消息最大数量 */
|
||||
MAX_BATCH_SIZE: 100
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 序列化配置常量
|
||||
*/
|
||||
export const SERIALIZATION_CONFIG = {
|
||||
/** 默认压缩级别 */
|
||||
DEFAULT_COMPRESSION_LEVEL: 6,
|
||||
/** 启用压缩的最小数据大小 (bytes) */
|
||||
MIN_COMPRESSION_SIZE: 1024,
|
||||
/** 序列化缓冲区初始大小 (bytes) */
|
||||
INITIAL_BUFFER_SIZE: 4096,
|
||||
/** 序列化缓冲区最大大小 (bytes) */
|
||||
MAX_BUFFER_SIZE: 16777216 // 16MB
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* TSRPC配置常量
|
||||
*/
|
||||
export const TSRPC_CONFIG = {
|
||||
/** 默认服务器URL */
|
||||
DEFAULT_SERVER_URL: 'ws://localhost:3000',
|
||||
/** 默认超时时间 (ms) */
|
||||
DEFAULT_TIMEOUT: 30000,
|
||||
/** 默认心跳配置 */
|
||||
DEFAULT_HEARTBEAT: {
|
||||
interval: 10000,
|
||||
timeout: 5000
|
||||
},
|
||||
/** 默认连接池配置 */
|
||||
DEFAULT_POOL_CONFIG: {
|
||||
minConnections: 1,
|
||||
maxConnections: 10,
|
||||
idleTimeout: 300000 // 5分钟
|
||||
}
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 权限配置常量
|
||||
*/
|
||||
export const AUTHORITY_CONFIG = {
|
||||
/** 权限优先级范围 */
|
||||
MIN_PRIORITY: 0,
|
||||
MAX_PRIORITY: 1000,
|
||||
/** 默认权限规则优先级 */
|
||||
DEFAULT_RULE_PRIORITY: 50,
|
||||
/** 服务端权限优先级 */
|
||||
SERVER_AUTHORITY_PRIORITY: 100,
|
||||
/** 网络身份权限优先级 */
|
||||
NETWORK_IDENTITY_PRIORITY: 80,
|
||||
/** 组件自定义权限优先级 */
|
||||
COMPONENT_CUSTOM_PRIORITY: 60,
|
||||
/** 实体所有者权限优先级 */
|
||||
ENTITY_OWNER_PRIORITY: 50,
|
||||
/** 默认拒绝规则优先级 */
|
||||
DEFAULT_DENY_PRIORITY: 0
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 性能监控配置常量
|
||||
*/
|
||||
export const PERFORMANCE_CONFIG = {
|
||||
/** 性能统计收集间隔 (ms) */
|
||||
STATS_COLLECTION_INTERVAL: 1000,
|
||||
/** 性能数据保留时间 (ms) */
|
||||
STATS_RETENTION_TIME: 300000, // 5分钟
|
||||
/** 警告阈值 */
|
||||
WARNING_THRESHOLDS: {
|
||||
/** RTT警告阈值 (ms) */
|
||||
RTT: 200,
|
||||
/** 丢包率警告阈值 */
|
||||
PACKET_LOSS: 0.05, // 5%
|
||||
/** 抖动警告阈值 (ms) */
|
||||
JITTER: 50,
|
||||
/** CPU使用率警告阈值 */
|
||||
CPU_USAGE: 0.8, // 80%
|
||||
/** 内存使用率警告阈值 */
|
||||
MEMORY_USAGE: 0.8 // 80%
|
||||
}
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 错误代码常量
|
||||
*/
|
||||
export const ERROR_CODES = {
|
||||
// 连接错误 (1000-1099)
|
||||
CONNECTION_FAILED: 1000,
|
||||
CONNECTION_TIMEOUT: 1001,
|
||||
CONNECTION_REFUSED: 1002,
|
||||
CONNECTION_LOST: 1003,
|
||||
|
||||
// 序列化错误 (1100-1199)
|
||||
SERIALIZATION_FAILED: 1100,
|
||||
DESERIALIZATION_FAILED: 1101,
|
||||
INVALID_DATA_FORMAT: 1102,
|
||||
|
||||
// SyncVar错误 (1200-1299)
|
||||
SYNCVAR_INIT_FAILED: 1200,
|
||||
SYNCVAR_METADATA_MISSING: 1201,
|
||||
SYNCVAR_TYPE_MISMATCH: 1202,
|
||||
SYNCVAR_AUTHORITY_DENIED: 1203,
|
||||
|
||||
// 消息错误 (1300-1399)
|
||||
MESSAGE_TIMEOUT: 1300,
|
||||
MESSAGE_TOO_LARGE: 1301,
|
||||
MESSAGE_INVALID_TYPE: 1302,
|
||||
MESSAGE_SEQUENCE_ERROR: 1303,
|
||||
|
||||
// TSRPC错误 (1400-1499)
|
||||
TSRPC_CALL_FAILED: 1400,
|
||||
TSRPC_METHOD_NOT_FOUND: 1401,
|
||||
TSRPC_INVALID_PARAMS: 1402,
|
||||
TSRPC_SERVER_ERROR: 1403,
|
||||
|
||||
// 权限错误 (1500-1599)
|
||||
PERMISSION_DENIED: 1500,
|
||||
INVALID_AUTHORITY: 1501,
|
||||
AUTHORITY_CHECK_FAILED: 1502
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 日志级别常量
|
||||
*/
|
||||
export const LOG_LEVELS = {
|
||||
ERROR: 0,
|
||||
WARN: 1,
|
||||
INFO: 2,
|
||||
DEBUG: 3,
|
||||
TRACE: 4
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 环境类型常量
|
||||
*/
|
||||
export const ENVIRONMENTS = {
|
||||
SERVER: 'server',
|
||||
CLIENT: 'client',
|
||||
HYBRID: 'hybrid'
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 网络事件名称常量
|
||||
*/
|
||||
export const NETWORK_EVENTS = {
|
||||
CONNECTED: 'connected',
|
||||
DISCONNECTED: 'disconnected',
|
||||
CONNECTING: 'connecting',
|
||||
RECONNECTING: 'reconnecting',
|
||||
RECONNECTED: 'reconnected',
|
||||
CONNECT_ERROR: 'connectError',
|
||||
RECONNECT_FAILED: 'reconnectFailed',
|
||||
MESSAGE: 'message',
|
||||
ERROR: 'error',
|
||||
HEARTBEAT: 'heartbeat',
|
||||
PERFORMANCE_WARNING: 'performanceWarning'
|
||||
} as const;
|
||||
|
||||
@@ -19,7 +19,10 @@ export { NetworkComponent } from './NetworkComponent';
|
||||
export { INetworkSyncable } from './INetworkSyncable';
|
||||
export { NetworkRole } from './NetworkRole';
|
||||
|
||||
// Protobuf序列化系统
|
||||
// TSRPC网络通信系统
|
||||
export * from './TSRPC';
|
||||
|
||||
// 装饰器序列化系统(用于组件同步)
|
||||
export * from './Serialization';
|
||||
|
||||
// 快照系统(帧同步)
|
||||
|
||||
151
packages/network/src/types/CoreTypes.ts
Normal file
151
packages/network/src/types/CoreTypes.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { Component, Entity } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 网络环境类型
|
||||
*/
|
||||
export type NetworkEnvironmentType = 'server' | 'client' | 'hybrid';
|
||||
|
||||
/**
|
||||
* 连接状态类型
|
||||
*/
|
||||
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
|
||||
|
||||
/**
|
||||
* 权限类型
|
||||
*/
|
||||
export enum AuthorityType {
|
||||
None = 'none',
|
||||
ReadOnly = 'readonly',
|
||||
ReadWrite = 'readwrite',
|
||||
Full = 'full'
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络组件基接口
|
||||
*/
|
||||
export interface INetworkComponent extends Component {
|
||||
networkId?: string;
|
||||
hasNetworkAuthority?(): boolean;
|
||||
getNetworkState?(): Uint8Array;
|
||||
applyNetworkState?(data: Uint8Array): void;
|
||||
|
||||
/** 允许通过字符串键访问属性 */
|
||||
[propertyKey: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 具有权限检查能力的组件
|
||||
*/
|
||||
export interface IAuthorizedComponent extends INetworkComponent {
|
||||
hasAuthority(context?: AuthorityContext): boolean;
|
||||
checkAuthority?(context?: AuthorityContext): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限上下文
|
||||
*/
|
||||
export interface AuthorityContext {
|
||||
environment: NetworkEnvironmentType;
|
||||
networkId?: string;
|
||||
entityId?: number;
|
||||
clientId?: string;
|
||||
level: AuthorityType;
|
||||
timestamp: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络实体接口
|
||||
*/
|
||||
export interface INetworkEntity extends Entity {
|
||||
ownerId?: string;
|
||||
hasNetworkAuthority?(): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件构造器类型
|
||||
*/
|
||||
export type ComponentConstructor<T extends Component = Component> = new (...args: unknown[]) => T;
|
||||
|
||||
/**
|
||||
* 网络组件构造器类型
|
||||
*/
|
||||
export type NetworkComponentConstructor<T extends INetworkComponent = INetworkComponent> = new (...args: unknown[]) => T;
|
||||
|
||||
/**
|
||||
* 事件处理器类型映射
|
||||
*/
|
||||
export interface NetworkEventHandlers {
|
||||
connected: () => void;
|
||||
disconnected: (reason?: string) => void;
|
||||
connecting: () => void;
|
||||
reconnecting: (attempt: number) => void;
|
||||
reconnected: () => void;
|
||||
connectError: (error: Error) => void;
|
||||
reconnectFailed: () => void;
|
||||
message: (data: Uint8Array) => void;
|
||||
error: (error: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型安全的事件发射器接口
|
||||
*/
|
||||
export interface ITypedEventEmitter<TEvents extends Record<string, (...args: any[]) => void>> {
|
||||
on<K extends keyof TEvents>(event: K, handler: TEvents[K]): void;
|
||||
off<K extends keyof TEvents>(event: K, handler: TEvents[K]): void;
|
||||
emit<K extends keyof TEvents>(event: K, ...args: Parameters<TEvents[K]>): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络统计接口
|
||||
*/
|
||||
export interface NetworkStats {
|
||||
connectionCount: number;
|
||||
totalConnections: number;
|
||||
uptime: number;
|
||||
bytesTransferred: number;
|
||||
messagesCount: number;
|
||||
errors: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能指标接口
|
||||
*/
|
||||
export interface PerformanceMetrics {
|
||||
rtt: number;
|
||||
latency: number;
|
||||
jitter: number;
|
||||
packetLoss: number;
|
||||
bandwidth: number;
|
||||
connectionQuality: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型守卫工具类
|
||||
*/
|
||||
export class TypeGuards {
|
||||
static isNetworkComponent(obj: unknown): obj is INetworkComponent {
|
||||
return obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
obj instanceof Component;
|
||||
}
|
||||
|
||||
static isAuthorizedComponent(obj: unknown): obj is IAuthorizedComponent {
|
||||
return TypeGuards.isNetworkComponent(obj) &&
|
||||
typeof (obj as IAuthorizedComponent).hasAuthority === 'function';
|
||||
}
|
||||
|
||||
static isNetworkEntity(obj: unknown): obj is INetworkEntity {
|
||||
return obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
obj instanceof Entity;
|
||||
}
|
||||
|
||||
static isValidNetworkEnvironment(env: string): env is NetworkEnvironmentType {
|
||||
return ['server', 'client', 'hybrid'].includes(env);
|
||||
}
|
||||
|
||||
static isValidConnectionState(state: string): state is ConnectionState {
|
||||
return ['disconnected', 'connecting', 'connected', 'reconnecting'].includes(state);
|
||||
}
|
||||
}
|
||||
207
packages/network/src/types/MessageTypes.ts
Normal file
207
packages/network/src/types/MessageTypes.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 消息类型枚举
|
||||
*/
|
||||
export enum MessageType {
|
||||
// 基础消息类型 (0-99)
|
||||
HEARTBEAT = 0,
|
||||
PING = 1,
|
||||
PONG = 2,
|
||||
ERROR = 3,
|
||||
|
||||
// 连接管理 (100-199)
|
||||
CONNECT = 100,
|
||||
DISCONNECT = 101,
|
||||
RECONNECT = 102,
|
||||
AUTH = 103,
|
||||
|
||||
// SyncVar消息 (200-299)
|
||||
SYNC_VAR_UPDATE = 200,
|
||||
SYNC_VAR_BATCH = 201,
|
||||
SYNC_VAR_REQUEST = 202,
|
||||
|
||||
// 快照消息 (300-399)
|
||||
SNAPSHOT_FULL = 300,
|
||||
SNAPSHOT_INCREMENTAL = 301,
|
||||
SNAPSHOT_REQUEST = 302,
|
||||
|
||||
// TSRPC消息 (400-499)
|
||||
TSRPC_CALL = 400,
|
||||
TSRPC_RESPONSE = 401,
|
||||
TSRPC_NOTIFICATION = 402,
|
||||
|
||||
// 自定义消息 (500+)
|
||||
CUSTOM = 500
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息优先级
|
||||
*/
|
||||
export enum MessagePriority {
|
||||
CRITICAL = 0, // 关键消息(连接、认证等)
|
||||
HIGH = 1, // 高优先级(实时游戏数据)
|
||||
NORMAL = 2, // 普通优先级(常规同步)
|
||||
LOW = 3, // 低优先级(统计、日志等)
|
||||
BACKGROUND = 4 // 后台消息(清理、维护等)
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络消息基接口
|
||||
*/
|
||||
export interface INetworkMessage {
|
||||
readonly type: MessageType;
|
||||
readonly timestamp: number;
|
||||
readonly priority: MessagePriority;
|
||||
readonly sequenceNumber?: number;
|
||||
serialize(): Uint8Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息头接口
|
||||
*/
|
||||
export interface MessageHeader {
|
||||
type: MessageType;
|
||||
size: number;
|
||||
timestamp: number;
|
||||
priority: MessagePriority;
|
||||
sequenceNumber?: number;
|
||||
checksum?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳消息接口
|
||||
*/
|
||||
export interface IHeartbeatMessage extends INetworkMessage {
|
||||
readonly pingId: string;
|
||||
readonly payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar更新消息接口
|
||||
*/
|
||||
export interface ISyncVarMessage extends INetworkMessage {
|
||||
readonly networkId: string;
|
||||
readonly componentType: string;
|
||||
readonly fieldUpdates: SyncVarFieldUpdate[];
|
||||
readonly isFullSync: boolean;
|
||||
readonly senderId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SyncVar字段更新
|
||||
*/
|
||||
export interface SyncVarFieldUpdate {
|
||||
readonly fieldNumber: number;
|
||||
readonly propertyKey: string;
|
||||
readonly newValue: unknown;
|
||||
readonly oldValue?: unknown;
|
||||
readonly timestamp: number;
|
||||
readonly authorityOnly: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误消息接口
|
||||
*/
|
||||
export interface IErrorMessage extends INetworkMessage {
|
||||
readonly errorCode: string;
|
||||
readonly errorMessage: string;
|
||||
readonly errorData?: Record<string, unknown>;
|
||||
readonly originalMessageType?: MessageType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快照消息接口
|
||||
*/
|
||||
export interface ISnapshotMessage extends INetworkMessage {
|
||||
readonly snapshotId: string;
|
||||
readonly snapshotType: 'full' | 'incremental';
|
||||
readonly entityData: EntitySnapshot[];
|
||||
readonly compressionType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体快照数据
|
||||
*/
|
||||
export interface EntitySnapshot {
|
||||
readonly entityId: number;
|
||||
readonly components: ComponentSnapshot[];
|
||||
readonly timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件快照数据
|
||||
*/
|
||||
export interface ComponentSnapshot {
|
||||
readonly componentType: string;
|
||||
readonly data: Uint8Array;
|
||||
readonly version?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TSRPC消息接口
|
||||
*/
|
||||
export interface ITsrpcMessage extends INetworkMessage {
|
||||
readonly method: string;
|
||||
readonly requestId?: string;
|
||||
readonly params?: Record<string, unknown>;
|
||||
readonly result?: unknown;
|
||||
readonly error?: TsrpcError;
|
||||
}
|
||||
|
||||
/**
|
||||
* TSRPC错误
|
||||
*/
|
||||
export interface TsrpcError {
|
||||
readonly code: number;
|
||||
readonly message: string;
|
||||
readonly data?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息工厂接口
|
||||
*/
|
||||
export interface IMessageFactory {
|
||||
createMessage<T extends INetworkMessage>(
|
||||
type: MessageType,
|
||||
data: Record<string, unknown>
|
||||
): T;
|
||||
|
||||
deserializeMessage(data: Uint8Array): INetworkMessage | null;
|
||||
|
||||
registerMessageType<T extends INetworkMessage>(
|
||||
type: MessageType,
|
||||
constructor: new (...args: unknown[]) => T
|
||||
): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息处理器接口
|
||||
*/
|
||||
export interface IMessageHandler<T extends INetworkMessage = INetworkMessage> {
|
||||
readonly messageType: MessageType;
|
||||
readonly priority: number;
|
||||
|
||||
canHandle(message: INetworkMessage): message is T;
|
||||
handle(message: T, context?: MessageHandlerContext): Promise<void> | void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息处理上下文
|
||||
*/
|
||||
export interface MessageHandlerContext {
|
||||
readonly connectionId?: string;
|
||||
readonly senderId?: string;
|
||||
readonly timestamp: number;
|
||||
readonly metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息统计接口
|
||||
*/
|
||||
export interface MessageStats {
|
||||
readonly totalSent: number;
|
||||
readonly totalReceived: number;
|
||||
readonly totalDropped: number;
|
||||
readonly averageSize: number;
|
||||
readonly messagesByType: Map<MessageType, number>;
|
||||
readonly messagesByPriority: Map<MessagePriority, number>;
|
||||
}
|
||||
@@ -12,6 +12,11 @@ import { SerializedData } from '../Serialization/SerializationTypes';
|
||||
* 扩展核心组件接口,添加网络同步功能
|
||||
*/
|
||||
export interface INetworkSyncable extends IComponent {
|
||||
/** 内部SyncVar ID */
|
||||
_syncVarId?: string;
|
||||
/** 是否禁用SyncVar监听 */
|
||||
_syncVarDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* 获取网络同步状态
|
||||
*/
|
||||
@@ -36,6 +41,9 @@ export interface INetworkSyncable extends IComponent {
|
||||
* 标记字段为脏状态
|
||||
*/
|
||||
markFieldDirty(fieldNumber: number): void;
|
||||
|
||||
/** 允许通过字符串键访问属性 */
|
||||
[propertyKey: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,10 +118,10 @@ export type MessageData =
|
||||
| null;
|
||||
|
||||
/**
|
||||
* 网络消息基接口
|
||||
* 为所有网络消息提供类型安全的基础
|
||||
* 基础网络消息接口
|
||||
* 为SyncVar等网络同步功能提供消息接口
|
||||
*/
|
||||
export interface INetworkMessage<TData extends MessageData = MessageData> {
|
||||
export interface IBasicNetworkMessage<TData extends MessageData = MessageData> {
|
||||
/** 消息类型 */
|
||||
readonly messageType: number;
|
||||
/** 消息数据 */
|
||||
|
||||
33
packages/network/src/types/index.ts
Normal file
33
packages/network/src/types/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 网络库类型导出
|
||||
*/
|
||||
|
||||
// 核心类型
|
||||
export * from './CoreTypes';
|
||||
export {
|
||||
INetworkSyncable,
|
||||
IBasicNetworkMessage,
|
||||
NetworkComponentType,
|
||||
SyncVarValue,
|
||||
MessageData,
|
||||
TypeGuards as NetworkTypeGuards
|
||||
} from './NetworkTypes';
|
||||
export {
|
||||
MessageType,
|
||||
MessagePriority,
|
||||
INetworkMessage,
|
||||
IHeartbeatMessage,
|
||||
ISyncVarMessage,
|
||||
IErrorMessage,
|
||||
ISnapshotMessage,
|
||||
ITsrpcMessage
|
||||
} from './MessageTypes';
|
||||
|
||||
// 常量
|
||||
export * from '../constants/NetworkConstants';
|
||||
|
||||
// 配置
|
||||
export * from '../Config/NetworkConfigManager';
|
||||
|
||||
// 错误处理
|
||||
export * from '../Error/NetworkErrorHandler';
|
||||
Reference in New Issue
Block a user