@typeonce/ritmo-devtools 0.6.0 → 0.8.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 +105 -21
- package/dist/Determinism.d.ts +70 -0
- package/dist/Determinism.d.ts.map +1 -0
- package/dist/Determinism.js +137 -0
- package/dist/Determinism.js.map +1 -0
- package/dist/Format.d.ts +8 -1
- package/dist/Format.d.ts.map +1 -1
- package/dist/Format.js +47 -5
- package/dist/Format.js.map +1 -1
- package/dist/Invariant.d.ts +34 -12
- package/dist/Invariant.d.ts.map +1 -1
- package/dist/Invariant.js +6 -19
- package/dist/Invariant.js.map +1 -1
- package/dist/Lint.d.ts +60 -0
- package/dist/Lint.d.ts.map +1 -0
- package/dist/Lint.js +15 -0
- package/dist/Lint.js.map +1 -0
- package/dist/Monitor.d.ts +18 -4
- package/dist/Monitor.d.ts.map +1 -1
- package/dist/Monitor.js +32 -21
- package/dist/Monitor.js.map +1 -1
- package/dist/Replay.d.ts +271 -0
- package/dist/Replay.d.ts.map +1 -0
- package/dist/Replay.js +335 -0
- package/dist/Replay.js.map +1 -0
- package/dist/ReplayNodeSink.d.ts +11 -0
- package/dist/ReplayNodeSink.d.ts.map +1 -0
- package/dist/ReplayNodeSink.js +29 -0
- package/dist/ReplayNodeSink.js.map +1 -0
- package/dist/ReplayVitePlugin.d.ts +41 -0
- package/dist/ReplayVitePlugin.d.ts.map +1 -0
- package/dist/ReplayVitePlugin.js +41 -0
- package/dist/ReplayVitePlugin.js.map +1 -0
- package/dist/ReplayViteSink.d.ts +30 -0
- package/dist/ReplayViteSink.d.ts.map +1 -0
- package/dist/ReplayViteSink.js +9 -0
- package/dist/ReplayViteSink.js.map +1 -0
- package/dist/Session.d.ts +66 -14
- package/dist/Session.d.ts.map +1 -1
- package/dist/Session.js +93 -7
- package/dist/Session.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +57 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/monitor.d.ts +4 -2
- package/dist/internal/monitor.d.ts.map +1 -1
- package/dist/internal/monitor.js +9 -3
- package/dist/internal/monitor.js.map +1 -1
- package/dist/internal/version.d.ts +7 -0
- package/dist/internal/version.d.ts.map +1 -0
- package/dist/internal/version.js +7 -0
- package/dist/internal/version.js.map +1 -0
- package/package.json +5 -2
- package/src/Determinism.ts +190 -0
- package/src/Format.ts +50 -4
- package/src/Invariant.ts +38 -12
- package/src/Lint.ts +72 -0
- package/src/Monitor.ts +49 -26
- package/src/Replay.ts +583 -0
- package/src/ReplayNodeSink.ts +38 -0
- package/src/ReplayVitePlugin.ts +74 -0
- package/src/ReplayViteSink.ts +33 -0
- package/src/Session.ts +154 -24
- package/src/cli.ts +55 -0
- package/src/index.ts +3 -0
- package/src/internal/monitor.ts +12 -5
- package/src/internal/version.ts +6 -0
package/dist/Replay.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import * as Result from "@typeonce/ritmo/Result";
|
|
2
|
+
import * as Format from "./Format.js";
|
|
3
|
+
import { libraryVersion } from "./internal/version.js";
|
|
4
|
+
import * as Rendered from "./Rendered.js";
|
|
5
|
+
import * as Session from "./Session.js";
|
|
6
|
+
/** The `@typeonce/ritmo-devtools` version written into replay files. */
|
|
7
|
+
export const version = libraryVersion;
|
|
8
|
+
/** A sink that keeps replays in memory (for tests, or for a host that delivers them itself). */
|
|
9
|
+
export const memorySink = () => {
|
|
10
|
+
const files = [];
|
|
11
|
+
return { files, save: (file) => { files.push(file); } };
|
|
12
|
+
};
|
|
13
|
+
/** FNV-1a over a string, as 8 hex digits: a cheap fingerprint for checkpoints and schemas. */
|
|
14
|
+
const fingerprint = (text) => {
|
|
15
|
+
let hash = 0x811c9dc5;
|
|
16
|
+
for (let index = 0; index < text.length; index++) {
|
|
17
|
+
hash ^= text.charCodeAt(index);
|
|
18
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
19
|
+
}
|
|
20
|
+
return hash.toString(16).padStart(8, "0");
|
|
21
|
+
};
|
|
22
|
+
/** The schema's fingerprint: what must match for a replay to load. */
|
|
23
|
+
export const schemaHash = (debug) => {
|
|
24
|
+
const description = debug.describe();
|
|
25
|
+
return fingerprint(JSON.stringify({
|
|
26
|
+
components: description.components.map(({ name, storage }) => ({ name, storage })),
|
|
27
|
+
resources: description.resources.map(({ name, storage }) => ({ name, storage })),
|
|
28
|
+
events: description.events,
|
|
29
|
+
relations: description.relations,
|
|
30
|
+
machines: description.machines.map(({ name, states }) => ({ name, states }))
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
const worldHash = (snapshot) => fingerprint(JSON.stringify(snapshot));
|
|
34
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
35
|
+
const isSource = (value) => isRecord(value) && typeof value["snapshot"] === "function";
|
|
36
|
+
/** The names of the services a schedule needs. */
|
|
37
|
+
const serviceNames = (schedule) => schedule.requirements.flatMap((requirement) => isRecord(requirement) && requirement["kind"] === "service" && typeof requirement["name"] === "string" ? [requirement["name"]] : []);
|
|
38
|
+
/**
|
|
39
|
+
* Starts recording a runtime made with `debug: { hostClock }`. The schedule's source
|
|
40
|
+
* services have their `snapshot` wrapped to record what the systems got; the
|
|
41
|
+
* services must not be frozen.
|
|
42
|
+
*/
|
|
43
|
+
export const recorder = (runtime, options) => {
|
|
44
|
+
const keepTicks = Math.max(1, Math.ceil(options.keep / options.step.dt));
|
|
45
|
+
const everyTicks = Math.max(1, Math.round((options.every ?? 5) / options.step.dt));
|
|
46
|
+
const constants = {};
|
|
47
|
+
const restores = [];
|
|
48
|
+
let current;
|
|
49
|
+
for (const name of serviceNames(options.schedule)) {
|
|
50
|
+
const service = runtime.services[name];
|
|
51
|
+
if (!isSource(service)) {
|
|
52
|
+
constants[name] = service;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const own = service.snapshot;
|
|
56
|
+
Reflect.set(service, "snapshot", () => {
|
|
57
|
+
const value = own.call(service);
|
|
58
|
+
if (current !== undefined) {
|
|
59
|
+
const calls = current.get(name);
|
|
60
|
+
if (calls === undefined)
|
|
61
|
+
current.set(name, [value]);
|
|
62
|
+
else
|
|
63
|
+
calls.push(value);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
});
|
|
67
|
+
restores.push(() => Reflect.set(service, "snapshot", own));
|
|
68
|
+
}
|
|
69
|
+
let tick = 0;
|
|
70
|
+
let sent = [];
|
|
71
|
+
let ticks = [];
|
|
72
|
+
/** Snapshot points still needed: the newest one at or before the window start, and every later one. */
|
|
73
|
+
let points = [];
|
|
74
|
+
let checkpoints = [];
|
|
75
|
+
const previous = new Map();
|
|
76
|
+
const broken = new Set();
|
|
77
|
+
const takePoint = () => {
|
|
78
|
+
const snapshot = runtime.snapshot();
|
|
79
|
+
points.push({ tick, snapshot, pendingCommands: runtime.debug.dump({ limit: 0 }).pendingCommands.length });
|
|
80
|
+
checkpoints.push({ tick, hash: worldHash(snapshot) });
|
|
81
|
+
const windowStart = tick - keepTicks;
|
|
82
|
+
let firstNeeded = -1;
|
|
83
|
+
for (let index = 0; index < points.length; index++)
|
|
84
|
+
if (points[index].tick <= windowStart)
|
|
85
|
+
firstNeeded = index;
|
|
86
|
+
if (firstNeeded > 0) {
|
|
87
|
+
points = points.slice(firstNeeded);
|
|
88
|
+
ticks = ticks.filter((record) => record.tick > points[0].tick);
|
|
89
|
+
checkpoints = checkpoints.filter((checkpoint) => checkpoint.tick >= points[0].tick);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
takePoint();
|
|
93
|
+
const save = (reason) => {
|
|
94
|
+
const start = points[0];
|
|
95
|
+
const file = {
|
|
96
|
+
format: "ritmo-replay",
|
|
97
|
+
version: 1,
|
|
98
|
+
library: libraryVersion,
|
|
99
|
+
schema: schemaHash(runtime.debug),
|
|
100
|
+
schedule: options.name,
|
|
101
|
+
reason,
|
|
102
|
+
savedAt: options.hostClock.now(),
|
|
103
|
+
constants,
|
|
104
|
+
start,
|
|
105
|
+
ticks: ticks.filter((record) => record.tick > start.tick),
|
|
106
|
+
checkpoints: checkpoints.filter((checkpoint) => checkpoint.tick >= start.tick)
|
|
107
|
+
};
|
|
108
|
+
options.sink.save(file);
|
|
109
|
+
return file;
|
|
110
|
+
};
|
|
111
|
+
const check = runtime.inspect;
|
|
112
|
+
const stopObserving = runtime.debug.observeHost((event) => {
|
|
113
|
+
if (event.type === "send") {
|
|
114
|
+
sent.push({ event: event.event, value: event.value });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (event.type === "failure.ignored") {
|
|
118
|
+
// An unread failure is a failure nobody saw: save it like one.
|
|
119
|
+
if (options.saveOn?.failure === true)
|
|
120
|
+
save(Format.ignoredFailure(event));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!event.schedules.includes(options.schedule))
|
|
124
|
+
return;
|
|
125
|
+
if (event.type === "tick.start") {
|
|
126
|
+
tick += 1;
|
|
127
|
+
current = new Map();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const calls = current ?? new Map();
|
|
131
|
+
current = undefined;
|
|
132
|
+
const changed = {};
|
|
133
|
+
for (const [name, values] of calls) {
|
|
134
|
+
const text = JSON.stringify(values);
|
|
135
|
+
if (previous.get(name) !== text)
|
|
136
|
+
changed[name] = values;
|
|
137
|
+
previous.set(name, text);
|
|
138
|
+
}
|
|
139
|
+
ticks.push({
|
|
140
|
+
tick,
|
|
141
|
+
...(Object.keys(changed).length > 0 ? { services: changed } : {}),
|
|
142
|
+
...(sent.length > 0 ? { sent } : {})
|
|
143
|
+
});
|
|
144
|
+
sent = [];
|
|
145
|
+
if (tick % everyTicks === 0)
|
|
146
|
+
takePoint();
|
|
147
|
+
if (event.outcome !== "ok" && options.saveOn?.failure === true) {
|
|
148
|
+
save(`tick ${tick} ${event.outcome === "defect" ? "threw" : "failed"}: ${describeError(event.error)}`);
|
|
149
|
+
}
|
|
150
|
+
for (const invariant of options.saveOn?.invariants ?? []) {
|
|
151
|
+
const message = check(invariant.inspector);
|
|
152
|
+
if (message === undefined) {
|
|
153
|
+
broken.delete(invariant.name);
|
|
154
|
+
}
|
|
155
|
+
else if (!broken.has(invariant.name)) {
|
|
156
|
+
broken.add(invariant.name);
|
|
157
|
+
save(`invariant ${invariant.name} broke at tick ${tick}: ${message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
save,
|
|
163
|
+
ticks: () => tick,
|
|
164
|
+
stop: () => {
|
|
165
|
+
stopObserving();
|
|
166
|
+
for (const restore of restores)
|
|
167
|
+
restore();
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
const describeError = (error) => {
|
|
172
|
+
if (error instanceof Error)
|
|
173
|
+
return error.message;
|
|
174
|
+
if (isRecord(error) && typeof error["system"] === "string")
|
|
175
|
+
return `${error["system"]} returned ${JSON.stringify(error["error"])}`;
|
|
176
|
+
return JSON.stringify(error) ?? String(error);
|
|
177
|
+
};
|
|
178
|
+
/** Checks the shape of untrusted replay data (`JSON.parse` output). */
|
|
179
|
+
export const parse = (data) => {
|
|
180
|
+
const invalid = (message) => Result.failure({ _tag: "InvalidReplay", message });
|
|
181
|
+
if (!isRecord(data) || data["format"] !== "ritmo-replay")
|
|
182
|
+
return invalid("not a ritmo replay (format)");
|
|
183
|
+
if (data["version"] !== 1)
|
|
184
|
+
return invalid(`unsupported replay version ${JSON.stringify(data["version"])}`);
|
|
185
|
+
for (const key of ["library", "schema", "schedule", "reason"]) {
|
|
186
|
+
if (typeof data[key] !== "string")
|
|
187
|
+
return invalid(`${key} must be a string`);
|
|
188
|
+
}
|
|
189
|
+
const start = data["start"];
|
|
190
|
+
if (!isRecord(start) || typeof start["tick"] !== "number" || !isRecord(start["snapshot"]))
|
|
191
|
+
return invalid("start must hold a tick and a snapshot");
|
|
192
|
+
if (!Array.isArray(data["ticks"]) || !data["ticks"].every((record) => isRecord(record) && typeof record["tick"] === "number"))
|
|
193
|
+
return invalid("ticks must be tick records");
|
|
194
|
+
if (!Array.isArray(data["checkpoints"]))
|
|
195
|
+
return invalid("checkpoints must be a list");
|
|
196
|
+
if (!isRecord(data["constants"]))
|
|
197
|
+
return invalid("constants must be an object");
|
|
198
|
+
// The snapshot itself is validated by `restore`, and service values by the systems that read them.
|
|
199
|
+
return Result.success(data);
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Opens a replay: builds the game with `make(playback)`, checks the schema,
|
|
203
|
+
* restores the start, and returns a session ready to `play`.
|
|
204
|
+
*/
|
|
205
|
+
export const session = (make, file) => {
|
|
206
|
+
/** The last tick replayed (the start's tick before any). */
|
|
207
|
+
let ran = file.start.tick;
|
|
208
|
+
let calls = new Map();
|
|
209
|
+
const latest = new Map();
|
|
210
|
+
const byTick = new Map(file.ticks.map((record) => [record.tick, record]));
|
|
211
|
+
const playback = {
|
|
212
|
+
source: (descriptor) => ({
|
|
213
|
+
snapshot: () => {
|
|
214
|
+
const values = latest.get(descriptor.name) ?? [];
|
|
215
|
+
const index = calls.get(descriptor.name) ?? 0;
|
|
216
|
+
calls.set(descriptor.name, index + 1);
|
|
217
|
+
// Each call this tick returns what the recorded call returned (the last one again past the end).
|
|
218
|
+
return values[Math.min(index, values.length - 1)];
|
|
219
|
+
}
|
|
220
|
+
}),
|
|
221
|
+
constant: (descriptor) => file.constants[descriptor.name]
|
|
222
|
+
};
|
|
223
|
+
const target = make(playback);
|
|
224
|
+
const schedule = target.schedules[file.schedule];
|
|
225
|
+
if (schedule === undefined)
|
|
226
|
+
return Result.failure({ _tag: "MissingSchedule", schedule: file.schedule });
|
|
227
|
+
const current = schemaHash(target.runtime.debug);
|
|
228
|
+
if (current !== file.schema) {
|
|
229
|
+
return Result.failure({ _tag: "SchemaMismatch", message: "replay recorded with an older schema", recorded: file.schema, current });
|
|
230
|
+
}
|
|
231
|
+
const restored = target.runtime.restore(file.start.snapshot);
|
|
232
|
+
if (!restored.ok)
|
|
233
|
+
return Result.failure({ _tag: "RestoreFailed", error: restored.error });
|
|
234
|
+
const replay = Session.make(target.runtime, { schedules: { [file.schedule]: schedule }, hostClock: target.hostClock });
|
|
235
|
+
const send = target.runtime.send;
|
|
236
|
+
const events = new Map(target.runtime.debug.describe().events.map(({ name }) => [name, { kind: "event", name, key: Symbol.for(`ritmo/event/${name}`) }]));
|
|
237
|
+
const hashes = new Map([[ran, worldHash(target.runtime.snapshot())]]);
|
|
238
|
+
const recorded = new Map(file.checkpoints.map(({ tick, hash }) => [tick, hash]));
|
|
239
|
+
const last = file.ticks.at(-1)?.tick ?? file.start.tick;
|
|
240
|
+
const play = (options = {}) => {
|
|
241
|
+
const to = Math.min(options.to ?? last, last);
|
|
242
|
+
const frames = Math.max(0, to - ran);
|
|
243
|
+
const diverged = [];
|
|
244
|
+
const run = replay.run(file.schedule, {
|
|
245
|
+
frames,
|
|
246
|
+
// `until` runs after each frame: the tick is checked, and the next one's input set up before it runs.
|
|
247
|
+
until: () => {
|
|
248
|
+
ran += 1;
|
|
249
|
+
const hash = worldHash(target.runtime.snapshot());
|
|
250
|
+
hashes.set(ran, hash);
|
|
251
|
+
const expected = recorded.get(ran);
|
|
252
|
+
if (expected !== undefined && expected !== hash)
|
|
253
|
+
diverged.push({ tick: ran, recorded: expected, replayed: hash });
|
|
254
|
+
prepare(ran + 1);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
return Rendered.make({ run: run.data, diverged }, diverged.length === 0
|
|
259
|
+
? `${run.text}\ncheckpoints match`
|
|
260
|
+
: `${run.text}\nDIVERGED at ${diverged.map((entry) => `tick ${entry.tick}`).join(", ")}: the simulation is not deterministic, or the code changed since recording`);
|
|
261
|
+
};
|
|
262
|
+
/** Sets up tick `next`: its sends, and its sources' values. */
|
|
263
|
+
const prepare = (next) => {
|
|
264
|
+
const record = byTick.get(next);
|
|
265
|
+
for (const [name, values] of Object.entries(record?.services ?? {}))
|
|
266
|
+
latest.set(name, values);
|
|
267
|
+
calls = new Map();
|
|
268
|
+
for (const { event, value } of record?.sent ?? []) {
|
|
269
|
+
const descriptor = events.get(event);
|
|
270
|
+
if (descriptor !== undefined)
|
|
271
|
+
send(descriptor, value);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
prepare(ran + 1);
|
|
275
|
+
return Result.success({
|
|
276
|
+
session: replay,
|
|
277
|
+
file,
|
|
278
|
+
play,
|
|
279
|
+
hashes: () => hashes,
|
|
280
|
+
snapshot: () => target.runtime.snapshot()
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Replays twice and compares the world after every tick: different hashes
|
|
285
|
+
* mean the simulation reads something the replay does not hold (wall time,
|
|
286
|
+
* `Math.random`, host state).
|
|
287
|
+
*/
|
|
288
|
+
export const verify = (make, file) => {
|
|
289
|
+
const first = session(make, file);
|
|
290
|
+
if (!first.ok)
|
|
291
|
+
return first;
|
|
292
|
+
const second = session(make, file);
|
|
293
|
+
if (!second.ok)
|
|
294
|
+
return second;
|
|
295
|
+
const played = first.value.play();
|
|
296
|
+
second.value.play();
|
|
297
|
+
const ticks = [...first.value.hashes().keys()].sort((left, right) => left - right);
|
|
298
|
+
const firstDifference = ticks.find((tick) => first.value.hashes().get(tick) !== second.value.hashes().get(tick));
|
|
299
|
+
const deterministic = firstDifference === undefined;
|
|
300
|
+
return Result.success(Rendered.make({ deterministic, firstDifference, diverged: played.data.diverged }, deterministic
|
|
301
|
+
? `deterministic over ${ticks.length - 1} ticks${played.data.diverged.length === 0 ? ", and matches the recording" : `, but differs from the recording at tick ${played.data.diverged[0].tick}`}`
|
|
302
|
+
: `NOT deterministic: two replays differ from tick ${firstDifference}`));
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Cuts ticks `from`..`to` (recorder tick numbers) into a smaller replay that
|
|
306
|
+
* starts at the end of tick `from - 1`: few enough ticks to keep next to a
|
|
307
|
+
* test and play with `session`.
|
|
308
|
+
*/
|
|
309
|
+
export const extract = (make, file, range) => {
|
|
310
|
+
const opened = session(make, file);
|
|
311
|
+
if (!opened.ok)
|
|
312
|
+
return opened;
|
|
313
|
+
opened.value.play({ to: range.from - 1 });
|
|
314
|
+
const start = opened.value.snapshot();
|
|
315
|
+
// Each kept tick carries its sources' values in full, so the extract does not depend on earlier ticks.
|
|
316
|
+
const latest = new Map();
|
|
317
|
+
const ticks = [];
|
|
318
|
+
for (const record of file.ticks) {
|
|
319
|
+
for (const [name, values] of Object.entries(record.services ?? {}))
|
|
320
|
+
latest.set(name, values);
|
|
321
|
+
if (record.tick < range.from || record.tick > range.to)
|
|
322
|
+
continue;
|
|
323
|
+
ticks.push({ tick: record.tick, services: Object.fromEntries(latest), ...(record.sent === undefined ? {} : { sent: record.sent }) });
|
|
324
|
+
}
|
|
325
|
+
opened.value.play({ to: range.to });
|
|
326
|
+
const checkpoints = [...opened.value.hashes()].filter(([tick]) => tick >= range.from - 1 && tick <= range.to).map(([tick, hash]) => ({ tick, hash }));
|
|
327
|
+
return Result.success({
|
|
328
|
+
...file,
|
|
329
|
+
reason: `${file.reason} (ticks ${range.from}-${range.to})`,
|
|
330
|
+
start: { tick: range.from - 1, snapshot: start, pendingCommands: 0 },
|
|
331
|
+
ticks,
|
|
332
|
+
checkpoints
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
//# sourceMappingURL=Replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Replay.js","sourceRoot":"","sources":["../src/Replay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type * as Replay from "./Replay.ts";
|
|
2
|
+
/** Where the last replay was written, and every path written so far. */
|
|
3
|
+
export interface NodeSink extends Replay.Sink {
|
|
4
|
+
readonly written: ReadonlyArray<string>;
|
|
5
|
+
}
|
|
6
|
+
/** The file name of a replay: its save time, then its tick count. */
|
|
7
|
+
export declare const fileName: (file: Replay.ReplayFile) => string;
|
|
8
|
+
export declare const make: (options: {
|
|
9
|
+
readonly directory: string;
|
|
10
|
+
}) => NodeSink;
|
|
11
|
+
//# sourceMappingURL=ReplayNodeSink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayNodeSink.d.ts","sourceRoot":"","sources":["../src/ReplayNodeSink.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAA;AAE1C,wEAAwE;AACxE,MAAM,WAAW,QAAS,SAAQ,MAAM,CAAC,IAAI;IAC3C,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACxC;AAED,qEAAqE;AACrE,eAAO,MAAM,QAAQ,SAAU,MAAM,CAAC,UAAU,KAAG,MACqE,CAAA;AAExH,eAAO,MAAM,IAAI,YAAa;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAG,QAW9D,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `Replay.Sink` for Node: each saved replay becomes a JSON file in a
|
|
3
|
+
* directory. For headless runs (bots, scripts) that record replays.
|
|
4
|
+
*
|
|
5
|
+
* @module ReplayNodeSink
|
|
6
|
+
* @docGroup devtools
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const recorder = Replay.recorder(runtime, { ..., sink: ReplayNodeSink.make({ directory: ".replays" }) })
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
/** The file name of a replay: its save time, then its tick count. */
|
|
16
|
+
export const fileName = (file) => `${new Date(file.savedAt).toISOString().replaceAll(":", "-")}-tick-${file.ticks.at(-1)?.tick ?? file.start.tick}.json`;
|
|
17
|
+
export const make = (options) => {
|
|
18
|
+
const written = [];
|
|
19
|
+
return {
|
|
20
|
+
written,
|
|
21
|
+
save: (file) => {
|
|
22
|
+
mkdirSync(options.directory, { recursive: true });
|
|
23
|
+
const path = join(options.directory, fileName(file));
|
|
24
|
+
writeFileSync(path, `${JSON.stringify(file)}\n`);
|
|
25
|
+
written.push(path);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=ReplayNodeSink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayNodeSink.js","sourceRoot":"","sources":["../src/ReplayNodeSink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAShC,qEAAqE;AACrE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAuB,EAAU,EAAE,CAC1D,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,OAAO,CAAA;AAExH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,OAAuC,EAAY,EAAE;IACxE,MAAM,OAAO,GAAkB,EAAE,CAAA;IACjC,OAAO;QACL,OAAO;QACP,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;YACb,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACjD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;YACpD,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAChD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Vite plugin (Node side) that receives the replays `ReplayViteSink`
|
|
3
|
+
* posts from the page and writes them into the game's repository as
|
|
4
|
+
* `.replays/<time>.json`, logging the path, the tick count, and the reason.
|
|
5
|
+
* Development only: it does nothing in a build.
|
|
6
|
+
*
|
|
7
|
+
* Add `.replays/` to the game's `.gitignore`.
|
|
8
|
+
*
|
|
9
|
+
* @module ReplayVitePlugin
|
|
10
|
+
* @docGroup devtools
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // vite.config.ts
|
|
15
|
+
* import * as ReplayVitePlugin from "@typeonce/ritmo-devtools/ReplayVitePlugin"
|
|
16
|
+
* export default defineConfig({ plugins: [ReplayVitePlugin.plugin()] })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
20
|
+
/** The part of Vite's dev server the plugin uses. */
|
|
21
|
+
export interface DevServer {
|
|
22
|
+
readonly middlewares: {
|
|
23
|
+
readonly use: (path: string, handler: (request: IncomingMessage, response: ServerResponse, next: () => void) => void) => void;
|
|
24
|
+
};
|
|
25
|
+
readonly config: {
|
|
26
|
+
readonly root: string;
|
|
27
|
+
readonly logger: {
|
|
28
|
+
readonly info: (message: string) => void;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** A Vite plugin object. */
|
|
33
|
+
export interface Plugin {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly apply: "serve";
|
|
36
|
+
readonly configureServer: (server: DevServer) => void;
|
|
37
|
+
}
|
|
38
|
+
export declare const plugin: (options?: {
|
|
39
|
+
readonly directory?: string;
|
|
40
|
+
}) => Plugin;
|
|
41
|
+
//# sourceMappingURL=ReplayVitePlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayVitePlugin.d.ts","sourceRoot":"","sources":["../src/ReplayVitePlugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAMhE,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,CAAA;KAC9H,CAAA;IACD,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;SAAE,CAAA;KAAE,CAAA;CAC1G;AAED,4BAA4B;AAC5B,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACtD;AAED,eAAO,MAAM,MAAM,aAAa;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KAAQ,MAkCrE,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as Replay from "./Replay.js";
|
|
2
|
+
import * as ReplayNodeSink from "./ReplayNodeSink.js";
|
|
3
|
+
import { endpoint } from "./ReplayViteSink.js";
|
|
4
|
+
export const plugin = (options = {}) => ({
|
|
5
|
+
name: "ritmo-replays",
|
|
6
|
+
apply: "serve",
|
|
7
|
+
configureServer: (server) => {
|
|
8
|
+
const sink = ReplayNodeSink.make({ directory: `${server.config.root}/${options.directory ?? ".replays"}` });
|
|
9
|
+
server.middlewares.use(endpoint, (request, response, next) => {
|
|
10
|
+
if (request.method !== "POST")
|
|
11
|
+
return next();
|
|
12
|
+
let body = "";
|
|
13
|
+
request.setEncoding("utf8");
|
|
14
|
+
request.on("data", (chunk) => {
|
|
15
|
+
body += chunk;
|
|
16
|
+
});
|
|
17
|
+
request.on("end", () => {
|
|
18
|
+
let data;
|
|
19
|
+
try {
|
|
20
|
+
data = JSON.parse(body);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
data = undefined;
|
|
24
|
+
}
|
|
25
|
+
const parsed = Replay.parse(data);
|
|
26
|
+
if (!parsed.ok) {
|
|
27
|
+
response.statusCode = 400;
|
|
28
|
+
response.end(parsed.error._tag === "InvalidReplay" ? parsed.error.message : parsed.error._tag);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
sink.save(parsed.value);
|
|
32
|
+
const path = sink.written.at(-1).slice(server.config.root.length + 1);
|
|
33
|
+
const ticks = parsed.value.ticks.length;
|
|
34
|
+
server.config.logger.info(`[ritmo] replay saved: ${path} (${ticks} ticks): ${parsed.value.reason}`);
|
|
35
|
+
response.statusCode = 201;
|
|
36
|
+
response.end(path);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=ReplayVitePlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayVitePlugin.js","sourceRoot":"","sources":["../src/ReplayVitePlugin.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAiB9C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,GAAoC,EAAE,EAAU,EAAE,CAAC,CAAC;IAChF,IAAI,EAAE,eAAe;IACrB,KAAK,EAAE,OAAO;IACd,eAAe,EAAE,CAAC,MAAM,EAAE,EAAE;QAC1B,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,SAAS,IAAI,UAAU,EAAE,EAAE,CAAC,CAAA;QAC3G,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;YAC3D,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;gBAAE,OAAO,IAAI,EAAE,CAAA;YAC5C,IAAI,IAAI,GAAG,EAAE,CAAA;YACb,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YAC3B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;gBACnC,IAAI,IAAI,KAAK,CAAA;YACf,CAAC,CAAC,CAAA;YACF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACrB,IAAI,IAAa,CAAA;gBACjB,IAAI,CAAC;oBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACzB,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,GAAG,SAAS,CAAA;gBAClB,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACjC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;oBACf,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAA;oBACzB,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;oBAC9F,OAAM;gBACR,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;gBACvB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;gBACtE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA;gBACvC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,IAAI,KAAK,KAAK,YAAY,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;gBACnG,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAA;gBACzB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YACpB,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `Replay.Sink` for a page served by Vite in development: each saved
|
|
3
|
+
* replay is posted to the dev server, where `ReplayVitePlugin` writes it
|
|
4
|
+
* into the game's repository (`.replays/`). The page's `fetch` is passed in.
|
|
5
|
+
*
|
|
6
|
+
* @module ReplayViteSink
|
|
7
|
+
* @docGroup devtools
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* if (import.meta.env.DEV) {
|
|
12
|
+
* const sink = ReplayViteSink.make({ fetch: (url, init) => fetch(url, init) })
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type * as Replay from "./Replay.ts";
|
|
17
|
+
/** The dev server's endpoint for replays (what `ReplayVitePlugin` serves). */
|
|
18
|
+
export declare const endpoint = "/__ritmo/replays";
|
|
19
|
+
export interface Options {
|
|
20
|
+
/** The page's `fetch` (or any function with its shape for these two arguments). */
|
|
21
|
+
readonly fetch: (url: string, init: {
|
|
22
|
+
readonly method: "POST";
|
|
23
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
24
|
+
readonly body: string;
|
|
25
|
+
}) => Promise<unknown>;
|
|
26
|
+
/** Called when a post fails (the dev server is down, the plugin is not installed). */
|
|
27
|
+
readonly onError?: (error: unknown) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare const make: (options: Options) => Replay.Sink;
|
|
30
|
+
//# sourceMappingURL=ReplayViteSink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayViteSink.d.ts","sourceRoot":"","sources":["../src/ReplayViteSink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAA;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,qBAAqB,CAAA;AAE1C,MAAM,WAAW,OAAO;IACtB,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/J,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AAED,eAAO,MAAM,IAAI,YAAa,OAAO,KAAG,MAAM,CAAC,IAK7C,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The dev server's endpoint for replays (what `ReplayVitePlugin` serves). */
|
|
2
|
+
export const endpoint = "/__ritmo/replays";
|
|
3
|
+
export const make = (options) => ({
|
|
4
|
+
save: (file) => {
|
|
5
|
+
void options.fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(file) })
|
|
6
|
+
.catch((error) => options.onError?.(error));
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=ReplayViteSink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayViteSink.js","sourceRoot":"","sources":["../src/ReplayViteSink.ts"],"names":[],"mappings":"AAiBA,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAG,kBAAkB,CAAA;AAS1C,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,OAAgB,EAAe,EAAE,CAAC,CAAC;IACtD,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;QACb,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;aAC1H,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IACxD,CAAC;CACF,CAAC,CAAA"}
|