// performance-manager.ts — Performance monitoring and optimization system // TypeScript rewrite with proper type safety import { PerformanceStats } from './types'; class PerformanceManager { private frameTimes: number[] = []; private maxFrameHistory: number = 60; private lastFrameTime: number = 0; private fps: number = 60; private memoryWarnings: number = 0; private renderQuality: number = 1.0; private adaptiveMode: boolean = false; constructor() { this.setupMonitoring(); } private setupMonitoring(): void { let lastTime = performance.now(); let frameCount = 0; const monitorLoop = (currentTime: number) => { frameCount++; if (frameCount % 10 === 0) { const delta = currentTime - lastTime; const avgFrameTime = delta / 10; this.fps = Math.round(1000 / avgFrameTime); this.frameTimes.push(avgFrameTime); if (this.frameTimes.length > this.maxFrameHistory) { this.frameTimes.shift(); } this.analyzePerformance(); lastTime = currentTime; } requestAnimationFrame(monitorLoop); }; requestAnimationFrame(monitorLoop); if (window.performance && (performance as any).memory) { setInterval(() => this.checkMemory(), 2000); } } private analyzePerformance(): void { if (this.frameTimes.length < 10) return; const sorted = [...this.frameTimes].sort((a, b) => a - b); const avgFrameTime = sorted.reduce((a, b) => a + b, 0) / sorted.length; const p90FrameTime = sorted[Math.floor(sorted.length * 0.9)]; const frameBudget = 16; // 16ms for 60fps if (p90FrameTime > frameBudget * 1.5) { this.setRenderQuality(Math.max(0.5, this.renderQuality - 0.2)); this.adaptiveMode = true; } else if (p90FrameTime > frameBudget * 1.2) { this.setRenderQuality(Math.max(0.7, this.renderQuality - 0.1)); } else if (avgFrameTime < frameBudget * 0.8 && this.adaptiveMode) { this.setRenderQuality(Math.min(1.0, this.renderQuality + 0.05)); if (this.renderQuality >= 0.95) { this.adaptiveMode = false; } } } private checkMemory(): void { try { // Use type assertion for non-standard memory API const perf = performance as { memory?: { usedJSHeapSize: number; jsHeapSizeLimit: number } }; const memory = perf.memory; if (!memory) { throw new Error('Memory API not available'); } const usedHeap = memory.usedJSHeapSize; const heapLimit = memory.jsHeapSizeLimit; const usageRatio = usedHeap / heapLimit; if (usageRatio > 0.8) { this.memoryWarnings++; if (this.memoryWarnings > 3) { this.triggerMemoryCleanup(); this.memoryWarnings = 0; } this.setRenderQuality(Math.max(0.6, this.renderQuality - 0.1)); } else { this.memoryWarnings = Math.max(0, this.memoryWarnings - 0.5); } } catch (e) { console.warn('Memory monitoring not available:', e); } } private triggerMemoryCleanup(): void { if (window.gc) { window.gc(); } } setRenderQuality(quality: number): void { this.renderQuality = Math.max(0.3, Math.min(1.0, quality)); console.debug(`[Perf] Render quality adjusted to: ${(this.renderQuality * 100).toFixed(0)}%`); } getCurrentQuality(): number { return this.renderQuality; } isPerformanceCritical(): boolean { return this.adaptiveMode || this.fps < 45; } getPerformanceStats(): PerformanceStats { return { fps: this.fps, avgFrameTime: this.frameTimes.length > 0 ? this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length : 0, renderQuality: this.renderQuality, adaptiveMode: this.adaptiveMode }; } cleanup(): void { this.frameTimes = []; } } export { PerformanceManager };