重新整理网络架构,tsrpc/syncvar并行
This commit is contained in:
3
package-lock.json
generated
3
package-lock.json
generated
@@ -9701,8 +9701,7 @@
|
|||||||
"node_modules/reflect-metadata": {
|
"node_modules/reflect-metadata": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
|
||||||
"license": "Apache-2.0"
|
|
||||||
},
|
},
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
|
|||||||
@@ -32,17 +32,20 @@
|
|||||||
"test:watch": "jest --watch --config jest.config.cjs",
|
"test:watch": "jest --watch --config jest.config.cjs",
|
||||||
"test:coverage": "jest --coverage --config jest.config.cjs",
|
"test:coverage": "jest --coverage --config jest.config.cjs",
|
||||||
"test:ci": "jest --ci --coverage --config jest.config.cjs",
|
"test:ci": "jest --ci --coverage --config jest.config.cjs",
|
||||||
"test:clear": "jest --clearCache"
|
"test:clear": "jest --clearCache",
|
||||||
|
"publish:patch": "npm version patch && npm publish",
|
||||||
|
"publish:minor": "npm version minor && npm publish",
|
||||||
|
"publish:major": "npm version major && npm publish"
|
||||||
},
|
},
|
||||||
"author": "yhh",
|
"author": "yhh",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reflect-metadata": "^0.2.2",
|
|
||||||
"ws": "^8.18.0",
|
|
||||||
"isomorphic-ws": "^5.0.0",
|
"isomorphic-ws": "^5.0.0",
|
||||||
"uuid": "^10.0.0",
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"tsbuffer": "^2.2.10",
|
||||||
"tsrpc": "^3.4.19",
|
"tsrpc": "^3.4.19",
|
||||||
"tsbuffer": "^2.2.10"
|
"uuid": "^10.0.0",
|
||||||
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@esengine/ecs-framework": ">=2.1.29"
|
"@esengine/ecs-framework": ">=2.1.29"
|
||||||
@@ -54,8 +57,8 @@
|
|||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^20.19.0",
|
"@types/node": "^20.19.0",
|
||||||
"@types/ws": "^8.5.13",
|
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
|
"@types/ws": "^8.5.13",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"rimraf": "^5.0.0",
|
"rimraf": "^5.0.0",
|
||||||
|
|||||||
@@ -1,478 +0,0 @@
|
|||||||
/**
|
|
||||||
* 网络配置管理器
|
|
||||||
*/
|
|
||||||
|
|
||||||
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;
|
|
||||||
@@ -1,477 +0,0 @@
|
|||||||
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('心跳配置已更新');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,512 +0,0 @@
|
|||||||
import WebSocket from 'isomorphic-ws';
|
|
||||||
import { NetworkConnection } from './NetworkConnection';
|
|
||||||
import { SyncVarUpdateMessage } from '../Messaging/MessageTypes';
|
|
||||||
import { SyncVarMessageHandler } from '../SyncVar/SyncVarMessageHandler';
|
|
||||||
import { MessageHandler } from '../Messaging/MessageHandler';
|
|
||||||
import { NetworkPerformanceMonitor } from './NetworkPerformanceMonitor';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端事件接口
|
|
||||||
*/
|
|
||||||
export interface NetworkClientEvents {
|
|
||||||
connected: () => void;
|
|
||||||
disconnected: (reason?: string) => void;
|
|
||||||
message: (data: Uint8Array) => void;
|
|
||||||
error: (error: Error) => void;
|
|
||||||
reconnecting: (attempt: number) => void;
|
|
||||||
reconnected: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络客户端
|
|
||||||
*
|
|
||||||
* 管理与服务端的WebSocket连接,支持自动重连
|
|
||||||
* 提供消息发送和接收功能
|
|
||||||
*/
|
|
||||||
export class NetworkClient {
|
|
||||||
private static readonly logger = createLogger('NetworkClient');
|
|
||||||
private _connection: NetworkConnection | null = null;
|
|
||||||
private _url: string = '';
|
|
||||||
private _isConnected: boolean = false;
|
|
||||||
private _isConnecting: boolean = false;
|
|
||||||
private _connectTime: number = 0;
|
|
||||||
private _eventHandlers: Map<keyof NetworkClientEvents, Function[]> = new Map();
|
|
||||||
|
|
||||||
// SyncVar相关组件
|
|
||||||
private _syncVarHandler: SyncVarMessageHandler;
|
|
||||||
private _messageHandler: MessageHandler;
|
|
||||||
|
|
||||||
// 性能监控
|
|
||||||
private _performanceMonitor: NetworkPerformanceMonitor;
|
|
||||||
|
|
||||||
// 重连配置
|
|
||||||
private _autoReconnect: boolean = true;
|
|
||||||
private _reconnectAttempts: number = 0;
|
|
||||||
private _maxReconnectAttempts: number = 5;
|
|
||||||
private _reconnectDelay: number = 1000; // 初始重连延迟1秒
|
|
||||||
private _maxReconnectDelay: number = 30000; // 最大重连延迟30秒
|
|
||||||
private _reconnectTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 初始化SyncVar组件
|
|
||||||
this._syncVarHandler = new SyncVarMessageHandler();
|
|
||||||
this._messageHandler = MessageHandler.Instance;
|
|
||||||
this._performanceMonitor = NetworkPerformanceMonitor.Instance;
|
|
||||||
|
|
||||||
// 注册SyncVar消息处理器
|
|
||||||
this._messageHandler.registerHandler(
|
|
||||||
400, // MessageType.SYNC_VAR_UPDATE
|
|
||||||
SyncVarUpdateMessage,
|
|
||||||
this._syncVarHandler,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接到服务端
|
|
||||||
*
|
|
||||||
* @param url - 服务端WebSocket地址
|
|
||||||
* @param autoReconnect - 是否启用自动重连
|
|
||||||
* @returns Promise<void>
|
|
||||||
*/
|
|
||||||
public async connect(url: string, autoReconnect: boolean = true): Promise<void> {
|
|
||||||
if (this._isConnected || this._isConnecting) {
|
|
||||||
throw new Error('客户端已连接或正在连接中');
|
|
||||||
}
|
|
||||||
|
|
||||||
this._url = url;
|
|
||||||
this._autoReconnect = autoReconnect;
|
|
||||||
this._isConnecting = true;
|
|
||||||
this._reconnectAttempts = 0;
|
|
||||||
|
|
||||||
return this.attemptConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 尝试建立连接
|
|
||||||
*
|
|
||||||
* @returns Promise<void>
|
|
||||||
*/
|
|
||||||
private async attemptConnection(): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const ws = new WebSocket(this._url);
|
|
||||||
|
|
||||||
// 设置连接超时
|
|
||||||
const connectTimeout = setTimeout(() => {
|
|
||||||
ws.close();
|
|
||||||
this.handleConnectionFailed(new Error('连接超时'), resolve, reject);
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
clearTimeout(connectTimeout);
|
|
||||||
this.handleConnectionSuccess(ws, resolve);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (event) => {
|
|
||||||
clearTimeout(connectTimeout);
|
|
||||||
const error = new Error(`连接失败: ${event.toString()}`);
|
|
||||||
this.handleConnectionFailed(error, resolve, reject);
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.handleConnectionFailed(error as Error, resolve, reject);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理连接成功
|
|
||||||
*
|
|
||||||
* @param ws - WebSocket连接
|
|
||||||
* @param resolve - Promise resolve函数
|
|
||||||
*/
|
|
||||||
private handleConnectionSuccess(ws: WebSocket, resolve: () => void): void {
|
|
||||||
this._connection = new NetworkConnection(ws, 'client', this._url);
|
|
||||||
this._isConnected = true;
|
|
||||||
this._isConnecting = false;
|
|
||||||
this._connectTime = Date.now();
|
|
||||||
this._reconnectAttempts = 0;
|
|
||||||
|
|
||||||
// 设置连接事件监听
|
|
||||||
this._connection.on('connected', () => {
|
|
||||||
NetworkClient.logger.info(`连接成功: ${this._url}`);
|
|
||||||
this.emit('connected');
|
|
||||||
|
|
||||||
// 如果这是重连,触发重连成功事件
|
|
||||||
if (this._reconnectAttempts > 0) {
|
|
||||||
this.emit('reconnected');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this._connection.on('disconnected', (reason) => {
|
|
||||||
NetworkClient.logger.info(`连接断开: ${reason}`);
|
|
||||||
this.handleDisconnection(reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._connection.on('message', async (data) => {
|
|
||||||
this.recordMessagePerformance(data, false);
|
|
||||||
this.emit('message', data);
|
|
||||||
|
|
||||||
// 自动处理消息
|
|
||||||
await this._messageHandler.handleRawMessage(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._connection.on('error', (error) => {
|
|
||||||
NetworkClient.logger.error('连接错误:', error);
|
|
||||||
this.emit('error', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理连接失败
|
|
||||||
*
|
|
||||||
* @param error - 错误对象
|
|
||||||
* @param resolve - Promise resolve函数
|
|
||||||
* @param reject - Promise reject函数
|
|
||||||
*/
|
|
||||||
private handleConnectionFailed(error: Error, resolve: () => void, reject: (error: Error) => void): void {
|
|
||||||
this._isConnecting = false;
|
|
||||||
|
|
||||||
if (this._autoReconnect && this._reconnectAttempts < this._maxReconnectAttempts) {
|
|
||||||
this.scheduleReconnection(resolve, reject);
|
|
||||||
} else {
|
|
||||||
this._autoReconnect = false;
|
|
||||||
this.emit('error', error);
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理连接断开
|
|
||||||
*
|
|
||||||
* @param reason - 断开原因
|
|
||||||
*/
|
|
||||||
private handleDisconnection(reason?: string): void {
|
|
||||||
const wasConnected = this._isConnected;
|
|
||||||
|
|
||||||
this._isConnected = false;
|
|
||||||
this._connection = null;
|
|
||||||
this._connectTime = 0;
|
|
||||||
|
|
||||||
this.emit('disconnected', reason);
|
|
||||||
|
|
||||||
// 如果启用自动重连且之前是连接状态
|
|
||||||
if (this._autoReconnect && wasConnected && this._reconnectAttempts < this._maxReconnectAttempts) {
|
|
||||||
this.scheduleReconnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 安排重连
|
|
||||||
*
|
|
||||||
* @param resolve - Promise resolve函数(可选)
|
|
||||||
* @param reject - Promise reject函数(可选)
|
|
||||||
*/
|
|
||||||
private scheduleReconnection(resolve?: () => void, reject?: (error: Error) => void): void {
|
|
||||||
this._reconnectAttempts++;
|
|
||||||
|
|
||||||
// 计算重连延迟(指数退避)
|
|
||||||
const delay = Math.min(
|
|
||||||
this._reconnectDelay * Math.pow(2, this._reconnectAttempts - 1),
|
|
||||||
this._maxReconnectDelay
|
|
||||||
);
|
|
||||||
|
|
||||||
NetworkClient.logger.info(`${delay}ms后尝试重连 (${this._reconnectAttempts}/${this._maxReconnectAttempts})`);
|
|
||||||
this.emit('reconnecting', this._reconnectAttempts);
|
|
||||||
|
|
||||||
this._reconnectTimer = setTimeout(async () => {
|
|
||||||
this._reconnectTimer = null;
|
|
||||||
this._isConnecting = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.attemptConnection();
|
|
||||||
if (resolve) resolve();
|
|
||||||
} catch (error) {
|
|
||||||
if (reject) reject(error as Error);
|
|
||||||
}
|
|
||||||
}, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 断开连接
|
|
||||||
*
|
|
||||||
* @param reason - 断开原因
|
|
||||||
*/
|
|
||||||
public async disconnect(reason: string = 'Disconnected by client'): Promise<void> {
|
|
||||||
// 停止自动重连
|
|
||||||
this._autoReconnect = false;
|
|
||||||
|
|
||||||
// 清除重连定时器
|
|
||||||
if (this._reconnectTimer) {
|
|
||||||
clearTimeout(this._reconnectTimer);
|
|
||||||
this._reconnectTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭连接
|
|
||||||
if (this._connection) {
|
|
||||||
this._connection.close(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置状态
|
|
||||||
this._isConnected = false;
|
|
||||||
this._isConnecting = false;
|
|
||||||
this._connection = null;
|
|
||||||
this._connectTime = 0;
|
|
||||||
this._reconnectAttempts = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息
|
|
||||||
*
|
|
||||||
* @param data - 要发送的数据
|
|
||||||
* @returns 是否发送成功
|
|
||||||
*/
|
|
||||||
public send(data: Uint8Array): boolean {
|
|
||||||
if (!this._connection || !this._isConnected) {
|
|
||||||
NetworkClient.logger.warn('未连接,无法发送数据');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const success = this._connection.send(data);
|
|
||||||
if (success) {
|
|
||||||
this.recordMessagePerformance(data, true);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动触发重连
|
|
||||||
*
|
|
||||||
* @returns Promise<void>
|
|
||||||
*/
|
|
||||||
public async reconnect(): Promise<void> {
|
|
||||||
if (this._isConnected) {
|
|
||||||
await this.disconnect('Manual reconnect');
|
|
||||||
}
|
|
||||||
|
|
||||||
this._autoReconnect = true;
|
|
||||||
this._reconnectAttempts = 0;
|
|
||||||
|
|
||||||
return this.connect(this._url, this._autoReconnect);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置自动重连配置
|
|
||||||
*
|
|
||||||
* @param enabled - 是否启用自动重连
|
|
||||||
* @param maxAttempts - 最大重连次数
|
|
||||||
* @param initialDelay - 初始重连延迟(毫秒)
|
|
||||||
* @param maxDelay - 最大重连延迟(毫秒)
|
|
||||||
*/
|
|
||||||
public setReconnectConfig(
|
|
||||||
enabled: boolean = true,
|
|
||||||
maxAttempts: number = 5,
|
|
||||||
initialDelay: number = 1000,
|
|
||||||
maxDelay: number = 30000
|
|
||||||
): void {
|
|
||||||
this._autoReconnect = enabled;
|
|
||||||
this._maxReconnectAttempts = maxAttempts;
|
|
||||||
this._reconnectDelay = initialDelay;
|
|
||||||
this._maxReconnectDelay = maxDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加事件监听器
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param handler - 事件处理函数
|
|
||||||
*/
|
|
||||||
public on<K extends keyof NetworkClientEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: NetworkClientEvents[K]
|
|
||||||
): void {
|
|
||||||
if (!this._eventHandlers.has(event)) {
|
|
||||||
this._eventHandlers.set(event, []);
|
|
||||||
}
|
|
||||||
this._eventHandlers.get(event)!.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除事件监听器
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param handler - 事件处理函数
|
|
||||||
*/
|
|
||||||
public off<K extends keyof NetworkClientEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: NetworkClientEvents[K]
|
|
||||||
): void {
|
|
||||||
const handlers = this._eventHandlers.get(event);
|
|
||||||
if (handlers) {
|
|
||||||
const index = handlers.indexOf(handler);
|
|
||||||
if (index !== -1) {
|
|
||||||
handlers.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 触发事件
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param args - 事件参数
|
|
||||||
*/
|
|
||||||
private emit<K extends keyof NetworkClientEvents>(
|
|
||||||
event: K,
|
|
||||||
...args: Parameters<NetworkClientEvents[K]>
|
|
||||||
): void {
|
|
||||||
const handlers = this._eventHandlers.get(event);
|
|
||||||
if (handlers) {
|
|
||||||
handlers.forEach(handler => {
|
|
||||||
try {
|
|
||||||
handler(...args);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkClient.logger.error(`事件处理器错误 (${event}):`, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否已连接
|
|
||||||
*/
|
|
||||||
public get isConnected(): boolean {
|
|
||||||
return this._isConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否正在连接
|
|
||||||
*/
|
|
||||||
public get isConnecting(): boolean {
|
|
||||||
return this._isConnecting;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务端URL
|
|
||||||
*/
|
|
||||||
public get url(): string {
|
|
||||||
return this._url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接时长(毫秒)
|
|
||||||
*/
|
|
||||||
public get connectedTime(): number {
|
|
||||||
return this._connectTime > 0 ? Date.now() - this._connectTime : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取重连次数
|
|
||||||
*/
|
|
||||||
public get reconnectAttempts(): number {
|
|
||||||
return this._reconnectAttempts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接对象
|
|
||||||
*/
|
|
||||||
public get connection(): NetworkConnection | null {
|
|
||||||
return this._connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取客户端统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
isConnected: boolean;
|
|
||||||
isConnecting: boolean;
|
|
||||||
url: string;
|
|
||||||
connectedTime: number;
|
|
||||||
reconnectAttempts: number;
|
|
||||||
maxReconnectAttempts: number;
|
|
||||||
autoReconnect: boolean;
|
|
||||||
connectionStats?: ReturnType<NetworkConnection['getStats']>;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
isConnected: this._isConnected,
|
|
||||||
isConnecting: this._isConnecting,
|
|
||||||
url: this._url,
|
|
||||||
connectedTime: this.connectedTime,
|
|
||||||
reconnectAttempts: this._reconnectAttempts,
|
|
||||||
maxReconnectAttempts: this._maxReconnectAttempts,
|
|
||||||
autoReconnect: this._autoReconnect,
|
|
||||||
connectionStats: this._connection ? this._connection.getStats() : undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送SyncVar更新消息到服务端
|
|
||||||
*
|
|
||||||
* @param message - SyncVar更新消息
|
|
||||||
* @returns 是否发送成功
|
|
||||||
*/
|
|
||||||
public sendSyncVarMessage(message: SyncVarUpdateMessage): boolean {
|
|
||||||
try {
|
|
||||||
const serializedMessage = message.serialize();
|
|
||||||
const success = this.send(serializedMessage);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
NetworkClient.logger.debug(`发送SyncVar消息: ${message.networkId}.${message.componentType}`);
|
|
||||||
} else {
|
|
||||||
NetworkClient.logger.warn(`SyncVar消息发送失败: ${message.networkId}.${message.componentType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
} catch (error) {
|
|
||||||
NetworkClient.logger.error('发送SyncVar消息失赅:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量发送SyncVar更新消息
|
|
||||||
*
|
|
||||||
* @param messages - SyncVar更新消息数组
|
|
||||||
* @returns 成功发送的消息数量
|
|
||||||
*/
|
|
||||||
public sendSyncVarMessages(messages: SyncVarUpdateMessage[]): number {
|
|
||||||
let successCount = 0;
|
|
||||||
|
|
||||||
for (const message of messages) {
|
|
||||||
if (this.sendSyncVarMessage(message)) {
|
|
||||||
successCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkClient.logger.debug(`批量发送SyncVar消息: ${successCount}/${messages.length} 成功`);
|
|
||||||
return successCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录消息传输性能
|
|
||||||
*/
|
|
||||||
private recordMessagePerformance(data: Uint8Array, sent: boolean): void {
|
|
||||||
const size = data.length;
|
|
||||||
if (sent) {
|
|
||||||
this._performanceMonitor.recordDataTransfer(size, 0);
|
|
||||||
this._performanceMonitor.recordMessageTransfer(1, 0);
|
|
||||||
} else {
|
|
||||||
this._performanceMonitor.recordDataTransfer(0, size);
|
|
||||||
this._performanceMonitor.recordMessageTransfer(0, 1);
|
|
||||||
}
|
|
||||||
this._performanceMonitor.updateActiveConnections(this._isConnected ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取性能监控数据
|
|
||||||
*/
|
|
||||||
public getPerformanceMetrics(): any {
|
|
||||||
return this._performanceMonitor.getCurrentMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取性能报告
|
|
||||||
*/
|
|
||||||
public getPerformanceReport(timeRangeMs?: number): any {
|
|
||||||
return this._performanceMonitor.generateReport(timeRangeMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,484 +0,0 @@
|
|||||||
import WebSocket from 'isomorphic-ws';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络连接状态
|
|
||||||
*/
|
|
||||||
export enum ConnectionState {
|
|
||||||
Disconnected = 'disconnected',
|
|
||||||
Connecting = 'connecting',
|
|
||||||
Connected = 'connected',
|
|
||||||
Disconnecting = 'disconnecting'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络连接事件
|
|
||||||
*/
|
|
||||||
export interface NetworkConnectionEvents {
|
|
||||||
connected: () => 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络连接抽象类
|
|
||||||
*
|
|
||||||
* 封装WebSocket连接,提供统一的连接管理接口
|
|
||||||
* 支持二进制消息传输,集成心跳检测
|
|
||||||
*/
|
|
||||||
export class NetworkConnection {
|
|
||||||
private static readonly logger = createLogger('NetworkConnection');
|
|
||||||
private _ws: WebSocket | null = null;
|
|
||||||
private _state: ConnectionState = ConnectionState.Disconnected;
|
|
||||||
private _connectionId: string = '';
|
|
||||||
private _address: string = '';
|
|
||||||
private _connectedTime: number = 0;
|
|
||||||
private _lastPingTime: number = 0;
|
|
||||||
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 = '',
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置WebSocket事件监听
|
|
||||||
*/
|
|
||||||
private setupWebSocket(): void {
|
|
||||||
if (!this._ws) return;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
const error = new Error(`WebSocket error: ${event.toString()}`);
|
|
||||||
this.emit('error', error);
|
|
||||||
};
|
|
||||||
|
|
||||||
this._ws.onmessage = (event) => {
|
|
||||||
try {
|
|
||||||
let data: Uint8Array;
|
|
||||||
|
|
||||||
if (event.data instanceof ArrayBuffer) {
|
|
||||||
data = new Uint8Array(event.data);
|
|
||||||
} else if (event.data instanceof Uint8Array) {
|
|
||||||
data = event.data;
|
|
||||||
} else if (typeof event.data === 'string') {
|
|
||||||
// 处理字符串消息(如心跳)
|
|
||||||
if (event.data === 'pong') {
|
|
||||||
this._lastPingTime = Date.now();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 将字符串转换为Uint8Array
|
|
||||||
data = new TextEncoder().encode(event.data);
|
|
||||||
} else {
|
|
||||||
NetworkConnection.logger.warn(' 收到未知类型的消息:', typeof event.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('message', data);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkConnection.logger.error(' 消息处理错误:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动心跳检测
|
|
||||||
*/
|
|
||||||
private startPingInterval(): void {
|
|
||||||
this._pingInterval = setInterval(() => {
|
|
||||||
if (this._state === ConnectionState.Connected) {
|
|
||||||
this.ping();
|
|
||||||
}
|
|
||||||
}, NetworkConnection.PING_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止心跳检测
|
|
||||||
*/
|
|
||||||
private stopPingInterval(): void {
|
|
||||||
if (this._pingInterval) {
|
|
||||||
clearInterval(this._pingInterval);
|
|
||||||
this._pingInterval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送心跳包
|
|
||||||
*/
|
|
||||||
private ping(): void {
|
|
||||||
if (this._ws && this._state === ConnectionState.Connected) {
|
|
||||||
try {
|
|
||||||
this._ws.send('ping');
|
|
||||||
} catch (error) {
|
|
||||||
NetworkConnection.logger.error(' 心跳发送失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送二进制数据
|
|
||||||
*
|
|
||||||
* @param data - 要发送的数据
|
|
||||||
* @returns 是否发送成功
|
|
||||||
*/
|
|
||||||
public send(data: Uint8Array): boolean {
|
|
||||||
if (!this._ws || this._state !== ConnectionState.Connected) {
|
|
||||||
NetworkConnection.logger.warn(' 连接未就绪,无法发送数据');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this._ws.send(data);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
NetworkConnection.logger.error(' 数据发送失败:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否应该重连
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭连接
|
|
||||||
*
|
|
||||||
* @param reason - 关闭原因
|
|
||||||
*/
|
|
||||||
public close(reason: string = 'Connection closed by local'): void {
|
|
||||||
if (this._state === ConnectionState.Disconnected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._state = ConnectionState.Disconnecting;
|
|
||||||
|
|
||||||
// 主动关闭时禁用自动重连
|
|
||||||
this._enableAutoReconnect = false;
|
|
||||||
this.resetReconnectState();
|
|
||||||
|
|
||||||
this.stopPingInterval();
|
|
||||||
|
|
||||||
if (this._ws) {
|
|
||||||
try {
|
|
||||||
this._ws.close(1000, reason);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkConnection.logger.error(' 连接关闭失败:', error);
|
|
||||||
}
|
|
||||||
this._ws = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加事件监听器
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param handler - 事件处理函数
|
|
||||||
*/
|
|
||||||
public on<K extends keyof NetworkConnectionEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: NetworkConnectionEvents[K]
|
|
||||||
): void {
|
|
||||||
if (!this._eventHandlers.has(event)) {
|
|
||||||
this._eventHandlers.set(event, []);
|
|
||||||
}
|
|
||||||
this._eventHandlers.get(event)!.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除事件监听器
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param handler - 事件处理函数
|
|
||||||
*/
|
|
||||||
public off<K extends keyof NetworkConnectionEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: NetworkConnectionEvents[K]
|
|
||||||
): void {
|
|
||||||
const handlers = this._eventHandlers.get(event);
|
|
||||||
if (handlers) {
|
|
||||||
const index = handlers.indexOf(handler);
|
|
||||||
if (index !== -1) {
|
|
||||||
handlers.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 触发事件
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param args - 事件参数
|
|
||||||
*/
|
|
||||||
private emit<K extends keyof NetworkConnectionEvents>(
|
|
||||||
event: K,
|
|
||||||
...args: Parameters<NetworkConnectionEvents[K]>
|
|
||||||
): void {
|
|
||||||
const handlers = this._eventHandlers.get(event);
|
|
||||||
if (handlers) {
|
|
||||||
handlers.forEach(handler => {
|
|
||||||
try {
|
|
||||||
handler(...args);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkConnection.logger.error(`事件处理器错误 (${event}):`, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接ID
|
|
||||||
*/
|
|
||||||
public get connectionId(): string {
|
|
||||||
return this._connectionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接地址
|
|
||||||
*/
|
|
||||||
public get address(): string {
|
|
||||||
return this._address;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接状态
|
|
||||||
*/
|
|
||||||
public get state(): ConnectionState {
|
|
||||||
return this._state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否已连接
|
|
||||||
*/
|
|
||||||
public get isConnected(): boolean {
|
|
||||||
return this._state === ConnectionState.Connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接时长(毫秒)
|
|
||||||
*/
|
|
||||||
public get connectedTime(): number {
|
|
||||||
return this._connectedTime > 0 ? Date.now() - this._connectedTime : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取最后一次心跳时间
|
|
||||||
*/
|
|
||||||
public get lastPingTime(): number {
|
|
||||||
return this._lastPingTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
connectionId: string;
|
|
||||||
address: string;
|
|
||||||
state: ConnectionState;
|
|
||||||
connectedTime: number;
|
|
||||||
lastPingTime: number;
|
|
||||||
isAlive: boolean;
|
|
||||||
} {
|
|
||||||
const now = Date.now();
|
|
||||||
const isAlive = this._state === ConnectionState.Connected &&
|
|
||||||
(this._lastPingTime === 0 || (now - this._lastPingTime) < NetworkConnection.PING_TIMEOUT * 2);
|
|
||||||
|
|
||||||
return {
|
|
||||||
connectionId: this._connectionId,
|
|
||||||
address: this._address,
|
|
||||||
state: this._state,
|
|
||||||
connectedTime: this.connectedTime,
|
|
||||||
lastPingTime: this._lastPingTime,
|
|
||||||
isAlive
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
import { NetworkRole } from '../NetworkRole';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络环境状态
|
|
||||||
*/
|
|
||||||
export enum NetworkEnvironmentState {
|
|
||||||
/** 未初始化状态 */
|
|
||||||
None = 'none',
|
|
||||||
/** 服务端模式 */
|
|
||||||
Server = 'server',
|
|
||||||
/** 客户端模式 */
|
|
||||||
Client = 'client',
|
|
||||||
/** 混合模式(既是服务端又是客户端,用于特殊场景) */
|
|
||||||
Hybrid = 'hybrid'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络环境管理器
|
|
||||||
*
|
|
||||||
* 全局管理当前网络环境状态,让NetworkComponent能够自动检测角色
|
|
||||||
* 避免在构造函数中传递角色参数,保持与核心ECS框架的兼容性
|
|
||||||
*/
|
|
||||||
export class NetworkEnvironment {
|
|
||||||
private static readonly logger = createLogger('NetworkEnvironment');
|
|
||||||
private static _instance: NetworkEnvironment | null = null;
|
|
||||||
private _state: NetworkEnvironmentState = NetworkEnvironmentState.None;
|
|
||||||
private _serverStartTime: number = 0;
|
|
||||||
private _clientConnectTime: number = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取NetworkEnvironment单例实例
|
|
||||||
*/
|
|
||||||
public static get Instance(): NetworkEnvironment {
|
|
||||||
if (!NetworkEnvironment._instance) {
|
|
||||||
NetworkEnvironment._instance = new NetworkEnvironment();
|
|
||||||
}
|
|
||||||
return NetworkEnvironment._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置为服务端模式
|
|
||||||
*/
|
|
||||||
public static SetServerMode(): void {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
|
|
||||||
if (instance._state === NetworkEnvironmentState.Client) {
|
|
||||||
// 如果已经是客户端,则变为混合模式
|
|
||||||
instance._state = NetworkEnvironmentState.Hybrid;
|
|
||||||
} else {
|
|
||||||
instance._state = NetworkEnvironmentState.Server;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance._serverStartTime = Date.now();
|
|
||||||
NetworkEnvironment.logger.info(`环境设置为: ${instance._state}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置为客户端模式
|
|
||||||
*/
|
|
||||||
public static SetClientMode(): void {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
|
|
||||||
if (instance._state === NetworkEnvironmentState.Server) {
|
|
||||||
// 如果已经是服务端,则变为混合模式
|
|
||||||
instance._state = NetworkEnvironmentState.Hybrid;
|
|
||||||
} else {
|
|
||||||
instance._state = NetworkEnvironmentState.Client;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance._clientConnectTime = Date.now();
|
|
||||||
NetworkEnvironment.logger.info(`环境设置为: ${instance._state}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除服务端模式
|
|
||||||
*/
|
|
||||||
public static ClearServerMode(): void {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
|
|
||||||
if (instance._state === NetworkEnvironmentState.Server) {
|
|
||||||
instance._state = NetworkEnvironmentState.None;
|
|
||||||
} else if (instance._state === NetworkEnvironmentState.Hybrid) {
|
|
||||||
instance._state = NetworkEnvironmentState.Client;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance._serverStartTime = 0;
|
|
||||||
NetworkEnvironment.logger.info(`服务端模式已清除,当前状态: ${instance._state}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除客户端模式
|
|
||||||
*/
|
|
||||||
public static ClearClientMode(): void {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
|
|
||||||
if (instance._state === NetworkEnvironmentState.Client) {
|
|
||||||
instance._state = NetworkEnvironmentState.None;
|
|
||||||
} else if (instance._state === NetworkEnvironmentState.Hybrid) {
|
|
||||||
instance._state = NetworkEnvironmentState.Server;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance._clientConnectTime = 0;
|
|
||||||
NetworkEnvironment.logger.info(`客户端模式已清除,当前状态: ${instance._state}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置环境状态
|
|
||||||
*/
|
|
||||||
public static Reset(): void {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
instance._state = NetworkEnvironmentState.None;
|
|
||||||
instance._serverStartTime = 0;
|
|
||||||
instance._clientConnectTime = 0;
|
|
||||||
NetworkEnvironment.logger.info('环境状态已重置');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否为服务端环境
|
|
||||||
*/
|
|
||||||
public static get isServer(): boolean {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
return instance._state === NetworkEnvironmentState.Server ||
|
|
||||||
instance._state === NetworkEnvironmentState.Hybrid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否为客户端环境
|
|
||||||
*/
|
|
||||||
public static get isClient(): boolean {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
return instance._state === NetworkEnvironmentState.Client ||
|
|
||||||
instance._state === NetworkEnvironmentState.Hybrid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否为混合环境
|
|
||||||
*/
|
|
||||||
public static get isHybrid(): boolean {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
return instance._state === NetworkEnvironmentState.Hybrid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前环境状态
|
|
||||||
*/
|
|
||||||
public static get state(): NetworkEnvironmentState {
|
|
||||||
return NetworkEnvironment.Instance._state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取主要角色(用于NetworkComponent)
|
|
||||||
*
|
|
||||||
* 在混合模式下,优先返回服务端角色
|
|
||||||
* @returns 当前主要网络角色
|
|
||||||
*/
|
|
||||||
public static getPrimaryRole(): NetworkRole {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
|
|
||||||
switch (instance._state) {
|
|
||||||
case NetworkEnvironmentState.Server:
|
|
||||||
case NetworkEnvironmentState.Hybrid:
|
|
||||||
return NetworkRole.SERVER;
|
|
||||||
case NetworkEnvironmentState.Client:
|
|
||||||
return NetworkRole.CLIENT;
|
|
||||||
case NetworkEnvironmentState.None:
|
|
||||||
default:
|
|
||||||
// 默认返回客户端角色,避免抛出异常
|
|
||||||
return NetworkRole.CLIENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查环境是否已初始化
|
|
||||||
*/
|
|
||||||
public static get isInitialized(): boolean {
|
|
||||||
return NetworkEnvironment.Instance._state !== NetworkEnvironmentState.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务端运行时间(毫秒)
|
|
||||||
*/
|
|
||||||
public static get serverUptime(): number {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
return instance._serverStartTime > 0 ? Date.now() - instance._serverStartTime : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取客户端连接时间(毫秒)
|
|
||||||
*/
|
|
||||||
public static get clientConnectTime(): number {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
return instance._clientConnectTime > 0 ? Date.now() - instance._clientConnectTime : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取环境统计信息
|
|
||||||
*/
|
|
||||||
public static getStats(): {
|
|
||||||
state: NetworkEnvironmentState;
|
|
||||||
isServer: boolean;
|
|
||||||
isClient: boolean;
|
|
||||||
isHybrid: boolean;
|
|
||||||
isInitialized: boolean;
|
|
||||||
primaryRole: NetworkRole;
|
|
||||||
serverUptime: number;
|
|
||||||
clientConnectTime: number;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
state: NetworkEnvironment.state,
|
|
||||||
isServer: NetworkEnvironment.isServer,
|
|
||||||
isClient: NetworkEnvironment.isClient,
|
|
||||||
isHybrid: NetworkEnvironment.isHybrid,
|
|
||||||
isInitialized: NetworkEnvironment.isInitialized,
|
|
||||||
primaryRole: NetworkEnvironment.getPrimaryRole(),
|
|
||||||
serverUptime: NetworkEnvironment.serverUptime,
|
|
||||||
clientConnectTime: NetworkEnvironment.clientConnectTime
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制设置环境状态(用于测试)
|
|
||||||
*
|
|
||||||
* @param state - 要设置的环境状态
|
|
||||||
* @param serverStartTime - 服务端启动时间(可选)
|
|
||||||
* @param clientConnectTime - 客户端连接时间(可选)
|
|
||||||
*/
|
|
||||||
public static forceSetState(
|
|
||||||
state: NetworkEnvironmentState,
|
|
||||||
serverStartTime?: number,
|
|
||||||
clientConnectTime?: number
|
|
||||||
): void {
|
|
||||||
const instance = NetworkEnvironment.Instance;
|
|
||||||
instance._state = state;
|
|
||||||
|
|
||||||
if (serverStartTime !== undefined) {
|
|
||||||
instance._serverStartTime = serverStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientConnectTime !== undefined) {
|
|
||||||
instance._clientConnectTime = clientConnectTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkEnvironment.logger.debug(`强制设置环境状态为: ${state}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,481 +0,0 @@
|
|||||||
import { Component, createLogger } from '@esengine/ecs-framework';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络对象身份组件
|
|
||||||
*
|
|
||||||
* 为ECS实体提供网络身份标识,支持网络对象的唯一识别和管理
|
|
||||||
* 每个需要网络同步的实体都必须拥有此组件
|
|
||||||
*/
|
|
||||||
export class NetworkIdentity extends Component {
|
|
||||||
private static readonly logger = createLogger('NetworkIdentity');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络对象唯一ID
|
|
||||||
*/
|
|
||||||
public readonly networkId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为服务端权威对象
|
|
||||||
*/
|
|
||||||
public hasAuthority: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对象拥有者的连接ID(客户端ID)
|
|
||||||
*/
|
|
||||||
public ownerId: string = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络对象是否处于激活状态
|
|
||||||
*/
|
|
||||||
public isNetworkActive: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对象创建时间
|
|
||||||
*/
|
|
||||||
public readonly createdAt: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 最后同步时间
|
|
||||||
*/
|
|
||||||
public lastSyncTime: number = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步序列号(用于确保消息顺序)
|
|
||||||
*/
|
|
||||||
public syncSequence: number = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络对象场景标识(用于场景管理)
|
|
||||||
*/
|
|
||||||
public sceneId: string = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为预制体实例
|
|
||||||
*/
|
|
||||||
public isPrefabInstance: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 预制体名称(如果是预制体实例)
|
|
||||||
*/
|
|
||||||
public prefabName: string = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造函数
|
|
||||||
*
|
|
||||||
* @param networkId - 指定网络ID,如果不提供则自动生成
|
|
||||||
* @param hasAuthority - 是否有权威,默认false
|
|
||||||
*/
|
|
||||||
constructor(networkId?: string, hasAuthority: boolean = false) {
|
|
||||||
super();
|
|
||||||
this.networkId = networkId || uuidv4();
|
|
||||||
this.hasAuthority = hasAuthority;
|
|
||||||
this.createdAt = Date.now();
|
|
||||||
this.lastSyncTime = this.createdAt;
|
|
||||||
|
|
||||||
// 自动注册到NetworkIdentityRegistry
|
|
||||||
NetworkIdentityRegistry.Instance.register(this);
|
|
||||||
|
|
||||||
NetworkIdentity.logger.debug(` 创建网络对象: ${this.networkId}, 权威: ${hasAuthority}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置对象拥有者
|
|
||||||
*
|
|
||||||
* @param ownerId - 拥有者连接ID
|
|
||||||
*/
|
|
||||||
public setOwner(ownerId: string): void {
|
|
||||||
const oldOwnerId = this.ownerId;
|
|
||||||
this.ownerId = ownerId;
|
|
||||||
|
|
||||||
NetworkIdentity.logger.debug(` 对象 ${this.networkId} 拥有者变更: ${oldOwnerId} -> ${ownerId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置权威状态
|
|
||||||
*
|
|
||||||
* @param hasAuthority - 是否有权威
|
|
||||||
*/
|
|
||||||
public setAuthority(hasAuthority: boolean): void {
|
|
||||||
if (this.hasAuthority !== hasAuthority) {
|
|
||||||
this.hasAuthority = hasAuthority;
|
|
||||||
NetworkIdentity.logger.debug(` 对象 ${this.networkId} 权威状态变更: ${hasAuthority}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 激活网络对象
|
|
||||||
*/
|
|
||||||
public activate(): void {
|
|
||||||
if (!this.isNetworkActive) {
|
|
||||||
this.isNetworkActive = true;
|
|
||||||
NetworkIdentity.logger.debug(` 激活网络对象: ${this.networkId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停用网络对象
|
|
||||||
*/
|
|
||||||
public deactivate(): void {
|
|
||||||
if (this.isNetworkActive) {
|
|
||||||
this.isNetworkActive = false;
|
|
||||||
NetworkIdentity.logger.debug(` 停用网络对象: ${this.networkId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取下一个同步序列号
|
|
||||||
*/
|
|
||||||
public getNextSyncSequence(): number {
|
|
||||||
return ++this.syncSequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新最后同步时间
|
|
||||||
*/
|
|
||||||
public updateSyncTime(): void {
|
|
||||||
this.lastSyncTime = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否需要同步
|
|
||||||
*
|
|
||||||
* @param maxInterval - 最大同步间隔(毫秒)
|
|
||||||
*/
|
|
||||||
public needsSync(maxInterval: number = 1000): boolean {
|
|
||||||
return Date.now() - this.lastSyncTime > maxInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取网络对象统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
networkId: string;
|
|
||||||
hasAuthority: boolean;
|
|
||||||
ownerId: string;
|
|
||||||
isActive: boolean;
|
|
||||||
createdAt: number;
|
|
||||||
lastSyncTime: number;
|
|
||||||
syncSequence: number;
|
|
||||||
age: number;
|
|
||||||
timeSinceLastSync: number;
|
|
||||||
} {
|
|
||||||
const now = Date.now();
|
|
||||||
return {
|
|
||||||
networkId: this.networkId,
|
|
||||||
hasAuthority: this.hasAuthority,
|
|
||||||
ownerId: this.ownerId,
|
|
||||||
isActive: this.isNetworkActive,
|
|
||||||
createdAt: this.createdAt,
|
|
||||||
lastSyncTime: this.lastSyncTime,
|
|
||||||
syncSequence: this.syncSequence,
|
|
||||||
age: now - this.createdAt,
|
|
||||||
timeSinceLastSync: now - this.lastSyncTime
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理网络对象
|
|
||||||
*/
|
|
||||||
public cleanup(): void {
|
|
||||||
NetworkIdentityRegistry.Instance.unregister(this.networkId);
|
|
||||||
this.deactivate();
|
|
||||||
NetworkIdentity.logger.debug(` 清理网络对象: ${this.networkId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络身份注册表
|
|
||||||
*
|
|
||||||
* 管理所有网络对象的注册和查找
|
|
||||||
*/
|
|
||||||
export class NetworkIdentityRegistry {
|
|
||||||
private static readonly logger = createLogger('NetworkIdentityRegistry');
|
|
||||||
private static _instance: NetworkIdentityRegistry | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络对象注册表
|
|
||||||
* Key: networkId, Value: NetworkIdentity实例
|
|
||||||
*/
|
|
||||||
private _identities: Map<string, NetworkIdentity> = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按拥有者分组的对象
|
|
||||||
* Key: ownerId, Value: NetworkIdentity集合
|
|
||||||
*/
|
|
||||||
private _ownerObjects: Map<string, Set<NetworkIdentity>> = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权威对象集合
|
|
||||||
*/
|
|
||||||
private _authorityObjects: Set<NetworkIdentity> = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取注册表单例
|
|
||||||
*/
|
|
||||||
public static get Instance(): NetworkIdentityRegistry {
|
|
||||||
if (!NetworkIdentityRegistry._instance) {
|
|
||||||
NetworkIdentityRegistry._instance = new NetworkIdentityRegistry();
|
|
||||||
}
|
|
||||||
return NetworkIdentityRegistry._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册网络对象
|
|
||||||
*
|
|
||||||
* @param identity - 网络身份组件
|
|
||||||
*/
|
|
||||||
public register(identity: NetworkIdentity): void {
|
|
||||||
if (this._identities.has(identity.networkId)) {
|
|
||||||
NetworkIdentityRegistry.logger.warn(` 网络对象ID重复: ${identity.networkId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._identities.set(identity.networkId, identity);
|
|
||||||
|
|
||||||
// 按拥有者分组
|
|
||||||
if (identity.ownerId) {
|
|
||||||
this.addToOwnerGroup(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 权威对象管理
|
|
||||||
if (identity.hasAuthority) {
|
|
||||||
this._authorityObjects.add(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkIdentityRegistry.logger.debug(` 注册网络对象: ${identity.networkId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注销网络对象
|
|
||||||
*
|
|
||||||
* @param networkId - 网络对象ID
|
|
||||||
*/
|
|
||||||
public unregister(networkId: string): boolean {
|
|
||||||
const identity = this._identities.get(networkId);
|
|
||||||
if (!identity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._identities.delete(networkId);
|
|
||||||
|
|
||||||
// 从拥有者分组中移除
|
|
||||||
if (identity.ownerId) {
|
|
||||||
this.removeFromOwnerGroup(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从权威对象集合中移除
|
|
||||||
this._authorityObjects.delete(identity);
|
|
||||||
|
|
||||||
NetworkIdentityRegistry.logger.debug(` 注销网络对象: ${networkId}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过网络ID查找对象
|
|
||||||
*
|
|
||||||
* @param networkId - 网络对象ID
|
|
||||||
* @returns 网络身份组件或undefined
|
|
||||||
*/
|
|
||||||
public find(networkId: string): NetworkIdentity | undefined {
|
|
||||||
return this._identities.get(networkId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定拥有者的所有对象
|
|
||||||
*
|
|
||||||
* @param ownerId - 拥有者ID
|
|
||||||
* @returns 网络身份组件数组
|
|
||||||
*/
|
|
||||||
public getObjectsByOwner(ownerId: string): NetworkIdentity[] {
|
|
||||||
const ownerObjects = this._ownerObjects.get(ownerId);
|
|
||||||
return ownerObjects ? Array.from(ownerObjects) : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有权威对象
|
|
||||||
*
|
|
||||||
* @returns 权威对象数组
|
|
||||||
*/
|
|
||||||
public getAuthorityObjects(): NetworkIdentity[] {
|
|
||||||
return Array.from(this._authorityObjects);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有激活的网络对象
|
|
||||||
*
|
|
||||||
* @returns 激活的网络对象数组
|
|
||||||
*/
|
|
||||||
public getActiveObjects(): NetworkIdentity[] {
|
|
||||||
return Array.from(this._identities.values()).filter(identity => identity.isNetworkActive);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取需要同步的对象
|
|
||||||
*
|
|
||||||
* @param maxInterval - 最大同步间隔
|
|
||||||
* @returns 需要同步的对象数组
|
|
||||||
*/
|
|
||||||
public getObjectsNeedingSync(maxInterval: number = 1000): NetworkIdentity[] {
|
|
||||||
return this.getActiveObjects().filter(identity => identity.needsSync(maxInterval));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新对象拥有者
|
|
||||||
*
|
|
||||||
* @param networkId - 网络对象ID
|
|
||||||
* @param newOwnerId - 新拥有者ID
|
|
||||||
*/
|
|
||||||
public updateObjectOwner(networkId: string, newOwnerId: string): boolean {
|
|
||||||
const identity = this._identities.get(networkId);
|
|
||||||
if (!identity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从旧拥有者分组中移除
|
|
||||||
if (identity.ownerId) {
|
|
||||||
this.removeFromOwnerGroup(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置新拥有者
|
|
||||||
identity.setOwner(newOwnerId);
|
|
||||||
|
|
||||||
// 添加到新拥有者分组
|
|
||||||
if (newOwnerId) {
|
|
||||||
this.addToOwnerGroup(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新对象权威状态
|
|
||||||
*
|
|
||||||
* @param networkId - 网络对象ID
|
|
||||||
* @param hasAuthority - 是否有权威
|
|
||||||
*/
|
|
||||||
public updateObjectAuthority(networkId: string, hasAuthority: boolean): boolean {
|
|
||||||
const identity = this._identities.get(networkId);
|
|
||||||
if (!identity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldAuthority = identity.hasAuthority;
|
|
||||||
identity.setAuthority(hasAuthority);
|
|
||||||
|
|
||||||
// 更新权威对象集合
|
|
||||||
if (hasAuthority && !oldAuthority) {
|
|
||||||
this._authorityObjects.add(identity);
|
|
||||||
} else if (!hasAuthority && oldAuthority) {
|
|
||||||
this._authorityObjects.delete(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理断开连接客户端的对象
|
|
||||||
*
|
|
||||||
* @param disconnectedOwnerId - 断开连接的客户端ID
|
|
||||||
*/
|
|
||||||
public cleanupDisconnectedOwner(disconnectedOwnerId: string): NetworkIdentity[] {
|
|
||||||
const ownerObjects = this.getObjectsByOwner(disconnectedOwnerId);
|
|
||||||
|
|
||||||
for (const identity of ownerObjects) {
|
|
||||||
// 移除拥有权,但保留对象(由服务端接管)
|
|
||||||
this.updateObjectOwner(identity.networkId, '');
|
|
||||||
identity.setAuthority(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkIdentityRegistry.logger.debug(` 清理断开连接客户端 ${disconnectedOwnerId} 的 ${ownerObjects.length} 个对象`);
|
|
||||||
return ownerObjects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加到拥有者分组
|
|
||||||
*/
|
|
||||||
private addToOwnerGroup(identity: NetworkIdentity): void {
|
|
||||||
if (!identity.ownerId) return;
|
|
||||||
|
|
||||||
let ownerSet = this._ownerObjects.get(identity.ownerId);
|
|
||||||
if (!ownerSet) {
|
|
||||||
ownerSet = new Set();
|
|
||||||
this._ownerObjects.set(identity.ownerId, ownerSet);
|
|
||||||
}
|
|
||||||
ownerSet.add(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从拥有者分组中移除
|
|
||||||
*/
|
|
||||||
private removeFromOwnerGroup(identity: NetworkIdentity): void {
|
|
||||||
if (!identity.ownerId) return;
|
|
||||||
|
|
||||||
const ownerSet = this._ownerObjects.get(identity.ownerId);
|
|
||||||
if (ownerSet) {
|
|
||||||
ownerSet.delete(identity);
|
|
||||||
if (ownerSet.size === 0) {
|
|
||||||
this._ownerObjects.delete(identity.ownerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取注册表统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
totalObjects: number;
|
|
||||||
activeObjects: number;
|
|
||||||
authorityObjects: number;
|
|
||||||
ownerCount: number;
|
|
||||||
averageAge: number;
|
|
||||||
oldestObject?: string;
|
|
||||||
newestObject?: string;
|
|
||||||
} {
|
|
||||||
const all = Array.from(this._identities.values());
|
|
||||||
const active = all.filter(i => i.isNetworkActive);
|
|
||||||
|
|
||||||
let totalAge = 0;
|
|
||||||
let oldestTime = Date.now();
|
|
||||||
let newestTime = 0;
|
|
||||||
let oldestId = '';
|
|
||||||
let newestId = '';
|
|
||||||
|
|
||||||
for (const identity of all) {
|
|
||||||
const age = Date.now() - identity.createdAt;
|
|
||||||
totalAge += age;
|
|
||||||
|
|
||||||
if (identity.createdAt < oldestTime) {
|
|
||||||
oldestTime = identity.createdAt;
|
|
||||||
oldestId = identity.networkId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identity.createdAt > newestTime) {
|
|
||||||
newestTime = identity.createdAt;
|
|
||||||
newestId = identity.networkId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalObjects: all.length,
|
|
||||||
activeObjects: active.length,
|
|
||||||
authorityObjects: this._authorityObjects.size,
|
|
||||||
ownerCount: this._ownerObjects.size,
|
|
||||||
averageAge: all.length > 0 ? totalAge / all.length : 0,
|
|
||||||
oldestObject: oldestId || undefined,
|
|
||||||
newestObject: newestId || undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空注册表
|
|
||||||
*/
|
|
||||||
public clear(): void {
|
|
||||||
this._identities.clear();
|
|
||||||
this._ownerObjects.clear();
|
|
||||||
this._authorityObjects.clear();
|
|
||||||
NetworkIdentityRegistry.logger.info(' 已清空注册表');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
import { NetworkServer } from './NetworkServer';
|
|
||||||
import { NetworkClient } from './NetworkClient';
|
|
||||||
import { NetworkEnvironment } from './NetworkEnvironment';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络管理器 - 网络框架的核心入口
|
|
||||||
*
|
|
||||||
* 负责管理整个网络生命周期
|
|
||||||
* 支持启动服务端、客户端,管理连接状态
|
|
||||||
*/
|
|
||||||
export class NetworkManager {
|
|
||||||
private static readonly _logger = createLogger('NetworkManager');
|
|
||||||
private static _instance: NetworkManager | null = null;
|
|
||||||
private _server: NetworkServer | null = null;
|
|
||||||
private _client: NetworkClient | null = null;
|
|
||||||
private _isServer: boolean = false;
|
|
||||||
private _isClient: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取NetworkManager单例实例
|
|
||||||
*/
|
|
||||||
public static get Instance(): NetworkManager {
|
|
||||||
if (!NetworkManager._instance) {
|
|
||||||
NetworkManager._instance = new NetworkManager();
|
|
||||||
}
|
|
||||||
return NetworkManager._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动服务端
|
|
||||||
*
|
|
||||||
* @param port - 监听端口
|
|
||||||
* @param host - 监听地址,默认为 '0.0.0.0'
|
|
||||||
* @returns Promise<boolean> 启动是否成功
|
|
||||||
*/
|
|
||||||
public static async StartServer(port: number, host: string = '0.0.0.0'): Promise<boolean> {
|
|
||||||
const instance = NetworkManager.Instance;
|
|
||||||
|
|
||||||
if (instance._isServer) {
|
|
||||||
NetworkManager._logger.warn('服务端已经在运行');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
instance._server = new NetworkServer();
|
|
||||||
await instance._server.start(port, host);
|
|
||||||
instance._isServer = true;
|
|
||||||
|
|
||||||
// 自动设置网络环境为服务端模式
|
|
||||||
NetworkEnvironment.SetServerMode();
|
|
||||||
|
|
||||||
NetworkManager._logger.info(`服务端启动成功,监听 ${host}:${port}`);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
NetworkManager._logger.error('服务端启动失败:', error);
|
|
||||||
instance._server = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动客户端
|
|
||||||
*
|
|
||||||
* @param url - 服务端WebSocket地址
|
|
||||||
* @returns Promise<boolean> 连接是否成功
|
|
||||||
*/
|
|
||||||
public static async StartClient(url: string): Promise<boolean> {
|
|
||||||
const instance = NetworkManager.Instance;
|
|
||||||
|
|
||||||
if (instance._isClient) {
|
|
||||||
NetworkManager._logger.warn('客户端已经在运行');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
instance._client = new NetworkClient();
|
|
||||||
await instance._client.connect(url);
|
|
||||||
instance._isClient = true;
|
|
||||||
|
|
||||||
// 自动设置网络环境为客户端模式
|
|
||||||
NetworkEnvironment.SetClientMode();
|
|
||||||
|
|
||||||
NetworkManager._logger.info(`客户端连接成功: ${url}`);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
NetworkManager._logger.error('客户端连接失败:', error);
|
|
||||||
instance._client = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止服务端
|
|
||||||
*/
|
|
||||||
public static async StopServer(): Promise<void> {
|
|
||||||
const instance = NetworkManager.Instance;
|
|
||||||
|
|
||||||
if (instance._server && instance._isServer) {
|
|
||||||
await instance._server.stop();
|
|
||||||
instance._server = null;
|
|
||||||
instance._isServer = false;
|
|
||||||
|
|
||||||
// 清除服务端环境模式
|
|
||||||
NetworkEnvironment.ClearServerMode();
|
|
||||||
|
|
||||||
NetworkManager._logger.info('服务端已停止');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 断开客户端连接
|
|
||||||
*/
|
|
||||||
public static async StopClient(): Promise<void> {
|
|
||||||
const instance = NetworkManager.Instance;
|
|
||||||
|
|
||||||
if (instance._client && instance._isClient) {
|
|
||||||
await instance._client.disconnect();
|
|
||||||
instance._client = null;
|
|
||||||
instance._isClient = false;
|
|
||||||
|
|
||||||
// 清除客户端环境模式
|
|
||||||
NetworkEnvironment.ClearClientMode();
|
|
||||||
|
|
||||||
NetworkManager._logger.info('客户端已断开连接');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 完全停止网络管理器
|
|
||||||
*/
|
|
||||||
public static async Stop(): Promise<void> {
|
|
||||||
await NetworkManager.StopServer();
|
|
||||||
await NetworkManager.StopClient();
|
|
||||||
|
|
||||||
// 重置网络环境
|
|
||||||
NetworkEnvironment.Reset();
|
|
||||||
|
|
||||||
NetworkManager._instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否为服务端
|
|
||||||
*/
|
|
||||||
public static get isServer(): boolean {
|
|
||||||
return NetworkManager.Instance._isServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否为客户端
|
|
||||||
*/
|
|
||||||
public static get isClient(): boolean {
|
|
||||||
return NetworkManager.Instance._isClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务端实例
|
|
||||||
*/
|
|
||||||
public static get server(): NetworkServer | null {
|
|
||||||
return NetworkManager.Instance._server;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务端实例 (方法形式,用于动态调用)
|
|
||||||
*/
|
|
||||||
public static GetServer(): NetworkServer | null {
|
|
||||||
return NetworkManager.Instance._server;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取客户端实例
|
|
||||||
*/
|
|
||||||
public static get client(): NetworkClient | null {
|
|
||||||
return NetworkManager.Instance._client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前连接数(仅服务端有效)
|
|
||||||
*/
|
|
||||||
public static get connectionCount(): number {
|
|
||||||
const instance = NetworkManager.Instance;
|
|
||||||
return instance._server ? instance._server.connectionCount : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取网络统计信息
|
|
||||||
*/
|
|
||||||
public static getNetworkStats(): {
|
|
||||||
isServer: boolean;
|
|
||||||
isClient: boolean;
|
|
||||||
connectionCount: number;
|
|
||||||
serverUptime?: number;
|
|
||||||
clientConnectedTime?: number;
|
|
||||||
} {
|
|
||||||
const instance = NetworkManager.Instance;
|
|
||||||
|
|
||||||
return {
|
|
||||||
isServer: instance._isServer,
|
|
||||||
isClient: instance._isClient,
|
|
||||||
connectionCount: instance._server ? instance._server.connectionCount : 0,
|
|
||||||
serverUptime: instance._server ? instance._server.uptime : undefined,
|
|
||||||
clientConnectedTime: instance._client ? instance._client.connectedTime : undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,858 +0,0 @@
|
|||||||
/**
|
|
||||||
* 网络性能监控器
|
|
||||||
*
|
|
||||||
* 监控网络连接的性能指标,包括延迟、吞吐量、包丢失率等
|
|
||||||
*/
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
import { HeartbeatManager, HeartbeatStats } from './HeartbeatManager';
|
|
||||||
import { NetworkConnection } from './NetworkConnection';
|
|
||||||
|
|
||||||
export interface NetworkMetrics {
|
|
||||||
/** 往返时延 (ms) */
|
|
||||||
rtt: number;
|
|
||||||
/** 延迟 (ms) */
|
|
||||||
latency: number;
|
|
||||||
/** 上行带宽 (bytes/s) */
|
|
||||||
uploadBandwidth: number;
|
|
||||||
/** 下行带宽 (bytes/s) */
|
|
||||||
downloadBandwidth: number;
|
|
||||||
/** 包丢失率 (0-1) */
|
|
||||||
packetLoss: number;
|
|
||||||
/** 抖动 (ms) */
|
|
||||||
jitter: number;
|
|
||||||
/** 连接质量评分 (0-100) */
|
|
||||||
connectionQuality: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PerformanceSnapshot {
|
|
||||||
/** 时间戳 */
|
|
||||||
timestamp: number;
|
|
||||||
/** 网络指标 */
|
|
||||||
metrics: NetworkMetrics;
|
|
||||||
/** 连接统计 */
|
|
||||||
connectionStats: {
|
|
||||||
/** 发送的总字节数 */
|
|
||||||
bytesSent: number;
|
|
||||||
/** 接收的总字节数 */
|
|
||||||
bytesReceived: number;
|
|
||||||
/** 发送的总消息数 */
|
|
||||||
messagesSent: number;
|
|
||||||
/** 接收的总消息数 */
|
|
||||||
messagesReceived: number;
|
|
||||||
/** 活跃连接数 */
|
|
||||||
activeConnections: number;
|
|
||||||
};
|
|
||||||
/** SyncVar同步统计 */
|
|
||||||
syncVarStats?: {
|
|
||||||
/** 同步的组件数 */
|
|
||||||
syncedComponents: number;
|
|
||||||
/** 同步的字段数 */
|
|
||||||
syncedFields: number;
|
|
||||||
/** 平均同步频率 (Hz) */
|
|
||||||
averageSyncRate: number;
|
|
||||||
/** 同步数据大小 (bytes) */
|
|
||||||
syncDataSize: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络性能监控器
|
|
||||||
*/
|
|
||||||
export class NetworkPerformanceMonitor {
|
|
||||||
private static readonly logger = createLogger('NetworkPerformanceMonitor');
|
|
||||||
private static _instance: NetworkPerformanceMonitor | null = null;
|
|
||||||
|
|
||||||
/** 性能快照历史 */
|
|
||||||
private _snapshots: PerformanceSnapshot[] = [];
|
|
||||||
|
|
||||||
/** 最大历史记录数量 */
|
|
||||||
private _maxSnapshots: number = 100;
|
|
||||||
|
|
||||||
/** 监控间隔 (ms) */
|
|
||||||
private _monitoringInterval: number = 1000;
|
|
||||||
|
|
||||||
/** 监控定时器 */
|
|
||||||
private _monitoringTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** 是否正在监控 */
|
|
||||||
private _isMonitoring: boolean = false;
|
|
||||||
|
|
||||||
/** RTT测量历史 */
|
|
||||||
private _rttHistory: number[] = [];
|
|
||||||
|
|
||||||
/** 最大RTT历史长度 */
|
|
||||||
private _maxRttHistory: number = 20;
|
|
||||||
|
|
||||||
/** 带宽测量窗口 */
|
|
||||||
private _bandwidthWindow: {
|
|
||||||
timestamp: number;
|
|
||||||
uploadBytes: number;
|
|
||||||
downloadBytes: number;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
/** 带宽测量窗口大小 */
|
|
||||||
private _bandwidthWindowSize: number = 10;
|
|
||||||
|
|
||||||
/** 连接统计 */
|
|
||||||
private _connectionStats = {
|
|
||||||
bytesSent: 0,
|
|
||||||
bytesReceived: 0,
|
|
||||||
messagesSent: 0,
|
|
||||||
messagesReceived: 0,
|
|
||||||
activeConnections: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 事件监听器 */
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
return NetworkPerformanceMonitor._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始性能监控
|
|
||||||
*
|
|
||||||
* @param interval - 监控间隔 (ms)
|
|
||||||
*/
|
|
||||||
public startMonitoring(interval: number = 1000): void {
|
|
||||||
if (this._isMonitoring) {
|
|
||||||
NetworkPerformanceMonitor.logger.warn('监控已在运行');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._monitoringInterval = interval;
|
|
||||||
this._isMonitoring = true;
|
|
||||||
|
|
||||||
this._monitoringTimer = setInterval(() => {
|
|
||||||
this.collectMetrics();
|
|
||||||
}, this._monitoringInterval);
|
|
||||||
|
|
||||||
NetworkPerformanceMonitor.logger.info(`开始性能监控,间隔: ${interval}ms`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止性能监控
|
|
||||||
*/
|
|
||||||
public stopMonitoring(): void {
|
|
||||||
if (!this._isMonitoring) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._monitoringTimer) {
|
|
||||||
clearInterval(this._monitoringTimer);
|
|
||||||
this._monitoringTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isMonitoring = false;
|
|
||||||
NetworkPerformanceMonitor.logger.info('停止性能监控');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集网络性能指标
|
|
||||||
*/
|
|
||||||
private collectMetrics(): void {
|
|
||||||
const timestamp = Date.now();
|
|
||||||
|
|
||||||
// 计算网络指标
|
|
||||||
const metrics = this.calculateNetworkMetrics();
|
|
||||||
|
|
||||||
// 获取SyncVar统计
|
|
||||||
const syncVarStats = this.getSyncVarStatistics();
|
|
||||||
|
|
||||||
// 创建性能快照
|
|
||||||
const snapshot: PerformanceSnapshot = {
|
|
||||||
timestamp,
|
|
||||||
metrics,
|
|
||||||
connectionStats: { ...this._connectionStats },
|
|
||||||
syncVarStats
|
|
||||||
};
|
|
||||||
|
|
||||||
// 添加到历史记录
|
|
||||||
this._snapshots.push(snapshot);
|
|
||||||
|
|
||||||
// 限制历史记录数量
|
|
||||||
if (this._snapshots.length > this._maxSnapshots) {
|
|
||||||
this._snapshots.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 触发监控事件
|
|
||||||
this.emit('metricsCollected', snapshot);
|
|
||||||
|
|
||||||
// 检查性能警告
|
|
||||||
this.checkPerformanceWarnings(metrics);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算网络指标
|
|
||||||
*/
|
|
||||||
private calculateNetworkMetrics(): NetworkMetrics {
|
|
||||||
// 计算RTT
|
|
||||||
const avgRtt = this._rttHistory.length > 0
|
|
||||||
? this._rttHistory.reduce((a, b) => a + b, 0) / this._rttHistory.length
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// 计算抖动
|
|
||||||
const jitter = this.calculateJitter();
|
|
||||||
|
|
||||||
// 计算带宽
|
|
||||||
const { uploadBandwidth, downloadBandwidth } = this.calculateBandwidth();
|
|
||||||
|
|
||||||
// 获取真实的包丢失率(优先使用心跳管理器数据)
|
|
||||||
const packetLoss = this.getAccuratePacketLoss();
|
|
||||||
|
|
||||||
// 计算连接质量评分
|
|
||||||
const connectionQuality = this.calculateConnectionQuality(avgRtt, jitter, packetLoss);
|
|
||||||
|
|
||||||
return {
|
|
||||||
rtt: avgRtt,
|
|
||||||
latency: avgRtt / 2, // 单向延迟近似为RTT的一半
|
|
||||||
uploadBandwidth,
|
|
||||||
downloadBandwidth,
|
|
||||||
packetLoss,
|
|
||||||
jitter,
|
|
||||||
connectionQuality
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算抖动
|
|
||||||
*/
|
|
||||||
private calculateJitter(): number {
|
|
||||||
if (this._rttHistory.length < 2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalVariation = 0;
|
|
||||||
for (let i = 1; i < this._rttHistory.length; i++) {
|
|
||||||
totalVariation += Math.abs(this._rttHistory[i] - this._rttHistory[i - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalVariation / (this._rttHistory.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算带宽
|
|
||||||
*/
|
|
||||||
private calculateBandwidth(): { uploadBandwidth: number; downloadBandwidth: number } {
|
|
||||||
if (this._bandwidthWindow.length < 2) {
|
|
||||||
return { uploadBandwidth: 0, downloadBandwidth: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const first = this._bandwidthWindow[0];
|
|
||||||
const last = this._bandwidthWindow[this._bandwidthWindow.length - 1];
|
|
||||||
const timeDiff = (last.timestamp - first.timestamp) / 1000; // 转换为秒
|
|
||||||
|
|
||||||
if (timeDiff <= 0) {
|
|
||||||
return { uploadBandwidth: 0, downloadBandwidth: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadBandwidth = (last.uploadBytes - first.uploadBytes) / timeDiff;
|
|
||||||
const downloadBandwidth = (last.downloadBytes - first.downloadBytes) / timeDiff;
|
|
||||||
|
|
||||||
return { uploadBandwidth, downloadBandwidth };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 估算包丢失率
|
|
||||||
* 使用多种指标进行更精确的丢包检测
|
|
||||||
*/
|
|
||||||
private estimatePacketLoss(): number {
|
|
||||||
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 variance = recentRtt.reduce((sum, rtt) => sum + Math.pow(rtt - avgRtt, 2), 0) / recentRtt.length;
|
|
||||||
const stdDev = Math.sqrt(variance);
|
|
||||||
const coefficientOfVariation = stdDev / avgRtt;
|
|
||||||
|
|
||||||
// 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%丢包率
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算连接质量评分
|
|
||||||
*/
|
|
||||||
private calculateConnectionQuality(rtt: number, jitter: number, packetLoss: number): number {
|
|
||||||
let quality = 100;
|
|
||||||
|
|
||||||
// RTT影响(大于100ms开始扣分)
|
|
||||||
if (rtt > 100) {
|
|
||||||
quality -= Math.min((rtt - 100) / 10, 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 抖动影响(大于20ms开始扣分)
|
|
||||||
if (jitter > 20) {
|
|
||||||
quality -= Math.min((jitter - 20) / 5, 25);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 丢包影响
|
|
||||||
quality -= packetLoss * 100;
|
|
||||||
|
|
||||||
return Math.max(Math.min(quality, 100), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取SyncVar统计信息
|
|
||||||
* 改进异常处理,提供更详细的错误信息和降级策略
|
|
||||||
* 使用懒加载避免循环依赖问题
|
|
||||||
*/
|
|
||||||
private getSyncVarStatistics(): PerformanceSnapshot['syncVarStats'] {
|
|
||||||
try {
|
|
||||||
// 使用懒加载获取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();
|
|
||||||
|
|
||||||
// 验证统计数据的有效性
|
|
||||||
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) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查性能警告
|
|
||||||
*/
|
|
||||||
private checkPerformanceWarnings(metrics: NetworkMetrics): void {
|
|
||||||
const warnings: string[] = [];
|
|
||||||
|
|
||||||
if (metrics.rtt > 200) {
|
|
||||||
warnings.push(`高延迟: ${metrics.rtt.toFixed(1)}ms`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metrics.jitter > 50) {
|
|
||||||
warnings.push(`高抖动: ${metrics.jitter.toFixed(1)}ms`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metrics.packetLoss > 0.05) {
|
|
||||||
warnings.push(`高丢包率: ${(metrics.packetLoss * 100).toFixed(1)}%`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metrics.connectionQuality < 70) {
|
|
||||||
warnings.push(`连接质量低: ${metrics.connectionQuality.toFixed(0)}/100`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (warnings.length > 0) {
|
|
||||||
this.emit('performanceWarning', warnings);
|
|
||||||
NetworkPerformanceMonitor.logger.warn('性能警告:', warnings.join(', '));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录RTT测量
|
|
||||||
*/
|
|
||||||
public recordRtt(rtt: number): void {
|
|
||||||
this._rttHistory.push(rtt);
|
|
||||||
|
|
||||||
if (this._rttHistory.length > this._maxRttHistory) {
|
|
||||||
this._rttHistory.shift();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录数据传输
|
|
||||||
*/
|
|
||||||
public recordDataTransfer(sent: number, received: number): void {
|
|
||||||
this._connectionStats.bytesSent += sent;
|
|
||||||
this._connectionStats.bytesReceived += received;
|
|
||||||
|
|
||||||
// 更新带宽测量窗口
|
|
||||||
const timestamp = Date.now();
|
|
||||||
this._bandwidthWindow.push({
|
|
||||||
timestamp,
|
|
||||||
uploadBytes: this._connectionStats.bytesSent,
|
|
||||||
downloadBytes: this._connectionStats.bytesReceived
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this._bandwidthWindow.length > this._bandwidthWindowSize) {
|
|
||||||
this._bandwidthWindow.shift();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录消息传输
|
|
||||||
*/
|
|
||||||
public recordMessageTransfer(sent: number, received: number): void {
|
|
||||||
this._connectionStats.messagesSent += sent;
|
|
||||||
this._connectionStats.messagesReceived += received;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新活跃连接数
|
|
||||||
*/
|
|
||||||
public updateActiveConnections(count: number): void {
|
|
||||||
this._connectionStats.activeConnections = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前网络指标
|
|
||||||
*/
|
|
||||||
public getCurrentMetrics(): NetworkMetrics {
|
|
||||||
return this.calculateNetworkMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取性能快照历史
|
|
||||||
*/
|
|
||||||
public getSnapshots(count?: number): PerformanceSnapshot[] {
|
|
||||||
if (count && count > 0) {
|
|
||||||
return this._snapshots.slice(-count);
|
|
||||||
}
|
|
||||||
return [...this._snapshots];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取最新的性能快照
|
|
||||||
*/
|
|
||||||
public getLatestSnapshot(): PerformanceSnapshot | null {
|
|
||||||
return this._snapshots.length > 0 ? this._snapshots[this._snapshots.length - 1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除历史数据
|
|
||||||
*/
|
|
||||||
public clearHistory(): void {
|
|
||||||
this._snapshots = [];
|
|
||||||
this._rttHistory = [];
|
|
||||||
this._bandwidthWindow = [];
|
|
||||||
NetworkPerformanceMonitor.logger.debug('清除历史数据');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成性能报告
|
|
||||||
*/
|
|
||||||
public generateReport(timeRangeMs?: number): {
|
|
||||||
summary: {
|
|
||||||
averageRtt: number;
|
|
||||||
averageJitter: number;
|
|
||||||
averagePacketLoss: number;
|
|
||||||
averageQuality: number;
|
|
||||||
totalBytesSent: number;
|
|
||||||
totalBytesReceived: number;
|
|
||||||
totalMessages: number;
|
|
||||||
};
|
|
||||||
snapshots: PerformanceSnapshot[];
|
|
||||||
} {
|
|
||||||
let snapshots = this._snapshots;
|
|
||||||
|
|
||||||
if (timeRangeMs && timeRangeMs > 0) {
|
|
||||||
const cutoffTime = Date.now() - timeRangeMs;
|
|
||||||
snapshots = snapshots.filter(s => s.timestamp >= cutoffTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshots.length === 0) {
|
|
||||||
return {
|
|
||||||
summary: {
|
|
||||||
averageRtt: 0,
|
|
||||||
averageJitter: 0,
|
|
||||||
averagePacketLoss: 0,
|
|
||||||
averageQuality: 0,
|
|
||||||
totalBytesSent: 0,
|
|
||||||
totalBytesReceived: 0,
|
|
||||||
totalMessages: 0
|
|
||||||
},
|
|
||||||
snapshots: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const summary = {
|
|
||||||
averageRtt: snapshots.reduce((sum, s) => sum + s.metrics.rtt, 0) / snapshots.length,
|
|
||||||
averageJitter: snapshots.reduce((sum, s) => sum + s.metrics.jitter, 0) / snapshots.length,
|
|
||||||
averagePacketLoss: snapshots.reduce((sum, s) => sum + s.metrics.packetLoss, 0) / snapshots.length,
|
|
||||||
averageQuality: snapshots.reduce((sum, s) => sum + s.metrics.connectionQuality, 0) / snapshots.length,
|
|
||||||
totalBytesSent: this._connectionStats.bytesSent,
|
|
||||||
totalBytesReceived: this._connectionStats.bytesReceived,
|
|
||||||
totalMessages: this._connectionStats.messagesSent + this._connectionStats.messagesReceived
|
|
||||||
};
|
|
||||||
|
|
||||||
return { summary, snapshots };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加事件监听器
|
|
||||||
*/
|
|
||||||
public on(event: string, listener: Function): void {
|
|
||||||
if (!this._eventListeners.has(event)) {
|
|
||||||
this._eventListeners.set(event, []);
|
|
||||||
}
|
|
||||||
this._eventListeners.get(event)!.push(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除事件监听器
|
|
||||||
*/
|
|
||||||
public off(event: string, listener: Function): void {
|
|
||||||
const listeners = this._eventListeners.get(event);
|
|
||||||
if (listeners) {
|
|
||||||
const index = listeners.indexOf(listener);
|
|
||||||
if (index !== -1) {
|
|
||||||
listeners.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 触发事件
|
|
||||||
*/
|
|
||||||
private emit(event: string, ...args: any[]): void {
|
|
||||||
const listeners = this._eventListeners.get(event);
|
|
||||||
if (listeners) {
|
|
||||||
listeners.forEach(listener => {
|
|
||||||
try {
|
|
||||||
listener(...args);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkPerformanceMonitor.logger.error(`事件处理器错误 (${event}):`, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置监控参数
|
|
||||||
*/
|
|
||||||
public configure(options: {
|
|
||||||
maxSnapshots?: number;
|
|
||||||
maxRttHistory?: number;
|
|
||||||
bandwidthWindowSize?: number;
|
|
||||||
}): void {
|
|
||||||
if (options.maxSnapshots && options.maxSnapshots > 0) {
|
|
||||||
this._maxSnapshots = options.maxSnapshots;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.maxRttHistory && options.maxRttHistory > 0) {
|
|
||||||
this._maxRttHistory = options.maxRttHistory;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.bandwidthWindowSize && options.bandwidthWindowSize > 0) {
|
|
||||||
this._bandwidthWindowSize = options.bandwidthWindowSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkPerformanceMonitor.logger.info('配置已更新:', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取监控器统计信息
|
|
||||||
*/
|
|
||||||
public getMonitorStats(): {
|
|
||||||
isMonitoring: boolean;
|
|
||||||
interval: number;
|
|
||||||
snapshotCount: number;
|
|
||||||
rttHistoryLength: number;
|
|
||||||
bandwidthWindowSize: number;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
isMonitoring: this._isMonitoring,
|
|
||||||
interval: this._monitoringInterval,
|
|
||||||
snapshotCount: this._snapshots.length,
|
|
||||||
rttHistoryLength: this._rttHistory.length,
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
185
packages/network/src/Core/NetworkRegistry.ts
Normal file
185
packages/network/src/Core/NetworkRegistry.ts
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* 网络注册表
|
||||||
|
*
|
||||||
|
* 管理所有网络对象的注册和查找
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createLogger } from '@esengine/ecs-framework';
|
||||||
|
import { NetworkIdentity } from '../NetworkIdentity';
|
||||||
|
|
||||||
|
const logger = createLogger('NetworkRegistry');
|
||||||
|
|
||||||
|
export class NetworkRegistry {
|
||||||
|
private static _instance: NetworkRegistry | null = null;
|
||||||
|
|
||||||
|
/** 网络对象映射表 networkId -> NetworkIdentity */
|
||||||
|
private networkObjects: Map<number, NetworkIdentity> = new Map();
|
||||||
|
|
||||||
|
/** 下一个可用的网络ID */
|
||||||
|
private nextNetworkId: number = 1;
|
||||||
|
|
||||||
|
/** 本地玩家对象 */
|
||||||
|
private localPlayer: NetworkIdentity | null = null;
|
||||||
|
|
||||||
|
public static get instance(): NetworkRegistry {
|
||||||
|
if (!NetworkRegistry._instance) {
|
||||||
|
NetworkRegistry._instance = new NetworkRegistry();
|
||||||
|
}
|
||||||
|
return NetworkRegistry._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册网络对象
|
||||||
|
* @param identity 网络身份组件
|
||||||
|
* @param networkId 指定的网络ID,如果不提供则自动分配
|
||||||
|
* @returns 分配的网络ID
|
||||||
|
*/
|
||||||
|
public register(identity: NetworkIdentity, networkId?: number): number {
|
||||||
|
// 使用指定ID或自动分配
|
||||||
|
const assignedId = networkId || this.nextNetworkId++;
|
||||||
|
|
||||||
|
// 检查ID是否已被占用
|
||||||
|
if (this.networkObjects.has(assignedId)) {
|
||||||
|
logger.error(`网络ID ${assignedId} 已被占用`);
|
||||||
|
throw new Error(`Network ID ${assignedId} is already in use`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册对象
|
||||||
|
identity.networkId = assignedId;
|
||||||
|
this.networkObjects.set(assignedId, identity);
|
||||||
|
|
||||||
|
// 确保下一个ID不冲突
|
||||||
|
if (assignedId >= this.nextNetworkId) {
|
||||||
|
this.nextNetworkId = assignedId + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`注册网络对象: ID=${assignedId}, Type=${identity.entity?.name || 'Unknown'}`);
|
||||||
|
return assignedId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销网络对象
|
||||||
|
* @param networkId 网络ID
|
||||||
|
*/
|
||||||
|
public unregister(networkId: number): void {
|
||||||
|
const identity = this.networkObjects.get(networkId);
|
||||||
|
if (identity) {
|
||||||
|
this.networkObjects.delete(networkId);
|
||||||
|
|
||||||
|
// 如果是本地玩家,清除引用
|
||||||
|
if (this.localPlayer === identity) {
|
||||||
|
this.localPlayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`注销网络对象: ID=${networkId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据网络ID查找对象
|
||||||
|
* @param networkId 网络ID
|
||||||
|
* @returns 网络身份组件
|
||||||
|
*/
|
||||||
|
public find(networkId: number): NetworkIdentity | null {
|
||||||
|
return this.networkObjects.get(networkId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有网络对象
|
||||||
|
*/
|
||||||
|
public getAllNetworkObjects(): NetworkIdentity[] {
|
||||||
|
return Array.from(this.networkObjects.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有拥有权威的对象
|
||||||
|
*/
|
||||||
|
public getAuthorityObjects(): NetworkIdentity[] {
|
||||||
|
return Array.from(this.networkObjects.values()).filter(identity => identity.hasAuthority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定客户端拥有的对象
|
||||||
|
* @param ownerId 客户端ID
|
||||||
|
*/
|
||||||
|
public getObjectsByOwner(ownerId: number): NetworkIdentity[] {
|
||||||
|
return Array.from(this.networkObjects.values()).filter(identity => identity.ownerId === ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置本地玩家
|
||||||
|
* @param identity 本地玩家的网络身份组件
|
||||||
|
*/
|
||||||
|
public setLocalPlayer(identity: NetworkIdentity): void {
|
||||||
|
// 清除之前的本地玩家标记
|
||||||
|
if (this.localPlayer) {
|
||||||
|
this.localPlayer.isLocalPlayer = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置新的本地玩家
|
||||||
|
this.localPlayer = identity;
|
||||||
|
identity.setAsLocalPlayer();
|
||||||
|
|
||||||
|
logger.info(`设置本地玩家: ID=${identity.networkId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取本地玩家
|
||||||
|
*/
|
||||||
|
public getLocalPlayer(): NetworkIdentity | null {
|
||||||
|
return this.localPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理指定客户端断开连接后的对象
|
||||||
|
* @param ownerId 断开连接的客户端ID
|
||||||
|
*/
|
||||||
|
public cleanupDisconnectedClient(ownerId: number): void {
|
||||||
|
const ownedObjects = this.getObjectsByOwner(ownerId);
|
||||||
|
|
||||||
|
for (const identity of ownedObjects) {
|
||||||
|
// 移除权威,转移给服务端
|
||||||
|
identity.setAuthority(false, 0);
|
||||||
|
|
||||||
|
// 如果是本地玩家,清除引用
|
||||||
|
if (identity === this.localPlayer) {
|
||||||
|
this.localPlayer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`清理断开连接客户端 ${ownerId} 的 ${ownedObjects.length} 个对象`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查网络ID是否存在
|
||||||
|
* @param networkId 网络ID
|
||||||
|
*/
|
||||||
|
public exists(networkId: number): boolean {
|
||||||
|
return this.networkObjects.has(networkId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置注册表(用于测试)
|
||||||
|
*/
|
||||||
|
public reset(): void {
|
||||||
|
this.networkObjects.clear();
|
||||||
|
this.nextNetworkId = 1;
|
||||||
|
this.localPlayer = null;
|
||||||
|
logger.info('网络注册表已重置');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计信息
|
||||||
|
*/
|
||||||
|
public getStats() {
|
||||||
|
const objects = Array.from(this.networkObjects.values());
|
||||||
|
return {
|
||||||
|
totalObjects: objects.length,
|
||||||
|
authorityObjects: objects.filter(o => o.hasAuthority).length,
|
||||||
|
localPlayerCount: this.localPlayer ? 1 : 0,
|
||||||
|
nextNetworkId: this.nextNetworkId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,594 +0,0 @@
|
|||||||
import WebSocket, { WebSocketServer } from 'ws';
|
|
||||||
import { NetworkConnection } from './NetworkConnection';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { SyncVarUpdateMessage } from '../Messaging/MessageTypes';
|
|
||||||
import { SyncVarMessageHandler } from '../SyncVar/SyncVarMessageHandler';
|
|
||||||
import { SyncVarSyncScheduler } from '../SyncVar/SyncVarSyncScheduler';
|
|
||||||
import { MessageHandler } from '../Messaging/MessageHandler';
|
|
||||||
import { NetworkPerformanceMonitor } from './NetworkPerformanceMonitor';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务端事件接口
|
|
||||||
*/
|
|
||||||
export interface NetworkServerEvents {
|
|
||||||
clientConnected: (connection: NetworkConnection) => void;
|
|
||||||
clientDisconnected: (connection: NetworkConnection, reason?: string) => void;
|
|
||||||
clientMessage: (connection: NetworkConnection, data: Uint8Array) => void;
|
|
||||||
serverStarted: (port: number, host: string) => void;
|
|
||||||
serverStopped: () => void;
|
|
||||||
error: (error: Error) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络服务端
|
|
||||||
*
|
|
||||||
* 管理WebSocket服务器,处理客户端连接和消息分发
|
|
||||||
* 支持多客户端连接,提供广播和单播功能
|
|
||||||
*/
|
|
||||||
export class NetworkServer {
|
|
||||||
private static readonly _logger = createLogger('NetworkServer');
|
|
||||||
private _wss: WebSocketServer | null = null;
|
|
||||||
private _connections: Map<string, NetworkConnection> = new Map();
|
|
||||||
private _isRunning: boolean = false;
|
|
||||||
private _port: number = 0;
|
|
||||||
private _host: string = '';
|
|
||||||
private _startTime: number = 0;
|
|
||||||
private _eventHandlers: Map<keyof NetworkServerEvents, Function[]> = new Map();
|
|
||||||
|
|
||||||
// SyncVar相关组件
|
|
||||||
private _syncVarHandler: SyncVarMessageHandler;
|
|
||||||
private _syncScheduler: SyncVarSyncScheduler;
|
|
||||||
private _messageHandler: MessageHandler;
|
|
||||||
|
|
||||||
// 性能监控
|
|
||||||
private _performanceMonitor: NetworkPerformanceMonitor;
|
|
||||||
|
|
||||||
// 服务器配置
|
|
||||||
private static readonly MAX_CONNECTIONS = 100;
|
|
||||||
private static readonly CONNECTION_TIMEOUT = 60000; // 60秒
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// 初始化SyncVar组件
|
|
||||||
this._syncVarHandler = new SyncVarMessageHandler();
|
|
||||||
this._syncScheduler = SyncVarSyncScheduler.Instance;
|
|
||||||
this._messageHandler = MessageHandler.Instance;
|
|
||||||
this._performanceMonitor = NetworkPerformanceMonitor.Instance;
|
|
||||||
|
|
||||||
// 注册SyncVar消息处理器
|
|
||||||
this._messageHandler.registerHandler(
|
|
||||||
400, // MessageType.SYNC_VAR_UPDATE
|
|
||||||
SyncVarUpdateMessage,
|
|
||||||
this._syncVarHandler,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
// 设置SyncVar消息发送回调
|
|
||||||
this._syncScheduler.setMessageSendCallback(async (message: SyncVarUpdateMessage) => {
|
|
||||||
await this.broadcastSyncVarMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动服务器
|
|
||||||
*
|
|
||||||
* @param port - 监听端口
|
|
||||||
* @param host - 监听地址
|
|
||||||
*/
|
|
||||||
public async start(port: number, host: string = '0.0.0.0'): Promise<void> {
|
|
||||||
if (this._isRunning) {
|
|
||||||
throw new Error('服务器已经在运行');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
this._wss = new WebSocketServer({
|
|
||||||
port,
|
|
||||||
host,
|
|
||||||
maxPayload: 16 * 1024 * 1024, // 16MB
|
|
||||||
perMessageDeflate: true,
|
|
||||||
clientTracking: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this._wss.on('connection', (ws: WebSocket, request) => {
|
|
||||||
this.handleNewConnection(ws, request);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._wss.on('listening', () => {
|
|
||||||
this._isRunning = true;
|
|
||||||
this._port = port;
|
|
||||||
this._host = host;
|
|
||||||
this._startTime = Date.now();
|
|
||||||
|
|
||||||
// 启动SyncVar同步调度器
|
|
||||||
this.startSyncVarScheduler();
|
|
||||||
|
|
||||||
// 启动性能监控
|
|
||||||
this.startPerformanceMonitoring();
|
|
||||||
|
|
||||||
NetworkServer._logger.info(`服务器启动成功: ${host}:${port}`);
|
|
||||||
this.emit('serverStarted', port, host);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._wss.on('error', (error) => {
|
|
||||||
NetworkServer._logger.error('服务器错误:', error);
|
|
||||||
this.emit('error', error);
|
|
||||||
|
|
||||||
if (!this._isRunning) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止服务器
|
|
||||||
*/
|
|
||||||
public async stop(): Promise<void> {
|
|
||||||
if (!this._isRunning || !this._wss) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
// 关闭所有客户端连接
|
|
||||||
const connections = Array.from(this._connections.values());
|
|
||||||
connections.forEach(connection => {
|
|
||||||
connection.close('Server shutting down');
|
|
||||||
});
|
|
||||||
this._connections.clear();
|
|
||||||
|
|
||||||
// 停止SyncVar同步调度器
|
|
||||||
this.stopSyncVarScheduler();
|
|
||||||
|
|
||||||
// 停止性能监控
|
|
||||||
this.stopPerformanceMonitoring();
|
|
||||||
|
|
||||||
// 关闭WebSocket服务器
|
|
||||||
this._wss!.close(() => {
|
|
||||||
this._isRunning = false;
|
|
||||||
this._wss = null;
|
|
||||||
this._port = 0;
|
|
||||||
this._host = '';
|
|
||||||
this._startTime = 0;
|
|
||||||
|
|
||||||
NetworkServer._logger.info('服务器已停止');
|
|
||||||
this.emit('serverStopped');
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理新的客户端连接
|
|
||||||
*
|
|
||||||
* @param ws - WebSocket连接
|
|
||||||
* @param request - HTTP请求对象
|
|
||||||
*/
|
|
||||||
private handleNewConnection(ws: WebSocket, request: any): void {
|
|
||||||
// 检查连接数限制
|
|
||||||
if (this._connections.size >= NetworkServer.MAX_CONNECTIONS) {
|
|
||||||
NetworkServer._logger.warn('达到最大连接数限制,拒绝新连接');
|
|
||||||
ws.close(1013, 'Server full');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成连接ID和获取客户端地址
|
|
||||||
const connectionId = uuidv4();
|
|
||||||
const clientAddress = request.socket.remoteAddress || 'unknown';
|
|
||||||
|
|
||||||
// 创建连接对象
|
|
||||||
const connection = new NetworkConnection(ws, connectionId, clientAddress);
|
|
||||||
|
|
||||||
// 设置连接事件监听
|
|
||||||
connection.on('connected', () => {
|
|
||||||
this._connections.set(connectionId, connection);
|
|
||||||
NetworkServer._logger.info(`客户端连接: ${connectionId} (${clientAddress})`);
|
|
||||||
this.emit('clientConnected', connection);
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.on('disconnected', (reason) => {
|
|
||||||
this._connections.delete(connectionId);
|
|
||||||
NetworkServer._logger.info(`客户端断开: ${connectionId} (${reason})`);
|
|
||||||
this.emit('clientDisconnected', connection, reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.on('message', async (data) => {
|
|
||||||
this.recordMessagePerformance(data, false);
|
|
||||||
this.emit('clientMessage', connection, data);
|
|
||||||
|
|
||||||
// 自动处理消息
|
|
||||||
await this._messageHandler.handleRawMessage(data, connection);
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.on('error', (error) => {
|
|
||||||
NetworkServer._logger.error(`连接错误 ${connectionId}:`, error);
|
|
||||||
this.emit('error', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 向指定客户端发送消息
|
|
||||||
*
|
|
||||||
* @param connectionId - 连接ID
|
|
||||||
* @param data - 消息数据
|
|
||||||
* @returns 是否发送成功
|
|
||||||
*/
|
|
||||||
public sendToClient(connectionId: string, data: Uint8Array): boolean {
|
|
||||||
const connection = this._connections.get(connectionId);
|
|
||||||
if (!connection) {
|
|
||||||
NetworkServer._logger.warn(`连接不存在: ${connectionId}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const success = connection.send(data);
|
|
||||||
if (success) {
|
|
||||||
this.recordMessagePerformance(data, true);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 广播消息给所有客户端
|
|
||||||
*
|
|
||||||
* @param data - 消息数据
|
|
||||||
* @param excludeConnection - 排除的连接ID(可选)
|
|
||||||
* @returns 成功发送的连接数
|
|
||||||
*/
|
|
||||||
public broadcast(data: Uint8Array, excludeConnection?: string): number {
|
|
||||||
let successCount = 0;
|
|
||||||
|
|
||||||
for (const [connectionId, connection] of this._connections) {
|
|
||||||
if (excludeConnection && connectionId === excludeConnection) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connection.send(data)) {
|
|
||||||
successCount++;
|
|
||||||
this.recordMessagePerformance(data, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return successCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 向多个指定客户端发送消息
|
|
||||||
*
|
|
||||||
* @param connectionIds - 连接ID数组
|
|
||||||
* @param data - 消息数据
|
|
||||||
* @returns 成功发送的连接数
|
|
||||||
*/
|
|
||||||
public sendToMultipleClients(connectionIds: string[], data: Uint8Array): number {
|
|
||||||
let successCount = 0;
|
|
||||||
|
|
||||||
connectionIds.forEach(connectionId => {
|
|
||||||
if (this.sendToClient(connectionId, data)) {
|
|
||||||
successCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return successCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 断开指定客户端连接
|
|
||||||
*
|
|
||||||
* @param connectionId - 连接ID
|
|
||||||
* @param reason - 断开原因
|
|
||||||
* @returns 是否成功断开
|
|
||||||
*/
|
|
||||||
public disconnectClient(connectionId: string, reason: string = 'Disconnected by server'): boolean {
|
|
||||||
const connection = this._connections.get(connectionId);
|
|
||||||
if (!connection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.close(reason);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定客户端连接
|
|
||||||
*
|
|
||||||
* @param connectionId - 连接ID
|
|
||||||
* @returns 连接对象
|
|
||||||
*/
|
|
||||||
public getConnection(connectionId: string): NetworkConnection | null {
|
|
||||||
return this._connections.get(connectionId) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有活跃连接
|
|
||||||
*
|
|
||||||
* @returns 连接数组
|
|
||||||
*/
|
|
||||||
public getAllConnections(): NetworkConnection[] {
|
|
||||||
return Array.from(this._connections.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取活跃连接的ID列表
|
|
||||||
*
|
|
||||||
* @returns 连接ID数组
|
|
||||||
*/
|
|
||||||
public getConnectionIds(): string[] {
|
|
||||||
return Array.from(this._connections.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加事件监听器
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param handler - 事件处理函数
|
|
||||||
*/
|
|
||||||
public on<K extends keyof NetworkServerEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: NetworkServerEvents[K]
|
|
||||||
): void {
|
|
||||||
if (!this._eventHandlers.has(event)) {
|
|
||||||
this._eventHandlers.set(event, []);
|
|
||||||
}
|
|
||||||
this._eventHandlers.get(event)!.push(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除事件监听器
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param handler - 事件处理函数
|
|
||||||
*/
|
|
||||||
public off<K extends keyof NetworkServerEvents>(
|
|
||||||
event: K,
|
|
||||||
handler: NetworkServerEvents[K]
|
|
||||||
): void {
|
|
||||||
const handlers = this._eventHandlers.get(event);
|
|
||||||
if (handlers) {
|
|
||||||
const index = handlers.indexOf(handler);
|
|
||||||
if (index !== -1) {
|
|
||||||
handlers.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 触发事件
|
|
||||||
*
|
|
||||||
* @param event - 事件名称
|
|
||||||
* @param args - 事件参数
|
|
||||||
*/
|
|
||||||
private emit<K extends keyof NetworkServerEvents>(
|
|
||||||
event: K,
|
|
||||||
...args: Parameters<NetworkServerEvents[K]>
|
|
||||||
): void {
|
|
||||||
const handlers = this._eventHandlers.get(event);
|
|
||||||
if (handlers) {
|
|
||||||
handlers.forEach(handler => {
|
|
||||||
try {
|
|
||||||
handler(...args);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkServer._logger.error(`事件处理器错误 (${event}):`, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查服务器是否正在运行
|
|
||||||
*/
|
|
||||||
public get isRunning(): boolean {
|
|
||||||
return this._isRunning;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前连接数
|
|
||||||
*/
|
|
||||||
public get connectionCount(): number {
|
|
||||||
return this._connections.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取监听端口
|
|
||||||
*/
|
|
||||||
public get port(): number {
|
|
||||||
return this._port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取监听地址
|
|
||||||
*/
|
|
||||||
public get host(): string {
|
|
||||||
return this._host;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器运行时间(毫秒)
|
|
||||||
*/
|
|
||||||
public get uptime(): number {
|
|
||||||
return this._startTime > 0 ? Date.now() - this._startTime : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动SyncVar同步调度器
|
|
||||||
*/
|
|
||||||
private startSyncVarScheduler(): void {
|
|
||||||
try {
|
|
||||||
this._syncScheduler.start();
|
|
||||||
NetworkServer._logger.info('SyncVar同步调度器已启动');
|
|
||||||
} catch (error) {
|
|
||||||
NetworkServer._logger.error('启动SyncVar调度器失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止SyncVar同步调度器
|
|
||||||
*/
|
|
||||||
private stopSyncVarScheduler(): void {
|
|
||||||
try {
|
|
||||||
this._syncScheduler.stop();
|
|
||||||
NetworkServer._logger.info('SyncVar同步调度器已停止');
|
|
||||||
} catch (error) {
|
|
||||||
NetworkServer._logger.error('停止SyncVar调度器失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 广播SyncVar更新消息
|
|
||||||
*
|
|
||||||
* @param message - SyncVar更新消息
|
|
||||||
*/
|
|
||||||
public async broadcastSyncVarMessage(message: SyncVarUpdateMessage): Promise<void> {
|
|
||||||
try {
|
|
||||||
const serializedMessage = message.serialize();
|
|
||||||
const successCount = this.broadcast(serializedMessage);
|
|
||||||
|
|
||||||
NetworkServer._logger.info(`广播SyncVar消息: ${message.networkId}.${message.componentType}, 成功发送到 ${successCount} 个客户端`);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkServer._logger.error('广播SyncVar消息失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送SyncVar消息到指定客户端
|
|
||||||
*
|
|
||||||
* @param connectionId - 连接ID
|
|
||||||
* @param message - SyncVar更新消息
|
|
||||||
*/
|
|
||||||
public async sendSyncVarMessage(connectionId: string, message: SyncVarUpdateMessage): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const serializedMessage = message.serialize();
|
|
||||||
return this.sendToClient(connectionId, serializedMessage);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkServer._logger.error(`发送SyncVar消息到客户端 ${connectionId} 失败:`, error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送SyncVar消息到指定客户端列表(排除某个客户端)
|
|
||||||
*
|
|
||||||
* @param message - SyncVar更新消息
|
|
||||||
* @param excludeConnectionId - 要排除的连接ID
|
|
||||||
*/
|
|
||||||
public async broadcastSyncVarMessageExcept(message: SyncVarUpdateMessage, excludeConnectionId: string): Promise<number> {
|
|
||||||
try {
|
|
||||||
const serializedMessage = message.serialize();
|
|
||||||
const allConnections = Array.from(this._connections.keys());
|
|
||||||
const targetConnections = allConnections.filter(id => id !== excludeConnectionId);
|
|
||||||
|
|
||||||
return this.sendToMultipleClients(targetConnections, serializedMessage);
|
|
||||||
} catch (error) {
|
|
||||||
NetworkServer._logger.error('广播SyncVar消息(排除指定客户端)失败:', error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取SyncVar调度器统计信息
|
|
||||||
*/
|
|
||||||
public getSyncVarStats(): any {
|
|
||||||
return this._syncScheduler.getStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置SyncVar同步调度器
|
|
||||||
*/
|
|
||||||
public configureSyncVarScheduler(config: any): void {
|
|
||||||
this._syncScheduler.configure(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动性能监控
|
|
||||||
*/
|
|
||||||
private startPerformanceMonitoring(): void {
|
|
||||||
try {
|
|
||||||
this._performanceMonitor.startMonitoring();
|
|
||||||
NetworkServer._logger.info('性能监控已启动');
|
|
||||||
} catch (error) {
|
|
||||||
NetworkServer._logger.error('启动性能监控失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止性能监控
|
|
||||||
*/
|
|
||||||
private stopPerformanceMonitoring(): void {
|
|
||||||
try {
|
|
||||||
this._performanceMonitor.stopMonitoring();
|
|
||||||
NetworkServer._logger.info('性能监控已停止');
|
|
||||||
} catch (error) {
|
|
||||||
NetworkServer._logger.error('停止性能监控失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录消息传输性能
|
|
||||||
*/
|
|
||||||
private recordMessagePerformance(data: Uint8Array, sent: boolean): void {
|
|
||||||
const size = data.length;
|
|
||||||
if (sent) {
|
|
||||||
this._performanceMonitor.recordDataTransfer(size, 0);
|
|
||||||
this._performanceMonitor.recordMessageTransfer(1, 0);
|
|
||||||
} else {
|
|
||||||
this._performanceMonitor.recordDataTransfer(0, size);
|
|
||||||
this._performanceMonitor.recordMessageTransfer(0, 1);
|
|
||||||
}
|
|
||||||
this._performanceMonitor.updateActiveConnections(this._connections.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取性能监控数据
|
|
||||||
*/
|
|
||||||
public getPerformanceMetrics(): any {
|
|
||||||
return this._performanceMonitor.getCurrentMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取性能报告
|
|
||||||
*/
|
|
||||||
public getPerformanceReport(timeRangeMs?: number): any {
|
|
||||||
return this._performanceMonitor.generateReport(timeRangeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
isRunning: boolean;
|
|
||||||
connectionCount: number;
|
|
||||||
maxConnections: number;
|
|
||||||
port: number;
|
|
||||||
host: string;
|
|
||||||
uptime: number;
|
|
||||||
connections: Array<{
|
|
||||||
connectionId: string;
|
|
||||||
address: string;
|
|
||||||
connectedTime: number;
|
|
||||||
isAlive: boolean;
|
|
||||||
}>;
|
|
||||||
} {
|
|
||||||
const connectionStats = Array.from(this._connections.values()).map(conn => {
|
|
||||||
const stats = conn.getStats();
|
|
||||||
return {
|
|
||||||
connectionId: stats.connectionId,
|
|
||||||
address: stats.address,
|
|
||||||
connectedTime: stats.connectedTime,
|
|
||||||
isAlive: stats.isAlive
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
isRunning: this._isRunning,
|
|
||||||
connectionCount: this._connections.size,
|
|
||||||
maxConnections: NetworkServer.MAX_CONNECTIONS,
|
|
||||||
port: this._port,
|
|
||||||
host: this._host,
|
|
||||||
uptime: this.uptime,
|
|
||||||
connections: connectionStats
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
223
packages/network/src/Core/RpcManager.ts
Normal file
223
packages/network/src/Core/RpcManager.ts
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/**
|
||||||
|
* RPC 管理器
|
||||||
|
*
|
||||||
|
* 负责处理客户端 RPC 和命令的注册、调用和消息路由
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createLogger } from '@esengine/ecs-framework';
|
||||||
|
import { NetworkBehaviour } from '../NetworkBehaviour';
|
||||||
|
import { NetworkRegistry } from './NetworkRegistry';
|
||||||
|
import { RpcMessage, RpcMetadata } from '../types/NetworkTypes';
|
||||||
|
import { getClientRpcMetadata } from '../decorators/ClientRpc';
|
||||||
|
import { getCommandMetadata } from '../decorators/Command';
|
||||||
|
|
||||||
|
const logger = createLogger('RpcManager');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC 调用信息
|
||||||
|
*/
|
||||||
|
interface RpcCall {
|
||||||
|
networkId: number;
|
||||||
|
componentType: string;
|
||||||
|
methodName: string;
|
||||||
|
args: any[];
|
||||||
|
timestamp: number;
|
||||||
|
isClientRpc: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RpcManager {
|
||||||
|
private static _instance: RpcManager | null = null;
|
||||||
|
|
||||||
|
/** 已注册的网络组件类型 */
|
||||||
|
private registeredComponents: Set<string> = new Set();
|
||||||
|
|
||||||
|
/** 待发送的 RPC 调用队列 */
|
||||||
|
private pendingRpcCalls: RpcCall[] = [];
|
||||||
|
|
||||||
|
public static get instance(): RpcManager {
|
||||||
|
if (!RpcManager._instance) {
|
||||||
|
RpcManager._instance = new RpcManager();
|
||||||
|
}
|
||||||
|
return RpcManager._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册网络组件的 RPC 方法
|
||||||
|
*/
|
||||||
|
public registerComponent(component: NetworkBehaviour): void {
|
||||||
|
const componentType = component.constructor.name;
|
||||||
|
|
||||||
|
if (this.registeredComponents.has(componentType)) {
|
||||||
|
return; // 已经注册过了
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 ClientRpc 和 Command 元数据
|
||||||
|
const clientRpcMetadata = getClientRpcMetadata(component.constructor);
|
||||||
|
const commandMetadata = getCommandMetadata(component.constructor);
|
||||||
|
|
||||||
|
if (clientRpcMetadata.length === 0 && commandMetadata.length === 0) {
|
||||||
|
return; // 没有 RPC 方法
|
||||||
|
}
|
||||||
|
|
||||||
|
this.registeredComponents.add(componentType);
|
||||||
|
|
||||||
|
logger.debug(`注册 RPC 组件: ${componentType}, ClientRpc: ${clientRpcMetadata.length}, Commands: ${commandMetadata.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 ClientRpc 调用到队列
|
||||||
|
*/
|
||||||
|
public addClientRpcCall(
|
||||||
|
networkId: number,
|
||||||
|
componentType: string,
|
||||||
|
methodName: string,
|
||||||
|
args: any[] = []
|
||||||
|
): void {
|
||||||
|
const rpcCall: RpcCall = {
|
||||||
|
networkId,
|
||||||
|
componentType,
|
||||||
|
methodName,
|
||||||
|
args,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
isClientRpc: true
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pendingRpcCalls.push(rpcCall);
|
||||||
|
logger.debug(`添加 ClientRpc 调用: ${componentType}.${methodName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 Command 调用到队列
|
||||||
|
*/
|
||||||
|
public addCommandCall(
|
||||||
|
networkId: number,
|
||||||
|
componentType: string,
|
||||||
|
methodName: string,
|
||||||
|
args: any[] = []
|
||||||
|
): void {
|
||||||
|
const rpcCall: RpcCall = {
|
||||||
|
networkId,
|
||||||
|
componentType,
|
||||||
|
methodName,
|
||||||
|
args,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
isClientRpc: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pendingRpcCalls.push(rpcCall);
|
||||||
|
logger.debug(`添加 Command 调用: ${componentType}.${methodName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待发送的 RPC 消息
|
||||||
|
*/
|
||||||
|
public getPendingRpcMessages(): RpcMessage[] {
|
||||||
|
const messages: RpcMessage[] = [];
|
||||||
|
|
||||||
|
for (const rpcCall of this.pendingRpcCalls) {
|
||||||
|
messages.push({
|
||||||
|
type: 'rpc',
|
||||||
|
networkId: rpcCall.networkId,
|
||||||
|
data: {
|
||||||
|
componentType: rpcCall.componentType,
|
||||||
|
methodName: rpcCall.methodName,
|
||||||
|
args: rpcCall.args
|
||||||
|
},
|
||||||
|
methodName: rpcCall.methodName,
|
||||||
|
args: rpcCall.args,
|
||||||
|
isClientRpc: rpcCall.isClientRpc,
|
||||||
|
timestamp: rpcCall.timestamp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空待发送队列
|
||||||
|
this.pendingRpcCalls.length = 0;
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理收到的 RPC 消息
|
||||||
|
*/
|
||||||
|
public handleRpcMessage(message: RpcMessage): void {
|
||||||
|
const networkIdentity = NetworkRegistry.instance.find(message.networkId);
|
||||||
|
if (!networkIdentity) {
|
||||||
|
logger.warn(`找不到网络ID为 ${message.networkId} 的对象`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到对应的组件
|
||||||
|
const targetComponent = networkIdentity.networkBehaviours
|
||||||
|
.find(b => b.constructor.name === message.data.componentType);
|
||||||
|
|
||||||
|
if (!targetComponent) {
|
||||||
|
logger.warn(`找不到组件: ${message.data.componentType} (网络ID: ${message.networkId})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证方法是否存在
|
||||||
|
const method = (targetComponent as any)[message.methodName];
|
||||||
|
if (typeof method !== 'function') {
|
||||||
|
logger.warn(`方法不存在: ${message.data.componentType}.${message.methodName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证权限
|
||||||
|
if (!this.validateRpcPermission(targetComponent as NetworkBehaviour, message)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 执行方法
|
||||||
|
method.apply(targetComponent, message.args);
|
||||||
|
logger.debug(`执行 RPC: ${message.data.componentType}.${message.methodName}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`RPC 执行失败: ${message.data.componentType}.${message.methodName}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证 RPC 调用权限
|
||||||
|
*/
|
||||||
|
private validateRpcPermission(component: NetworkBehaviour, message: RpcMessage): boolean {
|
||||||
|
let metadata: RpcMetadata[] = [];
|
||||||
|
|
||||||
|
if (message.isClientRpc) {
|
||||||
|
metadata = getClientRpcMetadata(component.constructor);
|
||||||
|
} else {
|
||||||
|
metadata = getCommandMetadata(component.constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rpcMeta = metadata.find(m => m.methodName === message.methodName);
|
||||||
|
if (!rpcMeta) {
|
||||||
|
logger.warn(`未找到 RPC 元数据: ${message.data.componentType}.${message.methodName}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查权限要求
|
||||||
|
if (rpcMeta.requiresAuthority && !component.hasAuthority) {
|
||||||
|
logger.warn(`RPC 权限不足: ${message.data.componentType}.${message.methodName}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理所有待发送的 RPC 调用
|
||||||
|
*/
|
||||||
|
public clearPendingCalls(): void {
|
||||||
|
this.pendingRpcCalls.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计信息
|
||||||
|
*/
|
||||||
|
public getStats() {
|
||||||
|
return {
|
||||||
|
registeredComponents: this.registeredComponents.size,
|
||||||
|
pendingRpcCalls: this.pendingRpcCalls.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
222
packages/network/src/Core/SyncVarManager.ts
Normal file
222
packages/network/src/Core/SyncVarManager.ts
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
/**
|
||||||
|
* 同步变量管理器
|
||||||
|
*
|
||||||
|
* 负责管理 SyncVar 的同步逻辑,包括变化检测、权限验证和消息发送
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createLogger } from '@esengine/ecs-framework';
|
||||||
|
import { NetworkBehaviour } from '../NetworkBehaviour';
|
||||||
|
import { getSyncVarMetadata, SYNCVAR_METADATA_KEY } from '../decorators/SyncVar';
|
||||||
|
import { SyncVarMessage, SyncVarMetadata } from '../types/NetworkTypes';
|
||||||
|
|
||||||
|
const logger = createLogger('SyncVarManager');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SyncVar 变化记录
|
||||||
|
*/
|
||||||
|
interface SyncVarChange {
|
||||||
|
networkId: number;
|
||||||
|
componentType: string;
|
||||||
|
propertyName: string;
|
||||||
|
oldValue: any;
|
||||||
|
newValue: any;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SyncVarManager {
|
||||||
|
private static _instance: SyncVarManager | null = null;
|
||||||
|
|
||||||
|
/** 待同步的变化列表 */
|
||||||
|
private pendingChanges: SyncVarChange[] = [];
|
||||||
|
|
||||||
|
/** 已注册的网络组件实例 */
|
||||||
|
private registeredComponents: Map<string, NetworkBehaviour[]> = new Map();
|
||||||
|
|
||||||
|
public static get instance(): SyncVarManager {
|
||||||
|
if (!SyncVarManager._instance) {
|
||||||
|
SyncVarManager._instance = new SyncVarManager();
|
||||||
|
}
|
||||||
|
return SyncVarManager._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册网络组件,为其设置 SyncVar 支持
|
||||||
|
*/
|
||||||
|
public registerComponent(component: NetworkBehaviour): void {
|
||||||
|
const componentType = component.constructor.name;
|
||||||
|
|
||||||
|
// 获取 SyncVar 元数据
|
||||||
|
const metadata = getSyncVarMetadata(component.constructor);
|
||||||
|
if (metadata.length === 0) {
|
||||||
|
return; // 没有 SyncVar,无需处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到注册列表
|
||||||
|
if (!this.registeredComponents.has(componentType)) {
|
||||||
|
this.registeredComponents.set(componentType, []);
|
||||||
|
}
|
||||||
|
this.registeredComponents.get(componentType)!.push(component);
|
||||||
|
|
||||||
|
// 为组件添加变化通知方法
|
||||||
|
this.addSyncVarSupport(component, metadata);
|
||||||
|
|
||||||
|
logger.debug(`注册网络组件: ${componentType},包含 ${metadata.length} 个 SyncVar`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销网络组件
|
||||||
|
*/
|
||||||
|
public unregisterComponent(component: NetworkBehaviour): void {
|
||||||
|
const componentType = component.constructor.name;
|
||||||
|
const components = this.registeredComponents.get(componentType);
|
||||||
|
|
||||||
|
if (components) {
|
||||||
|
const index = components.indexOf(component);
|
||||||
|
if (index !== -1) {
|
||||||
|
components.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (components.length === 0) {
|
||||||
|
this.registeredComponents.delete(componentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`注销网络组件: ${componentType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理收到的 SyncVar 消息
|
||||||
|
*/
|
||||||
|
public handleSyncVarMessage(message: SyncVarMessage): void {
|
||||||
|
const components = this.registeredComponents.get(message.componentType);
|
||||||
|
if (!components) {
|
||||||
|
logger.warn(`收到未知组件类型的 SyncVar 消息: ${message.componentType}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到对应的网络对象
|
||||||
|
const targetComponent = components.find(c => c.networkId === message.networkId);
|
||||||
|
if (!targetComponent) {
|
||||||
|
logger.warn(`找不到网络ID为 ${message.networkId} 的组件: ${message.componentType}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用同步值
|
||||||
|
this.applySyncVar(targetComponent, message.propertyName, message.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待发送的 SyncVar 消息
|
||||||
|
*/
|
||||||
|
public getPendingMessages(): SyncVarMessage[] {
|
||||||
|
const messages: SyncVarMessage[] = [];
|
||||||
|
|
||||||
|
for (const change of this.pendingChanges) {
|
||||||
|
messages.push({
|
||||||
|
type: 'syncvar',
|
||||||
|
networkId: change.networkId,
|
||||||
|
componentType: change.componentType,
|
||||||
|
propertyName: change.propertyName,
|
||||||
|
value: change.newValue,
|
||||||
|
data: change.newValue,
|
||||||
|
timestamp: change.timestamp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空待同步列表
|
||||||
|
this.pendingChanges.length = 0;
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为网络组件添加 SyncVar 支持
|
||||||
|
*/
|
||||||
|
private addSyncVarSupport(component: NetworkBehaviour, metadata: SyncVarMetadata[]): void {
|
||||||
|
// 添加变化通知方法
|
||||||
|
(component as any).notifySyncVarChanged = (propertyName: string, oldValue: any, newValue: any) => {
|
||||||
|
this.onSyncVarChanged(component, propertyName, oldValue, newValue, metadata);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 SyncVar 变化
|
||||||
|
*/
|
||||||
|
private onSyncVarChanged(
|
||||||
|
component: NetworkBehaviour,
|
||||||
|
propertyName: string,
|
||||||
|
oldValue: any,
|
||||||
|
newValue: any,
|
||||||
|
metadata: SyncVarMetadata[]
|
||||||
|
): void {
|
||||||
|
// 找到对应的元数据
|
||||||
|
const syncVarMeta = metadata.find(m => m.propertyName === propertyName);
|
||||||
|
if (!syncVarMeta) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
if (syncVarMeta.authorityOnly && !component.hasAuthority) {
|
||||||
|
logger.warn(`权限不足,无法修改 SyncVar: ${component.constructor.name}.${propertyName}`);
|
||||||
|
// 回滚值
|
||||||
|
(component as any)[`_${propertyName}`] = oldValue;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录变化
|
||||||
|
const change: SyncVarChange = {
|
||||||
|
networkId: component.networkId,
|
||||||
|
componentType: component.constructor.name,
|
||||||
|
propertyName,
|
||||||
|
oldValue,
|
||||||
|
newValue,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pendingChanges.push(change);
|
||||||
|
|
||||||
|
logger.debug(`SyncVar 变化: ${change.componentType}.${propertyName} = ${newValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用同步变量值
|
||||||
|
*/
|
||||||
|
private applySyncVar(component: NetworkBehaviour, propertyName: string, value: any): void {
|
||||||
|
try {
|
||||||
|
// 直接设置内部值,跳过 setter 的权限检查
|
||||||
|
(component as any)[`_${propertyName}`] = value;
|
||||||
|
|
||||||
|
// 获取并调用变化回调
|
||||||
|
const metadata = getSyncVarMetadata(component.constructor);
|
||||||
|
const syncVarMeta = metadata.find(m => m.propertyName === propertyName);
|
||||||
|
|
||||||
|
if (syncVarMeta?.onChanged && typeof (component as any)[syncVarMeta.onChanged] === 'function') {
|
||||||
|
(component as any)[syncVarMeta.onChanged](undefined, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`应用 SyncVar: ${component.constructor.name}.${propertyName} = ${value}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`应用 SyncVar 失败: ${component.constructor.name}.${propertyName}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理所有待同步变化
|
||||||
|
*/
|
||||||
|
public clearPendingChanges(): void {
|
||||||
|
this.pendingChanges.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计信息
|
||||||
|
*/
|
||||||
|
public getStats() {
|
||||||
|
return {
|
||||||
|
registeredComponents: this.registeredComponents.size,
|
||||||
|
pendingChanges: this.pendingChanges.length,
|
||||||
|
totalInstances: Array.from(this.registeredComponents.values())
|
||||||
|
.reduce((sum, components) => sum + components.length, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* 网络核心模块导出
|
|
||||||
*
|
|
||||||
* 提供网络管理、连接和通信的核心功能
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { NetworkManager } from './NetworkManager';
|
|
||||||
export { NetworkServer } from './NetworkServer';
|
|
||||||
export { NetworkClient } from './NetworkClient';
|
|
||||||
export { NetworkConnection, ConnectionState } from './NetworkConnection';
|
|
||||||
export { NetworkEnvironment, NetworkEnvironmentState } from './NetworkEnvironment';
|
|
||||||
export { NetworkIdentity, NetworkIdentityRegistry } from './NetworkIdentity';
|
|
||||||
export { NetworkPerformanceMonitor } from './NetworkPerformanceMonitor';
|
|
||||||
// 事件接口导出
|
|
||||||
export type { NetworkServerEvents } from './NetworkServer';
|
|
||||||
export type { NetworkClientEvents } from './NetworkClient';
|
|
||||||
export type { NetworkConnectionEvents } from './NetworkConnection';
|
|
||||||
|
|
||||||
// 性能监控类型导出
|
|
||||||
export type { NetworkMetrics, PerformanceSnapshot } from './NetworkPerformanceMonitor';
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
/**
|
|
||||||
* 网络错误处理系统
|
|
||||||
*/
|
|
||||||
|
|
||||||
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,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* 网络同步接口
|
|
||||||
*
|
|
||||||
* 为帧同步框架提供网络状态管理
|
|
||||||
*/
|
|
||||||
export interface INetworkSyncable {
|
|
||||||
/**
|
|
||||||
* 获取网络同步状态
|
|
||||||
*
|
|
||||||
* @returns 序列化的网络状态数据
|
|
||||||
*/
|
|
||||||
getNetworkState(): Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用网络状态
|
|
||||||
*
|
|
||||||
* @param data - 网络状态数据
|
|
||||||
*/
|
|
||||||
applyNetworkState(data: Uint8Array): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取变化的字段编号列表
|
|
||||||
*
|
|
||||||
* @returns 变化字段的编号数组
|
|
||||||
*/
|
|
||||||
getDirtyFields(): number[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记所有字段为干净状态
|
|
||||||
*/
|
|
||||||
markClean(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记字段为脏状态
|
|
||||||
*
|
|
||||||
* @param fieldNumber - 字段编号
|
|
||||||
*/
|
|
||||||
markFieldDirty(fieldNumber: number): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查字段是否为脏状态
|
|
||||||
*
|
|
||||||
* @param fieldNumber - 字段编号
|
|
||||||
* @returns 是否为脏状态
|
|
||||||
*/
|
|
||||||
isFieldDirty(fieldNumber: number): boolean;
|
|
||||||
}
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
import { NetworkMessage } from './NetworkMessage';
|
|
||||||
import { NetworkConnection } from '../Core/NetworkConnection';
|
|
||||||
import { IBasicNetworkMessage, MessageData } from '../types/NetworkTypes';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息处理器接口
|
|
||||||
*/
|
|
||||||
export interface IMessageHandler<T extends IBasicNetworkMessage = IBasicNetworkMessage> {
|
|
||||||
/**
|
|
||||||
* 处理消息
|
|
||||||
*
|
|
||||||
* @param message - 网络消息
|
|
||||||
* @param connection - 发送消息的连接(服务端有效)
|
|
||||||
*/
|
|
||||||
handle(message: T, connection?: NetworkConnection): Promise<void> | void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息处理器注册信息
|
|
||||||
*/
|
|
||||||
interface MessageHandlerInfo<T extends MessageData = MessageData> {
|
|
||||||
handler: IMessageHandler<IBasicNetworkMessage<T>>;
|
|
||||||
messageClass: new (...args: any[]) => IBasicNetworkMessage<T>;
|
|
||||||
priority: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息处理器管理器
|
|
||||||
*
|
|
||||||
* 负责注册、查找和调用消息处理器
|
|
||||||
* 支持消息优先级和类型匹配
|
|
||||||
*/
|
|
||||||
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[]) => IBasicNetworkMessage> = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取消息处理器单例
|
|
||||||
*/
|
|
||||||
public static get Instance(): MessageHandler {
|
|
||||||
if (!MessageHandler._instance) {
|
|
||||||
MessageHandler._instance = new MessageHandler();
|
|
||||||
}
|
|
||||||
return MessageHandler._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册消息处理器
|
|
||||||
*
|
|
||||||
* @param messageType - 消息类型ID
|
|
||||||
* @param messageClass - 消息类构造函数
|
|
||||||
* @param handler - 消息处理器
|
|
||||||
* @param priority - 处理优先级(数字越小优先级越高)
|
|
||||||
*/
|
|
||||||
public registerHandler<TData extends MessageData, T extends IBasicNetworkMessage<TData>>(
|
|
||||||
messageType: number,
|
|
||||||
messageClass: new (...args: any[]) => T,
|
|
||||||
handler: IMessageHandler<T>,
|
|
||||||
priority: number = 0
|
|
||||||
): void {
|
|
||||||
// 注册消息类
|
|
||||||
this._messageClasses.set(messageType, messageClass);
|
|
||||||
|
|
||||||
// 获取或创建处理器列表
|
|
||||||
if (!this._handlers.has(messageType)) {
|
|
||||||
this._handlers.set(messageType, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlers = this._handlers.get(messageType)!;
|
|
||||||
|
|
||||||
// 检查是否已经注册了相同的处理器
|
|
||||||
const existingIndex = handlers.findIndex(h => h.handler === handler);
|
|
||||||
if (existingIndex !== -1) {
|
|
||||||
MessageHandler.logger.warn(`消息类型 ${messageType} 的处理器已存在,将替换优先级`);
|
|
||||||
handlers[existingIndex].priority = priority;
|
|
||||||
} else {
|
|
||||||
// 添加新处理器
|
|
||||||
handlers.push({
|
|
||||||
handler: handler as IMessageHandler<IBasicNetworkMessage>,
|
|
||||||
messageClass: messageClass as new (...args: any[]) => IBasicNetworkMessage,
|
|
||||||
priority
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按优先级排序(数字越小优先级越高)
|
|
||||||
handlers.sort((a, b) => a.priority - b.priority);
|
|
||||||
|
|
||||||
MessageHandler.logger.debug(`注册消息处理器: 类型=${messageType}, 优先级=${priority}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注销消息处理器
|
|
||||||
*
|
|
||||||
* @param messageType - 消息类型ID
|
|
||||||
* @param handler - 消息处理器
|
|
||||||
*/
|
|
||||||
public unregisterHandler(messageType: number, handler: IMessageHandler): void {
|
|
||||||
const handlers = this._handlers.get(messageType);
|
|
||||||
if (!handlers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = handlers.findIndex(h => h.handler === handler);
|
|
||||||
if (index !== -1) {
|
|
||||||
handlers.splice(index, 1);
|
|
||||||
MessageHandler.logger.debug(`注销消息处理器: 类型=${messageType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有处理器了,清理映射
|
|
||||||
if (handlers.length === 0) {
|
|
||||||
this._handlers.delete(messageType);
|
|
||||||
this._messageClasses.delete(messageType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理原始消息数据
|
|
||||||
*
|
|
||||||
* @param data - 原始消息数据
|
|
||||||
* @param connection - 发送消息的连接(服务端有效)
|
|
||||||
* @returns 是否成功处理
|
|
||||||
*/
|
|
||||||
public async handleRawMessage(data: Uint8Array, connection?: NetworkConnection): Promise<boolean> {
|
|
||||||
if (data.length < 4) {
|
|
||||||
MessageHandler.logger.error('消息数据长度不足,至少需要4字节消息类型');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取消息类型(前4字节)
|
|
||||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
||||||
const messageType = view.getUint32(0, true);
|
|
||||||
|
|
||||||
// 查找消息类
|
|
||||||
const MessageClass = this._messageClasses.get(messageType);
|
|
||||||
if (!MessageClass) {
|
|
||||||
MessageHandler.logger.warn(`未知的消息类型: ${messageType}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建消息实例并反序列化
|
|
||||||
try {
|
|
||||||
const message = new MessageClass();
|
|
||||||
message.deserialize(data);
|
|
||||||
|
|
||||||
return await this.handleMessage(message, connection);
|
|
||||||
} catch (error) {
|
|
||||||
MessageHandler.logger.error(`消息反序列化失败 (类型=${messageType}):`, error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理网络消息
|
|
||||||
*
|
|
||||||
* @param message - 网络消息
|
|
||||||
* @param connection - 发送消息的连接(服务端有效)
|
|
||||||
* @returns 是否成功处理
|
|
||||||
*/
|
|
||||||
public async handleMessage(message: IBasicNetworkMessage, connection?: NetworkConnection): Promise<boolean> {
|
|
||||||
const messageType = message.messageType;
|
|
||||||
const handlers = this._handlers.get(messageType);
|
|
||||||
|
|
||||||
if (!handlers || handlers.length === 0) {
|
|
||||||
MessageHandler.logger.warn(`没有找到消息类型 ${messageType} 的处理器`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let handledCount = 0;
|
|
||||||
|
|
||||||
// 按优先级顺序执行所有处理器
|
|
||||||
for (const handlerInfo of handlers) {
|
|
||||||
try {
|
|
||||||
const result = handlerInfo.handler.handle(message, connection);
|
|
||||||
|
|
||||||
// 支持异步处理器
|
|
||||||
if (result instanceof Promise) {
|
|
||||||
await result;
|
|
||||||
}
|
|
||||||
|
|
||||||
handledCount++;
|
|
||||||
} catch (error) {
|
|
||||||
MessageHandler.logger.error(`处理器执行错误 (类型=${messageType}, 优先级=${handlerInfo.priority}):`, error);
|
|
||||||
// 继续执行其他处理器
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handledCount > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取已注册的消息类型列表
|
|
||||||
*
|
|
||||||
* @returns 消息类型数组
|
|
||||||
*/
|
|
||||||
public getRegisteredMessageTypes(): number[] {
|
|
||||||
return Array.from(this._messageClasses.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查消息类型是否已注册
|
|
||||||
*
|
|
||||||
* @param messageType - 消息类型ID
|
|
||||||
* @returns 是否已注册
|
|
||||||
*/
|
|
||||||
public isMessageTypeRegistered(messageType: number): boolean {
|
|
||||||
return this._messageClasses.has(messageType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取消息类型的处理器数量
|
|
||||||
*
|
|
||||||
* @param messageType - 消息类型ID
|
|
||||||
* @returns 处理器数量
|
|
||||||
*/
|
|
||||||
public getHandlerCount(messageType: number): number {
|
|
||||||
const handlers = this._handlers.get(messageType);
|
|
||||||
return handlers ? handlers.length : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除所有处理器
|
|
||||||
*/
|
|
||||||
public clear(): void {
|
|
||||||
this._handlers.clear();
|
|
||||||
this._messageClasses.clear();
|
|
||||||
MessageHandler.logger.info('已清除所有消息处理器');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取消息处理器统计信息
|
|
||||||
*
|
|
||||||
* @returns 统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
totalMessageTypes: number;
|
|
||||||
totalHandlers: number;
|
|
||||||
messageTypes: Array<{
|
|
||||||
type: number;
|
|
||||||
handlerCount: number;
|
|
||||||
className: string;
|
|
||||||
}>;
|
|
||||||
} {
|
|
||||||
let totalHandlers = 0;
|
|
||||||
const messageTypes: Array<{ type: number; handlerCount: number; className: string }> = [];
|
|
||||||
|
|
||||||
for (const [type, handlers] of this._handlers) {
|
|
||||||
const handlerCount = handlers.length;
|
|
||||||
totalHandlers += handlerCount;
|
|
||||||
|
|
||||||
const MessageClass = this._messageClasses.get(type);
|
|
||||||
const className = MessageClass ? MessageClass.name : 'Unknown';
|
|
||||||
|
|
||||||
messageTypes.push({
|
|
||||||
type,
|
|
||||||
handlerCount,
|
|
||||||
className
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalMessageTypes: this._messageClasses.size,
|
|
||||||
totalHandlers,
|
|
||||||
messageTypes
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息处理器装饰器
|
|
||||||
*
|
|
||||||
* 用于自动注册消息处理器
|
|
||||||
*
|
|
||||||
* @param messageType - 消息类型ID
|
|
||||||
* @param messageClass - 消息类构造函数
|
|
||||||
* @param priority - 处理优先级
|
|
||||||
*/
|
|
||||||
export function MessageHandlerDecorator<TData extends MessageData, T extends IBasicNetworkMessage<TData>>(
|
|
||||||
messageType: number,
|
|
||||||
messageClass: new (...args: any[]) => T,
|
|
||||||
priority: number = 0
|
|
||||||
) {
|
|
||||||
return function(target: unknown, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
||||||
const originalMethod = descriptor.value;
|
|
||||||
|
|
||||||
if (typeof originalMethod !== 'function') {
|
|
||||||
throw new Error(`[MessageHandlerDecorator] ${propertyKey} is not a function`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册处理器
|
|
||||||
const handler: IMessageHandler<T> = {
|
|
||||||
handle: async (message: T, connection?: NetworkConnection) => {
|
|
||||||
return await originalMethod.call(target, message, connection);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MessageHandler.Instance.registerHandler(messageType, messageClass, handler, priority);
|
|
||||||
|
|
||||||
return descriptor;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
import { NetworkMessage, JsonMessage } from './NetworkMessage';
|
|
||||||
import { MessageType as CoreMessageType } from '../types/MessageTypes';
|
|
||||||
import { MESSAGE_CONFIG } from '../constants/NetworkConstants';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 内置消息类型枚举
|
|
||||||
*/
|
|
||||||
export enum MessageType {
|
|
||||||
// 基础消息类型 (0-99)
|
|
||||||
RAW = 0,
|
|
||||||
JSON = 1,
|
|
||||||
PROTOBUF = 2,
|
|
||||||
|
|
||||||
// 连接管理消息 (100-199)
|
|
||||||
CONNECT_REQUEST = 100,
|
|
||||||
CONNECT_RESPONSE = 101,
|
|
||||||
DISCONNECT = 102,
|
|
||||||
PING = 103,
|
|
||||||
PONG = 104,
|
|
||||||
|
|
||||||
// 身份验证消息 (200-299)
|
|
||||||
AUTH_REQUEST = 200,
|
|
||||||
AUTH_RESPONSE = 201,
|
|
||||||
|
|
||||||
// 网络对象管理 (300-399)
|
|
||||||
SPAWN_OBJECT = 300,
|
|
||||||
DESTROY_OBJECT = 301,
|
|
||||||
TRANSFER_AUTHORITY = 302,
|
|
||||||
|
|
||||||
// 组件同步消息 (400-499)
|
|
||||||
SYNC_VAR_UPDATE = 400,
|
|
||||||
COMPONENT_STATE = 401,
|
|
||||||
BATCH_UPDATE = 402,
|
|
||||||
|
|
||||||
// RPC调用消息 (500-599)
|
|
||||||
CLIENT_RPC = 500,
|
|
||||||
SERVER_RPC = 501,
|
|
||||||
RPC_RESPONSE = 502,
|
|
||||||
|
|
||||||
// 场景管理消息 (600-699)
|
|
||||||
SCENE_LOAD = 600,
|
|
||||||
SCENE_LOADED = 601,
|
|
||||||
SCENE_UNLOAD = 602,
|
|
||||||
|
|
||||||
// 自定义消息 (1000+)
|
|
||||||
CUSTOM = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接请求消息
|
|
||||||
*/
|
|
||||||
export class ConnectRequestMessage extends JsonMessage<{
|
|
||||||
clientVersion: string;
|
|
||||||
protocolVersion: number;
|
|
||||||
clientId?: string;
|
|
||||||
}> {
|
|
||||||
public override readonly messageType: number = MessageType.CONNECT_REQUEST;
|
|
||||||
|
|
||||||
constructor(clientVersion: string = '1.0.0', protocolVersion: number = 1, clientId?: string) {
|
|
||||||
super({
|
|
||||||
clientVersion,
|
|
||||||
protocolVersion,
|
|
||||||
clientId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接响应消息
|
|
||||||
*/
|
|
||||||
export class ConnectResponseMessage extends JsonMessage<{
|
|
||||||
success: boolean;
|
|
||||||
clientId: string;
|
|
||||||
serverVersion: string;
|
|
||||||
protocolVersion: number;
|
|
||||||
errorMessage?: string;
|
|
||||||
}> {
|
|
||||||
public override readonly messageType: number = MessageType.CONNECT_RESPONSE;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
success: boolean,
|
|
||||||
clientId: string,
|
|
||||||
serverVersion: string = '1.0.0',
|
|
||||||
protocolVersion: number = 1,
|
|
||||||
errorMessage?: string
|
|
||||||
) {
|
|
||||||
super({
|
|
||||||
success,
|
|
||||||
clientId,
|
|
||||||
serverVersion,
|
|
||||||
protocolVersion,
|
|
||||||
errorMessage
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 断开连接消息
|
|
||||||
*/
|
|
||||||
export class DisconnectMessage extends JsonMessage<{
|
|
||||||
reason: string;
|
|
||||||
code?: number;
|
|
||||||
}> {
|
|
||||||
public override readonly messageType: number = MessageType.DISCONNECT;
|
|
||||||
|
|
||||||
constructor(reason: string, code?: number) {
|
|
||||||
super({
|
|
||||||
reason,
|
|
||||||
code
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 心跳消息
|
|
||||||
*/
|
|
||||||
export class PingMessage extends NetworkMessage<{ pingId: number }> {
|
|
||||||
public readonly messageType: number = MessageType.PING;
|
|
||||||
private _data: { pingId: number };
|
|
||||||
|
|
||||||
public get data(): { pingId: number } {
|
|
||||||
return this._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get pingId(): number {
|
|
||||||
return this._data.pingId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set pingId(value: number) {
|
|
||||||
this._data.pingId = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(pingId: number = Date.now()) {
|
|
||||||
super();
|
|
||||||
this._data = { pingId };
|
|
||||||
}
|
|
||||||
|
|
||||||
public serialize(): Uint8Array {
|
|
||||||
const buffer = new ArrayBuffer(12); // 4字节时间戳 + 4字节pingId + 4字节消息类型
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
|
|
||||||
view.setUint32(0, this.messageType, true);
|
|
||||||
view.setUint32(4, this.timestamp, true);
|
|
||||||
view.setUint32(8, this._data.pingId, true);
|
|
||||||
|
|
||||||
return new Uint8Array(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public deserialize(data: Uint8Array): void {
|
|
||||||
if (data.length < 12) {
|
|
||||||
throw new Error('Ping消息数据长度不足');
|
|
||||||
}
|
|
||||||
|
|
||||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
||||||
// messageType在第0-3字节已经被外部处理
|
|
||||||
this.timestamp = view.getUint32(4, true);
|
|
||||||
this._data.pingId = view.getUint32(8, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 心跳响应消息
|
|
||||||
*/
|
|
||||||
export class PongMessage extends NetworkMessage<{ pingId: number; serverTime: number }> {
|
|
||||||
public readonly messageType: number = MessageType.PONG;
|
|
||||||
private _data: { pingId: number; serverTime: number };
|
|
||||||
|
|
||||||
public get data(): { pingId: number; serverTime: number } {
|
|
||||||
return this._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get pingId(): number {
|
|
||||||
return this._data.pingId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set pingId(value: number) {
|
|
||||||
this._data.pingId = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get serverTime(): number {
|
|
||||||
return this._data.serverTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set serverTime(value: number) {
|
|
||||||
this._data.serverTime = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(pingId: number = 0, serverTime: number = Date.now()) {
|
|
||||||
super();
|
|
||||||
this._data = { pingId, serverTime };
|
|
||||||
}
|
|
||||||
|
|
||||||
public serialize(): Uint8Array {
|
|
||||||
const buffer = new ArrayBuffer(16); // 4字节消息类型 + 4字节时间戳 + 4字节pingId + 4字节服务器时间
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
|
|
||||||
view.setUint32(0, this.messageType, true);
|
|
||||||
view.setUint32(4, this.timestamp, true);
|
|
||||||
view.setUint32(8, this._data.pingId, true);
|
|
||||||
view.setUint32(12, this._data.serverTime, true);
|
|
||||||
|
|
||||||
return new Uint8Array(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public deserialize(data: Uint8Array): void {
|
|
||||||
if (data.length < 16) {
|
|
||||||
throw new Error('Pong消息数据长度不足');
|
|
||||||
}
|
|
||||||
|
|
||||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
||||||
// messageType在第0-3字节已经被外部处理
|
|
||||||
this.timestamp = view.getUint32(4, true);
|
|
||||||
this._data.pingId = view.getUint32(8, true);
|
|
||||||
this._data.serverTime = view.getUint32(12, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络对象生成消息
|
|
||||||
*/
|
|
||||||
export class SpawnObjectMessage extends JsonMessage<{
|
|
||||||
networkId: string;
|
|
||||||
prefabName: string;
|
|
||||||
position: { x: number; y: number; z?: number };
|
|
||||||
rotation?: { x: number; y: number; z: number; w: number };
|
|
||||||
ownerId: string;
|
|
||||||
hasAuthority: boolean;
|
|
||||||
components?: Array<{
|
|
||||||
type: string;
|
|
||||||
data: string; // base64编码的protobuf数据
|
|
||||||
}>;
|
|
||||||
}> {
|
|
||||||
public override readonly messageType: number = MessageType.SPAWN_OBJECT;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
networkId: string,
|
|
||||||
prefabName: string,
|
|
||||||
position: { x: number; y: number; z?: number },
|
|
||||||
ownerId: string,
|
|
||||||
hasAuthority: boolean = false,
|
|
||||||
rotation?: { x: number; y: number; z: number; w: number },
|
|
||||||
components?: Array<{ type: string; data: string }>
|
|
||||||
) {
|
|
||||||
super({
|
|
||||||
networkId,
|
|
||||||
prefabName,
|
|
||||||
position,
|
|
||||||
rotation,
|
|
||||||
ownerId,
|
|
||||||
hasAuthority,
|
|
||||||
components
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络对象销毁消息
|
|
||||||
*/
|
|
||||||
export class DestroyObjectMessage extends JsonMessage<{
|
|
||||||
networkId: string;
|
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
public override readonly messageType: number = MessageType.DESTROY_OBJECT;
|
|
||||||
|
|
||||||
constructor(networkId: string, reason?: string) {
|
|
||||||
super({
|
|
||||||
networkId,
|
|
||||||
reason
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权限转移消息
|
|
||||||
*/
|
|
||||||
export class TransferAuthorityMessage extends JsonMessage<{
|
|
||||||
networkId: string;
|
|
||||||
newOwnerId: string;
|
|
||||||
previousOwnerId: string;
|
|
||||||
}> {
|
|
||||||
public override readonly messageType: number = MessageType.TRANSFER_AUTHORITY;
|
|
||||||
|
|
||||||
constructor(networkId: string, newOwnerId: string, previousOwnerId: string) {
|
|
||||||
super({
|
|
||||||
networkId,
|
|
||||||
newOwnerId,
|
|
||||||
previousOwnerId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar字段更新数据
|
|
||||||
*/
|
|
||||||
export interface SyncVarFieldUpdate {
|
|
||||||
/** 字段编号 */
|
|
||||||
fieldNumber: number;
|
|
||||||
/** 字段名称(用于调试) */
|
|
||||||
propertyKey: string;
|
|
||||||
/** 序列化后的新值 */
|
|
||||||
newValue: string | number | boolean | null | undefined | Date | Uint8Array | Record<string, unknown> | unknown[];
|
|
||||||
/** 序列化后的旧值(用于回滚或调试) */
|
|
||||||
oldValue?: string | number | boolean | null | undefined | Date | Uint8Array | Record<string, unknown> | unknown[];
|
|
||||||
/** 字段变化时间戳 */
|
|
||||||
timestamp: number;
|
|
||||||
/** 是否是权威字段(只有权威端可以修改) */
|
|
||||||
authorityOnly?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar更新消息数据结构
|
|
||||||
*/
|
|
||||||
export interface SyncVarUpdateData extends Record<string, unknown> {
|
|
||||||
/** 网络对象ID */
|
|
||||||
networkId: string;
|
|
||||||
/** 组件类型名称 */
|
|
||||||
componentType: string;
|
|
||||||
/** 字段更新列表 */
|
|
||||||
fieldUpdates: SyncVarFieldUpdate[];
|
|
||||||
/** 是否是完整状态同步(而非增量更新) */
|
|
||||||
isFullSync: boolean;
|
|
||||||
/** 发送者ID */
|
|
||||||
senderId: string;
|
|
||||||
/** 同步序号(用于确保顺序) */
|
|
||||||
syncSequence: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar更新消息
|
|
||||||
*
|
|
||||||
* 支持增量同步和批处理
|
|
||||||
*/
|
|
||||||
export class SyncVarUpdateMessage extends JsonMessage<SyncVarUpdateData> {
|
|
||||||
public override readonly messageType: number = MessageType.SYNC_VAR_UPDATE;
|
|
||||||
|
|
||||||
/** 网络对象ID */
|
|
||||||
public get networkId(): string {
|
|
||||||
return this.payload.networkId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set networkId(value: string) {
|
|
||||||
this.payload.networkId = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 组件类型名称 */
|
|
||||||
public get componentType(): string {
|
|
||||||
return this.payload.componentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set componentType(value: string) {
|
|
||||||
this.payload.componentType = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 字段更新列表 */
|
|
||||||
public get fieldUpdates(): SyncVarFieldUpdate[] {
|
|
||||||
return this.payload.fieldUpdates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set fieldUpdates(value: SyncVarFieldUpdate[]) {
|
|
||||||
this.payload.fieldUpdates = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 是否是完整状态同步(而非增量更新) */
|
|
||||||
public get isFullSync(): boolean {
|
|
||||||
return this.payload.isFullSync;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set isFullSync(value: boolean) {
|
|
||||||
this.payload.isFullSync = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 同步序号(用于确保顺序) */
|
|
||||||
public get syncSequence(): number {
|
|
||||||
return this.payload.syncSequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set syncSequence(value: number) {
|
|
||||||
this.payload.syncSequence = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
networkId: string = '',
|
|
||||||
componentType: string = '',
|
|
||||||
fieldUpdates: SyncVarFieldUpdate[] = [],
|
|
||||||
isFullSync: boolean = false,
|
|
||||||
senderId: string = '',
|
|
||||||
syncSequence: number = 0
|
|
||||||
) {
|
|
||||||
const data: SyncVarUpdateData = {
|
|
||||||
networkId,
|
|
||||||
componentType,
|
|
||||||
fieldUpdates,
|
|
||||||
isFullSync,
|
|
||||||
senderId,
|
|
||||||
syncSequence
|
|
||||||
};
|
|
||||||
super(data, senderId, syncSequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加字段更新
|
|
||||||
*/
|
|
||||||
public addFieldUpdate(update: SyncVarFieldUpdate): void {
|
|
||||||
this.payload.fieldUpdates.push(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定字段的更新
|
|
||||||
*/
|
|
||||||
public getFieldUpdate(fieldNumber: number): SyncVarFieldUpdate | undefined {
|
|
||||||
return this.payload.fieldUpdates.find(update => update.fieldNumber === fieldNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除指定字段的更新
|
|
||||||
*/
|
|
||||||
public removeFieldUpdate(fieldNumber: number): boolean {
|
|
||||||
const index = this.payload.fieldUpdates.findIndex(update => update.fieldNumber === fieldNumber);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.payload.fieldUpdates.splice(index, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空所有字段更新
|
|
||||||
*/
|
|
||||||
public clearFieldUpdates(): void {
|
|
||||||
this.payload.fieldUpdates = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取更新的字段数量
|
|
||||||
*/
|
|
||||||
public getUpdateCount(): number {
|
|
||||||
return this.payload.fieldUpdates.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否有字段更新
|
|
||||||
*/
|
|
||||||
public hasUpdates(): boolean {
|
|
||||||
return this.payload.fieldUpdates.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建消息副本
|
|
||||||
*/
|
|
||||||
public override clone(): SyncVarUpdateMessage {
|
|
||||||
return new SyncVarUpdateMessage(
|
|
||||||
this.payload.networkId,
|
|
||||||
this.payload.componentType,
|
|
||||||
[...this.payload.fieldUpdates], // 深拷贝字段更新数组
|
|
||||||
this.payload.isFullSync,
|
|
||||||
this.payload.senderId,
|
|
||||||
this.payload.syncSequence
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取消息统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
updateCount: number;
|
|
||||||
estimatedSize: number;
|
|
||||||
hasAuthorityOnlyFields: boolean;
|
|
||||||
oldestUpdateTime: number;
|
|
||||||
newestUpdateTime: number;
|
|
||||||
} {
|
|
||||||
if (this.payload.fieldUpdates.length === 0) {
|
|
||||||
return {
|
|
||||||
updateCount: 0,
|
|
||||||
estimatedSize: this.getSize(),
|
|
||||||
hasAuthorityOnlyFields: false,
|
|
||||||
oldestUpdateTime: 0,
|
|
||||||
newestUpdateTime: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestamps = this.payload.fieldUpdates.map(u => u.timestamp);
|
|
||||||
const hasAuthorityOnlyFields = this.payload.fieldUpdates.some(u => u.authorityOnly);
|
|
||||||
|
|
||||||
return {
|
|
||||||
updateCount: this.payload.fieldUpdates.length,
|
|
||||||
estimatedSize: this.getSize(),
|
|
||||||
hasAuthorityOnlyFields,
|
|
||||||
oldestUpdateTime: Math.min(...timestamps),
|
|
||||||
newestUpdateTime: Math.max(...timestamps)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量更新消息
|
|
||||||
*
|
|
||||||
* 用于一次性发送多个对象的状态更新
|
|
||||||
*/
|
|
||||||
export class BatchUpdateMessage extends JsonMessage<{
|
|
||||||
updates: Array<{
|
|
||||||
networkId: string;
|
|
||||||
componentType: string;
|
|
||||||
data: string; // base64编码的完整组件状态
|
|
||||||
}>;
|
|
||||||
}> {
|
|
||||||
public override readonly messageType: number = MessageType.BATCH_UPDATE;
|
|
||||||
|
|
||||||
constructor(updates: Array<{ networkId: string; componentType: string; data: string }>) {
|
|
||||||
super({ updates });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,675 +0,0 @@
|
|||||||
import { IBasicNetworkMessage, MessageData } from '../types/NetworkTypes';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络消息基类
|
|
||||||
*
|
|
||||||
* 所有网络消息都应该继承此类
|
|
||||||
* 提供消息的序列化和反序列化功能
|
|
||||||
*/
|
|
||||||
export abstract class NetworkMessage<TData extends MessageData = MessageData> implements IBasicNetworkMessage<TData> {
|
|
||||||
/**
|
|
||||||
* 消息类型ID
|
|
||||||
* 每个消息类型都应该有唯一的ID
|
|
||||||
*/
|
|
||||||
public abstract readonly messageType: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息数据
|
|
||||||
*/
|
|
||||||
public abstract readonly data: TData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息时间戳
|
|
||||||
*/
|
|
||||||
public timestamp: number = Date.now();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送者ID
|
|
||||||
*/
|
|
||||||
public senderId?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息序列号
|
|
||||||
*/
|
|
||||||
public sequence?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化消息为二进制数据
|
|
||||||
*
|
|
||||||
* @returns 序列化后的数据
|
|
||||||
*/
|
|
||||||
public abstract serialize(): Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从二进制数据反序列化消息
|
|
||||||
*
|
|
||||||
* @param data - 二进制数据
|
|
||||||
*/
|
|
||||||
public abstract deserialize(data: Uint8Array): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建消息实例
|
|
||||||
*/
|
|
||||||
protected constructor(
|
|
||||||
senderId?: string,
|
|
||||||
sequence?: number
|
|
||||||
) {
|
|
||||||
this.senderId = senderId;
|
|
||||||
this.sequence = sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取消息大小(字节)
|
|
||||||
*
|
|
||||||
* @returns 消息大小
|
|
||||||
*/
|
|
||||||
public getSize(): number {
|
|
||||||
return this.serialize().length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建消息副本
|
|
||||||
*
|
|
||||||
* @returns 消息副本
|
|
||||||
*/
|
|
||||||
public clone(): NetworkMessage<TData> {
|
|
||||||
const Constructor = this.constructor as new (senderId?: string, sequence?: number) => NetworkMessage<TData>;
|
|
||||||
const cloned = new Constructor(this.senderId, this.sequence);
|
|
||||||
const data = this.serialize();
|
|
||||||
cloned.deserialize(data);
|
|
||||||
return cloned;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 原始二进制消息
|
|
||||||
*
|
|
||||||
* 用于传输原始二进制数据,不进行额外的序列化处理
|
|
||||||
*/
|
|
||||||
export class RawMessage extends NetworkMessage<Uint8Array> {
|
|
||||||
public readonly messageType: number = 0;
|
|
||||||
private _data: Uint8Array;
|
|
||||||
|
|
||||||
public get data(): Uint8Array {
|
|
||||||
return this._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
data: Uint8Array = new Uint8Array(0),
|
|
||||||
senderId?: string,
|
|
||||||
sequence?: number
|
|
||||||
) {
|
|
||||||
super(senderId, sequence);
|
|
||||||
this._data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public serialize(): Uint8Array {
|
|
||||||
// 创建包含消息类型的完整消息格式:[4字节消息类型][原始数据]
|
|
||||||
const buffer = new ArrayBuffer(4 + this._data.length);
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
const uint8Array = new Uint8Array(buffer);
|
|
||||||
|
|
||||||
// 写入消息类型
|
|
||||||
view.setUint32(0, this.messageType, true);
|
|
||||||
|
|
||||||
// 写入原始数据
|
|
||||||
uint8Array.set(this._data, 4);
|
|
||||||
|
|
||||||
return uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public deserialize(data: Uint8Array): void {
|
|
||||||
// 原始数据从第4字节开始(前4字节是消息类型)
|
|
||||||
this._data = data.subarray(4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON消息
|
|
||||||
*
|
|
||||||
* 用于传输JSON数据,自动进行JSON序列化和反序列化
|
|
||||||
*/
|
|
||||||
export class JsonMessage<T = Record<string, unknown>> extends NetworkMessage<Record<string, unknown>> {
|
|
||||||
public readonly messageType: number = 1;
|
|
||||||
private _data: Record<string, unknown>;
|
|
||||||
|
|
||||||
public get data(): Record<string, unknown> {
|
|
||||||
return this._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
payload: T = {} as T,
|
|
||||||
senderId?: string,
|
|
||||||
sequence?: number
|
|
||||||
) {
|
|
||||||
super(senderId, sequence);
|
|
||||||
this._data = { payload };
|
|
||||||
}
|
|
||||||
|
|
||||||
public get payload(): T {
|
|
||||||
return this._data.payload as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
public serialize(): Uint8Array {
|
|
||||||
const payloadBytes = this.serializePayload(this._data.payload);
|
|
||||||
const senderIdBytes = new TextEncoder().encode(this.senderId || '');
|
|
||||||
|
|
||||||
const buffer = new ArrayBuffer(
|
|
||||||
4 + // messageType
|
|
||||||
8 + // timestamp
|
|
||||||
4 + // sequence
|
|
||||||
4 + // senderId length
|
|
||||||
senderIdBytes.length + // senderId
|
|
||||||
payloadBytes.length
|
|
||||||
);
|
|
||||||
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
const uint8Array = new Uint8Array(buffer);
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
view.setUint32(offset, this.messageType, true);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
view.setBigUint64(offset, BigInt(this.timestamp), true);
|
|
||||||
offset += 8;
|
|
||||||
|
|
||||||
view.setUint32(offset, this.sequence || 0, true);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
view.setUint32(offset, senderIdBytes.length, true);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
uint8Array.set(senderIdBytes, offset);
|
|
||||||
offset += senderIdBytes.length;
|
|
||||||
|
|
||||||
uint8Array.set(payloadBytes, offset);
|
|
||||||
|
|
||||||
return uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public deserialize(data: Uint8Array): void {
|
|
||||||
const view = new DataView(data.buffer, data.byteOffset);
|
|
||||||
let offset = 4; // 跳过messageType
|
|
||||||
|
|
||||||
this.timestamp = Number(view.getBigUint64(offset, true));
|
|
||||||
offset += 8;
|
|
||||||
|
|
||||||
this.sequence = view.getUint32(offset, true);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
const senderIdLength = view.getUint32(offset, true);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
this.senderId = new TextDecoder().decode(data.subarray(offset, offset + senderIdLength));
|
|
||||||
offset += senderIdLength;
|
|
||||||
|
|
||||||
const payloadBytes = data.subarray(offset);
|
|
||||||
this._data = { payload: this.deserializePayload(payloadBytes) };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化payload,子类可以重写以使用不同的序列化策略
|
|
||||||
*/
|
|
||||||
protected serializePayload(payload: unknown): Uint8Array {
|
|
||||||
const jsonString = JSON.stringify(payload);
|
|
||||||
return new TextEncoder().encode(jsonString);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 反序列化payload,子类可以重写以使用不同的反序列化策略
|
|
||||||
*/
|
|
||||||
protected deserializePayload(data: Uint8Array): unknown {
|
|
||||||
const jsonString = new TextDecoder().decode(data);
|
|
||||||
return JSON.parse(jsonString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Protobuf消息包装器
|
|
||||||
*
|
|
||||||
* 用于包装已经序列化的Protobuf数据
|
|
||||||
*/
|
|
||||||
export class ProtobufMessage extends NetworkMessage<Uint8Array> {
|
|
||||||
public readonly messageType: number = 2;
|
|
||||||
private _componentType: string;
|
|
||||||
private _data: Uint8Array;
|
|
||||||
|
|
||||||
public get componentType(): string {
|
|
||||||
return this._componentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get data(): Uint8Array {
|
|
||||||
return this._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
componentType: string = '',
|
|
||||||
data: Uint8Array = new Uint8Array(0),
|
|
||||||
senderId?: string,
|
|
||||||
sequence?: number
|
|
||||||
) {
|
|
||||||
super(senderId, sequence);
|
|
||||||
this._componentType = componentType;
|
|
||||||
this._data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public serialize(): Uint8Array {
|
|
||||||
// 创建包含头部信息的消息格式:
|
|
||||||
// [4字节消息类型][4字节时间戳][1字节组件类型长度][组件类型字符串][protobuf数据]
|
|
||||||
const typeBytes = new TextEncoder().encode(this._componentType);
|
|
||||||
const buffer = new ArrayBuffer(4 + 4 + 1 + typeBytes.length + this._data.length);
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
const uint8Array = new Uint8Array(buffer);
|
|
||||||
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
// 写入消息类型(4字节)
|
|
||||||
view.setUint32(offset, this.messageType, true);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
// 写入时间戳(4字节)
|
|
||||||
view.setUint32(offset, this.timestamp, true);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
// 写入组件类型长度(1字节)
|
|
||||||
view.setUint8(offset, typeBytes.length);
|
|
||||||
offset += 1;
|
|
||||||
|
|
||||||
// 写入组件类型字符串
|
|
||||||
uint8Array.set(typeBytes, offset);
|
|
||||||
offset += typeBytes.length;
|
|
||||||
|
|
||||||
// 写入protobuf数据
|
|
||||||
uint8Array.set(this._data, offset);
|
|
||||||
|
|
||||||
return uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public deserialize(data: Uint8Array): void {
|
|
||||||
if (data.length < 9) { // 4+4+1 = 9字节最小长度
|
|
||||||
throw new Error('Protobuf消息数据长度不足');
|
|
||||||
}
|
|
||||||
|
|
||||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
||||||
let offset = 4; // 跳过前4字节消息类型
|
|
||||||
|
|
||||||
// 读取时间戳(4字节)
|
|
||||||
this.timestamp = view.getUint32(offset, true);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
// 读取组件类型长度(1字节)
|
|
||||||
const typeLength = view.getUint8(offset);
|
|
||||||
offset += 1;
|
|
||||||
|
|
||||||
if (data.length < offset + typeLength) {
|
|
||||||
throw new Error('Protobuf消息组件类型数据不足');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取组件类型字符串
|
|
||||||
const typeBytes = data.subarray(offset, offset + typeLength);
|
|
||||||
this._componentType = new TextDecoder().decode(typeBytes);
|
|
||||||
offset += typeLength;
|
|
||||||
|
|
||||||
// 读取protobuf数据
|
|
||||||
this._data = data.subarray(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 心跳消息
|
|
||||||
* 用于维持连接和检测网络延迟
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* 消息系统导出
|
|
||||||
*
|
|
||||||
* 提供网络消息的定义、处理和管理功能
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 消息基类和类型
|
|
||||||
export * from './NetworkMessage';
|
|
||||||
export * from './MessageTypes';
|
|
||||||
export * from './MessageHandler';
|
|
||||||
|
|
||||||
// 导出SyncVar相关的接口和类型
|
|
||||||
export type { SyncVarFieldUpdate } from './MessageTypes';
|
|
||||||
141
packages/network/src/NetworkBehaviour.ts
Normal file
141
packages/network/src/NetworkBehaviour.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* 网络组件基类
|
||||||
|
*
|
||||||
|
* 所有需要网络同步功能的组件都应继承此类
|
||||||
|
* 提供 SyncVar 和 RPC 功能的基础实现
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
import { INetworkBehaviour } from './types/NetworkTypes';
|
||||||
|
import { NetworkIdentity } from './NetworkIdentity';
|
||||||
|
import { NetworkManager } from './NetworkManager';
|
||||||
|
|
||||||
|
export abstract class NetworkBehaviour extends Component implements INetworkBehaviour {
|
||||||
|
/** 网络身份组件引用 */
|
||||||
|
public networkIdentity: NetworkIdentity | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否拥有权威
|
||||||
|
* 权威端可以修改带有 authorityOnly 标记的 SyncVar
|
||||||
|
*/
|
||||||
|
public get hasAuthority(): boolean {
|
||||||
|
return this.networkIdentity?.hasAuthority || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为本地玩家
|
||||||
|
*/
|
||||||
|
public get isLocalPlayer(): boolean {
|
||||||
|
return this.networkIdentity?.isLocalPlayer || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在服务端运行
|
||||||
|
*/
|
||||||
|
public get isServer(): boolean {
|
||||||
|
return NetworkManager.isServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否在客户端运行
|
||||||
|
*/
|
||||||
|
public get isClient(): boolean {
|
||||||
|
return NetworkManager.isClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络ID
|
||||||
|
*/
|
||||||
|
public get networkId(): number {
|
||||||
|
return this.networkIdentity?.networkId || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件初始化时自动注册到网络身份
|
||||||
|
*/
|
||||||
|
public start(): void {
|
||||||
|
this.registerNetworkBehaviour();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件销毁时自动从网络身份注销
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
this.unregisterNetworkBehaviour();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册到网络身份组件
|
||||||
|
*/
|
||||||
|
private registerNetworkBehaviour(): void {
|
||||||
|
if (!this.entity) return;
|
||||||
|
|
||||||
|
const networkIdentity = this.entity.getComponent?.(NetworkIdentity);
|
||||||
|
if (networkIdentity) {
|
||||||
|
networkIdentity.addNetworkBehaviour(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从网络身份组件注销
|
||||||
|
*/
|
||||||
|
private unregisterNetworkBehaviour(): void {
|
||||||
|
if (this.networkIdentity) {
|
||||||
|
this.networkIdentity.removeNetworkBehaviour(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查权威性
|
||||||
|
* 用于验证是否可以执行需要权威的操作
|
||||||
|
*/
|
||||||
|
protected checkAuthority(): boolean {
|
||||||
|
if (!this.hasAuthority) {
|
||||||
|
console.warn(`[NetworkBehaviour] 操作被拒绝:${this.constructor.name} 没有权威`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送客户端RPC
|
||||||
|
* 只能在服务端调用,向指定客户端或所有客户端发送消息
|
||||||
|
*/
|
||||||
|
protected sendClientRpc(methodName: string, args: any[] = [], targetClient?: number): void {
|
||||||
|
if (!this.isServer) {
|
||||||
|
console.warn(`[NetworkBehaviour] ClientRpc 只能在服务端调用: ${methodName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkManager.instance?.sendClientRpc(
|
||||||
|
this.networkId,
|
||||||
|
this.constructor.name,
|
||||||
|
methodName,
|
||||||
|
args,
|
||||||
|
targetClient
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送命令到服务端
|
||||||
|
* 只能在客户端调用,向服务端发送命令
|
||||||
|
*/
|
||||||
|
protected sendCommand(methodName: string, args: any[] = []): void {
|
||||||
|
if (!this.isClient) {
|
||||||
|
console.warn(`[NetworkBehaviour] Command 只能在客户端调用: ${methodName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.hasAuthority) {
|
||||||
|
console.warn(`[NetworkBehaviour] Command 需要权威才能调用: ${methodName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkManager.instance?.sendCommand(
|
||||||
|
this.networkId,
|
||||||
|
this.constructor.name,
|
||||||
|
methodName,
|
||||||
|
args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { INetworkSyncable } from './INetworkSyncable';
|
|
||||||
import { NetworkRole } from './NetworkRole';
|
|
||||||
import { NetworkEnvironment } from './Core/NetworkEnvironment';
|
|
||||||
import { SyncVarManager } from './SyncVar/SyncVarManager';
|
|
||||||
import { getSyncVarMetadata } from './SyncVar/SyncVarDecorator';
|
|
||||||
import { ComponentRegistry } from '@esengine/ecs-framework';
|
|
||||||
import { isTsrpcSerializable } from './Serialization/TsrpcDecorators';
|
|
||||||
import { TsrpcSerializer } from './Serialization/TsrpcSerializer';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络组件基类
|
|
||||||
*
|
|
||||||
* 继承核心ECS的Component类,添加网络同步功能。
|
|
||||||
* 用于需要网络同步的组件,提供帧同步框架所需的网络状态管理。
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* import { NetworkComponent } from '@esengine/ecs-framework-network';
|
|
||||||
* import { TsrpcSerializable, SyncField } from '@esengine/ecs-framework-network';
|
|
||||||
*
|
|
||||||
* @TsrpcSerializable()
|
|
||||||
* class PositionComponent extends NetworkComponent {
|
|
||||||
* @SyncField()
|
|
||||||
* public x: number = 0;
|
|
||||||
*
|
|
||||||
* @SyncField()
|
|
||||||
* public y: number = 0;
|
|
||||||
*
|
|
||||||
* constructor(x: number = 0, y: number = 0) {
|
|
||||||
* super();
|
|
||||||
* this.x = x;
|
|
||||||
* this.y = y;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // 客户端特有逻辑
|
|
||||||
* public onClientUpdate(): void {
|
|
||||||
* if (__CLIENT__) {
|
|
||||||
* // 客户端构建时才包含此逻辑
|
|
||||||
* this.handleInputPrediction();
|
|
||||||
* this.interpolatePosition();
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // 服务端特有逻辑
|
|
||||||
* public onServerUpdate(): void {
|
|
||||||
* if (__SERVER__) {
|
|
||||||
* // 服务端构建时才包含此逻辑
|
|
||||||
* this.validateMovement();
|
|
||||||
* this.broadcastToClients();
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // 使用方式(角色由环境自动检测)
|
|
||||||
* const position = new PositionComponent(10, 20);
|
|
||||||
* // 角色由 NetworkManager.StartServer() 或 StartClient() 决定
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export abstract class NetworkComponent extends Component implements INetworkSyncable {
|
|
||||||
/** SyncVar内部ID */
|
|
||||||
_syncVarId?: string;
|
|
||||||
/** SyncVar监听禁用标志 */
|
|
||||||
_syncVarDisabled?: boolean;
|
|
||||||
|
|
||||||
/** 允许通过字符串键访问属性 */
|
|
||||||
[propertyKey: string]: unknown;
|
|
||||||
/**
|
|
||||||
* 脏字段标记集合
|
|
||||||
*
|
|
||||||
* 记录已修改但尚未同步的字段编号
|
|
||||||
*/
|
|
||||||
private _dirtyFields: Set<number> = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字段变化时间戳
|
|
||||||
*
|
|
||||||
* 记录每个字段最后修改的时间
|
|
||||||
*/
|
|
||||||
private _fieldTimestamps: Map<number, number> = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造函数
|
|
||||||
*
|
|
||||||
* 角色信息通过NetworkEnvironment自动获取,无需手动传入
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.initializeSyncVar();
|
|
||||||
this.ensureComponentRegistered();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 确保当前组件类型已注册到ComponentRegistry
|
|
||||||
*/
|
|
||||||
private ensureComponentRegistered(): void {
|
|
||||||
try {
|
|
||||||
|
|
||||||
// 检查当前组件类型是否已注册
|
|
||||||
if (!ComponentRegistry.isRegistered(this.constructor as any)) {
|
|
||||||
// 如果未注册,自动注册
|
|
||||||
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 = { info: console.log, warn: console.warn, error: console.error, debug: console.debug };
|
|
||||||
logger.warn(`无法注册组件类型 ${this.constructor.name}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化SyncVar系统
|
|
||||||
*
|
|
||||||
* 如果组件有SyncVar字段,自动创建代理来监听变化
|
|
||||||
*/
|
|
||||||
private initializeSyncVar(): void {
|
|
||||||
const metadata = getSyncVarMetadata(this.constructor);
|
|
||||||
if (metadata.length > 0) {
|
|
||||||
const logger = { info: console.log, warn: console.warn, error: console.error, debug: console.debug };
|
|
||||||
logger.debug(`${this.constructor.name} 发现 ${metadata.length} 个SyncVar字段,将启用代理监听`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取网络角色
|
|
||||||
*
|
|
||||||
* 从全局网络环境获取当前角色
|
|
||||||
* @returns 当前组件的网络角色
|
|
||||||
*/
|
|
||||||
public getRole(): NetworkRole {
|
|
||||||
return NetworkEnvironment.getPrimaryRole();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否为客户端角色
|
|
||||||
*
|
|
||||||
* @returns 是否为客户端
|
|
||||||
*/
|
|
||||||
public isClient(): boolean {
|
|
||||||
return NetworkEnvironment.isClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否为服务端角色
|
|
||||||
*
|
|
||||||
* @returns 是否为服务端
|
|
||||||
*/
|
|
||||||
public isServer(): boolean {
|
|
||||||
return NetworkEnvironment.isServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取网络同步状态
|
|
||||||
*
|
|
||||||
* 序列化当前组件状态为网络传输格式
|
|
||||||
* @returns 序列化的网络状态数据
|
|
||||||
*/
|
|
||||||
public getNetworkState(): Uint8Array {
|
|
||||||
if (!isTsrpcSerializable(this)) {
|
|
||||||
throw new Error(`组件 ${this.constructor.name} 不支持网络同步,请添加@TsrpcSerializable装饰器`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const serializer = TsrpcSerializer.getInstance();
|
|
||||||
const serializedData = serializer.serialize(this);
|
|
||||||
if (!serializedData) {
|
|
||||||
throw new Error(`序列化失败: 组件=${this.constructor.name}`);
|
|
||||||
}
|
|
||||||
return serializedData.data;
|
|
||||||
} catch (error) {
|
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
||||||
throw new Error(`获取网络状态失败: 组件=${this.constructor.name}, 错误=${errorMsg}, 可能原因: 序列化字段格式错误或网络连接问题`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用网络状态
|
|
||||||
*
|
|
||||||
* 从网络数据恢复组件状态
|
|
||||||
* @param data - 网络状态数据
|
|
||||||
*/
|
|
||||||
public applyNetworkState(data: Uint8Array): void {
|
|
||||||
if (!isTsrpcSerializable(this)) {
|
|
||||||
throw new Error(`组件 ${this.constructor.name} 不支持网络同步,请添加@TsrpcSerializable装饰器`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const serializer = TsrpcSerializer.getInstance();
|
|
||||||
const serializedData = {
|
|
||||||
type: 'tsrpc' as const,
|
|
||||||
componentType: this.constructor.name,
|
|
||||||
data: data,
|
|
||||||
size: data.length
|
|
||||||
};
|
|
||||||
|
|
||||||
// 反序列化并应用到当前组件实例
|
|
||||||
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) {
|
|
||||||
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}, 已恢复到安全状态`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取变化的字段编号列表
|
|
||||||
*
|
|
||||||
* @returns 变化字段的编号数组
|
|
||||||
*/
|
|
||||||
public getDirtyFields(): number[] {
|
|
||||||
return Array.from(this._dirtyFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记所有字段为干净状态
|
|
||||||
*
|
|
||||||
* 清除所有脏字段标记
|
|
||||||
*/
|
|
||||||
public markClean(): void {
|
|
||||||
this._dirtyFields.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记字段为脏状态
|
|
||||||
*
|
|
||||||
* 用于标记字段已修改,需要网络同步
|
|
||||||
* @param fieldNumber - 字段编号
|
|
||||||
*/
|
|
||||||
public markFieldDirty(fieldNumber: number): void {
|
|
||||||
this._dirtyFields.add(fieldNumber);
|
|
||||||
this._fieldTimestamps.set(fieldNumber, Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查字段是否为脏状态
|
|
||||||
*
|
|
||||||
* @param fieldNumber - 字段编号
|
|
||||||
* @returns 是否为脏状态
|
|
||||||
*/
|
|
||||||
public isFieldDirty(fieldNumber: number): boolean {
|
|
||||||
return this._dirtyFields.has(fieldNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取字段最后修改时间
|
|
||||||
*
|
|
||||||
* @param fieldNumber - 字段编号
|
|
||||||
* @returns 最后修改时间戳,如果字段从未修改则返回0
|
|
||||||
*/
|
|
||||||
public getFieldTimestamp(fieldNumber: number): number {
|
|
||||||
return this._fieldTimestamps.get(fieldNumber) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有脏字段及其时间戳
|
|
||||||
*
|
|
||||||
* @returns 脏字段和时间戳的映射
|
|
||||||
*/
|
|
||||||
public getDirtyFieldsWithTimestamps(): Map<number, number> {
|
|
||||||
const result = new Map<number, number>();
|
|
||||||
for (const fieldNumber of this._dirtyFields) {
|
|
||||||
result.set(fieldNumber, this._fieldTimestamps.get(fieldNumber) || 0);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取待同步的SyncVar变化
|
|
||||||
*
|
|
||||||
* @returns 待同步的变化数组
|
|
||||||
*/
|
|
||||||
public getSyncVarChanges(): any[] {
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
return syncVarManager.getPendingChanges(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建SyncVar同步数据
|
|
||||||
*
|
|
||||||
* @returns 同步数据,如果没有变化则返回null
|
|
||||||
*/
|
|
||||||
public createSyncVarData(): any {
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
return syncVarManager.createSyncData(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用SyncVar同步数据
|
|
||||||
*
|
|
||||||
* @param syncData - 同步数据
|
|
||||||
*/
|
|
||||||
public applySyncVarData(syncData: any): void {
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
syncVarManager.applySyncData(this, syncData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除SyncVar变化记录
|
|
||||||
*
|
|
||||||
* @param propertyKeys - 要清除的属性名数组,如果不提供则清除所有
|
|
||||||
*/
|
|
||||||
public clearSyncVarChanges(propertyKeys?: string[]): void {
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
syncVarManager.clearChanges(this, propertyKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件是否有SyncVar字段
|
|
||||||
*
|
|
||||||
* @returns 是否有SyncVar字段
|
|
||||||
*/
|
|
||||||
public hasSyncVars(): boolean {
|
|
||||||
const metadata = getSyncVarMetadata(this.constructor);
|
|
||||||
return metadata.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取SyncVar统计信息
|
|
||||||
*
|
|
||||||
* @returns 统计信息
|
|
||||||
*/
|
|
||||||
public getSyncVarStats(): any {
|
|
||||||
const metadata = getSyncVarMetadata(this.constructor);
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
const changes = syncVarManager.getPendingChanges(this);
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalSyncVars: metadata.length,
|
|
||||||
pendingChanges: changes.length,
|
|
||||||
syncVarFields: metadata.map(m => ({
|
|
||||||
propertyKey: m.propertyKey,
|
|
||||||
fieldNumber: m.fieldNumber,
|
|
||||||
hasHook: !!m.options.hook,
|
|
||||||
authorityOnly: !!m.options.authorityOnly
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端更新逻辑
|
|
||||||
*
|
|
||||||
* 子类可以重写此方法实现客户端特有的逻辑,如:
|
|
||||||
* - 输入预测
|
|
||||||
* - 状态插值
|
|
||||||
* - 回滚重放
|
|
||||||
*/
|
|
||||||
public onClientUpdate(): void {
|
|
||||||
// 默认空实现,子类可根据需要重写
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务端更新逻辑
|
|
||||||
*
|
|
||||||
* 子类可以重写此方法实现服务端特有的逻辑,如:
|
|
||||||
* - 输入验证
|
|
||||||
* - 权威状态计算
|
|
||||||
* - 状态广播
|
|
||||||
*/
|
|
||||||
public onServerUpdate(): void {
|
|
||||||
// 默认空实现,子类可根据需要重写
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 统一的更新入口
|
|
||||||
*
|
|
||||||
* 根据角色调用相应的更新方法
|
|
||||||
*/
|
|
||||||
public override update(): void {
|
|
||||||
if (this.isClient()) {
|
|
||||||
this.onClientUpdate();
|
|
||||||
} else if (this.isServer()) {
|
|
||||||
this.onServerUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
105
packages/network/src/NetworkIdentity.ts
Normal file
105
packages/network/src/NetworkIdentity.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* 网络身份组件
|
||||||
|
*
|
||||||
|
* 标识网络对象的唯一身份,管理网络组件和权威性
|
||||||
|
* 所有需要网络同步的实体都必须拥有此组件
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
import { INetworkBehaviour } from './types/NetworkTypes';
|
||||||
|
|
||||||
|
export class NetworkIdentity extends Component {
|
||||||
|
/** 网络对象的唯一标识符 */
|
||||||
|
public networkId: number = 0;
|
||||||
|
|
||||||
|
/** 所有者客户端ID,0 表示服务端拥有 */
|
||||||
|
public ownerId: number = 0;
|
||||||
|
|
||||||
|
/** 是否拥有权威,权威端可以修改 SyncVar 和发送 RPC */
|
||||||
|
public hasAuthority: boolean = false;
|
||||||
|
|
||||||
|
/** 是否为本地玩家对象 */
|
||||||
|
public isLocalPlayer: boolean = false;
|
||||||
|
|
||||||
|
/** 挂载的网络组件列表 */
|
||||||
|
public networkBehaviours: INetworkBehaviour[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加网络组件
|
||||||
|
* @param behaviour 要添加的网络组件
|
||||||
|
*/
|
||||||
|
public addNetworkBehaviour(behaviour: INetworkBehaviour): void {
|
||||||
|
if (this.networkBehaviours.includes(behaviour)) {
|
||||||
|
return; // 已经添加过了
|
||||||
|
}
|
||||||
|
|
||||||
|
this.networkBehaviours.push(behaviour);
|
||||||
|
behaviour.networkIdentity = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除网络组件
|
||||||
|
* @param behaviour 要移除的网络组件
|
||||||
|
*/
|
||||||
|
public removeNetworkBehaviour(behaviour: INetworkBehaviour): void {
|
||||||
|
const index = this.networkBehaviours.indexOf(behaviour);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.networkBehaviours.splice(index, 1);
|
||||||
|
behaviour.networkIdentity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置权威性
|
||||||
|
* @param hasAuthority 是否拥有权威
|
||||||
|
* @param ownerId 所有者客户端ID
|
||||||
|
*/
|
||||||
|
public setAuthority(hasAuthority: boolean, ownerId: number = 0): void {
|
||||||
|
this.hasAuthority = hasAuthority;
|
||||||
|
this.ownerId = ownerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置为本地玩家
|
||||||
|
*/
|
||||||
|
public setAsLocalPlayer(): void {
|
||||||
|
this.isLocalPlayer = true;
|
||||||
|
this.hasAuthority = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定类型的网络组件
|
||||||
|
*/
|
||||||
|
public getNetworkBehaviour<T extends INetworkBehaviour>(type: new (...args: any[]) => T): T | null {
|
||||||
|
return this.networkBehaviours.find(b => b instanceof type) as T || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有指定类型的网络组件
|
||||||
|
*/
|
||||||
|
public getNetworkBehaviours<T extends INetworkBehaviour>(type: new (...args: any[]) => T): T[] {
|
||||||
|
return this.networkBehaviours.filter(b => b instanceof type) as T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件启动时的处理
|
||||||
|
*/
|
||||||
|
public start(): void {
|
||||||
|
// 如果没有分配网络ID,从网络管理器获取
|
||||||
|
if (this.networkId === 0) {
|
||||||
|
// 这里需要从 NetworkManager 获取新的网络ID
|
||||||
|
// 实现延后到 NetworkManager 完成
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件销毁时的清理
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
// 清理所有网络组件的引用
|
||||||
|
this.networkBehaviours.forEach(behaviour => {
|
||||||
|
behaviour.networkIdentity = null;
|
||||||
|
});
|
||||||
|
this.networkBehaviours.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
449
packages/network/src/NetworkManager.ts
Normal file
449
packages/network/src/NetworkManager.ts
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
/**
|
||||||
|
* 网络管理器
|
||||||
|
*
|
||||||
|
* 网络库的核心管理类,负责:
|
||||||
|
* - 客户端/服务端连接管理
|
||||||
|
* - 网络消息路由和处理
|
||||||
|
* - SyncVar 同步调度
|
||||||
|
* - RPC 调用管理
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createLogger, Component } from '@esengine/ecs-framework';
|
||||||
|
import {
|
||||||
|
NetworkConfig,
|
||||||
|
NetworkSide,
|
||||||
|
NetworkConnectionState,
|
||||||
|
NetworkStats,
|
||||||
|
NetworkEventHandlers,
|
||||||
|
SyncVarMessage,
|
||||||
|
RpcMessage,
|
||||||
|
NetworkMessage
|
||||||
|
} from './types/NetworkTypes';
|
||||||
|
import { NetworkRegistry } from './core/NetworkRegistry';
|
||||||
|
import { SyncVarManager } from './core/SyncVarManager';
|
||||||
|
import { RpcManager } from './core/RpcManager';
|
||||||
|
import { NetworkIdentity } from './NetworkIdentity';
|
||||||
|
import { NetworkBehaviour } from './NetworkBehaviour';
|
||||||
|
import { TsrpcTransport, TransportEventHandlers } from './transport/TsrpcTransport';
|
||||||
|
|
||||||
|
const logger = createLogger('NetworkManager');
|
||||||
|
|
||||||
|
export class NetworkManager extends Component {
|
||||||
|
private static _instance: NetworkManager | null = null;
|
||||||
|
|
||||||
|
/** 当前网络端类型 */
|
||||||
|
private networkSide: NetworkSide = 'client';
|
||||||
|
|
||||||
|
/** 连接状态 */
|
||||||
|
private connectionState: NetworkConnectionState = 'disconnected';
|
||||||
|
|
||||||
|
/** 网络配置 */
|
||||||
|
private config: NetworkConfig = {
|
||||||
|
port: 7777,
|
||||||
|
host: 'localhost',
|
||||||
|
maxConnections: 100,
|
||||||
|
syncRate: 20,
|
||||||
|
compression: false
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 网络统计信息 */
|
||||||
|
private stats: NetworkStats = {
|
||||||
|
connectionCount: 0,
|
||||||
|
bytesSent: 0,
|
||||||
|
bytesReceived: 0,
|
||||||
|
messagesSent: 0,
|
||||||
|
messagesReceived: 0,
|
||||||
|
averageLatency: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 事件处理器 */
|
||||||
|
private eventHandlers: Partial<NetworkEventHandlers> = {};
|
||||||
|
|
||||||
|
/** 同步定时器 */
|
||||||
|
private syncTimer: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
/** TSRPC传输层 */
|
||||||
|
private transport: TsrpcTransport | null = null;
|
||||||
|
|
||||||
|
public static get instance(): NetworkManager | null {
|
||||||
|
return NetworkManager._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get isServer(): boolean {
|
||||||
|
return NetworkManager._instance?.networkSide === 'server' ||
|
||||||
|
NetworkManager._instance?.networkSide === 'host';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get isClient(): boolean {
|
||||||
|
return NetworkManager._instance?.networkSide === 'client' ||
|
||||||
|
NetworkManager._instance?.networkSide === 'host';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get isConnected(): boolean {
|
||||||
|
return NetworkManager._instance?.connectionState === 'connected';
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
if (NetworkManager._instance) {
|
||||||
|
throw new Error('NetworkManager 已存在实例,请使用 NetworkManager.instance');
|
||||||
|
}
|
||||||
|
NetworkManager._instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动服务端
|
||||||
|
* @param config 网络配置
|
||||||
|
*/
|
||||||
|
public async startServer(config?: Partial<NetworkConfig>): Promise<void> {
|
||||||
|
if (this.connectionState !== 'disconnected') {
|
||||||
|
throw new Error('网络管理器已在运行中');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.networkSide = 'server';
|
||||||
|
this.config = { ...this.config, ...config };
|
||||||
|
this.connectionState = 'connecting';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 初始化TSRPC传输层
|
||||||
|
await this.initializeTransport();
|
||||||
|
|
||||||
|
// 启动TSRPC服务端
|
||||||
|
await this.transport!.startServer();
|
||||||
|
|
||||||
|
this.connectionState = 'connected';
|
||||||
|
this.startSyncLoop();
|
||||||
|
|
||||||
|
logger.info(`服务端已启动,端口: ${this.config.port}`);
|
||||||
|
this.eventHandlers.onConnected?.();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.connectionState = 'disconnected';
|
||||||
|
logger.error('启动服务端失败:', error);
|
||||||
|
this.eventHandlers.onError?.(error as Error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接到服务端
|
||||||
|
* @param host 服务端地址
|
||||||
|
* @param port 服务端端口
|
||||||
|
*/
|
||||||
|
public async connectToServer(host?: string, port?: number): Promise<void> {
|
||||||
|
if (this.connectionState !== 'disconnected') {
|
||||||
|
throw new Error('已经连接或正在连接中');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.networkSide = 'client';
|
||||||
|
this.config.host = host || this.config.host;
|
||||||
|
this.config.port = port || this.config.port;
|
||||||
|
this.connectionState = 'connecting';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 初始化TSRPC传输层
|
||||||
|
await this.initializeTransport();
|
||||||
|
|
||||||
|
// 连接到TSRPC服务端
|
||||||
|
await this.transport!.connectToServer();
|
||||||
|
|
||||||
|
this.connectionState = 'connected';
|
||||||
|
this.startSyncLoop();
|
||||||
|
|
||||||
|
logger.info(`已连接到服务端: ${this.config.host}:${this.config.port}`);
|
||||||
|
this.eventHandlers.onConnected?.();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.connectionState = 'disconnected';
|
||||||
|
logger.error('连接服务端失败:', error);
|
||||||
|
this.eventHandlers.onError?.(error as Error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开连接
|
||||||
|
*/
|
||||||
|
public async disconnect(): Promise<void> {
|
||||||
|
if (this.connectionState === 'disconnected') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionState = 'disconnected';
|
||||||
|
this.stopSyncLoop();
|
||||||
|
|
||||||
|
// 关闭TSRPC传输层连接
|
||||||
|
if (this.transport) {
|
||||||
|
await this.transport.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('网络连接已断开');
|
||||||
|
this.eventHandlers.onDisconnected?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册网络对象
|
||||||
|
* @param entity 包含 NetworkIdentity 的实体
|
||||||
|
* @returns 分配的网络ID
|
||||||
|
*/
|
||||||
|
public registerNetworkObject(entity: any): number {
|
||||||
|
const networkIdentity = entity.getComponent?.(NetworkIdentity);
|
||||||
|
if (!networkIdentity) {
|
||||||
|
throw new Error('实体必须包含 NetworkIdentity 组件才能注册为网络对象');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册到网络注册表
|
||||||
|
const networkId = NetworkRegistry.instance.register(networkIdentity);
|
||||||
|
|
||||||
|
// 注册所有网络组件到管理器
|
||||||
|
const networkBehaviours = entity.getComponents?.()?.filter((c: any) => c instanceof NetworkBehaviour) || [];
|
||||||
|
for (const behaviour of networkBehaviours) {
|
||||||
|
SyncVarManager.instance.registerComponent(behaviour as NetworkBehaviour);
|
||||||
|
RpcManager.instance.registerComponent(behaviour as NetworkBehaviour);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`注册网络对象: ${entity.name}, ID: ${networkId}`);
|
||||||
|
return networkId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送客户端 RPC(服务端调用)
|
||||||
|
*/
|
||||||
|
public sendClientRpc(
|
||||||
|
networkId: number,
|
||||||
|
componentType: string,
|
||||||
|
methodName: string,
|
||||||
|
args: any[] = [],
|
||||||
|
targetClient?: number
|
||||||
|
): void {
|
||||||
|
if (!NetworkManager.isServer) {
|
||||||
|
logger.warn('ClientRpc 只能在服务端调用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message: RpcMessage = {
|
||||||
|
type: 'rpc',
|
||||||
|
networkId,
|
||||||
|
data: {
|
||||||
|
componentType,
|
||||||
|
methodName,
|
||||||
|
args
|
||||||
|
},
|
||||||
|
methodName,
|
||||||
|
args,
|
||||||
|
isClientRpc: true,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sendMessage(message, targetClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送命令到服务端(客户端调用)
|
||||||
|
*/
|
||||||
|
public sendCommand(
|
||||||
|
networkId: number,
|
||||||
|
componentType: string,
|
||||||
|
methodName: string,
|
||||||
|
args: any[] = []
|
||||||
|
): void {
|
||||||
|
if (!NetworkManager.isClient) {
|
||||||
|
logger.warn('Command 只能在客户端调用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message: RpcMessage = {
|
||||||
|
type: 'rpc',
|
||||||
|
networkId,
|
||||||
|
data: {
|
||||||
|
componentType,
|
||||||
|
methodName,
|
||||||
|
args
|
||||||
|
},
|
||||||
|
methodName,
|
||||||
|
args,
|
||||||
|
isClientRpc: false,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置事件处理器
|
||||||
|
*/
|
||||||
|
public on<K extends keyof NetworkEventHandlers>(
|
||||||
|
event: K,
|
||||||
|
handler: NetworkEventHandlers[K]
|
||||||
|
): void {
|
||||||
|
this.eventHandlers[event] = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取网络统计信息
|
||||||
|
*/
|
||||||
|
public getStats(): NetworkStats {
|
||||||
|
return { ...this.stats };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接状态
|
||||||
|
*/
|
||||||
|
public getConnectionState(): NetworkConnectionState {
|
||||||
|
return this.connectionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件销毁时清理
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
this.disconnect();
|
||||||
|
NetworkManager._instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化传输层
|
||||||
|
*/
|
||||||
|
private async initializeTransport(): Promise<void> {
|
||||||
|
if (this.transport) {
|
||||||
|
return; // 已经初始化
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建TSRPC传输层
|
||||||
|
this.transport = new TsrpcTransport(this.config);
|
||||||
|
|
||||||
|
// 设置传输层事件处理器
|
||||||
|
const transportHandlers: TransportEventHandlers = {
|
||||||
|
onConnected: () => {
|
||||||
|
logger.debug('传输层连接已建立');
|
||||||
|
},
|
||||||
|
|
||||||
|
onDisconnected: (reason) => {
|
||||||
|
logger.debug(`传输层连接已断开: ${reason}`);
|
||||||
|
if (this.connectionState === 'connected') {
|
||||||
|
this.connectionState = 'disconnected';
|
||||||
|
this.eventHandlers.onDisconnected?.(reason);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onClientConnected: (clientId) => {
|
||||||
|
logger.debug(`客户端 ${clientId} 已连接到传输层`);
|
||||||
|
this.eventHandlers.onClientConnected?.(clientId);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClientDisconnected: (clientId, reason) => {
|
||||||
|
logger.debug(`客户端 ${clientId} 已从传输层断开: ${reason}`);
|
||||||
|
this.eventHandlers.onClientDisconnected?.(clientId, reason);
|
||||||
|
|
||||||
|
// 清理断开连接的客户端对象
|
||||||
|
NetworkRegistry.instance.cleanupDisconnectedClient(clientId);
|
||||||
|
},
|
||||||
|
|
||||||
|
onMessage: (message, fromClientId) => {
|
||||||
|
this.handleMessage(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
onError: (error) => {
|
||||||
|
logger.error('传输层错误:', error);
|
||||||
|
this.eventHandlers.onError?.(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.transport.setEventHandlers(transportHandlers);
|
||||||
|
logger.debug('TSRPC传输层已初始化');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动同步循环
|
||||||
|
*/
|
||||||
|
private startSyncLoop(): void {
|
||||||
|
if (this.syncTimer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const interval = 1000 / (this.config.syncRate || 20);
|
||||||
|
this.syncTimer = setInterval(() => {
|
||||||
|
this.processSyncVars();
|
||||||
|
}, interval);
|
||||||
|
|
||||||
|
logger.debug(`同步循环已启动,频率: ${this.config.syncRate} Hz`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止同步循环
|
||||||
|
*/
|
||||||
|
private stopSyncLoop(): void {
|
||||||
|
if (this.syncTimer) {
|
||||||
|
clearInterval(this.syncTimer);
|
||||||
|
this.syncTimer = null;
|
||||||
|
logger.debug('同步循环已停止');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 SyncVar 同步
|
||||||
|
*/
|
||||||
|
private processSyncVars(): void {
|
||||||
|
if (!NetworkManager.isServer) {
|
||||||
|
return; // 只有服务端发送同步消息
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncVarMessages = SyncVarManager.instance.getPendingMessages();
|
||||||
|
for (const message of syncVarMessages) {
|
||||||
|
this.sendMessage(message).catch(error => {
|
||||||
|
logger.error('发送SyncVar消息失败:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 RPC 消息
|
||||||
|
const rpcMessages = RpcManager.instance.getPendingRpcMessages();
|
||||||
|
for (const message of rpcMessages) {
|
||||||
|
this.sendMessage(message).catch(error => {
|
||||||
|
logger.error('发送RPC消息失败:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送网络消息
|
||||||
|
*/
|
||||||
|
private async sendMessage(message: NetworkMessage, targetClient?: number): Promise<void> {
|
||||||
|
if (!this.transport) {
|
||||||
|
logger.warn('传输层未初始化,无法发送消息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.transport.sendMessage(message, targetClient);
|
||||||
|
this.stats.messagesSent++;
|
||||||
|
logger.debug(`发送消息: ${message.type}, 网络ID: ${message.networkId}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('发送消息失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理收到的网络消息(供外部调用)
|
||||||
|
*/
|
||||||
|
public handleIncomingMessage(message: NetworkMessage): void {
|
||||||
|
this.handleMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理收到的网络消息
|
||||||
|
*/
|
||||||
|
private handleMessage(message: NetworkMessage): void {
|
||||||
|
this.stats.messagesReceived++;
|
||||||
|
|
||||||
|
switch (message.type) {
|
||||||
|
case 'syncvar':
|
||||||
|
SyncVarManager.instance.handleSyncVarMessage(message as SyncVarMessage);
|
||||||
|
break;
|
||||||
|
case 'rpc':
|
||||||
|
RpcManager.instance.handleRpcMessage(message as RpcMessage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.warn(`未知消息类型: ${message.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* 网络组件角色枚举
|
|
||||||
*
|
|
||||||
* 定义网络组件在帧同步框架中的角色
|
|
||||||
*/
|
|
||||||
export enum NetworkRole {
|
|
||||||
/**
|
|
||||||
* 客户端角色
|
|
||||||
*
|
|
||||||
* 组件将执行客户端特有的逻辑,如:
|
|
||||||
* - 输入预测
|
|
||||||
* - 状态插值
|
|
||||||
* - 回滚重放
|
|
||||||
*/
|
|
||||||
CLIENT = 'client',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务端角色
|
|
||||||
*
|
|
||||||
* 组件将执行服务端特有的逻辑,如:
|
|
||||||
* - 输入验证
|
|
||||||
* - 权威状态计算
|
|
||||||
* - 状态广播
|
|
||||||
*/
|
|
||||||
SERVER = 'server'
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface SerializedData {
|
|
||||||
type: 'tsrpc' | 'json';
|
|
||||||
componentType: string;
|
|
||||||
data: Uint8Array | any;
|
|
||||||
size: number;
|
|
||||||
schema?: string;
|
|
||||||
version?: number;
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
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();
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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,37 +0,0 @@
|
|||||||
export { SerializedData } from './SerializationTypes';
|
|
||||||
|
|
||||||
export {
|
|
||||||
SyncFieldOptions,
|
|
||||||
TsrpcSerializableOptions,
|
|
||||||
TsrpcFieldMetadata,
|
|
||||||
TsrpcComponentMetadata,
|
|
||||||
TsrpcSerializable,
|
|
||||||
TsrpcSupportedTypes,
|
|
||||||
TsrpcSerializationStats
|
|
||||||
} from './TsrpcTypes';
|
|
||||||
|
|
||||||
export {
|
|
||||||
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,92 +0,0 @@
|
|||||||
/**
|
|
||||||
* 可序列化接口
|
|
||||||
*
|
|
||||||
* 实现此接口的类可以被快照系统序列化和反序列化
|
|
||||||
*/
|
|
||||||
export interface ISnapshotable {
|
|
||||||
/**
|
|
||||||
* 序列化对象到可传输的数据格式
|
|
||||||
*
|
|
||||||
* @returns 序列化后的数据
|
|
||||||
*/
|
|
||||||
serialize(): any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从序列化数据恢复对象状态
|
|
||||||
*
|
|
||||||
* @param data - 序列化数据
|
|
||||||
*/
|
|
||||||
deserialize(data: any): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 快照配置接口
|
|
||||||
*/
|
|
||||||
export interface SnapshotConfig {
|
|
||||||
/** 是否包含在快照中 */
|
|
||||||
includeInSnapshot: boolean;
|
|
||||||
/** 压缩级别 (0-9) */
|
|
||||||
compressionLevel: number;
|
|
||||||
/** 同步优先级 (数字越大优先级越高) */
|
|
||||||
syncPriority: number;
|
|
||||||
/** 是否启用增量快照 */
|
|
||||||
enableIncremental: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件快照数据
|
|
||||||
*/
|
|
||||||
export interface ComponentSnapshot {
|
|
||||||
/** 组件类型名称 */
|
|
||||||
type: string;
|
|
||||||
/** 组件ID */
|
|
||||||
id: number;
|
|
||||||
/** 序列化数据 */
|
|
||||||
data: any;
|
|
||||||
/** 是否启用 */
|
|
||||||
enabled: boolean;
|
|
||||||
/** 快照配置 */
|
|
||||||
config?: SnapshotConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实体快照数据
|
|
||||||
*/
|
|
||||||
export interface EntitySnapshot {
|
|
||||||
/** 实体ID */
|
|
||||||
id: number;
|
|
||||||
/** 实体名称 */
|
|
||||||
name: string;
|
|
||||||
/** 是否启用 */
|
|
||||||
enabled: boolean;
|
|
||||||
/** 是否激活 */
|
|
||||||
active: boolean;
|
|
||||||
/** 标签 */
|
|
||||||
tag: number;
|
|
||||||
/** 更新顺序 */
|
|
||||||
updateOrder: number;
|
|
||||||
/** 组件快照列表 */
|
|
||||||
components: ComponentSnapshot[];
|
|
||||||
/** 子实体ID列表 */
|
|
||||||
children: number[];
|
|
||||||
/** 父实体ID */
|
|
||||||
parent?: number;
|
|
||||||
/** 快照时间戳 */
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 场景快照数据
|
|
||||||
*/
|
|
||||||
export interface SceneSnapshot {
|
|
||||||
/** 实体快照列表 */
|
|
||||||
entities: EntitySnapshot[];
|
|
||||||
/** 快照时间戳 */
|
|
||||||
timestamp: number;
|
|
||||||
/** 框架版本 */
|
|
||||||
version: string;
|
|
||||||
/** 快照类型 */
|
|
||||||
type: 'full' | 'incremental';
|
|
||||||
/** 基础快照ID(增量快照使用) */
|
|
||||||
baseSnapshotId?: string;
|
|
||||||
}
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
import { Component, ComponentType } from '@esengine/ecs-framework';
|
|
||||||
import { ISnapshotable, SnapshotConfig } from './ISnapshotable';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 快照扩展接口
|
|
||||||
*
|
|
||||||
* 为Component基类提供快照功能的扩展接口
|
|
||||||
*/
|
|
||||||
export interface ISnapshotExtension {
|
|
||||||
/** 快照配置 */
|
|
||||||
snapshotConfig?: SnapshotConfig;
|
|
||||||
|
|
||||||
/** 序列化方法 */
|
|
||||||
serialize?(): any;
|
|
||||||
|
|
||||||
/** 反序列化方法 */
|
|
||||||
deserialize?(data: any): void;
|
|
||||||
|
|
||||||
/** 变化检测方法 */
|
|
||||||
hasChanged?(baseData: any): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 快照装饰器
|
|
||||||
*
|
|
||||||
* 用于标记组件属性为可序列化
|
|
||||||
*/
|
|
||||||
export function Serializable(config?: Partial<SnapshotConfig>) {
|
|
||||||
return function <T extends Component>(target: T, propertyKey: keyof T) {
|
|
||||||
const comp = target as T & { snapshotConfig?: SnapshotConfig; _serializableProperties?: Set<string> };
|
|
||||||
|
|
||||||
// 确保组件有快照配置
|
|
||||||
if (!comp.snapshotConfig) {
|
|
||||||
comp.snapshotConfig = {
|
|
||||||
includeInSnapshot: true,
|
|
||||||
compressionLevel: 0,
|
|
||||||
syncPriority: 5,
|
|
||||||
enableIncremental: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记属性为可序列化
|
|
||||||
if (!comp._serializableProperties) {
|
|
||||||
comp._serializableProperties = new Set<string>();
|
|
||||||
}
|
|
||||||
comp._serializableProperties.add(propertyKey as string);
|
|
||||||
|
|
||||||
// 应用配置
|
|
||||||
if (config) {
|
|
||||||
Object.assign(comp.snapshotConfig, config);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 快照配置装饰器
|
|
||||||
*
|
|
||||||
* 用于配置组件的快照行为
|
|
||||||
*/
|
|
||||||
export function SnapshotConfigDecorator(config: SnapshotConfig) {
|
|
||||||
return function <T extends ComponentType>(target: T) {
|
|
||||||
target.prototype.snapshotConfig = config;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 快照扩展工具类
|
|
||||||
*/
|
|
||||||
export class SnapshotExtension {
|
|
||||||
/**
|
|
||||||
* 为组件添加快照支持
|
|
||||||
*
|
|
||||||
* @param component - 目标组件
|
|
||||||
* @param config - 快照配置
|
|
||||||
*/
|
|
||||||
public static enableSnapshot<T extends Component>(component: T, config?: Partial<SnapshotConfig>): void {
|
|
||||||
const defaultConfig: SnapshotConfig = {
|
|
||||||
includeInSnapshot: true,
|
|
||||||
compressionLevel: 0,
|
|
||||||
syncPriority: 5,
|
|
||||||
enableIncremental: true
|
|
||||||
};
|
|
||||||
|
|
||||||
(component as T & { snapshotConfig?: SnapshotConfig }).snapshotConfig = { ...defaultConfig, ...config };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 禁用组件的快照功能
|
|
||||||
*
|
|
||||||
* @param component - 目标组件
|
|
||||||
*/
|
|
||||||
public static disableSnapshot<T extends Component>(component: T): void {
|
|
||||||
const comp = component as T & { snapshotConfig?: SnapshotConfig };
|
|
||||||
if (comp.snapshotConfig) {
|
|
||||||
comp.snapshotConfig.includeInSnapshot = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件是否支持快照
|
|
||||||
*
|
|
||||||
* @param component - 目标组件
|
|
||||||
* @returns 是否支持快照
|
|
||||||
*/
|
|
||||||
public static isSnapshotable<T extends Component>(component: T): component is T & ISnapshotExtension {
|
|
||||||
const config = (component as T & { snapshotConfig?: SnapshotConfig }).snapshotConfig;
|
|
||||||
return config?.includeInSnapshot === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件的可序列化属性
|
|
||||||
*
|
|
||||||
* @param component - 目标组件
|
|
||||||
* @returns 可序列化属性列表
|
|
||||||
*/
|
|
||||||
public static getSerializableProperties<T extends Component>(component: T): (keyof T)[] {
|
|
||||||
const comp = component as T & { _serializableProperties?: Set<string> };
|
|
||||||
if (comp._serializableProperties) {
|
|
||||||
return Array.from(comp._serializableProperties) as (keyof T)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有标记,返回所有公共属性
|
|
||||||
const publicProperties: (keyof T)[] = [];
|
|
||||||
for (const key in component) {
|
|
||||||
if (component.hasOwnProperty(key) &&
|
|
||||||
typeof component[key] !== 'function' &&
|
|
||||||
key !== 'id' &&
|
|
||||||
key !== 'entity' &&
|
|
||||||
key !== '_enabled' &&
|
|
||||||
key !== '_updateOrder') {
|
|
||||||
publicProperties.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return publicProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建组件的默认序列化方法
|
|
||||||
*
|
|
||||||
* @param component - 目标组件
|
|
||||||
* @returns 序列化数据
|
|
||||||
*/
|
|
||||||
public static createDefaultSerializer<T extends Component>(component: T): () => Partial<T> {
|
|
||||||
return function(): Partial<T> {
|
|
||||||
const data = {} as Partial<T>;
|
|
||||||
const properties = SnapshotExtension.getSerializableProperties(component);
|
|
||||||
|
|
||||||
for (const prop of properties) {
|
|
||||||
const value = component[prop];
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
data[prop] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建组件的默认反序列化方法
|
|
||||||
*
|
|
||||||
* @param component - 目标组件
|
|
||||||
* @returns 反序列化函数
|
|
||||||
*/
|
|
||||||
public static createDefaultDeserializer<T extends Component>(component: T): (data: Partial<T>) => void {
|
|
||||||
return function(data: Partial<T>): void {
|
|
||||||
const properties = SnapshotExtension.getSerializableProperties(component);
|
|
||||||
|
|
||||||
for (const prop of properties) {
|
|
||||||
if (data.hasOwnProperty(prop) && data[prop] !== undefined) {
|
|
||||||
component[prop] = data[prop]!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建简单的变化检测方法
|
|
||||||
*
|
|
||||||
* @param component - 目标组件
|
|
||||||
* @returns 变化检测函数
|
|
||||||
*/
|
|
||||||
public static createSimpleChangeDetector<T extends Component>(component: T): (baseData: Partial<T>) => boolean {
|
|
||||||
return function(baseData: Partial<T>): boolean {
|
|
||||||
const properties = SnapshotExtension.getSerializableProperties(component);
|
|
||||||
|
|
||||||
for (const prop of properties) {
|
|
||||||
const currentValue = component[prop];
|
|
||||||
const baseValue = baseData[prop];
|
|
||||||
|
|
||||||
if (currentValue !== baseValue) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建深度变化检测方法
|
|
||||||
*
|
|
||||||
* @param component - 目标组件
|
|
||||||
* @returns 变化检测函数
|
|
||||||
*/
|
|
||||||
public static createDeepChangeDetector<T extends Component>(component: T): (baseData: Partial<T>) => boolean {
|
|
||||||
return function(baseData: Partial<T>): boolean {
|
|
||||||
const properties = SnapshotExtension.getSerializableProperties(component);
|
|
||||||
|
|
||||||
for (const prop of properties) {
|
|
||||||
const currentValue = component[prop];
|
|
||||||
const baseValue = baseData[prop];
|
|
||||||
|
|
||||||
if (SnapshotExtension.deepCompare(currentValue, baseValue)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 深度比较两个值
|
|
||||||
*/
|
|
||||||
private static deepCompare(value1: unknown, value2: unknown): boolean {
|
|
||||||
if (value1 === value2) return false;
|
|
||||||
|
|
||||||
if (typeof value1 !== typeof value2) return true;
|
|
||||||
|
|
||||||
if (value1 === null || value2 === null) return value1 !== value2;
|
|
||||||
|
|
||||||
if (typeof value1 !== 'object' || typeof value2 !== 'object') return value1 !== value2;
|
|
||||||
|
|
||||||
if (Array.isArray(value1) !== Array.isArray(value2)) return true;
|
|
||||||
|
|
||||||
if (Array.isArray(value1) && Array.isArray(value2)) {
|
|
||||||
if (value1.length !== value2.length) return true;
|
|
||||||
for (let i = 0; i < value1.length; i++) {
|
|
||||||
if (this.deepCompare(value1[i], value2[i])) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value1 || !value2) return true;
|
|
||||||
|
|
||||||
const keys1 = Object.keys(value1);
|
|
||||||
const keys2 = Object.keys(value2);
|
|
||||||
|
|
||||||
if (keys1.length !== keys2.length) return true;
|
|
||||||
|
|
||||||
for (const key of keys1) {
|
|
||||||
if (!keys2.includes(key)) return true;
|
|
||||||
if (this.deepCompare((value1 as Record<string, unknown>)[key], (value2 as Record<string, unknown>)[key])) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,798 +0,0 @@
|
|||||||
import { Entity, Component, ComponentType, createLogger } from '@esengine/ecs-framework';
|
|
||||||
import { ISnapshotable, SceneSnapshot, EntitySnapshot, ComponentSnapshot, SnapshotConfig } from './ISnapshotable';
|
|
||||||
import { TsrpcSerializer } from '../Serialization/TsrpcSerializer';
|
|
||||||
import { SerializedData } from '../Serialization/SerializationTypes';
|
|
||||||
import { isTsrpcSerializable } from '../Serialization/TsrpcDecorators';
|
|
||||||
import {
|
|
||||||
NetworkComponentType,
|
|
||||||
IComponentFactory,
|
|
||||||
SerializationTarget,
|
|
||||||
TypeGuards,
|
|
||||||
INetworkSyncable
|
|
||||||
} from '../types/NetworkTypes';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件类型注册表
|
|
||||||
*/
|
|
||||||
class ComponentTypeRegistry implements IComponentFactory {
|
|
||||||
private static _instance: ComponentTypeRegistry | null = null;
|
|
||||||
private _componentTypes: Map<string, NetworkComponentType> = new Map();
|
|
||||||
|
|
||||||
public static get Instance(): ComponentTypeRegistry {
|
|
||||||
if (!ComponentTypeRegistry._instance) {
|
|
||||||
ComponentTypeRegistry._instance = new ComponentTypeRegistry();
|
|
||||||
}
|
|
||||||
return ComponentTypeRegistry._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册组件类型
|
|
||||||
*/
|
|
||||||
public register<T extends Component & INetworkSyncable>(name: string, constructor: NetworkComponentType<T>): void {
|
|
||||||
this._componentTypes.set(name, constructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件构造函数
|
|
||||||
*/
|
|
||||||
public get(name: string): NetworkComponentType | undefined {
|
|
||||||
return this._componentTypes.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动注册组件类型(通过构造函数名)
|
|
||||||
*/
|
|
||||||
public autoRegister<T extends Component & INetworkSyncable>(constructor: NetworkComponentType<T>): void {
|
|
||||||
this.register(constructor.name, constructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有已注册的组件类型
|
|
||||||
*/
|
|
||||||
public getAllTypes(): string[] {
|
|
||||||
return Array.from(this._componentTypes.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件类型是否已注册(按名称)
|
|
||||||
*/
|
|
||||||
public isRegisteredByName(name: string): boolean {
|
|
||||||
return this._componentTypes.has(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除所有注册
|
|
||||||
*/
|
|
||||||
public clear(): void {
|
|
||||||
this._componentTypes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建组件实例
|
|
||||||
*/
|
|
||||||
public create<T extends Component & INetworkSyncable>(
|
|
||||||
componentType: NetworkComponentType<T>,
|
|
||||||
...args: unknown[]
|
|
||||||
): T {
|
|
||||||
return new componentType(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件类型是否已注册
|
|
||||||
*/
|
|
||||||
public isRegistered<T extends Component & INetworkSyncable>(
|
|
||||||
componentType: NetworkComponentType<T>
|
|
||||||
): boolean {
|
|
||||||
return this._componentTypes.has(componentType.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件类型名称
|
|
||||||
*/
|
|
||||||
public getTypeName<T extends Component & INetworkSyncable>(
|
|
||||||
componentType: NetworkComponentType<T>
|
|
||||||
): string {
|
|
||||||
return componentType.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 快照管理器
|
|
||||||
*
|
|
||||||
* 负责创建和管理ECS系统的快照,支持完整快照和增量快照
|
|
||||||
* 使用protobuf序列化
|
|
||||||
*/
|
|
||||||
export class SnapshotManager {
|
|
||||||
private static readonly logger = createLogger('SnapshotManager');
|
|
||||||
|
|
||||||
/** 默认快照配置 */
|
|
||||||
private static readonly DEFAULT_CONFIG: SnapshotConfig = {
|
|
||||||
includeInSnapshot: true,
|
|
||||||
compressionLevel: 0,
|
|
||||||
syncPriority: 5,
|
|
||||||
enableIncremental: true
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 框架版本 */
|
|
||||||
private readonly version: string = '1.0.0';
|
|
||||||
|
|
||||||
/** 最后快照时间戳 */
|
|
||||||
private lastSnapshotTime: number = 0;
|
|
||||||
|
|
||||||
/** 快照缓存 */
|
|
||||||
private snapshotCache = new Map<string, SceneSnapshot>();
|
|
||||||
|
|
||||||
/** 最大缓存数量 */
|
|
||||||
private maxCacheSize: number = 10;
|
|
||||||
|
|
||||||
/** TSRPC序列化器 */
|
|
||||||
private tsrpcSerializer: TsrpcSerializer;
|
|
||||||
|
|
||||||
/** 组件类型注册表 */
|
|
||||||
private componentRegistry: ComponentTypeRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造函数
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.tsrpcSerializer = TsrpcSerializer.getInstance();
|
|
||||||
this.componentRegistry = ComponentTypeRegistry.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建场景快照
|
|
||||||
*
|
|
||||||
* @param entities - 实体列表
|
|
||||||
* @param type - 快照类型
|
|
||||||
* @returns 场景快照
|
|
||||||
*/
|
|
||||||
public createSceneSnapshot(entities: Entity[], type: 'full' | 'incremental' = 'full'): SceneSnapshot {
|
|
||||||
const entitySnapshots: EntitySnapshot[] = [];
|
|
||||||
|
|
||||||
const sortedEntities = entities.sort((a, b) => a.id - b.id);
|
|
||||||
|
|
||||||
for (const entity of sortedEntities) {
|
|
||||||
if (entity.isDestroyed) continue;
|
|
||||||
|
|
||||||
const entitySnapshot = this.createEntitySnapshot(entity);
|
|
||||||
if (entitySnapshot) {
|
|
||||||
entitySnapshots.push(entitySnapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const snapshot: SceneSnapshot = {
|
|
||||||
entities: entitySnapshots,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
version: this.version,
|
|
||||||
type: type
|
|
||||||
};
|
|
||||||
|
|
||||||
this.lastSnapshotTime = snapshot.timestamp;
|
|
||||||
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从快照恢复场景
|
|
||||||
*
|
|
||||||
* @param snapshot - 场景快照
|
|
||||||
* @param targetEntities - 目标实体列表(用于增量恢复)
|
|
||||||
* @param createMissingEntities - 是否创建缺失的实体
|
|
||||||
*/
|
|
||||||
public restoreFromSnapshot(snapshot: SceneSnapshot, targetEntities?: Entity[], createMissingEntities: boolean = false): Entity[] {
|
|
||||||
if (snapshot.type === 'incremental' && targetEntities) {
|
|
||||||
return this.restoreIncrementalSnapshot(snapshot, targetEntities);
|
|
||||||
} else {
|
|
||||||
return this.restoreFullSnapshot(snapshot, targetEntities, createMissingEntities);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从快照恢复实体列表
|
|
||||||
*
|
|
||||||
* @param snapshot - 场景快照
|
|
||||||
* @param targetEntities - 目标实体列表
|
|
||||||
* @param createMissingEntities - 是否创建缺失的实体
|
|
||||||
*/
|
|
||||||
public restoreEntitiesFromSnapshot(snapshot: SceneSnapshot, targetEntities: Entity[], createMissingEntities: boolean = false): Entity[] {
|
|
||||||
const restoredEntities: Entity[] = [];
|
|
||||||
const targetEntityMap = new Map<number, Entity>();
|
|
||||||
|
|
||||||
for (const entity of targetEntities) {
|
|
||||||
targetEntityMap.set(entity.id, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entitySnapshot of snapshot.entities) {
|
|
||||||
let targetEntity = targetEntityMap.get(entitySnapshot.id);
|
|
||||||
|
|
||||||
if (!targetEntity && createMissingEntities) {
|
|
||||||
// 创建缺失的实体
|
|
||||||
const newEntity = this.createEntityFromSnapshot(entitySnapshot);
|
|
||||||
if (newEntity) {
|
|
||||||
restoredEntities.push(newEntity);
|
|
||||||
}
|
|
||||||
} else if (targetEntity) {
|
|
||||||
// 恢复现有实体
|
|
||||||
this.restoreEntityFromSnapshot(targetEntity, entitySnapshot);
|
|
||||||
restoredEntities.push(targetEntity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return restoredEntities;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从快照创建实体
|
|
||||||
*/
|
|
||||||
private createEntityFromSnapshot(entitySnapshot: EntitySnapshot): Entity | null {
|
|
||||||
try {
|
|
||||||
const entity = new Entity(entitySnapshot.name, entitySnapshot.id);
|
|
||||||
|
|
||||||
// 设置基本属性
|
|
||||||
entity.enabled = entitySnapshot.enabled;
|
|
||||||
entity.active = entitySnapshot.active;
|
|
||||||
entity.tag = entitySnapshot.tag;
|
|
||||||
entity.updateOrder = entitySnapshot.updateOrder;
|
|
||||||
|
|
||||||
// 创建组件
|
|
||||||
for (const componentSnapshot of entitySnapshot.components) {
|
|
||||||
this.createComponentFromSnapshot(entity, componentSnapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
} catch (error) {
|
|
||||||
SnapshotManager.logger.error(`创建实体失败: ${entitySnapshot.name}`, error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从快照创建组件
|
|
||||||
*/
|
|
||||||
private createComponentFromSnapshot(entity: Entity, componentSnapshot: ComponentSnapshot): void {
|
|
||||||
try {
|
|
||||||
// 尝试获取组件构造函数
|
|
||||||
const componentType = this.getComponentType(componentSnapshot.type);
|
|
||||||
if (!componentType) {
|
|
||||||
SnapshotManager.logger.warn(`未知组件类型: ${componentSnapshot.type}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建组件实例
|
|
||||||
const component = entity.createComponent(componentType);
|
|
||||||
|
|
||||||
// 恢复组件启用状态
|
|
||||||
component.enabled = componentSnapshot.enabled;
|
|
||||||
|
|
||||||
// 恢复组件数据
|
|
||||||
const serializedData = componentSnapshot.data as SerializedData;
|
|
||||||
|
|
||||||
if (!isTsrpcSerializable(component)) {
|
|
||||||
throw new Error(`[SnapshotManager] 组件 ${component.constructor.name} 不支持TSRPC反序列化`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tsrpcSerializer.deserialize(serializedData);
|
|
||||||
} catch (error) {
|
|
||||||
SnapshotManager.logger.error(`创建组件失败: ${componentSnapshot.type}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件类型
|
|
||||||
*/
|
|
||||||
private getComponentType(typeName: string): NetworkComponentType | null {
|
|
||||||
const componentType = this.componentRegistry.get(typeName);
|
|
||||||
if (!componentType) {
|
|
||||||
SnapshotManager.logger.warn(`组件类型 ${typeName} 未注册,请先调用 registerComponentType() 注册`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return componentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建快速快照(跳过变化检测)
|
|
||||||
*
|
|
||||||
* @param entities - 实体列表
|
|
||||||
* @returns 场景快照
|
|
||||||
*/
|
|
||||||
public createQuickSnapshot(entities: Entity[]): SceneSnapshot {
|
|
||||||
return this.createSceneSnapshot(entities, 'full');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建增量快照
|
|
||||||
*
|
|
||||||
* @param entities - 实体列表
|
|
||||||
* @param baseSnapshot - 基础快照
|
|
||||||
* @param enableChangeDetection - 是否启用变化检测
|
|
||||||
* @returns 增量快照
|
|
||||||
*/
|
|
||||||
public createIncrementalSnapshot(entities: Entity[], baseSnapshot: SceneSnapshot, enableChangeDetection: boolean = true): SceneSnapshot {
|
|
||||||
const incrementalEntities: EntitySnapshot[] = [];
|
|
||||||
|
|
||||||
const baseEntityMap = new Map<number, EntitySnapshot>();
|
|
||||||
for (const entity of baseSnapshot.entities) {
|
|
||||||
baseEntityMap.set(entity.id, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
if (entity.isDestroyed) continue;
|
|
||||||
|
|
||||||
const baseEntity = baseEntityMap.get(entity.id);
|
|
||||||
if (!baseEntity) {
|
|
||||||
const entitySnapshot = this.createEntitySnapshot(entity);
|
|
||||||
if (entitySnapshot) {
|
|
||||||
incrementalEntities.push(entitySnapshot);
|
|
||||||
}
|
|
||||||
} else if (enableChangeDetection) {
|
|
||||||
const changedComponents = this.getChangedComponents(entity, baseEntity);
|
|
||||||
if (this.hasEntityStructureChanged(entity, baseEntity) || changedComponents.length > 0) {
|
|
||||||
const incrementalEntitySnapshot = this.createIncrementalEntitySnapshot(entity, baseEntity, changedComponents);
|
|
||||||
if (incrementalEntitySnapshot) {
|
|
||||||
incrementalEntities.push(incrementalEntitySnapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
entities: incrementalEntities,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
version: this.version,
|
|
||||||
type: 'incremental',
|
|
||||||
baseSnapshotId: this.generateSnapshotId(baseSnapshot)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 缓存快照
|
|
||||||
*
|
|
||||||
* @param id - 快照ID
|
|
||||||
* @param snapshot - 快照数据
|
|
||||||
*/
|
|
||||||
public cacheSnapshot(id: string, snapshot: SceneSnapshot): void {
|
|
||||||
// 清理过期缓存
|
|
||||||
if (this.snapshotCache.size >= this.maxCacheSize) {
|
|
||||||
const oldestKey = this.snapshotCache.keys().next().value;
|
|
||||||
if (oldestKey) {
|
|
||||||
this.snapshotCache.delete(oldestKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.snapshotCache.set(id, snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取缓存的快照
|
|
||||||
*
|
|
||||||
* @param id - 快照ID
|
|
||||||
* @returns 快照数据或undefined
|
|
||||||
*/
|
|
||||||
public getCachedSnapshot(id: string): SceneSnapshot | undefined {
|
|
||||||
return this.snapshotCache.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空快照缓存
|
|
||||||
*/
|
|
||||||
public clearCache(): void {
|
|
||||||
this.snapshotCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空所有缓存
|
|
||||||
*/
|
|
||||||
public clearAllCaches(): void {
|
|
||||||
this.snapshotCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取缓存统计信息
|
|
||||||
*/
|
|
||||||
public getCacheStats(): {
|
|
||||||
snapshotCacheSize: number;
|
|
||||||
tsrpcStats?: {
|
|
||||||
registeredComponents: number;
|
|
||||||
tsrpcAvailable: boolean;
|
|
||||||
};
|
|
||||||
} {
|
|
||||||
const stats: any = {
|
|
||||||
snapshotCacheSize: this.snapshotCache.size
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.tsrpcSerializer) {
|
|
||||||
stats.tsrpcStats = this.tsrpcSerializer.getStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册组件类型
|
|
||||||
*
|
|
||||||
* @param constructor - 组件构造函数
|
|
||||||
*/
|
|
||||||
public registerComponentType<T extends Component & INetworkSyncable>(constructor: NetworkComponentType<T>): void {
|
|
||||||
this.componentRegistry.autoRegister(constructor);
|
|
||||||
SnapshotManager.logger.debug(`已注册组件类型: ${constructor.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量注册组件类型
|
|
||||||
*
|
|
||||||
* @param constructors - 组件构造函数数组
|
|
||||||
*/
|
|
||||||
public registerComponentTypes(constructors: Array<NetworkComponentType>): void {
|
|
||||||
for (const constructor of constructors) {
|
|
||||||
this.registerComponentType(constructor as any);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件类型是否已注册
|
|
||||||
*
|
|
||||||
* @param typeName - 组件类型名称
|
|
||||||
*/
|
|
||||||
public isComponentTypeRegistered(typeName: string): boolean {
|
|
||||||
return this.componentRegistry.isRegisteredByName(typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有已注册的组件类型
|
|
||||||
*/
|
|
||||||
public getRegisteredComponentTypes(): string[] {
|
|
||||||
return this.componentRegistry.getAllTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建实体快照
|
|
||||||
*/
|
|
||||||
private createEntitySnapshot(entity: Entity): EntitySnapshot | null {
|
|
||||||
const componentSnapshots: ComponentSnapshot[] = [];
|
|
||||||
|
|
||||||
for (const component of entity.components) {
|
|
||||||
const componentSnapshot = this.createComponentSnapshot(component);
|
|
||||||
if (componentSnapshot) {
|
|
||||||
componentSnapshots.push(componentSnapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: entity.id,
|
|
||||||
name: entity.name,
|
|
||||||
enabled: entity.enabled,
|
|
||||||
active: entity.active,
|
|
||||||
tag: entity.tag,
|
|
||||||
updateOrder: entity.updateOrder,
|
|
||||||
components: componentSnapshots,
|
|
||||||
children: entity.children.map(child => child.id),
|
|
||||||
parent: entity.parent?.id || undefined,
|
|
||||||
timestamp: Date.now()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建组件快照
|
|
||||||
*
|
|
||||||
* 优先使用TSRPC序列化,fallback到JSON序列化
|
|
||||||
*/
|
|
||||||
private createComponentSnapshot(component: Component): ComponentSnapshot | null {
|
|
||||||
if (!this.isComponentSnapshotable(component)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let serializedData: SerializedData;
|
|
||||||
|
|
||||||
// 优先使用TSRPC序列化
|
|
||||||
if (isTsrpcSerializable(component)) {
|
|
||||||
try {
|
|
||||||
const tsrpcResult = this.tsrpcSerializer.serialize(component);
|
|
||||||
if (tsrpcResult) {
|
|
||||||
serializedData = tsrpcResult;
|
|
||||||
} else {
|
|
||||||
throw new Error('TSRPC序列化返回null');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
SnapshotManager.logger.warn(`[SnapshotManager] TSRPC序列化失败,fallback到JSON: ${error}`);
|
|
||||||
serializedData = this.createJsonSerializedData(component);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// fallback到JSON序列化
|
|
||||||
serializedData = this.createJsonSerializedData(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: component.constructor.name,
|
|
||||||
id: component.id,
|
|
||||||
data: serializedData,
|
|
||||||
enabled: component.enabled,
|
|
||||||
config: this.getComponentSnapshotConfig(component)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建JSON序列化数据
|
|
||||||
*/
|
|
||||||
private createJsonSerializedData(component: Component): SerializedData {
|
|
||||||
// 使用replacer排除循环引用和不需要的属性
|
|
||||||
const jsonData = JSON.stringify(component, (key, value) => {
|
|
||||||
// 排除entity引用以避免循环引用
|
|
||||||
if (key === 'entity') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
// 排除函数和symbol
|
|
||||||
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
type: 'json',
|
|
||||||
componentType: component.constructor.name,
|
|
||||||
data: jsonData,
|
|
||||||
size: jsonData.length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件是否支持快照
|
|
||||||
*/
|
|
||||||
private isComponentSnapshotable(component: Component): boolean {
|
|
||||||
// 检查是否有快照配置
|
|
||||||
const config = this.getComponentSnapshotConfig(component);
|
|
||||||
return config.includeInSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件快照配置
|
|
||||||
*/
|
|
||||||
private getComponentSnapshotConfig(component: Component): SnapshotConfig {
|
|
||||||
// 检查组件是否有自定义配置
|
|
||||||
const componentWithConfig = component as Component & { snapshotConfig?: Partial<SnapshotConfig> };
|
|
||||||
if (componentWithConfig.snapshotConfig) {
|
|
||||||
return { ...SnapshotManager.DEFAULT_CONFIG, ...componentWithConfig.snapshotConfig };
|
|
||||||
}
|
|
||||||
|
|
||||||
return SnapshotManager.DEFAULT_CONFIG;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复完整快照
|
|
||||||
*/
|
|
||||||
private restoreFullSnapshot(snapshot: SceneSnapshot, targetEntities?: Entity[], createMissingEntities: boolean = false): Entity[] {
|
|
||||||
if (targetEntities && createMissingEntities) {
|
|
||||||
return this.restoreEntitiesFromSnapshot(snapshot, targetEntities, true);
|
|
||||||
} else if (targetEntities) {
|
|
||||||
return this.restoreEntitiesFromSnapshot(snapshot, targetEntities, false);
|
|
||||||
} else {
|
|
||||||
const restoredEntities: Entity[] = [];
|
|
||||||
for (const entitySnapshot of snapshot.entities) {
|
|
||||||
const entity = this.createEntityFromSnapshot(entitySnapshot);
|
|
||||||
if (entity) {
|
|
||||||
restoredEntities.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restoredEntities;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复增量快照
|
|
||||||
*/
|
|
||||||
private restoreIncrementalSnapshot(snapshot: SceneSnapshot, targetEntities: Entity[]): Entity[] {
|
|
||||||
const restoredEntities: Entity[] = [];
|
|
||||||
const targetEntityMap = new Map<number, Entity>();
|
|
||||||
|
|
||||||
for (const entity of targetEntities) {
|
|
||||||
targetEntityMap.set(entity.id, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entitySnapshot of snapshot.entities) {
|
|
||||||
const targetEntity = targetEntityMap.get(entitySnapshot.id);
|
|
||||||
if (targetEntity) {
|
|
||||||
this.restoreEntityFromSnapshot(targetEntity, entitySnapshot);
|
|
||||||
restoredEntities.push(targetEntity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return restoredEntities;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从快照恢复实体
|
|
||||||
*/
|
|
||||||
private restoreEntityFromSnapshot(entity: Entity, entitySnapshot: EntitySnapshot): void {
|
|
||||||
// 恢复实体基本属性
|
|
||||||
entity.enabled = entitySnapshot.enabled;
|
|
||||||
entity.active = entitySnapshot.active;
|
|
||||||
entity.tag = entitySnapshot.tag;
|
|
||||||
entity.updateOrder = entitySnapshot.updateOrder;
|
|
||||||
|
|
||||||
// 恢复组件
|
|
||||||
for (const componentSnapshot of entitySnapshot.components) {
|
|
||||||
this.restoreComponentFromSnapshot(entity, componentSnapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从快照恢复组件
|
|
||||||
*
|
|
||||||
* 使用TSRPC反序列化
|
|
||||||
*/
|
|
||||||
private restoreComponentFromSnapshot(entity: Entity, componentSnapshot: ComponentSnapshot): void {
|
|
||||||
// 查找现有组件
|
|
||||||
const componentType = this.getComponentType(componentSnapshot.type);
|
|
||||||
if (!componentType) {
|
|
||||||
SnapshotManager.logger.warn(`组件类型 ${componentSnapshot.type} 未注册,无法恢复`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let component = entity.getComponent(componentType);
|
|
||||||
|
|
||||||
if (!component) {
|
|
||||||
// 组件不存在,需要创建
|
|
||||||
SnapshotManager.logger.warn(`组件 ${componentSnapshot.type} 不存在于实体 ${entity.name},无法恢复`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复组件启用状态
|
|
||||||
component.enabled = componentSnapshot.enabled;
|
|
||||||
|
|
||||||
// 恢复组件数据
|
|
||||||
const serializedData = componentSnapshot.data as SerializedData;
|
|
||||||
|
|
||||||
if (serializedData.type === 'tsrpc' && isTsrpcSerializable(component)) {
|
|
||||||
// 使用TSRPC反序列化
|
|
||||||
this.tsrpcSerializer.deserialize(serializedData);
|
|
||||||
} else if (serializedData.type === 'json') {
|
|
||||||
// 使用JSON反序列化
|
|
||||||
this.deserializeFromJson(component, serializedData);
|
|
||||||
} else {
|
|
||||||
SnapshotManager.logger.warn(`[SnapshotManager] 组件 ${component.constructor.name} 序列化类型不匹配或不支持`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从JSON数据反序列化组件
|
|
||||||
*/
|
|
||||||
private deserializeFromJson(component: Component, serializedData: SerializedData): void {
|
|
||||||
try {
|
|
||||||
const jsonData = JSON.parse(serializedData.data as string);
|
|
||||||
Object.assign(component, jsonData);
|
|
||||||
} catch (error) {
|
|
||||||
SnapshotManager.logger.error(`[SnapshotManager] JSON反序列化失败: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查实体结构是否发生变化(组件数量、类型等)
|
|
||||||
*/
|
|
||||||
private hasEntityStructureChanged(entity: Entity, baseSnapshot: EntitySnapshot): boolean {
|
|
||||||
// 检查基本属性变化
|
|
||||||
if (entity.enabled !== baseSnapshot.enabled ||
|
|
||||||
entity.active !== baseSnapshot.active ||
|
|
||||||
entity.tag !== baseSnapshot.tag ||
|
|
||||||
entity.updateOrder !== baseSnapshot.updateOrder) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查组件数量变化
|
|
||||||
if (entity.components.length !== baseSnapshot.components.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查组件类型变化
|
|
||||||
const currentComponentTypes = new Set(entity.components.map(c => c.constructor.name));
|
|
||||||
const baseComponentTypes = new Set(baseSnapshot.components.map(c => c.type));
|
|
||||||
|
|
||||||
if (currentComponentTypes.size !== baseComponentTypes.size) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const type of currentComponentTypes) {
|
|
||||||
if (!baseComponentTypes.has(type)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取发生变化的组件列表
|
|
||||||
*/
|
|
||||||
private getChangedComponents(entity: Entity, baseSnapshot: EntitySnapshot): ComponentSnapshot[] {
|
|
||||||
const changedComponents: ComponentSnapshot[] = [];
|
|
||||||
|
|
||||||
const baseComponentMap = new Map<string, ComponentSnapshot>();
|
|
||||||
for (const comp of baseSnapshot.components) {
|
|
||||||
baseComponentMap.set(comp.type, comp);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const component of entity.components) {
|
|
||||||
const baseComponent = baseComponentMap.get(component.constructor.name);
|
|
||||||
|
|
||||||
if (!baseComponent) {
|
|
||||||
const componentSnapshot = this.createComponentSnapshot(component);
|
|
||||||
if (componentSnapshot) {
|
|
||||||
changedComponents.push(componentSnapshot);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.hasComponentDataChanged(component, baseComponent)) {
|
|
||||||
const componentSnapshot = this.createComponentSnapshot(component);
|
|
||||||
if (componentSnapshot) {
|
|
||||||
changedComponents.push(componentSnapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changedComponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件数据是否发生变化
|
|
||||||
*/
|
|
||||||
private hasComponentDataChanged(component: Component, baseComponent: ComponentSnapshot): boolean {
|
|
||||||
if (component.enabled !== baseComponent.enabled) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasChangeDetectionMethod(component)) {
|
|
||||||
try {
|
|
||||||
const componentWithMethod = component as Component & { hasChanged(data: unknown): boolean };
|
|
||||||
return componentWithMethod.hasChanged(baseComponent.data);
|
|
||||||
} catch {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件是否有变化检测方法
|
|
||||||
*/
|
|
||||||
private hasChangeDetectionMethod(component: Component): component is Component & { hasChanged(data: unknown): boolean } {
|
|
||||||
return typeof (component as any).hasChanged === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建增量实体快照(只包含变化的组件)
|
|
||||||
*/
|
|
||||||
private createIncrementalEntitySnapshot(entity: Entity, baseSnapshot: EntitySnapshot, changedComponents: ComponentSnapshot[]): EntitySnapshot | null {
|
|
||||||
// 检查实体基本属性是否变化
|
|
||||||
const hasBasicChanges = entity.enabled !== baseSnapshot.enabled ||
|
|
||||||
entity.active !== baseSnapshot.active ||
|
|
||||||
entity.tag !== baseSnapshot.tag ||
|
|
||||||
entity.updateOrder !== baseSnapshot.updateOrder;
|
|
||||||
|
|
||||||
// 如果没有基本变化且没有组件变化,返回null
|
|
||||||
if (!hasBasicChanges && changedComponents.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: entity.id,
|
|
||||||
name: entity.name,
|
|
||||||
enabled: entity.enabled,
|
|
||||||
active: entity.active,
|
|
||||||
tag: entity.tag,
|
|
||||||
updateOrder: entity.updateOrder,
|
|
||||||
components: changedComponents, // 只包含变化的组件
|
|
||||||
children: entity.children.map(child => child.id),
|
|
||||||
parent: entity.parent?.id || undefined,
|
|
||||||
timestamp: Date.now()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成快照ID
|
|
||||||
*/
|
|
||||||
private generateSnapshotId(snapshot: SceneSnapshot): string {
|
|
||||||
return `${snapshot.timestamp}_${snapshot.entities.length}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* 快照系统模块
|
|
||||||
*
|
|
||||||
* 提供ECS系统的快照功能,支持实体和组件的序列化与反序列化
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 核心接口
|
|
||||||
export * from './ISnapshotable';
|
|
||||||
|
|
||||||
// 快照管理器
|
|
||||||
export { SnapshotManager } from './SnapshotManager';
|
|
||||||
|
|
||||||
// 快照扩展
|
|
||||||
export {
|
|
||||||
ISnapshotExtension,
|
|
||||||
Serializable,
|
|
||||||
SnapshotConfigDecorator,
|
|
||||||
SnapshotExtension
|
|
||||||
} from './SnapshotExtension';
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
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生成器配置已更新');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,318 +0,0 @@
|
|||||||
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)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
import 'reflect-metadata';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
const logger = createLogger('SyncVarDecorator');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar配置选项
|
|
||||||
*/
|
|
||||||
export interface SyncVarOptions {
|
|
||||||
/**
|
|
||||||
* 值变化时的回调函数名
|
|
||||||
*
|
|
||||||
* 回调函数签名: (oldValue: T, newValue: T) => void
|
|
||||||
*/
|
|
||||||
hook?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否只有拥有权限的客户端才能修改
|
|
||||||
*
|
|
||||||
* 默认为false,任何客户端都可以修改
|
|
||||||
*/
|
|
||||||
authorityOnly?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义序列化函数
|
|
||||||
*
|
|
||||||
* 如果不提供,将使用默认的类型序列化
|
|
||||||
*/
|
|
||||||
serializer?: (value: any) => Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义反序列化函数
|
|
||||||
*/
|
|
||||||
deserializer?: (data: Uint8Array) => any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步频率限制(毫秒)
|
|
||||||
*
|
|
||||||
* 防止过于频繁的网络同步,默认为0(不限制)
|
|
||||||
*/
|
|
||||||
throttleMs?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar元数据信息
|
|
||||||
*/
|
|
||||||
export interface SyncVarMetadata {
|
|
||||||
/**
|
|
||||||
* 属性名称
|
|
||||||
*/
|
|
||||||
propertyKey: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字段编号(用于protobuf序列化)
|
|
||||||
*/
|
|
||||||
fieldNumber: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置选项
|
|
||||||
*/
|
|
||||||
options: SyncVarOptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 属性类型
|
|
||||||
*/
|
|
||||||
type: Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 最后同步时间(用于频率限制)
|
|
||||||
*/
|
|
||||||
lastSyncTime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar元数据存储
|
|
||||||
*/
|
|
||||||
const SYNCVAR_METADATA_KEY = Symbol('syncvar:metadata');
|
|
||||||
const SYNCVAR_FIELD_COUNTER = Symbol('syncvar:field_counter');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取类的SyncVar元数据
|
|
||||||
*
|
|
||||||
* @param target - 目标类
|
|
||||||
* @returns SyncVar元数据数组
|
|
||||||
*/
|
|
||||||
export function getSyncVarMetadata(target: any): SyncVarMetadata[] {
|
|
||||||
return Reflect.getMetadata(SYNCVAR_METADATA_KEY, target) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置类的SyncVar元数据
|
|
||||||
*
|
|
||||||
* @param target - 目标类
|
|
||||||
* @param metadata - 元数据数组
|
|
||||||
*/
|
|
||||||
export function setSyncVarMetadata(target: any, metadata: SyncVarMetadata[]): void {
|
|
||||||
Reflect.defineMetadata(SYNCVAR_METADATA_KEY, metadata, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取下一个可用的字段编号
|
|
||||||
*
|
|
||||||
* @param target - 目标类
|
|
||||||
* @returns 字段编号
|
|
||||||
*/
|
|
||||||
function getNextFieldNumber(target: any): number {
|
|
||||||
let counter = Reflect.getMetadata(SYNCVAR_FIELD_COUNTER, target) || 1;
|
|
||||||
const nextNumber = counter;
|
|
||||||
Reflect.defineMetadata(SYNCVAR_FIELD_COUNTER, counter + 1, target);
|
|
||||||
return nextNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查属性是否为SyncVar
|
|
||||||
*
|
|
||||||
* @param target - 目标对象
|
|
||||||
* @param propertyKey - 属性名
|
|
||||||
* @returns 是否为SyncVar
|
|
||||||
*/
|
|
||||||
export function isSyncVar(target: any, propertyKey: string): boolean {
|
|
||||||
const metadata = getSyncVarMetadata(target.constructor);
|
|
||||||
return metadata.some(m => m.propertyKey === propertyKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定属性的SyncVar元数据
|
|
||||||
*
|
|
||||||
* @param target - 目标对象
|
|
||||||
* @param propertyKey - 属性名
|
|
||||||
* @returns SyncVar元数据
|
|
||||||
*/
|
|
||||||
export function getSyncVarMetadataForProperty(target: any, propertyKey: string): SyncVarMetadata | undefined {
|
|
||||||
const metadata = getSyncVarMetadata(target.constructor);
|
|
||||||
return metadata.find(m => m.propertyKey === propertyKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar装饰器
|
|
||||||
*
|
|
||||||
* 标记字段为自动同步变量,当值改变时会自动发送给其他客户端
|
|
||||||
*
|
|
||||||
* @param options - 配置选项
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* class PlayerComponent extends NetworkComponent {
|
|
||||||
* @SyncVar()
|
|
||||||
* public health: number = 100;
|
|
||||||
*
|
|
||||||
* @SyncVar({ hook: 'onNameChanged' })
|
|
||||||
* public playerName: string = 'Player';
|
|
||||||
*
|
|
||||||
* @SyncVar({ authorityOnly: true })
|
|
||||||
* public isReady: boolean = false;
|
|
||||||
*
|
|
||||||
* onNameChanged(oldName: string, newName: string) {
|
|
||||||
* const logger = createLogger('PlayerComponent');
|
|
||||||
* logger.info(`Name changed: ${oldName} -> ${newName}`);
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function SyncVar(options: SyncVarOptions = {}): PropertyDecorator {
|
|
||||||
return function (target: any, propertyKey: string | symbol) {
|
|
||||||
if (typeof propertyKey !== 'string') {
|
|
||||||
throw new Error('SyncVar装饰器只能用于字符串属性名');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取属性类型
|
|
||||||
const type = Reflect.getMetadata('design:type', target, propertyKey);
|
|
||||||
if (!type) {
|
|
||||||
logger.warn(`无法获取属性 ${propertyKey} 的类型信息`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取现有元数据
|
|
||||||
const existingMetadata = getSyncVarMetadata(target.constructor);
|
|
||||||
|
|
||||||
// 检查是否已经存在
|
|
||||||
const existingIndex = existingMetadata.findIndex(m => m.propertyKey === propertyKey);
|
|
||||||
if (existingIndex !== -1) {
|
|
||||||
logger.warn(`属性 ${propertyKey} 已经被标记为SyncVar,将覆盖配置`);
|
|
||||||
existingMetadata[existingIndex].options = options;
|
|
||||||
existingMetadata[existingIndex].type = type;
|
|
||||||
} else {
|
|
||||||
// 添加新的元数据
|
|
||||||
const fieldNumber = getNextFieldNumber(target.constructor);
|
|
||||||
const metadata: SyncVarMetadata = {
|
|
||||||
propertyKey,
|
|
||||||
fieldNumber,
|
|
||||||
options,
|
|
||||||
type,
|
|
||||||
lastSyncTime: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
existingMetadata.push(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存元数据
|
|
||||||
setSyncVarMetadata(target.constructor, existingMetadata);
|
|
||||||
|
|
||||||
logger.debug(`注册同步变量: ${target.constructor.name}.${propertyKey}, 字段编号: ${existingMetadata.find(m => m.propertyKey === propertyKey)?.fieldNumber}`);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证SyncVar配置的有效性
|
|
||||||
*
|
|
||||||
* @param target - 目标类实例
|
|
||||||
* @param metadata - SyncVar元数据
|
|
||||||
* @returns 验证结果
|
|
||||||
*/
|
|
||||||
export function validateSyncVarMetadata(target: any, metadata: SyncVarMetadata): {
|
|
||||||
isValid: boolean;
|
|
||||||
errors: string[];
|
|
||||||
} {
|
|
||||||
const errors: string[] = [];
|
|
||||||
|
|
||||||
// 检查属性是否存在
|
|
||||||
if (!(metadata.propertyKey in target)) {
|
|
||||||
errors.push(`属性 ${metadata.propertyKey} 不存在于类 ${target.constructor.name} 中`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查hook函数是否存在
|
|
||||||
if (metadata.options.hook) {
|
|
||||||
if (typeof target[metadata.options.hook] !== 'function') {
|
|
||||||
errors.push(`Hook函数 ${metadata.options.hook} 不存在或不是函数`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查自定义序列化函数
|
|
||||||
if (metadata.options.serializer && typeof metadata.options.serializer !== 'function') {
|
|
||||||
errors.push(`自定义序列化函数必须是function类型`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metadata.options.deserializer && typeof metadata.options.deserializer !== 'function') {
|
|
||||||
errors.push(`自定义反序列化函数必须是function类型`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查频率限制
|
|
||||||
if (metadata.options.throttleMs !== undefined &&
|
|
||||||
(typeof metadata.options.throttleMs !== 'number' || metadata.options.throttleMs < 0)) {
|
|
||||||
errors.push(`throttleMs必须是非负数`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
isValid: errors.length === 0,
|
|
||||||
errors
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取类的所有SyncVar统计信息
|
|
||||||
*
|
|
||||||
* @param target - 目标类
|
|
||||||
* @returns 统计信息
|
|
||||||
*/
|
|
||||||
export function getSyncVarStats(target: any): {
|
|
||||||
totalCount: number;
|
|
||||||
withHooks: number;
|
|
||||||
authorityOnly: number;
|
|
||||||
customSerialized: number;
|
|
||||||
throttled: number;
|
|
||||||
fieldNumbers: number[];
|
|
||||||
} {
|
|
||||||
const metadata = getSyncVarMetadata(target);
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalCount: metadata.length,
|
|
||||||
withHooks: metadata.filter(m => m.options.hook).length,
|
|
||||||
authorityOnly: metadata.filter(m => m.options.authorityOnly).length,
|
|
||||||
customSerialized: metadata.filter(m => m.options.serializer || m.options.deserializer).length,
|
|
||||||
throttled: metadata.filter(m => m.options.throttleMs !== undefined && m.options.throttleMs > 0).length,
|
|
||||||
fieldNumbers: metadata.map(m => m.fieldNumber).sort((a, b) => a - b)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { createSyncVarProxy } from './SyncVarProxy';
|
|
||||||
import { getSyncVarMetadata } from './SyncVarDecorator';
|
|
||||||
import { INetworkSyncable } from '../types/NetworkTypes';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar工厂函数
|
|
||||||
*
|
|
||||||
* 为NetworkComponent创建带有SyncVar代理的实例
|
|
||||||
* 这是必需的,因为TypeScript类构造函数不能直接返回代理对象
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建带SyncVar支持的NetworkComponent实例
|
|
||||||
*
|
|
||||||
* @param ComponentClass - 组件类构造函数
|
|
||||||
* @param args - 构造函数参数
|
|
||||||
* @returns 带代理的组件实例
|
|
||||||
*/
|
|
||||||
const logger = createLogger('SyncVarFactory');
|
|
||||||
|
|
||||||
export function createNetworkComponent<T extends INetworkSyncable>(
|
|
||||||
ComponentClass: new (...args: any[]) => T,
|
|
||||||
...args: any[]
|
|
||||||
): T {
|
|
||||||
// 创建组件实例
|
|
||||||
const instance = new ComponentClass(...args);
|
|
||||||
|
|
||||||
// 检查是否有SyncVar字段
|
|
||||||
const metadata = getSyncVarMetadata(ComponentClass);
|
|
||||||
|
|
||||||
if (metadata.length === 0) {
|
|
||||||
// 没有SyncVar,直接返回原实例
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建代理包装实例
|
|
||||||
const proxy = createSyncVarProxy(instance, {
|
|
||||||
debugLog: false // 可以根据需要启用调试
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.debug(`为 ${ComponentClass.name} 创建了SyncVar代理,包含 ${metadata.length} 个同步字段`);
|
|
||||||
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar组件装饰器
|
|
||||||
*
|
|
||||||
* 装饰器版本的工厂函数,自动为类添加SyncVar支持
|
|
||||||
* 注意:由于TypeScript装饰器的限制,这个方法有一些局限性
|
|
||||||
*
|
|
||||||
* @param options - 配置选项
|
|
||||||
*/
|
|
||||||
export function NetworkComponentWithSyncVar(options: { debugLog?: boolean } = {}) {
|
|
||||||
return function <T extends new (...args: any[]) => INetworkSyncable>(constructor: T) {
|
|
||||||
return class extends constructor {
|
|
||||||
constructor(...args: any[]) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
// 检查是否需要创建代理
|
|
||||||
const metadata = getSyncVarMetadata(constructor);
|
|
||||||
if (metadata.length > 0) {
|
|
||||||
// 返回代理实例
|
|
||||||
return createSyncVarProxy(this as INetworkSyncable, {
|
|
||||||
debugLog: options.debugLog || false
|
|
||||||
}) as this;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
} as T;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 便捷方法:检查实例是否使用了SyncVar工厂创建
|
|
||||||
*
|
|
||||||
* @param instance - 组件实例
|
|
||||||
* @returns 是否使用了SyncVar工厂
|
|
||||||
*/
|
|
||||||
export function isNetworkComponentWithSyncVar(instance: any): boolean {
|
|
||||||
return instance && (instance._syncVarProxied === true || instance.hasSyncVars?.() === true);
|
|
||||||
}
|
|
||||||
@@ -1,846 +0,0 @@
|
|||||||
import {
|
|
||||||
SyncVarMetadata,
|
|
||||||
getSyncVarMetadata,
|
|
||||||
validateSyncVarMetadata,
|
|
||||||
getSyncVarMetadataForProperty
|
|
||||||
} from './SyncVarDecorator';
|
|
||||||
import { NetworkEnvironment } from '../Core/NetworkEnvironment';
|
|
||||||
import { SyncVarUpdateMessage, SyncVarFieldUpdate } from '../Messaging/MessageTypes';
|
|
||||||
import {
|
|
||||||
SyncVarValue,
|
|
||||||
INetworkSyncable,
|
|
||||||
NetworkComponentType,
|
|
||||||
TypeGuards
|
|
||||||
} from '../types/NetworkTypes';
|
|
||||||
import { SYNCVAR_CONFIG } from '../constants/NetworkConstants';
|
|
||||||
import { INetworkComponent } from '../types/CoreTypes';
|
|
||||||
import { ComponentIdGenerator } from './ComponentIdGenerator';
|
|
||||||
import { SyncVarAuthorityManager } from './SyncVarAuthority';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar变化记录
|
|
||||||
*/
|
|
||||||
export interface SyncVarChange {
|
|
||||||
/**
|
|
||||||
* 属性名
|
|
||||||
*/
|
|
||||||
propertyKey: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字段编号
|
|
||||||
*/
|
|
||||||
fieldNumber: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 旧值
|
|
||||||
*/
|
|
||||||
oldValue: SyncVarValue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新值
|
|
||||||
*/
|
|
||||||
newValue: SyncVarValue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 变化时间戳
|
|
||||||
*/
|
|
||||||
timestamp: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否需要网络同步
|
|
||||||
*/
|
|
||||||
needsSync: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar同步数据
|
|
||||||
*/
|
|
||||||
export interface SyncVarSyncData {
|
|
||||||
/**
|
|
||||||
* 组件类名
|
|
||||||
*/
|
|
||||||
componentType: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络对象ID(将来实现)
|
|
||||||
*/
|
|
||||||
networkId?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字段更新数据
|
|
||||||
*/
|
|
||||||
fieldUpdates: Array<{
|
|
||||||
fieldNumber: number;
|
|
||||||
data: Uint8Array;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 时间戳
|
|
||||||
*/
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar管理器
|
|
||||||
*
|
|
||||||
* 负责管理组件的SyncVar变量,检测变化,处理序列化和同步
|
|
||||||
*/
|
|
||||||
export class SyncVarManager {
|
|
||||||
private static _instance: SyncVarManager | null = null;
|
|
||||||
private static readonly logger = {
|
|
||||||
info: console.log,
|
|
||||||
warn: console.warn,
|
|
||||||
error: console.error,
|
|
||||||
debug: console.debug
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件实例的SyncVar变化监听器
|
|
||||||
* Key: 组件实例的唯一ID
|
|
||||||
* Value: 变化记录数组
|
|
||||||
*/
|
|
||||||
private _componentChanges: Map<string, SyncVarChange[]> = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件实例的最后同步时间
|
|
||||||
*/
|
|
||||||
private _lastSyncTimes: Map<string, Map<string, number>> = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取SyncVarManager单例
|
|
||||||
*/
|
|
||||||
public static get Instance(): SyncVarManager {
|
|
||||||
if (!SyncVarManager._instance) {
|
|
||||||
SyncVarManager._instance = new SyncVarManager();
|
|
||||||
}
|
|
||||||
return SyncVarManager._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化组件的SyncVar系统
|
|
||||||
*
|
|
||||||
* @param component - 网络组件实例
|
|
||||||
* @returns 是否成功初始化
|
|
||||||
*/
|
|
||||||
public initializeComponent<T extends INetworkSyncable>(component: T): boolean {
|
|
||||||
const componentId = this.getComponentId(component);
|
|
||||||
const metadata = getSyncVarMetadata(component.constructor as NetworkComponentType);
|
|
||||||
|
|
||||||
if (metadata.length === 0) {
|
|
||||||
// 没有SyncVar,无需初始化
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证所有SyncVar配置
|
|
||||||
const validationErrors: string[] = [];
|
|
||||||
for (const meta of metadata) {
|
|
||||||
const validation = validateSyncVarMetadata(component, meta);
|
|
||||||
if (!validation.isValid) {
|
|
||||||
validationErrors.push(...validation.errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validationErrors.length > 0) {
|
|
||||||
SyncVarManager.logger.error(`组件 ${component.constructor.name} 的SyncVar配置错误:`, validationErrors);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化变化记录
|
|
||||||
this._componentChanges.set(componentId, []);
|
|
||||||
this._lastSyncTimes.set(componentId, new Map());
|
|
||||||
|
|
||||||
SyncVarManager.logger.info(`初始化组件 ${component.constructor.name} 的SyncVar系统,共 ${metadata.length} 个同步变量`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理组件的SyncVar系统
|
|
||||||
*
|
|
||||||
* @param component - 网络组件实例
|
|
||||||
*/
|
|
||||||
public cleanupComponent<T extends INetworkSyncable>(component: T): void {
|
|
||||||
const componentId = this.getComponentId(component);
|
|
||||||
this._componentChanges.delete(componentId);
|
|
||||||
this._lastSyncTimes.delete(componentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录SyncVar字段的变化
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param propertyKey - 属性名
|
|
||||||
* @param oldValue - 旧值
|
|
||||||
* @param newValue - 新值
|
|
||||||
*/
|
|
||||||
public recordChange<T extends INetworkSyncable>(
|
|
||||||
component: T,
|
|
||||||
propertyKey: string,
|
|
||||||
oldValue: SyncVarValue,
|
|
||||||
newValue: SyncVarValue
|
|
||||||
): void {
|
|
||||||
const componentId = this.getComponentId(component);
|
|
||||||
const metadata = getSyncVarMetadataForProperty(component, propertyKey);
|
|
||||||
|
|
||||||
if (!metadata) {
|
|
||||||
SyncVarManager.logger.warn(`属性 ${propertyKey} 不是SyncVar`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查值是否真的发生了变化
|
|
||||||
if (!TypeGuards.isSyncVarValue(oldValue) || !TypeGuards.isSyncVarValue(newValue)) {
|
|
||||||
SyncVarManager.logger.warn(`无效的SyncVar值类型: ${typeof oldValue}, ${typeof newValue}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isValueEqual(oldValue, newValue)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查频率限制
|
|
||||||
const now = Date.now();
|
|
||||||
const lastSyncTimes = this._lastSyncTimes.get(componentId);
|
|
||||||
const lastSyncTime = lastSyncTimes?.get(propertyKey) || 0;
|
|
||||||
|
|
||||||
if (metadata.options.throttleMs && metadata.options.throttleMs > 0) {
|
|
||||||
if (now - lastSyncTime < metadata.options.throttleMs) {
|
|
||||||
SyncVarManager.logger.debug(`属性 ${propertyKey} 变化过于频繁,跳过同步`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查权限
|
|
||||||
if (metadata.options.authorityOnly && !this.hasAuthority(component)) {
|
|
||||||
SyncVarManager.logger.warn(`属性 ${propertyKey} 需要权限才能修改,但当前没有权限`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录变化
|
|
||||||
const change: SyncVarChange = {
|
|
||||||
propertyKey,
|
|
||||||
fieldNumber: metadata.fieldNumber,
|
|
||||||
oldValue,
|
|
||||||
newValue,
|
|
||||||
timestamp: now,
|
|
||||||
needsSync: this.shouldSync(component, metadata)
|
|
||||||
};
|
|
||||||
|
|
||||||
let changes = this._componentChanges.get(componentId);
|
|
||||||
if (!changes) {
|
|
||||||
changes = [];
|
|
||||||
this._componentChanges.set(componentId, changes);
|
|
||||||
}
|
|
||||||
|
|
||||||
changes.push(change);
|
|
||||||
|
|
||||||
// 更新最后同步时间
|
|
||||||
if (lastSyncTimes) {
|
|
||||||
lastSyncTimes.set(propertyKey, now);
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncVarManager.logger.debug(`记录变化: ${component.constructor.name}.${propertyKey} = ${newValue} (was ${oldValue})`);
|
|
||||||
|
|
||||||
// 触发hook回调
|
|
||||||
this.triggerHook(component, metadata, oldValue, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件的待同步变化
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @returns 待同步的变化数组
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除组件的变化记录
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param propertyKeys - 要清除的属性名数组,如果不提供则清除所有
|
|
||||||
*/
|
|
||||||
public clearChanges<T extends INetworkSyncable>(component: T, propertyKeys?: string[]): void {
|
|
||||||
const componentId = this.getComponentId(component);
|
|
||||||
const changes = this._componentChanges.get(componentId);
|
|
||||||
|
|
||||||
if (!changes) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyKeys) {
|
|
||||||
// 清除指定属性的变化
|
|
||||||
const filteredChanges = changes.filter(change => !propertyKeys.includes(change.propertyKey));
|
|
||||||
this._componentChanges.set(componentId, filteredChanges);
|
|
||||||
} else {
|
|
||||||
// 清除所有变化
|
|
||||||
this._componentChanges.set(componentId, []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建同步数据
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @returns 同步数据
|
|
||||||
*/
|
|
||||||
public createSyncData<T extends INetworkSyncable>(component: T): SyncVarSyncData | null {
|
|
||||||
const pendingChanges = this.getPendingChanges(component);
|
|
||||||
|
|
||||||
if (pendingChanges.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldUpdates: Array<{ fieldNumber: number; data: Uint8Array }> = [];
|
|
||||||
|
|
||||||
for (const change of pendingChanges) {
|
|
||||||
try {
|
|
||||||
const serializedData = this.serializeValue(component, change.propertyKey, change.newValue);
|
|
||||||
fieldUpdates.push({
|
|
||||||
fieldNumber: change.fieldNumber,
|
|
||||||
data: serializedData
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarManager.logger.error(`序列化失败 ${change.propertyKey}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldUpdates.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
componentType: component.constructor.name,
|
|
||||||
fieldUpdates,
|
|
||||||
timestamp: Date.now()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用同步数据
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param syncData - 同步数据
|
|
||||||
*/
|
|
||||||
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]));
|
|
||||||
|
|
||||||
for (const update of syncData.fieldUpdates) {
|
|
||||||
const meta = metadataMap.get(update.fieldNumber);
|
|
||||||
if (!meta) {
|
|
||||||
SyncVarManager.logger.warn(`未找到字段编号 ${update.fieldNumber} 的元数据`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const newValue = this.deserializeValue(component, meta.propertyKey, update.data);
|
|
||||||
const oldValue = component[meta.propertyKey];
|
|
||||||
|
|
||||||
// 直接设置值,不通过代理(避免循环触发)
|
|
||||||
this.setValueDirectly(component, meta.propertyKey, newValue);
|
|
||||||
|
|
||||||
// 触发hook回调
|
|
||||||
this.triggerHook(component, meta, oldValue, newValue);
|
|
||||||
|
|
||||||
SyncVarManager.logger.debug(`应用同步: ${component.constructor.name}.${meta.propertyKey} = ${newValue}`);
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarManager.logger.error(`反序列化失败 ${meta.propertyKey}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<T extends INetworkSyncable>(component: T): string {
|
|
||||||
if (!component._syncVarId) {
|
|
||||||
const idGenerator = SyncVarManager.getIdGenerator();
|
|
||||||
component._syncVarId = idGenerator.generateId(component);
|
|
||||||
}
|
|
||||||
return component._syncVarId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查两个值是否相等
|
|
||||||
*
|
|
||||||
* @param a - 值A
|
|
||||||
* @param b - 值B
|
|
||||||
* @returns 是否相等
|
|
||||||
*/
|
|
||||||
private isValueEqual(a: unknown, b: unknown): boolean {
|
|
||||||
// 基础类型比较
|
|
||||||
if (typeof a !== typeof b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a === b) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对象比较(浅比较)
|
|
||||||
if (typeof a === 'object' && a !== null && b !== null) {
|
|
||||||
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 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<T extends INetworkSyncable>(component: T, clientId?: string): boolean {
|
|
||||||
return this._authorityManager.hasAuthority(component as unknown as INetworkComponent, clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否应该同步
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param metadata - SyncVar元数据
|
|
||||||
* @returns 是否应该同步
|
|
||||||
*/
|
|
||||||
private shouldSync<T extends INetworkSyncable>(component: T, metadata: SyncVarMetadata): boolean {
|
|
||||||
// 权限检查:权威字段只有在有权限时才同步
|
|
||||||
if (metadata.options.authorityOnly && !this.hasAuthority(component)) {
|
|
||||||
SyncVarManager.logger.debug(`字段 ${metadata.propertyKey} 是权威字段,但当前没有权限,跳过同步`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 环境检查:服务端可以同步所有字段
|
|
||||||
if (NetworkEnvironment.isServer) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 客户端:非权威字段可以同步,权威字段需要检查权限
|
|
||||||
if (metadata.options.authorityOnly) {
|
|
||||||
return this.hasAuthority(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 普通字段客户端也可以同步
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 触发hook回调
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param metadata - SyncVar元数据
|
|
||||||
* @param oldValue - 旧值
|
|
||||||
* @param newValue - 新值
|
|
||||||
*/
|
|
||||||
private triggerHook<T extends INetworkSyncable>(component: T, metadata: SyncVarMetadata, oldValue: unknown, newValue: unknown): void {
|
|
||||||
if (!metadata.options.hook) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hookFunction = component[metadata.options.hook];
|
|
||||||
if (typeof hookFunction === 'function') {
|
|
||||||
try {
|
|
||||||
hookFunction.call(component, oldValue, newValue);
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarManager.logger.error(`Hook函数执行失败 ${metadata.options.hook}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化值
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param propertyKey - 属性名
|
|
||||||
* @param value - 值
|
|
||||||
* @returns 序列化数据
|
|
||||||
*/
|
|
||||||
private serializeValue<T extends INetworkSyncable>(component: T, propertyKey: string, value: unknown): Uint8Array {
|
|
||||||
const metadata = getSyncVarMetadataForProperty(component, propertyKey);
|
|
||||||
|
|
||||||
if (metadata?.options.serializer) {
|
|
||||||
return metadata.options.serializer(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.serializeValueToBinary(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 反序列化值
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param propertyKey - 属性名
|
|
||||||
* @param data - 序列化数据
|
|
||||||
* @returns 反序列化的值
|
|
||||||
*/
|
|
||||||
private deserializeValue<T extends INetworkSyncable>(component: T, propertyKey: string, data: Uint8Array): unknown {
|
|
||||||
const metadata = getSyncVarMetadataForProperty(component, propertyKey);
|
|
||||||
|
|
||||||
if (metadata?.options.deserializer) {
|
|
||||||
return metadata.options.deserializer(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.deserializeValueFromBinary(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将值序列化为二进制数据
|
|
||||||
*/
|
|
||||||
private serializeValueToBinary(value: unknown): Uint8Array {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return new Uint8Array([0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'boolean') {
|
|
||||||
return new Uint8Array([1, value ? 1 : 0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
const buffer = new ArrayBuffer(9);
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
view.setUint8(0, 2);
|
|
||||||
view.setFloat64(1, value, true);
|
|
||||||
return new Uint8Array(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const encoded = new TextEncoder().encode(value);
|
|
||||||
const buffer = new Uint8Array(5 + encoded.length);
|
|
||||||
const view = new DataView(buffer.buffer);
|
|
||||||
view.setUint8(0, 3);
|
|
||||||
view.setUint32(1, encoded.length, true);
|
|
||||||
buffer.set(encoded, 5);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonString = JSON.stringify(value);
|
|
||||||
const encoded = new TextEncoder().encode(jsonString);
|
|
||||||
const buffer = new Uint8Array(5 + encoded.length);
|
|
||||||
const view = new DataView(buffer.buffer);
|
|
||||||
view.setUint8(0, 4);
|
|
||||||
view.setUint32(1, encoded.length, true);
|
|
||||||
buffer.set(encoded, 5);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从二进制数据反序列化值
|
|
||||||
*/
|
|
||||||
private deserializeValueFromBinary(data: Uint8Array): unknown {
|
|
||||||
if (data.length === 0) return null;
|
|
||||||
|
|
||||||
const view = new DataView(data.buffer, data.byteOffset);
|
|
||||||
const type = view.getUint8(0);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 0: return null;
|
|
||||||
case 1: return view.getUint8(1) === 1;
|
|
||||||
case 2: return view.getFloat64(1, true);
|
|
||||||
case 3: {
|
|
||||||
const length = view.getUint32(1, true);
|
|
||||||
return new TextDecoder().decode(data.subarray(5, 5 + length));
|
|
||||||
}
|
|
||||||
case 4: {
|
|
||||||
const length = view.getUint32(1, true);
|
|
||||||
const jsonString = new TextDecoder().decode(data.subarray(5, 5 + length));
|
|
||||||
return JSON.parse(jsonString);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error(`未知的序列化类型: ${type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 直接设置值(绕过代理)
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param propertyKey - 属性名
|
|
||||||
* @param value - 值
|
|
||||||
*/
|
|
||||||
private setValueDirectly<T extends INetworkSyncable>(component: T, propertyKey: string, value: unknown): void {
|
|
||||||
// 临时禁用代理监听
|
|
||||||
const originalValue = component._syncVarDisabled;
|
|
||||||
component._syncVarDisabled = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
(component as Record<string, unknown>)[propertyKey] = value;
|
|
||||||
} finally {
|
|
||||||
component._syncVarDisabled = originalValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建SyncVar更新消息
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param networkId - 网络对象ID
|
|
||||||
* @param senderId - 发送者ID
|
|
||||||
* @param syncSequence - 同步序号
|
|
||||||
* @param isFullSync - 是否是完整同步
|
|
||||||
* @returns SyncVar更新消息,如果没有待同步的变化则返回null
|
|
||||||
*/
|
|
||||||
public createSyncVarUpdateMessage<T extends INetworkSyncable>(
|
|
||||||
component: T,
|
|
||||||
networkId: string = '',
|
|
||||||
senderId: string = '',
|
|
||||||
syncSequence: number = 0,
|
|
||||||
isFullSync: boolean = false
|
|
||||||
): SyncVarUpdateMessage | null {
|
|
||||||
const pendingChanges = this.getPendingChanges(component);
|
|
||||||
|
|
||||||
if (pendingChanges.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 转换变化记录为消息格式
|
|
||||||
const fieldUpdates: SyncVarFieldUpdate[] = [];
|
|
||||||
|
|
||||||
for (const change of pendingChanges) {
|
|
||||||
const metadata = getSyncVarMetadataForProperty(component, change.propertyKey);
|
|
||||||
if (!metadata) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldUpdate: SyncVarFieldUpdate = {
|
|
||||||
fieldNumber: change.fieldNumber,
|
|
||||||
propertyKey: change.propertyKey,
|
|
||||||
newValue: change.newValue as any,
|
|
||||||
oldValue: change.oldValue as any,
|
|
||||||
timestamp: change.timestamp,
|
|
||||||
authorityOnly: metadata.options.authorityOnly
|
|
||||||
};
|
|
||||||
|
|
||||||
fieldUpdates.push(fieldUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldUpdates.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = new SyncVarUpdateMessage(
|
|
||||||
networkId,
|
|
||||||
component.constructor.name,
|
|
||||||
fieldUpdates,
|
|
||||||
isFullSync,
|
|
||||||
senderId,
|
|
||||||
syncSequence
|
|
||||||
);
|
|
||||||
|
|
||||||
SyncVarManager.logger.debug(`创建SyncVar更新消息: ${component.constructor.name}, ${fieldUpdates.length} 个字段`);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用SyncVar更新消息
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @param message - SyncVar更新消息
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadata = getSyncVarMetadata(component.constructor);
|
|
||||||
const metadataMap = new Map(metadata.map(m => [m.fieldNumber, m]));
|
|
||||||
|
|
||||||
for (const fieldUpdate of message.fieldUpdates) {
|
|
||||||
const meta = metadataMap.get(fieldUpdate.fieldNumber);
|
|
||||||
if (!meta) {
|
|
||||||
SyncVarManager.logger.warn(`未找到字段编号 ${fieldUpdate.fieldNumber} 的元数据`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 权限检查:权威字段在客户端通常应该接受来自服务端的更新
|
|
||||||
// 只有当客户端试图应用自己产生的权威字段更新时才拒绝
|
|
||||||
if (fieldUpdate.authorityOnly && NetworkEnvironment.isClient && !this.hasAuthority(component)) {
|
|
||||||
// 如果这是来自服务端的更新,则允许应用
|
|
||||||
// 这里简单实现:客户端接受所有权威字段的更新
|
|
||||||
SyncVarManager.logger.debug(`客户端接受权威字段更新: ${fieldUpdate.propertyKey}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const oldValue = component[meta.propertyKey];
|
|
||||||
|
|
||||||
// 直接设置值,不通过代理(避免循环触发)
|
|
||||||
this.setValueDirectly(component, meta.propertyKey, fieldUpdate.newValue);
|
|
||||||
|
|
||||||
// 触发hook回调
|
|
||||||
this.triggerHook(component, meta, oldValue, fieldUpdate.newValue);
|
|
||||||
|
|
||||||
SyncVarManager.logger.debug(`应用SyncVar消息更新: ${component.constructor.name}.${meta.propertyKey} = ${fieldUpdate.newValue}`);
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarManager.logger.error(`应用SyncVar更新失败 ${meta.propertyKey}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除对应的变化记录(已经同步完成)
|
|
||||||
this.clearChanges(component, message.fieldUpdates.map(u => u.propertyKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量创建多个组件的SyncVar更新消息
|
|
||||||
*
|
|
||||||
* @param components - 组件实例数组
|
|
||||||
* @param networkIds - 对应的网络对象ID数组
|
|
||||||
* @param senderId - 发送者ID
|
|
||||||
* @param syncSequence - 同步序号
|
|
||||||
* @returns SyncVar更新消息数组
|
|
||||||
*/
|
|
||||||
public createBatchSyncVarUpdateMessages<T extends INetworkSyncable>(
|
|
||||||
components: T[],
|
|
||||||
networkIds: string[] = [],
|
|
||||||
senderId: string = '',
|
|
||||||
syncSequence: number = 0
|
|
||||||
): SyncVarUpdateMessage[] {
|
|
||||||
const messages: SyncVarUpdateMessage[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < components.length; i++) {
|
|
||||||
const component = components[i];
|
|
||||||
const networkId = networkIds[i] || '';
|
|
||||||
|
|
||||||
const message = this.createSyncVarUpdateMessage(
|
|
||||||
component,
|
|
||||||
networkId,
|
|
||||||
senderId,
|
|
||||||
syncSequence + i
|
|
||||||
);
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
messages.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 过滤需要同步的组件
|
|
||||||
*
|
|
||||||
* @param components - 组件数组
|
|
||||||
* @returns 有待同步变化的组件数组
|
|
||||||
*/
|
|
||||||
public filterComponentsWithChanges<T extends INetworkSyncable>(components: T[]): T[] {
|
|
||||||
return components.filter(component => {
|
|
||||||
const pendingChanges = this.getPendingChanges(component);
|
|
||||||
return pendingChanges.length > 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件的变化统计
|
|
||||||
*
|
|
||||||
* @param component - 组件实例
|
|
||||||
* @returns 变化统计信息
|
|
||||||
*/
|
|
||||||
public getComponentChangeStats<T extends INetworkSyncable>(component: T): {
|
|
||||||
totalChanges: number;
|
|
||||||
pendingChanges: number;
|
|
||||||
lastChangeTime: number;
|
|
||||||
fieldChangeCounts: Map<string, number>;
|
|
||||||
hasAuthorityOnlyChanges: boolean;
|
|
||||||
} {
|
|
||||||
const componentId = this.getComponentId(component);
|
|
||||||
const changes = this._componentChanges.get(componentId) || [];
|
|
||||||
const pendingChanges = changes.filter(c => c.needsSync);
|
|
||||||
|
|
||||||
const fieldChangeCounts = new Map<string, number>();
|
|
||||||
let lastChangeTime = 0;
|
|
||||||
let hasAuthorityOnlyChanges = false;
|
|
||||||
|
|
||||||
for (const change of changes) {
|
|
||||||
const count = fieldChangeCounts.get(change.propertyKey) || 0;
|
|
||||||
fieldChangeCounts.set(change.propertyKey, count + 1);
|
|
||||||
lastChangeTime = Math.max(lastChangeTime, change.timestamp);
|
|
||||||
|
|
||||||
if (!hasAuthorityOnlyChanges) {
|
|
||||||
const metadata = getSyncVarMetadataForProperty(component, change.propertyKey);
|
|
||||||
if (metadata?.options.authorityOnly) {
|
|
||||||
hasAuthorityOnlyChanges = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalChanges: changes.length,
|
|
||||||
pendingChanges: pendingChanges.length,
|
|
||||||
lastChangeTime,
|
|
||||||
fieldChangeCounts,
|
|
||||||
hasAuthorityOnlyChanges
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取管理器统计信息
|
|
||||||
*
|
|
||||||
* @returns 统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
totalComponents: number;
|
|
||||||
totalChanges: number;
|
|
||||||
pendingChanges: number;
|
|
||||||
components: Array<{
|
|
||||||
id: string;
|
|
||||||
changes: number;
|
|
||||||
pending: number;
|
|
||||||
}>;
|
|
||||||
} {
|
|
||||||
let totalChanges = 0;
|
|
||||||
let pendingChanges = 0;
|
|
||||||
const components: Array<{ id: string; changes: number; pending: number }> = [];
|
|
||||||
|
|
||||||
for (const [componentId, changes] of this._componentChanges) {
|
|
||||||
const pendingCount = changes.filter(c => c.needsSync).length;
|
|
||||||
totalChanges += changes.length;
|
|
||||||
pendingChanges += pendingCount;
|
|
||||||
|
|
||||||
components.push({
|
|
||||||
id: componentId,
|
|
||||||
changes: changes.length,
|
|
||||||
pending: pendingCount
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalComponents: this._componentChanges.size,
|
|
||||||
totalChanges,
|
|
||||||
pendingChanges,
|
|
||||||
components
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
import { IMessageHandler } from '../Messaging/MessageHandler';
|
|
||||||
import { SyncVarUpdateMessage } from '../Messaging/MessageTypes';
|
|
||||||
import { NetworkConnection } from '../Core/NetworkConnection';
|
|
||||||
import { NetworkIdentityRegistry } from '../Core/NetworkIdentity';
|
|
||||||
import { SyncVarManager } from './SyncVarManager';
|
|
||||||
import { NetworkEnvironment } from '../Core/NetworkEnvironment';
|
|
||||||
import { ComponentRegistry, createLogger } from '@esengine/ecs-framework';
|
|
||||||
import { NetworkManager } from '../Core/NetworkManager';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar更新消息处理器
|
|
||||||
*
|
|
||||||
* 处理接收到的SyncVar更新消息,自动查找目标网络对象并应用更新
|
|
||||||
*/
|
|
||||||
export class SyncVarMessageHandler implements IMessageHandler<SyncVarUpdateMessage> {
|
|
||||||
private static readonly logger = createLogger('SyncVarMessageHandler');
|
|
||||||
private _processedMessages: Set<string> = new Set();
|
|
||||||
private _maxProcessedCache: number = 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理SyncVar更新消息
|
|
||||||
*
|
|
||||||
* @param message - SyncVar更新消息
|
|
||||||
* @param connection - 发送消息的连接(服务端有效)
|
|
||||||
*/
|
|
||||||
public async handle(message: SyncVarUpdateMessage, connection?: NetworkConnection): Promise<void> {
|
|
||||||
try {
|
|
||||||
// 生成消息唯一标识符用于去重
|
|
||||||
const messageKey = this.generateMessageKey(message);
|
|
||||||
if (this._processedMessages.has(messageKey)) {
|
|
||||||
SyncVarMessageHandler.logger.debug(` 跳过重复消息: ${messageKey}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加到已处理缓存
|
|
||||||
this.addToProcessedCache(messageKey);
|
|
||||||
|
|
||||||
// 验证消息基本有效性
|
|
||||||
if (!this.validateMessage(message)) {
|
|
||||||
SyncVarMessageHandler.logger.error(' 消息验证失败');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找目标网络对象
|
|
||||||
const targetIdentity = NetworkIdentityRegistry.Instance.find(message.networkId);
|
|
||||||
if (!targetIdentity) {
|
|
||||||
SyncVarMessageHandler.logger.warn(` 未找到网络对象: ${message.networkId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 权限检查
|
|
||||||
if (!this.checkAuthority(message, connection, targetIdentity)) {
|
|
||||||
SyncVarMessageHandler.logger.warn(` 权限检查失败: ${message.networkId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找目标组件
|
|
||||||
const targetComponent = this.findTargetComponent(targetIdentity, message.componentType);
|
|
||||||
if (!targetComponent) {
|
|
||||||
SyncVarMessageHandler.logger.warn(` 未找到目标组件: ${message.componentType} on ${message.networkId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 应用SyncVar更新
|
|
||||||
this.applySyncVarUpdates(targetComponent, message);
|
|
||||||
|
|
||||||
// 更新网络对象的同步信息
|
|
||||||
targetIdentity.updateSyncTime();
|
|
||||||
if (message.syncSequence > targetIdentity.syncSequence) {
|
|
||||||
targetIdentity.syncSequence = message.syncSequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是服务端接收的消息,需要转发给其他客户端
|
|
||||||
if (NetworkEnvironment.isServer && connection) {
|
|
||||||
await this.forwardToOtherClients(message, connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncVarMessageHandler.logger.debug(` 成功处理SyncVar更新: ${message.networkId}.${message.componentType}, ${message.fieldUpdates.length}个字段`);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarMessageHandler.logger.error(' 处理SyncVar更新失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成消息唯一标识符
|
|
||||||
*/
|
|
||||||
private generateMessageKey(message: SyncVarUpdateMessage): string {
|
|
||||||
return `${message.networkId}_${message.componentType}_${message.syncSequence}_${message.timestamp}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加到已处理消息缓存
|
|
||||||
*/
|
|
||||||
private addToProcessedCache(messageKey: string): void {
|
|
||||||
this._processedMessages.add(messageKey);
|
|
||||||
|
|
||||||
// 限制缓存大小
|
|
||||||
if (this._processedMessages.size > this._maxProcessedCache) {
|
|
||||||
const toDelete = Array.from(this._processedMessages).slice(0, this._maxProcessedCache / 2);
|
|
||||||
toDelete.forEach(key => this._processedMessages.delete(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证消息基本有效性
|
|
||||||
*/
|
|
||||||
private validateMessage(message: SyncVarUpdateMessage): boolean {
|
|
||||||
if (!message.networkId || !message.componentType) {
|
|
||||||
SyncVarMessageHandler.logger.error(' 消息缺少必要字段');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!message.fieldUpdates || message.fieldUpdates.length === 0) {
|
|
||||||
SyncVarMessageHandler.logger.error(' 消息没有字段更新');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查时间戳合理性(不能是未来的时间,不能太久以前)
|
|
||||||
const now = Date.now();
|
|
||||||
const maxAge = 60000; // 1分钟
|
|
||||||
if (message.timestamp > now + 5000 || message.timestamp < now - maxAge) {
|
|
||||||
SyncVarMessageHandler.logger.warn(` 消息时间戳异常: ${message.timestamp}, 当前: ${now}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查操作权限
|
|
||||||
*/
|
|
||||||
private checkAuthority(
|
|
||||||
message: SyncVarUpdateMessage,
|
|
||||||
connection: NetworkConnection | undefined,
|
|
||||||
targetIdentity: any
|
|
||||||
): boolean {
|
|
||||||
// 服务端始终有权限处理消息
|
|
||||||
if (NetworkEnvironment.isServer) {
|
|
||||||
// 但需要检查客户端发送的消息是否有权限修改对象
|
|
||||||
if (connection) {
|
|
||||||
// 检查是否是对象拥有者
|
|
||||||
if (targetIdentity.ownerId && targetIdentity.ownerId !== connection.connectionId) {
|
|
||||||
// 非拥有者只能发送非权威字段更新
|
|
||||||
const hasAuthorityOnlyUpdates = message.fieldUpdates.some(update => update.authorityOnly);
|
|
||||||
if (hasAuthorityOnlyUpdates) {
|
|
||||||
SyncVarMessageHandler.logger.warn(` 非拥有者 ${connection.connectionId} 尝试修改权威字段`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 客户端接收到的消息通常来自服务端,应该允许
|
|
||||||
if (NetworkEnvironment.isClient) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查找目标组件
|
|
||||||
*/
|
|
||||||
private findTargetComponent(targetIdentity: any, componentType: string): any {
|
|
||||||
const entity = targetIdentity.entity;
|
|
||||||
if (!entity || typeof entity.getComponent !== 'function') {
|
|
||||||
SyncVarMessageHandler.logger.error(' NetworkIdentity缺少有效的Entity引用');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取组件类
|
|
||||||
const ComponentClass = this.getComponentClassByName(componentType);
|
|
||||||
if (!ComponentClass) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用Entity的getComponent方法查找组件
|
|
||||||
const component = entity.getComponent(ComponentClass);
|
|
||||||
if (!component) {
|
|
||||||
SyncVarMessageHandler.logger.warn(` Entity ${entity.id} 上未找到组件: ${componentType}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return component;
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarMessageHandler.logger.error(`查找组件失败: ${componentType}`, error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据组件名称获取组件类
|
|
||||||
*/
|
|
||||||
private getComponentClassByName(componentType: string): any {
|
|
||||||
const componentClass = ComponentRegistry.getComponentType(componentType);
|
|
||||||
|
|
||||||
if (!componentClass) {
|
|
||||||
SyncVarMessageHandler.logger.warn(` 未找到组件类型: ${componentType}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return componentClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用SyncVar更新到组件
|
|
||||||
*/
|
|
||||||
private applySyncVarUpdates(targetComponent: any, message: SyncVarUpdateMessage): void {
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
|
|
||||||
try {
|
|
||||||
syncVarManager.applySyncVarUpdateMessage(targetComponent, message);
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarMessageHandler.logger.error(' 应用SyncVar更新失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转发消息给其他客户端(服务端专用)
|
|
||||||
*/
|
|
||||||
private async forwardToOtherClients(
|
|
||||||
message: SyncVarUpdateMessage,
|
|
||||||
senderConnection: NetworkConnection
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
// 获取NetworkServer实例
|
|
||||||
const server = NetworkManager.GetServer();
|
|
||||||
|
|
||||||
if (!server || !server.isRunning) {
|
|
||||||
SyncVarMessageHandler.logger.warn(' NetworkServer未运行,无法转发消息');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用NetworkServer的broadcastSyncVarMessageExcept方法排除发送者
|
|
||||||
const successCount = await server.broadcastSyncVarMessageExcept(message, senderConnection.connectionId);
|
|
||||||
|
|
||||||
if (successCount > 0) {
|
|
||||||
SyncVarMessageHandler.logger.debug(` 成功转发消息给 ${successCount} 个其他客户端 (发送者: ${senderConnection.connectionId})`);
|
|
||||||
} else {
|
|
||||||
SyncVarMessageHandler.logger.debug(` 没有其他客户端需要转发消息 (发送者: ${senderConnection.connectionId})`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarMessageHandler.logger.error(`转发消息失败 (发送者: ${senderConnection.connectionId}):`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取处理器统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): {
|
|
||||||
processedMessages: number;
|
|
||||||
cacheSize: number;
|
|
||||||
maxCacheSize: number;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
processedMessages: this._processedMessages.size,
|
|
||||||
cacheSize: this._processedMessages.size,
|
|
||||||
maxCacheSize: this._maxProcessedCache
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理已处理消息缓存
|
|
||||||
*/
|
|
||||||
public clearProcessedCache(): void {
|
|
||||||
this._processedMessages.clear();
|
|
||||||
SyncVarMessageHandler.logger.info('已清理消息处理缓存');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置最大缓存大小
|
|
||||||
*/
|
|
||||||
public setMaxCacheSize(maxSize: number): void {
|
|
||||||
this._maxProcessedCache = Math.max(100, maxSize);
|
|
||||||
|
|
||||||
// 如果当前缓存超过新的最大值,进行清理
|
|
||||||
if (this._processedMessages.size > this._maxProcessedCache) {
|
|
||||||
const toDelete = Array.from(this._processedMessages).slice(0, this._processedMessages.size - this._maxProcessedCache);
|
|
||||||
toDelete.forEach(key => this._processedMessages.delete(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,478 +0,0 @@
|
|||||||
import { SyncVarUpdateMessage, SyncVarFieldUpdate } from '../Messaging/MessageTypes';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar优化配置
|
|
||||||
*/
|
|
||||||
export interface SyncVarOptimizationConfig {
|
|
||||||
/** 是否启用消息合并 */
|
|
||||||
enableMessageMerging: boolean;
|
|
||||||
/** 最大合并时间窗口(毫秒) */
|
|
||||||
mergeTimeWindow: number;
|
|
||||||
/** 是否启用Delta压缩 */
|
|
||||||
enableDeltaCompression: boolean;
|
|
||||||
/** 是否启用批量发送 */
|
|
||||||
enableBatchSending: boolean;
|
|
||||||
/** 批量大小限制 */
|
|
||||||
batchSizeLimit: number;
|
|
||||||
/** 是否启用优先级队列 */
|
|
||||||
enablePriorityQueue: boolean;
|
|
||||||
/** 是否启用距离剔除 */
|
|
||||||
enableDistanceCulling: boolean;
|
|
||||||
/** 距离剔除半径 */
|
|
||||||
cullingDistance: number;
|
|
||||||
/** 是否启用频率限制 */
|
|
||||||
enableRateLimit: boolean;
|
|
||||||
/** 每秒最大消息数 */
|
|
||||||
maxMessagesPerSecond: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息合并器
|
|
||||||
*/
|
|
||||||
export class SyncVarMessageMerger {
|
|
||||||
private _pendingMessages: Map<string, SyncVarUpdateMessage[]> = new Map();
|
|
||||||
private _mergeTimers: Map<string, NodeJS.Timeout> = new Map();
|
|
||||||
private _config: SyncVarOptimizationConfig;
|
|
||||||
|
|
||||||
constructor(config: SyncVarOptimizationConfig) {
|
|
||||||
this._config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加消息到合并队列
|
|
||||||
*
|
|
||||||
* @param message - SyncVar更新消息
|
|
||||||
* @param onMerged - 合并完成回调
|
|
||||||
*/
|
|
||||||
public addMessage(
|
|
||||||
message: SyncVarUpdateMessage,
|
|
||||||
onMerged: (mergedMessage: SyncVarUpdateMessage) => void
|
|
||||||
): void {
|
|
||||||
if (!this._config.enableMessageMerging) {
|
|
||||||
onMerged(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = `${message.networkId}_${message.componentType}`;
|
|
||||||
|
|
||||||
// 获取或创建消息列表
|
|
||||||
let messages = this._pendingMessages.get(key);
|
|
||||||
if (!messages) {
|
|
||||||
messages = [];
|
|
||||||
this._pendingMessages.set(key, messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加消息到列表
|
|
||||||
messages.push(message);
|
|
||||||
|
|
||||||
// 清除现有计时器
|
|
||||||
const existingTimer = this._mergeTimers.get(key);
|
|
||||||
if (existingTimer) {
|
|
||||||
clearTimeout(existingTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置新的合并计时器
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
this.mergeAndSend(key, onMerged);
|
|
||||||
}, this._config.mergeTimeWindow);
|
|
||||||
|
|
||||||
this._mergeTimers.set(key, timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并并发送消息
|
|
||||||
*/
|
|
||||||
private mergeAndSend(
|
|
||||||
key: string,
|
|
||||||
onMerged: (mergedMessage: SyncVarUpdateMessage) => void
|
|
||||||
): void {
|
|
||||||
const messages = this._pendingMessages.get(key);
|
|
||||||
if (!messages || messages.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理
|
|
||||||
this._pendingMessages.delete(key);
|
|
||||||
this._mergeTimers.delete(key);
|
|
||||||
|
|
||||||
if (messages.length === 1) {
|
|
||||||
// 只有一个消息,直接发送
|
|
||||||
onMerged(messages[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并多个消息
|
|
||||||
const mergedMessage = this.mergeMessages(messages);
|
|
||||||
onMerged(mergedMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并多个消息为单个消息
|
|
||||||
*/
|
|
||||||
private mergeMessages(messages: SyncVarUpdateMessage[]): SyncVarUpdateMessage {
|
|
||||||
if (messages.length === 1) {
|
|
||||||
return messages[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstMessage = messages[0];
|
|
||||||
const fieldUpdateMap = new Map<number, SyncVarFieldUpdate>();
|
|
||||||
|
|
||||||
// 合并字段更新(后面的覆盖前面的)
|
|
||||||
for (const message of messages) {
|
|
||||||
for (const fieldUpdate of message.fieldUpdates) {
|
|
||||||
fieldUpdateMap.set(fieldUpdate.fieldNumber, fieldUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建合并后的消息
|
|
||||||
const mergedFieldUpdates = Array.from(fieldUpdateMap.values());
|
|
||||||
const lastMessage = messages[messages.length - 1];
|
|
||||||
|
|
||||||
return new SyncVarUpdateMessage(
|
|
||||||
firstMessage.networkId,
|
|
||||||
firstMessage.componentType,
|
|
||||||
mergedFieldUpdates,
|
|
||||||
false, // 合并的消息总是增量同步
|
|
||||||
firstMessage.senderId,
|
|
||||||
lastMessage.syncSequence // 使用最新的序列号
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制刷新所有待合并的消息
|
|
||||||
*/
|
|
||||||
public flush(onMerged: (mergedMessage: SyncVarUpdateMessage) => void): void {
|
|
||||||
for (const key of this._pendingMessages.keys()) {
|
|
||||||
this.mergeAndSend(key, onMerged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理所有待合并的消息
|
|
||||||
*/
|
|
||||||
public clear(): void {
|
|
||||||
// 清除所有计时器
|
|
||||||
for (const timer of this._mergeTimers.values()) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._pendingMessages.clear();
|
|
||||||
this._mergeTimers.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 频率限制器
|
|
||||||
*/
|
|
||||||
export class SyncVarRateLimiter {
|
|
||||||
private _messageCount: Map<string, number> = new Map();
|
|
||||||
private _resetTimers: Map<string, NodeJS.Timeout> = new Map();
|
|
||||||
private _config: SyncVarOptimizationConfig;
|
|
||||||
|
|
||||||
constructor(config: SyncVarOptimizationConfig) {
|
|
||||||
this._config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否允许发送消息
|
|
||||||
*
|
|
||||||
* @param networkId - 网络对象ID
|
|
||||||
* @returns 是否允许发送
|
|
||||||
*/
|
|
||||||
public canSend(networkId: string): boolean {
|
|
||||||
if (!this._config.enableRateLimit) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentCount = this._messageCount.get(networkId) || 0;
|
|
||||||
|
|
||||||
if (currentCount >= this._config.maxMessagesPerSecond) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 增加计数
|
|
||||||
this._messageCount.set(networkId, currentCount + 1);
|
|
||||||
|
|
||||||
// 如果这是第一个消息,设置重置计时器
|
|
||||||
if (currentCount === 0) {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
this._messageCount.delete(networkId);
|
|
||||||
this._resetTimers.delete(networkId);
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
this._resetTimers.set(networkId, timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置指定对象的频率限制
|
|
||||||
*/
|
|
||||||
public reset(networkId: string): void {
|
|
||||||
this._messageCount.delete(networkId);
|
|
||||||
|
|
||||||
const timer = this._resetTimers.get(networkId);
|
|
||||||
if (timer) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
this._resetTimers.delete(networkId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理所有频率限制
|
|
||||||
*/
|
|
||||||
public clear(): void {
|
|
||||||
for (const timer of this._resetTimers.values()) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._messageCount.clear();
|
|
||||||
this._resetTimers.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 距离剔除器
|
|
||||||
*/
|
|
||||||
export class SyncVarDistanceCuller {
|
|
||||||
private _config: SyncVarOptimizationConfig;
|
|
||||||
private _positionCache: Map<string, { x: number; y: number; z?: number }> = new Map();
|
|
||||||
|
|
||||||
constructor(config: SyncVarOptimizationConfig) {
|
|
||||||
this._config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新对象位置
|
|
||||||
*
|
|
||||||
* @param networkId - 网络对象ID
|
|
||||||
* @param position - 位置坐标
|
|
||||||
*/
|
|
||||||
public updatePosition(networkId: string, position: { x: number; y: number; z?: number }): void {
|
|
||||||
this._positionCache.set(networkId, { ...position });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否应该向指定观察者发送消息
|
|
||||||
*
|
|
||||||
* @param targetId - 目标对象ID
|
|
||||||
* @param observerId - 观察者ID
|
|
||||||
* @returns 是否应该发送
|
|
||||||
*/
|
|
||||||
public shouldSendTo(targetId: string, observerId: string): boolean {
|
|
||||||
if (!this._config.enableDistanceCulling) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetPos = this._positionCache.get(targetId);
|
|
||||||
const observerPos = this._positionCache.get(observerId);
|
|
||||||
|
|
||||||
if (!targetPos || !observerPos) {
|
|
||||||
// 位置信息不完整,默认发送
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const distance = this.calculateDistance(targetPos, observerPos);
|
|
||||||
return distance <= this._config.cullingDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取在指定范围内的观察者列表
|
|
||||||
*
|
|
||||||
* @param targetId - 目标对象ID
|
|
||||||
* @param observerIds - 观察者ID列表
|
|
||||||
* @returns 在范围内的观察者ID列表
|
|
||||||
*/
|
|
||||||
public getObserversInRange(targetId: string, observerIds: string[]): string[] {
|
|
||||||
if (!this._config.enableDistanceCulling) {
|
|
||||||
return observerIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
return observerIds.filter(observerId => this.shouldSendTo(targetId, observerId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算两点之间的距离
|
|
||||||
*/
|
|
||||||
private calculateDistance(
|
|
||||||
pos1: { x: number; y: number; z?: number },
|
|
||||||
pos2: { x: number; y: number; z?: number }
|
|
||||||
): number {
|
|
||||||
const dx = pos1.x - pos2.x;
|
|
||||||
const dy = pos1.y - pos2.y;
|
|
||||||
const dz = (pos1.z || 0) - (pos2.z || 0);
|
|
||||||
|
|
||||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理位置缓存
|
|
||||||
*/
|
|
||||||
public clear(): void {
|
|
||||||
this._positionCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除指定对象的位置信息
|
|
||||||
*/
|
|
||||||
public remove(networkId: string): void {
|
|
||||||
this._positionCache.delete(networkId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar性能优化器
|
|
||||||
*/
|
|
||||||
export class SyncVarOptimizer {
|
|
||||||
private static readonly logger = createLogger('SyncVarOptimizer');
|
|
||||||
private _config: SyncVarOptimizationConfig;
|
|
||||||
private _messageMerger: SyncVarMessageMerger;
|
|
||||||
private _rateLimiter: SyncVarRateLimiter;
|
|
||||||
private _distanceCuller: SyncVarDistanceCuller;
|
|
||||||
|
|
||||||
// 统计信息
|
|
||||||
private _stats = {
|
|
||||||
messagesProcessed: 0,
|
|
||||||
messagesBlocked: 0,
|
|
||||||
messagesMerged: 0,
|
|
||||||
bytesSaved: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(config?: Partial<SyncVarOptimizationConfig>) {
|
|
||||||
// 默认配置
|
|
||||||
this._config = {
|
|
||||||
enableMessageMerging: true,
|
|
||||||
mergeTimeWindow: 16, // 1帧时间
|
|
||||||
enableDeltaCompression: true,
|
|
||||||
enableBatchSending: true,
|
|
||||||
batchSizeLimit: 10,
|
|
||||||
enablePriorityQueue: true,
|
|
||||||
enableDistanceCulling: false,
|
|
||||||
cullingDistance: 100,
|
|
||||||
enableRateLimit: true,
|
|
||||||
maxMessagesPerSecond: 60,
|
|
||||||
...config
|
|
||||||
};
|
|
||||||
|
|
||||||
this._messageMerger = new SyncVarMessageMerger(this._config);
|
|
||||||
this._rateLimiter = new SyncVarRateLimiter(this._config);
|
|
||||||
this._distanceCuller = new SyncVarDistanceCuller(this._config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理SyncVar消息优化
|
|
||||||
*
|
|
||||||
* @param message - 原始消息
|
|
||||||
* @param targetObservers - 目标观察者列表
|
|
||||||
* @param onOptimized - 优化完成回调
|
|
||||||
*/
|
|
||||||
public optimizeMessage(
|
|
||||||
message: SyncVarUpdateMessage,
|
|
||||||
targetObservers: string[] = [],
|
|
||||||
onOptimized: (optimizedMessages: SyncVarUpdateMessage[], observers: string[]) => void
|
|
||||||
): void {
|
|
||||||
this._stats.messagesProcessed++;
|
|
||||||
|
|
||||||
// 频率限制检查
|
|
||||||
if (!this._rateLimiter.canSend(message.networkId)) {
|
|
||||||
this._stats.messagesBlocked++;
|
|
||||||
SyncVarOptimizer.logger.debug(` 消息被频率限制阻止: ${message.networkId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 距离剔除
|
|
||||||
const validObservers = this._distanceCuller.getObserversInRange(message.networkId, targetObservers);
|
|
||||||
|
|
||||||
if (validObservers.length === 0 && targetObservers.length > 0) {
|
|
||||||
this._stats.messagesBlocked++;
|
|
||||||
SyncVarOptimizer.logger.debug(` 消息被距离剔除阻止: ${message.networkId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消息合并
|
|
||||||
this._messageMerger.addMessage(message, (mergedMessage) => {
|
|
||||||
if (mergedMessage !== message) {
|
|
||||||
this._stats.messagesMerged++;
|
|
||||||
SyncVarOptimizer.logger.debug(` 消息已合并: ${message.networkId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
onOptimized([mergedMessage], validObservers);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新对象位置(用于距离剔除)
|
|
||||||
*/
|
|
||||||
public updateObjectPosition(networkId: string, position: { x: number; y: number; z?: number }): void {
|
|
||||||
this._distanceCuller.updatePosition(networkId, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置优化器
|
|
||||||
*/
|
|
||||||
public configure(config: Partial<SyncVarOptimizationConfig>): void {
|
|
||||||
this._config = { ...this._config, ...config };
|
|
||||||
SyncVarOptimizer.logger.info(' 配置已更新:', this._config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制刷新所有待处理的消息
|
|
||||||
*/
|
|
||||||
public flush(onOptimized?: (optimizedMessages: SyncVarUpdateMessage[], observers: string[]) => void): void {
|
|
||||||
this._messageMerger.flush((mergedMessage) => {
|
|
||||||
if (onOptimized) {
|
|
||||||
onOptimized([mergedMessage], []);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置指定对象的优化状态
|
|
||||||
*/
|
|
||||||
public resetObject(networkId: string): void {
|
|
||||||
this._rateLimiter.reset(networkId);
|
|
||||||
this._distanceCuller.remove(networkId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取优化统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): typeof SyncVarOptimizer.prototype._stats & {
|
|
||||||
config: SyncVarOptimizationConfig;
|
|
||||||
optimizationRatio: number;
|
|
||||||
} {
|
|
||||||
const optimizationRatio = this._stats.messagesProcessed > 0
|
|
||||||
? (this._stats.messagesBlocked + this._stats.messagesMerged) / this._stats.messagesProcessed
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...this._stats,
|
|
||||||
config: { ...this._config },
|
|
||||||
optimizationRatio
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置统计信息
|
|
||||||
*/
|
|
||||||
public resetStats(): void {
|
|
||||||
this._stats = {
|
|
||||||
messagesProcessed: 0,
|
|
||||||
messagesBlocked: 0,
|
|
||||||
messagesMerged: 0,
|
|
||||||
bytesSaved: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理优化器
|
|
||||||
*/
|
|
||||||
public cleanup(): void {
|
|
||||||
this._messageMerger.clear();
|
|
||||||
this._rateLimiter.clear();
|
|
||||||
this._distanceCuller.clear();
|
|
||||||
this.resetStats();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
import { getSyncVarMetadata, isSyncVar } from './SyncVarDecorator';
|
|
||||||
import { SyncVarManager } from './SyncVarManager';
|
|
||||||
import { INetworkSyncable, SyncVarValue, TypeGuards } from '../types/NetworkTypes';
|
|
||||||
import { createLogger } from '@esengine/ecs-framework';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar代理配置
|
|
||||||
*/
|
|
||||||
export interface SyncVarProxyOptions {
|
|
||||||
/**
|
|
||||||
* 是否启用调试日志
|
|
||||||
*/
|
|
||||||
debugLog?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义属性过滤器
|
|
||||||
*/
|
|
||||||
propertyFilter?: (propertyKey: string) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建SyncVar代理
|
|
||||||
*
|
|
||||||
* 为组件实例创建Proxy,拦截SyncVar字段的读写操作,
|
|
||||||
* 当字段值发生变化时自动触发同步逻辑
|
|
||||||
*
|
|
||||||
* @param target - 目标组件实例
|
|
||||||
* @param options - 代理配置选项
|
|
||||||
* @returns 代理包装的组件实例
|
|
||||||
*/
|
|
||||||
export function createSyncVarProxy<T extends INetworkSyncable>(
|
|
||||||
target: T,
|
|
||||||
options: SyncVarProxyOptions = {}
|
|
||||||
): T {
|
|
||||||
const { debugLog = false, propertyFilter } = options;
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
|
|
||||||
// 检查目标是否有SyncVar
|
|
||||||
const metadata = getSyncVarMetadata(target.constructor);
|
|
||||||
const logger = createLogger('SyncVarProxy');
|
|
||||||
|
|
||||||
if (metadata.length === 0) {
|
|
||||||
if (debugLog) {
|
|
||||||
logger.debug(`对象 ${target.constructor.name} 没有SyncVar,返回原对象`);
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化SyncVar管理器
|
|
||||||
syncVarManager.initializeComponent(target);
|
|
||||||
|
|
||||||
if (debugLog) {
|
|
||||||
logger.debug(`为 ${target.constructor.name} 创建代理,SyncVar字段:`,
|
|
||||||
metadata.map(m => m.propertyKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存储原始值的副本,用于比较变化
|
|
||||||
const originalValues = new Map<string, unknown>();
|
|
||||||
|
|
||||||
// 初始化原始值
|
|
||||||
for (const meta of metadata) {
|
|
||||||
if (meta.propertyKey in target) {
|
|
||||||
originalValues.set(meta.propertyKey, (target as Record<string, unknown>)[meta.propertyKey]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxy = new Proxy(target, {
|
|
||||||
/**
|
|
||||||
* 拦截属性读取
|
|
||||||
*/
|
|
||||||
get(obj: T, prop: string | symbol): unknown {
|
|
||||||
// 内部属性和方法直接返回
|
|
||||||
if (typeof prop === 'symbol' || prop.startsWith('_') || prop.startsWith('$')) {
|
|
||||||
return Reflect.get(obj, prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
const propertyKey = prop as string;
|
|
||||||
|
|
||||||
// 如果有自定义过滤器且不通过,直接返回
|
|
||||||
if (propertyFilter && !propertyFilter(propertyKey)) {
|
|
||||||
return Reflect.get(obj, prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = Reflect.get(obj, prop);
|
|
||||||
|
|
||||||
if (debugLog && isSyncVar(obj, propertyKey)) {
|
|
||||||
logger.debug(`GET ${obj.constructor.name}.${propertyKey} = ${value}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截属性设置
|
|
||||||
*/
|
|
||||||
set(obj: T, prop: string | symbol, newValue: unknown): boolean {
|
|
||||||
// 内部属性和方法直接设置
|
|
||||||
if (typeof prop === 'symbol' || prop.startsWith('_') || prop.startsWith('$')) {
|
|
||||||
return Reflect.set(obj, prop, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
const propertyKey = prop as string;
|
|
||||||
|
|
||||||
// 如果有自定义过滤器且不通过,直接设置
|
|
||||||
if (propertyFilter && !propertyFilter(propertyKey)) {
|
|
||||||
return Reflect.set(obj, prop, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否被临时禁用(用于避免循环)
|
|
||||||
if ((obj as any)._syncVarDisabled) {
|
|
||||||
return Reflect.set(obj, prop, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否为SyncVar
|
|
||||||
if (!isSyncVar(obj, propertyKey)) {
|
|
||||||
return Reflect.set(obj, prop, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取旧值
|
|
||||||
const oldValue = originalValues.get(propertyKey);
|
|
||||||
|
|
||||||
if (debugLog) {
|
|
||||||
logger.debug(`SET ${obj.constructor.name}.${propertyKey} = ${newValue} (was ${oldValue})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置新值
|
|
||||||
const result = Reflect.set(obj, prop, newValue);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
// 更新原始值记录
|
|
||||||
originalValues.set(propertyKey, newValue);
|
|
||||||
|
|
||||||
// 记录变化到SyncVar管理器
|
|
||||||
try {
|
|
||||||
if (TypeGuards.isSyncVarValue(oldValue) && TypeGuards.isSyncVarValue(newValue)) {
|
|
||||||
syncVarManager.recordChange(obj, propertyKey, oldValue, newValue);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`记录SyncVar变化失败:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截属性删除
|
|
||||||
*/
|
|
||||||
deleteProperty(obj: T, prop: string | symbol): boolean {
|
|
||||||
const propertyKey = prop as string;
|
|
||||||
|
|
||||||
if (typeof prop === 'string' && isSyncVar(obj, propertyKey)) {
|
|
||||||
logger.warn(`尝试删除SyncVar属性 ${propertyKey},这可能会导致同步问题`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Reflect.deleteProperty(obj, prop);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截属性枚举
|
|
||||||
*/
|
|
||||||
ownKeys(obj: T): ArrayLike<string | symbol> {
|
|
||||||
return Reflect.ownKeys(obj);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截属性描述符获取
|
|
||||||
*/
|
|
||||||
getOwnPropertyDescriptor(obj: T, prop: string | symbol): PropertyDescriptor | undefined {
|
|
||||||
return Reflect.getOwnPropertyDescriptor(obj, prop);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截in操作符
|
|
||||||
*/
|
|
||||||
has(obj: T, prop: string | symbol): boolean {
|
|
||||||
return Reflect.has(obj, prop);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 标记为已代理
|
|
||||||
(proxy as T & { _syncVarProxied: boolean; _syncVarOptions: SyncVarProxyOptions })._syncVarProxied = true;
|
|
||||||
(proxy as T & { _syncVarProxied: boolean; _syncVarOptions: SyncVarProxyOptions })._syncVarOptions = options;
|
|
||||||
|
|
||||||
if (debugLog) {
|
|
||||||
logger.debug(`${target.constructor.name} 代理创建完成`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查对象是否已经被SyncVar代理
|
|
||||||
*
|
|
||||||
* @param obj - 要检查的对象
|
|
||||||
* @returns 是否已被代理
|
|
||||||
*/
|
|
||||||
export function isSyncVarProxied(obj: unknown): obj is { _syncVarProxied: boolean } {
|
|
||||||
return typeof obj === 'object' && obj !== null && '_syncVarProxied' in obj && (obj as any)._syncVarProxied === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取代理对象的原始目标
|
|
||||||
*
|
|
||||||
* @param proxy - 代理对象
|
|
||||||
* @returns 原始目标对象,如果不是代理则返回原对象
|
|
||||||
*/
|
|
||||||
export function getSyncVarProxyTarget<T>(proxy: T): T {
|
|
||||||
// 注意:JavaScript的Proxy没有直接方法获取target
|
|
||||||
// 这里返回proxy本身,因为我们的代理是透明的
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 销毁SyncVar代理
|
|
||||||
*
|
|
||||||
* 清理代理相关的资源,但注意JavaScript的Proxy无法真正"销毁"
|
|
||||||
* 这个函数主要是清理管理器中的相关数据
|
|
||||||
*
|
|
||||||
* @param proxy - 代理对象
|
|
||||||
*/
|
|
||||||
export function destroySyncVarProxy(proxy: INetworkSyncable & { _syncVarProxied?: boolean; _syncVarDestroyed?: boolean }): void {
|
|
||||||
if (!isSyncVarProxied(proxy)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理SyncVar管理器中的数据
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
syncVarManager.cleanupComponent(proxy);
|
|
||||||
|
|
||||||
// 标记为已销毁(虽然代理仍然存在)
|
|
||||||
proxy._syncVarProxied = false;
|
|
||||||
proxy._syncVarDestroyed = true;
|
|
||||||
|
|
||||||
const logger = createLogger('SyncVarProxy');
|
|
||||||
logger.debug(`${proxy.constructor?.name || 'Unknown'} 代理已销毁`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 临时禁用SyncVar代理监听
|
|
||||||
*
|
|
||||||
* 在回调函数执行期间禁用SyncVar变化监听,避免循环触发
|
|
||||||
*
|
|
||||||
* @param proxy - 代理对象
|
|
||||||
* @param callback - 要执行的回调函数
|
|
||||||
* @returns 回调函数的返回值
|
|
||||||
*/
|
|
||||||
export function withSyncVarDisabled<TResult>(proxy: INetworkSyncable & { _syncVarDisabled?: boolean; _syncVarProxied?: boolean }, callback: () => TResult): TResult {
|
|
||||||
if (!isSyncVarProxied(proxy)) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
const wasDisabled = proxy._syncVarDisabled;
|
|
||||||
proxy._syncVarDisabled = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return callback();
|
|
||||||
} finally {
|
|
||||||
proxy._syncVarDisabled = wasDisabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量更新SyncVar字段
|
|
||||||
*
|
|
||||||
* 在批量更新期间暂时禁用同步,最后一次性触发变化检测
|
|
||||||
*
|
|
||||||
* @param proxy - 代理对象
|
|
||||||
* @param updates - 要更新的字段和值的映射
|
|
||||||
*/
|
|
||||||
export function batchUpdateSyncVar(proxy: INetworkSyncable & { _syncVarProxied?: boolean; _syncVarDisabled?: boolean }, updates: Record<string, unknown>): void {
|
|
||||||
if (!isSyncVarProxied(proxy)) {
|
|
||||||
// 如果不是代理对象,直接批量更新
|
|
||||||
Object.assign(proxy, updates);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
withSyncVarDisabled(proxy, () => {
|
|
||||||
// 记录旧值
|
|
||||||
const oldValues: Record<string, unknown> = {};
|
|
||||||
for (const key of Object.keys(updates)) {
|
|
||||||
if (isSyncVar(proxy, key)) {
|
|
||||||
oldValues[key] = (proxy as unknown as Record<string, unknown>)[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量更新
|
|
||||||
Object.assign(proxy, updates);
|
|
||||||
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
for (const [key, newValue] of Object.entries(updates)) {
|
|
||||||
if (isSyncVar(proxy, key)) {
|
|
||||||
const oldValue = oldValues[key];
|
|
||||||
if (TypeGuards.isSyncVarValue(oldValue) && TypeGuards.isSyncVarValue(newValue)) {
|
|
||||||
syncVarManager.recordChange(proxy, key, oldValue, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,685 +0,0 @@
|
|||||||
import { SyncVarManager } from './SyncVarManager';
|
|
||||||
import { NetworkIdentityRegistry, NetworkIdentity } from '../Core/NetworkIdentity';
|
|
||||||
import { SyncVarUpdateMessage } from '../Messaging/MessageTypes';
|
|
||||||
import { NetworkEnvironment } from '../Core/NetworkEnvironment';
|
|
||||||
import { ComponentRegistry, createLogger } from '@esengine/ecs-framework';
|
|
||||||
import { NetworkComponent } from '../NetworkComponent';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar同步调度配置
|
|
||||||
*/
|
|
||||||
export interface SyncVarSyncConfig {
|
|
||||||
/** 同步频率(毫秒) */
|
|
||||||
syncInterval: number;
|
|
||||||
/** 最大批处理消息数量 */
|
|
||||||
maxBatchSize: number;
|
|
||||||
/** 最大每帧处理对象数量 */
|
|
||||||
maxObjectsPerFrame: number;
|
|
||||||
/** 是否启用优先级排序 */
|
|
||||||
enablePrioritySort: boolean;
|
|
||||||
/** 最小同步间隔(防止过于频繁) */
|
|
||||||
minSyncInterval: number;
|
|
||||||
/** 是否启用增量同步 */
|
|
||||||
enableIncrementalSync: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步优先级计算器
|
|
||||||
*/
|
|
||||||
export interface ISyncPriorityCalculator {
|
|
||||||
/**
|
|
||||||
* 计算组件的同步优先级
|
|
||||||
*
|
|
||||||
* @param component - 网络组件
|
|
||||||
* @param identity - 网络身份
|
|
||||||
* @returns 优先级值,数字越大优先级越高
|
|
||||||
*/
|
|
||||||
calculatePriority(component: any, identity: NetworkIdentity): number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认优先级计算器
|
|
||||||
*/
|
|
||||||
export class DefaultSyncPriorityCalculator implements ISyncPriorityCalculator {
|
|
||||||
public calculatePriority(component: any, identity: NetworkIdentity): number {
|
|
||||||
let priority = 0;
|
|
||||||
|
|
||||||
// 权威对象优先级更高
|
|
||||||
if (identity.hasAuthority) {
|
|
||||||
priority += 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 距离上次同步时间越长,优先级越高
|
|
||||||
const timeSinceLastSync = Date.now() - identity.lastSyncTime;
|
|
||||||
priority += Math.min(timeSinceLastSync / 1000, 10); // 最多加10分
|
|
||||||
|
|
||||||
// 变化数量越多,优先级越高
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
const changes = syncVarManager.getPendingChanges(component);
|
|
||||||
priority += changes.length;
|
|
||||||
|
|
||||||
return priority;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar同步调度器
|
|
||||||
*
|
|
||||||
* 负责定期扫描网络对象的SyncVar变化,创建和分发同步消息
|
|
||||||
* 支持批处理、优先级排序和性能优化
|
|
||||||
*/
|
|
||||||
export class SyncVarSyncScheduler {
|
|
||||||
private static readonly logger = createLogger('SyncVarSyncScheduler');
|
|
||||||
private static _instance: SyncVarSyncScheduler | null = null;
|
|
||||||
|
|
||||||
private _config: SyncVarSyncConfig;
|
|
||||||
private _priorityCalculator: ISyncPriorityCalculator;
|
|
||||||
private _isRunning: boolean = false;
|
|
||||||
private _syncTimer: NodeJS.Timeout | null = null;
|
|
||||||
private _lastSyncTime: number = 0;
|
|
||||||
private _syncCounter: number = 0;
|
|
||||||
|
|
||||||
// 统计信息
|
|
||||||
private _stats = {
|
|
||||||
totalSyncCycles: 0,
|
|
||||||
totalObjectsScanned: 0,
|
|
||||||
totalMessagesSent: 0,
|
|
||||||
totalChangesProcessed: 0,
|
|
||||||
averageCycleTime: 0,
|
|
||||||
lastCycleTime: 0,
|
|
||||||
errors: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// 消息发送回调
|
|
||||||
private _messageSendCallback: ((message: SyncVarUpdateMessage) => Promise<void>) | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取调度器单例
|
|
||||||
*/
|
|
||||||
public static get Instance(): SyncVarSyncScheduler {
|
|
||||||
if (!SyncVarSyncScheduler._instance) {
|
|
||||||
SyncVarSyncScheduler._instance = new SyncVarSyncScheduler();
|
|
||||||
}
|
|
||||||
return SyncVarSyncScheduler._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
// 默认配置
|
|
||||||
this._config = {
|
|
||||||
syncInterval: 50, // 20fps
|
|
||||||
maxBatchSize: 10,
|
|
||||||
maxObjectsPerFrame: 50,
|
|
||||||
enablePrioritySort: true,
|
|
||||||
minSyncInterval: 16, // 最小16ms (60fps)
|
|
||||||
enableIncrementalSync: true
|
|
||||||
};
|
|
||||||
|
|
||||||
this._priorityCalculator = new DefaultSyncPriorityCalculator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置调度器
|
|
||||||
*
|
|
||||||
* @param config - 调度器配置
|
|
||||||
*/
|
|
||||||
public configure(config: Partial<SyncVarSyncConfig>): void {
|
|
||||||
this._config = { ...this._config, ...config };
|
|
||||||
|
|
||||||
// 如果正在运行,重启以应用新配置
|
|
||||||
if (this._isRunning) {
|
|
||||||
this.stop();
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncVarSyncScheduler.logger.debug('调度器配置已更新:', this._config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置优先级计算器
|
|
||||||
*
|
|
||||||
* @param calculator - 优先级计算器
|
|
||||||
*/
|
|
||||||
public setPriorityCalculator(calculator: ISyncPriorityCalculator): void {
|
|
||||||
this._priorityCalculator = calculator;
|
|
||||||
SyncVarSyncScheduler.logger.debug('优先级计算器已更新');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置消息发送回调
|
|
||||||
*
|
|
||||||
* @param callback - 消息发送回调函数
|
|
||||||
*/
|
|
||||||
public setMessageSendCallback(callback: (message: SyncVarUpdateMessage) => Promise<void>): void {
|
|
||||||
this._messageSendCallback = callback;
|
|
||||||
SyncVarSyncScheduler.logger.debug('消息发送回调已设置');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动调度器
|
|
||||||
*/
|
|
||||||
public start(): void {
|
|
||||||
if (this._isRunning) {
|
|
||||||
SyncVarSyncScheduler.logger.warn('调度器已经在运行');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isRunning = true;
|
|
||||||
this._lastSyncTime = Date.now();
|
|
||||||
|
|
||||||
// 设置定时器
|
|
||||||
this._syncTimer = setInterval(() => {
|
|
||||||
this.performSyncCycle();
|
|
||||||
}, this._config.syncInterval);
|
|
||||||
|
|
||||||
SyncVarSyncScheduler.logger.info(`调度器已启动,同步间隔: ${this._config.syncInterval}ms`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止调度器
|
|
||||||
*/
|
|
||||||
public stop(): void {
|
|
||||||
if (!this._isRunning) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isRunning = false;
|
|
||||||
|
|
||||||
if (this._syncTimer) {
|
|
||||||
clearInterval(this._syncTimer);
|
|
||||||
this._syncTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncVarSyncScheduler.logger.info('调度器已停止');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行一次同步周期
|
|
||||||
*/
|
|
||||||
public performSyncCycle(): void {
|
|
||||||
if (!this._isRunning) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cycleStartTime = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 检查最小同步间隔
|
|
||||||
if (cycleStartTime - this._lastSyncTime < this._config.minSyncInterval) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stats.totalSyncCycles++;
|
|
||||||
this._lastSyncTime = cycleStartTime;
|
|
||||||
|
|
||||||
// 获取所有激活的网络对象
|
|
||||||
const activeObjects = NetworkIdentityRegistry.Instance.getActiveObjects();
|
|
||||||
this._stats.totalObjectsScanned += activeObjects.length;
|
|
||||||
|
|
||||||
// 收集需要同步的组件
|
|
||||||
const syncCandidates = this.collectSyncCandidates(activeObjects);
|
|
||||||
|
|
||||||
// 优先级排序
|
|
||||||
if (this._config.enablePrioritySort) {
|
|
||||||
syncCandidates.sort((a, b) => b.priority - a.priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 限制每帧处理的对象数量
|
|
||||||
const objectsToProcess = syncCandidates.slice(0, this._config.maxObjectsPerFrame);
|
|
||||||
|
|
||||||
// 创建和发送同步消息
|
|
||||||
this.processSyncCandidates(objectsToProcess);
|
|
||||||
|
|
||||||
// 更新统计信息
|
|
||||||
const cycleTime = Date.now() - cycleStartTime;
|
|
||||||
this._stats.lastCycleTime = cycleTime;
|
|
||||||
this._stats.averageCycleTime = (this._stats.averageCycleTime * (this._stats.totalSyncCycles - 1) + cycleTime) / this._stats.totalSyncCycles;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this._stats.errors++;
|
|
||||||
SyncVarSyncScheduler.logger.error('同步周期执行失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集同步候选对象
|
|
||||||
*/
|
|
||||||
private collectSyncCandidates(activeObjects: NetworkIdentity[]): Array<{
|
|
||||||
identity: NetworkIdentity;
|
|
||||||
component: any;
|
|
||||||
priority: number;
|
|
||||||
changeCount: number;
|
|
||||||
}> {
|
|
||||||
const candidates: Array<{
|
|
||||||
identity: NetworkIdentity;
|
|
||||||
component: any;
|
|
||||||
priority: number;
|
|
||||||
changeCount: number;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
|
|
||||||
for (const identity of activeObjects) {
|
|
||||||
try {
|
|
||||||
// 获取对象的所有网络组件
|
|
||||||
const components = this.getNetworkComponents(identity);
|
|
||||||
|
|
||||||
for (const component of components) {
|
|
||||||
// 检查组件是否有SyncVar变化
|
|
||||||
const pendingChanges = syncVarManager.getPendingChanges(component);
|
|
||||||
if (pendingChanges.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 权限检查:只有有权限的对象才能发起同步
|
|
||||||
if (!this.canComponentSync(component, identity)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算优先级
|
|
||||||
const priority = this._priorityCalculator.calculatePriority(component, identity);
|
|
||||||
|
|
||||||
candidates.push({
|
|
||||||
identity,
|
|
||||||
component,
|
|
||||||
priority,
|
|
||||||
changeCount: pendingChanges.length
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarSyncScheduler.logger.error(`处理网络对象失败: ${identity.networkId}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取网络对象的所有网络组件
|
|
||||||
*/
|
|
||||||
private getNetworkComponents(identity: NetworkIdentity): NetworkComponent[] {
|
|
||||||
const entity = identity.entity;
|
|
||||||
if (!entity) {
|
|
||||||
SyncVarSyncScheduler.logger.warn(`NetworkIdentity ${identity.networkId} 缺少Entity引用`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const networkComponents: NetworkComponent[] = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取所有已注册的组件类型
|
|
||||||
const allRegisteredTypes = ComponentRegistry.getAllRegisteredTypes();
|
|
||||||
|
|
||||||
for (const [ComponentClass] of allRegisteredTypes) {
|
|
||||||
// 检查是否为NetworkComponent子类
|
|
||||||
if (ComponentClass.prototype instanceof NetworkComponent || ComponentClass === NetworkComponent) {
|
|
||||||
const component = entity.getComponent(ComponentClass as any);
|
|
||||||
if (component && component instanceof NetworkComponent) {
|
|
||||||
networkComponents.push(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarSyncScheduler.logger.error(`获取网络组件失败 (${identity.networkId}):`, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkComponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查组件是否可以进行同步
|
|
||||||
*/
|
|
||||||
private canComponentSync(component: any, identity: NetworkIdentity): boolean {
|
|
||||||
// 服务端对象通常有同步权限
|
|
||||||
if (NetworkEnvironment.isServer && identity.hasAuthority) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 客户端只能同步自己拥有的对象
|
|
||||||
if (NetworkEnvironment.isClient) {
|
|
||||||
// 这里需要获取当前客户端ID,暂时简化处理
|
|
||||||
return identity.hasAuthority;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理同步候选对象
|
|
||||||
*/
|
|
||||||
private processSyncCandidates(candidates: Array<{
|
|
||||||
identity: NetworkIdentity;
|
|
||||||
component: any;
|
|
||||||
priority: number;
|
|
||||||
changeCount: number;
|
|
||||||
}>): void {
|
|
||||||
const syncVarManager = SyncVarManager.Instance;
|
|
||||||
const messageBatch: SyncVarUpdateMessage[] = [];
|
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
try {
|
|
||||||
// 创建SyncVar更新消息
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
candidate.component,
|
|
||||||
candidate.identity.networkId,
|
|
||||||
'', // senderId,后续可以从环境获取
|
|
||||||
candidate.identity.getNextSyncSequence(),
|
|
||||||
false // isFullSync
|
|
||||||
);
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
messageBatch.push(message);
|
|
||||||
this._stats.totalChangesProcessed += candidate.changeCount;
|
|
||||||
|
|
||||||
// 更新对象的同步时间
|
|
||||||
candidate.identity.updateSyncTime();
|
|
||||||
|
|
||||||
// 清除已同步的变化
|
|
||||||
syncVarManager.clearChanges(candidate.component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查批处理大小限制
|
|
||||||
if (messageBatch.length >= this._config.maxBatchSize) {
|
|
||||||
this.sendMessageBatch(messageBatch);
|
|
||||||
messageBatch.length = 0; // 清空数组
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarSyncScheduler.logger.error(`处理同步候选对象失败: ${candidate.identity.networkId}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送剩余的消息
|
|
||||||
if (messageBatch.length > 0) {
|
|
||||||
this.sendMessageBatch(messageBatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息批次
|
|
||||||
*/
|
|
||||||
private async sendMessageBatch(messages: SyncVarUpdateMessage[]): Promise<void> {
|
|
||||||
if (!this._messageSendCallback) {
|
|
||||||
SyncVarSyncScheduler.logger.warn('没有设置消息发送回调,消息被丢弃');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const message of messages) {
|
|
||||||
try {
|
|
||||||
await this._messageSendCallback(message);
|
|
||||||
this._stats.totalMessagesSent++;
|
|
||||||
} catch (error) {
|
|
||||||
SyncVarSyncScheduler.logger.error('发送SyncVar消息失败:', error);
|
|
||||||
this._stats.errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动触发同步
|
|
||||||
*
|
|
||||||
* @param networkId - 指定网络对象ID,如果不提供则同步所有对象
|
|
||||||
*/
|
|
||||||
public manualSync(networkId?: string): void {
|
|
||||||
if (networkId) {
|
|
||||||
const identity = NetworkIdentityRegistry.Instance.find(networkId);
|
|
||||||
if (identity) {
|
|
||||||
this.performSyncCycle();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.performSyncCycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取调度器统计信息
|
|
||||||
*/
|
|
||||||
public getStats(): typeof SyncVarSyncScheduler.prototype._stats & {
|
|
||||||
isRunning: boolean;
|
|
||||||
config: SyncVarSyncConfig;
|
|
||||||
uptime: number;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
...this._stats,
|
|
||||||
isRunning: this._isRunning,
|
|
||||||
config: { ...this._config },
|
|
||||||
uptime: this._isRunning ? Date.now() - (this._lastSyncTime - this._config.syncInterval) : 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置统计信息
|
|
||||||
*/
|
|
||||||
public resetStats(): void {
|
|
||||||
this._stats = {
|
|
||||||
totalSyncCycles: 0,
|
|
||||||
totalObjectsScanned: 0,
|
|
||||||
totalMessagesSent: 0,
|
|
||||||
totalChangesProcessed: 0,
|
|
||||||
averageCycleTime: 0,
|
|
||||||
lastCycleTime: 0,
|
|
||||||
errors: 0
|
|
||||||
};
|
|
||||||
SyncVarSyncScheduler.logger.debug('统计信息已重置');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前配置
|
|
||||||
*/
|
|
||||||
public getConfig(): SyncVarSyncConfig {
|
|
||||||
return { ...this._config };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查调度器是否正在运行
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* SyncVar同步变量系统导出
|
|
||||||
*
|
|
||||||
* 提供自动变量同步功能,支持网络状态的实时同步
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 装饰器和元数据
|
|
||||||
export * from './SyncVarDecorator';
|
|
||||||
|
|
||||||
// 管理器
|
|
||||||
export { SyncVarManager } from './SyncVarManager';
|
|
||||||
|
|
||||||
// 代理系统
|
|
||||||
export * from './SyncVarProxy';
|
|
||||||
|
|
||||||
// 工厂函数
|
|
||||||
export * from './SyncVarFactory';
|
|
||||||
|
|
||||||
// 消息处理器
|
|
||||||
export { SyncVarMessageHandler } from './SyncVarMessageHandler';
|
|
||||||
|
|
||||||
// 同步调度器
|
|
||||||
export {
|
|
||||||
SyncVarSyncScheduler,
|
|
||||||
DefaultSyncPriorityCalculator
|
|
||||||
} from './SyncVarSyncScheduler';
|
|
||||||
export type {
|
|
||||||
SyncVarSyncConfig,
|
|
||||||
ISyncPriorityCalculator
|
|
||||||
} from './SyncVarSyncScheduler';
|
|
||||||
|
|
||||||
// 性能优化器
|
|
||||||
export {
|
|
||||||
SyncVarOptimizer,
|
|
||||||
SyncVarMessageMerger,
|
|
||||||
SyncVarRateLimiter,
|
|
||||||
SyncVarDistanceCuller
|
|
||||||
} from './SyncVarOptimizer';
|
|
||||||
export type {
|
|
||||||
SyncVarOptimizationConfig
|
|
||||||
} from './SyncVarOptimizer';
|
|
||||||
|
|
||||||
@@ -1,383 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,698 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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';
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* 组件更新消息(服务器广播给客户端)
|
|
||||||
*/
|
|
||||||
export interface MsgComponentUpdate {
|
|
||||||
entityId: number;
|
|
||||||
componentType: string;
|
|
||||||
componentData: any;
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* 加入房间协议
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* 组件同步协议定义
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 同步组件数据请求
|
|
||||||
export interface ReqSyncComponent {
|
|
||||||
entityId: number;
|
|
||||||
componentType: string;
|
|
||||||
componentData: any;
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同步组件数据响应
|
|
||||||
export interface ResSyncComponent {
|
|
||||||
success: boolean;
|
|
||||||
entityId: number;
|
|
||||||
errorMsg?: string;
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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": {}
|
|
||||||
};
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
/**
|
|
||||||
* 网络库常量定义
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络配置常量
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
|
|
||||||
107
packages/network/src/decorators/ClientRpc.ts
Normal file
107
packages/network/src/decorators/ClientRpc.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* ClientRpc 装饰器
|
||||||
|
*
|
||||||
|
* 用于标记可以从服务端调用的客户端方法
|
||||||
|
* 只能在服务端调用,会向指定客户端或所有客户端发送RPC消息
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { RpcMetadata } from '../types/NetworkTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClientRpc 装饰器选项
|
||||||
|
*/
|
||||||
|
export interface ClientRpcOptions {
|
||||||
|
/** 是否需要权威验证,默认为 true */
|
||||||
|
requiresAuthority?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储 ClientRpc 元数据的 Symbol
|
||||||
|
*/
|
||||||
|
export const CLIENT_RPC_METADATA_KEY = Symbol('client_rpc_metadata');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClientRpc 装饰器
|
||||||
|
*
|
||||||
|
* @param options 装饰器选项
|
||||||
|
* @returns 方法装饰器函数
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* class PlayerController extends NetworkBehaviour {
|
||||||
|
* @ClientRpc()
|
||||||
|
* public showEffect(effectType: string, position: Vector3): void {
|
||||||
|
* // 这个方法会在客户端执行
|
||||||
|
* console.log(`显示特效: ${effectType} at ${position}`);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* private triggerEffect(): void {
|
||||||
|
* if (this.isServer) {
|
||||||
|
* // 服务端调用,会发送到所有客户端
|
||||||
|
* this.showEffect('explosion', new Vector3(0, 0, 0));
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function ClientRpc(options: ClientRpcOptions = {}): MethodDecorator {
|
||||||
|
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||||
|
if (typeof propertyKey !== 'string') {
|
||||||
|
throw new Error('ClientRpc can only be applied to string method names');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取或创建元数据数组
|
||||||
|
let metadata: RpcMetadata[] = Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target.constructor);
|
||||||
|
if (!metadata) {
|
||||||
|
metadata = [];
|
||||||
|
Reflect.defineMetadata(CLIENT_RPC_METADATA_KEY, metadata, target.constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加当前方法的元数据
|
||||||
|
const rpcMetadata: RpcMetadata = {
|
||||||
|
methodName: propertyKey,
|
||||||
|
isClientRpc: true,
|
||||||
|
requiresAuthority: options.requiresAuthority !== false // 默认为 true
|
||||||
|
};
|
||||||
|
|
||||||
|
metadata.push(rpcMetadata);
|
||||||
|
|
||||||
|
// 保存原始方法
|
||||||
|
const originalMethod = descriptor.value;
|
||||||
|
|
||||||
|
// 包装方法以添加网络功能
|
||||||
|
descriptor.value = function (this: any, ...args: any[]) {
|
||||||
|
// 如果在服务端调用,发送RPC消息
|
||||||
|
if (this.isServer) {
|
||||||
|
if (rpcMetadata.requiresAuthority && !this.hasAuthority) {
|
||||||
|
console.warn(`[ClientRpc] 权限不足,无法调用: ${propertyKey}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送客户端RPC
|
||||||
|
this.sendClientRpc?.(propertyKey, args);
|
||||||
|
} else if (this.isClient) {
|
||||||
|
// 在客户端直接执行原始方法
|
||||||
|
return originalMethod.apply(this, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类的 ClientRpc 元数据
|
||||||
|
*/
|
||||||
|
export function getClientRpcMetadata(target: any): RpcMetadata[] {
|
||||||
|
return Reflect.getMetadata(CLIENT_RPC_METADATA_KEY, target) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查方法是否为 ClientRpc
|
||||||
|
*/
|
||||||
|
export function isClientRpc(target: any, methodName: string): boolean {
|
||||||
|
const metadata = getClientRpcMetadata(target);
|
||||||
|
return metadata.some(m => m.methodName === methodName);
|
||||||
|
}
|
||||||
108
packages/network/src/decorators/Command.ts
Normal file
108
packages/network/src/decorators/Command.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* Command 装饰器
|
||||||
|
*
|
||||||
|
* 用于标记可以从客户端调用的服务端方法
|
||||||
|
* 只能在客户端调用,会向服务端发送命令消息
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { RpcMetadata } from '../types/NetworkTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command 装饰器选项
|
||||||
|
*/
|
||||||
|
export interface CommandOptions {
|
||||||
|
/** 是否需要权威验证,默认为 true */
|
||||||
|
requiresAuthority?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储 Command 元数据的 Symbol
|
||||||
|
*/
|
||||||
|
export const COMMAND_METADATA_KEY = Symbol('command_metadata');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command 装饰器
|
||||||
|
*
|
||||||
|
* @param options 装饰器选项
|
||||||
|
* @returns 方法装饰器函数
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* class PlayerController extends NetworkBehaviour {
|
||||||
|
* @Command()
|
||||||
|
* public movePlayer(direction: Vector3): void {
|
||||||
|
* // 这个方法会在服务端执行
|
||||||
|
* console.log(`玩家移动: ${direction}`);
|
||||||
|
* // 更新玩家位置逻辑...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* private handleInput(): void {
|
||||||
|
* if (this.isClient && this.hasAuthority) {
|
||||||
|
* // 客户端调用,会发送到服务端
|
||||||
|
* this.movePlayer(new Vector3(1, 0, 0));
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function Command(options: CommandOptions = {}): MethodDecorator {
|
||||||
|
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||||
|
if (typeof propertyKey !== 'string') {
|
||||||
|
throw new Error('Command can only be applied to string method names');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取或创建元数据数组
|
||||||
|
let metadata: RpcMetadata[] = Reflect.getMetadata(COMMAND_METADATA_KEY, target.constructor);
|
||||||
|
if (!metadata) {
|
||||||
|
metadata = [];
|
||||||
|
Reflect.defineMetadata(COMMAND_METADATA_KEY, metadata, target.constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加当前方法的元数据
|
||||||
|
const rpcMetadata: RpcMetadata = {
|
||||||
|
methodName: propertyKey,
|
||||||
|
isClientRpc: false,
|
||||||
|
requiresAuthority: options.requiresAuthority !== false // 默认为 true
|
||||||
|
};
|
||||||
|
|
||||||
|
metadata.push(rpcMetadata);
|
||||||
|
|
||||||
|
// 保存原始方法
|
||||||
|
const originalMethod = descriptor.value;
|
||||||
|
|
||||||
|
// 包装方法以添加网络功能
|
||||||
|
descriptor.value = function (this: any, ...args: any[]) {
|
||||||
|
// 如果在客户端调用,发送命令消息
|
||||||
|
if (this.isClient) {
|
||||||
|
if (rpcMetadata.requiresAuthority && !this.hasAuthority) {
|
||||||
|
console.warn(`[Command] 权限不足,无法调用: ${propertyKey}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送命令到服务端
|
||||||
|
this.sendCommand?.(propertyKey, args);
|
||||||
|
} else if (this.isServer) {
|
||||||
|
// 在服务端直接执行原始方法
|
||||||
|
return originalMethod.apply(this, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类的 Command 元数据
|
||||||
|
*/
|
||||||
|
export function getCommandMetadata(target: any): RpcMetadata[] {
|
||||||
|
return Reflect.getMetadata(COMMAND_METADATA_KEY, target) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查方法是否为 Command
|
||||||
|
*/
|
||||||
|
export function isCommand(target: any, methodName: string): boolean {
|
||||||
|
const metadata = getCommandMetadata(target);
|
||||||
|
return metadata.some(m => m.methodName === methodName);
|
||||||
|
}
|
||||||
116
packages/network/src/decorators/SyncVar.ts
Normal file
116
packages/network/src/decorators/SyncVar.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* SyncVar 装饰器
|
||||||
|
*
|
||||||
|
* 用于标记需要在网络间自动同步的属性
|
||||||
|
* 当属性值发生变化时,会自动同步到其他网络端
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { SyncVarMetadata } from '../types/NetworkTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SyncVar 装饰器选项
|
||||||
|
*/
|
||||||
|
export interface SyncVarOptions {
|
||||||
|
/** 是否仅权威端可修改,默认为 true */
|
||||||
|
authorityOnly?: boolean;
|
||||||
|
/** 变化回调函数名,属性变化时会调用此方法 */
|
||||||
|
onChanged?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储 SyncVar 元数据的 Symbol
|
||||||
|
*/
|
||||||
|
export const SYNCVAR_METADATA_KEY = Symbol('syncvar_metadata');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SyncVar 装饰器
|
||||||
|
*
|
||||||
|
* @param options 装饰器选项
|
||||||
|
* @returns 属性装饰器函数
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* class PlayerController extends NetworkBehaviour {
|
||||||
|
* @SyncVar({ onChanged: 'onHealthChanged' })
|
||||||
|
* public health: number = 100;
|
||||||
|
*
|
||||||
|
* @SyncVar({ authorityOnly: false })
|
||||||
|
* public playerName: string = '';
|
||||||
|
*
|
||||||
|
* private onHealthChanged(oldValue: number, newValue: number): void {
|
||||||
|
* console.log(`Health changed from ${oldValue} to ${newValue}`);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function SyncVar(options: SyncVarOptions = {}): PropertyDecorator {
|
||||||
|
return function (target: any, propertyKey: string | symbol) {
|
||||||
|
if (typeof propertyKey !== 'string') {
|
||||||
|
throw new Error('SyncVar can only be applied to string property keys');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取或创建元数据数组
|
||||||
|
let metadata: SyncVarMetadata[] = Reflect.getMetadata(SYNCVAR_METADATA_KEY, target.constructor);
|
||||||
|
if (!metadata) {
|
||||||
|
metadata = [];
|
||||||
|
Reflect.defineMetadata(SYNCVAR_METADATA_KEY, metadata, target.constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加当前属性的元数据
|
||||||
|
const syncVarMetadata: SyncVarMetadata = {
|
||||||
|
propertyName: propertyKey,
|
||||||
|
authorityOnly: options.authorityOnly !== false, // 默认为 true
|
||||||
|
onChanged: options.onChanged
|
||||||
|
};
|
||||||
|
|
||||||
|
metadata.push(syncVarMetadata);
|
||||||
|
|
||||||
|
// 创建属性的内部存储
|
||||||
|
const internalKey = `_${propertyKey}`;
|
||||||
|
const changeKey = `_${propertyKey}_changed`;
|
||||||
|
|
||||||
|
// 重新定义属性的 getter 和 setter
|
||||||
|
Object.defineProperty(target, propertyKey, {
|
||||||
|
get: function (this: any) {
|
||||||
|
return this[internalKey];
|
||||||
|
},
|
||||||
|
set: function (this: any, newValue: any) {
|
||||||
|
const oldValue = this[internalKey];
|
||||||
|
|
||||||
|
// 检查值是否真的发生了变化
|
||||||
|
if (oldValue === newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this[internalKey] = newValue;
|
||||||
|
this[changeKey] = true;
|
||||||
|
|
||||||
|
// 调用变化回调
|
||||||
|
if (options.onChanged && typeof this[options.onChanged] === 'function') {
|
||||||
|
this[options.onChanged](oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知同步管理器
|
||||||
|
this.notifySyncVarChanged?.(propertyKey, oldValue, newValue);
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类的 SyncVar 元数据
|
||||||
|
*/
|
||||||
|
export function getSyncVarMetadata(target: any): SyncVarMetadata[] {
|
||||||
|
return Reflect.getMetadata(SYNCVAR_METADATA_KEY, target) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查属性是否为 SyncVar
|
||||||
|
*/
|
||||||
|
export function isSyncVar(target: any, propertyName: string): boolean {
|
||||||
|
const metadata = getSyncVarMetadata(target);
|
||||||
|
return metadata.some(m => m.propertyName === propertyName);
|
||||||
|
}
|
||||||
@@ -1,29 +1,27 @@
|
|||||||
/**
|
/**
|
||||||
* ECS Framework Network Plugin - 网络插件
|
* ECS Network Library
|
||||||
*
|
*
|
||||||
* 为ECS框架提供完整的网络同步和多人游戏功能
|
* 基于 ECS 架构和 TSRPC 的网络同步库
|
||||||
* 支持客户端-服务端模式
|
* 提供简洁易用的网络组件和同步机制
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 核心网络管理
|
// 核心组件
|
||||||
export * from './Core';
|
export { NetworkManager } from './NetworkManager';
|
||||||
|
export { NetworkIdentity } from './NetworkIdentity';
|
||||||
|
export { NetworkBehaviour } from './NetworkBehaviour';
|
||||||
|
|
||||||
// 消息系统
|
// 装饰器
|
||||||
export * from './Messaging';
|
export { SyncVar } from './decorators/SyncVar';
|
||||||
|
export { ClientRpc } from './decorators/ClientRpc';
|
||||||
|
export { Command } from './decorators/Command';
|
||||||
|
|
||||||
// SyncVar同步变量系统
|
// 核心管理器
|
||||||
export * from './SyncVar';
|
export { SyncVarManager } from './core/SyncVarManager';
|
||||||
|
export { RpcManager } from './core/RpcManager';
|
||||||
|
export { NetworkRegistry } from './core/NetworkRegistry';
|
||||||
|
|
||||||
// 网络组件基类
|
// 传输层
|
||||||
export { NetworkComponent } from './NetworkComponent';
|
export * from './transport';
|
||||||
export { INetworkSyncable } from './INetworkSyncable';
|
|
||||||
export { NetworkRole } from './NetworkRole';
|
|
||||||
|
|
||||||
// TSRPC网络通信系统
|
// 类型定义
|
||||||
export * from './TSRPC';
|
export * from './types/NetworkTypes';
|
||||||
|
|
||||||
// 装饰器序列化系统(用于组件同步)
|
|
||||||
export * from './Serialization';
|
|
||||||
|
|
||||||
// 快照系统(帧同步)
|
|
||||||
export * from './Snapshot';
|
|
||||||
431
packages/network/src/transport/TsrpcClient.ts
Normal file
431
packages/network/src/transport/TsrpcClient.ts
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
/**
|
||||||
|
* TSRPC 客户端传输层
|
||||||
|
*
|
||||||
|
* 封装TSRPC客户端功能,提供服务端连接和消息收发
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { WsClient } from 'tsrpc';
|
||||||
|
import { createLogger } from '@esengine/ecs-framework';
|
||||||
|
import { serviceProto, ServiceType } from './protocols/serviceProto';
|
||||||
|
import { NetworkConfig, NetworkMessage } from '../types/NetworkTypes';
|
||||||
|
import {
|
||||||
|
ReqJoinRoom, ResJoinRoom,
|
||||||
|
ReqServerStatus, ResServerStatus,
|
||||||
|
ReqPing, ResPing,
|
||||||
|
MsgNetworkMessage,
|
||||||
|
MsgSyncVar,
|
||||||
|
MsgRpcCall,
|
||||||
|
MsgNetworkObjectSpawn,
|
||||||
|
MsgNetworkObjectDespawn,
|
||||||
|
MsgClientDisconnected,
|
||||||
|
MsgAuthorityChange
|
||||||
|
} from './protocols/NetworkProtocols';
|
||||||
|
|
||||||
|
const logger = createLogger('TsrpcClient');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接状态
|
||||||
|
*/
|
||||||
|
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TSRPC客户端包装器
|
||||||
|
*/
|
||||||
|
export class TsrpcClient {
|
||||||
|
private client: WsClient<ServiceType> | null = null;
|
||||||
|
private config: NetworkConfig;
|
||||||
|
private connectionState: ConnectionState = 'disconnected';
|
||||||
|
private clientId: number = 0;
|
||||||
|
private roomId: string = '';
|
||||||
|
private reconnectAttempts: number = 0;
|
||||||
|
private maxReconnectAttempts: number = 5;
|
||||||
|
private reconnectInterval: number = 2000;
|
||||||
|
private heartbeatInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
/** 统计信息 */
|
||||||
|
private stats = {
|
||||||
|
messagesSent: 0,
|
||||||
|
messagesReceived: 0,
|
||||||
|
bytesSent: 0,
|
||||||
|
bytesReceived: 0,
|
||||||
|
latency: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 事件处理器 */
|
||||||
|
public onConnected?: () => void;
|
||||||
|
public onDisconnected?: (reason?: string) => void;
|
||||||
|
public onReconnecting?: () => void;
|
||||||
|
public onMessage?: (message: NetworkMessage) => void;
|
||||||
|
public onError?: (error: Error) => void;
|
||||||
|
|
||||||
|
constructor(config: NetworkConfig) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接到服务端
|
||||||
|
*/
|
||||||
|
public async connect(): Promise<void> {
|
||||||
|
if (this.connectionState !== 'disconnected') {
|
||||||
|
throw new Error('客户端已连接或正在连接中');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionState = 'connecting';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建WebSocket客户端
|
||||||
|
this.client = new WsClient(serviceProto, {
|
||||||
|
server: `ws://${this.config.host}:${this.config.port}`,
|
||||||
|
// 自动重连配置
|
||||||
|
heartbeat: {
|
||||||
|
interval: 30000,
|
||||||
|
timeout: 5000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setupEventHandlers();
|
||||||
|
|
||||||
|
// 连接到服务端
|
||||||
|
const connectResult = await this.client.connect();
|
||||||
|
if (!connectResult.isSucc) {
|
||||||
|
throw new Error(`连接失败: ${connectResult.errMsg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入房间
|
||||||
|
const joinResult = await this.client.callApi('network/JoinRoom', {
|
||||||
|
roomId: this.config.roomId,
|
||||||
|
clientInfo: {
|
||||||
|
version: '1.0.0',
|
||||||
|
platform: typeof window !== 'undefined' ? 'browser' : 'node'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!joinResult.isSucc) {
|
||||||
|
throw new Error(`加入房间失败: ${joinResult.err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clientId = joinResult.res.clientId;
|
||||||
|
this.roomId = joinResult.res.roomId;
|
||||||
|
this.connectionState = 'connected';
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
|
||||||
|
// 启动心跳
|
||||||
|
this.startHeartbeat();
|
||||||
|
|
||||||
|
logger.info(`连接成功,客户端ID: ${this.clientId}, 房间: ${this.roomId}`);
|
||||||
|
this.onConnected?.();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.connectionState = 'disconnected';
|
||||||
|
logger.error('连接服务端失败:', error);
|
||||||
|
this.onError?.(error as Error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开连接
|
||||||
|
*/
|
||||||
|
public async disconnect(): Promise<void> {
|
||||||
|
if (this.connectionState === 'disconnected') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionState = 'disconnected';
|
||||||
|
this.stopHeartbeat();
|
||||||
|
|
||||||
|
if (this.client) {
|
||||||
|
await this.client.disconnect();
|
||||||
|
this.client = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('客户端已断开连接');
|
||||||
|
this.onDisconnected?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息到服务端
|
||||||
|
*/
|
||||||
|
public async sendMessage(message: NetworkMessage): Promise<void> {
|
||||||
|
if (!this.isConnected()) {
|
||||||
|
throw new Error('客户端未连接');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tsrpcMessage: MsgNetworkMessage = {
|
||||||
|
type: message.type as 'syncvar' | 'rpc',
|
||||||
|
networkId: message.networkId,
|
||||||
|
data: message.data,
|
||||||
|
timestamp: message.timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.client!.sendMsg('network/NetworkMessage', tsrpcMessage);
|
||||||
|
this.stats.messagesSent++;
|
||||||
|
|
||||||
|
logger.debug(`发送消息: ${message.type}, 网络ID: ${message.networkId}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('发送消息失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送SyncVar同步消息
|
||||||
|
*/
|
||||||
|
public async sendSyncVar(
|
||||||
|
networkId: number,
|
||||||
|
componentType: string,
|
||||||
|
propertyName: string,
|
||||||
|
value: any
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.isConnected()) {
|
||||||
|
throw new Error('客户端未连接');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const message: MsgSyncVar = {
|
||||||
|
networkId,
|
||||||
|
componentType,
|
||||||
|
propertyName,
|
||||||
|
value,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.client!.sendMsg('network/SyncVar', message);
|
||||||
|
this.stats.messagesSent++;
|
||||||
|
|
||||||
|
logger.debug(`发送SyncVar: ${componentType}.${propertyName} = ${value}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('发送SyncVar失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送RPC调用消息
|
||||||
|
*/
|
||||||
|
public async sendRpcCall(
|
||||||
|
networkId: number,
|
||||||
|
componentType: string,
|
||||||
|
methodName: string,
|
||||||
|
args: any[],
|
||||||
|
isClientRpc: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.isConnected()) {
|
||||||
|
throw new Error('客户端未连接');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const message: MsgRpcCall = {
|
||||||
|
networkId,
|
||||||
|
componentType,
|
||||||
|
methodName,
|
||||||
|
args,
|
||||||
|
isClientRpc,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.client!.sendMsg('network/RpcCall', message);
|
||||||
|
this.stats.messagesSent++;
|
||||||
|
|
||||||
|
logger.debug(`发送RPC: ${componentType}.${methodName}(${isClientRpc ? 'ClientRpc' : 'Command'})`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('发送RPC失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询服务端状态
|
||||||
|
*/
|
||||||
|
public async getServerStatus(): Promise<ResServerStatus> {
|
||||||
|
if (!this.isConnected()) {
|
||||||
|
throw new Error('客户端未连接');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.client!.callApi('network/ServerStatus', {});
|
||||||
|
if (!result.isSucc) {
|
||||||
|
throw new Error(`查询服务端状态失败: ${result.err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送心跳
|
||||||
|
*/
|
||||||
|
public async ping(): Promise<number> {
|
||||||
|
if (!this.isConnected()) {
|
||||||
|
throw new Error('客户端未连接');
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
const result = await this.client!.callApi('network/Ping', {
|
||||||
|
timestamp: startTime
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.isSucc) {
|
||||||
|
throw new Error(`心跳失败: ${result.err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const latency = Date.now() - startTime;
|
||||||
|
this.stats.latency = latency;
|
||||||
|
return latency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接状态
|
||||||
|
*/
|
||||||
|
public getConnectionState(): ConnectionState {
|
||||||
|
return this.connectionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已连接
|
||||||
|
*/
|
||||||
|
public isConnected(): boolean {
|
||||||
|
return this.connectionState === 'connected' && (this.client?.isConnected || false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端ID
|
||||||
|
*/
|
||||||
|
public getClientId(): number {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计信息
|
||||||
|
*/
|
||||||
|
public getStats() {
|
||||||
|
return { ...this.stats };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置事件处理器
|
||||||
|
*/
|
||||||
|
private setupEventHandlers(): void {
|
||||||
|
if (!this.client) return;
|
||||||
|
|
||||||
|
// 连接断开处理
|
||||||
|
this.client.flows.postDisconnectFlow.push((v) => {
|
||||||
|
if (this.connectionState !== 'disconnected') {
|
||||||
|
logger.warn('连接意外断开,尝试重连...');
|
||||||
|
this.connectionState = 'reconnecting';
|
||||||
|
this.onReconnecting?.();
|
||||||
|
this.attemptReconnect();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 消息监听
|
||||||
|
this.client.listenMsg('network/NetworkMessage', msg => {
|
||||||
|
this.stats.messagesReceived++;
|
||||||
|
|
||||||
|
const networkMessage: NetworkMessage = {
|
||||||
|
type: msg.type,
|
||||||
|
networkId: msg.networkId,
|
||||||
|
data: msg.data,
|
||||||
|
timestamp: msg.timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onMessage?.(networkMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
// SyncVar消息监听
|
||||||
|
this.client.listenMsg('network/SyncVar', msg => {
|
||||||
|
this.stats.messagesReceived++;
|
||||||
|
|
||||||
|
const networkMessage: NetworkMessage = {
|
||||||
|
type: 'syncvar',
|
||||||
|
networkId: msg.networkId,
|
||||||
|
data: {
|
||||||
|
componentType: msg.componentType,
|
||||||
|
propertyName: msg.propertyName,
|
||||||
|
value: msg.value
|
||||||
|
},
|
||||||
|
timestamp: msg.timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onMessage?.(networkMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
// RPC消息监听
|
||||||
|
this.client.listenMsg('network/RpcCall', msg => {
|
||||||
|
this.stats.messagesReceived++;
|
||||||
|
|
||||||
|
const networkMessage: NetworkMessage = {
|
||||||
|
type: 'rpc',
|
||||||
|
networkId: msg.networkId,
|
||||||
|
data: {
|
||||||
|
componentType: msg.componentType,
|
||||||
|
methodName: msg.methodName,
|
||||||
|
args: msg.args
|
||||||
|
},
|
||||||
|
timestamp: msg.timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onMessage?.(networkMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 客户端断开通知
|
||||||
|
this.client.listenMsg('network/ClientDisconnected', msg => {
|
||||||
|
logger.info(`客户端 ${msg.clientId} 断开连接: ${msg.reason}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 权威转移通知
|
||||||
|
this.client.listenMsg('network/AuthorityChange', msg => {
|
||||||
|
logger.info(`网络对象 ${msg.networkId} 权威转移给客户端 ${msg.newOwnerId}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试重连
|
||||||
|
*/
|
||||||
|
private async attemptReconnect(): Promise<void> {
|
||||||
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||||
|
logger.error('重连次数已达上限,停止重连');
|
||||||
|
this.connectionState = 'disconnected';
|
||||||
|
this.onDisconnected?.('max_reconnect_attempts_reached');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
logger.info(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, this.reconnectInterval));
|
||||||
|
|
||||||
|
// 重新连接
|
||||||
|
await this.connect();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`重连失败:`, error);
|
||||||
|
// 继续尝试重连
|
||||||
|
this.attemptReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动心跳
|
||||||
|
*/
|
||||||
|
private startHeartbeat(): void {
|
||||||
|
this.heartbeatInterval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await this.ping();
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('心跳失败:', error);
|
||||||
|
}
|
||||||
|
}, 30000); // 30秒心跳
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止心跳
|
||||||
|
*/
|
||||||
|
private stopHeartbeat(): void {
|
||||||
|
if (this.heartbeatInterval) {
|
||||||
|
clearInterval(this.heartbeatInterval);
|
||||||
|
this.heartbeatInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
364
packages/network/src/transport/TsrpcServer.ts
Normal file
364
packages/network/src/transport/TsrpcServer.ts
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
/**
|
||||||
|
* TSRPC 服务端传输层
|
||||||
|
*
|
||||||
|
* 封装TSRPC服务端功能,提供网络消息处理和客户端管理
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { WsServer } from 'tsrpc';
|
||||||
|
import { createLogger } from '@esengine/ecs-framework';
|
||||||
|
import { serviceProto, ServiceType } from './protocols/serviceProto';
|
||||||
|
import { NetworkConfig, NetworkMessage } from '../types/NetworkTypes';
|
||||||
|
import {
|
||||||
|
ReqJoinRoom, ResJoinRoom,
|
||||||
|
ReqServerStatus, ResServerStatus,
|
||||||
|
ReqPing, ResPing,
|
||||||
|
MsgNetworkMessage,
|
||||||
|
MsgSyncVar,
|
||||||
|
MsgRpcCall,
|
||||||
|
MsgNetworkObjectSpawn,
|
||||||
|
MsgNetworkObjectDespawn,
|
||||||
|
MsgClientDisconnected,
|
||||||
|
MsgAuthorityChange
|
||||||
|
} from './protocols/NetworkProtocols';
|
||||||
|
|
||||||
|
const logger = createLogger('TsrpcServer');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接信息
|
||||||
|
*/
|
||||||
|
interface ClientConnection {
|
||||||
|
/** 客户端ID */
|
||||||
|
id: number;
|
||||||
|
/** 连接对象 */
|
||||||
|
connection: any;
|
||||||
|
/** 连接时间 */
|
||||||
|
connectTime: number;
|
||||||
|
/** 最后活跃时间 */
|
||||||
|
lastActivity: number;
|
||||||
|
/** 客户端信息 */
|
||||||
|
clientInfo?: {
|
||||||
|
version: string;
|
||||||
|
platform: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TSRPC服务端包装器
|
||||||
|
*/
|
||||||
|
export class TsrpcServer {
|
||||||
|
private server: WsServer<ServiceType> | null = null;
|
||||||
|
private clients: Map<number, ClientConnection> = new Map();
|
||||||
|
private nextClientId: number = 1;
|
||||||
|
private config: NetworkConfig;
|
||||||
|
private startTime: number = 0;
|
||||||
|
|
||||||
|
/** 统计信息 */
|
||||||
|
private stats = {
|
||||||
|
messagesSent: 0,
|
||||||
|
messagesReceived: 0,
|
||||||
|
bytesSent: 0,
|
||||||
|
bytesReceived: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 事件处理器 */
|
||||||
|
public onClientConnected?: (clientId: number) => void;
|
||||||
|
public onClientDisconnected?: (clientId: number, reason?: string) => void;
|
||||||
|
public onMessage?: (message: NetworkMessage, fromClientId: number) => void;
|
||||||
|
|
||||||
|
constructor(config: NetworkConfig) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动服务端
|
||||||
|
*/
|
||||||
|
public async start(): Promise<void> {
|
||||||
|
if (this.server) {
|
||||||
|
throw new Error('服务端已经在运行中');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建TSRPC WebSocket服务端
|
||||||
|
this.server = new WsServer(serviceProto, {
|
||||||
|
port: this.config.port || 7777
|
||||||
|
});
|
||||||
|
|
||||||
|
this.startTime = Date.now();
|
||||||
|
this.setupApiHandlers();
|
||||||
|
this.setupMessageHandlers();
|
||||||
|
this.setupConnectionHandlers();
|
||||||
|
|
||||||
|
// 启动服务端
|
||||||
|
await this.server.start();
|
||||||
|
logger.info(`TSRPC服务端已启动,端口: ${this.config.port}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止服务端
|
||||||
|
*/
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
if (this.server) {
|
||||||
|
await this.server.stop();
|
||||||
|
this.server = null;
|
||||||
|
this.clients.clear();
|
||||||
|
logger.info('TSRPC服务端已停止');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向指定客户端发送消息
|
||||||
|
*/
|
||||||
|
public sendToClient(clientId: number, message: any): void {
|
||||||
|
const client = this.clients.get(clientId);
|
||||||
|
if (!client) {
|
||||||
|
logger.warn(`客户端不存在: ${clientId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 发送消息给指定连接
|
||||||
|
this.server?.broadcastMsg(message.type, message.data || message, [client.connection]);
|
||||||
|
this.stats.messagesSent++;
|
||||||
|
logger.debug(`向客户端 ${clientId} 发送消息: ${message.type || 'unknown'}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`向客户端 ${clientId} 发送消息失败:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向所有客户端广播消息
|
||||||
|
*/
|
||||||
|
public broadcast(message: any, excludeClientId?: number): void {
|
||||||
|
if (!this.server) {
|
||||||
|
logger.warn('服务端未启动,无法广播消息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (excludeClientId) {
|
||||||
|
// 排除指定客户端
|
||||||
|
const targetConnections = Array.from(this.clients.entries())
|
||||||
|
.filter(([clientId, client]) => clientId !== excludeClientId)
|
||||||
|
.map(([clientId, client]) => client.connection);
|
||||||
|
|
||||||
|
this.server.broadcastMsg(message.type, message.data || message, targetConnections);
|
||||||
|
this.stats.messagesSent += targetConnections.length;
|
||||||
|
} else {
|
||||||
|
// 广播给所有客户端
|
||||||
|
this.server.broadcastMsg(message.type, message.data || message);
|
||||||
|
this.stats.messagesSent += this.clients.size;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('广播消息失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`广播消息给 ${this.clients.size} 个客户端: ${message.type || 'unknown'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接的客户端列表
|
||||||
|
*/
|
||||||
|
public getConnectedClients(): number[] {
|
||||||
|
return Array.from(this.clients.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端数量
|
||||||
|
*/
|
||||||
|
public getClientCount(): number {
|
||||||
|
return this.clients.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务端统计信息
|
||||||
|
*/
|
||||||
|
public getStats() {
|
||||||
|
return {
|
||||||
|
...this.stats,
|
||||||
|
clientCount: this.clients.size,
|
||||||
|
uptime: Date.now() - this.startTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置API处理器
|
||||||
|
*/
|
||||||
|
private setupApiHandlers(): void {
|
||||||
|
if (!this.server) return;
|
||||||
|
|
||||||
|
// 客户端加入房间
|
||||||
|
this.server.implementApi('network/JoinRoom', call => {
|
||||||
|
const clientId = this.nextClientId++;
|
||||||
|
const client: ClientConnection = {
|
||||||
|
id: clientId,
|
||||||
|
connection: call.conn,
|
||||||
|
connectTime: Date.now(),
|
||||||
|
lastActivity: Date.now(),
|
||||||
|
clientInfo: call.req.clientInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
this.clients.set(clientId, client);
|
||||||
|
logger.info(`客户端 ${clientId} 连接成功`);
|
||||||
|
|
||||||
|
// 通知上层
|
||||||
|
this.onClientConnected?.(clientId);
|
||||||
|
|
||||||
|
// 返回响应
|
||||||
|
call.succ({
|
||||||
|
clientId,
|
||||||
|
roomId: call.req.roomId || 'default',
|
||||||
|
serverInfo: {
|
||||||
|
version: '1.0.0',
|
||||||
|
syncRate: this.config.syncRate || 20
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 服务端状态查询
|
||||||
|
this.server.implementApi('network/ServerStatus', call => {
|
||||||
|
const stats = this.getStats();
|
||||||
|
call.succ({
|
||||||
|
clientCount: stats.clientCount,
|
||||||
|
networkObjectCount: 0, // 这里需要从NetworkRegistry获取
|
||||||
|
uptime: stats.uptime,
|
||||||
|
networkStats: {
|
||||||
|
messagesSent: stats.messagesSent,
|
||||||
|
messagesReceived: stats.messagesReceived,
|
||||||
|
bytesSent: stats.bytesSent,
|
||||||
|
bytesReceived: stats.bytesReceived
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 心跳检测
|
||||||
|
this.server.implementApi('network/Ping', call => {
|
||||||
|
call.succ({
|
||||||
|
serverTimestamp: Date.now(),
|
||||||
|
clientTimestamp: call.req.timestamp
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置消息处理器
|
||||||
|
*/
|
||||||
|
private setupMessageHandlers(): void {
|
||||||
|
if (!this.server) return;
|
||||||
|
|
||||||
|
// 网络消息处理
|
||||||
|
this.server.listenMsg('network/NetworkMessage', msg => {
|
||||||
|
const clientId = this.getClientIdByConnection(msg.conn);
|
||||||
|
if (clientId) {
|
||||||
|
this.stats.messagesReceived++;
|
||||||
|
this.updateClientActivity(clientId);
|
||||||
|
|
||||||
|
// 转换为内部消息格式
|
||||||
|
const networkMessage: NetworkMessage = {
|
||||||
|
type: msg.msg.type,
|
||||||
|
networkId: msg.msg.networkId,
|
||||||
|
data: msg.msg.data,
|
||||||
|
timestamp: msg.msg.timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onMessage?.(networkMessage, clientId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// SyncVar消息处理
|
||||||
|
this.server.listenMsg('network/SyncVar', msg => {
|
||||||
|
const clientId = this.getClientIdByConnection(msg.conn);
|
||||||
|
if (clientId) {
|
||||||
|
this.stats.messagesReceived++;
|
||||||
|
this.updateClientActivity(clientId);
|
||||||
|
|
||||||
|
// 转换并广播给其他客户端
|
||||||
|
const syncVarMessage: MsgSyncVar = msg.msg;
|
||||||
|
this.broadcast({
|
||||||
|
type: 'network/SyncVar',
|
||||||
|
data: syncVarMessage
|
||||||
|
}, clientId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// RPC调用消息处理
|
||||||
|
this.server.listenMsg('network/RpcCall', msg => {
|
||||||
|
const clientId = this.getClientIdByConnection(msg.conn);
|
||||||
|
if (clientId) {
|
||||||
|
this.stats.messagesReceived++;
|
||||||
|
this.updateClientActivity(clientId);
|
||||||
|
|
||||||
|
const rpcMessage: MsgRpcCall = msg.msg;
|
||||||
|
|
||||||
|
if (rpcMessage.isClientRpc) {
|
||||||
|
// 服务端到客户端的RPC,广播给所有客户端
|
||||||
|
this.broadcast({
|
||||||
|
type: 'network/RpcCall',
|
||||||
|
data: rpcMessage
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 客户端到服务端的Command,只在服务端处理
|
||||||
|
const networkMessage: NetworkMessage = {
|
||||||
|
type: 'rpc',
|
||||||
|
networkId: rpcMessage.networkId,
|
||||||
|
data: {
|
||||||
|
componentType: rpcMessage.componentType,
|
||||||
|
methodName: rpcMessage.methodName,
|
||||||
|
args: rpcMessage.args
|
||||||
|
},
|
||||||
|
timestamp: rpcMessage.timestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onMessage?.(networkMessage, clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置连接处理器
|
||||||
|
*/
|
||||||
|
private setupConnectionHandlers(): void {
|
||||||
|
if (!this.server) return;
|
||||||
|
|
||||||
|
// 连接断开处理
|
||||||
|
this.server.flows.postDisconnectFlow.push(conn => {
|
||||||
|
const clientId = this.getClientIdByConnection(conn);
|
||||||
|
if (clientId) {
|
||||||
|
this.clients.delete(clientId);
|
||||||
|
logger.info(`客户端 ${clientId} 断开连接`);
|
||||||
|
|
||||||
|
// 通知其他客户端
|
||||||
|
this.broadcast({
|
||||||
|
type: 'network/ClientDisconnected',
|
||||||
|
data: { clientId, reason: 'disconnected' }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通知上层
|
||||||
|
this.onClientDisconnected?.(clientId, 'disconnected');
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据连接对象获取客户端ID
|
||||||
|
*/
|
||||||
|
private getClientIdByConnection(conn: any): number | null {
|
||||||
|
for (const [clientId, client] of this.clients) {
|
||||||
|
if (client.connection === conn) {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新客户端活跃时间
|
||||||
|
*/
|
||||||
|
private updateClientActivity(clientId: number): void {
|
||||||
|
const client = this.clients.get(clientId);
|
||||||
|
if (client) {
|
||||||
|
client.lastActivity = Date.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
338
packages/network/src/transport/TsrpcTransport.ts
Normal file
338
packages/network/src/transport/TsrpcTransport.ts
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
/**
|
||||||
|
* TSRPC 传输管理器
|
||||||
|
*
|
||||||
|
* 统一管理TSRPC服务端和客户端,提供通用的传输接口
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createLogger } from '@esengine/ecs-framework';
|
||||||
|
import { TsrpcServer } from './TsrpcServer';
|
||||||
|
import { TsrpcClient } from './TsrpcClient';
|
||||||
|
import { NetworkConfig, NetworkMessage, NetworkSide } from '../types/NetworkTypes';
|
||||||
|
|
||||||
|
const logger = createLogger('TsrpcTransport');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传输事件处理器
|
||||||
|
*/
|
||||||
|
export interface TransportEventHandlers {
|
||||||
|
/** 连接建立 */
|
||||||
|
onConnected?: () => void;
|
||||||
|
/** 连接断开 */
|
||||||
|
onDisconnected?: (reason?: string) => void;
|
||||||
|
/** 客户端连接(仅服务端) */
|
||||||
|
onClientConnected?: (clientId: number) => void;
|
||||||
|
/** 客户端断开(仅服务端) */
|
||||||
|
onClientDisconnected?: (clientId: number, reason?: string) => void;
|
||||||
|
/** 收到消息 */
|
||||||
|
onMessage?: (message: NetworkMessage, fromClientId?: number) => void;
|
||||||
|
/** 发生错误 */
|
||||||
|
onError?: (error: Error) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TSRPC传输层管理器
|
||||||
|
*/
|
||||||
|
export class TsrpcTransport {
|
||||||
|
private server: TsrpcServer | null = null;
|
||||||
|
private client: TsrpcClient | null = null;
|
||||||
|
private networkSide: NetworkSide = 'client';
|
||||||
|
private config: NetworkConfig;
|
||||||
|
private eventHandlers: TransportEventHandlers = {};
|
||||||
|
|
||||||
|
constructor(config: NetworkConfig) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动服务端
|
||||||
|
*/
|
||||||
|
public async startServer(): Promise<void> {
|
||||||
|
if (this.server) {
|
||||||
|
throw new Error('服务端已经在运行中');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.networkSide = 'server';
|
||||||
|
this.server = new TsrpcServer(this.config);
|
||||||
|
|
||||||
|
// 设置服务端事件处理器
|
||||||
|
this.server.onClientConnected = (clientId) => {
|
||||||
|
logger.info(`客户端 ${clientId} 已连接`);
|
||||||
|
this.eventHandlers.onClientConnected?.(clientId);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.server.onClientDisconnected = (clientId, reason) => {
|
||||||
|
logger.info(`客户端 ${clientId} 已断开: ${reason}`);
|
||||||
|
this.eventHandlers.onClientDisconnected?.(clientId, reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.server.onMessage = (message, fromClientId) => {
|
||||||
|
this.eventHandlers.onMessage?.(message, fromClientId);
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.server.start();
|
||||||
|
logger.info('TSRPC服务端已启动');
|
||||||
|
this.eventHandlers.onConnected?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接到服务端
|
||||||
|
*/
|
||||||
|
public async connectToServer(): Promise<void> {
|
||||||
|
if (this.client) {
|
||||||
|
throw new Error('客户端已经连接或正在连接中');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.networkSide = 'client';
|
||||||
|
this.client = new TsrpcClient(this.config);
|
||||||
|
|
||||||
|
// 设置客户端事件处理器
|
||||||
|
this.client.onConnected = () => {
|
||||||
|
logger.info('已连接到服务端');
|
||||||
|
this.eventHandlers.onConnected?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.client.onDisconnected = (reason) => {
|
||||||
|
logger.info(`已断开连接: ${reason}`);
|
||||||
|
this.eventHandlers.onDisconnected?.(reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.client.onMessage = (message) => {
|
||||||
|
this.eventHandlers.onMessage?.(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.client.onError = (error) => {
|
||||||
|
logger.error('客户端错误:', error);
|
||||||
|
this.eventHandlers.onError?.(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.client.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开连接/停止服务端
|
||||||
|
*/
|
||||||
|
public async disconnect(): Promise<void> {
|
||||||
|
if (this.server) {
|
||||||
|
await this.server.stop();
|
||||||
|
this.server = null;
|
||||||
|
logger.info('TSRPC服务端已停止');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.client) {
|
||||||
|
await this.client.disconnect();
|
||||||
|
this.client = null;
|
||||||
|
logger.info('TSRPC客户端已断开');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventHandlers.onDisconnected?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
*/
|
||||||
|
public async sendMessage(message: NetworkMessage, targetClientId?: number): Promise<void> {
|
||||||
|
if (this.networkSide === 'server' && this.server) {
|
||||||
|
// 服务端模式:发送给指定客户端或广播
|
||||||
|
if (targetClientId) {
|
||||||
|
this.server.sendToClient(targetClientId, {
|
||||||
|
type: 'network/NetworkMessage',
|
||||||
|
data: message
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.server.broadcast({
|
||||||
|
type: 'network/NetworkMessage',
|
||||||
|
data: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (this.networkSide === 'client' && this.client) {
|
||||||
|
// 客户端模式:发送给服务端
|
||||||
|
await this.client.sendMessage(message);
|
||||||
|
} else {
|
||||||
|
throw new Error('传输层未初始化或状态错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送SyncVar消息
|
||||||
|
*/
|
||||||
|
public async sendSyncVar(
|
||||||
|
networkId: number,
|
||||||
|
componentType: string,
|
||||||
|
propertyName: string,
|
||||||
|
value: any,
|
||||||
|
targetClientId?: number
|
||||||
|
): Promise<void> {
|
||||||
|
if (this.networkSide === 'server' && this.server) {
|
||||||
|
const message = {
|
||||||
|
type: 'network/SyncVar',
|
||||||
|
data: {
|
||||||
|
networkId,
|
||||||
|
componentType,
|
||||||
|
propertyName,
|
||||||
|
value,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (targetClientId) {
|
||||||
|
this.server.sendToClient(targetClientId, message);
|
||||||
|
} else {
|
||||||
|
this.server.broadcast(message);
|
||||||
|
}
|
||||||
|
} else if (this.networkSide === 'client' && this.client) {
|
||||||
|
await this.client.sendSyncVar(networkId, componentType, propertyName, value);
|
||||||
|
} else {
|
||||||
|
throw new Error('传输层未初始化或状态错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送RPC消息
|
||||||
|
*/
|
||||||
|
public async sendRpcCall(
|
||||||
|
networkId: number,
|
||||||
|
componentType: string,
|
||||||
|
methodName: string,
|
||||||
|
args: any[],
|
||||||
|
isClientRpc: boolean,
|
||||||
|
targetClientId?: number
|
||||||
|
): Promise<void> {
|
||||||
|
if (this.networkSide === 'server' && this.server) {
|
||||||
|
const message = {
|
||||||
|
type: 'network/RpcCall',
|
||||||
|
data: {
|
||||||
|
networkId,
|
||||||
|
componentType,
|
||||||
|
methodName,
|
||||||
|
args,
|
||||||
|
isClientRpc,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (targetClientId) {
|
||||||
|
this.server.sendToClient(targetClientId, message);
|
||||||
|
} else {
|
||||||
|
this.server.broadcast(message);
|
||||||
|
}
|
||||||
|
} else if (this.networkSide === 'client' && this.client) {
|
||||||
|
await this.client.sendRpcCall(networkId, componentType, methodName, args, isClientRpc);
|
||||||
|
} else {
|
||||||
|
throw new Error('传输层未初始化或状态错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接状态
|
||||||
|
*/
|
||||||
|
public isConnected(): boolean {
|
||||||
|
if (this.networkSide === 'server' && this.server) {
|
||||||
|
return true; // 服务端启动即为连接状态
|
||||||
|
} else if (this.networkSide === 'client' && this.client) {
|
||||||
|
return this.client.isConnected();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取网络端类型
|
||||||
|
*/
|
||||||
|
public getNetworkSide(): NetworkSide {
|
||||||
|
return this.networkSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端ID(仅客户端模式)
|
||||||
|
*/
|
||||||
|
public getClientId(): number {
|
||||||
|
if (this.networkSide === 'client' && this.client) {
|
||||||
|
return this.client.getClientId();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接的客户端列表(仅服务端模式)
|
||||||
|
*/
|
||||||
|
public getConnectedClients(): number[] {
|
||||||
|
if (this.networkSide === 'server' && this.server) {
|
||||||
|
return this.server.getConnectedClients();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端数量(仅服务端模式)
|
||||||
|
*/
|
||||||
|
public getClientCount(): number {
|
||||||
|
if (this.networkSide === 'server' && this.server) {
|
||||||
|
return this.server.getClientCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计信息
|
||||||
|
*/
|
||||||
|
public getStats() {
|
||||||
|
if (this.networkSide === 'server' && this.server) {
|
||||||
|
return this.server.getStats();
|
||||||
|
} else if (this.networkSide === 'client' && this.client) {
|
||||||
|
const clientStats = this.client.getStats();
|
||||||
|
return {
|
||||||
|
messagesSent: clientStats.messagesSent,
|
||||||
|
messagesReceived: clientStats.messagesReceived,
|
||||||
|
bytesSent: clientStats.bytesSent,
|
||||||
|
bytesReceived: clientStats.bytesReceived,
|
||||||
|
clientCount: 0,
|
||||||
|
uptime: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
messagesSent: 0,
|
||||||
|
messagesReceived: 0,
|
||||||
|
bytesSent: 0,
|
||||||
|
bytesReceived: 0,
|
||||||
|
clientCount: 0,
|
||||||
|
uptime: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询服务端状态(仅客户端模式)
|
||||||
|
*/
|
||||||
|
public async getServerStatus() {
|
||||||
|
if (this.networkSide === 'client' && this.client) {
|
||||||
|
return await this.client.getServerStatus();
|
||||||
|
}
|
||||||
|
throw new Error('只能在客户端模式下查询服务端状态');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送心跳(仅客户端模式)
|
||||||
|
*/
|
||||||
|
public async ping(): Promise<number> {
|
||||||
|
if (this.networkSide === 'client' && this.client) {
|
||||||
|
return await this.client.ping();
|
||||||
|
}
|
||||||
|
throw new Error('只能在客户端模式下发送心跳');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置事件处理器
|
||||||
|
*/
|
||||||
|
public setEventHandlers(handlers: TransportEventHandlers): void {
|
||||||
|
this.eventHandlers = { ...this.eventHandlers, ...handlers };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单个事件处理器
|
||||||
|
*/
|
||||||
|
public on<K extends keyof TransportEventHandlers>(
|
||||||
|
event: K,
|
||||||
|
handler: TransportEventHandlers[K]
|
||||||
|
): void {
|
||||||
|
this.eventHandlers[event] = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
packages/network/src/transport/index.ts
Normal file
9
packages/network/src/transport/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* 传输层模块导出
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { TsrpcTransport } from './TsrpcTransport';
|
||||||
|
export { TsrpcServer } from './TsrpcServer';
|
||||||
|
export { TsrpcClient } from './TsrpcClient';
|
||||||
|
export * from './protocols/NetworkProtocols';
|
||||||
|
export { serviceProto, ServiceType } from './protocols/serviceProto';
|
||||||
184
packages/network/src/transport/protocols/NetworkProtocols.ts
Normal file
184
packages/network/src/transport/protocols/NetworkProtocols.ts
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* 网络库 TSRPC 协议定义
|
||||||
|
* 定义所有网络消息的类型和结构
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接请求
|
||||||
|
*/
|
||||||
|
export interface ReqJoinRoom {
|
||||||
|
/** 房间ID,可选 */
|
||||||
|
roomId?: string;
|
||||||
|
/** 客户端信息 */
|
||||||
|
clientInfo?: {
|
||||||
|
/** 客户端版本 */
|
||||||
|
version: string;
|
||||||
|
/** 客户端平台 */
|
||||||
|
platform: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接响应
|
||||||
|
*/
|
||||||
|
export interface ResJoinRoom {
|
||||||
|
/** 分配的客户端ID */
|
||||||
|
clientId: number;
|
||||||
|
/** 房间ID */
|
||||||
|
roomId: string;
|
||||||
|
/** 服务端信息 */
|
||||||
|
serverInfo: {
|
||||||
|
/** 服务端版本 */
|
||||||
|
version: string;
|
||||||
|
/** 同步频率 */
|
||||||
|
syncRate: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络消息广播
|
||||||
|
*/
|
||||||
|
export interface MsgNetworkMessage {
|
||||||
|
/** 消息类型 */
|
||||||
|
type: 'syncvar' | 'rpc';
|
||||||
|
/** 网络对象ID */
|
||||||
|
networkId: number;
|
||||||
|
/** 消息数据 */
|
||||||
|
data: any;
|
||||||
|
/** 时间戳 */
|
||||||
|
timestamp: number;
|
||||||
|
/** 发送者客户端ID */
|
||||||
|
senderId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SyncVar 同步消息
|
||||||
|
*/
|
||||||
|
export interface MsgSyncVar {
|
||||||
|
/** 网络对象ID */
|
||||||
|
networkId: number;
|
||||||
|
/** 组件类型名 */
|
||||||
|
componentType: string;
|
||||||
|
/** 属性名 */
|
||||||
|
propertyName: string;
|
||||||
|
/** 新的属性值 */
|
||||||
|
value: any;
|
||||||
|
/** 时间戳 */
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC 调用消息
|
||||||
|
*/
|
||||||
|
export interface MsgRpcCall {
|
||||||
|
/** 网络对象ID */
|
||||||
|
networkId: number;
|
||||||
|
/** 组件类型名 */
|
||||||
|
componentType: string;
|
||||||
|
/** 方法名 */
|
||||||
|
methodName: string;
|
||||||
|
/** 参数 */
|
||||||
|
args: any[];
|
||||||
|
/** 是否为客户端RPC */
|
||||||
|
isClientRpc: boolean;
|
||||||
|
/** 时间戳 */
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络对象生成通知
|
||||||
|
*/
|
||||||
|
export interface MsgNetworkObjectSpawn {
|
||||||
|
/** 网络对象ID */
|
||||||
|
networkId: number;
|
||||||
|
/** 实体名称 */
|
||||||
|
entityName: string;
|
||||||
|
/** 所有者客户端ID */
|
||||||
|
ownerId: number;
|
||||||
|
/** 是否拥有权威 */
|
||||||
|
hasAuthority: boolean;
|
||||||
|
/** 初始组件数据 */
|
||||||
|
components: Array<{
|
||||||
|
/** 组件类型 */
|
||||||
|
type: string;
|
||||||
|
/** 组件数据 */
|
||||||
|
data: any;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络对象销毁通知
|
||||||
|
*/
|
||||||
|
export interface MsgNetworkObjectDespawn {
|
||||||
|
/** 网络对象ID */
|
||||||
|
networkId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端断开连接通知
|
||||||
|
*/
|
||||||
|
export interface MsgClientDisconnected {
|
||||||
|
/** 断开连接的客户端ID */
|
||||||
|
clientId: number;
|
||||||
|
/** 断开原因 */
|
||||||
|
reason?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权威转移通知
|
||||||
|
*/
|
||||||
|
export interface MsgAuthorityChange {
|
||||||
|
/** 网络对象ID */
|
||||||
|
networkId: number;
|
||||||
|
/** 新的权威所有者ID */
|
||||||
|
newOwnerId: number;
|
||||||
|
/** 是否拥有权威 */
|
||||||
|
hasAuthority: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端状态查询请求
|
||||||
|
*/
|
||||||
|
export interface ReqServerStatus {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端状态响应
|
||||||
|
*/
|
||||||
|
export interface ResServerStatus {
|
||||||
|
/** 连接的客户端数量 */
|
||||||
|
clientCount: number;
|
||||||
|
/** 网络对象数量 */
|
||||||
|
networkObjectCount: number;
|
||||||
|
/** 服务器运行时间(毫秒) */
|
||||||
|
uptime: number;
|
||||||
|
/** 网络统计 */
|
||||||
|
networkStats: {
|
||||||
|
/** 发送的消息数 */
|
||||||
|
messagesSent: number;
|
||||||
|
/** 接收的消息数 */
|
||||||
|
messagesReceived: number;
|
||||||
|
/** 发送的字节数 */
|
||||||
|
bytesSent: number;
|
||||||
|
/** 接收的字节数 */
|
||||||
|
bytesReceived: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 心跳请求
|
||||||
|
*/
|
||||||
|
export interface ReqPing {
|
||||||
|
/** 客户端时间戳 */
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 心跳响应
|
||||||
|
*/
|
||||||
|
export interface ResPing {
|
||||||
|
/** 服务端时间戳 */
|
||||||
|
serverTimestamp: number;
|
||||||
|
/** 客户端时间戳(回传) */
|
||||||
|
clientTimestamp: number;
|
||||||
|
}
|
||||||
108
packages/network/src/transport/protocols/serviceProto.ts
Normal file
108
packages/network/src/transport/protocols/serviceProto.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* TSRPC 服务协议定义
|
||||||
|
* 定义API调用和消息类型
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ServiceProto } from 'tsrpc';
|
||||||
|
import {
|
||||||
|
ReqJoinRoom, ResJoinRoom,
|
||||||
|
ReqServerStatus, ResServerStatus,
|
||||||
|
ReqPing, ResPing,
|
||||||
|
MsgNetworkMessage,
|
||||||
|
MsgSyncVar,
|
||||||
|
MsgRpcCall,
|
||||||
|
MsgNetworkObjectSpawn,
|
||||||
|
MsgNetworkObjectDespawn,
|
||||||
|
MsgClientDisconnected,
|
||||||
|
MsgAuthorityChange
|
||||||
|
} from './NetworkProtocols';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络服务协议
|
||||||
|
* 定义所有可用的API和消息类型
|
||||||
|
*/
|
||||||
|
export const serviceProto: ServiceProto<ServiceType> = {
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "network/JoinRoom",
|
||||||
|
"type": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "network/ServerStatus",
|
||||||
|
"type": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "network/Ping",
|
||||||
|
"type": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "network/NetworkMessage",
|
||||||
|
"type": "msg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "network/SyncVar",
|
||||||
|
"type": "msg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "network/RpcCall",
|
||||||
|
"type": "msg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "network/NetworkObjectSpawn",
|
||||||
|
"type": "msg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "network/NetworkObjectDespawn",
|
||||||
|
"type": "msg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "network/ClientDisconnected",
|
||||||
|
"type": "msg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "network/AuthorityChange",
|
||||||
|
"type": "msg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务类型定义
|
||||||
|
* 用于类型安全的API调用和消息发送
|
||||||
|
*/
|
||||||
|
export interface ServiceType {
|
||||||
|
api: {
|
||||||
|
"network/JoinRoom": {
|
||||||
|
req: ReqJoinRoom;
|
||||||
|
res: ResJoinRoom;
|
||||||
|
};
|
||||||
|
"network/ServerStatus": {
|
||||||
|
req: ReqServerStatus;
|
||||||
|
res: ResServerStatus;
|
||||||
|
};
|
||||||
|
"network/Ping": {
|
||||||
|
req: ReqPing;
|
||||||
|
res: ResPing;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
msg: {
|
||||||
|
"network/NetworkMessage": MsgNetworkMessage;
|
||||||
|
"network/SyncVar": MsgSyncVar;
|
||||||
|
"network/RpcCall": MsgRpcCall;
|
||||||
|
"network/NetworkObjectSpawn": MsgNetworkObjectSpawn;
|
||||||
|
"network/NetworkObjectDespawn": MsgNetworkObjectDespawn;
|
||||||
|
"network/ClientDisconnected": MsgClientDisconnected;
|
||||||
|
"network/AuthorityChange": MsgAuthorityChange;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
/**
|
|
||||||
* 消息类型枚举
|
|
||||||
*/
|
|
||||||
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>;
|
|
||||||
}
|
|
||||||
@@ -1,306 +1,162 @@
|
|||||||
/**
|
/**
|
||||||
* 网络库类型定义
|
* 网络库核心类型定义
|
||||||
*
|
|
||||||
* 基于核心库的类型系统,为网络功能提供特定的类型约束
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType, IComponent, Component } from '@esengine/ecs-framework';
|
|
||||||
import { SerializedData } from '../Serialization/SerializationTypes';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络同步组件接口
|
|
||||||
* 扩展核心组件接口,添加网络同步功能
|
|
||||||
*/
|
|
||||||
export interface INetworkSyncable extends IComponent {
|
|
||||||
/** 内部SyncVar ID */
|
|
||||||
_syncVarId?: string;
|
|
||||||
/** 是否禁用SyncVar监听 */
|
|
||||||
_syncVarDisabled?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取网络同步状态
|
|
||||||
*/
|
|
||||||
getNetworkState(): Uint8Array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用网络状态
|
|
||||||
*/
|
|
||||||
applyNetworkState(data: Uint8Array): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取脏字段列表
|
|
||||||
*/
|
|
||||||
getDirtyFields(): number[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记为干净状态
|
|
||||||
*/
|
|
||||||
markClean(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记字段为脏状态
|
|
||||||
*/
|
|
||||||
markFieldDirty(fieldNumber: number): void;
|
|
||||||
|
|
||||||
/** 允许通过字符串键访问属性 */
|
|
||||||
[propertyKey: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络组件构造函数类型
|
|
||||||
* 基于核心库的ComponentType,添加网络特性约束
|
|
||||||
*/
|
|
||||||
export type NetworkComponentType<T extends Component & INetworkSyncable = Component & INetworkSyncable> = ComponentType<T>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar值类型约束
|
|
||||||
* 定义可以被SyncVar同步的值类型
|
|
||||||
*/
|
|
||||||
export type SyncVarValue =
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| null
|
|
||||||
| undefined
|
|
||||||
| Date
|
|
||||||
| Uint8Array
|
|
||||||
| Record<string, unknown>
|
|
||||||
| unknown[];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SyncVar元数据接口
|
* 网络连接状态
|
||||||
* 用于类型安全的SyncVar配置
|
|
||||||
*/
|
*/
|
||||||
export interface ISyncVarMetadata<T = SyncVarValue> {
|
export type NetworkConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
|
||||||
/** 属性名 */
|
|
||||||
propertyKey: string;
|
|
||||||
/** 字段编号 */
|
|
||||||
fieldNumber: number;
|
|
||||||
/** 配置选项 */
|
|
||||||
options: ISyncVarOptions<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SyncVar选项接口
|
* 网络端类型
|
||||||
*/
|
*/
|
||||||
export interface ISyncVarOptions<T = SyncVarValue> {
|
export type NetworkSide = 'client' | 'server' | 'host';
|
||||||
/** Hook回调函数名 */
|
|
||||||
hook?: string;
|
|
||||||
/** 是否仅权威端可修改 */
|
|
||||||
authorityOnly?: boolean;
|
|
||||||
/** 节流时间(毫秒) */
|
|
||||||
throttleMs?: number;
|
|
||||||
/** 自定义序列化函数 */
|
|
||||||
serializer?: (value: T) => Uint8Array;
|
|
||||||
/** 自定义反序列化函数 */
|
|
||||||
deserializer?: (data: Uint8Array) => T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组件序列化目标类型
|
* 网络消息类型
|
||||||
* 约束可以被序列化的组件类型
|
|
||||||
*/
|
*/
|
||||||
export type SerializationTarget = Component & INetworkSyncable & {
|
export interface NetworkMessage {
|
||||||
readonly constructor: NetworkComponentType;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息数据约束类型
|
|
||||||
* 定义网络消息中可以传输的数据类型
|
|
||||||
*/
|
|
||||||
export type MessageData =
|
|
||||||
| Record<string, unknown>
|
|
||||||
| Uint8Array
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 基础网络消息接口
|
|
||||||
* 为SyncVar等网络同步功能提供消息接口
|
|
||||||
*/
|
|
||||||
export interface IBasicNetworkMessage<TData extends MessageData = MessageData> {
|
|
||||||
/** 消息类型 */
|
/** 消息类型 */
|
||||||
readonly messageType: number;
|
type: string;
|
||||||
|
/** 网络对象ID */
|
||||||
|
networkId: number;
|
||||||
/** 消息数据 */
|
/** 消息数据 */
|
||||||
readonly data: TData;
|
data: any;
|
||||||
/** 发送者ID */
|
|
||||||
senderId?: string;
|
|
||||||
/** 消息时间戳 */
|
|
||||||
timestamp: number;
|
|
||||||
/** 消息序列号 */
|
|
||||||
sequence?: number;
|
|
||||||
|
|
||||||
/** 序列化消息 */
|
|
||||||
serialize(): Uint8Array;
|
|
||||||
/** 反序列化消息 */
|
|
||||||
deserialize(data: Uint8Array): void;
|
|
||||||
/** 获取消息大小 */
|
|
||||||
getSize(): number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SyncVar更新数据接口
|
|
||||||
*/
|
|
||||||
export interface ISyncVarFieldUpdate {
|
|
||||||
/** 字段编号 */
|
|
||||||
fieldNumber: number;
|
|
||||||
/** 属性名 */
|
|
||||||
propertyKey: string;
|
|
||||||
/** 新值 */
|
|
||||||
newValue: SyncVarValue;
|
|
||||||
/** 旧值 */
|
|
||||||
oldValue: SyncVarValue;
|
|
||||||
/** 时间戳 */
|
/** 时间戳 */
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
/** 是否需要权限 */
|
|
||||||
authorityOnly?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 快照数据接口
|
* 同步变量消息
|
||||||
*/
|
*/
|
||||||
export interface ISnapshotData {
|
export interface SyncVarMessage extends NetworkMessage {
|
||||||
|
type: 'syncvar';
|
||||||
/** 组件类型名 */
|
/** 组件类型名 */
|
||||||
componentType: string;
|
componentType: string;
|
||||||
/** 序列化数据 */
|
/** 属性名 */
|
||||||
data: SerializedData;
|
propertyName: string;
|
||||||
/** 组件ID */
|
/** 属性值 */
|
||||||
componentId: number;
|
value: any;
|
||||||
/** 是否启用 */
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类型安全的组件工厂接口
|
* RPC消息
|
||||||
*/
|
*/
|
||||||
export interface IComponentFactory {
|
export interface RpcMessage extends NetworkMessage {
|
||||||
/** 创建组件实例 */
|
type: 'rpc';
|
||||||
create<T extends Component & INetworkSyncable>(
|
/** RPC方法名 */
|
||||||
componentType: NetworkComponentType<T>,
|
methodName: string;
|
||||||
...args: unknown[]
|
/** RPC参数 */
|
||||||
): T;
|
args: any[];
|
||||||
|
/** 是否为客户端RPC */
|
||||||
/** 检查组件类型是否已注册 */
|
isClientRpc: boolean;
|
||||||
isRegistered<T extends Component & INetworkSyncable>(
|
|
||||||
componentType: NetworkComponentType<T>
|
|
||||||
): boolean;
|
|
||||||
|
|
||||||
/** 获取组件类型名称 */
|
|
||||||
getTypeName<T extends Component & INetworkSyncable>(
|
|
||||||
componentType: NetworkComponentType<T>
|
|
||||||
): string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络性能指标接口
|
* 网络配置选项
|
||||||
*/
|
*/
|
||||||
export interface INetworkPerformanceMetrics {
|
export interface NetworkConfig {
|
||||||
/** RTT(往返时间) */
|
/** 服务器端口 */
|
||||||
rtt: number;
|
port?: number;
|
||||||
/** 带宽利用率 */
|
/** 服务器地址 */
|
||||||
bandwidth: number;
|
host?: string;
|
||||||
/** 丢包率 */
|
/** 房间ID */
|
||||||
packetLoss: number;
|
roomId?: string;
|
||||||
/** 抖动 */
|
/** 最大连接数 */
|
||||||
jitter: number;
|
maxConnections?: number;
|
||||||
/** 连接质量评分 */
|
/** 同步频率 (Hz) */
|
||||||
quality: number;
|
syncRate?: number;
|
||||||
/** 最后更新时间 */
|
/** 是否启用压缩 */
|
||||||
lastUpdate: number;
|
compression?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 序列化上下文接口
|
* 网络统计信息
|
||||||
* 为序列化过程提供上下文信息
|
|
||||||
*/
|
*/
|
||||||
export interface ISerializationContext {
|
export interface NetworkStats {
|
||||||
/** 目标组件类型 */
|
/** 连接数 */
|
||||||
componentType: string;
|
connectionCount: number;
|
||||||
/** 序列化选项 */
|
/** 发送的字节数 */
|
||||||
options?: {
|
bytesSent: number;
|
||||||
enableValidation?: boolean;
|
/** 接收的字节数 */
|
||||||
compressionLevel?: number;
|
bytesReceived: number;
|
||||||
};
|
/** 发送的消息数 */
|
||||||
|
messagesSent: number;
|
||||||
|
/** 接收的消息数 */
|
||||||
|
messagesReceived: number;
|
||||||
|
/** 平均延迟 (ms) */
|
||||||
|
averageLatency: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 类型守卫函数类型定义
|
* 网络事件处理器
|
||||||
*/
|
*/
|
||||||
export type TypeGuard<T> = (value: unknown) => value is T;
|
export interface NetworkEventHandlers {
|
||||||
|
/** 连接建立 */
|
||||||
/**
|
onConnected: () => void;
|
||||||
* 常用类型守卫函数
|
/** 连接断开 */
|
||||||
*/
|
onDisconnected: (reason?: string) => void;
|
||||||
export const TypeGuards = {
|
/** 客户端加入 */
|
||||||
/** 检查是否为SyncVar值 */
|
onClientConnected: (clientId: number) => void;
|
||||||
isSyncVarValue: ((value: unknown): value is SyncVarValue => {
|
/** 客户端离开 */
|
||||||
return value === null ||
|
onClientDisconnected: (clientId: number, reason?: string) => void;
|
||||||
value === undefined ||
|
/** 发生错误 */
|
||||||
typeof value === 'string' ||
|
onError: (error: Error) => void;
|
||||||
typeof value === 'number' ||
|
|
||||||
typeof value === 'boolean' ||
|
|
||||||
value instanceof Date ||
|
|
||||||
value instanceof Uint8Array ||
|
|
||||||
(typeof value === 'object' && value !== null);
|
|
||||||
}) as TypeGuard<SyncVarValue>,
|
|
||||||
|
|
||||||
/** 检查是否为网络消息数据 */
|
|
||||||
isMessageData: ((value: unknown): value is MessageData => {
|
|
||||||
return value === null ||
|
|
||||||
typeof value === 'string' ||
|
|
||||||
typeof value === 'number' ||
|
|
||||||
typeof value === 'boolean' ||
|
|
||||||
value instanceof Uint8Array ||
|
|
||||||
(typeof value === 'object' && value !== null && !(value instanceof Date));
|
|
||||||
}) as TypeGuard<MessageData>,
|
|
||||||
|
|
||||||
/** 检查是否为序列化目标 */
|
|
||||||
isSerializationTarget: ((value: unknown): value is SerializationTarget => {
|
|
||||||
return typeof value === 'object' &&
|
|
||||||
value !== null &&
|
|
||||||
'getNetworkState' in value &&
|
|
||||||
'applyNetworkState' in value &&
|
|
||||||
typeof (value as { getNetworkState?: unknown }).getNetworkState === 'function';
|
|
||||||
}) as TypeGuard<SerializationTarget>
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络错误类型枚举
|
|
||||||
*/
|
|
||||||
export enum NetworkErrorType {
|
|
||||||
CONNECTION_FAILED = 'CONNECTION_FAILED',
|
|
||||||
SERIALIZATION_FAILED = 'SERIALIZATION_FAILED',
|
|
||||||
DESERIALIZATION_FAILED = 'DESERIALIZATION_FAILED',
|
|
||||||
SYNC_VAR_ERROR = 'SYNC_VAR_ERROR',
|
|
||||||
MESSAGE_TIMEOUT = 'MESSAGE_TIMEOUT',
|
|
||||||
INVALID_DATA = 'INVALID_DATA',
|
|
||||||
PERMISSION_DENIED = 'PERMISSION_DENIED'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络错误接口
|
* 网络行为接口
|
||||||
|
* 所有网络组件都需要实现此接口
|
||||||
*/
|
*/
|
||||||
export interface INetworkError extends Error {
|
export interface INetworkBehaviour {
|
||||||
readonly type: NetworkErrorType;
|
/** 网络身份组件引用 */
|
||||||
readonly code?: string | number;
|
networkIdentity: any | null;
|
||||||
readonly context?: Record<string, unknown>;
|
/** 是否拥有权威 */
|
||||||
readonly timestamp: number;
|
hasAuthority: boolean;
|
||||||
|
/** 是否为本地玩家 */
|
||||||
|
isLocalPlayer: boolean;
|
||||||
|
/** 是否在服务端 */
|
||||||
|
isServer: boolean;
|
||||||
|
/** 是否在客户端 */
|
||||||
|
isClient: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建类型安全的网络错误
|
* 同步变量元数据
|
||||||
*/
|
*/
|
||||||
export function createNetworkError(
|
export interface SyncVarMetadata {
|
||||||
type: NetworkErrorType,
|
/** 属性名 */
|
||||||
message: string,
|
propertyName: string;
|
||||||
context?: Record<string, unknown>
|
/** 是否仅权威端可修改 */
|
||||||
): INetworkError {
|
authorityOnly: boolean;
|
||||||
const error = new Error(message) as INetworkError;
|
/** 变化回调函数名 */
|
||||||
Object.defineProperty(error, 'type', { value: type, writable: false });
|
onChanged?: string;
|
||||||
Object.defineProperty(error, 'context', { value: context, writable: false });
|
}
|
||||||
Object.defineProperty(error, 'timestamp', { value: Date.now(), writable: false });
|
|
||||||
return error;
|
/**
|
||||||
|
* RPC元数据
|
||||||
|
*/
|
||||||
|
export interface RpcMetadata {
|
||||||
|
/** 方法名 */
|
||||||
|
methodName: string;
|
||||||
|
/** 是否为客户端RPC */
|
||||||
|
isClientRpc: boolean;
|
||||||
|
/** 是否需要权威验证 */
|
||||||
|
requiresAuthority: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络连接信息
|
||||||
|
*/
|
||||||
|
export interface NetworkConnection {
|
||||||
|
/** 连接ID */
|
||||||
|
id: number;
|
||||||
|
/** 连接状态 */
|
||||||
|
state: NetworkConnectionState;
|
||||||
|
/** 延迟 (ms) */
|
||||||
|
latency: number;
|
||||||
|
/** 最后活跃时间 */
|
||||||
|
lastActivity: number;
|
||||||
}
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* 网络库类型导出
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 核心类型
|
|
||||||
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';
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
import { NetworkRole } from '../src/NetworkRole';
|
|
||||||
|
|
||||||
// 模拟Component基类
|
|
||||||
class Component {
|
|
||||||
public update(): void {
|
|
||||||
// 默认空实现
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟INetworkSyncable接口
|
|
||||||
interface INetworkSyncable {
|
|
||||||
getNetworkState(): Uint8Array;
|
|
||||||
applyNetworkState(data: Uint8Array): void;
|
|
||||||
getDirtyFields(): number[];
|
|
||||||
markClean(): void;
|
|
||||||
markFieldDirty(fieldNumber: number): void;
|
|
||||||
isFieldDirty(fieldNumber: number): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简化版NetworkComponent用于测试
|
|
||||||
class TestableNetworkComponent extends Component implements INetworkSyncable {
|
|
||||||
private _dirtyFields: Set<number> = new Set();
|
|
||||||
private _fieldTimestamps: Map<number, number> = new Map();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRole(): NetworkRole {
|
|
||||||
// 模拟环境检测,默认返回客户端
|
|
||||||
return NetworkRole.CLIENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isClient(): boolean {
|
|
||||||
return true; // 在测试中简化为始终是客户端
|
|
||||||
}
|
|
||||||
|
|
||||||
public isServer(): boolean {
|
|
||||||
return false; // 在测试中简化为始终不是服务端
|
|
||||||
}
|
|
||||||
|
|
||||||
public onClientUpdate(): void {
|
|
||||||
// 默认空实现
|
|
||||||
}
|
|
||||||
|
|
||||||
public onServerUpdate(): void {
|
|
||||||
// 默认空实现
|
|
||||||
}
|
|
||||||
|
|
||||||
public override update(): void {
|
|
||||||
if (this.isClient()) {
|
|
||||||
this.onClientUpdate();
|
|
||||||
} else if (this.isServer()) {
|
|
||||||
this.onServerUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNetworkState(): Uint8Array {
|
|
||||||
return new Uint8Array([1, 2, 3]); // 模拟数据
|
|
||||||
}
|
|
||||||
|
|
||||||
public applyNetworkState(data: Uint8Array): void {
|
|
||||||
this.markClean();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDirtyFields(): number[] {
|
|
||||||
return Array.from(this._dirtyFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
public markClean(): void {
|
|
||||||
this._dirtyFields.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public markFieldDirty(fieldNumber: number): void {
|
|
||||||
this._dirtyFields.add(fieldNumber);
|
|
||||||
this._fieldTimestamps.set(fieldNumber, Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public isFieldDirty(fieldNumber: number): boolean {
|
|
||||||
return this._dirtyFields.has(fieldNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getFieldTimestamp(fieldNumber: number): number {
|
|
||||||
return this._fieldTimestamps.get(fieldNumber) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDirtyFieldsWithTimestamps(): Map<number, number> {
|
|
||||||
const result = new Map<number, number>();
|
|
||||||
for (const fieldNumber of this._dirtyFields) {
|
|
||||||
result.set(fieldNumber, this._fieldTimestamps.get(fieldNumber) || 0);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestNetworkComponent extends TestableNetworkComponent {
|
|
||||||
public value: number = 0;
|
|
||||||
|
|
||||||
constructor(value: number = 0) {
|
|
||||||
super();
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override onClientUpdate(): void {
|
|
||||||
this.value += 1;
|
|
||||||
this.markFieldDirty(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override onServerUpdate(): void {
|
|
||||||
this.value += 10;
|
|
||||||
this.markFieldDirty(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('NetworkComponent', () => {
|
|
||||||
describe('角色功能', () => {
|
|
||||||
test('应该正确获取角色信息', () => {
|
|
||||||
const component = new TestNetworkComponent();
|
|
||||||
|
|
||||||
expect(component.getRole()).toBe(NetworkRole.CLIENT);
|
|
||||||
expect(component.isClient()).toBe(true);
|
|
||||||
expect(component.isServer()).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('更新逻辑', () => {
|
|
||||||
test('组件应该调用对应的更新方法', () => {
|
|
||||||
const component = new TestNetworkComponent(5);
|
|
||||||
|
|
||||||
component.update();
|
|
||||||
|
|
||||||
expect(component.value).toBe(6); // 5 + 1 (客户端更新)
|
|
||||||
expect(component.getDirtyFields()).toContain(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('脏字段管理', () => {
|
|
||||||
test('应该正确标记和检查脏字段', () => {
|
|
||||||
const component = new TestNetworkComponent();
|
|
||||||
|
|
||||||
expect(component.isFieldDirty(1)).toBe(false);
|
|
||||||
|
|
||||||
component.markFieldDirty(1);
|
|
||||||
|
|
||||||
expect(component.isFieldDirty(1)).toBe(true);
|
|
||||||
expect(component.getDirtyFields()).toContain(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该正确清理脏字段', () => {
|
|
||||||
const component = new TestNetworkComponent();
|
|
||||||
|
|
||||||
component.markFieldDirty(1);
|
|
||||||
component.markFieldDirty(2);
|
|
||||||
|
|
||||||
expect(component.getDirtyFields()).toEqual(expect.arrayContaining([1, 2]));
|
|
||||||
|
|
||||||
component.markClean();
|
|
||||||
|
|
||||||
expect(component.getDirtyFields()).toEqual([]);
|
|
||||||
expect(component.isFieldDirty(1)).toBe(false);
|
|
||||||
expect(component.isFieldDirty(2)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该正确记录字段时间戳', () => {
|
|
||||||
const component = new TestNetworkComponent();
|
|
||||||
const beforeTime = Date.now();
|
|
||||||
|
|
||||||
component.markFieldDirty(1);
|
|
||||||
|
|
||||||
const timestamp = component.getFieldTimestamp(1);
|
|
||||||
const afterTime = Date.now();
|
|
||||||
|
|
||||||
expect(timestamp).toBeGreaterThanOrEqual(beforeTime);
|
|
||||||
expect(timestamp).toBeLessThanOrEqual(afterTime);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该正确获取脏字段和时间戳', () => {
|
|
||||||
const component = new TestNetworkComponent();
|
|
||||||
|
|
||||||
component.markFieldDirty(1);
|
|
||||||
component.markFieldDirty(3);
|
|
||||||
|
|
||||||
const dirtyFieldsWithTimestamps = component.getDirtyFieldsWithTimestamps();
|
|
||||||
|
|
||||||
expect(dirtyFieldsWithTimestamps.size).toBe(2);
|
|
||||||
expect(dirtyFieldsWithTimestamps.has(1)).toBe(true);
|
|
||||||
expect(dirtyFieldsWithTimestamps.has(3)).toBe(true);
|
|
||||||
expect(dirtyFieldsWithTimestamps.get(1)).toBeGreaterThan(0);
|
|
||||||
expect(dirtyFieldsWithTimestamps.get(3)).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('网络状态序列化', () => {
|
|
||||||
test('应该能获取网络状态', () => {
|
|
||||||
const component = new TestNetworkComponent(42);
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
const state = component.getNetworkState();
|
|
||||||
expect(state).toBeInstanceOf(Uint8Array);
|
|
||||||
expect(state.length).toBeGreaterThan(0);
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能应用网络状态', () => {
|
|
||||||
const sourceComponent = new TestNetworkComponent(100);
|
|
||||||
const targetComponent = new TestNetworkComponent(0);
|
|
||||||
|
|
||||||
const networkState = sourceComponent.getNetworkState();
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
targetComponent.applyNetworkState(networkState);
|
|
||||||
}).not.toThrow();
|
|
||||||
|
|
||||||
// 应用状态后应该清理脏字段
|
|
||||||
expect(targetComponent.getDirtyFields()).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import { NetworkManager } from '../src/Core/NetworkManager';
|
|
||||||
import { MessageHandler } from '../src/Messaging/MessageHandler';
|
|
||||||
import { JsonMessage } from '../src/Messaging/NetworkMessage';
|
|
||||||
|
|
||||||
// 测试消息
|
|
||||||
class TestMessage extends JsonMessage<{ text: string }> {
|
|
||||||
public override readonly messageType: number = 1000;
|
|
||||||
|
|
||||||
constructor(text: string = 'test') {
|
|
||||||
super({ text });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('网络核心功能测试', () => {
|
|
||||||
let serverPort: number;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// 每个测试使用不同端口避免冲突
|
|
||||||
serverPort = 8000 + Math.floor(Math.random() * 2000);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
try {
|
|
||||||
// 强制重置NetworkManager实例
|
|
||||||
const manager = (NetworkManager as any).Instance;
|
|
||||||
if (manager) {
|
|
||||||
// 直接重置内部状态
|
|
||||||
manager._isServer = false;
|
|
||||||
manager._isClient = false;
|
|
||||||
manager._server = null;
|
|
||||||
manager._client = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置单例实例
|
|
||||||
(NetworkManager as any)._instance = null;
|
|
||||||
|
|
||||||
// 清理消息处理器
|
|
||||||
MessageHandler.Instance.clear();
|
|
||||||
|
|
||||||
// 短暂等待
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 50));
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('清理时发生错误:', error);
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
describe('NetworkManager', () => {
|
|
||||||
test('应该能启动和停止服务端', async () => {
|
|
||||||
// 启动服务端
|
|
||||||
const startResult = await NetworkManager.StartServer(serverPort);
|
|
||||||
expect(startResult).toBe(true);
|
|
||||||
expect(NetworkManager.isServer).toBe(true);
|
|
||||||
expect(NetworkManager.connectionCount).toBe(0);
|
|
||||||
|
|
||||||
// 停止服务端
|
|
||||||
await NetworkManager.StopServer();
|
|
||||||
expect(NetworkManager.isServer).toBe(false);
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
test('应该能启动和停止客户端', async () => {
|
|
||||||
// 先启动服务端
|
|
||||||
const serverStarted = await NetworkManager.StartServer(serverPort);
|
|
||||||
expect(serverStarted).toBe(true);
|
|
||||||
|
|
||||||
// 等待服务端完全启动
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
|
||||||
|
|
||||||
// 启动客户端
|
|
||||||
const connectResult = await NetworkManager.StartClient(`ws://localhost:${serverPort}`);
|
|
||||||
expect(connectResult).toBe(true);
|
|
||||||
expect(NetworkManager.isClient).toBe(true);
|
|
||||||
|
|
||||||
// 停止客户端
|
|
||||||
await NetworkManager.StopClient();
|
|
||||||
expect(NetworkManager.isClient).toBe(false);
|
|
||||||
}, 10000);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('消息系统', () => {
|
|
||||||
test('应该能注册和处理消息', async () => {
|
|
||||||
let receivedMessage: TestMessage | null = null;
|
|
||||||
|
|
||||||
// 注册消息处理器
|
|
||||||
MessageHandler.Instance.registerHandler(
|
|
||||||
1000,
|
|
||||||
TestMessage,
|
|
||||||
{
|
|
||||||
handle: (message: TestMessage) => {
|
|
||||||
receivedMessage = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 创建测试消息
|
|
||||||
const testMessage = new TestMessage('Hello World');
|
|
||||||
|
|
||||||
// 序列化和反序列化测试
|
|
||||||
const serialized = testMessage.serialize();
|
|
||||||
expect(serialized.length).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
// 模拟消息处理
|
|
||||||
await MessageHandler.Instance.handleRawMessage(serialized);
|
|
||||||
|
|
||||||
// 验证消息被正确处理
|
|
||||||
expect(receivedMessage).not.toBeNull();
|
|
||||||
expect(receivedMessage!.payload!.text).toBe('Hello World');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能处理多个处理器', async () => {
|
|
||||||
let handler1Called = false;
|
|
||||||
let handler2Called = false;
|
|
||||||
|
|
||||||
// 注册多个处理器
|
|
||||||
MessageHandler.Instance.registerHandler(1000, TestMessage, {
|
|
||||||
handle: () => { handler1Called = true; }
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
MessageHandler.Instance.registerHandler(1000, TestMessage, {
|
|
||||||
handle: () => { handler2Called = true; }
|
|
||||||
}, 1);
|
|
||||||
|
|
||||||
// 发送消息
|
|
||||||
const testMessage = new TestMessage('Test');
|
|
||||||
await MessageHandler.Instance.handleMessage(testMessage);
|
|
||||||
|
|
||||||
// 验证两个处理器都被调用
|
|
||||||
expect(handler1Called).toBe(true);
|
|
||||||
expect(handler2Called).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 暂时跳过端到端通信测试,等其他问题修复后再处理
|
|
||||||
describe.skip('端到端通信', () => {
|
|
||||||
test('客户端和服务端应该能相互通信', async () => {
|
|
||||||
// 这个测试有复杂的WebSocket连接同步问题,暂时跳过
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
258
packages/network/tests/NetworkLibrary.test.ts
Normal file
258
packages/network/tests/NetworkLibrary.test.ts
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
/**
|
||||||
|
* 网络库基础功能测试
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { NetworkManager } from '../src/NetworkManager';
|
||||||
|
import { NetworkIdentity } from '../src/NetworkIdentity';
|
||||||
|
import { NetworkBehaviour } from '../src/NetworkBehaviour';
|
||||||
|
import { SyncVar } from '../src/decorators/SyncVar';
|
||||||
|
import { ClientRpc } from '../src/decorators/ClientRpc';
|
||||||
|
import { Command } from '../src/decorators/Command';
|
||||||
|
import { NetworkRegistry } from '../src/core/NetworkRegistry';
|
||||||
|
import { SyncVarManager } from '../src/core/SyncVarManager';
|
||||||
|
import { RpcManager } from '../src/core/RpcManager';
|
||||||
|
|
||||||
|
// 测试用的玩家组件
|
||||||
|
class TestPlayerComponent extends NetworkBehaviour {
|
||||||
|
@SyncVar({ onChanged: 'onHealthChanged' })
|
||||||
|
public health: number = 100;
|
||||||
|
|
||||||
|
@SyncVar()
|
||||||
|
public playerName: string = 'Player';
|
||||||
|
|
||||||
|
public lastHealthChangeValue: number = 0;
|
||||||
|
|
||||||
|
@ClientRpc()
|
||||||
|
public showDamageEffect(damage: number, position: { x: number; y: number }): void {
|
||||||
|
console.log(`显示伤害特效: ${damage} at (${position.x}, ${position.y})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command()
|
||||||
|
public movePlayer(direction: { x: number; y: number }): void {
|
||||||
|
console.log(`移动玩家: (${direction.x}, ${direction.y})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onHealthChanged(oldValue: number, newValue: number): void {
|
||||||
|
this.lastHealthChangeValue = newValue;
|
||||||
|
console.log(`生命值变化: ${oldValue} -> ${newValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟实体类
|
||||||
|
class MockEntity {
|
||||||
|
private components: any[] = [];
|
||||||
|
public name: string = 'TestEntity';
|
||||||
|
|
||||||
|
public addComponent(component: any): void {
|
||||||
|
this.components.push(component);
|
||||||
|
component.entity = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getComponent(componentType: any): any {
|
||||||
|
return this.components.find(c => c instanceof componentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getComponents(): any[] {
|
||||||
|
return this.components;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('网络库基础功能测试', () => {
|
||||||
|
let networkManager: NetworkManager;
|
||||||
|
let entity: MockEntity;
|
||||||
|
let networkIdentity: NetworkIdentity;
|
||||||
|
let playerComponent: TestPlayerComponent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// 重置单例
|
||||||
|
(NetworkManager as any)._instance = null;
|
||||||
|
NetworkRegistry.instance.reset();
|
||||||
|
SyncVarManager.instance.clearPendingChanges();
|
||||||
|
RpcManager.instance.clearPendingCalls();
|
||||||
|
|
||||||
|
// 创建网络管理器
|
||||||
|
networkManager = new NetworkManager();
|
||||||
|
|
||||||
|
// 创建测试实体
|
||||||
|
entity = new MockEntity();
|
||||||
|
networkIdentity = new NetworkIdentity();
|
||||||
|
playerComponent = new TestPlayerComponent();
|
||||||
|
|
||||||
|
entity.addComponent(networkIdentity);
|
||||||
|
entity.addComponent(playerComponent);
|
||||||
|
|
||||||
|
// 手动调用组件初始化以注册网络行为
|
||||||
|
playerComponent.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
networkManager?.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('网络身份管理', () => {
|
||||||
|
test('网络对象注册和查找', () => {
|
||||||
|
const networkId = networkManager.registerNetworkObject(entity);
|
||||||
|
|
||||||
|
expect(networkId).toBeGreaterThan(0);
|
||||||
|
expect(networkIdentity.networkId).toBe(networkId);
|
||||||
|
|
||||||
|
const foundIdentity = NetworkRegistry.instance.find(networkId);
|
||||||
|
expect(foundIdentity).toBe(networkIdentity);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('网络权威设置', () => {
|
||||||
|
networkManager.registerNetworkObject(entity);
|
||||||
|
|
||||||
|
expect(networkIdentity.hasAuthority).toBe(false);
|
||||||
|
expect(playerComponent.hasAuthority).toBe(false);
|
||||||
|
|
||||||
|
networkIdentity.setAuthority(true, 1);
|
||||||
|
expect(networkIdentity.hasAuthority).toBe(true);
|
||||||
|
expect(networkIdentity.ownerId).toBe(1);
|
||||||
|
expect(playerComponent.hasAuthority).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('本地玩家设置', () => {
|
||||||
|
networkManager.registerNetworkObject(entity);
|
||||||
|
|
||||||
|
expect(networkIdentity.isLocalPlayer).toBe(false);
|
||||||
|
expect(playerComponent.isLocalPlayer).toBe(false);
|
||||||
|
|
||||||
|
NetworkRegistry.instance.setLocalPlayer(networkIdentity);
|
||||||
|
expect(networkIdentity.isLocalPlayer).toBe(true);
|
||||||
|
expect(networkIdentity.hasAuthority).toBe(true);
|
||||||
|
expect(playerComponent.isLocalPlayer).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SyncVar 同步变量', () => {
|
||||||
|
test('SyncVar 属性同步', () => {
|
||||||
|
networkManager.registerNetworkObject(entity);
|
||||||
|
networkIdentity.setAuthority(true, 1);
|
||||||
|
|
||||||
|
// 修改同步变量
|
||||||
|
playerComponent.health = 80;
|
||||||
|
playerComponent.playerName = 'TestPlayer';
|
||||||
|
|
||||||
|
// 检查待同步消息
|
||||||
|
const messages = SyncVarManager.instance.getPendingMessages();
|
||||||
|
expect(messages.length).toBeGreaterThanOrEqual(2);
|
||||||
|
|
||||||
|
// 验证消息内容
|
||||||
|
const healthMessage = messages.find(m => m.propertyName === 'health');
|
||||||
|
expect(healthMessage).toBeDefined();
|
||||||
|
expect(healthMessage?.value).toBe(80);
|
||||||
|
|
||||||
|
const nameMessage = messages.find(m => m.propertyName === 'playerName');
|
||||||
|
expect(nameMessage).toBeDefined();
|
||||||
|
expect(nameMessage?.value).toBe('TestPlayer');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SyncVar 变化回调', () => {
|
||||||
|
networkManager.registerNetworkObject(entity);
|
||||||
|
networkIdentity.setAuthority(true, 1);
|
||||||
|
|
||||||
|
expect(playerComponent.lastHealthChangeValue).toBe(0);
|
||||||
|
|
||||||
|
playerComponent.health = 75;
|
||||||
|
expect(playerComponent.lastHealthChangeValue).toBe(75);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('权威验证', () => {
|
||||||
|
networkManager.registerNetworkObject(entity);
|
||||||
|
|
||||||
|
// 没有权威时不应该能修改
|
||||||
|
expect(networkIdentity.hasAuthority).toBe(false);
|
||||||
|
|
||||||
|
const originalHealth = playerComponent.health;
|
||||||
|
playerComponent.health = 50;
|
||||||
|
|
||||||
|
// 检查是否有待同步消息(没有权威应该没有)
|
||||||
|
const messages = SyncVarManager.instance.getPendingMessages();
|
||||||
|
expect(messages.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('RPC 远程过程调用', () => {
|
||||||
|
test('RPC 方法注册', () => {
|
||||||
|
networkManager.registerNetworkObject(entity);
|
||||||
|
|
||||||
|
const stats = RpcManager.instance.getStats();
|
||||||
|
expect(stats.registeredComponents).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('RPC 消息生成', () => {
|
||||||
|
networkManager.registerNetworkObject(entity);
|
||||||
|
|
||||||
|
// 模拟服务端调用ClientRpc
|
||||||
|
if (NetworkManager.isServer) {
|
||||||
|
playerComponent.showDamageEffect(25, { x: 100, y: 200 });
|
||||||
|
|
||||||
|
const rpcMessages = RpcManager.instance.getPendingRpcMessages();
|
||||||
|
const damageMessage = rpcMessages.find(m => m.methodName === 'showDamageEffect');
|
||||||
|
expect(damageMessage).toBeDefined();
|
||||||
|
expect(damageMessage?.isClientRpc).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('网络管理器状态', () => {
|
||||||
|
test('网络端类型判断', () => {
|
||||||
|
// 默认应该是客户端
|
||||||
|
expect(NetworkManager.isClient).toBe(true);
|
||||||
|
expect(NetworkManager.isServer).toBe(false);
|
||||||
|
expect(NetworkManager.isConnected).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('连接状态管理', () => {
|
||||||
|
expect(networkManager.getConnectionState()).toBe('disconnected');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('网络统计信息', () => {
|
||||||
|
const stats = networkManager.getStats();
|
||||||
|
expect(stats).toHaveProperty('connectionCount');
|
||||||
|
expect(stats).toHaveProperty('messagesSent');
|
||||||
|
expect(stats).toHaveProperty('messagesReceived');
|
||||||
|
expect(stats.connectionCount).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('网络注册表管理', () => {
|
||||||
|
test('多个网络对象管理', () => {
|
||||||
|
// 创建多个实体
|
||||||
|
const entity2 = new MockEntity();
|
||||||
|
const networkIdentity2 = new NetworkIdentity();
|
||||||
|
entity2.addComponent(networkIdentity2);
|
||||||
|
const playerComponent2 = new TestPlayerComponent();
|
||||||
|
entity2.addComponent(playerComponent2);
|
||||||
|
playerComponent2.start();
|
||||||
|
|
||||||
|
const networkId1 = networkManager.registerNetworkObject(entity);
|
||||||
|
const networkId2 = networkManager.registerNetworkObject(entity2);
|
||||||
|
|
||||||
|
expect(networkId1).not.toBe(networkId2);
|
||||||
|
expect(NetworkRegistry.instance.getAllNetworkObjects().length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('网络对象注销', () => {
|
||||||
|
const networkId = networkManager.registerNetworkObject(entity);
|
||||||
|
|
||||||
|
expect(NetworkRegistry.instance.exists(networkId)).toBe(true);
|
||||||
|
|
||||||
|
NetworkRegistry.instance.unregister(networkId);
|
||||||
|
|
||||||
|
expect(NetworkRegistry.instance.exists(networkId)).toBe(false);
|
||||||
|
expect(NetworkRegistry.instance.find(networkId)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('按所有者查找对象', () => {
|
||||||
|
const networkId = networkManager.registerNetworkObject(entity);
|
||||||
|
networkIdentity.setAuthority(true, 123);
|
||||||
|
|
||||||
|
const ownedObjects = NetworkRegistry.instance.getObjectsByOwner(123);
|
||||||
|
expect(ownedObjects.length).toBe(1);
|
||||||
|
expect(ownedObjects[0]).toBe(networkIdentity);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import { Component } from '@esengine/ecs-framework';
|
|
||||||
import { TsrpcSerializer, SyncField } from '../../src/Serialization';
|
|
||||||
import { TsrpcSerializable } from '../../src/Serialization/TsrpcDecorators';
|
|
||||||
|
|
||||||
@TsrpcSerializable()
|
|
||||||
class TestComponent extends Component {
|
|
||||||
@SyncField()
|
|
||||||
public health: number = 100;
|
|
||||||
|
|
||||||
@SyncField()
|
|
||||||
public name: string = 'Test';
|
|
||||||
|
|
||||||
@SyncField()
|
|
||||||
public isActive: boolean = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('TsrpcSerializer', () => {
|
|
||||||
let serializer: TsrpcSerializer;
|
|
||||||
let testComponent: TestComponent;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
serializer = TsrpcSerializer.getInstance();
|
|
||||||
testComponent = new TestComponent();
|
|
||||||
testComponent.health = 80;
|
|
||||||
testComponent.name = 'Player';
|
|
||||||
testComponent.isActive = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('序列化', () => {
|
|
||||||
it('应该能序列化TSRPC组件', () => {
|
|
||||||
const result = serializer.serialize(testComponent);
|
|
||||||
|
|
||||||
expect(result).not.toBeNull();
|
|
||||||
expect(result?.type).toBe('tsrpc');
|
|
||||||
expect(result?.componentType).toBe('TestComponent');
|
|
||||||
expect(result?.data).toBeInstanceOf(Uint8Array);
|
|
||||||
expect(result?.size).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('不支持的组件应该返回null', () => {
|
|
||||||
// 创建一个没有装饰器的组件类
|
|
||||||
class UnsupportedComponent extends Component {}
|
|
||||||
const unsupportedComponent = new UnsupportedComponent();
|
|
||||||
const result = serializer.serialize(unsupportedComponent);
|
|
||||||
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('反序列化', () => {
|
|
||||||
it('应该能反序列化TSRPC数据', () => {
|
|
||||||
// 先序列化
|
|
||||||
const serializedData = serializer.serialize(testComponent);
|
|
||||||
expect(serializedData).not.toBeNull();
|
|
||||||
|
|
||||||
// 再反序列化
|
|
||||||
const deserializedComponent = serializer.deserialize(serializedData!, TestComponent);
|
|
||||||
|
|
||||||
expect(deserializedComponent).not.toBeNull();
|
|
||||||
expect(deserializedComponent?.health).toBe(80);
|
|
||||||
expect(deserializedComponent?.name).toBe('Player');
|
|
||||||
expect(deserializedComponent?.isActive).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('错误的数据类型应该返回null', () => {
|
|
||||||
const invalidData = {
|
|
||||||
type: 'json' as const,
|
|
||||||
componentType: 'TestComponent',
|
|
||||||
data: {},
|
|
||||||
size: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = serializer.deserialize(invalidData);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('统计信息', () => {
|
|
||||||
it('应该正确更新统计信息', () => {
|
|
||||||
const initialStats = serializer.getStats();
|
|
||||||
|
|
||||||
// 执行序列化
|
|
||||||
serializer.serialize(testComponent);
|
|
||||||
|
|
||||||
const afterSerializeStats = serializer.getStats();
|
|
||||||
expect(afterSerializeStats.serializeCount).toBe(initialStats.serializeCount + 1);
|
|
||||||
|
|
||||||
// 执行反序列化
|
|
||||||
const serializedData = serializer.serialize(testComponent);
|
|
||||||
if (serializedData) {
|
|
||||||
serializer.deserialize(serializedData, TestComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalStats = serializer.getStats();
|
|
||||||
expect(finalStats.deserializeCount).toBe(initialStats.deserializeCount + 1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('性能功能', () => {
|
|
||||||
it('应该正确计算序列化大小', () => {
|
|
||||||
const initialStats = serializer.getStats();
|
|
||||||
|
|
||||||
// 执行序列化
|
|
||||||
const result = serializer.serialize(testComponent);
|
|
||||||
|
|
||||||
expect(result).not.toBeNull();
|
|
||||||
expect(result?.size).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
const finalStats = serializer.getStats();
|
|
||||||
expect(finalStats.averageSerializedSize).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* 序列化模块集成测试
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 导入所有测试
|
|
||||||
import './TsrpcSerializer.test';
|
|
||||||
|
|
||||||
// 这个文件确保所有序列化相关的测试都被包含在测试套件中
|
|
||||||
describe('序列化模块集成测试', () => {
|
|
||||||
it('应该包含所有序列化测试', () => {
|
|
||||||
// 这个测试确保模块正确加载
|
|
||||||
expect(true).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
import { SyncVar, getSyncVarMetadata, SyncVarManager } from '../src/SyncVar';
|
|
||||||
import { createNetworkComponent } from '../src/SyncVar/SyncVarFactory';
|
|
||||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
|
||||||
import { NetworkComponent } from '../src/NetworkComponent';
|
|
||||||
|
|
||||||
// 测试用的组件类
|
|
||||||
class TestPlayerComponent extends NetworkComponent {
|
|
||||||
private _hasAuthority: boolean = false;
|
|
||||||
|
|
||||||
public hasAuthority(): boolean {
|
|
||||||
return this._hasAuthority;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setAuthority(hasAuthority: boolean): void {
|
|
||||||
this._hasAuthority = hasAuthority;
|
|
||||||
}
|
|
||||||
@SyncVar()
|
|
||||||
public health: number = 100;
|
|
||||||
|
|
||||||
@SyncVar({ hook: 'onNameChanged' })
|
|
||||||
public playerName: string = 'Player';
|
|
||||||
|
|
||||||
@SyncVar({ authorityOnly: true })
|
|
||||||
public isReady: boolean = false;
|
|
||||||
|
|
||||||
@SyncVar()
|
|
||||||
public position = { x: 0, y: 0 };
|
|
||||||
|
|
||||||
// Hook回调函数
|
|
||||||
public onNameChangedCallCount = 0;
|
|
||||||
public lastNameChange: { oldName: string; newName: string } | null = null;
|
|
||||||
|
|
||||||
onNameChanged(oldName: string, newName: string) {
|
|
||||||
this.onNameChangedCallCount++;
|
|
||||||
this.lastNameChange = { oldName, newName };
|
|
||||||
console.log(`Name changed: ${oldName} -> ${newName}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestComponentWithoutSyncVar extends NetworkComponent {
|
|
||||||
public normalField: number = 42;
|
|
||||||
|
|
||||||
public hasAuthority(): boolean { return true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('SyncVar系统测试', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// 清理SyncVar管理器
|
|
||||||
const manager = SyncVarManager.Instance;
|
|
||||||
manager['_componentChanges'].clear();
|
|
||||||
manager['_lastSyncTimes'].clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('装饰器和元数据', () => {
|
|
||||||
test('应该正确收集SyncVar元数据', () => {
|
|
||||||
const metadata = getSyncVarMetadata(TestPlayerComponent);
|
|
||||||
|
|
||||||
expect(metadata.length).toBe(4);
|
|
||||||
|
|
||||||
const healthMeta = metadata.find(m => m.propertyKey === 'health');
|
|
||||||
expect(healthMeta).toBeDefined();
|
|
||||||
expect(healthMeta!.fieldNumber).toBe(1);
|
|
||||||
expect(healthMeta!.options.hook).toBeUndefined();
|
|
||||||
|
|
||||||
const nameMeta = metadata.find(m => m.propertyKey === 'playerName');
|
|
||||||
expect(nameMeta).toBeDefined();
|
|
||||||
expect(nameMeta!.options.hook).toBe('onNameChanged');
|
|
||||||
|
|
||||||
const readyMeta = metadata.find(m => m.propertyKey === 'isReady');
|
|
||||||
expect(readyMeta).toBeDefined();
|
|
||||||
expect(readyMeta!.options.authorityOnly).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('没有SyncVar的组件应该返回空元数据', () => {
|
|
||||||
const metadata = getSyncVarMetadata(TestComponentWithoutSyncVar);
|
|
||||||
expect(metadata.length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('代理和变化检测', () => {
|
|
||||||
test('代理应该能检测到字段变化', () => {
|
|
||||||
const instance = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(instance);
|
|
||||||
|
|
||||||
// 修改SyncVar字段
|
|
||||||
proxy.health = 80;
|
|
||||||
|
|
||||||
const changes = proxy.getSyncVarChanges();
|
|
||||||
expect(changes.length).toBe(1);
|
|
||||||
expect(changes[0].propertyKey).toBe('health');
|
|
||||||
expect(changes[0].oldValue).toBe(100);
|
|
||||||
expect(changes[0].newValue).toBe(80);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('非SyncVar字段不应该被记录', () => {
|
|
||||||
class TestMixedComponent extends NetworkComponent {
|
|
||||||
@SyncVar()
|
|
||||||
public syncField: number = 1;
|
|
||||||
|
|
||||||
public normalField: number = 2;
|
|
||||||
|
|
||||||
public hasAuthority(): boolean { return true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = new TestMixedComponent();
|
|
||||||
const proxy = createSyncVarProxy(instance);
|
|
||||||
|
|
||||||
// 修改SyncVar字段
|
|
||||||
proxy.syncField = 10;
|
|
||||||
// 修改普通字段
|
|
||||||
proxy.normalField = 20;
|
|
||||||
|
|
||||||
const changes = proxy.getSyncVarChanges();
|
|
||||||
expect(changes.length).toBe(1);
|
|
||||||
expect(changes[0].propertyKey).toBe('syncField');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Hook回调应该被触发', () => {
|
|
||||||
const instance = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(instance);
|
|
||||||
|
|
||||||
// 修改带hook的字段
|
|
||||||
proxy.playerName = 'NewPlayer';
|
|
||||||
|
|
||||||
expect(proxy.onNameChangedCallCount).toBe(1);
|
|
||||||
expect(proxy.lastNameChange).toEqual({
|
|
||||||
oldName: 'Player',
|
|
||||||
newName: 'NewPlayer'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('相同值不应该触发变化记录', () => {
|
|
||||||
const instance = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(instance);
|
|
||||||
|
|
||||||
// 设置相同的值
|
|
||||||
proxy.health = 100; // 原始值就是100
|
|
||||||
|
|
||||||
const changes = proxy.getSyncVarChanges();
|
|
||||||
expect(changes.length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('同步数据创建和应用', () => {
|
|
||||||
test('应该能创建同步数据', () => {
|
|
||||||
const instance = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(instance);
|
|
||||||
|
|
||||||
// 修改多个字段
|
|
||||||
proxy.health = 75;
|
|
||||||
proxy.playerName = 'Hero';
|
|
||||||
|
|
||||||
const syncData = proxy.createSyncVarData();
|
|
||||||
expect(syncData).not.toBeNull();
|
|
||||||
expect(syncData.componentType).toBe('TestPlayerComponent');
|
|
||||||
expect(syncData.fieldUpdates.length).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('没有变化时不应该创建同步数据', () => {
|
|
||||||
const instance = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(instance);
|
|
||||||
|
|
||||||
const syncData = proxy.createSyncVarData();
|
|
||||||
expect(syncData).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能应用同步数据', () => {
|
|
||||||
const sourceInstance = new TestPlayerComponent();
|
|
||||||
const sourceProxy = createSyncVarProxy(sourceInstance);
|
|
||||||
|
|
||||||
const targetInstance = new TestPlayerComponent();
|
|
||||||
const targetProxy = createSyncVarProxy(targetInstance);
|
|
||||||
|
|
||||||
// 修改源实例
|
|
||||||
sourceProxy.health = 60;
|
|
||||||
sourceProxy.playerName = 'Warrior';
|
|
||||||
|
|
||||||
// 创建同步数据
|
|
||||||
const syncData = sourceProxy.createSyncVarData();
|
|
||||||
expect(syncData).not.toBeNull();
|
|
||||||
|
|
||||||
// 应用到目标实例
|
|
||||||
targetProxy.applySyncVarData(syncData);
|
|
||||||
|
|
||||||
// 验证目标实例的值已更新
|
|
||||||
expect(targetProxy.health).toBe(60);
|
|
||||||
expect(targetProxy.playerName).toBe('Warrior');
|
|
||||||
|
|
||||||
// 验证hook被触发
|
|
||||||
expect(targetProxy.onNameChangedCallCount).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('对象类型同步', () => {
|
|
||||||
test('应该能同步对象类型', () => {
|
|
||||||
const instance = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(instance);
|
|
||||||
|
|
||||||
// 修改对象字段
|
|
||||||
proxy.position = { x: 100, y: 200 };
|
|
||||||
|
|
||||||
const changes = proxy.getSyncVarChanges();
|
|
||||||
expect(changes.length).toBe(1);
|
|
||||||
expect(changes[0].propertyKey).toBe('position');
|
|
||||||
expect(changes[0].newValue).toEqual({ x: 100, y: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('对象浅比较应该正确工作', () => {
|
|
||||||
const instance = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(instance);
|
|
||||||
|
|
||||||
// 设置相同的对象值
|
|
||||||
proxy.position = { x: 0, y: 0 }; // 原始值
|
|
||||||
|
|
||||||
const changes = proxy.getSyncVarChanges();
|
|
||||||
expect(changes.length).toBe(0); // 应该没有变化
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('工厂函数', () => {
|
|
||||||
test('createNetworkComponent应该为有SyncVar的组件创建代理', () => {
|
|
||||||
const component = createNetworkComponent(TestPlayerComponent);
|
|
||||||
|
|
||||||
expect(component.hasSyncVars()).toBe(true);
|
|
||||||
|
|
||||||
// 测试代理功能
|
|
||||||
component.health = 90;
|
|
||||||
const changes = component.getSyncVarChanges();
|
|
||||||
expect(changes.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createNetworkComponent应该为没有SyncVar的组件返回原实例', () => {
|
|
||||||
const component = createNetworkComponent(TestComponentWithoutSyncVar);
|
|
||||||
|
|
||||||
expect(component.hasSyncVars()).toBe(false);
|
|
||||||
|
|
||||||
// 修改普通字段不应该有变化记录
|
|
||||||
component.normalField = 999;
|
|
||||||
const changes = component.getSyncVarChanges();
|
|
||||||
expect(changes.length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('管理器统计', () => {
|
|
||||||
test('应该能获取管理器统计信息', () => {
|
|
||||||
const component1 = createNetworkComponent(TestPlayerComponent);
|
|
||||||
const component2 = createNetworkComponent(TestPlayerComponent);
|
|
||||||
|
|
||||||
component1.health = 80;
|
|
||||||
component2.health = 70;
|
|
||||||
component2.playerName = 'Test';
|
|
||||||
|
|
||||||
const manager = SyncVarManager.Instance;
|
|
||||||
const stats = manager.getStats();
|
|
||||||
|
|
||||||
expect(stats.totalComponents).toBe(2);
|
|
||||||
expect(stats.totalChanges).toBe(3); // 1 + 2 = 3
|
|
||||||
expect(stats.pendingChanges).toBe(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,400 +0,0 @@
|
|||||||
import { NetworkIdentity, NetworkIdentityRegistry } from '../src/Core/NetworkIdentity';
|
|
||||||
import { SyncVar, SyncVarManager } from '../src/SyncVar';
|
|
||||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
|
||||||
import { SyncVarSyncScheduler } from '../src/SyncVar/SyncVarSyncScheduler';
|
|
||||||
import { SyncVarOptimizer } from '../src/SyncVar/SyncVarOptimizer';
|
|
||||||
import { SyncVarUpdateMessage } from '../src/Messaging/MessageTypes';
|
|
||||||
import { NetworkComponent } from '../src/NetworkComponent';
|
|
||||||
import { NetworkEnvironment, NetworkEnvironmentState } from '../src/Core/NetworkEnvironment';
|
|
||||||
|
|
||||||
// 测试用网络组件
|
|
||||||
class TestGameObject extends NetworkComponent {
|
|
||||||
@SyncVar()
|
|
||||||
public health: number = 100;
|
|
||||||
|
|
||||||
@SyncVar({ hook: 'onPositionChanged' })
|
|
||||||
public position: { x: number; y: number } = { x: 0, y: 0 };
|
|
||||||
|
|
||||||
@SyncVar({ authorityOnly: true })
|
|
||||||
public serverFlag: boolean = false;
|
|
||||||
|
|
||||||
@SyncVar()
|
|
||||||
public playerName: string = 'TestPlayer';
|
|
||||||
|
|
||||||
public positionChangeCount: number = 0;
|
|
||||||
|
|
||||||
onPositionChanged(oldPos: any, newPos: any) {
|
|
||||||
this.positionChangeCount++;
|
|
||||||
console.log(`Position changed from ${JSON.stringify(oldPos)} to ${JSON.stringify(newPos)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('SyncVar端到端测试', () => {
|
|
||||||
let gameObject1: TestGameObject;
|
|
||||||
let gameObject2: TestGameObject;
|
|
||||||
let identity1: NetworkIdentity;
|
|
||||||
let identity2: NetworkIdentity;
|
|
||||||
let syncVarManager: SyncVarManager;
|
|
||||||
let syncScheduler: SyncVarSyncScheduler;
|
|
||||||
let optimizer: SyncVarOptimizer;
|
|
||||||
|
|
||||||
// 消息交换模拟
|
|
||||||
let messageExchange: Map<string, SyncVarUpdateMessage[]> = new Map();
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
// 重置环境
|
|
||||||
const env = NetworkEnvironment['Instance'];
|
|
||||||
env['_state'] = NetworkEnvironmentState.None;
|
|
||||||
env['_serverStartTime'] = 0;
|
|
||||||
env['_clientConnectTime'] = 0;
|
|
||||||
NetworkEnvironment.SetServerMode();
|
|
||||||
|
|
||||||
// 清理组件
|
|
||||||
syncVarManager = SyncVarManager.Instance;
|
|
||||||
syncVarManager['_componentChanges'].clear();
|
|
||||||
syncVarManager['_lastSyncTimes'].clear();
|
|
||||||
|
|
||||||
syncScheduler = SyncVarSyncScheduler.Instance;
|
|
||||||
optimizer = new SyncVarOptimizer();
|
|
||||||
messageExchange.clear();
|
|
||||||
|
|
||||||
// 创建测试对象
|
|
||||||
gameObject1 = createSyncVarProxy(new TestGameObject()) as TestGameObject;
|
|
||||||
gameObject2 = createSyncVarProxy(new TestGameObject()) as TestGameObject;
|
|
||||||
|
|
||||||
// 创建网络身份
|
|
||||||
identity1 = new NetworkIdentity('player1', true);
|
|
||||||
identity2 = new NetworkIdentity('player2', false);
|
|
||||||
|
|
||||||
// 初始化SyncVar系统
|
|
||||||
syncVarManager.initializeComponent(gameObject1);
|
|
||||||
syncVarManager.initializeComponent(gameObject2);
|
|
||||||
|
|
||||||
// 模拟消息发送回调
|
|
||||||
syncScheduler.setMessageSendCallback(async (message: SyncVarUpdateMessage) => {
|
|
||||||
// 将消息添加到交换队列
|
|
||||||
const messages = messageExchange.get(message.networkId) || [];
|
|
||||||
messages.push(message);
|
|
||||||
messageExchange.set(message.networkId, messages);
|
|
||||||
|
|
||||||
console.log(`[E2E] 模拟发送消息: ${message.networkId} -> ${message.fieldUpdates.length} 字段更新`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
// 清理
|
|
||||||
identity1.cleanup();
|
|
||||||
identity2.cleanup();
|
|
||||||
NetworkIdentityRegistry.Instance.clear();
|
|
||||||
syncScheduler.stop();
|
|
||||||
optimizer.cleanup();
|
|
||||||
// NetworkManager.Stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('基本SyncVar同步流程', async () => {
|
|
||||||
// 修改gameObject1的属性
|
|
||||||
gameObject1.health = 80;
|
|
||||||
gameObject1.playerName = 'Hero';
|
|
||||||
gameObject1.position = { x: 10, y: 20 };
|
|
||||||
|
|
||||||
// 检查是否有待同步的变化
|
|
||||||
const changes = syncVarManager.getPendingChanges(gameObject1);
|
|
||||||
expect(changes.length).toBe(3);
|
|
||||||
|
|
||||||
// 创建同步消息
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
gameObject1,
|
|
||||||
identity1.networkId,
|
|
||||||
'server',
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(message).not.toBeNull();
|
|
||||||
expect(message!.fieldUpdates.length).toBe(3);
|
|
||||||
expect(message!.networkId).toBe('player1');
|
|
||||||
|
|
||||||
// 验证字段更新内容
|
|
||||||
const healthUpdate = message!.fieldUpdates.find(u => u.propertyKey === 'health');
|
|
||||||
expect(healthUpdate).toBeDefined();
|
|
||||||
expect(healthUpdate!.newValue).toBe(80);
|
|
||||||
expect(healthUpdate!.oldValue).toBe(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('消息序列化和反序列化', async () => {
|
|
||||||
// 修改属性
|
|
||||||
gameObject1.health = 75;
|
|
||||||
gameObject1.position = { x: 5, y: 15 };
|
|
||||||
|
|
||||||
// 创建消息
|
|
||||||
const originalMessage = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
gameObject1,
|
|
||||||
identity1.networkId
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(originalMessage).not.toBeNull();
|
|
||||||
|
|
||||||
// 序列化
|
|
||||||
const serialized = originalMessage!.serialize();
|
|
||||||
expect(serialized.length).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
// 反序列化
|
|
||||||
const deserializedMessage = new SyncVarUpdateMessage();
|
|
||||||
deserializedMessage.deserialize(serialized);
|
|
||||||
|
|
||||||
// 验证反序列化结果
|
|
||||||
expect(deserializedMessage.networkId).toBe(originalMessage!.networkId);
|
|
||||||
expect(deserializedMessage.componentType).toBe(originalMessage!.componentType);
|
|
||||||
expect(deserializedMessage.fieldUpdates.length).toBe(originalMessage!.fieldUpdates.length);
|
|
||||||
|
|
||||||
// 验证字段内容
|
|
||||||
for (let i = 0; i < originalMessage!.fieldUpdates.length; i++) {
|
|
||||||
const original = originalMessage!.fieldUpdates[i];
|
|
||||||
const deserialized = deserializedMessage.fieldUpdates[i];
|
|
||||||
|
|
||||||
expect(deserialized.fieldNumber).toBe(original.fieldNumber);
|
|
||||||
expect(deserialized.propertyKey).toBe(original.propertyKey);
|
|
||||||
expect(deserialized.newValue).toEqual(original.newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('SyncVar消息应用', async () => {
|
|
||||||
// 在gameObject1上创建变化
|
|
||||||
gameObject1.health = 60;
|
|
||||||
gameObject1.playerName = 'Warrior';
|
|
||||||
|
|
||||||
// 创建消息
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
gameObject1,
|
|
||||||
identity1.networkId
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(message).not.toBeNull();
|
|
||||||
|
|
||||||
// 清除gameObject1的变化记录
|
|
||||||
syncVarManager.clearChanges(gameObject1);
|
|
||||||
|
|
||||||
// 应用到gameObject2
|
|
||||||
syncVarManager.applySyncVarUpdateMessage(gameObject2, message!);
|
|
||||||
|
|
||||||
// 验证gameObject2的状态
|
|
||||||
expect(gameObject2.health).toBe(60);
|
|
||||||
expect(gameObject2.playerName).toBe('Warrior');
|
|
||||||
|
|
||||||
// 验证Hook被触发
|
|
||||||
expect(gameObject2.positionChangeCount).toBe(0); // position没有改变
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Hook回调触发', async () => {
|
|
||||||
// 修改position触发hook
|
|
||||||
gameObject1.position = { x: 100, y: 200 };
|
|
||||||
|
|
||||||
// 创建并应用消息
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
gameObject1,
|
|
||||||
identity1.networkId
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(message).not.toBeNull();
|
|
||||||
|
|
||||||
// 应用到gameObject2
|
|
||||||
syncVarManager.applySyncVarUpdateMessage(gameObject2, message!);
|
|
||||||
|
|
||||||
// 验证Hook被触发
|
|
||||||
expect(gameObject2.positionChangeCount).toBe(1);
|
|
||||||
expect(gameObject2.position).toEqual({ x: 100, y: 200 });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('权威字段保护', async () => {
|
|
||||||
// 切换到客户端环境
|
|
||||||
const env = NetworkEnvironment['Instance'];
|
|
||||||
env['_state'] = NetworkEnvironmentState.None;
|
|
||||||
NetworkEnvironment.SetClientMode();
|
|
||||||
|
|
||||||
// 客户端尝试修改权威字段
|
|
||||||
gameObject1.serverFlag = true; // 这应该被阻止
|
|
||||||
|
|
||||||
// 检查是否有待同步变化
|
|
||||||
const changes = syncVarManager.getPendingChanges(gameObject1);
|
|
||||||
expect(changes.length).toBe(0); // 应该没有变化被记录
|
|
||||||
|
|
||||||
// 尝试创建消息
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
gameObject1,
|
|
||||||
identity1.networkId
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(message).toBeNull(); // 应该没有消息
|
|
||||||
});
|
|
||||||
|
|
||||||
test('消息优化器功能', async () => {
|
|
||||||
// 配置优化器
|
|
||||||
optimizer.configure({
|
|
||||||
enableMessageMerging: true,
|
|
||||||
mergeTimeWindow: 50,
|
|
||||||
enableRateLimit: true,
|
|
||||||
maxMessagesPerSecond: 10
|
|
||||||
});
|
|
||||||
|
|
||||||
// 快速连续修改属性
|
|
||||||
gameObject1.health = 90;
|
|
||||||
gameObject1.health = 80;
|
|
||||||
gameObject1.health = 70;
|
|
||||||
|
|
||||||
const messages: SyncVarUpdateMessage[] = [];
|
|
||||||
|
|
||||||
// 创建多个消息
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
const msg = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
gameObject1,
|
|
||||||
identity1.networkId,
|
|
||||||
'server',
|
|
||||||
i + 1
|
|
||||||
);
|
|
||||||
if (msg) {
|
|
||||||
messages.push(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(messages.length).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
// 测试优化器处理
|
|
||||||
let optimizedCount = 0;
|
|
||||||
|
|
||||||
for (const message of messages) {
|
|
||||||
optimizer.optimizeMessage(message, ['observer1'], (optimizedMessages, observers) => {
|
|
||||||
optimizedCount++;
|
|
||||||
expect(optimizedMessages.length).toBeGreaterThan(0);
|
|
||||||
expect(observers.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待合并完成
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
|
|
||||||
// 强制刷新优化器
|
|
||||||
optimizer.flush(() => {
|
|
||||||
optimizedCount++;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(optimizedCount).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
// 检查统计信息
|
|
||||||
const stats = optimizer.getStats();
|
|
||||||
expect(stats.messagesProcessed).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('网络对象身份管理', async () => {
|
|
||||||
const registry = NetworkIdentityRegistry.Instance;
|
|
||||||
|
|
||||||
// 验证对象已注册
|
|
||||||
const foundIdentity1 = registry.find(identity1.networkId);
|
|
||||||
const foundIdentity2 = registry.find(identity2.networkId);
|
|
||||||
|
|
||||||
expect(foundIdentity1).toBeDefined();
|
|
||||||
expect(foundIdentity2).toBeDefined();
|
|
||||||
expect(foundIdentity1!.networkId).toBe('player1');
|
|
||||||
expect(foundIdentity2!.networkId).toBe('player2');
|
|
||||||
|
|
||||||
// 测试权威对象查询
|
|
||||||
const authorityObjects = registry.getAuthorityObjects();
|
|
||||||
expect(authorityObjects.length).toBe(1);
|
|
||||||
expect(authorityObjects[0].networkId).toBe('player1');
|
|
||||||
|
|
||||||
// 测试激活状态
|
|
||||||
identity1.activate();
|
|
||||||
identity2.activate();
|
|
||||||
|
|
||||||
const activeObjects = registry.getActiveObjects();
|
|
||||||
expect(activeObjects.length).toBe(2);
|
|
||||||
|
|
||||||
// 测试统计信息
|
|
||||||
const stats = registry.getStats();
|
|
||||||
expect(stats.totalObjects).toBe(2);
|
|
||||||
expect(stats.activeObjects).toBe(2);
|
|
||||||
expect(stats.authorityObjects).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('同步调度器集成测试', async () => {
|
|
||||||
// 配置调度器
|
|
||||||
syncScheduler.configure({
|
|
||||||
syncInterval: 50,
|
|
||||||
maxBatchSize: 5,
|
|
||||||
enablePrioritySort: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 激活网络对象
|
|
||||||
identity1.activate();
|
|
||||||
identity2.activate();
|
|
||||||
|
|
||||||
// 修改多个对象的属性
|
|
||||||
gameObject1.health = 85;
|
|
||||||
gameObject1.playerName = 'Hero1';
|
|
||||||
|
|
||||||
gameObject2.health = 75;
|
|
||||||
gameObject2.playerName = 'Hero2';
|
|
||||||
|
|
||||||
// 启动调度器
|
|
||||||
syncScheduler.start();
|
|
||||||
|
|
||||||
// 等待调度器处理
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
|
||||||
|
|
||||||
// 检查消息交换
|
|
||||||
const messages1 = messageExchange.get('player1') || [];
|
|
||||||
const messages2 = messageExchange.get('player2') || [];
|
|
||||||
|
|
||||||
console.log(`Player1 messages: ${messages1.length}, Player2 messages: ${messages2.length}`);
|
|
||||||
|
|
||||||
// 停止调度器
|
|
||||||
syncScheduler.stop();
|
|
||||||
|
|
||||||
// 检查统计信息
|
|
||||||
const schedulerStats = syncScheduler.getStats();
|
|
||||||
expect(schedulerStats.totalSyncCycles).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('完整的客户端-服务端模拟', async () => {
|
|
||||||
// 服务端环境设置
|
|
||||||
NetworkEnvironment.SetServerMode();
|
|
||||||
const serverObject = createSyncVarProxy(new TestGameObject()) as TestGameObject;
|
|
||||||
const serverIdentity = new NetworkIdentity('server_obj', true);
|
|
||||||
serverIdentity.activate();
|
|
||||||
|
|
||||||
syncVarManager.initializeComponent(serverObject);
|
|
||||||
|
|
||||||
// 客户端环境设置
|
|
||||||
const env = NetworkEnvironment['Instance'];
|
|
||||||
env['_state'] = NetworkEnvironmentState.None;
|
|
||||||
NetworkEnvironment.SetClientMode();
|
|
||||||
|
|
||||||
const clientObject = createSyncVarProxy(new TestGameObject()) as TestGameObject;
|
|
||||||
syncVarManager.initializeComponent(clientObject);
|
|
||||||
|
|
||||||
// 服务端修改数据
|
|
||||||
NetworkEnvironment.SetServerMode();
|
|
||||||
serverObject.health = 50;
|
|
||||||
serverObject.playerName = 'ServerPlayer';
|
|
||||||
serverObject.position = { x: 30, y: 40 };
|
|
||||||
|
|
||||||
// 创建服务端消息
|
|
||||||
const serverMessage = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
serverObject,
|
|
||||||
serverIdentity.networkId,
|
|
||||||
'server'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(serverMessage).not.toBeNull();
|
|
||||||
|
|
||||||
// 切换到客户端接收消息
|
|
||||||
NetworkEnvironment.SetClientMode();
|
|
||||||
syncVarManager.applySyncVarUpdateMessage(clientObject, serverMessage!);
|
|
||||||
|
|
||||||
// 验证客户端状态
|
|
||||||
expect(clientObject.health).toBe(50);
|
|
||||||
expect(clientObject.playerName).toBe('ServerPlayer');
|
|
||||||
expect(clientObject.position).toEqual({ x: 30, y: 40 });
|
|
||||||
expect(clientObject.positionChangeCount).toBe(1);
|
|
||||||
|
|
||||||
console.log('[E2E] 客户端-服务端同步测试完成');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import 'reflect-metadata';
|
|
||||||
import { SyncVar } from '../src/SyncVar';
|
|
||||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
|
||||||
import { NetworkComponent } from '../src/NetworkComponent';
|
|
||||||
|
|
||||||
// 简化的测试用网络组件
|
|
||||||
class SimpleTestComponent extends NetworkComponent {
|
|
||||||
@SyncVar()
|
|
||||||
public health: number = 100;
|
|
||||||
|
|
||||||
@SyncVar()
|
|
||||||
public name: string = 'TestPlayer';
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('SyncVar端到端简单测试', () => {
|
|
||||||
test('基本的SyncVar代理创建', () => {
|
|
||||||
const component = new SimpleTestComponent();
|
|
||||||
const proxiedComponent = createSyncVarProxy(component) as SimpleTestComponent;
|
|
||||||
|
|
||||||
expect(proxiedComponent).toBeDefined();
|
|
||||||
expect(proxiedComponent.health).toBe(100);
|
|
||||||
expect(proxiedComponent.name).toBe('TestPlayer');
|
|
||||||
|
|
||||||
// 修改值应该能正常工作
|
|
||||||
proxiedComponent.health = 80;
|
|
||||||
expect(proxiedComponent.health).toBe(80);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('SyncVar变化记录', () => {
|
|
||||||
const component = createSyncVarProxy(new SimpleTestComponent()) as SimpleTestComponent;
|
|
||||||
|
|
||||||
// 修改值
|
|
||||||
component.health = 75;
|
|
||||||
component.name = 'Hero';
|
|
||||||
|
|
||||||
// 检查是否有变化记录
|
|
||||||
const changes = component.getSyncVarChanges();
|
|
||||||
expect(changes.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,430 +0,0 @@
|
|||||||
import { SyncVarUpdateMessage, SyncVarFieldUpdate, MessageType } from '../src/Messaging/MessageTypes';
|
|
||||||
import { SyncVar, getSyncVarMetadata, SyncVarManager } from '../src/SyncVar';
|
|
||||||
import { createSyncVarProxy } from '../src/SyncVar/SyncVarProxy';
|
|
||||||
import { NetworkEnvironment, NetworkEnvironmentState } from '../src/Core/NetworkEnvironment';
|
|
||||||
import { NetworkComponent } from '../src/NetworkComponent';
|
|
||||||
|
|
||||||
// 测试用的组件类
|
|
||||||
class TestPlayerComponent extends NetworkComponent {
|
|
||||||
private _hasAuthority: boolean = false;
|
|
||||||
|
|
||||||
public hasAuthority(): boolean {
|
|
||||||
return this._hasAuthority;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setAuthority(hasAuthority: boolean): void {
|
|
||||||
this._hasAuthority = hasAuthority;
|
|
||||||
}
|
|
||||||
@SyncVar()
|
|
||||||
public health: number = 100;
|
|
||||||
|
|
||||||
@SyncVar({ hook: 'onNameChanged' })
|
|
||||||
public playerName: string = 'Player';
|
|
||||||
|
|
||||||
@SyncVar({ authorityOnly: true })
|
|
||||||
public isReady: boolean = false;
|
|
||||||
|
|
||||||
@SyncVar()
|
|
||||||
public position = { x: 0, y: 0 };
|
|
||||||
|
|
||||||
// Hook回调函数
|
|
||||||
public onNameChangedCallCount = 0;
|
|
||||||
public lastNameChange: { oldName: string; newName: string } | null = null;
|
|
||||||
|
|
||||||
onNameChanged(oldName: string, newName: string) {
|
|
||||||
this.onNameChangedCallCount++;
|
|
||||||
this.lastNameChange = { oldName, newName };
|
|
||||||
console.log(`Name changed: ${oldName} -> ${newName}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('SyncVar消息系统测试', () => {
|
|
||||||
let syncVarManager: SyncVarManager;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// 重置网络环境 - 先清除所有状态再设置服务端
|
|
||||||
const env = NetworkEnvironment['Instance'];
|
|
||||||
env['_state'] = NetworkEnvironmentState.None;
|
|
||||||
env['_serverStartTime'] = 0;
|
|
||||||
env['_clientConnectTime'] = 0;
|
|
||||||
NetworkEnvironment.SetServerMode();
|
|
||||||
|
|
||||||
// 获取SyncVar管理器实例
|
|
||||||
syncVarManager = SyncVarManager.Instance;
|
|
||||||
|
|
||||||
// 清理管理器状态
|
|
||||||
syncVarManager['_componentChanges'].clear();
|
|
||||||
syncVarManager['_lastSyncTimes'].clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('SyncVarUpdateMessage基础功能', () => {
|
|
||||||
test('应该能正确创建SyncVarUpdateMessage', () => {
|
|
||||||
const fieldUpdates: SyncVarFieldUpdate[] = [
|
|
||||||
{
|
|
||||||
fieldNumber: 1,
|
|
||||||
propertyKey: 'health',
|
|
||||||
newValue: 80,
|
|
||||||
oldValue: 100,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
authorityOnly: false
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const message = new SyncVarUpdateMessage(
|
|
||||||
'player_001',
|
|
||||||
'TestPlayerComponent',
|
|
||||||
fieldUpdates,
|
|
||||||
false,
|
|
||||||
'server_001',
|
|
||||||
123
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(message.messageType).toBe(MessageType.SYNC_VAR_UPDATE);
|
|
||||||
expect(message.networkId).toBe('player_001');
|
|
||||||
expect(message.componentType).toBe('TestPlayerComponent');
|
|
||||||
expect(message.fieldUpdates.length).toBe(1);
|
|
||||||
expect(message.isFullSync).toBe(false);
|
|
||||||
expect(message.senderId).toBe('server_001');
|
|
||||||
expect(message.syncSequence).toBe(123);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能添加和移除字段更新', () => {
|
|
||||||
const message = new SyncVarUpdateMessage();
|
|
||||||
|
|
||||||
expect(message.hasUpdates()).toBe(false);
|
|
||||||
expect(message.getUpdateCount()).toBe(0);
|
|
||||||
|
|
||||||
const fieldUpdate: SyncVarFieldUpdate = {
|
|
||||||
fieldNumber: 1,
|
|
||||||
propertyKey: 'health',
|
|
||||||
newValue: 80,
|
|
||||||
timestamp: Date.now()
|
|
||||||
};
|
|
||||||
|
|
||||||
message.addFieldUpdate(fieldUpdate);
|
|
||||||
expect(message.hasUpdates()).toBe(true);
|
|
||||||
expect(message.getUpdateCount()).toBe(1);
|
|
||||||
|
|
||||||
const retrieved = message.getFieldUpdate(1);
|
|
||||||
expect(retrieved).toBeDefined();
|
|
||||||
expect(retrieved?.propertyKey).toBe('health');
|
|
||||||
|
|
||||||
const removed = message.removeFieldUpdate(1);
|
|
||||||
expect(removed).toBe(true);
|
|
||||||
expect(message.hasUpdates()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能序列化和反序列化消息', () => {
|
|
||||||
const fieldUpdates: SyncVarFieldUpdate[] = [
|
|
||||||
{
|
|
||||||
fieldNumber: 1,
|
|
||||||
propertyKey: 'health',
|
|
||||||
newValue: 75,
|
|
||||||
oldValue: 100,
|
|
||||||
timestamp: Date.now()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldNumber: 2,
|
|
||||||
propertyKey: 'playerName',
|
|
||||||
newValue: 'Hero',
|
|
||||||
oldValue: 'Player',
|
|
||||||
timestamp: Date.now()
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const originalMessage = new SyncVarUpdateMessage(
|
|
||||||
'player_001',
|
|
||||||
'TestPlayerComponent',
|
|
||||||
fieldUpdates,
|
|
||||||
true,
|
|
||||||
'server_001',
|
|
||||||
456
|
|
||||||
);
|
|
||||||
|
|
||||||
// 序列化
|
|
||||||
const serializedData = originalMessage.serialize();
|
|
||||||
expect(serializedData.length).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
// 反序列化
|
|
||||||
const deserializedMessage = new SyncVarUpdateMessage();
|
|
||||||
deserializedMessage.deserialize(serializedData);
|
|
||||||
|
|
||||||
// 验证反序列化结果
|
|
||||||
expect(deserializedMessage.networkId).toBe(originalMessage.networkId);
|
|
||||||
expect(deserializedMessage.componentType).toBe(originalMessage.componentType);
|
|
||||||
expect(deserializedMessage.fieldUpdates.length).toBe(originalMessage.fieldUpdates.length);
|
|
||||||
expect(deserializedMessage.isFullSync).toBe(originalMessage.isFullSync);
|
|
||||||
expect(deserializedMessage.senderId).toBe(originalMessage.senderId);
|
|
||||||
expect(deserializedMessage.syncSequence).toBe(originalMessage.syncSequence);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能获取消息统计信息', () => {
|
|
||||||
const message = new SyncVarUpdateMessage();
|
|
||||||
|
|
||||||
// 空消息统计
|
|
||||||
let stats = message.getStats();
|
|
||||||
expect(stats.updateCount).toBe(0);
|
|
||||||
expect(stats.hasAuthorityOnlyFields).toBe(false);
|
|
||||||
expect(stats.oldestUpdateTime).toBe(0);
|
|
||||||
expect(stats.newestUpdateTime).toBe(0);
|
|
||||||
|
|
||||||
// 添加字段更新
|
|
||||||
const now = Date.now();
|
|
||||||
message.addFieldUpdate({
|
|
||||||
fieldNumber: 1,
|
|
||||||
propertyKey: 'health',
|
|
||||||
newValue: 80,
|
|
||||||
timestamp: now - 1000
|
|
||||||
});
|
|
||||||
|
|
||||||
message.addFieldUpdate({
|
|
||||||
fieldNumber: 2,
|
|
||||||
propertyKey: 'isReady',
|
|
||||||
newValue: true,
|
|
||||||
timestamp: now,
|
|
||||||
authorityOnly: true
|
|
||||||
});
|
|
||||||
|
|
||||||
stats = message.getStats();
|
|
||||||
expect(stats.updateCount).toBe(2);
|
|
||||||
expect(stats.hasAuthorityOnlyFields).toBe(true);
|
|
||||||
expect(stats.oldestUpdateTime).toBe(now - 1000);
|
|
||||||
expect(stats.newestUpdateTime).toBe(now);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('SyncVarManager消息集成', () => {
|
|
||||||
test('应该能从组件变化创建SyncVarUpdateMessage', () => {
|
|
||||||
const component = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(component);
|
|
||||||
|
|
||||||
// 初始化组件
|
|
||||||
syncVarManager.initializeComponent(proxy);
|
|
||||||
|
|
||||||
// 修改字段
|
|
||||||
proxy.health = 75;
|
|
||||||
proxy.playerName = 'Hero';
|
|
||||||
|
|
||||||
// 创建消息
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
proxy,
|
|
||||||
'player_001',
|
|
||||||
'server_001',
|
|
||||||
100
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(message).not.toBeNull();
|
|
||||||
expect(message!.networkId).toBe('player_001');
|
|
||||||
expect(message!.componentType).toBe('TestPlayerComponent');
|
|
||||||
expect(message!.senderId).toBe('server_001');
|
|
||||||
expect(message!.syncSequence).toBe(100);
|
|
||||||
expect(message!.fieldUpdates.length).toBe(2);
|
|
||||||
|
|
||||||
// 验证字段更新内容
|
|
||||||
const healthUpdate = message!.fieldUpdates.find(u => u.propertyKey === 'health');
|
|
||||||
expect(healthUpdate).toBeDefined();
|
|
||||||
expect(healthUpdate!.newValue).toBe(75);
|
|
||||||
expect(healthUpdate!.oldValue).toBe(100);
|
|
||||||
|
|
||||||
const nameUpdate = message!.fieldUpdates.find(u => u.propertyKey === 'playerName');
|
|
||||||
expect(nameUpdate).toBeDefined();
|
|
||||||
expect(nameUpdate!.newValue).toBe('Hero');
|
|
||||||
expect(nameUpdate!.oldValue).toBe('Player');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('没有变化时应该返回null', () => {
|
|
||||||
const component = new TestPlayerComponent();
|
|
||||||
const proxy = createSyncVarProxy(component);
|
|
||||||
|
|
||||||
syncVarManager.initializeComponent(proxy);
|
|
||||||
|
|
||||||
// 没有修改任何字段
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(proxy);
|
|
||||||
|
|
||||||
expect(message).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能应用SyncVarUpdateMessage到组件', () => {
|
|
||||||
const sourceComponent = new TestPlayerComponent();
|
|
||||||
const sourceProxy = createSyncVarProxy(sourceComponent);
|
|
||||||
|
|
||||||
const targetComponent = new TestPlayerComponent();
|
|
||||||
const targetProxy = createSyncVarProxy(targetComponent);
|
|
||||||
|
|
||||||
// 初始化组件
|
|
||||||
syncVarManager.initializeComponent(sourceProxy);
|
|
||||||
syncVarManager.initializeComponent(targetProxy);
|
|
||||||
|
|
||||||
// 修改源组件
|
|
||||||
sourceProxy.health = 60;
|
|
||||||
sourceProxy.playerName = 'Warrior';
|
|
||||||
|
|
||||||
// 创建消息
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(
|
|
||||||
sourceProxy,
|
|
||||||
'player_001'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(message).not.toBeNull();
|
|
||||||
|
|
||||||
// 应用到目标组件
|
|
||||||
syncVarManager.applySyncVarUpdateMessage(targetProxy, message!);
|
|
||||||
|
|
||||||
// 验证目标组件状态
|
|
||||||
expect(targetProxy.health).toBe(60);
|
|
||||||
expect(targetProxy.playerName).toBe('Warrior');
|
|
||||||
|
|
||||||
// 验证hook被触发
|
|
||||||
expect(targetProxy.onNameChangedCallCount).toBe(1);
|
|
||||||
expect(targetProxy.lastNameChange).toEqual({
|
|
||||||
oldName: 'Player',
|
|
||||||
newName: 'Warrior'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能批量创建多个组件的消息', () => {
|
|
||||||
const component1 = createSyncVarProxy(new TestPlayerComponent());
|
|
||||||
const component2 = createSyncVarProxy(new TestPlayerComponent());
|
|
||||||
|
|
||||||
syncVarManager.initializeComponent(component1);
|
|
||||||
syncVarManager.initializeComponent(component2);
|
|
||||||
|
|
||||||
// 修改组件
|
|
||||||
component1.health = 80;
|
|
||||||
component2.playerName = 'Hero2';
|
|
||||||
|
|
||||||
// 批量创建消息
|
|
||||||
const messages = syncVarManager.createBatchSyncVarUpdateMessages(
|
|
||||||
[component1, component2],
|
|
||||||
['player_001', 'player_002'],
|
|
||||||
'server_001',
|
|
||||||
200
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(messages.length).toBe(2);
|
|
||||||
|
|
||||||
expect(messages[0].networkId).toBe('player_001');
|
|
||||||
expect(messages[0].syncSequence).toBe(200);
|
|
||||||
|
|
||||||
expect(messages[1].networkId).toBe('player_002');
|
|
||||||
expect(messages[1].syncSequence).toBe(201);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能过滤有变化的组件', () => {
|
|
||||||
const component1 = createSyncVarProxy(new TestPlayerComponent());
|
|
||||||
const component2 = createSyncVarProxy(new TestPlayerComponent());
|
|
||||||
const component3 = createSyncVarProxy(new TestPlayerComponent());
|
|
||||||
|
|
||||||
syncVarManager.initializeComponent(component1);
|
|
||||||
syncVarManager.initializeComponent(component2);
|
|
||||||
syncVarManager.initializeComponent(component3);
|
|
||||||
|
|
||||||
// 只修改component1和component3
|
|
||||||
component1.health = 80;
|
|
||||||
component3.playerName = 'Hero3';
|
|
||||||
// component2没有修改
|
|
||||||
|
|
||||||
const componentsWithChanges = syncVarManager.filterComponentsWithChanges([
|
|
||||||
component1, component2, component3
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(componentsWithChanges.length).toBe(2);
|
|
||||||
expect(componentsWithChanges).toContain(component1);
|
|
||||||
expect(componentsWithChanges).toContain(component3);
|
|
||||||
expect(componentsWithChanges).not.toContain(component2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('应该能获取组件变化统计', () => {
|
|
||||||
const component = createSyncVarProxy(new TestPlayerComponent());
|
|
||||||
syncVarManager.initializeComponent(component);
|
|
||||||
|
|
||||||
// 修改多个字段
|
|
||||||
component.health = 80;
|
|
||||||
component.health = 70; // 再次修改同一字段
|
|
||||||
component.playerName = 'Hero';
|
|
||||||
|
|
||||||
const stats = syncVarManager.getComponentChangeStats(component);
|
|
||||||
|
|
||||||
expect(stats.totalChanges).toBe(3);
|
|
||||||
expect(stats.pendingChanges).toBe(3);
|
|
||||||
expect(stats.lastChangeTime).toBeGreaterThan(0);
|
|
||||||
expect(stats.fieldChangeCounts.get('health')).toBe(2);
|
|
||||||
expect(stats.fieldChangeCounts.get('playerName')).toBe(1);
|
|
||||||
expect(stats.hasAuthorityOnlyChanges).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('权限和环境检查', () => {
|
|
||||||
test('权威字段应该被正确处理', () => {
|
|
||||||
// 重置环境为纯客户端模式
|
|
||||||
const env = NetworkEnvironment['Instance'];
|
|
||||||
env['_state'] = NetworkEnvironmentState.None;
|
|
||||||
env['_serverStartTime'] = 0;
|
|
||||||
env['_clientConnectTime'] = 0;
|
|
||||||
NetworkEnvironment.SetClientMode(); // 切换到纯客户端模式
|
|
||||||
|
|
||||||
const component = createSyncVarProxy(new TestPlayerComponent());
|
|
||||||
// 明确设置没有权限
|
|
||||||
component.setAuthority(false);
|
|
||||||
|
|
||||||
syncVarManager.initializeComponent(component);
|
|
||||||
|
|
||||||
console.log('当前环境:', NetworkEnvironment.isServer ? 'server' : 'client');
|
|
||||||
console.log('isServer:', NetworkEnvironment.isServer);
|
|
||||||
console.log('isClient:', NetworkEnvironment.isClient);
|
|
||||||
console.log('组件权限:', component.hasAuthority());
|
|
||||||
|
|
||||||
// 修改权威字段(客户端没有权限)
|
|
||||||
component.isReady = true;
|
|
||||||
|
|
||||||
// 检查待同步变化
|
|
||||||
const pendingChanges = syncVarManager.getPendingChanges(component);
|
|
||||||
console.log('待同步变化:', pendingChanges);
|
|
||||||
|
|
||||||
const message = syncVarManager.createSyncVarUpdateMessage(component);
|
|
||||||
console.log('创建的消息:', message);
|
|
||||||
|
|
||||||
// 在客户端模式下,权威字段不应该被同步
|
|
||||||
expect(message).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('客户端应该能接受来自服务端的权威字段更新', () => {
|
|
||||||
NetworkEnvironment.SetClientMode(); // 客户端模式
|
|
||||||
|
|
||||||
const component = createSyncVarProxy(new TestPlayerComponent());
|
|
||||||
syncVarManager.initializeComponent(component);
|
|
||||||
|
|
||||||
const fieldUpdates: SyncVarFieldUpdate[] = [
|
|
||||||
{
|
|
||||||
fieldNumber: 3, // isReady字段
|
|
||||||
propertyKey: 'isReady',
|
|
||||||
newValue: true,
|
|
||||||
oldValue: false,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
authorityOnly: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const message = new SyncVarUpdateMessage(
|
|
||||||
'player_001',
|
|
||||||
'TestPlayerComponent',
|
|
||||||
fieldUpdates
|
|
||||||
);
|
|
||||||
|
|
||||||
// 记录初始值
|
|
||||||
const initialValue = component.isReady;
|
|
||||||
expect(initialValue).toBe(false);
|
|
||||||
|
|
||||||
// 应用消息(客户端应该接受来自服务端的权威字段更新)
|
|
||||||
syncVarManager.applySyncVarUpdateMessage(component, message);
|
|
||||||
|
|
||||||
// 值应该改变
|
|
||||||
expect(component.isReady).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
// 清理
|
|
||||||
NetworkEnvironment.SetServerMode(); // 重置为服务器模式
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* SyncVar简单测试
|
|
||||||
*/
|
|
||||||
|
|
||||||
import 'reflect-metadata';
|
|
||||||
|
|
||||||
describe('SyncVar简单测试', () => {
|
|
||||||
test('基础功能测试', () => {
|
|
||||||
expect(true).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
145
packages/network/tests/TsrpcTransport.test.ts
Normal file
145
packages/network/tests/TsrpcTransport.test.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* TSRPC传输层测试
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { TsrpcTransport } from '../src/transport/TsrpcTransport';
|
||||||
|
import { NetworkConfig } from '../src/types/NetworkTypes';
|
||||||
|
|
||||||
|
// 简化测试,只验证基本功能
|
||||||
|
describe('TSRPC传输层测试', () => {
|
||||||
|
let serverTransport: TsrpcTransport;
|
||||||
|
let clientTransport: TsrpcTransport;
|
||||||
|
|
||||||
|
const serverConfig: NetworkConfig = {
|
||||||
|
port: 18888, // 使用不同端口避免冲突
|
||||||
|
host: 'localhost',
|
||||||
|
syncRate: 20
|
||||||
|
};
|
||||||
|
|
||||||
|
const clientConfig: NetworkConfig = {
|
||||||
|
port: 18888,
|
||||||
|
host: 'localhost'
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
serverTransport = new TsrpcTransport(serverConfig);
|
||||||
|
clientTransport = new TsrpcTransport(clientConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (serverTransport) {
|
||||||
|
await serverTransport.disconnect();
|
||||||
|
}
|
||||||
|
if (clientTransport) {
|
||||||
|
await clientTransport.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('传输层创建', () => {
|
||||||
|
test('创建服务端传输层', () => {
|
||||||
|
expect(serverTransport).toBeDefined();
|
||||||
|
expect(serverTransport.getNetworkSide()).toBe('client'); // 默认为客户端
|
||||||
|
expect(serverTransport.isConnected()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('创建客户端传输层', () => {
|
||||||
|
expect(clientTransport).toBeDefined();
|
||||||
|
expect(clientTransport.getNetworkSide()).toBe('client');
|
||||||
|
expect(clientTransport.isConnected()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('事件处理器设置', () => {
|
||||||
|
test('设置事件处理器', () => {
|
||||||
|
let connectedCalled = false;
|
||||||
|
let disconnectedCalled = false;
|
||||||
|
|
||||||
|
serverTransport.setEventHandlers({
|
||||||
|
onConnected: () => {
|
||||||
|
connectedCalled = true;
|
||||||
|
},
|
||||||
|
onDisconnected: () => {
|
||||||
|
disconnectedCalled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证事件处理器被正确设置
|
||||||
|
expect(connectedCalled).toBe(false);
|
||||||
|
expect(disconnectedCalled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('单独设置事件处理器', () => {
|
||||||
|
let errorCalled = false;
|
||||||
|
|
||||||
|
serverTransport.on('onError', (error) => {
|
||||||
|
errorCalled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(errorCalled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('基本功能验证', () => {
|
||||||
|
test('获取统计信息', () => {
|
||||||
|
const stats = serverTransport.getStats();
|
||||||
|
|
||||||
|
expect(stats).toHaveProperty('messagesSent');
|
||||||
|
expect(stats).toHaveProperty('messagesReceived');
|
||||||
|
expect(stats).toHaveProperty('bytesSent');
|
||||||
|
expect(stats).toHaveProperty('bytesReceived');
|
||||||
|
expect(stats).toHaveProperty('clientCount');
|
||||||
|
expect(stats).toHaveProperty('uptime');
|
||||||
|
|
||||||
|
// 初始值应该为0
|
||||||
|
expect(stats.messagesSent).toBe(0);
|
||||||
|
expect(stats.messagesReceived).toBe(0);
|
||||||
|
expect(stats.clientCount).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('客户端模式方法调用异常处理', async () => {
|
||||||
|
// 客户端模式下调用服务端方法应该抛出错误
|
||||||
|
await expect(serverTransport.getServerStatus()).rejects.toThrow('只能在客户端模式下查询服务端状态');
|
||||||
|
await expect(serverTransport.ping()).rejects.toThrow('只能在客户端模式下发送心跳');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未初始化时发送消息异常处理', async () => {
|
||||||
|
const testMessage = {
|
||||||
|
type: 'test',
|
||||||
|
networkId: 1,
|
||||||
|
data: { test: 'data' },
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 未连接时发送消息应该抛出错误
|
||||||
|
await expect(serverTransport.sendMessage(testMessage)).rejects.toThrow('传输层未初始化或状态错误');
|
||||||
|
await expect(serverTransport.sendSyncVar(1, 'TestComponent', 'testProp', 'testValue')).rejects.toThrow('传输层未初始化或状态错误');
|
||||||
|
await expect(serverTransport.sendRpcCall(1, 'TestComponent', 'testMethod', [], true)).rejects.toThrow('传输层未初始化或状态错误');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('网络配置', () => {
|
||||||
|
test('获取正确的网络端类型', async () => {
|
||||||
|
// 测试服务端模式
|
||||||
|
const config: NetworkConfig = {
|
||||||
|
port: 18889,
|
||||||
|
host: 'localhost'
|
||||||
|
};
|
||||||
|
|
||||||
|
const transport = new TsrpcTransport(config);
|
||||||
|
expect(transport.getNetworkSide()).toBe('client'); // 创建时默认为客户端
|
||||||
|
|
||||||
|
await transport.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('获取客户端ID和连接信息', () => {
|
||||||
|
expect(serverTransport.getClientId()).toBe(0); // 未连接时为0
|
||||||
|
expect(serverTransport.getConnectedClients()).toEqual([]); // 客户端模式返回空数组
|
||||||
|
expect(serverTransport.getClientCount()).toBe(0); // 客户端模式返回0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注意:由于在测试环境中启动真实的网络服务可能很复杂,
|
||||||
|
// 这里主要测试API的正确性和错误处理,
|
||||||
|
// 真正的端到端网络测试需要在集成测试中进行
|
||||||
|
});
|
||||||
@@ -1,72 +1,28 @@
|
|||||||
/**
|
/**
|
||||||
* Jest 测试全局设置文件
|
* Jest测试设置文件
|
||||||
*
|
|
||||||
* 此文件在每个测试文件执行前运行,用于设置全局测试环境
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 设置测试超时时间(毫秒)
|
import 'reflect-metadata';
|
||||||
|
|
||||||
|
// 全局Jest配置
|
||||||
|
expect.extend({});
|
||||||
|
|
||||||
|
// 设置测试环境变量
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
|
// 全局错误处理
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.error('未处理的Promise拒绝:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置全局测试超时
|
||||||
jest.setTimeout(10000);
|
jest.setTimeout(10000);
|
||||||
|
|
||||||
// 模拟控制台方法以减少测试输出噪音
|
// 清理函数
|
||||||
const originalConsoleLog = console.log;
|
|
||||||
const originalConsoleWarn = console.warn;
|
|
||||||
const originalConsoleError = console.error;
|
|
||||||
|
|
||||||
// 在测试环境中可以选择性地静默某些日志
|
|
||||||
beforeAll(() => {
|
|
||||||
// 可以在这里设置全局的模拟或配置
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
// 清理全局资源
|
|
||||||
});
|
|
||||||
|
|
||||||
// 每个测试前的清理
|
|
||||||
beforeEach(() => {
|
|
||||||
// 清理定时器
|
|
||||||
jest.clearAllTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
// 恢复所有模拟
|
// 清理所有定时器
|
||||||
jest.restoreAllMocks();
|
jest.clearAllTimers();
|
||||||
|
|
||||||
|
// 清理所有模拟
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 导出测试工具函数
|
|
||||||
export const TestUtils = {
|
|
||||||
/**
|
|
||||||
* 创建测试用的延迟
|
|
||||||
* @param ms 延迟毫秒数
|
|
||||||
*/
|
|
||||||
delay: (ms: number): Promise<void> => {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 等待条件满足
|
|
||||||
* @param condition 条件函数
|
|
||||||
* @param timeout 超时时间(毫秒)
|
|
||||||
* @param interval 检查间隔(毫秒)
|
|
||||||
*/
|
|
||||||
waitFor: async (
|
|
||||||
condition: () => boolean,
|
|
||||||
timeout: number = 5000,
|
|
||||||
interval: number = 10
|
|
||||||
): Promise<void> => {
|
|
||||||
const start = Date.now();
|
|
||||||
while (!condition() && Date.now() - start < timeout) {
|
|
||||||
await TestUtils.delay(interval);
|
|
||||||
}
|
|
||||||
if (!condition()) {
|
|
||||||
throw new Error(`等待条件超时 (${timeout}ms)`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟时间前进
|
|
||||||
* @param ms 前进的毫秒数
|
|
||||||
*/
|
|
||||||
advanceTime: (ms: number): void => {
|
|
||||||
jest.advanceTimersByTime(ms);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user