tla-ts 0.0.1 → 0.0.3-dev.3cf2ad0
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 +85 -40
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +216 -30
- package/dist/index.d.ts +216 -30
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +2 -2
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +20 -1
- package/dist/run-cli.js +20 -1
- package/dist/transfer.cjs +388 -39
- package/dist/transfer.js +388 -39
- package/package.json +6 -2
- package/src/ast.ts +122 -5
- package/src/bags.ts +161 -71
- package/src/builtins.ts +203 -23
- package/src/bundled-modules.ts +63 -32
- package/src/check.ts +945 -238
- package/src/cli-options.ts +58 -0
- package/src/codegen.ts +52 -18
- package/src/config.ts +39 -11
- package/src/environment.ts +485 -140
- package/src/errors.ts +59 -1
- package/src/evaluate.ts +489 -137
- package/src/exit-codes.ts +75 -0
- package/src/fingerprints.ts +48 -13
- package/src/index.ts +9 -4
- package/src/instances.ts +221 -66
- package/src/java-overrides.ts +241 -0
- package/src/json.ts +153 -72
- package/src/levels.ts +850 -0
- package/src/lexer.ts +103 -28
- package/src/liveness.ts +47 -91
- package/src/obligation-action.ts +384 -0
- package/src/obligation-anon.ts +575 -0
- package/src/obligation-deref.ts +637 -0
- package/src/obligation-expression.ts +1060 -0
- package/src/obligation-format.ts +406 -0
- package/src/obligation-generate.ts +1074 -0
- package/src/obligation-levels.ts +329 -0
- package/src/obligation-module.ts +1042 -0
- package/src/obligation-printer.ts +889 -0
- package/src/obligation-proof.ts +76 -0
- package/src/obligation-source.ts +1084 -0
- package/src/obligation-tuples.ts +184 -0
- package/src/obligations.ts +316 -0
- package/src/parallel-worker.ts +183 -202
- package/src/parallel.ts +163 -84
- package/src/parser.ts +958 -205
- package/src/pretty-print.ts +259 -0
- package/src/randomization.ts +24 -113
- package/src/resolve.ts +472 -41
- package/src/run-cli.ts +427 -61
- package/src/runtime.ts +63 -28
- package/src/spec-description.ts +509 -0
- package/src/standard-levels.ts +105 -0
- package/src/standard-modules.ts +223 -92
- package/src/subexpressions.ts +21 -35
- package/src/substitution-levels.ts +184 -0
- package/src/successors.ts +261 -46
- package/src/symmetry.ts +70 -22
- package/src/tableau.ts +43 -37
- package/src/tlc-ext.ts +120 -33
- package/src/tlc-module.ts +71 -47
- package/src/tlc-modules.ts +1022 -0
- package/src/tlc-order.ts +497 -0
- package/src/tlc-random.ts +411 -0
- package/src/tlc-summary.ts +109 -0
- package/src/transfer.ts +26 -5
- package/src/values.ts +1234 -176
package/src/parallel-worker.ts
CHANGED
|
@@ -1,65 +1,42 @@
|
|
|
1
1
|
import { createCanonicalizer } from "./canonical";
|
|
2
2
|
import { createSearch } from "./check";
|
|
3
3
|
import { createEnvironment } from "./environment";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
createFingerprintTable,
|
|
6
|
+
insertSharedFingerprint,
|
|
7
|
+
isSharedFingerprintsFull,
|
|
8
|
+
writeStateFingerprint,
|
|
9
|
+
} from "./fingerprints";
|
|
10
|
+
import type {
|
|
11
|
+
CoordinatorMessage,
|
|
12
|
+
LevelMessage,
|
|
13
|
+
Port,
|
|
14
|
+
StartMessage,
|
|
15
|
+
WorkerMessage,
|
|
16
|
+
} from "./parallel";
|
|
17
|
+
import { getSuccessors, type Successor } from "./successors";
|
|
18
|
+
import { getTlcStringOrder, getTlcVariableOrder } from "./tlc-order";
|
|
19
|
+
import { OUT_DEGREE_BUCKETS } from "./tlc-summary";
|
|
8
20
|
import { createStateDecoder, createStateEncoder, type EncodedStates } from "./transfer";
|
|
9
|
-
import type
|
|
21
|
+
import { getRankedStringCount, setStringOrder, type Value } from "./values";
|
|
10
22
|
|
|
11
23
|
const WORKER_THREADS_MODULE = "node:worker_threads";
|
|
24
|
+
const LOCKED = -1;
|
|
12
25
|
|
|
13
|
-
interface
|
|
26
|
+
interface Expansion {
|
|
14
27
|
states: Value[][];
|
|
15
|
-
|
|
16
|
-
|
|
28
|
+
origin: number;
|
|
29
|
+
end: number;
|
|
30
|
+
successors: Successor[] | undefined;
|
|
31
|
+
index: number;
|
|
32
|
+
added: number;
|
|
33
|
+
generated: number;
|
|
34
|
+
outDegrees: number[];
|
|
35
|
+
next: Value[][];
|
|
36
|
+
fingerprintStates: Value[][];
|
|
17
37
|
}
|
|
18
38
|
|
|
19
|
-
|
|
20
|
-
phase: "query" | "answer" | "sizes" | "balance";
|
|
21
|
-
level: number;
|
|
22
|
-
from: number;
|
|
23
|
-
fingerprints?: Int32Array;
|
|
24
|
-
bits?: Uint8Array;
|
|
25
|
-
states?: EncodedStates;
|
|
26
|
-
unmarks?: Int32Array;
|
|
27
|
-
size?: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface Transfer {
|
|
31
|
-
from: number;
|
|
32
|
-
to: number;
|
|
33
|
-
count: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const getBalancePlan = (sizes: number[]): Transfer[] => {
|
|
37
|
-
const total = sizes.reduce((sum, size) => sum + size, 0);
|
|
38
|
-
const average = total / sizes.length;
|
|
39
|
-
if (Math.max(...sizes) - Math.min(...sizes) <= Math.max(64, average / 4)) return [];
|
|
40
|
-
const targets = sizes.map(
|
|
41
|
-
(_, index) => Math.floor(total / sizes.length) + (index < total % sizes.length ? 1 : 0),
|
|
42
|
-
);
|
|
43
|
-
const donors = sizes.flatMap((size, index) =>
|
|
44
|
-
size > targets[index] ? [{ index, amount: size - targets[index] }] : [],
|
|
45
|
-
);
|
|
46
|
-
const receivers = sizes.flatMap((size, index) =>
|
|
47
|
-
size < targets[index] ? [{ index, amount: targets[index] - size }] : [],
|
|
48
|
-
);
|
|
49
|
-
const transfers: Transfer[] = [];
|
|
50
|
-
for (const donor of donors) {
|
|
51
|
-
for (const receiver of receivers) {
|
|
52
|
-
const moved = Math.min(donor.amount, receiver.amount);
|
|
53
|
-
if (moved === 0) continue;
|
|
54
|
-
transfers.push({ from: donor.index, to: receiver.index, count: moved });
|
|
55
|
-
donor.amount -= moved;
|
|
56
|
-
receiver.amount -= moved;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return transfers;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const getParentPort = async (): Promise<Port> => {
|
|
39
|
+
const getParentPort = async () => {
|
|
63
40
|
if (typeof (globalThis as { postMessage?: unknown }).postMessage === "function") {
|
|
64
41
|
return globalThis as unknown as Port;
|
|
65
42
|
}
|
|
@@ -69,184 +46,183 @@ const getParentPort = async (): Promise<Port> => {
|
|
|
69
46
|
return threads.parentPort as unknown as Port;
|
|
70
47
|
};
|
|
71
48
|
|
|
72
|
-
const createInbox = () => {
|
|
73
|
-
const messages = new Map<string, PeerMessage[]>();
|
|
74
|
-
const waiters = new Map<string, { count: number; resolve: (messages: PeerMessage[]) => void }>();
|
|
75
|
-
const getKey = (phase: PeerMessage["phase"], level: number) => `${phase}:${level}`;
|
|
76
|
-
const settle = (key: string) => {
|
|
77
|
-
const waiter = waiters.get(key);
|
|
78
|
-
const received = messages.get(key) ?? [];
|
|
79
|
-
if (!waiter || received.length < waiter.count) return;
|
|
80
|
-
waiters.delete(key);
|
|
81
|
-
messages.delete(key);
|
|
82
|
-
waiter.resolve(received);
|
|
83
|
-
};
|
|
84
|
-
return {
|
|
85
|
-
deliver: (message: PeerMessage) => {
|
|
86
|
-
const key = getKey(message.phase, message.level);
|
|
87
|
-
messages.set(key, [...(messages.get(key) ?? []), message]);
|
|
88
|
-
settle(key);
|
|
89
|
-
},
|
|
90
|
-
receive: (phase: PeerMessage["phase"], level: number, count: number) =>
|
|
91
|
-
new Promise<PeerMessage[]>((resolve) => {
|
|
92
|
-
const key = getKey(phase, level);
|
|
93
|
-
waiters.set(key, { count, resolve });
|
|
94
|
-
settle(key);
|
|
95
|
-
}),
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
|
|
99
49
|
const run = (start: StartMessage, parent: Port) => {
|
|
100
50
|
const modules = new Map(start.modules);
|
|
101
|
-
const
|
|
102
|
-
|
|
51
|
+
const loadModule = (name: string) => modules.get(name);
|
|
52
|
+
const classes = new Map(start.classes);
|
|
53
|
+
setStringOrder(getTlcStringOrder(start.source, start.config, loadModule));
|
|
54
|
+
const startupStrings = getRankedStringCount();
|
|
55
|
+
const specification = createEnvironment(start.config, {
|
|
56
|
+
source: start.source,
|
|
57
|
+
loadModule,
|
|
58
|
+
loadClass: (name) => classes.get(name),
|
|
103
59
|
print: () => {},
|
|
104
60
|
files: { readFile: undefined, writeFile: undefined },
|
|
105
61
|
});
|
|
106
62
|
const environment = specification.environment;
|
|
107
|
-
const search = createSearch(start.config, environment)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
63
|
+
const search = createSearch(start.config, environment, () =>
|
|
64
|
+
getTlcVariableOrder(start.source, loadModule),
|
|
65
|
+
);
|
|
66
|
+
const nextAction = search?.spec.next;
|
|
67
|
+
if (!search || nextAction === undefined)
|
|
68
|
+
return parent.postMessage({ type: "abort" } satisfies WorkerMessage);
|
|
111
69
|
const self = start.index;
|
|
112
70
|
const canonicalize = createCanonicalizer();
|
|
113
|
-
const decode = createStateDecoder(
|
|
114
|
-
const
|
|
115
|
-
const
|
|
71
|
+
const decode = createStateDecoder(environment.variables.length);
|
|
72
|
+
const shared = { fingerprints: start.fingerprints };
|
|
73
|
+
const requests = start.requests;
|
|
74
|
+
const remaining = start.remaining;
|
|
75
|
+
// Under symmetry, a successor the worker generated before has the representative it had then,
|
|
76
|
+
// so the worker skips the search for one.
|
|
77
|
+
const generatedStates = search.isSymmetric ? createFingerprintTable() : undefined;
|
|
116
78
|
const fingerprint = new Int32Array(2);
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
peer.port.start?.();
|
|
79
|
+
const waiters = { resume: () => {}, loot: (_states: EncodedStates) => {} };
|
|
80
|
+
start.ports.forEach((port) => {
|
|
81
|
+
port?.addEventListener("message", (event) => waiters.loot(event.data as EncodedStates));
|
|
82
|
+
port?.start?.();
|
|
122
83
|
});
|
|
123
|
-
for (let index = 0; index < start.seedFingerprints.length; index += 2) {
|
|
124
|
-
table.insert(start.seedFingerprints[index], start.seedFingerprints[index + 1]);
|
|
125
|
-
}
|
|
126
84
|
const frontier = { states: decode(start.seed).map(canonicalize) };
|
|
127
|
-
const send = (index: number, message: PeerMessage, transfer: unknown[]) =>
|
|
128
|
-
start.ports[index]?.postMessage(message, transfer);
|
|
129
85
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
86
|
+
// A worker that runs out of states asks a peer for some by writing its index + 1 to the peer's
|
|
87
|
+
// request word. The peer sends half of the states it hasn't started, keeps expanding, and wakes
|
|
88
|
+
// the workers waiting to ask it next.
|
|
89
|
+
const answer = (expansion: Expansion, request: number) => {
|
|
90
|
+
const thief = Atomics.load(requests, self) - 1;
|
|
91
|
+
const given = Math.floor((expansion.end - expansion.origin - 1) / 2);
|
|
92
|
+
const encoder = createStateEncoder();
|
|
93
|
+
const states = expansion.states.slice(expansion.end - given, expansion.end);
|
|
94
|
+
const isEncoded = given > 0 && states.every(encoder.encode);
|
|
95
|
+
if (isEncoded) expansion.end -= given;
|
|
96
|
+
const loot = isEncoded ? encoder.finish() : createStateEncoder().finish();
|
|
97
|
+
start.ports[thief]?.postMessage(loot, [loot.words.buffer]);
|
|
98
|
+
Atomics.store(requests, self, request);
|
|
99
|
+
Atomics.notify(requests, self);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// A worker answers any request before it pauses or runs out of states, and takes no more until
|
|
103
|
+
// it resumes.
|
|
104
|
+
const lock = (expansion: Expansion) => {
|
|
105
|
+
Atomics.store(remaining, self, 0);
|
|
106
|
+
if (Atomics.compareExchange(requests, self, 0, LOCKED) === 0) return;
|
|
107
|
+
answer(expansion, LOCKED);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const unlock = (expansion: Expansion) => {
|
|
111
|
+
Atomics.store(remaining, self, expansion.end - expansion.origin);
|
|
112
|
+
Atomics.store(requests, self, 0);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// The worker pauses before a successor when the shared table needs to grow, and resumes there.
|
|
116
|
+
const expand = (expansion: Expansion) => {
|
|
117
|
+
for (; expansion.origin < expansion.end; expansion.origin++) {
|
|
118
|
+
const state = expansion.states[expansion.origin];
|
|
119
|
+
if (!expansion.successors) {
|
|
120
|
+
if (Atomics.load(requests, self) > 0) answer(expansion, 0);
|
|
121
|
+
Atomics.store(remaining, self, expansion.end - expansion.origin - 1);
|
|
122
|
+
expansion.successors = getSuccessors(nextAction, state, environment, search.spec.scope);
|
|
123
|
+
expansion.generated += expansion.successors.length;
|
|
124
|
+
if (expansion.successors.length === 0 && start.config.checkDeadlock) return undefined;
|
|
125
|
+
}
|
|
126
|
+
const successors = expansion.successors;
|
|
127
|
+
for (; expansion.index < successors.length; expansion.index++) {
|
|
128
|
+
if (isSharedFingerprintsFull(shared.fingerprints)) return "paused";
|
|
129
|
+
const successor = successors[expansion.index];
|
|
142
130
|
if (search.findViolatedActionProperty(state, successor.state)) return undefined;
|
|
143
131
|
if (!search.satisfiesActionConstraints(state, successor.state)) continue;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (owner === self) {
|
|
148
|
-
if (!table.insert(fingerprint[0], fingerprint[1])) continue;
|
|
149
|
-
if (search.findViolatedInvariant(successor.state, state)) return undefined;
|
|
150
|
-
if (search.satisfiesConstraints(successor.state, state)) next.push(successor.state);
|
|
151
|
-
else table.remove(fingerprint[0], fingerprint[1]);
|
|
152
|
-
continue;
|
|
132
|
+
if (generatedStates) {
|
|
133
|
+
writeStateFingerprint(successor.state, fingerprint);
|
|
134
|
+
if (!generatedStates.insert(fingerprint[0], fingerprint[1])) continue;
|
|
153
135
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
136
|
+
canonicalize(successor.state);
|
|
137
|
+
const fingerprintState = search.getFingerprintState(successor.state);
|
|
138
|
+
writeStateFingerprint(fingerprintState, fingerprint);
|
|
139
|
+
if (!insertSharedFingerprint(shared.fingerprints, fingerprint[0], fingerprint[1])) continue;
|
|
140
|
+
if (search.findViolatedInvariant(successor.state, state)) return undefined;
|
|
141
|
+
if (!search.satisfiesConstraints(successor.state, state)) continue;
|
|
142
|
+
expansion.next.push(successor.state);
|
|
143
|
+
expansion.fingerprintStates.push(fingerprintState);
|
|
144
|
+
expansion.added++;
|
|
158
145
|
}
|
|
146
|
+
expansion.outDegrees[Math.min(expansion.added, OUT_DEGREE_BUCKETS - 1)]++;
|
|
147
|
+
expansion.successors = undefined;
|
|
148
|
+
expansion.index = 0;
|
|
149
|
+
expansion.added = 0;
|
|
159
150
|
}
|
|
160
|
-
return
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const answer = (fingerprints: Int32Array | number[]) => {
|
|
164
|
-
const bits = new Uint8Array(fingerprints.length / 2);
|
|
165
|
-
for (let index = 0; index < bits.length; index++) {
|
|
166
|
-
bits[index] = table.insert(fingerprints[index * 2], fingerprints[index * 2 + 1]) ? 1 : 0;
|
|
167
|
-
}
|
|
168
|
-
return bits;
|
|
151
|
+
return "done";
|
|
169
152
|
};
|
|
170
153
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
154
|
+
// Each attempt asks the peer with the most states left. While another worker's request to that
|
|
155
|
+
// peer is open, the worker waits for the peer to answer it. The worker stops once no peer that
|
|
156
|
+
// takes requests has two states left.
|
|
157
|
+
const steal = async (expansion: Expansion) => {
|
|
158
|
+
const skipped = new Set<number>();
|
|
159
|
+
for (;;) {
|
|
160
|
+
const victim = start.ports.reduce<number | undefined>((best, port, index) => {
|
|
161
|
+
if (!port || skipped.has(index) || Atomics.load(remaining, index) < 2) return best;
|
|
162
|
+
return best === undefined || Atomics.load(remaining, index) > Atomics.load(remaining, best)
|
|
163
|
+
? index
|
|
164
|
+
: best;
|
|
165
|
+
}, undefined);
|
|
166
|
+
if (victim === undefined) return false;
|
|
167
|
+
const request = Atomics.compareExchange(requests, victim, 0, self + 1);
|
|
168
|
+
if (request === LOCKED) skipped.add(victim);
|
|
169
|
+
if (request > 0) Atomics.wait(requests, victim, request);
|
|
170
|
+
if (request !== 0) continue;
|
|
171
|
+
const loot = await new Promise<EncodedStates>((resolve) => (waiters.loot = resolve));
|
|
172
|
+
if (loot.count === 0) {
|
|
173
|
+
skipped.add(victim);
|
|
180
174
|
continue;
|
|
181
175
|
}
|
|
182
|
-
|
|
183
|
-
|
|
176
|
+
expansion.states = decode(loot).map(canonicalize);
|
|
177
|
+
expansion.origin = 0;
|
|
178
|
+
expansion.end = expansion.states.length;
|
|
179
|
+
unlock(expansion);
|
|
180
|
+
return true;
|
|
184
181
|
}
|
|
185
|
-
return Int32Array.from(unmarks);
|
|
186
182
|
};
|
|
187
183
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
table.remove(fingerprints[index], fingerprints[index + 1]);
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const balance = async (level: number, next: Value[][], sizes: number[]) => {
|
|
195
|
-
const transfers = getBalancePlan(sizes);
|
|
196
|
-
if (transfers.length === 0) return true;
|
|
197
|
-
const outgoing = new Map(peers.map((peer) => [peer.index, createStateEncoder()]));
|
|
198
|
-
for (const transfer of transfers.filter((candidate) => candidate.from === self)) {
|
|
199
|
-
const encoder = outgoing.get(transfer.to);
|
|
200
|
-
const states = next.splice(next.length - transfer.count, transfer.count);
|
|
201
|
-
if (!encoder || !states.every(encoder.encode)) return false;
|
|
202
|
-
}
|
|
203
|
-
outgoing.forEach((encoder, index) => {
|
|
204
|
-
const states = encoder.finish();
|
|
205
|
-
send(index, { phase: "balance", level, from: self, states }, [states.words.buffer]);
|
|
206
|
-
});
|
|
207
|
-
const incoming = await inbox.receive("balance", level, peers.length);
|
|
208
|
-
incoming.forEach((message) => {
|
|
209
|
-
if (message.states) next.push(...decode(message.states).map(canonicalize));
|
|
210
|
-
});
|
|
211
|
-
return true;
|
|
212
|
-
};
|
|
184
|
+
const report = (message: WorkerMessage) =>
|
|
185
|
+
parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
|
|
213
186
|
|
|
214
187
|
const runLevel = async (level: number): Promise<LevelMessage | undefined> => {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
188
|
+
const expansion: Expansion = {
|
|
189
|
+
states: frontier.states,
|
|
190
|
+
origin: 0,
|
|
191
|
+
end: frontier.states.length,
|
|
192
|
+
successors: undefined,
|
|
193
|
+
index: 0,
|
|
194
|
+
added: 0,
|
|
195
|
+
generated: 0,
|
|
196
|
+
outDegrees: Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0),
|
|
197
|
+
next: [],
|
|
198
|
+
fingerprintStates: [],
|
|
199
|
+
};
|
|
200
|
+
for (;;) {
|
|
201
|
+
const status = expand(expansion);
|
|
202
|
+
if (status === undefined) return undefined;
|
|
203
|
+
lock(expansion);
|
|
204
|
+
if (status === "paused") {
|
|
205
|
+
const resumed = new Promise<void>((resolve) => (waiters.resume = resolve));
|
|
206
|
+
report({ type: "paused" });
|
|
207
|
+
await resumed;
|
|
208
|
+
unlock(expansion);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!(await steal(expansion))) break;
|
|
232
212
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
if (!(await balance(level, next, sizes))) return undefined;
|
|
245
|
-
frontier.states = next;
|
|
246
|
-
return { type: "level", level, generated: expanded.generated, added };
|
|
213
|
+
if (getRankedStringCount() !== startupStrings) return undefined;
|
|
214
|
+
frontier.states = expansion.next;
|
|
215
|
+
Atomics.store(remaining, self, frontier.states.length);
|
|
216
|
+
return {
|
|
217
|
+
type: "level",
|
|
218
|
+
level,
|
|
219
|
+
generated: expansion.generated,
|
|
220
|
+
added: expansion.next.length,
|
|
221
|
+
outDegrees: expansion.outDegrees,
|
|
222
|
+
fingerprints: search.getStoredFingerprints(expansion.next, expansion.fingerprintStates),
|
|
223
|
+
};
|
|
247
224
|
};
|
|
248
225
|
|
|
249
|
-
const report = (message: WorkerMessage) => parent.postMessage(message);
|
|
250
226
|
const step = (level: number) => {
|
|
251
227
|
runLevel(level).then(
|
|
252
228
|
(message) => report(message ?? { type: "abort" }),
|
|
@@ -254,9 +230,14 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
254
230
|
);
|
|
255
231
|
};
|
|
256
232
|
parent.addEventListener("message", (event) => {
|
|
257
|
-
const message = event.data as
|
|
233
|
+
const message = event.data as CoordinatorMessage;
|
|
258
234
|
if (message.type === "level") step(message.level);
|
|
235
|
+
if (message.type === "resize") {
|
|
236
|
+
shared.fingerprints = message.fingerprints;
|
|
237
|
+
waiters.resume();
|
|
238
|
+
}
|
|
259
239
|
});
|
|
240
|
+
Atomics.store(remaining, self, frontier.states.length);
|
|
260
241
|
report({ type: "ready" });
|
|
261
242
|
};
|
|
262
243
|
|