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
59 lines
2.4 KiB
TypeScript
59 lines
2.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { jumpLinkSegments, minimumRangeBetween, routeBetween } from './jump-links';
|
|
import { answerRouting, indexCatalogue } from './routing';
|
|
import { StarNeighbourhood } from './star-neighbourhood';
|
|
|
|
/** Stars a parsec apart along x, then a gap of 5 pc to one more. */
|
|
const POINTS = [...Array.from({ length: 5 }, (_, i) => ({ id: 10 + i, x: i, y: 0, z: 0 })), { id: 99, x: 9, y: 0, z: 0 }];
|
|
|
|
function catalogue() {
|
|
return {
|
|
kind: 'catalogue' as const,
|
|
ids: Int32Array.from(POINTS, (point) => point.id),
|
|
positions: Float32Array.from(POINTS.flatMap((point) => [point.x, point.y, point.z]))
|
|
};
|
|
}
|
|
|
|
describe('indexCatalogue', () => {
|
|
it('indexes the catalogue as it was packed, id by id', () => {
|
|
const index = indexCatalogue(catalogue());
|
|
|
|
for (const point of POINTS) {
|
|
expect(index.point(point.id)).toEqual(point);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('answerRouting', () => {
|
|
const index = indexCatalogue(catalogue());
|
|
const direct = new StarNeighbourhood(POINTS);
|
|
|
|
it('answers a route the range allows, with nothing to raise it to', () => {
|
|
const answer = answerRouting(index, { kind: 'route', requestId: 7, fromId: 10, toId: 14, rangePc: 1.5, ceilingPc: 8 });
|
|
|
|
expect(answer).toEqual({ kind: 'route', requestId: 7, route: routeBetween(direct, 10, 14, 1.5), neededRangePc: null });
|
|
});
|
|
|
|
it('answers a route the range does not allow with the range that would', () => {
|
|
const answer = answerRouting(index, { kind: 'route', requestId: 8, fromId: 10, toId: 99, rangePc: 1.5, ceilingPc: 8 });
|
|
|
|
expect(answer).toEqual({ kind: 'route', requestId: 8, route: null, neededRangePc: minimumRangeBetween(direct, 10, 99, 8) });
|
|
expect(answer.kind === 'route' && answer.neededRangePc).toBeCloseTo(5, 1);
|
|
});
|
|
|
|
it('offers nothing to raise to when even the ceiling does not reach', () => {
|
|
const answer = answerRouting(index, { kind: 'route', requestId: 9, fromId: 10, toId: 99, rangePc: 1.5, ceilingPc: 3 });
|
|
|
|
expect(answer).toMatchObject({ route: null, neededRangePc: null });
|
|
});
|
|
|
|
it('answers the graph as the segments it draws', () => {
|
|
const answer = answerRouting(index, { kind: 'links', requestId: 3, rangePc: 1.5 });
|
|
|
|
expect(answer.kind).toBe('links');
|
|
expect(answer.requestId).toBe(3);
|
|
expect(answer.kind === 'links' && Array.from(answer.segments)).toEqual(Array.from(jumpLinkSegments(direct, 1.5)));
|
|
});
|
|
});
|