import { useState, useEffect, useRef } from 'react'; import { Core } from '@esengine/ecs-framework'; import { Activity, BarChart3, Clock, Cpu, RefreshCw, Pause, Play, X, Wifi, WifiOff, Server, Search, Table2, TreePine } from 'lucide-react'; import { ProfilerService } from '../services/ProfilerService'; import '../styles/ProfilerWindow.css'; interface SystemPerformanceData { name: string; executionTime: number; entityCount: number; averageTime: number; minTime: number; maxTime: number; percentage: number; level: number; children?: SystemPerformanceData[]; isExpanded?: boolean; } interface ProfilerWindowProps { onClose: () => void; } type DataSource = 'local' | 'remote'; export function ProfilerWindow({ onClose }: ProfilerWindowProps) { const [systems, setSystems] = useState([]); const [totalFrameTime, setTotalFrameTime] = useState(0); const [isPaused, setIsPaused] = useState(false); const [sortBy, setSortBy] = useState<'time' | 'average' | 'name'>('time'); const [dataSource, setDataSource] = useState('local'); const [viewMode, setViewMode] = useState<'tree' | 'table'>('table'); const [searchQuery, setSearchQuery] = useState(''); const [isConnected, setIsConnected] = useState(false); const [isServerRunning, setIsServerRunning] = useState(false); const animationRef = useRef(); // Check ProfilerService connection status useEffect(() => { const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined; if (!profilerService) { return; } const checkStatus = () => { setIsConnected(profilerService.isConnected()); setIsServerRunning(profilerService.isServerActive()); }; checkStatus(); const interval = setInterval(checkStatus, 1000); return () => clearInterval(interval); }, []); const buildSystemTree = (flatSystems: Map, statsMap: Map): SystemPerformanceData[] => { const coreUpdate = flatSystems.get('Core.update'); const servicesUpdate = flatSystems.get('Services.update'); if (!coreUpdate) return []; const coreStats = statsMap.get('Core.update'); const coreNode: SystemPerformanceData = { name: 'Core.update', executionTime: coreUpdate.executionTime, entityCount: 0, averageTime: coreStats?.averageTime || 0, minTime: coreStats?.minTime || 0, maxTime: coreStats?.maxTime || 0, percentage: 100, level: 0, children: [], isExpanded: true }; if (servicesUpdate) { const servicesStats = statsMap.get('Services.update'); coreNode.children!.push({ name: 'Services.update', executionTime: servicesUpdate.executionTime, entityCount: 0, averageTime: servicesStats?.averageTime || 0, minTime: servicesStats?.minTime || 0, maxTime: servicesStats?.maxTime || 0, percentage: coreUpdate.executionTime > 0 ? (servicesUpdate.executionTime / coreUpdate.executionTime) * 100 : 0, level: 1, isExpanded: false }); } const sceneSystems: SystemPerformanceData[] = []; let sceneSystemsTotal = 0; for (const [name, data] of flatSystems.entries()) { if (name !== 'Core.update' && name !== 'Services.update') { const stats = statsMap.get(name); if (stats) { sceneSystems.push({ name, executionTime: data.executionTime, entityCount: data.entityCount, averageTime: stats.averageTime, minTime: stats.minTime, maxTime: stats.maxTime, percentage: 0, level: 1, isExpanded: false }); sceneSystemsTotal += data.executionTime; } } } sceneSystems.forEach(system => { system.percentage = coreUpdate.executionTime > 0 ? (system.executionTime / coreUpdate.executionTime) * 100 : 0; }); sceneSystems.sort((a, b) => b.executionTime - a.executionTime); coreNode.children!.push(...sceneSystems); return [coreNode]; }; // Subscribe to local performance data useEffect(() => { if (dataSource !== 'local') return; const updateProfilerData = () => { if (isPaused) { animationRef.current = requestAnimationFrame(updateProfilerData); return; } const coreInstance = Core.Instance; if (!coreInstance || !coreInstance._performanceMonitor?.isEnabled) { animationRef.current = requestAnimationFrame(updateProfilerData); return; } const performanceMonitor = coreInstance._performanceMonitor; const systemDataMap = performanceMonitor.getAllSystemData(); const systemStatsMap = performanceMonitor.getAllSystemStats(); const tree = buildSystemTree(systemDataMap, systemStatsMap); const coreData = systemDataMap.get('Core.update'); setSystems(tree); setTotalFrameTime(coreData?.executionTime || 0); animationRef.current = requestAnimationFrame(updateProfilerData); }; animationRef.current = requestAnimationFrame(updateProfilerData); return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [isPaused, sortBy, dataSource]); // Subscribe to remote performance data from ProfilerService useEffect(() => { if (dataSource !== 'remote') return; const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined; if (!profilerService) { console.warn('[ProfilerWindow] ProfilerService not available'); return; } const unsubscribe = profilerService.subscribe((data) => { if (isPaused) return; handleRemoteDebugData({ performance: { frameTime: data.totalFrameTime, systemPerformance: data.systems.map(sys => ({ systemName: sys.name, lastExecutionTime: sys.executionTime, averageTime: sys.averageTime, minTime: 0, maxTime: 0, entityCount: sys.entityCount, percentage: sys.percentage })) } }); }); return () => unsubscribe(); }, [dataSource, isPaused]); const handleReset = () => { if (dataSource === 'local') { const coreInstance = Core.Instance; if (coreInstance && coreInstance._performanceMonitor) { coreInstance._performanceMonitor.reset(); } } else { // Reset remote data setSystems([]); setTotalFrameTime(0); } }; const handleRemoteDebugData = (debugData: any) => { if (isPaused) return; const performance = debugData.performance; if (!performance) return; if (!performance.systemPerformance || !Array.isArray(performance.systemPerformance)) { return; } const flatSystemsMap = new Map(); const statsMap = new Map(); for (const system of performance.systemPerformance) { flatSystemsMap.set(system.systemName, { executionTime: system.lastExecutionTime || system.averageTime || 0, entityCount: system.entityCount || 0 }); statsMap.set(system.systemName, { averageTime: system.averageTime || 0, minTime: system.minTime || 0, maxTime: system.maxTime || 0 }); } const tree = buildSystemTree(flatSystemsMap, statsMap); setSystems(tree); setTotalFrameTime(performance.frameTime || 0); }; const handleDataSourceChange = (newSource: DataSource) => { if (newSource === 'remote' && dataSource === 'local') { // Switching to remote if (animationRef.current) { cancelAnimationFrame(animationRef.current); } } setDataSource(newSource); setSystems([]); setTotalFrameTime(0); }; const toggleExpand = (systemName: string) => { const toggleNode = (nodes: SystemPerformanceData[]): SystemPerformanceData[] => { return nodes.map(node => { if (node.name === systemName) { return { ...node, isExpanded: !node.isExpanded }; } if (node.children) { return { ...node, children: toggleNode(node.children) }; } return node; }); }; setSystems(toggleNode(systems)); }; const flattenTree = (nodes: SystemPerformanceData[]): SystemPerformanceData[] => { const result: SystemPerformanceData[] = []; for (const node of nodes) { result.push(node); if (node.isExpanded && node.children) { result.push(...flattenTree(node.children)); } } return result; }; const fps = totalFrameTime > 0 ? Math.round(1000 / totalFrameTime) : 0; const targetFrameTime = 16.67; const isOverBudget = totalFrameTime > targetFrameTime; let displaySystems = viewMode === 'tree' ? flattenTree(systems) : systems; // Apply search filter if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); if (viewMode === 'tree') { displaySystems = displaySystems.filter(sys => sys.name.toLowerCase().includes(query) ); } else { // For table view, flatten and filter const flatList: SystemPerformanceData[] = []; const flatten = (nodes: SystemPerformanceData[]) => { for (const node of nodes) { flatList.push(node); if (node.children) flatten(node.children); } }; flatten(systems); displaySystems = flatList.filter(sys => sys.name.toLowerCase().includes(query) ); } } else if (viewMode === 'table') { // For table view without search, flatten all const flatList: SystemPerformanceData[] = []; const flatten = (nodes: SystemPerformanceData[]) => { for (const node of nodes) { flatList.push(node); if (node.children) flatten(node.children); } }; flatten(systems); displaySystems = flatList; } return (
e.stopPropagation()}>

