@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
package/dist/devtools-client.js
CHANGED
|
@@ -1,852 +1,880 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ACK_INTERVAL_MS,
|
|
3
|
-
DEBUG_PROTOCOL_VERSION,
|
|
4
|
-
DISCOVERY_CHANNEL,
|
|
5
|
-
SERVER_LIVENESS_MS,
|
|
6
|
-
providerChannelName
|
|
7
|
-
} from "three-flatland/debug-protocol";
|
|
8
1
|
import { tracePerf } from "./perf-trace.js";
|
|
2
|
+
import { ACK_INTERVAL_MS, DEBUG_PROTOCOL_VERSION, DISCOVERY_CHANNEL, SERVER_LIVENESS_MS, providerChannelName } from "three-flatland/debug-protocol";
|
|
3
|
+
//#region src/devtools-client.ts
|
|
4
|
+
/**
|
|
5
|
+
* Discovery retry backoff. Starts at `QUERY_RETRY_MIN_MS`, doubles each
|
|
6
|
+
* attempt, caps at `QUERY_RETRY_MAX_MS`, and gives up after `QUERY_MAX_RETRIES`
|
|
7
|
+
* total tries. After giving up, a provider that comes online later will
|
|
8
|
+
* still wire us up via its own startup `provider:announce`.
|
|
9
|
+
*/
|
|
9
10
|
const QUERY_RETRY_MIN_MS = 500;
|
|
10
11
|
const QUERY_RETRY_MAX_MS = 5e3;
|
|
11
12
|
const QUERY_MAX_RETRIES = 10;
|
|
13
|
+
/**
|
|
14
|
+
* Size of the client-side decoded series rings. Sized generously so
|
|
15
|
+
* downstream consumers (graphs, percentile detail panels) have plenty
|
|
16
|
+
* of history across both 4 Hz batch and 60 Hz sample cadences. 1024 ×
|
|
17
|
+
* Float32 × ~9 fields ≈ 36 KB total — negligible.
|
|
18
|
+
*/
|
|
12
19
|
const CLIENT_SERIES_SIZE = 1024;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
20
|
+
/** Shared zero-length placeholder used for metadata-only registry snapshots. */
|
|
21
|
+
const EMPTY_SAMPLE = /* @__PURE__ */ new Float32Array(0);
|
|
22
|
+
/**
|
|
23
|
+
* Bus consumer. One per viewing surface (tweakpane panel, pop-out
|
|
24
|
+
* window, remote dashboard). Owns:
|
|
25
|
+
*
|
|
26
|
+
* - A stable consumer id (UUID v4).
|
|
27
|
+
* - A `BroadcastChannel` subscription.
|
|
28
|
+
* - Accumulated `DevtoolsState` derived from `data` packet deltas.
|
|
29
|
+
* - Ack timer (1 s, starts on `subscribe:ack`).
|
|
30
|
+
* - Server-liveness monitor; re-subscribes if no server message
|
|
31
|
+
* within `SERVER_LIVENESS_MS`.
|
|
32
|
+
*
|
|
33
|
+
* Call `start()` to send the initial subscribe + begin timers.
|
|
34
|
+
* `dispose()` sends unsubscribe, clears timers, closes the channel.
|
|
35
|
+
*/
|
|
36
|
+
var DevtoolsClient = class {
|
|
37
|
+
id;
|
|
38
|
+
state;
|
|
39
|
+
/** Shared discovery bus — carries query/announce/gone only. */
|
|
40
|
+
_discoveryBus;
|
|
41
|
+
/** Per-provider data bus. Opened when we subscribe; closed on switch / gone. */
|
|
42
|
+
_dataBus = null;
|
|
43
|
+
/** Bound handler for the current `_dataBus`. Tracked so we can detach on close. */
|
|
44
|
+
_dataHandler = null;
|
|
45
|
+
_features;
|
|
46
|
+
/**
|
|
47
|
+
* Registry entry filter. `null` = all entries; `string[]` = only
|
|
48
|
+
* these. Starts empty-array so providers don't drain (sometimes
|
|
49
|
+
* expensive) registry payloads until the pane actually needs them.
|
|
50
|
+
*/
|
|
51
|
+
_registrySelection = [];
|
|
52
|
+
/**
|
|
53
|
+
* Buffer subscription. Map of entry-name → { mode, thumbSize? }.
|
|
54
|
+
* Empty map = metadata-only (consumer is connected to the `buffers`
|
|
55
|
+
* feature but isn't actually watching anything — provider skips all
|
|
56
|
+
* readbacks). `null` is not a distinct state; represented by `{}`.
|
|
57
|
+
*/
|
|
58
|
+
_buffersSelection = {};
|
|
59
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
60
|
+
_chunkListeners = /* @__PURE__ */ new Set();
|
|
61
|
+
_rawMessageListeners = /* @__PURE__ */ new Set();
|
|
62
|
+
_ackTimer = null;
|
|
63
|
+
_livenessTimer = null;
|
|
64
|
+
_queryRetryTimer = null;
|
|
65
|
+
_lastServerAt = 0;
|
|
66
|
+
_subscribed = false;
|
|
67
|
+
_disposed = false;
|
|
68
|
+
/** Known providers by id — updated live via `provider:announce` + `provider:gone`. */
|
|
69
|
+
_providers = /* @__PURE__ */ new Map();
|
|
70
|
+
constructor(options) {
|
|
71
|
+
this.id = generateUuid();
|
|
72
|
+
this._features = [...options.features];
|
|
73
|
+
if (options.onChange) this._listeners.add(options.onChange);
|
|
74
|
+
this._discoveryBus = new BroadcastChannel(options.discoveryChannelName ?? DISCOVERY_CHANNEL);
|
|
75
|
+
const mkSeries = () => ({
|
|
76
|
+
data: new Float32Array(CLIENT_SERIES_SIZE),
|
|
77
|
+
write: 0,
|
|
78
|
+
length: 0
|
|
79
|
+
});
|
|
80
|
+
this.state = {
|
|
81
|
+
series: {
|
|
82
|
+
fps: mkSeries(),
|
|
83
|
+
cpuMs: mkSeries(),
|
|
84
|
+
gpuMs: mkSeries(),
|
|
85
|
+
heapUsedMB: mkSeries(),
|
|
86
|
+
drawCalls: mkSeries(),
|
|
87
|
+
triangles: mkSeries(),
|
|
88
|
+
primitives: mkSeries(),
|
|
89
|
+
geometries: mkSeries(),
|
|
90
|
+
textures: mkSeries(),
|
|
91
|
+
frames: {
|
|
92
|
+
data: new Uint32Array(CLIENT_SERIES_SIZE),
|
|
93
|
+
write: 0,
|
|
94
|
+
length: 0
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
registry: /* @__PURE__ */ new Map(),
|
|
98
|
+
buffers: /* @__PURE__ */ new Map(),
|
|
99
|
+
batches: {
|
|
100
|
+
frame: 0,
|
|
101
|
+
passes: [],
|
|
102
|
+
batches: []
|
|
103
|
+
},
|
|
104
|
+
providers: [],
|
|
105
|
+
selectedProviderId: null,
|
|
106
|
+
serverAlive: false,
|
|
107
|
+
serverLagMs: 0
|
|
108
|
+
};
|
|
109
|
+
this._discoveryBus.addEventListener("message", (ev) => {
|
|
110
|
+
this._onDiscoveryMessage(ev.data);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Begin discovery + subscription. Sends a `provider:query`, waits
|
|
115
|
+
* `DISCOVERY_WINDOW_MS` for `provider:announce` responses, then picks
|
|
116
|
+
* the best provider (user > system) and subscribes to it.
|
|
117
|
+
*
|
|
118
|
+
* Idempotent — calling again while already started is a no-op.
|
|
119
|
+
*/
|
|
120
|
+
start() {
|
|
121
|
+
if (this._disposed || this._subscribed) return;
|
|
122
|
+
this._subscribed = true;
|
|
123
|
+
this._sendQuery();
|
|
124
|
+
let retries = 0;
|
|
125
|
+
let nextDelay = QUERY_RETRY_MIN_MS;
|
|
126
|
+
const tick = () => {
|
|
127
|
+
if (this._disposed || this.state.selectedProviderId !== null) {
|
|
128
|
+
this._queryRetryTimer = null;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (retries >= QUERY_MAX_RETRIES) {
|
|
132
|
+
this._queryRetryTimer = null;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
retries++;
|
|
136
|
+
this._sendQuery();
|
|
137
|
+
nextDelay = Math.min(nextDelay * 2, QUERY_RETRY_MAX_MS);
|
|
138
|
+
this._queryRetryTimer = setTimeout(tick, nextDelay);
|
|
139
|
+
};
|
|
140
|
+
this._queryRetryTimer = setTimeout(tick, nextDelay);
|
|
141
|
+
this._livenessTimer = setInterval(() => this._checkLiveness(), 1e3);
|
|
142
|
+
}
|
|
143
|
+
_sendQuery() {
|
|
144
|
+
this._postDiscovery({
|
|
145
|
+
type: "provider:query",
|
|
146
|
+
payload: { requesterId: this.id }
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Update the feature set. Re-posts `subscribe` on the current
|
|
151
|
+
* provider's data channel.
|
|
152
|
+
*/
|
|
153
|
+
setFeatures(features) {
|
|
154
|
+
if (sameSet(this._features, features)) return;
|
|
155
|
+
this._features = [...features];
|
|
156
|
+
this._resubscribe();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Update the registry entry filter. Pass `null` for "every entry",
|
|
160
|
+
* `[]` to stop all registry traffic, or a list of names to narrow.
|
|
161
|
+
* Re-posts `subscribe` if the filter actually changed.
|
|
162
|
+
*/
|
|
163
|
+
/**
|
|
164
|
+
* Narrow which registry entries the provider should ship samples
|
|
165
|
+
* for. `null` = all entries; `[]` = none; `[name, …]` = only these.
|
|
166
|
+
* Metadata still flows regardless — only sample bytes are gated.
|
|
167
|
+
*/
|
|
168
|
+
setRegistry(names) {
|
|
169
|
+
if (sameFilter(this._registrySelection, names)) return;
|
|
170
|
+
this._registrySelection = names === null ? null : [...names];
|
|
171
|
+
this._resubscribe();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Update the buffer subscription. Map of entry-name → (mode, thumbSize?).
|
|
175
|
+
* - Empty map / missing names → metadata-only (provider sends no pixels,
|
|
176
|
+
* runs no readbacks). This is the idle state when no preview or
|
|
177
|
+
* modal is visible.
|
|
178
|
+
* - `{ name: { mode: 'thumbnail', thumbSize: 256 } }` → small
|
|
179
|
+
* downsampled preview. Use when a thumbnail card is visible.
|
|
180
|
+
* - `{ name: { mode: 'stream' } }` → full-size VP9 video stream.
|
|
181
|
+
* Use when the fullscreen modal is open.
|
|
182
|
+
*
|
|
183
|
+
* Mode changes (incl. unsubscribing) take effect on the next
|
|
184
|
+
* provider flush; the producer will drop old readbacks immediately.
|
|
185
|
+
*/
|
|
186
|
+
setBuffers(subscription) {
|
|
187
|
+
if (sameBufferSubscription(this._buffersSelection, subscription)) return;
|
|
188
|
+
this._buffersSelection = { ...subscription };
|
|
189
|
+
this._resubscribe();
|
|
190
|
+
}
|
|
191
|
+
_resubscribe() {
|
|
192
|
+
if (this._subscribed && this._dataBus !== null) {
|
|
193
|
+
const hasBuffers = Object.keys(this._buffersSelection).length > 0;
|
|
194
|
+
this._postData({
|
|
195
|
+
type: "subscribe",
|
|
196
|
+
payload: {
|
|
197
|
+
id: this.id,
|
|
198
|
+
features: this._features,
|
|
199
|
+
registry: this._registrySelection ?? void 0,
|
|
200
|
+
buffers: hasBuffers ? this._buffersSelection : void 0
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Manually switch to a different provider. No-op if unknown or if
|
|
207
|
+
* already selected. Closes the old data channel and opens the new one.
|
|
208
|
+
*/
|
|
209
|
+
selectProvider(providerId) {
|
|
210
|
+
if (!this._providers.has(providerId)) return;
|
|
211
|
+
if (this.state.selectedProviderId === providerId) return;
|
|
212
|
+
this._leaveDataChannel();
|
|
213
|
+
this.state.selectedProviderId = providerId;
|
|
214
|
+
this._resetAccumulatedState();
|
|
215
|
+
this._joinDataChannel(providerId);
|
|
216
|
+
this._postData({
|
|
217
|
+
type: "subscribe",
|
|
218
|
+
payload: {
|
|
219
|
+
id: this.id,
|
|
220
|
+
features: this._features,
|
|
221
|
+
registry: this._registrySelection ?? void 0,
|
|
222
|
+
buffers: Object.keys(this._buffersSelection).length > 0 ? this._buffersSelection : void 0
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
this._fire();
|
|
226
|
+
}
|
|
227
|
+
dispose() {
|
|
228
|
+
if (this._disposed) return;
|
|
229
|
+
this._disposed = true;
|
|
230
|
+
this._leaveDataChannel();
|
|
231
|
+
this._subscribed = false;
|
|
232
|
+
if (this._ackTimer !== null) {
|
|
233
|
+
clearInterval(this._ackTimer);
|
|
234
|
+
this._ackTimer = null;
|
|
235
|
+
}
|
|
236
|
+
if (this._livenessTimer !== null) {
|
|
237
|
+
clearInterval(this._livenessTimer);
|
|
238
|
+
this._livenessTimer = null;
|
|
239
|
+
}
|
|
240
|
+
if (this._queryRetryTimer !== null) {
|
|
241
|
+
clearTimeout(this._queryRetryTimer);
|
|
242
|
+
this._queryRetryTimer = null;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
this._discoveryBus.close();
|
|
246
|
+
} catch {}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Open a fresh per-provider data channel and start listening on it.
|
|
250
|
+
* Caller is responsible for posting the initial `subscribe`.
|
|
251
|
+
*/
|
|
252
|
+
_joinDataChannel(providerId) {
|
|
253
|
+
this._dataBus = new BroadcastChannel(providerChannelName(providerId));
|
|
254
|
+
this._dataHandler = (ev) => {
|
|
255
|
+
this._onDataMessage(ev.data);
|
|
256
|
+
};
|
|
257
|
+
this._dataBus.addEventListener("message", this._dataHandler);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Send `unsubscribe` (if we have someone to tell) and close the data
|
|
261
|
+
* channel. Idempotent — safe to call when there's no active channel.
|
|
262
|
+
*/
|
|
263
|
+
_leaveDataChannel() {
|
|
264
|
+
if (this._dataBus !== null) {
|
|
265
|
+
if (this._subscribed) try {
|
|
266
|
+
this._postData({
|
|
267
|
+
type: "unsubscribe",
|
|
268
|
+
payload: { id: this.id }
|
|
269
|
+
});
|
|
270
|
+
} catch {}
|
|
271
|
+
if (this._dataHandler !== null) try {
|
|
272
|
+
this._dataBus.removeEventListener("message", this._dataHandler);
|
|
273
|
+
} catch {}
|
|
274
|
+
try {
|
|
275
|
+
this._dataBus.close();
|
|
276
|
+
} catch {}
|
|
277
|
+
this._dataBus = null;
|
|
278
|
+
this._dataHandler = null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Handler for the shared discovery bus. Only discovery messages land
|
|
283
|
+
* here by protocol; anything else is ignored.
|
|
284
|
+
*/
|
|
285
|
+
_onDiscoveryMessage(msg) {
|
|
286
|
+
if (!msg || typeof msg !== "object" || !("type" in msg)) return;
|
|
287
|
+
tracePerf(msg);
|
|
288
|
+
switch (msg.type) {
|
|
289
|
+
case "provider:announce": {
|
|
290
|
+
const id = msg.payload.identity.id;
|
|
291
|
+
this._providers.set(id, msg.payload.identity);
|
|
292
|
+
this.state.providers = Array.from(this._providers.values());
|
|
293
|
+
this._fire();
|
|
294
|
+
if (this.state.selectedProviderId === null) this._pickProviderAndSubscribe();
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "provider:gone": {
|
|
298
|
+
const id = msg.payload.id;
|
|
299
|
+
if (!this._providers.has(id)) return;
|
|
300
|
+
this._providers.delete(id);
|
|
301
|
+
this.state.providers = Array.from(this._providers.values());
|
|
302
|
+
if (this.state.selectedProviderId === id) {
|
|
303
|
+
this._leaveDataChannel();
|
|
304
|
+
this.state.selectedProviderId = null;
|
|
305
|
+
this._resetAccumulatedState();
|
|
306
|
+
this._pickProviderAndSubscribe();
|
|
307
|
+
}
|
|
308
|
+
this._fire();
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
default: break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Handler for the per-provider data channel. Everything here is
|
|
316
|
+
* already implicitly addressed to us (we opened the channel with the
|
|
317
|
+
* provider's id), so no per-message filtering is required beyond
|
|
318
|
+
* matching our consumer `id` on `subscribe:ack`.
|
|
319
|
+
*/
|
|
320
|
+
_onDataMessage(msg) {
|
|
321
|
+
if (!msg || typeof msg !== "object" || !("type" in msg)) return;
|
|
322
|
+
tracePerf(msg);
|
|
323
|
+
if (this._rawMessageListeners.size > 0) for (const cb of this._rawMessageListeners) try {
|
|
324
|
+
cb(msg, "in");
|
|
325
|
+
} catch {}
|
|
326
|
+
switch (msg.type) {
|
|
327
|
+
case "subscribe:ack":
|
|
328
|
+
if (msg.payload.id !== this.id) return;
|
|
329
|
+
this._markServerAlive();
|
|
330
|
+
this._applyEnv(msg.payload.env);
|
|
331
|
+
if (this._ackTimer === null) this._ackTimer = setInterval(() => this._sendAck(), ACK_INTERVAL_MS);
|
|
332
|
+
this._fire();
|
|
333
|
+
break;
|
|
334
|
+
case "data": {
|
|
335
|
+
this._markServerAlive();
|
|
336
|
+
this.state.frame = msg.payload.frame;
|
|
337
|
+
const features = msg.payload.features;
|
|
338
|
+
if (features.stats !== void 0) this._applyStats(features.stats, msg.payload.frame);
|
|
339
|
+
if (features.env !== void 0) this._applyEnv(features.env);
|
|
340
|
+
if (features.registry !== void 0) this._applyRegistry(features.registry);
|
|
341
|
+
if (features.buffers !== void 0) this._applyBuffers(features.buffers);
|
|
342
|
+
if (features.batches !== void 0) this._applyBatches(features.batches);
|
|
343
|
+
this._fire();
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case "buffer:chunk": {
|
|
347
|
+
this._markServerAlive();
|
|
348
|
+
const payload = msg.payload;
|
|
349
|
+
for (const cb of this._chunkListeners) try {
|
|
350
|
+
cb(payload);
|
|
351
|
+
} catch {}
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case "buffer:raw": {
|
|
355
|
+
this._markServerAlive();
|
|
356
|
+
const payload = msg.payload;
|
|
357
|
+
let snap = this.state.buffers.get(payload.name);
|
|
358
|
+
if (snap !== void 0) {
|
|
359
|
+
snap.pixels = new Uint8Array(payload.data);
|
|
360
|
+
snap.width = payload.width;
|
|
361
|
+
snap.height = payload.height;
|
|
362
|
+
snap.version++;
|
|
363
|
+
} else {
|
|
364
|
+
snap = {
|
|
365
|
+
name: payload.name,
|
|
366
|
+
width: payload.width,
|
|
367
|
+
height: payload.height,
|
|
368
|
+
srcWidth: payload.width,
|
|
369
|
+
srcHeight: payload.height,
|
|
370
|
+
pixelType: "rgba8",
|
|
371
|
+
display: "colors",
|
|
372
|
+
version: 0,
|
|
373
|
+
pixels: new Uint8Array(payload.data)
|
|
374
|
+
};
|
|
375
|
+
this.state.buffers.set(payload.name, snap);
|
|
376
|
+
}
|
|
377
|
+
this._fire();
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case "ping":
|
|
381
|
+
this._markServerAlive();
|
|
382
|
+
break;
|
|
383
|
+
default: break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
_markServerAlive() {
|
|
387
|
+
this._lastServerAt = Date.now();
|
|
388
|
+
if (!this.state.serverAlive) {
|
|
389
|
+
this.state.serverAlive = true;
|
|
390
|
+
this._fire();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Pick the best provider from the currently-known set and subscribe
|
|
395
|
+
* to it. Preference: `user` over `system`; first-announced as
|
|
396
|
+
* tiebreak. No-op if zero providers are known (we'll stay waiting;
|
|
397
|
+
* any future `provider:announce` that arrives doesn't auto-trigger
|
|
398
|
+
* this — call it again or restart discovery).
|
|
399
|
+
*/
|
|
400
|
+
_pickProviderAndSubscribe() {
|
|
401
|
+
if (this.state.selectedProviderId !== null) return;
|
|
402
|
+
const providers = Array.from(this._providers.values());
|
|
403
|
+
if (providers.length === 0) return;
|
|
404
|
+
const chosen = providers.find((p) => p.kind === "user") ?? providers[0];
|
|
405
|
+
this.state.selectedProviderId = chosen.id;
|
|
406
|
+
this._joinDataChannel(chosen.id);
|
|
407
|
+
this._postData({
|
|
408
|
+
type: "subscribe",
|
|
409
|
+
payload: {
|
|
410
|
+
id: this.id,
|
|
411
|
+
features: this._features,
|
|
412
|
+
registry: this._registrySelection ?? void 0,
|
|
413
|
+
buffers: Object.keys(this._buffersSelection).length > 0 ? this._buffersSelection : void 0
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
this._fire();
|
|
417
|
+
}
|
|
418
|
+
/** Clear all feature-derived state so the next data packet can start fresh. */
|
|
419
|
+
_resetAccumulatedState() {
|
|
420
|
+
this.state.frame = void 0;
|
|
421
|
+
this._applyStats(null);
|
|
422
|
+
this._applyEnv(null);
|
|
423
|
+
this.state.registry.clear();
|
|
424
|
+
this.state.buffers.clear();
|
|
425
|
+
this._applyBatches(null);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Apply a stats batch. Null = reset (feature cleared / provider gone).
|
|
429
|
+
* Otherwise: decode each typed array onto the matching Float32 ring
|
|
430
|
+
* and update the scalar with the batch mean (natural smoothing for
|
|
431
|
+
* the text label).
|
|
432
|
+
*/
|
|
433
|
+
_applyStats(batch, batchFrame = 0) {
|
|
434
|
+
if (batch === null) {
|
|
435
|
+
this.state.drawCalls = void 0;
|
|
436
|
+
this.state.triangles = void 0;
|
|
437
|
+
this.state.primitives = void 0;
|
|
438
|
+
this.state.geometries = void 0;
|
|
439
|
+
this.state.textures = void 0;
|
|
440
|
+
this.state.cpuMs = void 0;
|
|
441
|
+
this.state.fps = void 0;
|
|
442
|
+
this.state.gpuMs = void 0;
|
|
443
|
+
this.state.heapUsedMB = void 0;
|
|
444
|
+
this.state.heapLimitMB = void 0;
|
|
445
|
+
const s = this.state.series;
|
|
446
|
+
s.fps.write = 0;
|
|
447
|
+
s.fps.length = 0;
|
|
448
|
+
s.cpuMs.write = 0;
|
|
449
|
+
s.cpuMs.length = 0;
|
|
450
|
+
s.gpuMs.write = 0;
|
|
451
|
+
s.gpuMs.length = 0;
|
|
452
|
+
s.heapUsedMB.write = 0;
|
|
453
|
+
s.heapUsedMB.length = 0;
|
|
454
|
+
s.drawCalls.write = 0;
|
|
455
|
+
s.drawCalls.length = 0;
|
|
456
|
+
s.triangles.write = 0;
|
|
457
|
+
s.triangles.length = 0;
|
|
458
|
+
s.primitives.write = 0;
|
|
459
|
+
s.primitives.length = 0;
|
|
460
|
+
s.geometries.write = 0;
|
|
461
|
+
s.geometries.length = 0;
|
|
462
|
+
s.textures.write = 0;
|
|
463
|
+
s.textures.length = 0;
|
|
464
|
+
s.frames.write = 0;
|
|
465
|
+
s.frames.length = 0;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const count = batch.count;
|
|
469
|
+
if (count <= 0) return;
|
|
470
|
+
const s = this.state.series;
|
|
471
|
+
{
|
|
472
|
+
const ring = s.frames;
|
|
473
|
+
const size = ring.data.length;
|
|
474
|
+
let w = ring.write;
|
|
475
|
+
for (let i = 0; i < count; i++) {
|
|
476
|
+
ring.data[w] = Math.max(0, batchFrame - count + 1 + i);
|
|
477
|
+
w = (w + 1) % size;
|
|
478
|
+
}
|
|
479
|
+
ring.write = w;
|
|
480
|
+
ring.length = Math.min(size, ring.length + count);
|
|
481
|
+
}
|
|
482
|
+
if (batch.fps) this.state.fps = this._ingestI16(s.fps, batch.fps, count, .1);
|
|
483
|
+
if (batch.cpuMs) this.state.cpuMs = this._ingestU16(s.cpuMs, batch.cpuMs, count, .01);
|
|
484
|
+
if (batch.gpuMs) this.state.gpuMs = this._ingestU16(s.gpuMs, batch.gpuMs, count, .01);
|
|
485
|
+
if (batch.heapUsedMB) this.state.heapUsedMB = this._ingestU16(s.heapUsedMB, batch.heapUsedMB, count, 1);
|
|
486
|
+
if (batch.drawCalls) this.state.drawCalls = this._ingestU32(s.drawCalls, batch.drawCalls, count);
|
|
487
|
+
if (batch.triangles) this.state.triangles = this._ingestU32(s.triangles, batch.triangles, count);
|
|
488
|
+
if (batch.primitives) this.state.primitives = this._ingestU32(s.primitives, batch.primitives, count);
|
|
489
|
+
if (batch.geometries) this.state.geometries = this._ingestU32(s.geometries, batch.geometries, count);
|
|
490
|
+
if (batch.textures) this.state.textures = this._ingestU32(s.textures, batch.textures, count);
|
|
491
|
+
if (batch.heapLimitMB !== void 0) this.state.heapLimitMB = batch.heapLimitMB;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Append `count` samples from a scaled integer view to a Float32 ring;
|
|
495
|
+
* return the batch mean (scaled). Zero-alloc past the ring itself.
|
|
496
|
+
*/
|
|
497
|
+
_ingestI16(ring, src, count, scale) {
|
|
498
|
+
const size = ring.data.length;
|
|
499
|
+
let sum = 0;
|
|
500
|
+
let w = ring.write;
|
|
501
|
+
for (let i = 0; i < count; i++) {
|
|
502
|
+
const v = src[i] * scale;
|
|
503
|
+
ring.data[w] = v;
|
|
504
|
+
sum += v;
|
|
505
|
+
w = (w + 1) % size;
|
|
506
|
+
}
|
|
507
|
+
ring.write = w;
|
|
508
|
+
ring.length = Math.min(size, ring.length + count);
|
|
509
|
+
return sum / count;
|
|
510
|
+
}
|
|
511
|
+
_ingestU16(ring, src, count, scale) {
|
|
512
|
+
const size = ring.data.length;
|
|
513
|
+
let sum = 0;
|
|
514
|
+
let w = ring.write;
|
|
515
|
+
for (let i = 0; i < count; i++) {
|
|
516
|
+
const v = src[i] * scale;
|
|
517
|
+
ring.data[w] = v;
|
|
518
|
+
sum += v;
|
|
519
|
+
w = (w + 1) % size;
|
|
520
|
+
}
|
|
521
|
+
ring.write = w;
|
|
522
|
+
ring.length = Math.min(size, ring.length + count);
|
|
523
|
+
return sum / count;
|
|
524
|
+
}
|
|
525
|
+
_ingestU32(ring, src, count) {
|
|
526
|
+
const size = ring.data.length;
|
|
527
|
+
let sum = 0;
|
|
528
|
+
let w = ring.write;
|
|
529
|
+
for (let i = 0; i < count; i++) {
|
|
530
|
+
const v = src[i];
|
|
531
|
+
ring.data[w] = v;
|
|
532
|
+
sum += v;
|
|
533
|
+
w = (w + 1) % size;
|
|
534
|
+
}
|
|
535
|
+
ring.write = w;
|
|
536
|
+
ring.length = Math.min(size, ring.length + count);
|
|
537
|
+
return sum / count;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Apply a batches feature payload — replaces the snapshot wholesale.
|
|
541
|
+
* The wire arrays are scratch pools on the producer; we truncate by
|
|
542
|
+
* the provided counts and copy fields into persistent snapshot
|
|
543
|
+
* objects the UI can hold across subsequent frames.
|
|
544
|
+
*
|
|
545
|
+
* The client-side snapshot arrays grow monotonically to the high
|
|
546
|
+
* watermark and entries are overwritten in place — same alloc
|
|
547
|
+
* discipline as the producer.
|
|
548
|
+
*/
|
|
549
|
+
_applyBatches(delta) {
|
|
550
|
+
if (delta === null) {
|
|
551
|
+
this.state.batches.frame = 0;
|
|
552
|
+
this.state.batches.passes.length = 0;
|
|
553
|
+
this.state.batches.batches.length = 0;
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
this.state.batches.frame = delta.frame;
|
|
557
|
+
const passesIn = delta.passes;
|
|
558
|
+
const passCount = delta.passCount | 0;
|
|
559
|
+
const passesOut = this.state.batches.passes;
|
|
560
|
+
if (passesIn !== void 0) for (let i = 0; i < passCount; i++) {
|
|
561
|
+
const src = passesIn[i];
|
|
562
|
+
if (src === void 0) continue;
|
|
563
|
+
let dst = passesOut[i];
|
|
564
|
+
if (dst === void 0) {
|
|
565
|
+
dst = {
|
|
566
|
+
label: "",
|
|
567
|
+
calls: 0,
|
|
568
|
+
triangles: 0,
|
|
569
|
+
cpuMs: 0,
|
|
570
|
+
depth: 0,
|
|
571
|
+
parent: -1
|
|
572
|
+
};
|
|
573
|
+
passesOut[i] = dst;
|
|
574
|
+
}
|
|
575
|
+
dst.label = src.label;
|
|
576
|
+
dst.calls = src.calls;
|
|
577
|
+
dst.triangles = src.triangles;
|
|
578
|
+
dst.cpuMs = src.cpuMs;
|
|
579
|
+
dst.depth = src.depth;
|
|
580
|
+
dst.parent = src.parent;
|
|
581
|
+
}
|
|
582
|
+
if (passesOut.length > passCount) passesOut.length = passCount;
|
|
583
|
+
const batchesIn = delta.batches;
|
|
584
|
+
const batchCount = delta.batchCount | 0;
|
|
585
|
+
const batchesOut = this.state.batches.batches;
|
|
586
|
+
if (batchesIn !== void 0) for (let i = 0; i < batchCount; i++) {
|
|
587
|
+
const src = batchesIn[i];
|
|
588
|
+
if (src === void 0) continue;
|
|
589
|
+
let dst = batchesOut[i];
|
|
590
|
+
if (dst === void 0) {
|
|
591
|
+
dst = {
|
|
592
|
+
runKey: 0,
|
|
593
|
+
materialId: 0,
|
|
594
|
+
layer: 0,
|
|
595
|
+
materialName: "",
|
|
596
|
+
spriteCount: 0,
|
|
597
|
+
batchIdx: 0,
|
|
598
|
+
kind: "sprite",
|
|
599
|
+
label: ""
|
|
600
|
+
};
|
|
601
|
+
batchesOut[i] = dst;
|
|
602
|
+
}
|
|
603
|
+
dst.runKey = src.runKey;
|
|
604
|
+
dst.materialId = src.materialId;
|
|
605
|
+
dst.layer = src.layer;
|
|
606
|
+
dst.materialName = src.materialName;
|
|
607
|
+
dst.spriteCount = src.spriteCount;
|
|
608
|
+
dst.batchIdx = src.batchIdx;
|
|
609
|
+
dst.kind = src.kind ?? "sprite";
|
|
610
|
+
dst.label = src.label ?? "";
|
|
611
|
+
}
|
|
612
|
+
if (batchesOut.length > batchCount) batchesOut.length = batchCount;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Apply a registry delta. `entries[name] === null` → remove;
|
|
616
|
+
* `entries[name]` present → upsert a snapshot keyed by `name`.
|
|
617
|
+
* A `null` batch (feature cleared) drops every entry.
|
|
618
|
+
*/
|
|
619
|
+
_applyRegistry(delta) {
|
|
620
|
+
if (delta === null) {
|
|
621
|
+
this.state.registry.clear();
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (!delta.entries) return;
|
|
625
|
+
const entries = delta.entries;
|
|
626
|
+
for (const name in entries) {
|
|
627
|
+
const d = entries[name];
|
|
628
|
+
if (d === void 0) continue;
|
|
629
|
+
if (d === null) {
|
|
630
|
+
this.state.registry.delete(name);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
let snap = this.state.registry.get(name);
|
|
634
|
+
const sample = d.sample ?? snap?.sample ?? EMPTY_SAMPLE;
|
|
635
|
+
if (snap === void 0) {
|
|
636
|
+
snap = {
|
|
637
|
+
name,
|
|
638
|
+
kind: d.kind,
|
|
639
|
+
version: d.version,
|
|
640
|
+
count: d.count,
|
|
641
|
+
sample,
|
|
642
|
+
label: d.label
|
|
643
|
+
};
|
|
644
|
+
this.state.registry.set(name, snap);
|
|
645
|
+
} else {
|
|
646
|
+
snap.kind = d.kind;
|
|
647
|
+
snap.version = d.version;
|
|
648
|
+
snap.count = d.count;
|
|
649
|
+
snap.sample = sample;
|
|
650
|
+
snap.label = d.label;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Apply a buffers delta. Metadata (width/height/version/pixelType)
|
|
656
|
+
* ships regardless of the client's selection; `pixels` only arrives
|
|
657
|
+
* when this entry is in the current selection. Retain the previous
|
|
658
|
+
* `pixels` when the delta is metadata-only so the UI keeps showing
|
|
659
|
+
* the last thumbnail while browsing.
|
|
660
|
+
*/
|
|
661
|
+
_applyBuffers(delta) {
|
|
662
|
+
if (delta === null) {
|
|
663
|
+
this.state.buffers.clear();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (!delta.entries) return;
|
|
667
|
+
const entries = delta.entries;
|
|
668
|
+
for (const name in entries) {
|
|
669
|
+
const d = entries[name];
|
|
670
|
+
if (d === void 0) continue;
|
|
671
|
+
if (d === null) {
|
|
672
|
+
this.state.buffers.delete(name);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
let snap = this.state.buffers.get(name);
|
|
676
|
+
const pixels = d.pixels ?? snap?.pixels ?? null;
|
|
677
|
+
const isFloat = d.pixelType === "rgba16f" || d.pixelType === "rgba32f";
|
|
678
|
+
const display = d.display ?? (isFloat ? "normalize" : "colors");
|
|
679
|
+
const srcW = d.srcWidth ?? d.width;
|
|
680
|
+
const srcH = d.srcHeight ?? d.height;
|
|
681
|
+
if (snap === void 0) {
|
|
682
|
+
snap = {
|
|
683
|
+
name,
|
|
684
|
+
width: d.width,
|
|
685
|
+
height: d.height,
|
|
686
|
+
srcWidth: srcW,
|
|
687
|
+
srcHeight: srcH,
|
|
688
|
+
pixelType: d.pixelType,
|
|
689
|
+
display,
|
|
690
|
+
version: d.version,
|
|
691
|
+
pixels,
|
|
692
|
+
label: d.label
|
|
693
|
+
};
|
|
694
|
+
this.state.buffers.set(name, snap);
|
|
695
|
+
} else {
|
|
696
|
+
snap.width = d.width;
|
|
697
|
+
snap.height = d.height;
|
|
698
|
+
snap.srcWidth = srcW;
|
|
699
|
+
snap.srcHeight = srcH;
|
|
700
|
+
snap.pixelType = d.pixelType;
|
|
701
|
+
snap.display = display;
|
|
702
|
+
snap.version = d.version;
|
|
703
|
+
snap.pixels = pixels;
|
|
704
|
+
snap.label = d.label;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
_applyEnv(delta) {
|
|
709
|
+
if (delta === null) {
|
|
710
|
+
this.state.threeFlatlandVersion = void 0;
|
|
711
|
+
this.state.threeRevision = void 0;
|
|
712
|
+
this.state.backendName = void 0;
|
|
713
|
+
this.state.backendTrackTimestamp = void 0;
|
|
714
|
+
this.state.backendDisjoint = void 0;
|
|
715
|
+
this.state.gpuModeEnabled = void 0;
|
|
716
|
+
this.state.canvasWidth = void 0;
|
|
717
|
+
this.state.canvasHeight = void 0;
|
|
718
|
+
this.state.canvasPixelRatio = void 0;
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if ("threeFlatlandVersion" in delta) this.state.threeFlatlandVersion = delta.threeFlatlandVersion ?? void 0;
|
|
722
|
+
if ("threeRevision" in delta) this.state.threeRevision = delta.threeRevision ?? void 0;
|
|
723
|
+
if (delta.backend !== void 0) {
|
|
724
|
+
const b = delta.backend;
|
|
725
|
+
if (b === null) {
|
|
726
|
+
this.state.backendName = void 0;
|
|
727
|
+
this.state.backendTrackTimestamp = void 0;
|
|
728
|
+
this.state.backendDisjoint = void 0;
|
|
729
|
+
this.state.gpuModeEnabled = void 0;
|
|
730
|
+
} else {
|
|
731
|
+
if ("name" in b) this.state.backendName = b.name ?? void 0;
|
|
732
|
+
if ("trackTimestamp" in b) this.state.backendTrackTimestamp = b.trackTimestamp ?? void 0;
|
|
733
|
+
if ("disjoint" in b) this.state.backendDisjoint = b.disjoint ?? void 0;
|
|
734
|
+
if ("gpuModeEnabled" in b) this.state.gpuModeEnabled = b.gpuModeEnabled ?? void 0;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (delta.canvas !== void 0) {
|
|
738
|
+
const c = delta.canvas;
|
|
739
|
+
if (c === null) {
|
|
740
|
+
this.state.canvasWidth = void 0;
|
|
741
|
+
this.state.canvasHeight = void 0;
|
|
742
|
+
this.state.canvasPixelRatio = void 0;
|
|
743
|
+
} else {
|
|
744
|
+
if ("width" in c) this.state.canvasWidth = c.width ?? void 0;
|
|
745
|
+
if ("height" in c) this.state.canvasHeight = c.height ?? void 0;
|
|
746
|
+
if ("pixelRatio" in c) this.state.canvasPixelRatio = c.pixelRatio ?? void 0;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
_checkLiveness() {
|
|
751
|
+
const now = Date.now();
|
|
752
|
+
const lag = this._lastServerAt === 0 ? now : now - this._lastServerAt;
|
|
753
|
+
this.state.serverLagMs = lag;
|
|
754
|
+
if (this._lastServerAt !== 0 && lag > SERVER_LIVENESS_MS) {
|
|
755
|
+
if (this.state.serverAlive) this.state.serverAlive = false;
|
|
756
|
+
if (this._dataBus !== null) this._postData({
|
|
757
|
+
type: "subscribe",
|
|
758
|
+
payload: {
|
|
759
|
+
id: this.id,
|
|
760
|
+
features: this._features,
|
|
761
|
+
registry: this._registrySelection ?? void 0,
|
|
762
|
+
buffers: Object.keys(this._buffersSelection).length > 0 ? this._buffersSelection : void 0
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
this._fire();
|
|
767
|
+
}
|
|
768
|
+
_sendAck() {
|
|
769
|
+
if (this._disposed || this._dataBus === null) return;
|
|
770
|
+
this._postData({
|
|
771
|
+
type: "ack",
|
|
772
|
+
payload: { id: this.id }
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
_postDiscovery(body) {
|
|
776
|
+
const msg = {
|
|
777
|
+
v: DEBUG_PROTOCOL_VERSION,
|
|
778
|
+
ts: Date.now(),
|
|
779
|
+
...body
|
|
780
|
+
};
|
|
781
|
+
this._fireRawMessage(msg, "out");
|
|
782
|
+
try {
|
|
783
|
+
this._discoveryBus.postMessage(msg);
|
|
784
|
+
} catch {}
|
|
785
|
+
}
|
|
786
|
+
_postData(body) {
|
|
787
|
+
if (this._dataBus === null) return;
|
|
788
|
+
const msg = {
|
|
789
|
+
v: DEBUG_PROTOCOL_VERSION,
|
|
790
|
+
ts: Date.now(),
|
|
791
|
+
...body
|
|
792
|
+
};
|
|
793
|
+
this._fireRawMessage(msg, "out");
|
|
794
|
+
try {
|
|
795
|
+
this._dataBus.postMessage(msg);
|
|
796
|
+
} catch {}
|
|
797
|
+
}
|
|
798
|
+
_fireRawMessage(msg, direction) {
|
|
799
|
+
if (this._rawMessageListeners.size === 0) return;
|
|
800
|
+
for (const cb of this._rawMessageListeners) try {
|
|
801
|
+
cb(msg, direction);
|
|
802
|
+
} catch {}
|
|
803
|
+
}
|
|
804
|
+
/** Subscribe to state-change events. Returns an unsubscribe function. */
|
|
805
|
+
addListener(cb) {
|
|
806
|
+
this._listeners.add(cb);
|
|
807
|
+
return () => {
|
|
808
|
+
this._listeners.delete(cb);
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
/** Explicit remove — equivalent to calling the unsubscribe returned from `addListener`. */
|
|
812
|
+
removeListener(cb) {
|
|
813
|
+
this._listeners.delete(cb);
|
|
814
|
+
}
|
|
815
|
+
/** Subscribe to incoming WebCodecs buffer chunks. Returns an unsubscribe function. */
|
|
816
|
+
addChunkListener(cb) {
|
|
817
|
+
this._chunkListeners.add(cb);
|
|
818
|
+
return () => {
|
|
819
|
+
this._chunkListeners.delete(cb);
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Subscribe to every bus message — both inbound (from providers) and
|
|
824
|
+
* outbound (our own `subscribe`/`ack`/etc.). Used by the dashboard's
|
|
825
|
+
* protocol-log panel. Listeners fire on the hot path so keep them
|
|
826
|
+
* cheap — filter and format lazily.
|
|
827
|
+
*/
|
|
828
|
+
addRawMessageListener(cb) {
|
|
829
|
+
this._rawMessageListeners.add(cb);
|
|
830
|
+
return () => {
|
|
831
|
+
this._rawMessageListeners.delete(cb);
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
_fire() {
|
|
835
|
+
for (const cb of this._listeners) try {
|
|
836
|
+
cb(this.state);
|
|
837
|
+
} catch {}
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
/** Cheap equality for two small feature lists treated as sets. */
|
|
815
841
|
function sameSet(a, b) {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
842
|
+
if (a.length !== b.length) return false;
|
|
843
|
+
for (const v of a) if (!b.includes(v)) return false;
|
|
844
|
+
return true;
|
|
819
845
|
}
|
|
846
|
+
/** Equality for registry filters (where `null` has a distinct meaning). */
|
|
820
847
|
function sameFilter(a, b) {
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
848
|
+
if (a === b) return true;
|
|
849
|
+
if (a === null || b === null) return false;
|
|
850
|
+
return sameSet(a, b);
|
|
824
851
|
}
|
|
852
|
+
/** Equality for buffer subscription records. Compares keys and nested fields. */
|
|
825
853
|
function sameBufferSubscription(a, b) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
854
|
+
const ak = Object.keys(a);
|
|
855
|
+
const bk = Object.keys(b);
|
|
856
|
+
if (ak.length !== bk.length) return false;
|
|
857
|
+
for (const k of ak) {
|
|
858
|
+
const av = a[k];
|
|
859
|
+
const bv = b[k];
|
|
860
|
+
if (av === void 0 || bv === void 0) return false;
|
|
861
|
+
if (av.mode !== bv.mode) return false;
|
|
862
|
+
if ((av.thumbSize ?? 256) !== (bv.thumbSize ?? 256)) return false;
|
|
863
|
+
}
|
|
864
|
+
return true;
|
|
837
865
|
}
|
|
838
866
|
function generateUuid() {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
867
|
+
const c = typeof crypto !== "undefined" ? crypto : void 0;
|
|
868
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
869
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
870
|
+
if (c && typeof c.getRandomValues === "function") c.getRandomValues(bytes);
|
|
871
|
+
else for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
872
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
873
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
874
|
+
const h = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
|
|
875
|
+
return `${h.slice(0, 4).join("")}-${h.slice(4, 6).join("")}-${h.slice(6, 8).join("")}-${h.slice(8, 10).join("")}-${h.slice(10).join("")}`;
|
|
848
876
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
877
|
+
//#endregion
|
|
878
|
+
export { DevtoolsClient };
|
|
879
|
+
|
|
852
880
|
//# sourceMappingURL=devtools-client.js.map
|