实现SyncVar装饰器和组件同步
This commit is contained in:
@@ -14,5 +14,11 @@ export * from './transport/WebSocketTransport';
|
||||
export * from './rooms/Room';
|
||||
export * from './rooms/RoomManager';
|
||||
|
||||
// 系统
|
||||
export * from './systems';
|
||||
|
||||
// 同步模块
|
||||
export * from './sync';
|
||||
|
||||
// 重新导出shared包的类型
|
||||
export * from '@esengine/network-shared';
|
||||
459
packages/network-server/src/sync/NetworkScopeManager.ts
Normal file
459
packages/network-server/src/sync/NetworkScopeManager.ts
Normal file
@@ -0,0 +1,459 @@
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { NetworkScope, SyncBatch } from '@esengine/network-shared';
|
||||
import { EventEmitter } from '@esengine/network-shared';
|
||||
|
||||
/**
|
||||
* 客户端位置信息
|
||||
*/
|
||||
export interface ClientPosition {
|
||||
clientId: string;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
lastUpdate: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 作用域配置
|
||||
*/
|
||||
export interface ScopeConfig {
|
||||
/** 默认可视范围 */
|
||||
defaultRange: number;
|
||||
/** 最大可视范围 */
|
||||
maxRange: number;
|
||||
/** 位置更新间隔(毫秒) */
|
||||
positionUpdateInterval: number;
|
||||
/** 是否启用LOD(细节层次) */
|
||||
enableLOD: boolean;
|
||||
/** LOD距离阈值 */
|
||||
lodDistances: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 作用域查询结果
|
||||
*/
|
||||
export interface ScopeQueryResult {
|
||||
/** 在范围内的客户端ID列表 */
|
||||
clientsInRange: string[];
|
||||
/** 距离映射 */
|
||||
distances: Map<string, number>;
|
||||
/** LOD级别映射 */
|
||||
lodLevels: Map<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义作用域规则
|
||||
*/
|
||||
export interface CustomScopeRule {
|
||||
name: string;
|
||||
condition: (batch: SyncBatch, clientId: string, clientPosition?: ClientPosition) => boolean;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络作用域管理器
|
||||
* 负责管理客户端的可见范围和网络作用域优化
|
||||
*/
|
||||
export class NetworkScopeManager extends EventEmitter {
|
||||
private logger = createLogger('NetworkScopeManager');
|
||||
private config: ScopeConfig;
|
||||
|
||||
/** 客户端位置信息 */
|
||||
private clientPositions = new Map<string, ClientPosition>();
|
||||
|
||||
/** 客户端可视范围 */
|
||||
private clientRanges = new Map<string, number>();
|
||||
|
||||
/** 房间映射 */
|
||||
private clientRooms = new Map<string, string>();
|
||||
private roomClients = new Map<string, Set<string>>();
|
||||
|
||||
/** 自定义作用域规则 */
|
||||
private customRules: CustomScopeRule[] = [];
|
||||
|
||||
/** 作用域缓存 */
|
||||
private scopeCache = new Map<string, { result: ScopeQueryResult; timestamp: number }>();
|
||||
private cacheTimeout = 100; // 100ms缓存
|
||||
|
||||
constructor(config: Partial<ScopeConfig> = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
defaultRange: 100,
|
||||
maxRange: 500,
|
||||
positionUpdateInterval: 100,
|
||||
enableLOD: true,
|
||||
lodDistances: [50, 150, 300],
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加客户端
|
||||
*/
|
||||
public addClient(clientId: string, position?: { x: number; y: number; z: number }): void {
|
||||
this.clientRanges.set(clientId, this.config.defaultRange);
|
||||
|
||||
if (position) {
|
||||
this.updateClientPosition(clientId, position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
this.logger.debug(`客户端 ${clientId} 已添加到作用域管理器`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除客户端
|
||||
*/
|
||||
public removeClient(clientId: string): void {
|
||||
this.clientPositions.delete(clientId);
|
||||
this.clientRanges.delete(clientId);
|
||||
|
||||
// 从房间中移除
|
||||
const roomId = this.clientRooms.get(clientId);
|
||||
if (roomId) {
|
||||
this.leaveRoom(clientId, roomId);
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
this.clearClientCache(clientId);
|
||||
|
||||
this.logger.debug(`客户端 ${clientId} 已从作用域管理器移除`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客户端位置
|
||||
*/
|
||||
public updateClientPosition(clientId: string, x: number, y: number, z: number): void {
|
||||
const clientPosition: ClientPosition = {
|
||||
clientId,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
lastUpdate: Date.now()
|
||||
};
|
||||
|
||||
this.clientPositions.set(clientId, clientPosition);
|
||||
|
||||
// 清理相关缓存
|
||||
this.clearClientCache(clientId);
|
||||
|
||||
this.emit('positionUpdated', clientId, clientPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置客户端可视范围
|
||||
*/
|
||||
public setClientRange(clientId: string, range: number): void {
|
||||
const clampedRange = Math.min(range, this.config.maxRange);
|
||||
this.clientRanges.set(clientId, clampedRange);
|
||||
|
||||
// 清理相关缓存
|
||||
this.clearClientCache(clientId);
|
||||
|
||||
this.logger.debug(`客户端 ${clientId} 可视范围设置为: ${clampedRange}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入房间
|
||||
*/
|
||||
public joinRoom(clientId: string, roomId: string): void {
|
||||
// 从旧房间离开
|
||||
const oldRoom = this.clientRooms.get(clientId);
|
||||
if (oldRoom) {
|
||||
this.leaveRoom(clientId, oldRoom);
|
||||
}
|
||||
|
||||
// 加入新房间
|
||||
this.clientRooms.set(clientId, roomId);
|
||||
|
||||
if (!this.roomClients.has(roomId)) {
|
||||
this.roomClients.set(roomId, new Set());
|
||||
}
|
||||
this.roomClients.get(roomId)!.add(clientId);
|
||||
|
||||
this.logger.debug(`客户端 ${clientId} 已加入房间 ${roomId}`);
|
||||
this.emit('clientJoinedRoom', clientId, roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 离开房间
|
||||
*/
|
||||
public leaveRoom(clientId: string, roomId: string): void {
|
||||
this.clientRooms.delete(clientId);
|
||||
|
||||
const roomClientSet = this.roomClients.get(roomId);
|
||||
if (roomClientSet) {
|
||||
roomClientSet.delete(clientId);
|
||||
|
||||
// 如果房间为空,删除房间
|
||||
if (roomClientSet.size === 0) {
|
||||
this.roomClients.delete(roomId);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug(`客户端 ${clientId} 已离开房间 ${roomId}`);
|
||||
this.emit('clientLeftRoom', clientId, roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义作用域规则
|
||||
*/
|
||||
public addCustomRule(rule: CustomScopeRule): void {
|
||||
this.customRules.push(rule);
|
||||
this.customRules.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
this.logger.debug(`已添加自定义作用域规则: ${rule.name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除自定义作用域规则
|
||||
*/
|
||||
public removeCustomRule(ruleName: string): boolean {
|
||||
const index = this.customRules.findIndex(rule => rule.name === ruleName);
|
||||
if (index >= 0) {
|
||||
this.customRules.splice(index, 1);
|
||||
this.logger.debug(`已移除自定义作用域规则: ${ruleName}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查批次是否应该发送给特定客户端
|
||||
*/
|
||||
public shouldSendToClient(batch: SyncBatch, clientId: string): boolean {
|
||||
// 检查自定义规则
|
||||
for (const rule of this.customRules) {
|
||||
const clientPosition = this.clientPositions.get(clientId);
|
||||
if (!rule.condition(batch, clientId, clientPosition)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查网络作用域
|
||||
for (const [prop, scope] of Object.entries(batch.scopes)) {
|
||||
if (!this.checkPropertyScope(scope, batch, clientId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询在范围内的客户端
|
||||
*/
|
||||
public queryClientsInRange(
|
||||
position: { x: number; y: number; z: number },
|
||||
range: number,
|
||||
excludeClientId?: string
|
||||
): ScopeQueryResult {
|
||||
const cacheKey = `${position.x},${position.y},${position.z},${range},${excludeClientId || ''}`;
|
||||
const cached = this.scopeCache.get(cacheKey);
|
||||
|
||||
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
|
||||
return cached.result;
|
||||
}
|
||||
|
||||
const clientsInRange: string[] = [];
|
||||
const distances = new Map<string, number>();
|
||||
const lodLevels = new Map<string, number>();
|
||||
|
||||
for (const [clientId, clientPosition] of this.clientPositions) {
|
||||
if (excludeClientId && clientId === excludeClientId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const distance = this.calculateDistance(position, clientPosition);
|
||||
|
||||
if (distance <= range) {
|
||||
clientsInRange.push(clientId);
|
||||
distances.set(clientId, distance);
|
||||
|
||||
// 计算LOD级别
|
||||
if (this.config.enableLOD) {
|
||||
const lodLevel = this.calculateLODLevel(distance);
|
||||
lodLevels.set(clientId, lodLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result: ScopeQueryResult = {
|
||||
clientsInRange,
|
||||
distances,
|
||||
lodLevels
|
||||
};
|
||||
|
||||
// 缓存结果
|
||||
this.scopeCache.set(cacheKey, {
|
||||
result,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取房间内的所有客户端
|
||||
*/
|
||||
public getRoomClients(roomId: string): string[] {
|
||||
const roomClientSet = this.roomClients.get(roomId);
|
||||
return roomClientSet ? Array.from(roomClientSet) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端所在房间
|
||||
*/
|
||||
public getClientRoom(clientId: string): string | undefined {
|
||||
return this.clientRooms.get(clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端位置
|
||||
*/
|
||||
public getClientPosition(clientId: string): ClientPosition | undefined {
|
||||
return this.clientPositions.get(clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有客户端位置
|
||||
*/
|
||||
public getAllClientPositions(): Map<string, ClientPosition> {
|
||||
return new Map(this.clientPositions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期缓存
|
||||
*/
|
||||
public cleanupCache(): void {
|
||||
const now = Date.now();
|
||||
for (const [key, cache] of this.scopeCache) {
|
||||
if (now - cache.timestamp > this.cacheTimeout * 2) {
|
||||
this.scopeCache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<ScopeConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
clientCount: number;
|
||||
roomCount: number;
|
||||
cacheSize: number;
|
||||
customRuleCount: number;
|
||||
} {
|
||||
return {
|
||||
clientCount: this.clientPositions.size,
|
||||
roomCount: this.roomClients.size,
|
||||
cacheSize: this.scopeCache.size,
|
||||
customRuleCount: this.customRules.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.clientPositions.clear();
|
||||
this.clientRanges.clear();
|
||||
this.clientRooms.clear();
|
||||
this.roomClients.clear();
|
||||
this.customRules.length = 0;
|
||||
this.scopeCache.clear();
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查属性作用域
|
||||
*/
|
||||
private checkPropertyScope(scope: NetworkScope, batch: SyncBatch, clientId: string): boolean {
|
||||
switch (scope) {
|
||||
case NetworkScope.Global:
|
||||
return true;
|
||||
|
||||
case NetworkScope.Room:
|
||||
const clientRoom = this.clientRooms.get(clientId);
|
||||
// 这里需要知道batch来源的实体所在房间,简化实现
|
||||
return true;
|
||||
|
||||
case NetworkScope.Owner:
|
||||
return batch.instanceId === clientId;
|
||||
|
||||
case NetworkScope.Nearby:
|
||||
return this.isClientNearby(batch, clientId);
|
||||
|
||||
case NetworkScope.Custom:
|
||||
// 由自定义规则处理
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端是否在附近
|
||||
*/
|
||||
private isClientNearby(batch: SyncBatch, clientId: string): boolean {
|
||||
const clientPosition = this.clientPositions.get(clientId);
|
||||
if (!clientPosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const clientRange = this.clientRanges.get(clientId) || this.config.defaultRange;
|
||||
|
||||
// 这里需要知道batch来源实体的位置,简化实现
|
||||
// 实际项目中应该从实体的Transform组件获取位置
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两点之间的距离
|
||||
*/
|
||||
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 - pos2.z;
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算LOD级别
|
||||
*/
|
||||
private calculateLODLevel(distance: number): number {
|
||||
for (let i = 0; i < this.config.lodDistances.length; i++) {
|
||||
if (distance <= this.config.lodDistances[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return this.config.lodDistances.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理客户端相关缓存
|
||||
*/
|
||||
private clearClientCache(clientId: string): void {
|
||||
const keysToDelete: string[] = [];
|
||||
|
||||
for (const key of this.scopeCache.keys()) {
|
||||
if (key.includes(clientId)) {
|
||||
keysToDelete.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of keysToDelete) {
|
||||
this.scopeCache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
501
packages/network-server/src/sync/SyncScheduler.ts
Normal file
501
packages/network-server/src/sync/SyncScheduler.ts
Normal file
@@ -0,0 +1,501 @@
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { SyncBatch } from '@esengine/network-shared';
|
||||
import { EventEmitter } from '@esengine/network-shared';
|
||||
|
||||
/**
|
||||
* 调度配置
|
||||
*/
|
||||
export interface SyncSchedulerConfig {
|
||||
/** 目标帧率 */
|
||||
targetFPS: number;
|
||||
/** 最大延迟(毫秒) */
|
||||
maxLatency: number;
|
||||
/** 带宽限制(字节/秒) */
|
||||
bandwidthLimit: number;
|
||||
/** 优先级权重 */
|
||||
priorityWeights: {
|
||||
high: number;
|
||||
medium: number;
|
||||
low: number;
|
||||
};
|
||||
/** 自适应调整间隔(毫秒) */
|
||||
adaptiveInterval: number;
|
||||
/** 是否启用自适应调整 */
|
||||
enableAdaptive: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度任务
|
||||
*/
|
||||
interface ScheduledTask {
|
||||
id: string;
|
||||
batch: SyncBatch;
|
||||
clientId: string;
|
||||
priority: number;
|
||||
deadline: number;
|
||||
size: number;
|
||||
retryCount: number;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端调度状态
|
||||
*/
|
||||
interface ClientScheduleState {
|
||||
clientId: string;
|
||||
bandwidth: {
|
||||
used: number;
|
||||
limit: number;
|
||||
resetTime: number;
|
||||
};
|
||||
latency: number;
|
||||
queueSize: number;
|
||||
lastSendTime: number;
|
||||
adaptiveRate: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度统计
|
||||
*/
|
||||
interface SchedulerStats {
|
||||
totalTasks: number;
|
||||
completedTasks: number;
|
||||
droppedTasks: number;
|
||||
averageLatency: number;
|
||||
bandwidthUtilization: number;
|
||||
queueSizes: { [clientId: string]: number };
|
||||
adaptiveRates: { [clientId: string]: number };
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步调度器
|
||||
* 负责优化同步数据的发送时机和优先级
|
||||
*/
|
||||
export class SyncScheduler extends EventEmitter {
|
||||
private logger = createLogger('SyncScheduler');
|
||||
private config: SyncSchedulerConfig;
|
||||
|
||||
/** 任务队列 */
|
||||
private taskQueue: ScheduledTask[] = [];
|
||||
private taskIdCounter = 0;
|
||||
|
||||
/** 客户端状态 */
|
||||
private clientStates = new Map<string, ClientScheduleState>();
|
||||
|
||||
/** 调度定时器 */
|
||||
private scheduleTimer: any = null;
|
||||
private adaptiveTimer: any = null;
|
||||
|
||||
/** 统计信息 */
|
||||
private stats: SchedulerStats = {
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
droppedTasks: 0,
|
||||
averageLatency: 0,
|
||||
bandwidthUtilization: 0,
|
||||
queueSizes: {},
|
||||
adaptiveRates: {}
|
||||
};
|
||||
|
||||
/** 自适应调整历史 */
|
||||
private latencyHistory = new Map<string, number[]>();
|
||||
private bandwidthHistory = new Map<string, number[]>();
|
||||
|
||||
constructor(config: Partial<SyncSchedulerConfig> = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
targetFPS: 60,
|
||||
maxLatency: 100,
|
||||
bandwidthLimit: 1024 * 1024, // 1MB/s
|
||||
priorityWeights: {
|
||||
high: 3,
|
||||
medium: 2,
|
||||
low: 1
|
||||
},
|
||||
adaptiveInterval: 1000,
|
||||
enableAdaptive: true,
|
||||
...config
|
||||
};
|
||||
|
||||
this.startScheduler();
|
||||
|
||||
if (this.config.enableAdaptive) {
|
||||
this.startAdaptiveAdjustment();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加客户端
|
||||
*/
|
||||
public addClient(clientId: string, bandwidth: number = this.config.bandwidthLimit): void {
|
||||
const clientState: ClientScheduleState = {
|
||||
clientId,
|
||||
bandwidth: {
|
||||
used: 0,
|
||||
limit: bandwidth,
|
||||
resetTime: Date.now() + 1000
|
||||
},
|
||||
latency: 0,
|
||||
queueSize: 0,
|
||||
lastSendTime: 0,
|
||||
adaptiveRate: 1000 / this.config.targetFPS // 初始发送间隔
|
||||
};
|
||||
|
||||
this.clientStates.set(clientId, clientState);
|
||||
this.latencyHistory.set(clientId, []);
|
||||
this.bandwidthHistory.set(clientId, []);
|
||||
|
||||
this.logger.debug(`客户端 ${clientId} 已添加到调度器`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除客户端
|
||||
*/
|
||||
public removeClient(clientId: string): void {
|
||||
this.clientStates.delete(clientId);
|
||||
this.latencyHistory.delete(clientId);
|
||||
this.bandwidthHistory.delete(clientId);
|
||||
|
||||
// 移除该客户端的所有任务
|
||||
this.taskQueue = this.taskQueue.filter(task => task.clientId !== clientId);
|
||||
|
||||
this.logger.debug(`客户端 ${clientId} 已从调度器移除`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度同步任务
|
||||
*/
|
||||
public schedule(batch: SyncBatch, clientId: string, priority: number = 5): string {
|
||||
const clientState = this.clientStates.get(clientId);
|
||||
if (!clientState) {
|
||||
this.logger.warn(`客户端 ${clientId} 不存在,无法调度任务`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const taskId = `task_${++this.taskIdCounter}`;
|
||||
const now = Date.now();
|
||||
|
||||
const task: ScheduledTask = {
|
||||
id: taskId,
|
||||
batch,
|
||||
clientId,
|
||||
priority,
|
||||
deadline: now + this.config.maxLatency,
|
||||
size: this.estimateBatchSize(batch),
|
||||
retryCount: 0,
|
||||
createdAt: now
|
||||
};
|
||||
|
||||
this.taskQueue.push(task);
|
||||
this.stats.totalTasks++;
|
||||
|
||||
// 按优先级和截止时间排序
|
||||
this.sortTaskQueue();
|
||||
|
||||
return taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客户端延迟
|
||||
*/
|
||||
public updateClientLatency(clientId: string, latency: number): void {
|
||||
const clientState = this.clientStates.get(clientId);
|
||||
if (clientState) {
|
||||
clientState.latency = latency;
|
||||
|
||||
// 记录延迟历史
|
||||
const history = this.latencyHistory.get(clientId) || [];
|
||||
history.push(latency);
|
||||
|
||||
// 保持最近50个记录
|
||||
if (history.length > 50) {
|
||||
history.shift();
|
||||
}
|
||||
|
||||
this.latencyHistory.set(clientId, history);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置客户端带宽限制
|
||||
*/
|
||||
public setClientBandwidth(clientId: string, bandwidth: number): void {
|
||||
const clientState = this.clientStates.get(clientId);
|
||||
if (clientState) {
|
||||
clientState.bandwidth.limit = bandwidth;
|
||||
this.logger.debug(`客户端 ${clientId} 带宽限制设置为: ${bandwidth} 字节/秒`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): SchedulerStats {
|
||||
// 更新队列大小统计
|
||||
for (const [clientId, clientState] of this.clientStates) {
|
||||
const clientTasks = this.taskQueue.filter(task => task.clientId === clientId);
|
||||
this.stats.queueSizes[clientId] = clientTasks.length;
|
||||
this.stats.adaptiveRates[clientId] = clientState.adaptiveRate;
|
||||
}
|
||||
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空任务队列
|
||||
*/
|
||||
public clearQueue(): number {
|
||||
const count = this.taskQueue.length;
|
||||
this.taskQueue.length = 0;
|
||||
this.stats.droppedTasks += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<SyncSchedulerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
|
||||
if (newConfig.enableAdaptive !== undefined) {
|
||||
if (newConfig.enableAdaptive) {
|
||||
this.startAdaptiveAdjustment();
|
||||
} else {
|
||||
this.stopAdaptiveAdjustment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁调度器
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.stopScheduler();
|
||||
this.stopAdaptiveAdjustment();
|
||||
this.taskQueue.length = 0;
|
||||
this.clientStates.clear();
|
||||
this.latencyHistory.clear();
|
||||
this.bandwidthHistory.clear();
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动调度器
|
||||
*/
|
||||
private startScheduler(): void {
|
||||
if (this.scheduleTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interval = 1000 / this.config.targetFPS;
|
||||
this.scheduleTimer = setInterval(() => {
|
||||
this.processTasks();
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止调度器
|
||||
*/
|
||||
private stopScheduler(): void {
|
||||
if (this.scheduleTimer) {
|
||||
clearInterval(this.scheduleTimer);
|
||||
this.scheduleTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动自适应调整
|
||||
*/
|
||||
private startAdaptiveAdjustment(): void {
|
||||
if (this.adaptiveTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.adaptiveTimer = setInterval(() => {
|
||||
this.performAdaptiveAdjustment();
|
||||
}, this.config.adaptiveInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止自适应调整
|
||||
*/
|
||||
private stopAdaptiveAdjustment(): void {
|
||||
if (this.adaptiveTimer) {
|
||||
clearInterval(this.adaptiveTimer);
|
||||
this.adaptiveTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理任务队列
|
||||
*/
|
||||
private processTasks(): void {
|
||||
const now = Date.now();
|
||||
const processedTasks: string[] = [];
|
||||
|
||||
for (const task of this.taskQueue) {
|
||||
const clientState = this.clientStates.get(task.clientId);
|
||||
if (!clientState) {
|
||||
processedTasks.push(task.id);
|
||||
this.stats.droppedTasks++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查截止时间
|
||||
if (now > task.deadline) {
|
||||
processedTasks.push(task.id);
|
||||
this.stats.droppedTasks++;
|
||||
this.logger.warn(`任务 ${task.id} 超时被丢弃`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查发送间隔
|
||||
if (now - clientState.lastSendTime < clientState.adaptiveRate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查带宽限制
|
||||
if (!this.checkBandwidthLimit(clientState, task.size)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 发送任务
|
||||
this.sendTask(task, clientState);
|
||||
processedTasks.push(task.id);
|
||||
this.stats.completedTasks++;
|
||||
}
|
||||
|
||||
// 移除已处理的任务
|
||||
this.taskQueue = this.taskQueue.filter(task => !processedTasks.includes(task.id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送任务
|
||||
*/
|
||||
private sendTask(task: ScheduledTask, clientState: ClientScheduleState): void {
|
||||
const now = Date.now();
|
||||
|
||||
// 更新客户端状态
|
||||
clientState.lastSendTime = now;
|
||||
clientState.bandwidth.used += task.size;
|
||||
|
||||
// 记录带宽历史
|
||||
const bandwidthHistory = this.bandwidthHistory.get(task.clientId) || [];
|
||||
bandwidthHistory.push(task.size);
|
||||
|
||||
if (bandwidthHistory.length > 50) {
|
||||
bandwidthHistory.shift();
|
||||
}
|
||||
|
||||
this.bandwidthHistory.set(task.clientId, bandwidthHistory);
|
||||
|
||||
// 发出事件
|
||||
this.emit('taskReady', task.batch, task.clientId);
|
||||
|
||||
this.logger.debug(`任务 ${task.id} 已发送给客户端 ${task.clientId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查带宽限制
|
||||
*/
|
||||
private checkBandwidthLimit(clientState: ClientScheduleState, taskSize: number): boolean {
|
||||
const now = Date.now();
|
||||
|
||||
// 重置带宽计数器
|
||||
if (now >= clientState.bandwidth.resetTime) {
|
||||
clientState.bandwidth.used = 0;
|
||||
clientState.bandwidth.resetTime = now + 1000;
|
||||
}
|
||||
|
||||
return clientState.bandwidth.used + taskSize <= clientState.bandwidth.limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算批次大小
|
||||
*/
|
||||
private estimateBatchSize(batch: SyncBatch): number {
|
||||
const propertyCount = Object.keys(batch.changes).length;
|
||||
return propertyCount * 50 + 200; // 基础开销 + 每个属性的估算大小
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序任务队列
|
||||
*/
|
||||
private sortTaskQueue(): void {
|
||||
this.taskQueue.sort((a, b) => {
|
||||
// 首先按优先级排序
|
||||
const priorityDiff = b.priority - a.priority;
|
||||
if (priorityDiff !== 0) {
|
||||
return priorityDiff;
|
||||
}
|
||||
|
||||
// 然后按截止时间排序
|
||||
return a.deadline - b.deadline;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行自适应调整
|
||||
*/
|
||||
private performAdaptiveAdjustment(): void {
|
||||
for (const [clientId, clientState] of this.clientStates) {
|
||||
this.adjustClientRate(clientId, clientState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整客户端发送频率
|
||||
*/
|
||||
private adjustClientRate(clientId: string, clientState: ClientScheduleState): void {
|
||||
const latencyHistory = this.latencyHistory.get(clientId) || [];
|
||||
const bandwidthHistory = this.bandwidthHistory.get(clientId) || [];
|
||||
|
||||
if (latencyHistory.length < 5) {
|
||||
return; // 数据不足,不进行调整
|
||||
}
|
||||
|
||||
// 计算平均延迟
|
||||
const avgLatency = latencyHistory.reduce((sum, lat) => sum + lat, 0) / latencyHistory.length;
|
||||
|
||||
// 计算带宽利用率
|
||||
const totalBandwidth = bandwidthHistory.reduce((sum, size) => sum + size, 0);
|
||||
const bandwidthUtilization = totalBandwidth / clientState.bandwidth.limit;
|
||||
|
||||
// 根据延迟和带宽利用率调整发送频率
|
||||
let adjustment = 1;
|
||||
|
||||
if (avgLatency > this.config.maxLatency) {
|
||||
// 延迟过高,降低发送频率
|
||||
adjustment = 1.2;
|
||||
} else if (avgLatency < this.config.maxLatency * 0.5) {
|
||||
// 延迟较低,可以提高发送频率
|
||||
adjustment = 0.9;
|
||||
}
|
||||
|
||||
if (bandwidthUtilization > 0.9) {
|
||||
// 带宽利用率过高,降低发送频率
|
||||
adjustment *= 1.1;
|
||||
} else if (bandwidthUtilization < 0.5) {
|
||||
// 带宽利用率较低,可以提高发送频率
|
||||
adjustment *= 0.95;
|
||||
}
|
||||
|
||||
// 应用调整
|
||||
clientState.adaptiveRate = Math.max(
|
||||
clientState.adaptiveRate * adjustment,
|
||||
1000 / (this.config.targetFPS * 2) // 最小间隔
|
||||
);
|
||||
|
||||
clientState.adaptiveRate = Math.min(
|
||||
clientState.adaptiveRate,
|
||||
1000 // 最大间隔1秒
|
||||
);
|
||||
|
||||
this.logger.debug(
|
||||
`客户端 ${clientId} 自适应调整: 延迟=${avgLatency.toFixed(1)}ms, ` +
|
||||
`带宽利用率=${(bandwidthUtilization * 100).toFixed(1)}%, ` +
|
||||
`新间隔=${clientState.adaptiveRate.toFixed(1)}ms`
|
||||
);
|
||||
}
|
||||
}
|
||||
6
packages/network-server/src/sync/index.ts
Normal file
6
packages/network-server/src/sync/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 服务端同步模块导出
|
||||
*/
|
||||
|
||||
export * from './NetworkScopeManager';
|
||||
export * from './SyncScheduler';
|
||||
539
packages/network-server/src/systems/SyncVarSystem.ts
Normal file
539
packages/network-server/src/systems/SyncVarSystem.ts
Normal file
@@ -0,0 +1,539 @@
|
||||
import { EntitySystem, createLogger } from '@esengine/ecs-framework';
|
||||
import {
|
||||
SyncVarManager,
|
||||
SyncBatch,
|
||||
SyncVarSerializer,
|
||||
NetworkScope,
|
||||
SyncMode,
|
||||
AuthorityType
|
||||
} from '@esengine/network-shared';
|
||||
import { NetworkServer } from '../core/NetworkServer';
|
||||
import { ConnectionManager } from '../core/ConnectionManager';
|
||||
|
||||
/**
|
||||
* 服务端SyncVar系统配置
|
||||
*/
|
||||
export interface SyncVarSystemConfig {
|
||||
/** 同步频率(毫秒) */
|
||||
syncRate: number;
|
||||
/** 最大同步批次大小 */
|
||||
maxBatchSize: number;
|
||||
/** 是否启用网络作用域优化 */
|
||||
enableScopeOptimization: boolean;
|
||||
/** 是否启用带宽限制 */
|
||||
enableBandwidthLimit: boolean;
|
||||
/** 每客户端最大带宽(字节/秒) */
|
||||
maxBandwidthPerClient: number;
|
||||
/** 是否启用优先级调度 */
|
||||
enablePriorityScheduling: boolean;
|
||||
/** 是否启用批量优化 */
|
||||
enableBatching: boolean;
|
||||
/** 批量超时时间(毫秒) */
|
||||
batchTimeout: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端同步状态
|
||||
*/
|
||||
interface ClientSyncState {
|
||||
clientId: string;
|
||||
lastSyncTime: number;
|
||||
pendingBatches: SyncBatch[];
|
||||
bandwidth: {
|
||||
used: number;
|
||||
limit: number;
|
||||
resetTime: number;
|
||||
};
|
||||
scope: {
|
||||
position?: { x: number; y: number; z: number };
|
||||
range: number;
|
||||
customFilter?: (batch: SyncBatch) => boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步统计信息
|
||||
*/
|
||||
interface SyncSystemStats {
|
||||
totalSyncs: number;
|
||||
totalBytes: number;
|
||||
clientCount: number;
|
||||
averageLatency: number;
|
||||
syncsPerSecond: number;
|
||||
droppedSyncs: number;
|
||||
scopeFiltered: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端SyncVar系统
|
||||
* 负责收集所有SyncVar变化并向客户端同步
|
||||
*/
|
||||
export class SyncVarSystem extends EntitySystem {
|
||||
private logger = createLogger('SyncVarSystem');
|
||||
private config: SyncVarSystemConfig;
|
||||
private syncVarManager: SyncVarManager;
|
||||
private serializer: SyncVarSerializer;
|
||||
private networkServer?: NetworkServer;
|
||||
private connectionManager?: ConnectionManager;
|
||||
|
||||
/** 客户端同步状态 */
|
||||
private clientStates = new Map<string, ClientSyncState>();
|
||||
|
||||
/** 待发送的批次队列 */
|
||||
private pendingBatches: SyncBatch[] = [];
|
||||
|
||||
/** 同步统计 */
|
||||
private stats: SyncSystemStats = {
|
||||
totalSyncs: 0,
|
||||
totalBytes: 0,
|
||||
clientCount: 0,
|
||||
averageLatency: 0,
|
||||
syncsPerSecond: 0,
|
||||
droppedSyncs: 0,
|
||||
scopeFiltered: 0
|
||||
};
|
||||
|
||||
/** 最后统计重置时间 */
|
||||
private lastStatsReset = Date.now();
|
||||
|
||||
/** 同步定时器 */
|
||||
private syncTimer: any = null;
|
||||
|
||||
constructor(config: Partial<SyncVarSystemConfig> = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
syncRate: 60, // 60ms = ~16fps
|
||||
maxBatchSize: 50,
|
||||
enableScopeOptimization: true,
|
||||
enableBandwidthLimit: true,
|
||||
maxBandwidthPerClient: 10240, // 10KB/s
|
||||
enablePriorityScheduling: true,
|
||||
enableBatching: true,
|
||||
batchTimeout: 16,
|
||||
...config
|
||||
};
|
||||
|
||||
this.syncVarManager = SyncVarManager.getInstance();
|
||||
this.serializer = new SyncVarSerializer({
|
||||
enableCompression: true,
|
||||
enableDeltaSync: true,
|
||||
enableBatching: this.config.enableBatching,
|
||||
batchTimeout: this.config.batchTimeout
|
||||
});
|
||||
|
||||
this.setupSyncVarManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化系统
|
||||
*/
|
||||
public override initialize(): void {
|
||||
super.initialize();
|
||||
|
||||
this.logger.info('SyncVar系统初始化');
|
||||
this.startSyncTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统更新
|
||||
*/
|
||||
protected override process(): void {
|
||||
this.updateClientStates();
|
||||
this.processScheduledSyncs();
|
||||
this.updateStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置网络服务器
|
||||
*/
|
||||
public setNetworkServer(server: NetworkServer): void {
|
||||
this.networkServer = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接管理器
|
||||
*/
|
||||
public setConnectionManager(manager: ConnectionManager): void {
|
||||
this.connectionManager = manager;
|
||||
|
||||
// 监听客户端连接事件
|
||||
manager.on('clientConnected', (clientId: string) => {
|
||||
this.addClient(clientId);
|
||||
});
|
||||
|
||||
manager.on('clientDisconnected', (clientId: string) => {
|
||||
this.removeClient(clientId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册网络实体
|
||||
*/
|
||||
public registerNetworkEntity(entity: any): void {
|
||||
this.syncVarManager.registerInstance(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销网络实体
|
||||
*/
|
||||
public unregisterNetworkEntity(entity: any): void {
|
||||
this.syncVarManager.unregisterInstance(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置客户端作用域
|
||||
*/
|
||||
public setClientScope(clientId: string, position?: { x: number; y: number; z: number }, range: number = 100): void {
|
||||
const clientState = this.clientStates.get(clientId);
|
||||
if (clientState) {
|
||||
clientState.scope.position = position;
|
||||
clientState.scope.range = range;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置客户端自定义过滤器
|
||||
*/
|
||||
public setClientFilter(clientId: string, filter: (batch: SyncBatch) => boolean): void {
|
||||
const clientState = this.clientStates.get(clientId);
|
||||
if (clientState) {
|
||||
clientState.scope.customFilter = filter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): SyncSystemStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this.stats = {
|
||||
totalSyncs: 0,
|
||||
totalBytes: 0,
|
||||
clientCount: this.clientStates.size,
|
||||
averageLatency: 0,
|
||||
syncsPerSecond: 0,
|
||||
droppedSyncs: 0,
|
||||
scopeFiltered: 0
|
||||
};
|
||||
this.lastStatsReset = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
public updateConfig(newConfig: Partial<SyncVarSystemConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
|
||||
if (newConfig.syncRate !== undefined) {
|
||||
this.restartSyncTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁系统
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.stopSyncTimer();
|
||||
this.clientStates.clear();
|
||||
this.pendingBatches.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SyncVar管理器事件
|
||||
*/
|
||||
private setupSyncVarManager(): void {
|
||||
this.syncVarManager.on('syncBatchReady', (batch: SyncBatch) => {
|
||||
this.enqueueBatch(batch);
|
||||
});
|
||||
|
||||
this.syncVarManager.on('syncError', (error: Error) => {
|
||||
this.logger.error('SyncVar同步错误:', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加客户端
|
||||
*/
|
||||
private addClient(clientId: string): void {
|
||||
const clientState: ClientSyncState = {
|
||||
clientId,
|
||||
lastSyncTime: 0,
|
||||
pendingBatches: [],
|
||||
bandwidth: {
|
||||
used: 0,
|
||||
limit: this.config.maxBandwidthPerClient,
|
||||
resetTime: Date.now() + 1000
|
||||
},
|
||||
scope: {
|
||||
range: 100
|
||||
}
|
||||
};
|
||||
|
||||
this.clientStates.set(clientId, clientState);
|
||||
this.stats.clientCount = this.clientStates.size;
|
||||
|
||||
this.logger.info(`客户端 ${clientId} 已添加到同步系统`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除客户端
|
||||
*/
|
||||
private removeClient(clientId: string): void {
|
||||
this.clientStates.delete(clientId);
|
||||
this.stats.clientCount = this.clientStates.size;
|
||||
|
||||
this.logger.info(`客户端 ${clientId} 已从同步系统移除`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将批次加入队列
|
||||
*/
|
||||
private enqueueBatch(batch: SyncBatch): void {
|
||||
this.pendingBatches.push(batch);
|
||||
|
||||
// 如果队列过长,移除最旧的批次
|
||||
if (this.pendingBatches.length > this.config.maxBatchSize * 2) {
|
||||
this.pendingBatches.shift();
|
||||
this.stats.droppedSyncs++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理计划的同步
|
||||
*/
|
||||
private processScheduledSyncs(): void {
|
||||
if (this.pendingBatches.length === 0 || this.clientStates.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const batchesToProcess = this.pendingBatches.splice(0, this.config.maxBatchSize);
|
||||
|
||||
for (const batch of batchesToProcess) {
|
||||
this.distributeBatchToClients(batch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将批次分发给客户端
|
||||
*/
|
||||
private distributeBatchToClients(batch: SyncBatch): void {
|
||||
for (const [clientId, clientState] of this.clientStates) {
|
||||
// 检查网络作用域
|
||||
if (this.config.enableScopeOptimization && !this.isInClientScope(batch, clientState)) {
|
||||
this.stats.scopeFiltered++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查带宽限制
|
||||
if (this.config.enableBandwidthLimit && !this.checkBandwidthLimit(clientId, batch)) {
|
||||
// 将批次添加到待发送队列
|
||||
clientState.pendingBatches.push(batch);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.sendBatchToClient(clientId, batch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查批次是否在客户端作用域内
|
||||
*/
|
||||
private isInClientScope(batch: SyncBatch, clientState: ClientSyncState): boolean {
|
||||
// 检查自定义过滤器
|
||||
if (clientState.scope.customFilter) {
|
||||
return clientState.scope.customFilter(batch);
|
||||
}
|
||||
|
||||
// 检查权限和作用域
|
||||
for (const [prop, scope] of Object.entries(batch.scopes)) {
|
||||
const authority = batch.authorities[prop];
|
||||
const syncMode = batch.syncModes[prop];
|
||||
|
||||
// 检查权限
|
||||
if (authority === AuthorityType.Client) {
|
||||
// 只有拥有权限的客户端才能看到
|
||||
if (batch.instanceId !== clientState.clientId) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查同步模式
|
||||
switch (syncMode) {
|
||||
case SyncMode.Owner:
|
||||
if (batch.instanceId !== clientState.clientId) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case SyncMode.Others:
|
||||
if (batch.instanceId === clientState.clientId) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case SyncMode.Nearby:
|
||||
if (!this.isNearby(batch, clientState)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查网络作用域
|
||||
switch (scope) {
|
||||
case NetworkScope.Owner:
|
||||
if (batch.instanceId !== clientState.clientId) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkScope.Nearby:
|
||||
if (!this.isNearby(batch, clientState)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在附近范围内
|
||||
*/
|
||||
private isNearby(batch: SyncBatch, clientState: ClientSyncState): boolean {
|
||||
// 简化实现,实际项目中需要根据具体的位置信息判断
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查带宽限制
|
||||
*/
|
||||
private checkBandwidthLimit(clientId: string, batch: SyncBatch): boolean {
|
||||
if (!this.config.enableBandwidthLimit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const clientState = this.clientStates.get(clientId);
|
||||
if (!clientState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
// 重置带宽计数器
|
||||
if (now >= clientState.bandwidth.resetTime) {
|
||||
clientState.bandwidth.used = 0;
|
||||
clientState.bandwidth.resetTime = now + 1000;
|
||||
}
|
||||
|
||||
// 估算批次大小
|
||||
const estimatedSize = this.estimateBatchSize(batch);
|
||||
|
||||
return clientState.bandwidth.used + estimatedSize <= clientState.bandwidth.limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算批次大小
|
||||
*/
|
||||
private estimateBatchSize(batch: SyncBatch): number {
|
||||
// 简化实现,根据变化属性数量估算
|
||||
const propertyCount = Object.keys(batch.changes).length;
|
||||
return propertyCount * 50; // 假设每个属性平均50字节
|
||||
}
|
||||
|
||||
/**
|
||||
* 向客户端发送批次
|
||||
*/
|
||||
private sendBatchToClient(clientId: string, batch: SyncBatch): void {
|
||||
if (!this.networkServer || !this.connectionManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const message = this.serializer.createSyncMessage(batch, 'server');
|
||||
this.networkServer.sendToClient(clientId, message);
|
||||
|
||||
// 更新统计
|
||||
const clientState = this.clientStates.get(clientId);
|
||||
if (clientState) {
|
||||
clientState.lastSyncTime = Date.now();
|
||||
const estimatedSize = this.estimateBatchSize(batch);
|
||||
clientState.bandwidth.used += estimatedSize;
|
||||
this.stats.totalBytes += estimatedSize;
|
||||
this.stats.totalSyncs++;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`向客户端 ${clientId} 发送同步数据失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客户端状态
|
||||
*/
|
||||
private updateClientStates(): void {
|
||||
const now = Date.now();
|
||||
|
||||
for (const [clientId, clientState] of this.clientStates) {
|
||||
// 处理待发送的批次
|
||||
if (clientState.pendingBatches.length > 0 &&
|
||||
this.checkBandwidthLimit(clientId, clientState.pendingBatches[0])) {
|
||||
|
||||
const batch = clientState.pendingBatches.shift()!;
|
||||
this.sendBatchToClient(clientId, batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新统计信息
|
||||
*/
|
||||
private updateStats(): void {
|
||||
const now = Date.now();
|
||||
const deltaTime = now - this.lastStatsReset;
|
||||
|
||||
if (deltaTime >= 1000) { // 每秒更新一次
|
||||
this.stats.syncsPerSecond = this.stats.totalSyncs / (deltaTime / 1000);
|
||||
this.lastStatsReset = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动同步定时器
|
||||
*/
|
||||
private startSyncTimer(): void {
|
||||
if (this.syncTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncTimer = setInterval(() => {
|
||||
this.processScheduledSyncs();
|
||||
}, this.config.syncRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止同步定时器
|
||||
*/
|
||||
private stopSyncTimer(): void {
|
||||
if (this.syncTimer) {
|
||||
clearInterval(this.syncTimer);
|
||||
this.syncTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启同步定时器
|
||||
*/
|
||||
private restartSyncTimer(): void {
|
||||
this.stopSyncTimer();
|
||||
this.startSyncTimer();
|
||||
}
|
||||
}
|
||||
5
packages/network-server/src/systems/index.ts
Normal file
5
packages/network-server/src/systems/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 服务端系统导出
|
||||
*/
|
||||
|
||||
export * from './SyncVarSystem';
|
||||
Reference in New Issue
Block a user