Performance Profiler

{isPaused && ( PAUSED )}
{dataSource === 'remote' && (
Port: 8080
{isConnected ? (
Connected
) : isServerRunning ? (
Waiting for game...
) : (
Server Off
)}
)} {dataSource === 'local' && (
Frame: {totalFrameTime.toFixed(2)}ms
FPS: {fps}
Systems: {systems.length}
)}
setSearchQuery(e.target.value)} className="search-input" />
{displaySystems.length === 0 ? (

No performance data available

{searchQuery ? 'No systems match your search' : 'Make sure Core debug mode is enabled and systems are running'}

) : viewMode === 'table' ? ( {displaySystems.map((system) => ( ))}
System Name Current Average Min Max % Entities
{system.name} targetFrameTime ? 'critical' : system.executionTime > targetFrameTime * 0.5 ? 'warning' : ''}`}> {system.executionTime.toFixed(2)}ms {system.averageTime.toFixed(2)}ms {system.minTime.toFixed(2)}ms {system.maxTime.toFixed(2)}ms {system.percentage.toFixed(1)}% {system.entityCount || '-'}
) : (
{displaySystems.map((system) => (
{system.children && system.children.length > 0 && ( )} {system.name} {system.entityCount > 0 && ( ({system.entityCount}) )}
targetFrameTime ? 'critical' : system.executionTime > targetFrameTime * 0.5 ? 'warning' : ''}`}> {system.executionTime.toFixed(2)}ms {system.percentage.toFixed(1)}%
Avg: {system.averageTime.toFixed(2)}ms Min: {system.minTime.toFixed(2)}ms Max: {system.maxTime.toFixed(2)}ms
))}
)}
Good (<8ms)
Warning (8-16ms)
Critical (>16ms)
); }