@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/src/Monitor.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* The monitor observes the runtime only while it is open, so a closed monitor
|
|
16
16
|
* costs nothing. While open it reduces trace events to counts and shows its
|
|
17
|
-
* own cost per tick. It needs a runtime made with `debug:
|
|
17
|
+
* own cost per tick. It needs a runtime made with `debug: { hostClock }`, and the page passed as `host`.
|
|
18
18
|
*
|
|
19
19
|
* @module Monitor
|
|
20
20
|
* @docGroup devtools
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* @example
|
|
23
23
|
* ```ts
|
|
24
24
|
* const monitor = Monitor.attach(runtime, {
|
|
25
|
+
* host: window,
|
|
25
26
|
* schedules: { fixedUpdate: step, frame },
|
|
26
27
|
* pick: (event) => entityUnderPointer(event),
|
|
27
28
|
* locate: (entity) => screenPositionOf(entity)
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
* ```
|
|
31
32
|
*/
|
|
32
33
|
import type * as Debug from "@typeonce/ritmo/Debug"
|
|
33
|
-
import type { ExecutableScheduleDefinition } from "@typeonce/ritmo/Schedule"
|
|
34
|
+
import type { AnyScheduleOrder, ExecutableScheduleDefinition } from "@typeonce/ritmo/Schedule"
|
|
34
35
|
import type { Schema } from "@typeonce/ritmo/Schema"
|
|
35
36
|
|
|
36
37
|
import * as Format from "./Format.ts"
|
|
@@ -57,8 +58,14 @@ export interface Options<S extends Schema.Any, Root> {
|
|
|
57
58
|
* panel and make up the system map. Name the schedule objects actually
|
|
58
59
|
* passed to `tick`: nested schedules are flattened into them.
|
|
59
60
|
*/
|
|
60
|
-
readonly schedules: Readonly<Record<string, ExecutableScheduleDefinition<S, any, Root, any, any>>>
|
|
61
|
-
/**
|
|
61
|
+
readonly schedules: Readonly<Record<string, ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>>
|
|
62
|
+
/**
|
|
63
|
+
* The page the panel lives in, passed explicitly (`window`): its
|
|
64
|
+
* `document`, timers, animation frames, keyboard events, and
|
|
65
|
+
* `performance` clock. The monitor reads no globals.
|
|
66
|
+
*/
|
|
67
|
+
readonly host: Window
|
|
68
|
+
/** Where the panel is added. Defaults to `host.document.body`. */
|
|
62
69
|
readonly mount?: HTMLElement
|
|
63
70
|
/** `KeyboardEvent.code` that toggles the panel. Defaults to `Backslash`. */
|
|
64
71
|
readonly key?: string
|
|
@@ -70,6 +77,11 @@ export interface Options<S extends Schema.Any, Root> {
|
|
|
70
77
|
readonly locate?: (entity: number) => { readonly x: number; readonly y: number } | undefined
|
|
71
78
|
/** Milliseconds between panel refreshes. Defaults to 250. */
|
|
72
79
|
readonly refreshMs?: number
|
|
80
|
+
/**
|
|
81
|
+
* A replay recorder (`Replay.recorder`): adds a "Save replay" button that
|
|
82
|
+
* saves the last seconds of play with the reason "Save replay button".
|
|
83
|
+
*/
|
|
84
|
+
readonly replay?: { readonly save: (reason: string) => unknown }
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
export interface Monitor {
|
|
@@ -154,7 +166,7 @@ const accessOf = (system: Debug.SystemDescription): Access => {
|
|
|
154
166
|
const unique = (values: ReadonlyArray<string>) => [...new Set(values)]
|
|
155
167
|
const writes = unique(system.queries.flatMap((query) => query.writes))
|
|
156
168
|
return {
|
|
157
|
-
reads: unique(system.queries.flatMap((query) => [...query.reads, ...query.optional, ...query.added, ...query.changed])).filter((name) => !writes.includes(name)),
|
|
169
|
+
reads: unique(system.queries.flatMap((query) => [...query.reads, ...query.optional, ...query.added, ...query.changed, ...query.anyOf.flatMap((members) => [...members.added, ...members.changed])])).filter((name) => !writes.includes(name)),
|
|
158
170
|
writes,
|
|
159
171
|
resourceReads: system.resources.reads.filter((name) => !system.resources.writes.includes(name)),
|
|
160
172
|
resourceWrites: system.resources.writes,
|
|
@@ -243,18 +255,19 @@ const STYLE = `
|
|
|
243
255
|
export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, options: Options<S, Root>): Monitor => {
|
|
244
256
|
const debug = runtime.debug
|
|
245
257
|
debug.nameSchedules(options.schedules)
|
|
246
|
-
const
|
|
258
|
+
const host = options.host
|
|
259
|
+
const mount = options.mount ?? host.document.body
|
|
247
260
|
const key = options.key ?? "Backslash"
|
|
248
261
|
const refreshMs = options.refreshMs ?? 250
|
|
249
262
|
|
|
250
|
-
if (document.getElementById("btm-style") === null) {
|
|
251
|
-
const style = document.createElement("style")
|
|
263
|
+
if (host.document.getElementById("btm-style") === null) {
|
|
264
|
+
const style = host.document.createElement("style")
|
|
252
265
|
style.id = "btm-style"
|
|
253
266
|
style.textContent = STYLE
|
|
254
|
-
document.head.append(style)
|
|
267
|
+
host.document.head.append(style)
|
|
255
268
|
}
|
|
256
269
|
|
|
257
|
-
const root = document.createElement("div")
|
|
270
|
+
const root = host.document.createElement("div")
|
|
258
271
|
root.className = "btm"
|
|
259
272
|
root.innerHTML = `
|
|
260
273
|
<button class="btm-toggle" type="button" title="Toggle the Ritmo monitor">◉ monitor</button>
|
|
@@ -264,6 +277,7 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
264
277
|
<button class="btm-tab" data-tab="overview" type="button">Overview</button>
|
|
265
278
|
<button class="btm-tab" data-tab="systems" type="button">Systems</button>
|
|
266
279
|
<button class="btm-tab" data-tab="entity" type="button">Entity</button>
|
|
280
|
+
${options.replay === undefined ? "" : `<button class="btm-save-replay" type="button" title="Save the last seconds of play as a replay">Save replay</button>`}
|
|
267
281
|
<button class="btm-close" type="button" title="Close">×</button>
|
|
268
282
|
</div>
|
|
269
283
|
<div class="btm-status"></div>
|
|
@@ -294,7 +308,7 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
294
308
|
let tab: Tab = "overview"
|
|
295
309
|
let collector: Collector | undefined
|
|
296
310
|
let stopObserving: (() => void) | undefined
|
|
297
|
-
let timer:
|
|
311
|
+
let timer: number | undefined
|
|
298
312
|
let openedAt = 0
|
|
299
313
|
let description: Debug.Description | undefined
|
|
300
314
|
/** How many systems read each component, to name what systems spawn. */
|
|
@@ -515,20 +529,20 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
515
529
|
|
|
516
530
|
const render = () => {
|
|
517
531
|
if (collector === undefined) return
|
|
518
|
-
const started = performance.now()
|
|
532
|
+
const started = host.performance.now()
|
|
519
533
|
const history = collector.history
|
|
520
534
|
const latest = history.slice(-RECENT)
|
|
521
535
|
const overhead = latest.reduce((total, sample) => total + sample.overheadMs, 0)
|
|
522
536
|
const ticks = latest.reduce((total, sample) => total + [...sample.schedules.values()].reduce((sum, window) => sum + window.ticks, 0), 0)
|
|
523
|
-
status.textContent = `observing for ${Math.round((performance.now() - openedAt) / 1000)}s · frame ${debug.frame()} · panel ${ticks === 0 ? "–" : number(overhead / ticks, 3)} ms/tick + ${number(renderMs, 1)} ms/refresh`
|
|
537
|
+
status.textContent = `observing for ${Math.round((host.performance.now() - openedAt) / 1000)}s · frame ${debug.frame()} · panel ${ticks === 0 ? "–" : number(overhead / ticks, 3)} ms/tick + ${number(renderMs, 1)} ms/refresh`
|
|
524
538
|
status.title = "The panel's own work. Tracing (on only while the panel is open) also slows every system run a little, and the times shown include it: compare them with each other, not with a closed-monitor run."
|
|
525
539
|
body.innerHTML = tab === "overview" ? renderOverview(history) : tab === "systems" ? renderSystems(history) : renderEntity()
|
|
526
|
-
renderMs = performance.now() - started
|
|
540
|
+
renderMs = host.performance.now() - started
|
|
527
541
|
}
|
|
528
542
|
|
|
529
543
|
const refresh = () => {
|
|
530
544
|
if (collector === undefined) return
|
|
531
|
-
collector.sample(performance.now())
|
|
545
|
+
collector.sample(host.performance.now())
|
|
532
546
|
render()
|
|
533
547
|
}
|
|
534
548
|
|
|
@@ -539,7 +553,7 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
539
553
|
ring.style.left = `${at.x}px`
|
|
540
554
|
ring.style.top = `${at.y}px`
|
|
541
555
|
}
|
|
542
|
-
if (!panel.hidden) requestAnimationFrame(placeRing)
|
|
556
|
+
if (!panel.hidden) host.requestAnimationFrame(placeRing)
|
|
543
557
|
}
|
|
544
558
|
|
|
545
559
|
const setTab = (next: Tab) => {
|
|
@@ -555,14 +569,14 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
555
569
|
readers = new Map(description.access.components.map((entry) => [entry.name, entry.readers.length]))
|
|
556
570
|
prefix = commonPrefix([...description.systems.map((system) => system.name), ...description.components.map((component) => component.name)])
|
|
557
571
|
browse.replaceChildren(new Option("browse by component…", ""), ...description.components.map((component) => new Option(short(component.name), component.name)))
|
|
558
|
-
collector = new Collector(() => debug.population(), performance.now())
|
|
572
|
+
collector = new Collector(() => debug.population(), () => host.performance.now())
|
|
559
573
|
collector.select(selected)
|
|
560
574
|
stopObserving = debug.observe(collector.observe)
|
|
561
|
-
openedAt = performance.now()
|
|
575
|
+
openedAt = host.performance.now()
|
|
562
576
|
panel.hidden = false
|
|
563
|
-
timer = setInterval(refresh, refreshMs)
|
|
577
|
+
timer = host.setInterval(refresh, refreshMs)
|
|
564
578
|
setTab(tab)
|
|
565
|
-
requestAnimationFrame(placeRing)
|
|
579
|
+
host.requestAnimationFrame(placeRing)
|
|
566
580
|
}
|
|
567
581
|
|
|
568
582
|
const close = () => {
|
|
@@ -571,7 +585,7 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
571
585
|
ring.hidden = true
|
|
572
586
|
stopObserving?.()
|
|
573
587
|
stopObserving = undefined
|
|
574
|
-
if (timer !== undefined) clearInterval(timer)
|
|
588
|
+
if (timer !== undefined) host.clearInterval(timer)
|
|
575
589
|
timer = undefined
|
|
576
590
|
collector = undefined
|
|
577
591
|
setPicking(false)
|
|
@@ -603,9 +617,9 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
603
617
|
picking = on
|
|
604
618
|
pickButton.classList.toggle("is-on", on)
|
|
605
619
|
pickButton.textContent = on ? "Click an entity… (Esc)" : "Pick on screen"
|
|
606
|
-
document.documentElement.classList.toggle("btm-picking", on)
|
|
607
|
-
if (on)
|
|
608
|
-
else
|
|
620
|
+
host.document.documentElement.classList.toggle("btm-picking", on)
|
|
621
|
+
if (on) host.addEventListener("pointerdown", onPick, { capture: true })
|
|
622
|
+
else host.removeEventListener("pointerdown", onPick, { capture: true })
|
|
609
623
|
}
|
|
610
624
|
|
|
611
625
|
// ── Events ─────────────────────────────────────────────────────────────
|
|
@@ -620,9 +634,18 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
620
634
|
setPicking(false)
|
|
621
635
|
}
|
|
622
636
|
}
|
|
623
|
-
|
|
637
|
+
host.addEventListener("keydown", onKey)
|
|
624
638
|
toggleButton.addEventListener("click", () => (panel.hidden ? open() : close()))
|
|
625
639
|
query<HTMLButtonElement>(".btm-close").addEventListener("click", close)
|
|
640
|
+
const replay = options.replay
|
|
641
|
+
if (replay !== undefined) {
|
|
642
|
+
const button = query<HTMLButtonElement>(".btm-save-replay")
|
|
643
|
+
button.addEventListener("click", () => {
|
|
644
|
+
replay.save("Save replay button")
|
|
645
|
+
button.textContent = "Saved"
|
|
646
|
+
host.setTimeout(() => { button.textContent = "Save replay" }, 1500)
|
|
647
|
+
})
|
|
648
|
+
}
|
|
626
649
|
for (const button of root.querySelectorAll<HTMLButtonElement>(".btm-tab")) {
|
|
627
650
|
button.addEventListener("click", () => setTab(button.dataset["tab"] as Tab))
|
|
628
651
|
}
|
|
@@ -681,7 +704,7 @@ export const attach = <S extends Schema.Any, Root>(runtime: Target<S, Root>, opt
|
|
|
681
704
|
select,
|
|
682
705
|
detach: () => {
|
|
683
706
|
close()
|
|
684
|
-
|
|
707
|
+
host.removeEventListener("keydown", onKey)
|
|
685
708
|
root.remove()
|
|
686
709
|
}
|
|
687
710
|
}
|