feat(directions): run route computation in a Web Worker with live progress (off UI thread)
This commit is contained in:
parent
62ddbf160c
commit
591fafc5de
@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
import { onMount } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import type { Map as LeafletMap } from 'leaflet';
|
import type { Map as LeafletMap } from 'leaflet';
|
||||||
import { search, type Searchable, type SearchResult } from '$lib/search';
|
import { search, type Searchable, type SearchResult } from '$lib/search';
|
||||||
import { geocode, type GeocodedPlace } from '$lib/geocode';
|
import { geocode, type GeocodedPlace } from '$lib/geocode';
|
||||||
import DirectionsPanel from '$lib/components/DirectionsPanel.svelte';
|
import DirectionsPanel from '$lib/components/DirectionsPanel.svelte';
|
||||||
import { prefs } from '$lib/prefs';
|
import { prefs } from '$lib/prefs';
|
||||||
import { route, type RouteGraph, type AvoidRule, type RouteResult } from '$lib/routing';
|
import type { RouteGraph, AvoidRule, RouteResult } from '$lib/routing';
|
||||||
|
|
||||||
type FeatureCollection = {
|
type FeatureCollection = {
|
||||||
type: string;
|
type: string;
|
||||||
@ -71,6 +71,11 @@
|
|||||||
let routeLoading = $state(false);
|
let routeLoading = $state(false);
|
||||||
let routeProgress = $state(0);
|
let routeProgress = $state(0);
|
||||||
let graphLabel = $derived(dumpLayers[0]?.label ?? '');
|
let graphLabel = $derived(dumpLayers[0]?.label ?? '');
|
||||||
|
// --- Web Worker for route computation (off the UI thread) ---
|
||||||
|
let routeWorker: Worker | null = null;
|
||||||
|
let routeToken = 0;
|
||||||
|
let pendingResolve: ((r: RouteResult) => void) | null = null;
|
||||||
|
let pendingReject: ((e: Error) => void) | null = null;
|
||||||
// --- search state ---
|
// --- search state ---
|
||||||
const searchables: Searchable[] = [];
|
const searchables: Searchable[] = [];
|
||||||
let query = $state('');
|
let query = $state('');
|
||||||
@ -177,24 +182,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run a route. Task 4 replaces this with a Web Worker call; for now a
|
// Lazily create the routing worker. It is recreated on demand and kept for
|
||||||
// synchronous in-thread computation keeps the panel functional.
|
// the component lifetime (destroyed on unmount).
|
||||||
|
function ensureWorker(): Worker {
|
||||||
|
if (routeWorker) return routeWorker;
|
||||||
|
const w = new Worker(new URL('../routing.worker.ts', import.meta.url), { type: 'module' });
|
||||||
|
w.onmessage = (e: MessageEvent) => {
|
||||||
|
const msg = e.data as { type: string; pct?: number; result?: RouteResult; error?: string; message?: string; token?: number };
|
||||||
|
if (msg.type === 'progress' && msg.pct !== undefined) {
|
||||||
|
routeProgress = msg.pct;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg.type === 'done' && pendingResolve) {
|
||||||
|
const r = pendingResolve; pendingResolve = null; pendingReject = null;
|
||||||
|
computingRoute = false;
|
||||||
|
r(msg.result as RouteResult);
|
||||||
|
} else if (msg.type === 'error' && pendingReject) {
|
||||||
|
const rj = pendingReject; pendingResolve = null; pendingReject = null;
|
||||||
|
computingRoute = false;
|
||||||
|
rj(new Error((msg as { message?: string }).message ?? 'Route computation failed.'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
routeWorker = w;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
function destroyWorker() {
|
||||||
|
if (routeWorker) { routeWorker.terminate(); routeWorker = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a route in the Web Worker, reporting progress back to the panel.
|
||||||
async function runRoute(
|
async function runRoute(
|
||||||
from: [number, number],
|
from: [number, number],
|
||||||
to: [number, number],
|
to: [number, number],
|
||||||
avoid: AvoidRule[]
|
avoid: AvoidRule[]
|
||||||
): Promise<RouteResult> {
|
): Promise<RouteResult> {
|
||||||
try {
|
|
||||||
if (!routeGraph) await loadRouteGraph();
|
if (!routeGraph) await loadRouteGraph();
|
||||||
if (!routeGraph) throw new Error('Road data layer is not available.');
|
if (!routeGraph) throw new Error('Road data layer is not available.');
|
||||||
|
const w = ensureWorker();
|
||||||
computingRoute = true;
|
computingRoute = true;
|
||||||
routeProgress = 1; // sync: no live progress in fallback
|
routeProgress = 0;
|
||||||
return route(routeGraph, from, to, routePois, avoid);
|
const token = ++routeToken;
|
||||||
} finally {
|
return await new Promise<RouteResult>((resolve, reject) => {
|
||||||
computingRoute = false;
|
pendingResolve = resolve;
|
||||||
}
|
pendingReject = reject;
|
||||||
|
w.postMessage({
|
||||||
|
type: 'route',
|
||||||
|
token,
|
||||||
|
graph: routeGraph,
|
||||||
|
pois: routePois,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
avoid
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDestroy(destroyWorker);
|
||||||
|
|
||||||
function toggle(name: string) {
|
function toggle(name: string) {
|
||||||
const g = layerGroups[name];
|
const g = layerGroups[name];
|
||||||
if (!g) return;
|
if (!g) return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user