实现SyncVar装饰器和组件同步

This commit is contained in:
YHH
2025-08-20 10:16:54 +08:00
parent 364bc4cdab
commit 0a1d7ac083
21 changed files with 5956 additions and 342 deletions

View 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);
}
}
}

View 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`
);
}
}

View File

@@ -0,0 +1,6 @@
/**
* 服务端同步模块导出
*/
export * from './NetworkScopeManager';
export * from './SyncScheduler';