135 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			135 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | export interface PooledCanvas { | ||
|  |   canvas: HTMLCanvasElement; | ||
|  |   ctx: CanvasRenderingContext2D; | ||
|  |   inUse: boolean; | ||
|  |   lastUsed: number; | ||
|  | } | ||
|  | 
 | ||
|  | export class CanvasPool { | ||
|  |   private static pool: PooledCanvas[] = []; | ||
|  |   private static maxPoolSize = 20; | ||
|  |   private static readonly MIN_CANVAS_SIZE = 256; | ||
|  |   private static readonly MAX_CANVAS_SIZE = 4096; | ||
|  | 
 | ||
|  |   public static acquire(width: number, height: number): PooledCanvas { | ||
|  |     // Try to find a suitable canvas from the pool
 | ||
|  |     const suitable = this.pool.find( | ||
|  |       (item) => !item.inUse && | ||
|  |       item.canvas.width >= width && | ||
|  |       item.canvas.height >= height && | ||
|  |       item.canvas.width <= width * 1.5 && | ||
|  |       item.canvas.height <= height * 1.5 | ||
|  |     ); | ||
|  | 
 | ||
|  |     if (suitable) { | ||
|  |       suitable.inUse = true; | ||
|  |       suitable.lastUsed = Date.now(); | ||
|  | 
 | ||
|  |       // Clear and resize if needed
 | ||
|  |       suitable.canvas.width = width; | ||
|  |       suitable.canvas.height = height; | ||
|  |       suitable.ctx.clearRect(0, 0, width, height); | ||
|  | 
 | ||
|  |       return suitable; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Create new canvas if pool not full
 | ||
|  |     if (this.pool.length < this.maxPoolSize) { | ||
|  |       const canvas = document.createElement('canvas'); | ||
|  |       const ctx = canvas.getContext('2d', { | ||
|  |         alpha: true, | ||
|  |         desynchronized: true, | ||
|  |       }) as CanvasRenderingContext2D; | ||
|  | 
 | ||
|  |       canvas.width = Math.min(Math.max(width, this.MIN_CANVAS_SIZE), this.MAX_CANVAS_SIZE); | ||
|  |       canvas.height = Math.min(Math.max(height, this.MIN_CANVAS_SIZE), this.MAX_CANVAS_SIZE); | ||
|  | 
 | ||
|  |       const pooledCanvas: PooledCanvas = { | ||
|  |         canvas, | ||
|  |         ctx, | ||
|  |         inUse: true, | ||
|  |         lastUsed: Date.now(), | ||
|  |       }; | ||
|  | 
 | ||
|  |       this.pool.push(pooledCanvas); | ||
|  |       return pooledCanvas; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Evict and reuse least recently used canvas
 | ||
|  |     const lru = this.pool | ||
|  |       .filter((item) => !item.inUse) | ||
|  |       .sort((a, b) => a.lastUsed - b.lastUsed)[0]; | ||
|  | 
 | ||
|  |     if (lru) { | ||
|  |       lru.canvas.width = width; | ||
|  |       lru.canvas.height = height; | ||
|  |       lru.ctx.clearRect(0, 0, width, height); | ||
|  |       lru.inUse = true; | ||
|  |       lru.lastUsed = Date.now(); | ||
|  |       return lru; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Fallback: create temporary canvas (shouldn't normally happen)
 | ||
|  |     const canvas = document.createElement('canvas'); | ||
|  |     const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; | ||
|  |     canvas.width = width; | ||
|  |     canvas.height = height; | ||
|  | 
 | ||
|  |     return { | ||
|  |       canvas, | ||
|  |       ctx, | ||
|  |       inUse: true, | ||
|  |       lastUsed: Date.now(), | ||
|  |     }; | ||
|  |   } | ||
|  | 
 | ||
|  |   public static release(pooledCanvas: PooledCanvas) { | ||
|  |     if (this.pool.includes(pooledCanvas)) { | ||
|  |       pooledCanvas.inUse = false; | ||
|  |       // Clear canvas to free memory
 | ||
|  |       pooledCanvas.ctx.clearRect(0, 0, pooledCanvas.canvas.width, pooledCanvas.canvas.height); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   public static releaseAll() { | ||
|  |     for (const item of this.pool) { | ||
|  |       item.inUse = false; | ||
|  |       item.ctx.clearRect(0, 0, item.canvas.width, item.canvas.height); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   public static destroy() { | ||
|  |     for (const item of this.pool) { | ||
|  |       item.canvas.width = 0; | ||
|  |       item.canvas.height = 0; | ||
|  |     } | ||
|  |     this.pool = []; | ||
|  |   } | ||
|  | 
 | ||
|  |   public static getStats() { | ||
|  |     return { | ||
|  |       poolSize: this.pool.length, | ||
|  |       maxPoolSize: this.maxPoolSize, | ||
|  |       inUse: this.pool.filter((item) => item.inUse).length, | ||
|  |       available: this.pool.filter((item) => !item.inUse).length, | ||
|  |     }; | ||
|  |   } | ||
|  | 
 | ||
|  |   public static adjustPoolSize(newSize: number) { | ||
|  |     if (newSize < this.pool.length) { | ||
|  |       // Remove excess canvases
 | ||
|  |       const toRemove = this.pool.length - newSize; | ||
|  |       const removed = this.pool | ||
|  |         .filter((item) => !item.inUse) | ||
|  |         .slice(0, toRemove); | ||
|  | 
 | ||
|  |       for (const item of removed) { | ||
|  |         const index = this.pool.indexOf(item); | ||
|  |         if (index > -1) { | ||
|  |           this.pool.splice(index, 1); | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |     this.maxPoolSize = newSize; | ||
|  |   } | ||
|  | } |