Files
dees-catalog/ts_web/elements/dees-pdf-shared/CanvasPool.ts

135 lines
3.6 KiB
TypeScript
Raw Normal View History

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;
}
}