import { invoke } from '@tauri-apps/api/core'; import { SettingsService } from './SettingsService'; import { LogLevel } from '@esengine/ecs-framework'; export interface SystemPerformanceData { name: string; executionTime: number; entityCount: number; averageTime: number; percentage: number; } export interface RemoteEntity { id: number; name: string; enabled: boolean; active: boolean; activeInHierarchy: boolean; componentCount: number; componentTypes: string[]; parentId: number | null; childIds: number[]; depth: number; tag: number; updateOrder: number; } export interface RemoteComponentDetail { typeName: string; properties: Record; } export interface RemoteEntityDetails { id: number; name: string; enabled: boolean; active: boolean; activeInHierarchy: boolean; scene: string; sceneName: string; sceneType: string; componentCount: number; componentTypes: string[]; components: RemoteComponentDetail[]; parentName: string | null; } export interface ProfilerData { totalFrameTime: number; systems: SystemPerformanceData[]; entityCount: number; componentCount: number; fps: number; entities?: RemoteEntity[]; } type ProfilerDataListener = (data: ProfilerData) => void; export class ProfilerService { private ws: WebSocket | null = null; private isServerRunning = false; private wsPort: string; private listeners: Set = new Set(); private currentData: ProfilerData | null = null; private checkServerInterval: NodeJS.Timeout | null = null; private reconnectTimeout: NodeJS.Timeout | null = null; private clientIdMap: Map = new Map(); // 客户端地址 -> 客户端ID映射 private autoStart: boolean; constructor() { const settings = SettingsService.getInstance(); this.wsPort = settings.get('profiler.port', '8080'); this.autoStart = settings.get('profiler.autoStart', true); this.startServerCheck(); this.listenToSettingsChanges(); // 如果设置了自动启动,则启动服务器 if (this.autoStart) { this.manualStartServer(); } } private listenToSettingsChanges(): void { window.addEventListener('settings:changed', ((event: CustomEvent) => { const newPort = event.detail['profiler.port']; if (newPort && newPort !== this.wsPort) { this.wsPort = newPort; this.reconnectWithNewPort(); } }) as EventListener); } private async reconnectWithNewPort(): Promise { this.disconnect(); if (this.checkServerInterval) { clearInterval(this.checkServerInterval); this.checkServerInterval = null; } try { await invoke('stop_profiler_server'); this.isServerRunning = false; } catch (error) { console.error('[ProfilerService] Failed to stop server:', error); } this.startServerCheck(); } public subscribe(listener: ProfilerDataListener): () => void { this.listeners.add(listener); // 如果已有数据,立即发送给新订阅者 if (this.currentData) { listener(this.currentData); } return () => { this.listeners.delete(listener); }; } public isConnected(): boolean { return this.ws !== null && this.ws.readyState === WebSocket.OPEN; } public isServerActive(): boolean { return this.isServerRunning; } /** * 手动启动服务器 */ public async manualStartServer(): Promise { await this.startServer(); } /** * 手动停止服务器 */ public async manualStopServer(): Promise { try { await invoke('stop_profiler_server'); this.isServerRunning = false; this.disconnect(); } catch (error) { console.error('[ProfilerService] Failed to stop server:', error); } } private startServerCheck(): void { this.checkServerStatus(); this.checkServerInterval = setInterval(() => { this.checkServerStatus(); }, 2000); } private async checkServerStatus(): Promise { try { const status = await invoke('get_profiler_status'); const wasRunning = this.isServerRunning; this.isServerRunning = status; // 服务器启动了,尝试连接 if (status && !this.ws) { this.connectToServer(); } // 服务器从运行变为停止 if (wasRunning && !status) { this.disconnect(); } } catch (error) { this.isServerRunning = false; } } private async startServer(): Promise { try { const port = parseInt(this.wsPort); await invoke('start_profiler_server', { port }); this.isServerRunning = true; } catch (error) { console.error('[ProfilerService] Failed to start server:', error); } } private connectToServer(): void { if (this.ws) return; try { const ws = new WebSocket(`ws://localhost:${this.wsPort}`); ws.onopen = () => { this.notifyListeners(this.createEmptyData()); }; ws.onclose = () => { this.ws = null; // 通知监听器连接已断开 if (this.currentData) { this.notifyListeners(this.currentData); } // 如果服务器还在运行,尝试重连 if (this.isServerRunning && !this.reconnectTimeout) { this.reconnectTimeout = setTimeout(() => { this.reconnectTimeout = null; this.connectToServer(); }, 3000); } }; ws.onerror = (error) => { console.error('[ProfilerService] WebSocket error:', error); this.ws = null; // 通知监听器连接出错 if (this.currentData) { this.notifyListeners(this.currentData); } }; ws.onmessage = (event) => { try { const message = JSON.parse(event.data); if (message.type === 'debug_data' && message.data) { this.handleDebugData(message.data); } else if (message.type === 'get_raw_entity_list_response' && message.data) { this.handleRawEntityListResponse(message.data); } else if (message.type === 'get_entity_details_response' && message.data) { this.handleEntityDetailsResponse(message.data); } else if (message.type === 'log' && message.data) { this.handleRemoteLog(message.data); } } catch (error) { console.error('[ProfilerService] Failed to parse message:', error); } }; this.ws = ws; } catch (error) { console.error('[ProfilerService] Failed to create WebSocket:', error); } } public requestEntityDetails(entityId: number): void { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { return; } try { const request = { type: 'get_entity_details', requestId: `entity_details_${entityId}_${Date.now()}`, entityId }; this.ws.send(JSON.stringify(request)); } catch (error) { console.error('[ProfilerService] Failed to request entity details:', error); } } private handleDebugData(debugData: any): void { const performance = debugData.performance; if (!performance) return; const totalFrameTime = performance.frameTime || 0; const fps = totalFrameTime > 0 ? Math.round(1000 / totalFrameTime) : 0; let systems: SystemPerformanceData[] = []; if (performance.systemPerformance && Array.isArray(performance.systemPerformance)) { systems = performance.systemPerformance .map((sys: any) => ({ name: sys.systemName, executionTime: sys.lastExecutionTime || sys.averageTime || 0, entityCount: sys.entityCount || 0, averageTime: sys.averageTime || 0, percentage: 0 })) .sort((a: SystemPerformanceData, b: SystemPerformanceData) => b.executionTime - a.executionTime ); const totalTime = performance.frameTime || 1; systems.forEach((sys: SystemPerformanceData) => { sys.percentage = (sys.executionTime / totalTime) * 100; }); } const entityCount = debugData.entities?.totalEntities || debugData.entities?.totalCount || 0; const componentTypes = debugData.components?.types || []; const componentCount = componentTypes.length; this.currentData = { totalFrameTime, systems, entityCount, componentCount, fps, entities: [] }; this.notifyListeners(this.currentData); // 请求完整的实体列表 this.requestRawEntityList(); } private requestRawEntityList(): void { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { return; } try { const request = { type: 'get_raw_entity_list', requestId: `entity_list_${Date.now()}` }; this.ws.send(JSON.stringify(request)); } catch (error) { console.error('[ProfilerService] Failed to request entity list:', error); } } private handleRawEntityListResponse(data: any): void { if (!data || !Array.isArray(data)) { return; } const entities: RemoteEntity[] = data.map((e: any) => ({ id: e.id, name: e.name || `Entity ${e.id}`, enabled: e.enabled !== false, active: e.active !== false, activeInHierarchy: e.activeInHierarchy !== false, componentCount: e.componentCount || 0, componentTypes: e.componentTypes || [], parentId: e.parentId || null, childIds: e.childIds || [], depth: e.depth || 0, tag: e.tag || 0, updateOrder: e.updateOrder || 0 })); if (this.currentData) { this.currentData.entities = entities; this.notifyListeners(this.currentData); } } private handleEntityDetailsResponse(data: any): void { if (!data) { return; } const entityDetails: RemoteEntityDetails = { id: data.id, name: data.name || `Entity ${data.id}`, enabled: data.enabled !== false, active: data.active !== false, activeInHierarchy: data.activeInHierarchy !== false, scene: data.scene || '', sceneName: data.sceneName || '', sceneType: data.sceneType || '', componentCount: data.componentCount || 0, componentTypes: data.componentTypes || [], components: data.components || [], parentName: data.parentName || null }; window.dispatchEvent(new CustomEvent('profiler:entity-details', { detail: entityDetails })); } private handleRemoteLog(data: any): void { if (!data) { return; } const levelMap: Record = { 'debug': LogLevel.Debug, 'info': LogLevel.Info, 'warn': LogLevel.Warn, 'error': LogLevel.Error, 'fatal': LogLevel.Fatal }; const level = levelMap[data.level?.toLowerCase() || 'info'] || LogLevel.Info; let message = data.message || ''; if (typeof message === 'object') { try { message = JSON.stringify(message, null, 2); } catch { message = String(message); } } const clientId = data.clientId || data.client_id || 'unknown'; window.dispatchEvent(new CustomEvent('profiler:remote-log', { detail: { level, message, timestamp: data.timestamp ? new Date(data.timestamp) : new Date(), clientId } })); } private createEmptyData(): ProfilerData { return { totalFrameTime: 0, systems: [], entityCount: 0, componentCount: 0, fps: 0 }; } private notifyListeners(data: ProfilerData): void { this.listeners.forEach(listener => { try { listener(data); } catch (error) { console.error('[ProfilerService] Error in listener:', error); } }); } private disconnect(): void { const hadConnection = this.ws !== null; if (this.ws) { this.ws.close(); this.ws = null; } if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); this.reconnectTimeout = null; } // 如果有连接且手动断开,通知监听器 if (hadConnection && this.currentData) { this.notifyListeners(this.currentData); } } public destroy(): void { this.disconnect(); if (this.checkServerInterval) { clearInterval(this.checkServerInterval); this.checkServerInterval = null; } this.listeners.clear(); this.currentData = null; } }