Files
temp_SSA_SCAN/lib/ml/detection-worker-manager.ts
2025-09-30 15:48:26 -06:00

214 lines
5.9 KiB
TypeScript

import type { DetectionConfig, DetectionResult, DetectionMetrics, WorkerMessage, WorkerResponse } from './types';
import { ModelCache } from './model-cache';
import { MODEL_VARIANTS } from './model-config';
/**
* Manages the detection worker and handles communication
*/
export class DetectionWorkerManager {
private worker: Worker | null = null;
private messageId = 0;
private pendingMessages = new Map<string, { resolve: (value: any) => void; reject: (reason?: any) => void }>();
private modelCache = new ModelCache();
private isWorkerReady = false;
constructor() {
this.initializeWorker();
}
private async initializeWorker() {
try {
// Create worker from the detection worker file
this.worker = new Worker(
new URL('../../workers/detection-worker.ts', import.meta.url),
{ type: 'module' }
);
this.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {
this.handleWorkerMessage(event.data);
};
this.worker.onerror = (error) => {
console.error('Worker error:', error);
this.isWorkerReady = false;
};
this.isWorkerReady = true;
console.log('Detection worker initialized');
await this.sendMessage('INITIALIZE', undefined);
} catch (error) {
console.error('Failed to initialize worker:', error);
this.isWorkerReady = false;
}
}
private handleWorkerMessage(message: WorkerResponse) {
const { type, id } = message;
const pending = this.pendingMessages.get(id);
if (!pending) {
console.warn('Received response for unknown message ID:', id);
return;
}
this.pendingMessages.delete(id);
if (type === 'ERROR') {
pending.reject(new Error((message as { error: string }).error));
} else if (type === 'DETECTION_RESULT') {
const detectionMessage = message as { result: DetectionResult | null };
pending.resolve({ result: detectionMessage.result });
} else if (type === 'INITIALIZED') {
pending.resolve(undefined);
} else if (type === 'METRICS_UPDATE') {
pending.resolve({ metrics: (message as { metrics: Partial<DetectionMetrics> }).metrics });
} else if (type === 'LOADED_MODEL') {
pending.resolve(undefined);
} else if (type === 'CONFIGURED') {
pending.resolve(undefined);
} else {
pending.resolve(message);
}
}
private async sendMessage<T>(type: WorkerMessage['type'], payload: unknown): Promise<T> {
if (!this.worker || !this.isWorkerReady) {
throw new Error('Worker not available');
}
const id = (this.messageId++).toString();
return new Promise((resolve, reject) => {
this.pendingMessages.set(id, { resolve, reject });
let message: WorkerMessage & { id: string };
if (type === 'INITIALIZE') {
message = { type, id };
} else if (type === 'DETECT') {
message = { type, imageData: payload.imageData, id };
} else if (type === 'UPDATE_CONFIG' || type === 'CONFIGURE') {
message = { type, config: payload, id };
} else if (type === 'LOAD_MODEL') {
message = { type, variant: payload.variant, modelData: payload.modelData, id };
} else {
throw new Error(`Unknown message type for sendMessage: ${type}`);
}
this.worker!.postMessage(message);
// Timeout after 30 seconds
setTimeout(() => {
if (this.pendingMessages.has(id)) {
this.pendingMessages.delete(id);
reject(new Error('Worker message timeout'));
}
}, 90000);
});
}
/**
* Load a model into the worker
*/
async loadModel(variant: 'quantized' | 'standard' | 'full', onProgress?: (progress: number) => void): Promise<void> {
const modelInfo = MODEL_VARIANTS[variant];
try {
// Get model data from cache or download
const modelData = await this.modelCache.getModel(variant, modelInfo, onProgress);
// Send model data to worker
await this.sendMessage('LOAD_MODEL', {
variant,
modelData
});
console.log(`Model ${variant} loaded successfully`);
} catch (error) {
console.error(`Failed to load model ${variant}:`, error);
throw error;
}
}
/**
* Configure the detection settings
*/
async configure(config: DetectionConfig): Promise<void> {
await this.sendMessage('CONFIGURE', config);
}
/**
* Perform detection on an image
*/
async detect(imageData: ImageData): Promise<DetectionResult[]> {
if (!this.isWorkerReady) {
throw new Error('Worker not ready');
}
try {
const results = await this.sendMessage<{ result: DetectionResult | null }>('DETECT', { imageData });
// Handle the case where results or results.result is undefined
if (!results || results.result === undefined || results.result === null) {
return [];
}
return [results.result];
} catch (error) {
console.error('Detection failed:', error);
throw error;
}
}
/**
* Get worker metrics
*/
async getMetrics(): Promise<object> {
return await this.sendMessage('getMetrics', {});
}
/**
* Check if worker is ready
*/
isReady(): boolean {
return this.isWorkerReady;
}
/**
* Terminate the worker
*/
destroy() {
if (this.worker) {
this.worker.terminate();
this.worker = null;
this.isWorkerReady = false;
}
// Reject all pending messages
this.pendingMessages.forEach(({ reject }) => {
reject(new Error('Worker terminated'));
});
this.pendingMessages.clear();
}
}
/**
* Singleton instance manager
*/
let workerManager: DetectionWorkerManager | null = null;
export function getDetectionWorkerManager(): DetectionWorkerManager {
if (!workerManager) {
workerManager = new DetectionWorkerManager();
}
return workerManager;
}
export function destroyDetectionWorkerManager() {
if (workerManager) {
workerManager.destroy();
workerManager = null;
}
}