chore: lint

This commit is contained in:
amhsirak
2025-03-04 22:40:51 +05:30
parent bcff0e5d83
commit 2f1aca94d9

View File

@@ -1,241 +1,241 @@
export class CanvasRenderer {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private offscreenCanvas: OffscreenCanvas | null = null;
private offscreenCtx: CanvasRenderingContext2D | null = null;
private lastFrameRequest: number | null = null;
private imageCache: Map<string, HTMLImageElement> = new Map();
private consecutiveFrameCount: number = 0;
private lastDrawTime: number = 0;
private memoryCheckCounter: number = 0;
private lastMemoryCheck: number = 0;
private memoryThreshold: number = 100000000; // 100MB
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
// Get 2D context with optimized settings
const ctx = canvas.getContext('2d', {
alpha: false, // Disable alpha for better performance
desynchronized: true, // Reduce latency when possible
});
if (!ctx) {
throw new Error('Could not get 2D context from canvas');
}
this.ctx = ctx;
// Apply performance optimizations
this.ctx.imageSmoothingEnabled = false;
// Set up offscreen canvas if supported
if (typeof OffscreenCanvas !== 'undefined') {
this.offscreenCanvas = new OffscreenCanvas(canvas.width, canvas.height);
const offCtx = this.offscreenCanvas.getContext('2d', {
alpha: false
});
if (offCtx) {
this.offscreenCtx = offCtx as unknown as CanvasRenderingContext2D;
this.offscreenCtx.imageSmoothingEnabled = false;
}
}
// Initial timestamp
this.lastDrawTime = performance.now();
this.lastMemoryCheck = performance.now();
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private offscreenCanvas: OffscreenCanvas | null = null;
private offscreenCtx: CanvasRenderingContext2D | null = null;
private lastFrameRequest: number | null = null;
private imageCache: Map<string, HTMLImageElement> = new Map();
private consecutiveFrameCount: number = 0;
private lastDrawTime: number = 0;
private memoryCheckCounter: number = 0;
private lastMemoryCheck: number = 0;
private memoryThreshold: number = 100000000; // 100MB
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
// Get 2D context with optimized settings
const ctx = canvas.getContext('2d', {
alpha: false, // Disable alpha for better performance
desynchronized: true, // Reduce latency when possible
});
if (!ctx) {
throw new Error('Could not get 2D context from canvas');
}
/**
* Renders a screenshot to the canvas, optimized for performance
*/
public drawScreenshot(
screenshot: string | ImageBitmap | HTMLImageElement,
x: number = 0,
y: number = 0,
width?: number,
height?: number
): void {
// Cancel any pending frame request
if (this.lastFrameRequest !== null) {
cancelAnimationFrame(this.lastFrameRequest);
}
// Check memory usage periodically
this.memoryCheckCounter++;
const now = performance.now();
if (this.memoryCheckCounter >= 30 || now - this.lastMemoryCheck > 5000) {
this.checkMemoryUsage();
this.memoryCheckCounter = 0;
this.lastMemoryCheck = now;
}
// Request a new frame
this.lastFrameRequest = requestAnimationFrame(() => {
this.renderFrame(screenshot, x, y, width, height);
this.ctx = ctx;
// Apply performance optimizations
this.ctx.imageSmoothingEnabled = false;
// Set up offscreen canvas if supported
if (typeof OffscreenCanvas !== 'undefined') {
this.offscreenCanvas = new OffscreenCanvas(canvas.width, canvas.height);
const offCtx = this.offscreenCanvas.getContext('2d', {
alpha: false
});
if (offCtx) {
this.offscreenCtx = offCtx as unknown as CanvasRenderingContext2D;
this.offscreenCtx.imageSmoothingEnabled = false;
}
}
private renderFrame(
screenshot: string | ImageBitmap | HTMLImageElement,
x: number,
y: number,
width?: number,
height?: number
): void {
// Target context (offscreen if available, otherwise main)
const targetCtx = this.offscreenCtx || this.ctx;
// Start timing the render
const startTime = performance.now();
const timeSinceLastDraw = startTime - this.lastDrawTime;
// Adaptive frame skipping for high-frequency updates
// If we're getting updates faster than 60fps and this isn't the first frame
if (timeSinceLastDraw < 16 && this.consecutiveFrameCount > 5) {
this.consecutiveFrameCount++;
// Skip some frames when we're getting excessive updates
if (this.consecutiveFrameCount % 2 !== 0) {
return;
// Initial timestamp
this.lastDrawTime = performance.now();
this.lastMemoryCheck = performance.now();
}
/**
* Renders a screenshot to the canvas, optimized for performance
*/
public drawScreenshot(
screenshot: string | ImageBitmap | HTMLImageElement,
x: number = 0,
y: number = 0,
width?: number,
height?: number
): void {
// Cancel any pending frame request
if (this.lastFrameRequest !== null) {
cancelAnimationFrame(this.lastFrameRequest);
}
// Check memory usage periodically
this.memoryCheckCounter++;
const now = performance.now();
if (this.memoryCheckCounter >= 30 || now - this.lastMemoryCheck > 5000) {
this.checkMemoryUsage();
this.memoryCheckCounter = 0;
this.lastMemoryCheck = now;
}
// Request a new frame
this.lastFrameRequest = requestAnimationFrame(() => {
this.renderFrame(screenshot, x, y, width, height);
});
}
private renderFrame(
screenshot: string | ImageBitmap | HTMLImageElement,
x: number,
y: number,
width?: number,
height?: number
): void {
// Target context (offscreen if available, otherwise main)
const targetCtx = this.offscreenCtx || this.ctx;
// Start timing the render
const startTime = performance.now();
const timeSinceLastDraw = startTime - this.lastDrawTime;
// Adaptive frame skipping for high-frequency updates
// If we're getting updates faster than 60fps and this isn't the first frame
if (timeSinceLastDraw < 16 && this.consecutiveFrameCount > 5) {
this.consecutiveFrameCount++;
// Skip some frames when we're getting excessive updates
if (this.consecutiveFrameCount % 2 !== 0) {
return;
}
} else {
this.consecutiveFrameCount = 0;
}
try {
if (typeof screenshot === 'string') {
// Check if we have this image in cache
let img = this.imageCache.get(screenshot);
if (!img) {
img = new Image();
img.src = screenshot;
this.imageCache.set(screenshot, img);
// If image isn't loaded yet, draw when it loads
if (!img.complete) {
img.onload = () => {
if (img) {
this.drawScreenshot(img, x, y, width, height);
}
};
return;
}
}
targetCtx.drawImage(
img,
x, y,
width || img.width,
height || img.height
);
} else {
this.consecutiveFrameCount = 0;
// Draw ImageBitmap or HTMLImageElement directly
targetCtx.drawImage(
screenshot,
x, y,
width || screenshot.width,
height || screenshot.height
);
}
try {
if (typeof screenshot === 'string') {
// Check if we have this image in cache
let img = this.imageCache.get(screenshot);
if (!img) {
img = new Image();
img.src = screenshot;
this.imageCache.set(screenshot, img);
// If image isn't loaded yet, draw when it loads
if (!img.complete) {
img.onload = () => {
if (img) {
this.drawScreenshot(img, x, y, width, height);
}
};
return;
}
}
targetCtx.drawImage(
img,
x, y,
width || img.width,
height || img.height
);
// If using offscreen canvas, copy to main canvas
if (this.offscreenCanvas && this.offscreenCtx) {
if ('transferToImageBitmap' in this.offscreenCanvas) {
// Use more efficient transfer when available
const bitmap = this.offscreenCanvas.transferToImageBitmap();
this.ctx.drawImage(bitmap, 0, 0);
} else {
// Draw ImageBitmap or HTMLImageElement directly
targetCtx.drawImage(
screenshot,
x, y,
width || screenshot.width,
height || screenshot.height
);
}
// If using offscreen canvas, copy to main canvas
if (this.offscreenCanvas && this.offscreenCtx) {
if ('transferToImageBitmap' in this.offscreenCanvas) {
// Use more efficient transfer when available
const bitmap = this.offscreenCanvas.transferToImageBitmap();
this.ctx.drawImage(bitmap, 0, 0);
} else {
// Fallback to drawImage
this.ctx.drawImage(this.offscreenCanvas, 0, 0);
}
}
// Update timestamp
this.lastDrawTime = performance.now();
} catch (error) {
console.error('Error rendering frame:', error);
}
}
/**
* Checks current memory usage and cleans up if necessary
*/
private checkMemoryUsage(): void {
if (window.performance && (performance as any).memory) {
const memory = (performance as any).memory;
if (memory.usedJSHeapSize > this.memoryThreshold) {
this.cleanupMemory();
// Fallback to drawImage
this.ctx.drawImage(this.offscreenCanvas, 0, 0);
}
}
// Update timestamp
this.lastDrawTime = performance.now();
} catch (error) {
console.error('Error rendering frame:', error);
}
/**
* Cleans up resources to reduce memory usage
*/
private cleanupMemory(): void {
// Limit image cache size
if (this.imageCache.size > 20) {
// Keep only the most recent 10 images
const keysToDelete = Array.from(this.imageCache.keys()).slice(0, this.imageCache.size - 10);
keysToDelete.forEach(key => {
this.imageCache.delete(key);
});
}
// Suggest garbage collection
if (window.gc) {
try {
window.gc();
} catch (e) {
// GC not available, ignore
}
}
/**
* Checks current memory usage and cleans up if necessary
*/
private checkMemoryUsage(): void {
if (window.performance && (performance as any).memory) {
const memory = (performance as any).memory;
if (memory.usedJSHeapSize > this.memoryThreshold) {
this.cleanupMemory();
}
}
/**
* Update canvas dimensions
*/
public updateCanvasSize(width: number, height: number): void {
this.canvas.width = width;
this.canvas.height = height;
// Re-apply context settings
this.ctx.imageSmoothingEnabled = false;
// Update offscreen canvas if available
if (this.offscreenCanvas) {
this.offscreenCanvas.width = width;
this.offscreenCanvas.height = height;
if (this.offscreenCtx) {
this.offscreenCtx.imageSmoothingEnabled = false;
}
}
/**
* Cleans up resources to reduce memory usage
*/
private cleanupMemory(): void {
// Limit image cache size
if (this.imageCache.size > 20) {
// Keep only the most recent 10 images
const keysToDelete = Array.from(this.imageCache.keys()).slice(0, this.imageCache.size - 10);
keysToDelete.forEach(key => {
this.imageCache.delete(key);
});
}
// Suggest garbage collection
if (window.gc) {
try {
window.gc();
} catch (e) {
// GC not available, ignore
}
}
/**
* Clean up resources
*/
public dispose(): void {
// Cancel any pending frame requests
if (this.lastFrameRequest !== null) {
cancelAnimationFrame(this.lastFrameRequest);
this.lastFrameRequest = null;
}
// Clear the image cache
this.imageCache.clear();
// Clear canvases
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (this.offscreenCtx && this.offscreenCanvas) {
this.offscreenCtx.clearRect(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height);
}
/**
* Update canvas dimensions
*/
public updateCanvasSize(width: number, height: number): void {
this.canvas.width = width;
this.canvas.height = height;
// Re-apply context settings
this.ctx.imageSmoothingEnabled = false;
// Update offscreen canvas if available
if (this.offscreenCanvas) {
this.offscreenCanvas.width = width;
this.offscreenCanvas.height = height;
if (this.offscreenCtx) {
this.offscreenCtx.imageSmoothingEnabled = false;
}
}
}
}
/**
* Clean up resources
*/
public dispose(): void {
// Cancel any pending frame requests
if (this.lastFrameRequest !== null) {
cancelAnimationFrame(this.lastFrameRequest);
this.lastFrameRequest = null;
}
// Clear the image cache
this.imageCache.clear();
// Clear canvases
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (this.offscreenCtx && this.offscreenCanvas) {
this.offscreenCtx.clearRect(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height);
}
}
}