Spaces:
Sleeping
Sleeping
| // Performance Optimization System for Web Audio Visualizer | |
| // Optimized for Hugging Face Spaces free tier deployment | |
| interface PerformanceMetrics { | |
| fps: number | |
| frameTime: number | |
| memoryUsage: number | |
| cpuUsage: number | |
| gpuMemory: number | |
| particleCount: number | |
| lastUpdate: number | |
| } | |
| interface OptimizationSettings { | |
| targetFPS: number | |
| minFPS: number | |
| maxParticles: number | |
| minParticles: number | |
| adaptiveQualityStep: number | |
| memoryThreshold: number | |
| cpuThreshold: number | |
| } | |
| interface CacheEntry { | |
| data: any | |
| timestamp: number | |
| accessCount: number | |
| size: number | |
| } | |
| export class PerformanceOptimizer { | |
| private metrics: PerformanceMetrics | |
| private settings: OptimizationSettings | |
| private frameHistory: number[] = [] | |
| private lastFrameTime: number = 0 | |
| private adaptiveLevel: number = 1.0 // 1.0 = max quality, 0.1 = min quality | |
| // Caching system | |
| private shaderCache: Map<string, any> = new Map() | |
| private geometryCache: Map<string, any> = new Map() | |
| private materialCache: Map<string, any> = new Map() | |
| private calculationCache: Map<string, CacheEntry> = new Map() | |
| // Object pools for reuse | |
| private bufferPool: Map<string, Float32Array[]> = new Map() | |
| // Performance monitoring | |
| private performanceObserver: PerformanceObserver | null = null | |
| private memoryInfo: any = null | |
| constructor(settings: Partial<OptimizationSettings> = {}) { | |
| this.settings = { | |
| targetFPS: 60, | |
| minFPS: 30, | |
| maxParticles: 15000, | |
| minParticles: 3000, | |
| adaptiveQualityStep: 0.05, | |
| memoryThreshold: 100 * 1024 * 1024, // 100MB | |
| cpuThreshold: 80, // 80% CPU usage | |
| ...settings | |
| } | |
| this.metrics = { | |
| fps: 60, | |
| frameTime: 16.67, | |
| memoryUsage: 0, | |
| cpuUsage: 0, | |
| gpuMemory: 0, | |
| particleCount: this.settings.maxParticles, | |
| lastUpdate: performance.now() | |
| } | |
| this.initializePerformanceMonitoring() | |
| } | |
| private initializePerformanceMonitoring() { | |
| // Modern performance monitoring | |
| if ('PerformanceObserver' in window) { | |
| this.performanceObserver = new PerformanceObserver((_list) => { | |
| // Performance entries are processed in updatePerformance method | |
| }) | |
| try { | |
| this.performanceObserver.observe({ entryTypes: ['measure'] }) | |
| } catch (e) { | |
| console.warn('Performance monitoring not fully supported') | |
| } | |
| } | |
| // Memory monitoring for supported browsers | |
| if ('memory' in performance) { | |
| this.memoryInfo = (performance as any).memory | |
| } | |
| } | |
| // Adaptive quality management | |
| updatePerformance(currentTime: number): void { | |
| const deltaTime = currentTime - this.lastFrameTime | |
| this.lastFrameTime = currentTime | |
| if (deltaTime > 0) { | |
| const currentFPS = 1000 / deltaTime | |
| this.frameHistory.push(currentFPS) | |
| // Keep only last 60 frames for rolling average | |
| if (this.frameHistory.length > 60) { | |
| this.frameHistory.shift() | |
| } | |
| // Calculate average FPS | |
| const avgFPS = this.frameHistory.reduce((a, b) => a + b, 0) / this.frameHistory.length | |
| this.metrics.fps = Math.round(avgFPS) | |
| this.metrics.frameTime = deltaTime | |
| // Update memory usage if available | |
| if (this.memoryInfo) { | |
| this.metrics.memoryUsage = this.memoryInfo.usedJSHeapSize | |
| this.metrics.gpuMemory = this.memoryInfo.totalJSHeapSize | |
| } | |
| // Adaptive quality adjustment | |
| this.adjustQualityLevel() | |
| } | |
| } | |
| private adjustQualityLevel(): void { | |
| const { fps } = this.metrics | |
| const { targetFPS, minFPS, adaptiveQualityStep } = this.settings | |
| if (fps < minFPS) { | |
| // Performance is bad, reduce quality aggressively | |
| this.adaptiveLevel = Math.max(0.1, this.adaptiveLevel - adaptiveQualityStep * 2) | |
| console.warn(`🚨 Performance low (${fps} FPS), reducing quality to ${(this.adaptiveLevel * 100).toFixed(0)}%`) | |
| } else if (fps < targetFPS) { | |
| // Performance is below target, reduce quality gradually | |
| this.adaptiveLevel = Math.max(0.1, this.adaptiveLevel - adaptiveQualityStep) | |
| } else if (fps > targetFPS + 10 && this.adaptiveLevel < 1.0) { | |
| // Performance is good, increase quality gradually | |
| this.adaptiveLevel = Math.min(1.0, this.adaptiveLevel + adaptiveQualityStep * 0.5) | |
| } | |
| // Update particle count based on quality level | |
| const targetParticles = Math.floor( | |
| this.settings.minParticles + | |
| (this.settings.maxParticles - this.settings.minParticles) * this.adaptiveLevel | |
| ) | |
| this.metrics.particleCount = targetParticles | |
| } | |
| // Smart caching system | |
| getCachedCalculation(key: string, calculator: () => any, ttl: number = 1000): any { | |
| const cached = this.calculationCache.get(key) | |
| const now = performance.now() | |
| if (cached && (now - cached.timestamp) < ttl) { | |
| cached.accessCount++ | |
| return cached.data | |
| } | |
| // Calculate new value | |
| const data = calculator() | |
| this.calculationCache.set(key, { | |
| data, | |
| timestamp: now, | |
| accessCount: 1, | |
| size: this.estimateSize(data) | |
| }) | |
| // Clean cache if it gets too large | |
| this.cleanCache() | |
| return data | |
| } | |
| // Shader caching for Three.js materials | |
| getCachedShader(vertexShader: string, fragmentShader: string, uniforms: any): any { | |
| const key = this.hashShader(vertexShader, fragmentShader) | |
| if (this.shaderCache.has(key)) { | |
| const cached = this.shaderCache.get(key) | |
| // Update uniforms on cached shader | |
| Object.assign(cached.uniforms, uniforms) | |
| return cached | |
| } | |
| // Create new shader (this would be done in the component) | |
| return null // Component will create and cache | |
| } | |
| cacheShader(vertexShader: string, fragmentShader: string, material: any): void { | |
| const key = this.hashShader(vertexShader, fragmentShader) | |
| this.shaderCache.set(key, material) | |
| } | |
| // Geometry and buffer pooling | |
| getPooledBuffer(type: string, size: number): Float32Array { | |
| const poolKey = `${type}_${size}` | |
| if (!this.bufferPool.has(poolKey)) { | |
| this.bufferPool.set(poolKey, []) | |
| } | |
| const pool = this.bufferPool.get(poolKey)! | |
| if (pool.length > 0) { | |
| const buffer = pool.pop()! | |
| buffer.fill(0) // Reset buffer | |
| return buffer | |
| } | |
| // Create new buffer if pool is empty | |
| return new Float32Array(size) | |
| } | |
| returnPooledBuffer(type: string, size: number, buffer: Float32Array): void { | |
| const poolKey = `${type}_${size}` | |
| if (!this.bufferPool.has(poolKey)) { | |
| this.bufferPool.set(poolKey, []) | |
| } | |
| const pool = this.bufferPool.get(poolKey)! | |
| // Limit pool size to prevent memory bloat | |
| if (pool.length < 5) { | |
| pool.push(buffer) | |
| } | |
| } | |
| // Level of Detail (LOD) calculations | |
| getLODSettings(distance: number, importance: number = 1.0): { | |
| particleSize: number | |
| updateFrequency: number | |
| detailLevel: number | |
| } { | |
| const distanceFactor = Math.max(0.1, Math.min(1.0, 5.0 / distance)) | |
| const qualityFactor = this.adaptiveLevel * importance | |
| return { | |
| particleSize: distanceFactor * qualityFactor, | |
| updateFrequency: Math.max(1, Math.floor(10 / (distanceFactor * qualityFactor))), | |
| detailLevel: distanceFactor * qualityFactor | |
| } | |
| } | |
| // Frame skipping for heavy operations | |
| shouldSkipFrame(_operation: string, frequency: number): boolean { | |
| const frameCount = Math.floor(performance.now() / 16.67) // Approximate frame count | |
| const skipPattern = Math.max(1, Math.floor(frequency / this.adaptiveLevel)) | |
| return frameCount % skipPattern !== 0 | |
| } | |
| // Memory management | |
| private cleanCache(): void { | |
| const now = performance.now() | |
| const maxCacheAge = 30000 // 30 seconds | |
| const maxCacheSize = 50 * 1024 * 1024 // 50MB | |
| let totalSize = 0 | |
| const entries = Array.from(this.calculationCache.entries()) | |
| // Calculate total cache size | |
| entries.forEach(([_, entry]) => { | |
| totalSize += entry.size | |
| }) | |
| // Clean old or least used entries if cache is too large | |
| if (totalSize > maxCacheSize) { | |
| entries | |
| .sort((a, b) => { | |
| const scoreA = a[1].accessCount / (now - a[1].timestamp) | |
| const scoreB = b[1].accessCount / (now - b[1].timestamp) | |
| return scoreA - scoreB // Lower score = less valuable | |
| }) | |
| .slice(0, Math.floor(entries.length * 0.3)) // Remove 30% least valuable | |
| .forEach(([key, _]) => { | |
| this.calculationCache.delete(key) | |
| }) | |
| } | |
| // Remove old entries | |
| entries.forEach(([key, entry]) => { | |
| if (now - entry.timestamp > maxCacheAge) { | |
| this.calculationCache.delete(key) | |
| } | |
| }) | |
| } | |
| // Utility functions | |
| private hashShader(vertex: string, fragment: string): string { | |
| // Simple hash function for shader caching | |
| let hash = 0 | |
| const str = vertex + fragment | |
| for (let i = 0; i < str.length; i++) { | |
| const char = str.charCodeAt(i) | |
| hash = ((hash << 5) - hash) + char | |
| hash = hash & hash // Convert to 32-bit integer | |
| } | |
| return hash.toString() | |
| } | |
| private estimateSize(obj: any): number { | |
| // Rough size estimation for caching | |
| const str = JSON.stringify(obj) | |
| return str.length * 2 // Approximate bytes (UTF-16) | |
| } | |
| // Public getters | |
| getMetrics(): PerformanceMetrics { | |
| return { ...this.metrics } | |
| } | |
| getAdaptiveLevel(): number { | |
| return this.adaptiveLevel | |
| } | |
| getOptimalParticleCount(baseCount: number): number { | |
| return Math.floor(baseCount * this.adaptiveLevel) | |
| } | |
| getOptimalUpdateFrequency(baseFrequency: number): number { | |
| return Math.max(1, Math.floor(baseFrequency / this.adaptiveLevel)) | |
| } | |
| // Cleanup | |
| dispose(): void { | |
| if (this.performanceObserver) { | |
| this.performanceObserver.disconnect() | |
| } | |
| this.shaderCache.clear() | |
| this.geometryCache.clear() | |
| this.materialCache.clear() | |
| this.calculationCache.clear() | |
| this.bufferPool.clear() | |
| } | |
| } | |
| // Singleton instance for global use | |
| export const performanceOptimizer = new PerformanceOptimizer({ | |
| targetFPS: 60, | |
| minFPS: 25, // Lower threshold for Hugging Face Spaces | |
| maxParticles: 8000, // Reduced for CPU deployment | |
| minParticles: 2000, | |
| adaptiveQualityStep: 0.08, | |
| memoryThreshold: 80 * 1024 * 1024, // 80MB for free tier | |
| cpuThreshold: 75 | |
| }) |