|
|
|
|
|
|
|
|
|
|
|
export class LRUCache<K, V> { |
|
private cache = new Map<K, V>(); |
|
private readonly maxSize: number; |
|
|
|
constructor(maxSize: number = 100) { |
|
this.maxSize = maxSize; |
|
} |
|
|
|
get(key: K): V | undefined { |
|
const value = this.cache.get(key); |
|
if (value !== undefined) { |
|
|
|
this.cache.delete(key); |
|
this.cache.set(key, value); |
|
} |
|
return value; |
|
} |
|
|
|
set(key: K, value: V): void { |
|
if (this.cache.has(key)) { |
|
this.cache.delete(key); |
|
} else if (this.cache.size >= this.maxSize) { |
|
|
|
const firstKey = this.cache.keys().next().value; |
|
this.cache.delete(firstKey); |
|
} |
|
this.cache.set(key, value); |
|
} |
|
|
|
clear(): void { |
|
this.cache.clear(); |
|
} |
|
|
|
size(): number { |
|
return this.cache.size; |
|
} |
|
} |
|
|
|
|
|
export function debounce<T extends (...args: any[]) => any>( |
|
func: T, |
|
wait: number, |
|
immediate?: boolean |
|
): (...args: Parameters<T>) => void { |
|
let timeout: NodeJS.Timeout | null = null; |
|
|
|
return function executedFunction(...args: Parameters<T>) { |
|
const later = () => { |
|
timeout = null; |
|
if (!immediate) func(...args); |
|
}; |
|
|
|
const callNow = immediate && !timeout; |
|
|
|
if (timeout) clearTimeout(timeout); |
|
timeout = setTimeout(later, wait); |
|
|
|
if (callNow) func(...args); |
|
}; |
|
} |
|
|
|
|
|
export function throttle<T extends (...args: any[]) => any>( |
|
func: T, |
|
limit: number |
|
): (...args: Parameters<T>) => void { |
|
let inThrottle: boolean = false; |
|
|
|
return function executedFunction(...args: Parameters<T>) { |
|
if (!inThrottle) { |
|
func(...args); |
|
inThrottle = true; |
|
setTimeout(() => inThrottle = false, limit); |
|
} |
|
}; |
|
} |
|
|
|
|
|
export function memoize<T extends (...args: any[]) => any>( |
|
fn: T, |
|
getKey?: (...args: Parameters<T>) => string |
|
): T & { cache: LRUCache<string, ReturnType<T>>; clearCache: () => void } { |
|
const cache = new LRUCache<string, ReturnType<T>>(50); |
|
|
|
const memoized = function (...args: Parameters<T>): ReturnType<T> { |
|
const key = getKey ? getKey(...args) : JSON.stringify(args); |
|
|
|
const cached = cache.get(key); |
|
if (cached !== undefined) { |
|
return cached; |
|
} |
|
|
|
const result = fn(...args); |
|
cache.set(key, result); |
|
return result; |
|
} as T & { cache: LRUCache<string, ReturnType<T>>; clearCache: () => void }; |
|
|
|
memoized.cache = cache; |
|
memoized.clearCache = () => cache.clear(); |
|
|
|
return memoized; |
|
} |
|
|
|
|
|
export class WorkerPool { |
|
private workers: Worker[] = []; |
|
private queue: Array<{ |
|
resolve: (value: any) => void; |
|
reject: (error: any) => void; |
|
data: any; |
|
}> = []; |
|
private busyWorkers = new Set<Worker>(); |
|
|
|
constructor(workerScript: string, poolSize: number = navigator.hardwareConcurrency || 4) { |
|
for (let i = 0; i < poolSize; i++) { |
|
const worker = new Worker(workerScript); |
|
worker.onmessage = (e) => this.handleWorkerMessage(worker, e); |
|
worker.onerror = (e) => this.handleWorkerError(worker, e); |
|
this.workers.push(worker); |
|
} |
|
} |
|
|
|
execute<T>(data: any): Promise<T> { |
|
return new Promise((resolve, reject) => { |
|
this.queue.push({ resolve, reject, data }); |
|
this.processQueue(); |
|
}); |
|
} |
|
|
|
private processQueue(): void { |
|
if (this.queue.length === 0) return; |
|
|
|
const availableWorker = this.workers.find(w => !this.busyWorkers.has(w)); |
|
if (!availableWorker) return; |
|
|
|
const task = this.queue.shift()!; |
|
this.busyWorkers.add(availableWorker); |
|
|
|
(availableWorker as any)._currentTask = task; |
|
availableWorker.postMessage(task.data); |
|
} |
|
|
|
private handleWorkerMessage(worker: Worker, e: MessageEvent): void { |
|
const task = (worker as any)._currentTask; |
|
if (task) { |
|
task.resolve(e.data); |
|
this.busyWorkers.delete(worker); |
|
delete (worker as any)._currentTask; |
|
this.processQueue(); |
|
} |
|
} |
|
|
|
private handleWorkerError(worker: Worker, e: ErrorEvent): void { |
|
const task = (worker as any)._currentTask; |
|
if (task) { |
|
task.reject(e); |
|
this.busyWorkers.delete(worker); |
|
delete (worker as any)._currentTask; |
|
this.processQueue(); |
|
} |
|
} |
|
|
|
terminate(): void { |
|
this.workers.forEach(worker => worker.terminate()); |
|
this.workers = []; |
|
this.queue = []; |
|
this.busyWorkers.clear(); |
|
} |
|
} |
|
|
|
|
|
export class LazyImageLoader { |
|
private observer: IntersectionObserver; |
|
private images = new Set<HTMLImageElement>(); |
|
|
|
constructor(rootMargin: string = '50px') { |
|
this.observer = new IntersectionObserver( |
|
(entries) => { |
|
entries.forEach(entry => { |
|
if (entry.isIntersecting) { |
|
const img = entry.target as HTMLImageElement; |
|
this.loadImage(img); |
|
} |
|
}); |
|
}, |
|
{ rootMargin } |
|
); |
|
} |
|
|
|
observe(img: HTMLImageElement): void { |
|
this.images.add(img); |
|
this.observer.observe(img); |
|
} |
|
|
|
unobserve(img: HTMLImageElement): void { |
|
this.images.delete(img); |
|
this.observer.unobserve(img); |
|
} |
|
|
|
private loadImage(img: HTMLImageElement): void { |
|
const src = img.dataset.src; |
|
if (src) { |
|
img.src = src; |
|
img.onload = () => { |
|
img.classList.add('loaded'); |
|
this.observer.unobserve(img); |
|
}; |
|
} |
|
} |
|
|
|
disconnect(): void { |
|
this.observer.disconnect(); |
|
this.images.clear(); |
|
} |
|
} |
|
|
|
|
|
export class PerformanceMonitor { |
|
private metrics = new Map<string, number[]>(); |
|
private observers: PerformanceObserver[] = []; |
|
|
|
constructor() { |
|
this.setupObservers(); |
|
} |
|
|
|
private setupObservers(): void { |
|
if ('PerformanceObserver' in window) { |
|
|
|
try { |
|
const longTaskObserver = new PerformanceObserver((list) => { |
|
for (const entry of list.getEntries()) { |
|
this.addMetric('long-task', entry.duration); |
|
} |
|
}); |
|
longTaskObserver.observe({ entryTypes: ['longtask'] }); |
|
this.observers.push(longTaskObserver); |
|
} catch (e) { |
|
|
|
} |
|
|
|
|
|
try { |
|
const layoutShiftObserver = new PerformanceObserver((list) => { |
|
for (const entry of list.getEntries()) { |
|
if ((entry as any).value && !(entry as any).hadRecentInput) { |
|
this.addMetric('layout-shift', (entry as any).value); |
|
} |
|
} |
|
}); |
|
layoutShiftObserver.observe({ entryTypes: ['layout-shift'] }); |
|
this.observers.push(layoutShiftObserver); |
|
} catch (e) { |
|
|
|
} |
|
} |
|
} |
|
|
|
addMetric(name: string, value: number): void { |
|
if (!this.metrics.has(name)) { |
|
this.metrics.set(name, []); |
|
} |
|
this.metrics.get(name)!.push(value); |
|
} |
|
|
|
getMetrics(): Record<string, { avg: number; max: number; count: number }> { |
|
const result: Record<string, { avg: number; max: number; count: number }> = {}; |
|
|
|
this.metrics.forEach((values, name) => { |
|
result[name] = { |
|
avg: values.reduce((a, b) => a + b, 0) / values.length, |
|
max: Math.max(...values), |
|
count: values.length |
|
}; |
|
}); |
|
|
|
return result; |
|
} |
|
|
|
clear(): void { |
|
this.metrics.clear(); |
|
} |
|
|
|
disconnect(): void { |
|
this.observers.forEach(observer => observer.disconnect()); |
|
this.observers = []; |
|
} |
|
} |
|
|
|
|
|
export const performanceMonitor = new PerformanceMonitor(); |
|
export const imageLoader = new LazyImageLoader(); |
|
|
|
|
|
export function usePerformanceMetrics() { |
|
return { |
|
getMetrics: () => performanceMonitor.getMetrics(), |
|
addMetric: (name: string, value: number) => performanceMonitor.addMetric(name, value), |
|
clear: () => performanceMonitor.clear() |
|
}; |
|
} |
|
|