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<WorkerResponse>) => { 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<DynamicMapLayersResult> { 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<DynamicMapLayersResult>(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;}
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<WorkerResponse>) => {
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<DynamicMapLayersResult> {
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<DynamicMapLayersResult>(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;
}