Plot routes and build the jump-link graph in a Web Worker

Route plotting ran on the main thread, and so did the jump-link graph:

- the range search for a far target, HD 2626 at 236 pc, takes 4-5 s;
- the graph at 8 pc is 3.7 million links, 6-10 s to build, then as many
  link objects again to turn into vertices.

The map stopped for as long as either ran.

A Web Worker now does both. RoutingClient sends it the catalogue's ids and
positions once, and it keeps its own spatial index. A route question comes
back with the route, or with the range that would open one. A graph comes
back as one Float32Array of segment vertices, transferred rather than
copied.

On the scene side, only the latest route request is shown: an earlier
answer arriving later is dropped. Only the graph for the range last asked
for is drawn. The Routes panel says "Plotting…" and holds its button while
a request is out.

collectJumpLinks gave way to jumpLinkSegments, which writes the vertex pairs
straight into floats rather than building link objects first; the scene was
its only caller. The routing module (routing.ts) is the message protocol and
the one function answering it, so the worker is a dozen lines, and the same
answers are worked out in place where there is no Worker, as in the unit
tests' DOM. The worker is built with its own tsconfig, as the Angular
builder expects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
This commit is contained in:
2026-09-16 14:17:26 +02:00
co-authored by Claude Opus 5
parent 2d997e41db
commit 8c69a7a8b2
16 changed files with 409 additions and 77 deletions
+23 -17
View File
@@ -29,13 +29,6 @@ export interface Route {
readonly longestHopPc: number;
}
/** An unordered pair of stars within range of each other. */
export interface JumpLink {
readonly from: number;
readonly to: number;
readonly distancePc: number;
}
/**
* A cap on how much of the catalogue one search may walk. A search that hits it has already
* visited more stars than any real chain passes through: the longest measured, Sol to HD 2626 at
@@ -221,17 +214,30 @@ export function minimumRangeBetween(index: StarNeighbourhood, fromId: number, to
}
/**
* Every link within `rangePc` in the whole catalogue, each pair once.
* Every link within `rangePc` in the whole catalogue, each pair once, as vertex pairs ready to
* draw: six floats a link, one end then the other.
*
* For drawing the graph, which is the only thing that wants all of it: routing asks for a
* star's neighbours as it reaches that star and never builds this.
* For drawing the graph, which is the only thing that wants all of it: routing asks for a star's
* neighbours as it reaches that star and never builds this. Written straight into floats rather
* than collected as link objects first, since at 8 pc there are 3.7 million links.
*/
export function collectJumpLinks(index: StarNeighbourhood, rangePc: number): JumpLink[] {
const links: JumpLink[] = [];
index.forEachPairWithin(rangePc, (a, b, distancePc) => {
// The smaller id first, always. The grid hands pairs over in whatever order it walks its
// cells, and a link that is `3-7` here and `7-3` there is two links to anything comparing.
links.push(a.id < b.id ? { from: a.id, to: b.id, distancePc } : { from: b.id, to: a.id, distancePc });
export function jumpLinkSegments(index: StarNeighbourhood, rangePc: number): Float32Array {
let vertices = new Float32Array(6 * 4096);
let length = 0;
index.forEachPairWithin(rangePc, (a, b) => {
if (length + 6 > vertices.length) {
const grown = new Float32Array(vertices.length * 2);
grown.set(vertices);
vertices = grown;
}
vertices[length++] = a.x;
vertices[length++] = a.y;
vertices[length++] = a.z;
vertices[length++] = b.x;
vertices[length++] = b.y;
vertices[length++] = b.z;
});
return links;
// Exact length rather than a view on the grown buffer: the answer is transferred whole, and a
// view would carry up to as much again in unused capacity with it.
return vertices.slice(0, length);
}