@mark1russell7/highways 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/build/index.d.ts +438 -74
- package/build/index.d.ts.map +1 -1
- package/build/index.js +837 -68
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bench/curves.ts +35 -8
- package/src/bench/room.ts +83 -15
- package/src/bench/sections.ts +121 -0
- package/src/groups/index.ts +2 -0
- package/src/groups/keys/index.ts +81 -0
- package/src/groups/quadtree/index.ts +10 -0
- package/src/index.ts +5 -0
- package/src/levels.ts +17 -6
- package/src/model.ts +2 -0
- package/src/pick.ts +36 -0
- package/src/room/index.ts +22 -2
- package/src/room/lanes.ts +175 -0
- package/src/routers/index.ts +1 -1
- package/src/routers/sections.ts +66 -18
- package/src/sections.ts +46 -3
- package/src/strategies/builtins.ts +194 -0
- package/src/strategies/index.ts +2 -0
- package/src/strategies/model.ts +161 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { CurveSegment } from "../curves/model.ts";
|
|
2
|
+
import type { CurveSpace } from "../curves/space.ts";
|
|
3
|
+
import type { Point } from "../geom.ts";
|
|
4
|
+
import type { Graph, Result, Router } from "../model.ts";
|
|
5
|
+
import type { Boxed, Gap, RoomLink } from "../room/index.ts";
|
|
6
|
+
import type { Section } from "../sections.ts";
|
|
7
|
+
|
|
8
|
+
/** The input of a curve strategy: one polyline and the context of its route. */
|
|
9
|
+
export interface CurveInput {
|
|
10
|
+
/** These points are the polyline: a route of a router, a lane of `laneRoutes` or a link of a zoom level. */
|
|
11
|
+
readonly points: readonly Point[];
|
|
12
|
+
/** The curve leaves the first point along this outward normal of its plug, if the strategy reads it. */
|
|
13
|
+
readonly startNormal?: Point;
|
|
14
|
+
/** The curve comes into the last point against this outward normal of its plug, if the strategy reads it. */
|
|
15
|
+
readonly endNormal?: Point;
|
|
16
|
+
/** The boxes that the curve must not go through, from `curveSpace`. */
|
|
17
|
+
readonly space?: CurveSpace;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The input of a room strategy: the boxes of a board and the links between them. */
|
|
21
|
+
export interface RoomInput {
|
|
22
|
+
readonly items: readonly Boxed[];
|
|
23
|
+
readonly links: readonly RoomLink[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The steps that have strategies, with the fixed input and output type of each step.
|
|
28
|
+
*
|
|
29
|
+
* - `group`: a graph in, the tree of sections out. The router `sections` makes its hubs and its zoom levels from this tree.
|
|
30
|
+
* - `router`: a graph in, the routes (and the `levels`, `bundles` and `runs`) out.
|
|
31
|
+
* - `curve`: a polyline in, the segments of a curve out.
|
|
32
|
+
* - `room`: the boxes and links of a board in, the gaps of the board with their need out.
|
|
33
|
+
*/
|
|
34
|
+
export interface StepTypes {
|
|
35
|
+
readonly group: { readonly input: Graph; readonly output: Section };
|
|
36
|
+
readonly router: { readonly input: Graph; readonly output: Result };
|
|
37
|
+
readonly curve: { readonly input: CurveInput; readonly output: CurveSegment[] };
|
|
38
|
+
readonly room: { readonly input: RoomInput; readonly output: Gap[] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The name of a step that has strategies. */
|
|
42
|
+
export type Step = keyof StepTypes;
|
|
43
|
+
|
|
44
|
+
/** The input type of a step. */
|
|
45
|
+
export type StepInput<S extends Step> = StepTypes[S]["input"];
|
|
46
|
+
|
|
47
|
+
/** The output type of a step. */
|
|
48
|
+
export type StepOutput<S extends Step> = StepTypes[S]["output"];
|
|
49
|
+
|
|
50
|
+
/** The steps that have strategies, in the order of the pipeline. */
|
|
51
|
+
export const STEPS: readonly Step[] = ["group", "router", "curve", "room"];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A strategy: one way to do one step, with a name and a description.
|
|
55
|
+
* The same input and the same options must give the same output.
|
|
56
|
+
*/
|
|
57
|
+
export interface Strategy<S extends Step = Step, O extends object = object> {
|
|
58
|
+
/** The step of the strategy. It fixes the input and the output type. */
|
|
59
|
+
readonly step: S;
|
|
60
|
+
/** The name of the strategy. Each name is unique in its step of a registry. */
|
|
61
|
+
readonly name: string;
|
|
62
|
+
/** One or two sentences that tell what the strategy does. The site and the documents show them. */
|
|
63
|
+
readonly describe: string;
|
|
64
|
+
/** The values of the options when the caller gives no value. */
|
|
65
|
+
readonly defaults: Readonly<O>;
|
|
66
|
+
/** The benchmark skips an input with more nodes than this. Without it, the strategy takes each input. */
|
|
67
|
+
readonly maxNodes?: number;
|
|
68
|
+
/** This method does the step. Each option that `options` does not give gets its value from `defaults`. */
|
|
69
|
+
run(input: StepInput<S>, options?: Partial<O>): StepOutput<S>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A strategy of any step. A registry holds a list of these. */
|
|
73
|
+
export type AnyStrategy = { [S in Step]: Strategy<S> }[Step];
|
|
74
|
+
|
|
75
|
+
/** The definition of a strategy, for `defineStrategy`. */
|
|
76
|
+
export interface StrategyDefinition<S extends Step, O extends object> {
|
|
77
|
+
readonly step: S;
|
|
78
|
+
readonly name: string;
|
|
79
|
+
readonly describe: string;
|
|
80
|
+
/** These values are the options when the caller gives no value. Give `{}` for a strategy without options. */
|
|
81
|
+
readonly defaults: O;
|
|
82
|
+
readonly maxNodes?: number;
|
|
83
|
+
/** This function does the step. It gets each option, from the caller or from `defaults`. */
|
|
84
|
+
run(input: StepInput<S>, options: Readonly<O>): StepOutput<S>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* This function makes a strategy from its definition. The strategy fills the missing options from `defaults`.
|
|
89
|
+
* When the caller gives no option, the definition gets the `defaults` object itself.
|
|
90
|
+
*/
|
|
91
|
+
export function defineStrategy<S extends Step, O extends object>(definition: StrategyDefinition<S, O>): Strategy<S, O> {
|
|
92
|
+
const { step, name, describe, defaults, maxNodes } = definition;
|
|
93
|
+
if (name.length === 0) throw new Error(`A strategy of the step "${step}" has no name.`);
|
|
94
|
+
const strategy: Strategy<S, O> = {
|
|
95
|
+
step,
|
|
96
|
+
name,
|
|
97
|
+
describe,
|
|
98
|
+
defaults: Object.freeze({ ...defaults }),
|
|
99
|
+
...(maxNodes === undefined ? {} : { maxNodes }),
|
|
100
|
+
run(input: StepInput<S>, options?: Partial<O>): StepOutput<S> {
|
|
101
|
+
const all = options === undefined || Object.keys(options).length === 0 ? strategy.defaults : { ...strategy.defaults, ...options };
|
|
102
|
+
return definition.run(input, all as Readonly<O>);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
return Object.freeze(strategy);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A registry: the strategies of each step, in order. A registry is data. No import changes it.
|
|
110
|
+
* A consumer makes its own registry with `createRegistry`, and adds a strategy with `register`.
|
|
111
|
+
* The list `group` is optional in the type, so a registry from version 0.1.5 stays a registry. `createRegistry` always gives it.
|
|
112
|
+
*/
|
|
113
|
+
export type Registry = { readonly [S in Exclude<Step, "group">]: readonly Strategy<S>[] } & { readonly group?: readonly Strategy<"group">[] };
|
|
114
|
+
|
|
115
|
+
/** This function makes a registry from a list of strategies. It keeps their order. Two strategies with one step and one name are an error. */
|
|
116
|
+
export function createRegistry(strategies: readonly AnyStrategy[]): Registry {
|
|
117
|
+
const out: { [S in Step]: Strategy<S>[] } = { group: [], router: [], curve: [], room: [] };
|
|
118
|
+
for (const s of strategies) {
|
|
119
|
+
const list = out[s.step] as AnyStrategy[] | undefined;
|
|
120
|
+
if (!list) throw new Error(`There is no step "${String(s.step)}". The steps are ${STEPS.join(", ")}.`);
|
|
121
|
+
if (list.some((t) => t.name === s.name)) throw new Error(`The step "${s.step}" has two strategies with the name "${s.name}".`);
|
|
122
|
+
list.push(s);
|
|
123
|
+
}
|
|
124
|
+
return Object.freeze({ group: Object.freeze(out.group), router: Object.freeze(out.router), curve: Object.freeze(out.curve), room: Object.freeze(out.room) });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** This function gives a new registry with the strategies of `registry` and then `strategies`. It does not change `registry`. */
|
|
128
|
+
export function register(registry: Registry, ...strategies: readonly AnyStrategy[]): Registry {
|
|
129
|
+
return createRegistry([...(registry.group ?? []), ...registry.router, ...registry.curve, ...registry.room, ...strategies]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** This function gives the strategies of one step of a registry, in order. */
|
|
133
|
+
export function listStrategies<S extends Step>(registry: Registry, step: S): readonly Strategy<S>[] {
|
|
134
|
+
return (registry[step] ?? []) as readonly Strategy<S>[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** This function gives the strategy of one step with a name, or `undefined` when the registry has no such strategy. */
|
|
138
|
+
export function findStrategy<S extends Step>(registry: Registry, step: S, name: string): Strategy<S> | undefined {
|
|
139
|
+
return listStrategies(registry, step).find((s) => s.name === name);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** This function gives the strategy of one step with a name. When the registry has no such strategy, the function throws an error. */
|
|
143
|
+
export function getStrategy<S extends Step>(registry: Registry, step: S, name: string): Strategy<S> {
|
|
144
|
+
const s = findStrategy(registry, step, name);
|
|
145
|
+
if (!s) throw new Error(`There is no ${step} "${name}". The registry has ${listStrategies(registry, step).map((t) => t.name).join(", ")}.`);
|
|
146
|
+
return s;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** This function gives a `Router` that runs a router strategy with the options. The router keeps the name and the `maxNodes` of the strategy. */
|
|
150
|
+
export function asRouter<O extends object>(strategy: Strategy<"router", O>, options?: Partial<O>): Router {
|
|
151
|
+
return {
|
|
152
|
+
name: strategy.name,
|
|
153
|
+
...(strategy.maxNodes === undefined ? {} : { maxNodes: strategy.maxNodes }),
|
|
154
|
+
route: (graph: Graph): Result => strategy.run(graph, options),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** This function gives the router strategies of a registry as `Router` objects, in order. The benchmark races them. */
|
|
159
|
+
export function routersOf(registry: Registry): Router[] {
|
|
160
|
+
return listStrategies(registry, "router").map((s) => asRouter(s));
|
|
161
|
+
}
|