blob: aeb4299559b939f85dafd0fd0f8c3d986948390a [file] [edit]
import type { WorkerConfig } from './worker/filter.worker';
export interface WorkerMessage {
type: string;
payload?: any;
}
export class ExploreWorkerController {
private worker: Worker | null = null;
private ready = false;
private workerRequestId = 0;
constructor(
private onLoaded: () => void,
private onReady: () => void,
private onProgress: (payload: any) => void,
private onError: (message: string) => void,
private onParamsReady: (payload: any) => void,
private onResult: (payload: any) => void,
private onSuggestResult: (payload: any, idx: number, requestId: number) => void,
private onStatus: (message: string) => void
) {}
public init() {
try {
console.log('WorkerController: Creating Web Worker');
let workerUrl = (window as any).WORKER_URL || '/dist/explore-multi-v2-sk/filter.worker.js';
const version = (window as any).perf?.app_version;
if (version && !workerUrl.startsWith('data:')) {
const separator = workerUrl.includes('?') ? '&' : '?';
workerUrl = `${workerUrl}${separator}v=${version}`;
}
const setupWorker = (w: Worker) => {
this.worker = w;
this.worker.onmessage = (e: MessageEvent) => this.handleMessage(e);
this.worker.onerror = (e: ErrorEvent) => {
console.error('WorkerController: Worker error event:', e);
this.onError(`Worker error: ${e.message}`);
};
};
if (workerUrl.startsWith('data:')) {
setupWorker(new Worker(workerUrl));
console.log('WorkerController: Worker created directly from Data URL');
} else {
console.log('WorkerController: Fetching worker code from:', workerUrl);
fetch(workerUrl)
.then(async (resp) => {
if (!resp.ok) throw new Error(`Failed to fetch worker: ${resp.statusText}`);
return await resp.text();
})
.then((code) => {
const blob = new Blob([code], { type: 'application/javascript' });
const blobUrl = URL.createObjectURL(blob);
setupWorker(new Worker(blobUrl));
console.log('WorkerController: Worker created via Blob URL');
})
.catch((e) => {
console.error('WorkerController: Failed to fetch or create worker:', e);
this.onError(`Failed to fetch or create worker: ${e.message}`);
});
}
} catch (e: any) {
console.error('WorkerController: Failed to initialize worker:', e);
this.onError(`Failed to initialize worker: ${e.message}`);
}
}
private handleMessage(e: MessageEvent) {
const { type, payload } = e.data;
if (type === 'LOADED') {
console.log('WorkerController: Worker loaded, sending INIT config');
this.onLoaded();
const origin = window.location.origin;
// Send config to worker
const config: WorkerConfig = {
metaUrl: `${origin}/_/wasm/meta.json`,
paramsUrlTemplate: `${origin}/_/wasm/params.json?v={version}`,
wasmUrlTemplate: `${origin}/dist/explore-multi-v2-sk/filter.wasm?v={version}`,
tracesUrlTemplate: `${origin}/_/wasm/traces.bin?v={version}`,
};
this.worker!.postMessage({
type: 'INIT',
payload: config,
});
} else if (type === 'STATUS') {
this.onStatus(payload.message);
} else if (type === 'PROGRESS') {
this.onProgress(payload);
} else if (type === 'READY') {
console.log('WorkerController: Worker Ready');
this.ready = true;
this.onReady();
} else if (type === 'ERROR') {
this.onError(payload.message);
} else if (type === 'PARAMS_READY') {
this.onParamsReady(payload);
} else if (type === 'RESULT') {
this.onResult(payload);
} else if (type === 'SUGGEST_RESULT') {
const { idx, requestId } = e.data;
this.onSuggestResult(payload, idx, requestId);
}
}
public filter(queries: any[], numUserQueries: number, requestId?: number): number {
if (!this.worker || !this.ready) {
console.warn('WorkerController: Worker not ready for filtering');
return -1;
}
const finalRequestId = requestId ?? ++this.workerRequestId;
this.workerRequestId = Math.max(this.workerRequestId, finalRequestId);
this.worker.postMessage({
type: 'FILTER',
queries: queries,
numUserQueries: numUserQueries,
requestId: finalRequestId,
});
return finalRequestId;
}
public suggest(
query: string,
currentQuery: any,
idx: number,
availableParams?: any[],
includeParams?: string[]
): number {
if (!this.worker || !this.ready) {
console.warn('WorkerController: Worker not ready for suggestions');
return 0;
}
this.workerRequestId++;
this.worker.postMessage({
type: 'SUGGEST',
query: query,
currentQuery: currentQuery,
requestId: this.workerRequestId,
idx: idx,
availableParams: availableParams,
includeParams: includeParams,
});
return this.workerRequestId;
}
public getRandomTrace(callback: (query: any) => void) {
if (!this.worker || !this.ready) {
callback(null);
return;
}
const handler = (e: MessageEvent) => {
if (e.data.type === 'RANDOM_TRACE_RESULT') {
this.worker!.removeEventListener('message', handler);
callback(e.data.payload);
}
};
this.worker.addEventListener('message', handler);
this.worker.postMessage({ type: 'GET_RANDOM_TRACE' });
}
public isReady(): boolean {
return this.ready;
}
public terminate() {
if (this.worker) {
this.worker.terminate();
this.worker = null;
this.ready = false;
}
}
}