File size: 10,272 Bytes
6a03d7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// 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
})