448 lines
15 KiB
TypeScript
448 lines
15 KiB
TypeScript
|
|
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
|||
|
|
import { NetworkComponent } from '../components';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 网络系统 - 处理网络同步和连接管理
|
|||
|
|
*/
|
|||
|
|
export class NetworkSystem extends EntitySystem {
|
|||
|
|
|
|||
|
|
/** 网络统计 */
|
|||
|
|
private networkStats = {
|
|||
|
|
totalEntities: 0,
|
|||
|
|
connectedEntities: 0,
|
|||
|
|
totalMessagesSent: 0,
|
|||
|
|
totalMessagesReceived: 0,
|
|||
|
|
averagePing: 0,
|
|||
|
|
networkTraffic: 0
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/** 消息处理队列 */
|
|||
|
|
private globalMessageQueue: Array<{
|
|||
|
|
from: string;
|
|||
|
|
to: string;
|
|||
|
|
messageType: string;
|
|||
|
|
data: any;
|
|||
|
|
timestamp: number;
|
|||
|
|
priority: number;
|
|||
|
|
}> = [];
|
|||
|
|
|
|||
|
|
constructor() {
|
|||
|
|
// 处理具有网络组件的实体
|
|||
|
|
super(Matcher.empty().all(NetworkComponent));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理所有实体
|
|||
|
|
*/
|
|||
|
|
protected process(entities: Entity[]): void {
|
|||
|
|
const deltaTime = Time.deltaTime;
|
|||
|
|
|
|||
|
|
this.networkStats.totalEntities = entities.length;
|
|||
|
|
this.networkStats.connectedEntities = entities.filter(e =>
|
|||
|
|
e.getComponent(NetworkComponent)?.connectionState === 'connected'
|
|||
|
|
).length;
|
|||
|
|
|
|||
|
|
for (const entity of entities) {
|
|||
|
|
this.processEntity(entity, deltaTime);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理全局消息队列
|
|||
|
|
this.processGlobalMessages();
|
|||
|
|
|
|||
|
|
// 更新网络统计
|
|||
|
|
this.updateGlobalNetworkStats(entities);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理单个实体
|
|||
|
|
*/
|
|||
|
|
private processEntity(entity: Entity, deltaTime: number): void {
|
|||
|
|
const network = entity.getComponent(NetworkComponent);
|
|||
|
|
|
|||
|
|
if (!network) return;
|
|||
|
|
|
|||
|
|
// 更新网络统计
|
|||
|
|
network.updateNetworkStats(deltaTime);
|
|||
|
|
|
|||
|
|
// 处理连接状态
|
|||
|
|
this.updateConnectionState(network, deltaTime);
|
|||
|
|
|
|||
|
|
// 处理消息队列
|
|||
|
|
this.processEntityMessages(network, entity);
|
|||
|
|
|
|||
|
|
// 处理数据同步
|
|||
|
|
this.processSynchronization(network, deltaTime);
|
|||
|
|
|
|||
|
|
// 处理群组通信
|
|||
|
|
this.processGroupCommunication(network);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新连接状态
|
|||
|
|
*/
|
|||
|
|
private updateConnectionState(network: NetworkComponent, deltaTime: number): void {
|
|||
|
|
const currentTime = Date.now();
|
|||
|
|
|
|||
|
|
switch (network.connectionState) {
|
|||
|
|
case 'disconnected':
|
|||
|
|
// 尝试连接
|
|||
|
|
if (network.config.autoReconnect &&
|
|||
|
|
network.networkStats.reconnectCount < network.config.maxReconnectAttempts) {
|
|||
|
|
network.connectionState = 'connecting';
|
|||
|
|
network.connection.lastHeartbeat = currentTime;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'connecting':
|
|||
|
|
// 模拟连接过程
|
|||
|
|
if (Math.random() > 0.1) { // 90% 成功率
|
|||
|
|
network.connectionState = 'connected';
|
|||
|
|
network.connection.sessionId = this.generateSessionId();
|
|||
|
|
network.connection.serverId = 'server_001';
|
|||
|
|
network.connection.lastHeartbeat = currentTime;
|
|||
|
|
} else if (currentTime - network.connection.lastHeartbeat > 5000) {
|
|||
|
|
// 连接超时
|
|||
|
|
network.connectionState = 'error';
|
|||
|
|
network.networkStats.errorCount++;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'connected':
|
|||
|
|
// 维持连接心跳
|
|||
|
|
if (currentTime - network.connection.lastHeartbeat > network.config.heartbeatInterval) {
|
|||
|
|
this.sendHeartbeat(network);
|
|||
|
|
network.connection.lastHeartbeat = currentTime;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 模拟网络质量变化
|
|||
|
|
network.connection.ping = Math.random() * 100 + 20; // 20-120ms
|
|||
|
|
network.connection.packetLoss = Math.random() * 0.05; // 0-5%
|
|||
|
|
network.connection.bandwidth = 1000 + Math.random() * 500; // 1000-1500 Kbps
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'error':
|
|||
|
|
// 错误状态,尝试重连
|
|||
|
|
if (network.config.autoReconnect &&
|
|||
|
|
network.networkStats.reconnectCount < network.config.maxReconnectAttempts) {
|
|||
|
|
network.connectionState = 'disconnected';
|
|||
|
|
network.networkStats.reconnectCount++;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理实体消息
|
|||
|
|
*/
|
|||
|
|
private processEntityMessages(network: NetworkComponent, entity: Entity): void {
|
|||
|
|
// 处理传出消息
|
|||
|
|
const outgoingMessages = network.messageQueue.outgoing.slice();
|
|||
|
|
network.messageQueue.outgoing = [];
|
|||
|
|
|
|||
|
|
for (const message of outgoingMessages) {
|
|||
|
|
if (this.sendMessage(network, message)) {
|
|||
|
|
this.networkStats.totalMessagesSent++;
|
|||
|
|
network.networkStats.totalBytesSent += this.estimateMessageSize(message);
|
|||
|
|
} else {
|
|||
|
|
// 发送失败,重新加入队列
|
|||
|
|
message.attempts++;
|
|||
|
|
if (message.attempts < message.maxAttempts) {
|
|||
|
|
network.messageQueue.outgoing.push(message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理传入消息
|
|||
|
|
this.processIncomingMessages(network, entity);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送消息
|
|||
|
|
*/
|
|||
|
|
private sendMessage(network: NetworkComponent, message: any): boolean {
|
|||
|
|
if (network.connectionState !== 'connected') {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 模拟网络延迟和丢包
|
|||
|
|
const shouldDelay = Math.random() < 0.3; // 30% 概率有延迟
|
|||
|
|
const shouldDrop = Math.random() < network.connection.packetLoss;
|
|||
|
|
|
|||
|
|
if (shouldDrop) {
|
|||
|
|
network.networkStats.errorCount++;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加到全局消息队列
|
|||
|
|
this.globalMessageQueue.push({
|
|||
|
|
from: network.networkId,
|
|||
|
|
to: message.targetId,
|
|||
|
|
messageType: message.messageType,
|
|||
|
|
data: message.data,
|
|||
|
|
timestamp: Date.now() + (shouldDelay ? Math.random() * 200 : 0),
|
|||
|
|
priority: message.priority
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理传入消息
|
|||
|
|
*/
|
|||
|
|
private processIncomingMessages(network: NetworkComponent, entity: Entity): void {
|
|||
|
|
// 从全局队列中获取发给此实体的消息
|
|||
|
|
const incomingMessages = this.globalMessageQueue.filter(msg =>
|
|||
|
|
msg.to === network.networkId && msg.timestamp <= Date.now()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 从全局队列中移除这些消息
|
|||
|
|
this.globalMessageQueue = this.globalMessageQueue.filter(msg =>
|
|||
|
|
!(msg.to === network.networkId && msg.timestamp <= Date.now())
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 处理消息
|
|||
|
|
for (const message of incomingMessages) {
|
|||
|
|
network.messageQueue.incoming.push({
|
|||
|
|
senderId: message.from,
|
|||
|
|
messageType: message.messageType,
|
|||
|
|
data: message.data,
|
|||
|
|
timestamp: message.timestamp,
|
|||
|
|
processed: false
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.networkStats.totalMessagesReceived++;
|
|||
|
|
network.networkStats.totalBytesReceived += this.estimateMessageSize(message);
|
|||
|
|
|
|||
|
|
// 立即处理某些类型的消息
|
|||
|
|
this.handleSpecialMessages(network, message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理特殊消息类型
|
|||
|
|
*/
|
|||
|
|
private handleSpecialMessages(network: NetworkComponent, message: any): void {
|
|||
|
|
switch (message.messageType) {
|
|||
|
|
case 'player_join_group':
|
|||
|
|
// 处理加入群组消息
|
|||
|
|
const groupData = message.data;
|
|||
|
|
if (groupData.members && Array.isArray(groupData.members)) {
|
|||
|
|
// 查找对应的网络组件并建立连接
|
|||
|
|
groupData.members.forEach((memberId: string) => {
|
|||
|
|
// 直接使用成员ID建立连接
|
|||
|
|
network.connectToPlayer(memberId);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'heartbeat':
|
|||
|
|
// 心跳响应
|
|||
|
|
network.connection.ping = Date.now() - message.data.timestamp;
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'sync_request':
|
|||
|
|
// 同步请求
|
|||
|
|
this.handleSyncRequest(network, message);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理数据同步
|
|||
|
|
*/
|
|||
|
|
private processSynchronization(network: NetworkComponent, deltaTime: number): void {
|
|||
|
|
const currentTime = Date.now();
|
|||
|
|
const syncInterval = 1000 / network.config.syncFrequency; // 转换为毫秒
|
|||
|
|
|
|||
|
|
if (currentTime - network.syncData.lastSyncTime >= syncInterval) {
|
|||
|
|
this.performSynchronization(network);
|
|||
|
|
network.syncData.lastSyncTime = currentTime;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理排队的更新
|
|||
|
|
this.processQueuedUpdates(network);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 执行同步
|
|||
|
|
*/
|
|||
|
|
private performSynchronization(network: NetworkComponent): void {
|
|||
|
|
if (network.syncData.dirtyFlags.size === 0) {
|
|||
|
|
return; // 没有需要同步的数据
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const syncData = {
|
|||
|
|
networkId: network.networkId,
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
properties: Array.from(network.syncData.dirtyFlags),
|
|||
|
|
checksum: this.calculateChecksum(network)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 发送同步数据给连接的玩家
|
|||
|
|
network.connectedPlayerIds.forEach(playerId => {
|
|||
|
|
network.sendMessage(playerId, 'sync_data', syncData, 7);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 记录同步历史
|
|||
|
|
network.syncData.syncHistory.push({
|
|||
|
|
timestamp: syncData.timestamp,
|
|||
|
|
dataSize: this.estimateMessageSize(syncData),
|
|||
|
|
properties: syncData.properties,
|
|||
|
|
success: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 清理脏标记
|
|||
|
|
network.syncData.dirtyFlags.clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理排队的更新
|
|||
|
|
*/
|
|||
|
|
private processQueuedUpdates(network: NetworkComponent): void {
|
|||
|
|
// 按优先级和时间戳排序
|
|||
|
|
network.syncData.queuedUpdates.sort((a, b) => {
|
|||
|
|
if (a.priority !== b.priority) {
|
|||
|
|
return b.priority - a.priority; // 高优先级优先
|
|||
|
|
}
|
|||
|
|
return a.timestamp - b.timestamp; // 时间戳早的优先
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 处理前10个更新
|
|||
|
|
const updatesToProcess = network.syncData.queuedUpdates.splice(0, 10);
|
|||
|
|
for (const update of updatesToProcess) {
|
|||
|
|
network.markDirty(update.property);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理群组通信
|
|||
|
|
*/
|
|||
|
|
private processGroupCommunication(network: NetworkComponent): void {
|
|||
|
|
if (network.groupMemberIds.length === 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 群组消息广播
|
|||
|
|
if (Math.random() < 0.01) { // 1% 概率发送群组消息
|
|||
|
|
const groupMessage = {
|
|||
|
|
type: 'group_update',
|
|||
|
|
data: {
|
|||
|
|
sender: network.networkId,
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
groupSize: network.groupMemberIds.length,
|
|||
|
|
status: network.connectionState
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
network.groupMemberIds.forEach(memberId => {
|
|||
|
|
if (memberId !== network.networkId) {
|
|||
|
|
network.sendMessage(memberId, 'group_message', groupMessage, 5);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理全局消息
|
|||
|
|
*/
|
|||
|
|
private processGlobalMessages(): void {
|
|||
|
|
// 移除过期消息
|
|||
|
|
const currentTime = Date.now();
|
|||
|
|
this.globalMessageQueue = this.globalMessageQueue.filter(msg =>
|
|||
|
|
currentTime - msg.timestamp < 30000 // 30秒过期
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 按优先级排序
|
|||
|
|
this.globalMessageQueue.sort((a, b) => b.priority - a.priority);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新全局网络统计
|
|||
|
|
*/
|
|||
|
|
private updateGlobalNetworkStats(entities: Entity[]): void {
|
|||
|
|
let totalPing = 0;
|
|||
|
|
let connectedCount = 0;
|
|||
|
|
let totalTraffic = 0;
|
|||
|
|
|
|||
|
|
for (const entity of entities) {
|
|||
|
|
const network = entity.getComponent(NetworkComponent);
|
|||
|
|
if (network && network.connectionState === 'connected') {
|
|||
|
|
totalPing += network.connection.ping;
|
|||
|
|
connectedCount++;
|
|||
|
|
totalTraffic += network.networkStats.totalBytesSent + network.networkStats.totalBytesReceived;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.networkStats.averagePing = connectedCount > 0 ? totalPing / connectedCount : 0;
|
|||
|
|
this.networkStats.networkTraffic = totalTraffic;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 辅助方法
|
|||
|
|
*/
|
|||
|
|
private generateSessionId(): string {
|
|||
|
|
return 'session_' + Math.random().toString(36).substring(2, 15);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private estimateMessageSize(message: any): number {
|
|||
|
|
return JSON.stringify(message).length;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private calculateChecksum(network: NetworkComponent): string {
|
|||
|
|
// 简单的校验和计算
|
|||
|
|
const data = JSON.stringify({
|
|||
|
|
networkId: network.networkId,
|
|||
|
|
connectionState: network.connectionState
|
|||
|
|
});
|
|||
|
|
return btoa(data).substring(0, 8);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private sendHeartbeat(network: NetworkComponent): void {
|
|||
|
|
network.sendMessage('server', 'heartbeat', { timestamp: Date.now() }, 10);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private findNetworkComponentById(networkId: string): NetworkComponent | null {
|
|||
|
|
// 这里应该有一个全局的网络组件注册表
|
|||
|
|
// 为了简化,我们返回null
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private handleSyncRequest(network: NetworkComponent, message: any): void {
|
|||
|
|
// 处理同步请求
|
|||
|
|
const response = {
|
|||
|
|
requestId: message.data.requestId,
|
|||
|
|
data: this.gatherSyncData(network),
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
network.sendMessage(message.from, 'sync_response', response, 8);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private gatherSyncData(network: NetworkComponent): any {
|
|||
|
|
return {
|
|||
|
|
networkId: network.networkId,
|
|||
|
|
connectionState: network.connectionState,
|
|||
|
|
ping: network.connection.ping,
|
|||
|
|
groupSize: network.groupMemberIds.length
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 系统初始化时调用
|
|||
|
|
*/
|
|||
|
|
public initialize(): void {
|
|||
|
|
super.initialize();
|
|||
|
|
console.log('🌐 网络系统已启动');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取系统统计信息
|
|||
|
|
*/
|
|||
|
|
public getSystemStats(): any {
|
|||
|
|
return {
|
|||
|
|
...this.networkStats,
|
|||
|
|
globalMessageQueueSize: this.globalMessageQueue.length,
|
|||
|
|
systemName: 'NetworkSystem'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|