import { useEffect, useRef, useState } from 'react'; import type { DependencyList } from 'react'; import type { DynamicMapLayersBuildPayload, DynamicMapLayersDataPayload, DynamicMapLayersResult, } from '@/components/map/dynamicMapLayers.worker'; type SyncRequest = { id: string; action: 'sync_dynamic_layers'; payload: DynamicMapLayersDataPayload; }; type BuildRequest = { id: string; action: 'build_dynamic_layers'; payload: DynamicMapLayersBuildPayload; }; type SyncAndBuildRequest = { id: string; action: 'sync_and_build_dynamic_layers'; payload: { data: DynamicMapLayersDataPayload; build: DynamicMapLayersBuildPayload; }; }; type WorkerRequest = SyncRequest | BuildRequest | SyncAndBuildRequest; type WorkerResponse = { id: string; ok: boolean; result?: DynamicMapLayersResult; error?: string; }; const EMPTY_RESULT: DynamicMapLayersResult = { commercialFlightsGeoJSON: null, privateFlightsGeoJSON: null, privateJetsGeoJSON: null, militaryFlightsGeoJSON: null, trackedFlightsGeoJSON: null, shipsGeoJSON: null, meshtasticGeoJSON: null, aprsGeoJSON: null, }; let worker: Worker | null = null; let reqCounter = 0; const pending = new Map< string, { resolve: (value: DynamicMapLayersResult) => void; reject: (error: Error) => void; } >(); function ensureWorker(): Worker { if (worker) return worker; worker = new Worker(new URL('../dynamicMapLayers.worker.ts', import.meta.url), { type: 'module' }); worker.onmessage = (event: MessageEvent) => { const msg = event.data; const handler = pending.get(msg.id); if (!handler) return; pending.delete(msg.id); if (msg.ok && msg.result) { handler.resolve(msg.result); } else { handler.reject(new Error(msg.error || 'worker_error')); } }; return worker; } function callWorker(request: WorkerRequest): Promise { return new Promise((resolve, reject) => { pending.set(request.id, { resolve, reject }); try { ensureWorker().postMessage(request); } catch (error) { pending.delete(request.id); reject(error as Error); } }); } export function useDynamicMapLayersWorker( dataPayload: DynamicMapLayersDataPayload, dataDeps: DependencyList, buildPayload: DynamicMapLayersBuildPayload, buildDeps: DependencyList, ): DynamicMapLayersResult { const [result, setResult] = useState(EMPTY_RESULT); const [syncVersion, setSyncVersion] = useState(0); const syncVersionRef = useRef(0); const requestVersionRef = useRef(0); const hasSyncedRef = useRef(false); useEffect(() => { let cancelled = false; const id = `mapw_sync_build_${Date.now()}_${reqCounter++}`; const currentSyncVersion = ++syncVersionRef.current; const requestVersion = ++requestVersionRef.current; callWorker({ id, action: 'sync_and_build_dynamic_layers', payload: { data: dataPayload, build: buildPayload }, }) .then((next) => { if (!cancelled) { hasSyncedRef.current = true; setSyncVersion(currentSyncVersion); if (requestVersion === requestVersionRef.current) { setResult(next); } } }) .catch((error) => { if (!cancelled) { console.error('Dynamic map layer worker sync failed', error); } }); return () => { cancelled = true; }; }, dataDeps); useEffect(() => { if (!hasSyncedRef.current) return; let cancelled = false; const requestVersion = ++requestVersionRef.current; const id = `mapw_build_${Date.now()}_${reqCounter++}`; callWorker({ id, action: 'build_dynamic_layers', payload: buildPayload }) .then((next) => { if (!cancelled && requestVersion === requestVersionRef.current) { setResult(next); } }) .catch((error) => { if (!cancelled) { console.error('Dynamic map layer worker build failed', error); } }); return () => { cancelled = true; }; }, [syncVersion, ...buildDeps]); return result; }