@three-flatland/devtools 0.1.0-alpha.3 → 1.0.0-alpha.6
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 +1 -1
- package/dist/buffers-modal.d.ts +16 -52
- package/dist/buffers-modal.d.ts.map +1 -0
- package/dist/buffers-modal.js +459 -464
- package/dist/buffers-modal.js.map +1 -1
- package/dist/buffers-view.d.ts +22 -34
- package/dist/buffers-view.d.ts.map +1 -0
- package/dist/buffers-view.js +252 -240
- package/dist/buffers-view.js.map +1 -1
- package/dist/create-pane.d.ts +38 -37
- package/dist/create-pane.d.ts.map +1 -0
- package/dist/create-pane.js +197 -166
- package/dist/create-pane.js.map +1 -1
- package/dist/dashboard/app.tsx +2 -0
- package/dist/dashboard/export.ts +1 -3
- package/dist/dashboard/frame-cursor.ts +81 -0
- package/dist/dashboard/hooks.ts +10 -2
- package/dist/dashboard/index.html +61 -0
- package/dist/dashboard/panels/batches.tsx +5 -18
- package/dist/dashboard/panels/buffers.tsx +52 -26
- package/dist/dashboard/panels/env.tsx +5 -6
- package/dist/dashboard/panels/protocol-log.tsx +146 -58
- package/dist/dashboard/panels/registry.tsx +55 -69
- package/dist/dashboard/panels/scrubber.tsx +100 -0
- package/dist/dashboard/panels/stats.tsx +60 -32
- package/dist/dashboard/protocol-store.ts +25 -8
- package/dist/dashboard/vendor/hooks.module.d.ts +146 -0
- package/dist/dashboard/vendor/jsx-runtime.d.ts +55 -0
- package/dist/dashboard/vendor/jsx.d.ts +3123 -0
- package/dist/dashboard/vendor/preact.module.d.ts +397 -0
- package/dist/devtools-client.d.ts +312 -300
- package/dist/devtools-client.d.ts.map +1 -0
- package/dist/devtools-client.js +866 -838
- package/dist/devtools-client.js.map +1 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.js +5 -12
- package/dist/perf-trace.d.ts +6 -4
- package/dist/perf-trace.d.ts.map +1 -0
- package/dist/perf-trace.js +80 -66
- package/dist/perf-trace.js.map +1 -1
- package/dist/plugins.d.ts +6 -6
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +5 -5
- package/dist/plugins.js.map +1 -1
- package/dist/provider-switcher.d.ts +8 -15
- package/dist/provider-switcher.d.ts.map +1 -0
- package/dist/provider-switcher.js +68 -68
- package/dist/provider-switcher.js.map +1 -1
- package/dist/react/devtools-provider.d.ts +11 -10
- package/dist/react/devtools-provider.d.ts.map +1 -0
- package/dist/react/devtools-provider.js +60 -32
- package/dist/react/devtools-provider.js.map +1 -1
- package/dist/react/use-fps-graph.d.ts +7 -7
- package/dist/react/use-fps-graph.d.ts.map +1 -0
- package/dist/react/use-fps-graph.js +27 -21
- package/dist/react/use-fps-graph.js.map +1 -1
- package/dist/react/use-pane-button.d.ts +4 -4
- package/dist/react/use-pane-button.d.ts.map +1 -0
- package/dist/react/use-pane-button.js +35 -30
- package/dist/react/use-pane-button.js.map +1 -1
- package/dist/react/use-pane-folder.d.ts +5 -4
- package/dist/react/use-pane-folder.d.ts.map +1 -0
- package/dist/react/use-pane-folder.js +33 -20
- package/dist/react/use-pane-folder.js.map +1 -1
- package/dist/react/use-pane-input.d.ts +31 -30
- package/dist/react/use-pane-input.d.ts.map +1 -0
- package/dist/react/use-pane-input.js +46 -42
- package/dist/react/use-pane-input.js.map +1 -1
- package/dist/react/use-pane-radio-grid.d.ts +20 -20
- package/dist/react/use-pane-radio-grid.d.ts.map +1 -0
- package/dist/react/use-pane-radio-grid.js +72 -50
- package/dist/react/use-pane-radio-grid.js.map +1 -1
- package/dist/react/use-pane.d.ts +4 -4
- package/dist/react/use-pane.d.ts.map +1 -0
- package/dist/react/use-pane.js +52 -17
- package/dist/react/use-pane.js.map +1 -1
- package/dist/react.d.ts +10 -12
- package/dist/react.js +2 -12
- package/dist/registry-view.d.ts +8 -21
- package/dist/registry-view.d.ts.map +1 -0
- package/dist/registry-view.js +357 -338
- package/dist/registry-view.js.map +1 -1
- package/dist/relay.d.ts +16 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +189 -0
- package/dist/relay.js.map +1 -0
- package/dist/remote.d.ts +10 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +28 -0
- package/dist/remote.js.map +1 -0
- package/dist/stats-graph.d.ts +23 -31
- package/dist/stats-graph.d.ts.map +1 -0
- package/dist/stats-graph.js +323 -264
- package/dist/stats-graph.js.map +1 -1
- package/dist/stats-row.d.ts +8 -21
- package/dist/stats-row.d.ts.map +1 -0
- package/dist/stats-row.js +99 -101
- package/dist/stats-row.js.map +1 -1
- package/dist/theme.d.ts +3 -1
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +43 -54
- package/dist/theme.js.map +1 -1
- package/dist/vite-plugin.d.ts +7 -6
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +87 -78
- package/dist/vite-plugin.js.map +1 -1
- package/package.json +33 -29
- package/dist/buffers-modal.cjs +0 -491
- package/dist/buffers-modal.cjs.map +0 -1
- package/dist/buffers-modal.d.cts +0 -62
- package/dist/buffers-view.cjs +0 -269
- package/dist/buffers-view.cjs.map +0 -1
- package/dist/buffers-view.d.cts +0 -39
- package/dist/create-pane.cjs +0 -203
- package/dist/create-pane.cjs.map +0 -1
- package/dist/create-pane.d.cts +0 -66
- package/dist/devtools-client.cjs +0 -870
- package/dist/devtools-client.cjs.map +0 -1
- package/dist/devtools-client.d.cts +0 -362
- package/dist/index.cjs +0 -42
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -8
- package/dist/index.js.map +0 -1
- package/dist/perf-trace.cjs +0 -103
- package/dist/perf-trace.cjs.map +0 -1
- package/dist/perf-trace.d.cts +0 -27
- package/dist/plugins.cjs +0 -44
- package/dist/plugins.cjs.map +0 -1
- package/dist/plugins.d.cts +0 -7
- package/dist/provider-switcher.cjs +0 -95
- package/dist/provider-switcher.cjs.map +0 -1
- package/dist/provider-switcher.d.cts +0 -18
- package/dist/react/devtools-provider.cjs +0 -59
- package/dist/react/devtools-provider.cjs.map +0 -1
- package/dist/react/devtools-provider.d.cts +0 -39
- package/dist/react/use-fps-graph.cjs +0 -49
- package/dist/react/use-fps-graph.cjs.map +0 -1
- package/dist/react/use-fps-graph.d.cts +0 -15
- package/dist/react/use-pane-button.cjs +0 -58
- package/dist/react/use-pane-button.cjs.map +0 -1
- package/dist/react/use-pane-button.d.cts +0 -10
- package/dist/react/use-pane-folder.cjs +0 -48
- package/dist/react/use-pane-folder.cjs.map +0 -1
- package/dist/react/use-pane-folder.d.cts +0 -15
- package/dist/react/use-pane-input.cjs +0 -70
- package/dist/react/use-pane-input.cjs.map +0 -1
- package/dist/react/use-pane-input.d.cts +0 -44
- package/dist/react/use-pane-radio-grid.cjs +0 -78
- package/dist/react/use-pane-radio-grid.cjs.map +0 -1
- package/dist/react/use-pane-radio-grid.d.cts +0 -35
- package/dist/react/use-pane.cjs +0 -45
- package/dist/react/use-pane.cjs.map +0 -1
- package/dist/react/use-pane.d.cts +0 -40
- package/dist/react.cjs +0 -50
- package/dist/react.cjs.map +0 -1
- package/dist/react.d.cts +0 -12
- package/dist/react.js.map +0 -1
- package/dist/registry-view.cjs +0 -371
- package/dist/registry-view.cjs.map +0 -1
- package/dist/registry-view.d.cts +0 -24
- package/dist/stats-graph.cjs +0 -297
- package/dist/stats-graph.cjs.map +0 -1
- package/dist/stats-graph.d.cts +0 -40
- package/dist/stats-row.cjs +0 -153
- package/dist/stats-row.cjs.map +0 -1
- package/dist/stats-row.d.cts +0 -24
- package/dist/theme.cjs +0 -260
- package/dist/theme.cjs.map +0 -1
- package/dist/theme.d.cts +0 -10
- package/dist/vite-plugin.cjs +0 -113
- package/dist/vite-plugin.cjs.map +0 -1
- package/dist/vite-plugin.d.cts +0 -9
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
/**
|
|
3
|
+
* Time-travel scrubber (#29 Phase A) — sits under the stats strip:
|
|
4
|
+
*
|
|
5
|
+
* [◀] ──●── [▶] [N/M] [● LIVE]
|
|
6
|
+
*
|
|
7
|
+
* Entry points: drag the slider, step ◀/▶, or click a protocol-log
|
|
8
|
+
* row. Return to live: the LIVE button, double-click the slider, or
|
|
9
|
+
* Esc. The scrubbable range is the frames covered by the stats series
|
|
10
|
+
* ring (~17s at 60 fps); deeper history is the Phase C flight recorder.
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect, useLayoutEffect } from 'preact/hooks'
|
|
13
|
+
import { useDevtoolsState, useFrameTick } from '../hooks.js'
|
|
14
|
+
import { addFrameCursorListener, getFrameCursor, goLive, setCursorProvider, setFrameCursor } from '../frame-cursor.js'
|
|
15
|
+
import { useState } from 'preact/hooks'
|
|
16
|
+
|
|
17
|
+
/** Oldest/newest engine frame currently held by the stats ring. */
|
|
18
|
+
function frameRange(state: ReturnType<typeof useDevtoolsState>): {
|
|
19
|
+
min: number
|
|
20
|
+
max: number
|
|
21
|
+
} | null {
|
|
22
|
+
const ring = state.series.frames
|
|
23
|
+
if (ring.length === 0) return null
|
|
24
|
+
const size = ring.data.length
|
|
25
|
+
const newest = ring.data[(ring.write - 1 + size) % size]!
|
|
26
|
+
const oldest = ring.data[(ring.write - ring.length + size) % size]!
|
|
27
|
+
return { min: oldest, max: newest }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function Scrubber() {
|
|
31
|
+
const state = useDevtoolsState()
|
|
32
|
+
useFrameTick()
|
|
33
|
+
const [, setTick] = useState(0)
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
return addFrameCursorListener(() => setTick((n) => (n + 1) & 0xffff))
|
|
37
|
+
}, [])
|
|
38
|
+
|
|
39
|
+
// Per-provider cursor memory follows the producer switcher. Layout
|
|
40
|
+
// effect, not plain effect: the sync must land before paint so panels
|
|
41
|
+
// reading getFrameCursor() during render never show one frame with the
|
|
42
|
+
// previous provider's cursor. Still post-render, so the listener →
|
|
43
|
+
// setState it triggers isn't mid-render.
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
setCursorProvider(state.selectedProviderId)
|
|
46
|
+
}, [state.selectedProviderId])
|
|
47
|
+
|
|
48
|
+
// Esc returns to live (no modal handling needed here — modals stop
|
|
49
|
+
// propagation of their own keys).
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const onKey = (ev: KeyboardEvent): void => {
|
|
52
|
+
// A handler that consumed Esc (detail pane close, modal) wins.
|
|
53
|
+
if (ev.defaultPrevented) return
|
|
54
|
+
if (ev.key === 'Escape' && getFrameCursor() !== null) goLive()
|
|
55
|
+
}
|
|
56
|
+
window.addEventListener('keydown', onKey)
|
|
57
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
58
|
+
}, [])
|
|
59
|
+
|
|
60
|
+
const cursor = getFrameCursor()
|
|
61
|
+
const range = frameRange(state)
|
|
62
|
+
if (range === null) return null
|
|
63
|
+
|
|
64
|
+
const parked = cursor !== null
|
|
65
|
+
const shown = parked ? Math.min(Math.max(cursor, range.min), range.max) : range.max
|
|
66
|
+
|
|
67
|
+
const step = (delta: number): void => {
|
|
68
|
+
const base = parked ? shown : range.max
|
|
69
|
+
const next = Math.min(range.max, Math.max(range.min, base + delta))
|
|
70
|
+
setFrameCursor(next)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div class={`frame-scrubber${parked ? ' parked' : ''}`}>
|
|
75
|
+
<button class="scrub-step" title="Back one frame" onClick={() => step(-1)}>
|
|
76
|
+
◀
|
|
77
|
+
</button>
|
|
78
|
+
<input
|
|
79
|
+
class="scrub-slider"
|
|
80
|
+
type="range"
|
|
81
|
+
min={range.min}
|
|
82
|
+
max={range.max}
|
|
83
|
+
value={shown}
|
|
84
|
+
onInput={(ev) => setFrameCursor(Number((ev.target as HTMLInputElement).value))}
|
|
85
|
+
onDblClick={() => goLive()}
|
|
86
|
+
/>
|
|
87
|
+
<button class="scrub-step" title="Forward one frame" onClick={() => step(1)}>
|
|
88
|
+
▶
|
|
89
|
+
</button>
|
|
90
|
+
<span class="scrub-frame">{parked ? `${shown}/${range.max}` : `${range.max}`}</span>
|
|
91
|
+
<button
|
|
92
|
+
class={`scrub-live${parked ? '' : ' active'}`}
|
|
93
|
+
title={parked ? 'Return to live' : 'Live'}
|
|
94
|
+
onClick={() => goLive()}
|
|
95
|
+
>
|
|
96
|
+
● LIVE
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* jittering zero.
|
|
14
14
|
*/
|
|
15
15
|
import { useEffect, useRef, useState } from 'preact/hooks'
|
|
16
|
-
import type { DevtoolsSeries, DevtoolsState } from '../../devtools-client.js'
|
|
16
|
+
import type { DevtoolsSeries, DevtoolsState, DevtoolsFrameSeries } from '../../devtools-client.js'
|
|
17
17
|
import { useDevtoolsState } from '../hooks.js'
|
|
18
|
+
import { getFrameCursor } from '../frame-cursor.js'
|
|
18
19
|
|
|
19
20
|
interface StatDef {
|
|
20
21
|
label: string
|
|
@@ -35,15 +36,15 @@ function fmtMB(v: number | undefined): string {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const STATS: StatDef[] = [
|
|
38
|
-
{ label: 'fps',
|
|
39
|
-
{ label: 'cpu',
|
|
40
|
-
{ label: 'gpu',
|
|
41
|
-
{ label: 'draws', pick: (s) => s.drawCalls,
|
|
42
|
-
{ label: 'tris',
|
|
43
|
-
{ label: 'prims', pick: (s) => s.primitives, series: (s) => s.series.primitives, format: fmt0,
|
|
44
|
-
{ label: 'geos',
|
|
45
|
-
{ label: 'tex',
|
|
46
|
-
{ label: 'heap',
|
|
39
|
+
{ label: 'fps', pick: (s) => s.fps, series: (s) => s.series.fps, format: fmt0, color: '#47cca9' },
|
|
40
|
+
{ label: 'cpu', pick: (s) => s.cpuMs, series: (s) => s.series.cpuMs, format: fmtMs, color: '#47cc6a' },
|
|
41
|
+
{ label: 'gpu', pick: (s) => s.gpuMs, series: (s) => s.series.gpuMs, format: fmtMs, color: '#ffa347' },
|
|
42
|
+
{ label: 'draws', pick: (s) => s.drawCalls, series: (s) => s.series.drawCalls, format: fmt0, color: '#5eb0ff' },
|
|
43
|
+
{ label: 'tris', pick: (s) => s.triangles, series: (s) => s.series.triangles, format: fmt0, color: '#c792ea' },
|
|
44
|
+
{ label: 'prims', pick: (s) => s.primitives, series: (s) => s.series.primitives, format: fmt0, color: '#ff8fa3' },
|
|
45
|
+
{ label: 'geos', pick: (s) => s.geometries, series: (s) => s.series.geometries, format: fmt0, color: '#f78c6c' },
|
|
46
|
+
{ label: 'tex', pick: (s) => s.textures, series: (s) => s.series.textures, format: fmt0, color: '#ffc371' },
|
|
47
|
+
{ label: 'heap', pick: (s) => s.heapUsedMB, series: (s) => s.series.heapUsedMB, format: fmtMB, color: '#d94c87' },
|
|
47
48
|
]
|
|
48
49
|
|
|
49
50
|
export function StatsStrip() {
|
|
@@ -75,13 +76,7 @@ export function StatsStrip() {
|
|
|
75
76
|
/>
|
|
76
77
|
))}
|
|
77
78
|
</div>
|
|
78
|
-
{expandedDef !== null && (
|
|
79
|
-
<StatDetail
|
|
80
|
-
def={expandedDef}
|
|
81
|
-
state={s}
|
|
82
|
-
onClose={() => setExpandedKey(null)}
|
|
83
|
-
/>
|
|
84
|
-
)}
|
|
79
|
+
{expandedDef !== null && <StatDetail def={expandedDef} state={s} onClose={() => setExpandedKey(null)} />}
|
|
85
80
|
</section>
|
|
86
81
|
)
|
|
87
82
|
}
|
|
@@ -104,8 +99,12 @@ function StatCard({
|
|
|
104
99
|
// instantly to match new peaks, decays slowly afterward (see
|
|
105
100
|
// `drawSeries`'s RANGE_DECAY).
|
|
106
101
|
const rangeRef = useRef<{ min: number; max: number } | null>(null)
|
|
107
|
-
const value = def.pick(state)
|
|
108
102
|
const series = def.series(state)
|
|
103
|
+
// Parked cursor: show the ring sample whose engine frame is the
|
|
104
|
+
// closest ≤ the cursor instead of the live smoothed value.
|
|
105
|
+
const cursor = getFrameCursor()
|
|
106
|
+
const parkedValue = cursor !== null ? seriesValueAtFrame(series, state.series.frames, cursor) : undefined
|
|
107
|
+
const value = cursor !== null ? parkedValue : def.pick(state)
|
|
109
108
|
|
|
110
109
|
useEffect(() => {
|
|
111
110
|
const canvas = canvasRef.current
|
|
@@ -133,16 +132,33 @@ function StatCard({
|
|
|
133
132
|
>
|
|
134
133
|
<canvas class="stat-canvas" ref={canvasRef} />
|
|
135
134
|
<span class="stat-label">{def.label}</span>
|
|
136
|
-
<span class=
|
|
135
|
+
<span class={`stat-value${cursor !== null ? ' stat-parked' : ''}`}>{def.format(value)}</span>
|
|
137
136
|
</button>
|
|
138
137
|
)
|
|
139
138
|
}
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Ring sample for the closest engine frame ≤ `frame` (time-travel
|
|
142
|
+
* cursor). Rings ingest in lockstep, so an index into the frames ring
|
|
143
|
+
* is valid for every series ring.
|
|
144
|
+
*/
|
|
145
|
+
function seriesValueAtFrame(series: DevtoolsSeries, frames: DevtoolsFrameSeries, frame: number): number | undefined {
|
|
146
|
+
const len = Math.min(series.length, frames.length)
|
|
147
|
+
if (len === 0) return undefined
|
|
148
|
+
const size = frames.data.length
|
|
149
|
+
// Walk newest → oldest; rings are small (1024) and this only runs
|
|
150
|
+
// while parked.
|
|
151
|
+
for (let i = 0; i < len; i++) {
|
|
152
|
+
const idx = (frames.write - 1 - i + size) % size
|
|
153
|
+
if (frames.data[idx]! <= frame) {
|
|
154
|
+
return series.data[(series.write - 1 - i + size) % size]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Cursor is older than everything we hold — show the oldest sample.
|
|
158
|
+
return series.data[(series.write - len + size) % size]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function StatDetail({ def, state, onClose }: { def: StatDef; state: DevtoolsState; onClose: () => void }) {
|
|
146
162
|
const series = def.series(state)
|
|
147
163
|
const stats = computeStats(series)
|
|
148
164
|
const histoRef = useRef<HTMLCanvasElement | null>(null)
|
|
@@ -166,7 +182,9 @@ function StatDetail({ def, state, onClose }: {
|
|
|
166
182
|
<div class="stat-detail-header">
|
|
167
183
|
<span class="stat-detail-label">{def.label}</span>
|
|
168
184
|
<span class="stat-detail-samples">{stats.count} samples</span>
|
|
169
|
-
<button type="button" class="stat-detail-close" onClick={onClose} aria-label="Close"
|
|
185
|
+
<button type="button" class="stat-detail-close" onClick={onClose} aria-label="Close">
|
|
186
|
+
×
|
|
187
|
+
</button>
|
|
170
188
|
</div>
|
|
171
189
|
<div class="stat-detail-metrics">
|
|
172
190
|
<Metric label="min" value={def.format(stats.min)} />
|
|
@@ -202,7 +220,8 @@ interface SeriesStats {
|
|
|
202
220
|
|
|
203
221
|
function computeStats(series: DevtoolsSeries): SeriesStats {
|
|
204
222
|
const len = series.length
|
|
205
|
-
if (len === 0)
|
|
223
|
+
if (len === 0)
|
|
224
|
+
return { count: 0, min: undefined, max: undefined, mean: undefined, p50: undefined, p95: undefined, p99: undefined }
|
|
206
225
|
const size = series.data.length
|
|
207
226
|
const start = (series.write - len + size) % size
|
|
208
227
|
// Copy into a dense array for sorting — len is typically ~256 so
|
|
@@ -236,7 +255,7 @@ function drawHistogram(
|
|
|
236
255
|
w: number,
|
|
237
256
|
h: number,
|
|
238
257
|
series: DevtoolsSeries,
|
|
239
|
-
color: string
|
|
258
|
+
color: string
|
|
240
259
|
): void {
|
|
241
260
|
ctx.clearRect(0, 0, w, h)
|
|
242
261
|
const len = series.length
|
|
@@ -333,7 +352,7 @@ function drawSeries(
|
|
|
333
352
|
h: number,
|
|
334
353
|
series: DevtoolsSeries,
|
|
335
354
|
color: string,
|
|
336
|
-
rangeRef: { current: { min: number; max: number } | null }
|
|
355
|
+
rangeRef: { current: { min: number; max: number } | null }
|
|
337
356
|
): void {
|
|
338
357
|
ctx.clearRect(0, 0, w, h)
|
|
339
358
|
const len = series.length
|
|
@@ -349,12 +368,21 @@ function drawSeries(
|
|
|
349
368
|
// compares per sample, no allocation). Top1 is the max; topK is
|
|
350
369
|
// the K-th-largest = our trimmed observedMax. When fewer than K
|
|
351
370
|
// samples exist (window still warming up), fall back to top1.
|
|
352
|
-
let top1 = -Infinity,
|
|
371
|
+
let top1 = -Infinity,
|
|
372
|
+
top2 = -Infinity,
|
|
373
|
+
top3 = -Infinity
|
|
353
374
|
for (let i = 0; i < len; i++) {
|
|
354
375
|
const v = series.data[(start + i) % size]!
|
|
355
|
-
if (v > top1) {
|
|
356
|
-
|
|
357
|
-
|
|
376
|
+
if (v > top1) {
|
|
377
|
+
top3 = top2
|
|
378
|
+
top2 = top1
|
|
379
|
+
top1 = v
|
|
380
|
+
} else if (v > top2) {
|
|
381
|
+
top3 = top2
|
|
382
|
+
top2 = v
|
|
383
|
+
} else if (v > top3) {
|
|
384
|
+
top3 = v
|
|
385
|
+
}
|
|
358
386
|
}
|
|
359
387
|
const observedMax = len < AXIS_TRIM_K ? top1 : top3
|
|
360
388
|
if (!Number.isFinite(observedMax) || observedMax < 0) return
|
|
@@ -102,12 +102,18 @@ export class ProtocolStore {
|
|
|
102
102
|
|
|
103
103
|
addListener(cb: Listener): () => void {
|
|
104
104
|
this._listeners.add(cb)
|
|
105
|
-
return () => {
|
|
105
|
+
return () => {
|
|
106
|
+
this._listeners.delete(cb)
|
|
107
|
+
}
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
private _fire(): void {
|
|
109
111
|
for (const cb of this._listeners) {
|
|
110
|
-
try {
|
|
112
|
+
try {
|
|
113
|
+
cb()
|
|
114
|
+
} catch {
|
|
115
|
+
/* ignore */
|
|
116
|
+
}
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
|
|
@@ -149,7 +155,7 @@ export class ProtocolStore {
|
|
|
149
155
|
if (this._flushTimer === null) {
|
|
150
156
|
this._flushTimer = (globalThis.setTimeout as unknown as (cb: () => void, ms: number) => number)(
|
|
151
157
|
() => this._flush(),
|
|
152
|
-
WRITE_FLUSH_MS
|
|
158
|
+
WRITE_FLUSH_MS
|
|
153
159
|
)
|
|
154
160
|
}
|
|
155
161
|
this._evictIfNeeded(providerId)
|
|
@@ -170,7 +176,9 @@ export class ProtocolStore {
|
|
|
170
176
|
try {
|
|
171
177
|
const tx = this._db.transaction(STORE, 'readwrite')
|
|
172
178
|
tx.objectStore(STORE).clear()
|
|
173
|
-
} catch {
|
|
179
|
+
} catch {
|
|
180
|
+
/* db closing */
|
|
181
|
+
}
|
|
174
182
|
}
|
|
175
183
|
this._fire()
|
|
176
184
|
}
|
|
@@ -189,7 +197,10 @@ export class ProtocolStore {
|
|
|
189
197
|
// Quick hit check.
|
|
190
198
|
let allHit = true
|
|
191
199
|
for (let id = startId; id <= endId; id++) {
|
|
192
|
-
if (!this._cache.has(cacheKey(providerId, id))) {
|
|
200
|
+
if (!this._cache.has(cacheKey(providerId, id))) {
|
|
201
|
+
allHit = false
|
|
202
|
+
break
|
|
203
|
+
}
|
|
193
204
|
}
|
|
194
205
|
if (allHit) return
|
|
195
206
|
const promise = this._readRange(providerId, startId, endId)
|
|
@@ -214,7 +225,7 @@ export class ProtocolStore {
|
|
|
214
225
|
async queryFiltered(
|
|
215
226
|
providerId: string,
|
|
216
227
|
predicate: (entry: LogEntry) => boolean,
|
|
217
|
-
signal?: { aborted: boolean }
|
|
228
|
+
signal?: { aborted: boolean }
|
|
218
229
|
): Promise<number[]> {
|
|
219
230
|
const db = await this._dbReady
|
|
220
231
|
const out: number[] = []
|
|
@@ -224,9 +235,15 @@ export class ProtocolStore {
|
|
|
224
235
|
const index = store.index(INDEX)
|
|
225
236
|
const req = index.openCursor(IDBKeyRange.only(providerId))
|
|
226
237
|
req.onsuccess = () => {
|
|
227
|
-
if (signal?.aborted === true) {
|
|
238
|
+
if (signal?.aborted === true) {
|
|
239
|
+
resolve()
|
|
240
|
+
return
|
|
241
|
+
}
|
|
228
242
|
const cursor = req.result
|
|
229
|
-
if (cursor === null) {
|
|
243
|
+
if (cursor === null) {
|
|
244
|
+
resolve()
|
|
245
|
+
return
|
|
246
|
+
}
|
|
230
247
|
const entry = cursor.value as LogEntry
|
|
231
248
|
if (predicate(entry)) {
|
|
232
249
|
out.push(entry.id)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ErrorInfo, PreactContext, Ref as PreactRef } from './preact.module';
|
|
2
|
+
|
|
3
|
+
type Inputs = ReadonlyArray<unknown>;
|
|
4
|
+
|
|
5
|
+
export type Dispatch<A> = (value: A) => void;
|
|
6
|
+
export type StateUpdater<S> = S | ((prevState: S) => S);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns a stateful value, and a function to update it.
|
|
10
|
+
* @param initialState The initial value (or a function that returns the initial value)
|
|
11
|
+
*/
|
|
12
|
+
export function useState<S>(
|
|
13
|
+
initialState: S | (() => S)
|
|
14
|
+
): [S, Dispatch<StateUpdater<S>>];
|
|
15
|
+
|
|
16
|
+
export function useState<S = undefined>(): [
|
|
17
|
+
S | undefined,
|
|
18
|
+
Dispatch<StateUpdater<S | undefined>>
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export type Reducer<S, A> = (prevState: S, action: A) => S;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* An alternative to `useState`.
|
|
25
|
+
*
|
|
26
|
+
* `useReducer` is usually preferable to `useState` when you have complex state logic that involves
|
|
27
|
+
* multiple sub-values. It also lets you optimize performance for components that trigger deep
|
|
28
|
+
* updates because you can pass `dispatch` down instead of callbacks.
|
|
29
|
+
* @param reducer Given the current state and an action, returns the new state
|
|
30
|
+
* @param initialState The initial value to store as state
|
|
31
|
+
*/
|
|
32
|
+
export function useReducer<S, A>(
|
|
33
|
+
reducer: Reducer<S, A>,
|
|
34
|
+
initialState: S
|
|
35
|
+
): [S, Dispatch<A>];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* An alternative to `useState`.
|
|
39
|
+
*
|
|
40
|
+
* `useReducer` is usually preferable to `useState` when you have complex state logic that involves
|
|
41
|
+
* multiple sub-values. It also lets you optimize performance for components that trigger deep
|
|
42
|
+
* updates because you can pass `dispatch` down instead of callbacks.
|
|
43
|
+
* @param reducer Given the current state and an action, returns the new state
|
|
44
|
+
* @param initialArg The initial argument to pass to the `init` function
|
|
45
|
+
* @param init A function that, given the `initialArg`, returns the initial value to store as state
|
|
46
|
+
*/
|
|
47
|
+
export function useReducer<S, A, I>(
|
|
48
|
+
reducer: Reducer<S, A>,
|
|
49
|
+
initialArg: I,
|
|
50
|
+
init: (arg: I) => S
|
|
51
|
+
): [S, Dispatch<A>];
|
|
52
|
+
|
|
53
|
+
/** @deprecated Use the `Ref` type instead. */
|
|
54
|
+
type PropRef<T> = MutableRef<T>;
|
|
55
|
+
interface Ref<T> {
|
|
56
|
+
readonly current: T | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface MutableRef<T> {
|
|
60
|
+
current: T;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
|
|
65
|
+
* (`initialValue`). The returned object will persist for the full lifetime of the component.
|
|
66
|
+
*
|
|
67
|
+
* Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
|
|
68
|
+
* value around similar to how you’d use instance fields in classes.
|
|
69
|
+
*
|
|
70
|
+
* @param initialValue the initial value to store in the ref object
|
|
71
|
+
*/
|
|
72
|
+
export function useRef<T>(initialValue: T): MutableRef<T>;
|
|
73
|
+
export function useRef<T>(initialValue: T | null): Ref<T>;
|
|
74
|
+
export function useRef<T = undefined>(): MutableRef<T | undefined>;
|
|
75
|
+
|
|
76
|
+
type EffectCallback = () => void | (() => void);
|
|
77
|
+
/**
|
|
78
|
+
* Accepts a function that contains imperative, possibly effectful code.
|
|
79
|
+
* The effects run after browser paint, without blocking it.
|
|
80
|
+
*
|
|
81
|
+
* @param effect Imperative function that can return a cleanup function
|
|
82
|
+
* @param inputs If present, effect will only activate if the values in the list change (using ===).
|
|
83
|
+
*/
|
|
84
|
+
export function useEffect(effect: EffectCallback, inputs?: Inputs): void;
|
|
85
|
+
|
|
86
|
+
type CreateHandle = () => object;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param ref The ref that will be mutated
|
|
90
|
+
* @param create The function that will be executed to get the value that will be attached to
|
|
91
|
+
* ref.current
|
|
92
|
+
* @param inputs If present, effect will only activate if the values in the list change (using ===).
|
|
93
|
+
*/
|
|
94
|
+
export function useImperativeHandle<T, R extends T>(
|
|
95
|
+
ref: PreactRef<T>,
|
|
96
|
+
create: () => R,
|
|
97
|
+
inputs?: Inputs
|
|
98
|
+
): void;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Accepts a function that contains imperative, possibly effectful code.
|
|
102
|
+
* Use this to read layout from the DOM and synchronously re-render.
|
|
103
|
+
* Updates scheduled inside `useLayoutEffect` will be flushed synchronously, after all DOM mutations but before the browser has a chance to paint.
|
|
104
|
+
* Prefer the standard `useEffect` hook when possible to avoid blocking visual updates.
|
|
105
|
+
*
|
|
106
|
+
* @param effect Imperative function that can return a cleanup function
|
|
107
|
+
* @param inputs If present, effect will only activate if the values in the list change (using ===).
|
|
108
|
+
*/
|
|
109
|
+
export function useLayoutEffect(effect: EffectCallback, inputs?: Inputs): void;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns a memoized version of the callback that only changes if one of the `inputs`
|
|
113
|
+
* has changed (using ===).
|
|
114
|
+
*/
|
|
115
|
+
export function useCallback<T extends Function>(callback: T, inputs: Inputs): T;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Pass a factory function and an array of inputs.
|
|
119
|
+
* useMemo will only recompute the memoized value when one of the inputs has changed.
|
|
120
|
+
* This optimization helps to avoid expensive calculations on every render.
|
|
121
|
+
* If no array is provided, a new value will be computed whenever a new function instance is passed as the first argument.
|
|
122
|
+
*/
|
|
123
|
+
// for `inputs`, allow undefined, but don't make it optional as that is very likely a mistake
|
|
124
|
+
export function useMemo<T>(factory: () => T, inputs: Inputs | undefined): T;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Returns the current context value, as given by the nearest context provider for the given context.
|
|
128
|
+
* When the provider updates, this Hook will trigger a rerender with the latest context value.
|
|
129
|
+
*
|
|
130
|
+
* @param context The context you want to use
|
|
131
|
+
*/
|
|
132
|
+
export function useContext<T>(context: PreactContext<T>): T;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Customize the displayed value in the devtools panel.
|
|
136
|
+
*
|
|
137
|
+
* @param value Custom hook name or object that is passed to formatter
|
|
138
|
+
* @param formatter Formatter to modify value before sending it to the devtools
|
|
139
|
+
*/
|
|
140
|
+
export function useDebugValue<T>(value: T, formatter?: (value: T) => any): void;
|
|
141
|
+
|
|
142
|
+
export function useErrorBoundary(
|
|
143
|
+
callback?: (error: any, errorInfo: ErrorInfo) => Promise<void> | void
|
|
144
|
+
): [any, () => void];
|
|
145
|
+
|
|
146
|
+
export function useId(): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX-runtime types for the vendored Preact copy. Mirrors
|
|
3
|
+
* `preact/jsx-runtime/src/index.d.ts` verbatim (structure-wise) but imports
|
|
4
|
+
* from the sibling vendored file instead of the npm-published `preact`.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
Attributes,
|
|
8
|
+
ComponentChild,
|
|
9
|
+
ComponentChildren,
|
|
10
|
+
ComponentType,
|
|
11
|
+
VNode,
|
|
12
|
+
} from './preact.module'
|
|
13
|
+
import { JSXInternal } from './jsx'
|
|
14
|
+
|
|
15
|
+
export { Fragment } from './preact.module'
|
|
16
|
+
export { JSXInternal as JSX }
|
|
17
|
+
|
|
18
|
+
export function jsx(
|
|
19
|
+
type: string,
|
|
20
|
+
props: JSXInternal.HTMLAttributes &
|
|
21
|
+
JSXInternal.SVGAttributes &
|
|
22
|
+
Record<string, unknown> & { children?: ComponentChild },
|
|
23
|
+
key?: string,
|
|
24
|
+
): VNode<unknown>
|
|
25
|
+
export function jsx<P>(
|
|
26
|
+
type: ComponentType<P>,
|
|
27
|
+
props: Attributes & P & { children?: ComponentChild },
|
|
28
|
+
key?: string,
|
|
29
|
+
): VNode<unknown>
|
|
30
|
+
|
|
31
|
+
export function jsxs(
|
|
32
|
+
type: string,
|
|
33
|
+
props: JSXInternal.HTMLAttributes &
|
|
34
|
+
JSXInternal.SVGAttributes &
|
|
35
|
+
Record<string, unknown> & { children?: ComponentChild[] },
|
|
36
|
+
key?: string,
|
|
37
|
+
): VNode<unknown>
|
|
38
|
+
export function jsxs<P>(
|
|
39
|
+
type: ComponentType<P>,
|
|
40
|
+
props: Attributes & P & { children?: ComponentChild[] },
|
|
41
|
+
key?: string,
|
|
42
|
+
): VNode<unknown>
|
|
43
|
+
|
|
44
|
+
export function jsxDEV(
|
|
45
|
+
type: string,
|
|
46
|
+
props: JSXInternal.HTMLAttributes &
|
|
47
|
+
JSXInternal.SVGAttributes &
|
|
48
|
+
Record<string, unknown> & { children?: ComponentChildren },
|
|
49
|
+
key?: string,
|
|
50
|
+
): VNode<unknown>
|
|
51
|
+
export function jsxDEV<P>(
|
|
52
|
+
type: ComponentType<P>,
|
|
53
|
+
props: Attributes & P & { children?: ComponentChildren },
|
|
54
|
+
key?: string,
|
|
55
|
+
): VNode<unknown>
|