@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.
Files changed (172) hide show
  1. package/README.md +1 -1
  2. package/dist/buffers-modal.d.ts +16 -52
  3. package/dist/buffers-modal.d.ts.map +1 -0
  4. package/dist/buffers-modal.js +459 -464
  5. package/dist/buffers-modal.js.map +1 -1
  6. package/dist/buffers-view.d.ts +22 -34
  7. package/dist/buffers-view.d.ts.map +1 -0
  8. package/dist/buffers-view.js +252 -240
  9. package/dist/buffers-view.js.map +1 -1
  10. package/dist/create-pane.d.ts +38 -37
  11. package/dist/create-pane.d.ts.map +1 -0
  12. package/dist/create-pane.js +197 -166
  13. package/dist/create-pane.js.map +1 -1
  14. package/dist/dashboard/app.tsx +2 -0
  15. package/dist/dashboard/export.ts +1 -3
  16. package/dist/dashboard/frame-cursor.ts +81 -0
  17. package/dist/dashboard/hooks.ts +10 -2
  18. package/dist/dashboard/index.html +61 -0
  19. package/dist/dashboard/panels/batches.tsx +5 -18
  20. package/dist/dashboard/panels/buffers.tsx +52 -26
  21. package/dist/dashboard/panels/env.tsx +5 -6
  22. package/dist/dashboard/panels/protocol-log.tsx +146 -58
  23. package/dist/dashboard/panels/registry.tsx +55 -69
  24. package/dist/dashboard/panels/scrubber.tsx +100 -0
  25. package/dist/dashboard/panels/stats.tsx +60 -32
  26. package/dist/dashboard/protocol-store.ts +25 -8
  27. package/dist/dashboard/vendor/hooks.module.d.ts +146 -0
  28. package/dist/dashboard/vendor/jsx-runtime.d.ts +55 -0
  29. package/dist/dashboard/vendor/jsx.d.ts +3123 -0
  30. package/dist/dashboard/vendor/preact.module.d.ts +397 -0
  31. package/dist/devtools-client.d.ts +312 -300
  32. package/dist/devtools-client.d.ts.map +1 -0
  33. package/dist/devtools-client.js +866 -838
  34. package/dist/devtools-client.js.map +1 -1
  35. package/dist/index.d.ts +6 -8
  36. package/dist/index.js +5 -12
  37. package/dist/perf-trace.d.ts +6 -4
  38. package/dist/perf-trace.d.ts.map +1 -0
  39. package/dist/perf-trace.js +80 -66
  40. package/dist/perf-trace.js.map +1 -1
  41. package/dist/plugins.d.ts +6 -6
  42. package/dist/plugins.d.ts.map +1 -0
  43. package/dist/plugins.js +5 -5
  44. package/dist/plugins.js.map +1 -1
  45. package/dist/provider-switcher.d.ts +8 -15
  46. package/dist/provider-switcher.d.ts.map +1 -0
  47. package/dist/provider-switcher.js +68 -68
  48. package/dist/provider-switcher.js.map +1 -1
  49. package/dist/react/devtools-provider.d.ts +11 -10
  50. package/dist/react/devtools-provider.d.ts.map +1 -0
  51. package/dist/react/devtools-provider.js +60 -32
  52. package/dist/react/devtools-provider.js.map +1 -1
  53. package/dist/react/use-fps-graph.d.ts +7 -7
  54. package/dist/react/use-fps-graph.d.ts.map +1 -0
  55. package/dist/react/use-fps-graph.js +27 -21
  56. package/dist/react/use-fps-graph.js.map +1 -1
  57. package/dist/react/use-pane-button.d.ts +4 -4
  58. package/dist/react/use-pane-button.d.ts.map +1 -0
  59. package/dist/react/use-pane-button.js +35 -30
  60. package/dist/react/use-pane-button.js.map +1 -1
  61. package/dist/react/use-pane-folder.d.ts +5 -4
  62. package/dist/react/use-pane-folder.d.ts.map +1 -0
  63. package/dist/react/use-pane-folder.js +33 -20
  64. package/dist/react/use-pane-folder.js.map +1 -1
  65. package/dist/react/use-pane-input.d.ts +31 -30
  66. package/dist/react/use-pane-input.d.ts.map +1 -0
  67. package/dist/react/use-pane-input.js +46 -42
  68. package/dist/react/use-pane-input.js.map +1 -1
  69. package/dist/react/use-pane-radio-grid.d.ts +20 -20
  70. package/dist/react/use-pane-radio-grid.d.ts.map +1 -0
  71. package/dist/react/use-pane-radio-grid.js +72 -50
  72. package/dist/react/use-pane-radio-grid.js.map +1 -1
  73. package/dist/react/use-pane.d.ts +4 -4
  74. package/dist/react/use-pane.d.ts.map +1 -0
  75. package/dist/react/use-pane.js +52 -17
  76. package/dist/react/use-pane.js.map +1 -1
  77. package/dist/react.d.ts +10 -12
  78. package/dist/react.js +2 -12
  79. package/dist/registry-view.d.ts +8 -21
  80. package/dist/registry-view.d.ts.map +1 -0
  81. package/dist/registry-view.js +357 -338
  82. package/dist/registry-view.js.map +1 -1
  83. package/dist/relay.d.ts +16 -0
  84. package/dist/relay.d.ts.map +1 -0
  85. package/dist/relay.js +189 -0
  86. package/dist/relay.js.map +1 -0
  87. package/dist/remote.d.ts +10 -0
  88. package/dist/remote.d.ts.map +1 -0
  89. package/dist/remote.js +28 -0
  90. package/dist/remote.js.map +1 -0
  91. package/dist/stats-graph.d.ts +23 -31
  92. package/dist/stats-graph.d.ts.map +1 -0
  93. package/dist/stats-graph.js +323 -264
  94. package/dist/stats-graph.js.map +1 -1
  95. package/dist/stats-row.d.ts +8 -21
  96. package/dist/stats-row.d.ts.map +1 -0
  97. package/dist/stats-row.js +99 -101
  98. package/dist/stats-row.js.map +1 -1
  99. package/dist/theme.d.ts +3 -1
  100. package/dist/theme.d.ts.map +1 -0
  101. package/dist/theme.js +43 -54
  102. package/dist/theme.js.map +1 -1
  103. package/dist/vite-plugin.d.ts +7 -6
  104. package/dist/vite-plugin.d.ts.map +1 -0
  105. package/dist/vite-plugin.js +87 -78
  106. package/dist/vite-plugin.js.map +1 -1
  107. package/package.json +33 -29
  108. package/dist/buffers-modal.cjs +0 -491
  109. package/dist/buffers-modal.cjs.map +0 -1
  110. package/dist/buffers-modal.d.cts +0 -62
  111. package/dist/buffers-view.cjs +0 -269
  112. package/dist/buffers-view.cjs.map +0 -1
  113. package/dist/buffers-view.d.cts +0 -39
  114. package/dist/create-pane.cjs +0 -203
  115. package/dist/create-pane.cjs.map +0 -1
  116. package/dist/create-pane.d.cts +0 -66
  117. package/dist/devtools-client.cjs +0 -870
  118. package/dist/devtools-client.cjs.map +0 -1
  119. package/dist/devtools-client.d.cts +0 -362
  120. package/dist/index.cjs +0 -42
  121. package/dist/index.cjs.map +0 -1
  122. package/dist/index.d.cts +0 -8
  123. package/dist/index.js.map +0 -1
  124. package/dist/perf-trace.cjs +0 -103
  125. package/dist/perf-trace.cjs.map +0 -1
  126. package/dist/perf-trace.d.cts +0 -27
  127. package/dist/plugins.cjs +0 -44
  128. package/dist/plugins.cjs.map +0 -1
  129. package/dist/plugins.d.cts +0 -7
  130. package/dist/provider-switcher.cjs +0 -95
  131. package/dist/provider-switcher.cjs.map +0 -1
  132. package/dist/provider-switcher.d.cts +0 -18
  133. package/dist/react/devtools-provider.cjs +0 -59
  134. package/dist/react/devtools-provider.cjs.map +0 -1
  135. package/dist/react/devtools-provider.d.cts +0 -39
  136. package/dist/react/use-fps-graph.cjs +0 -49
  137. package/dist/react/use-fps-graph.cjs.map +0 -1
  138. package/dist/react/use-fps-graph.d.cts +0 -15
  139. package/dist/react/use-pane-button.cjs +0 -58
  140. package/dist/react/use-pane-button.cjs.map +0 -1
  141. package/dist/react/use-pane-button.d.cts +0 -10
  142. package/dist/react/use-pane-folder.cjs +0 -48
  143. package/dist/react/use-pane-folder.cjs.map +0 -1
  144. package/dist/react/use-pane-folder.d.cts +0 -15
  145. package/dist/react/use-pane-input.cjs +0 -70
  146. package/dist/react/use-pane-input.cjs.map +0 -1
  147. package/dist/react/use-pane-input.d.cts +0 -44
  148. package/dist/react/use-pane-radio-grid.cjs +0 -78
  149. package/dist/react/use-pane-radio-grid.cjs.map +0 -1
  150. package/dist/react/use-pane-radio-grid.d.cts +0 -35
  151. package/dist/react/use-pane.cjs +0 -45
  152. package/dist/react/use-pane.cjs.map +0 -1
  153. package/dist/react/use-pane.d.cts +0 -40
  154. package/dist/react.cjs +0 -50
  155. package/dist/react.cjs.map +0 -1
  156. package/dist/react.d.cts +0 -12
  157. package/dist/react.js.map +0 -1
  158. package/dist/registry-view.cjs +0 -371
  159. package/dist/registry-view.cjs.map +0 -1
  160. package/dist/registry-view.d.cts +0 -24
  161. package/dist/stats-graph.cjs +0 -297
  162. package/dist/stats-graph.cjs.map +0 -1
  163. package/dist/stats-graph.d.cts +0 -40
  164. package/dist/stats-row.cjs +0 -153
  165. package/dist/stats-row.cjs.map +0 -1
  166. package/dist/stats-row.d.cts +0 -24
  167. package/dist/theme.cjs +0 -260
  168. package/dist/theme.cjs.map +0 -1
  169. package/dist/theme.d.cts +0 -10
  170. package/dist/vite-plugin.cjs +0 -113
  171. package/dist/vite-plugin.cjs.map +0 -1
  172. package/dist/vite-plugin.d.cts +0 -9
@@ -1,273 +1,332 @@
1
1
  import { STATS_BATCH_MS } from "three-flatland/debug-protocol";
2
- const MODES = ["fps", "ms", "gpu", "mem"];
2
+ //#region src/stats-graph.ts
3
+ const MODES = [
4
+ "fps",
5
+ "ms",
6
+ "gpu",
7
+ "mem"
8
+ ];
3
9
  const BUFFER_SIZE = 80;
4
10
  const MODE_COLORS = {
5
- fps: "#47cca9",
6
- // retro-cyan
7
- ms: "#47cc6a",
8
- // green variant
9
- gpu: "#ffa347",
10
- // retro-amber
11
- mem: "#d94c87"
12
- // retro-pink
11
+ fps: "#47cca9",
12
+ ms: "#47cc6a",
13
+ gpu: "#ffa347",
14
+ mem: "#d94c87"
13
15
  };
14
16
  const MODE_UNITS = {
15
- fps: "FPS",
16
- ms: "MS",
17
- gpu: "MS",
18
- mem: "MB"
17
+ fps: "FPS",
18
+ ms: "MS",
19
+ gpu: "MS",
20
+ mem: "MB"
19
21
  };
22
+ /**
23
+ * Add a cycling stats graph to a Tweakpane parent.
24
+ * Matches the visual design of the built-in fpsgraph blade.
25
+ */
20
26
  function addStatsGraph(parent, client, options = {}) {
21
- const { rows = 2, driver = "raf" } = options;
22
- let mode = "fps";
23
- let modeIndex = 0;
24
- const graphMax = {
25
- fps: 120,
26
- ms: 16.67,
27
- gpu: 16.67,
28
- mem: 256
29
- };
30
- const sessionMin = { fps: Infinity, ms: Infinity, gpu: Infinity, mem: Infinity };
31
- const sessionMax = { fps: 0, ms: 0, gpu: 0, mem: 0 };
32
- const lblv = document.createElement("div");
33
- lblv.className = "tp-lblv";
34
- lblv.style.cursor = "pointer";
35
- const lblvL = document.createElement("div");
36
- lblvL.className = "tp-lblv_l";
37
- lblvL.textContent = "fps";
38
- lblv.appendChild(lblvL);
39
- const lblvV = document.createElement("div");
40
- lblvV.className = "tp-lblv_v";
41
- lblv.appendChild(lblvV);
42
- const fpsv = document.createElement("div");
43
- fpsv.className = "tp-fpsv";
44
- lblvV.appendChild(fpsv);
45
- const fpsvG = document.createElement("div");
46
- fpsvG.className = "tp-fpsv_g";
47
- fpsv.appendChild(fpsvG);
48
- const grlv = document.createElement("div");
49
- grlv.className = "tp-grlv";
50
- fpsvG.appendChild(grlv);
51
- const canvas = document.createElement("canvas");
52
- canvas.classList.add("tp-grlv_g");
53
- canvas.style.cssText = `display:block;width:100%;height:calc(var(--cnt-usz, 20px) * ${rows})`;
54
- canvas.width = 1;
55
- canvas.height = 1;
56
- grlv.appendChild(canvas);
57
- const gfx = canvas.getContext("2d");
58
- const fpsvL = document.createElement("div");
59
- fpsvL.className = "tp-fpsv_l";
60
- fpsvL.style.cssText = "background:rgba(0,2,28,0.65);padding:1px 4px;border-radius:2px;";
61
- fpsv.appendChild(fpsvL);
62
- const valueSpan = document.createElement("span");
63
- valueSpan.className = "tp-fpsv_v";
64
- valueSpan.textContent = "--";
65
- fpsvL.appendChild(valueSpan);
66
- const unitSpan = document.createElement("span");
67
- unitSpan.className = "tp-fpsv_u";
68
- unitSpan.textContent = "FPS";
69
- fpsvL.appendChild(unitSpan);
70
- const blade = parent.addBlade({ view: "separator" });
71
- const bladeEl = blade.element;
72
- bladeEl.innerHTML = "";
73
- bladeEl.className = "tp-cntv";
74
- bladeEl.style.cssText = "";
75
- bladeEl.appendChild(lblv);
76
- function applyMode() {
77
- lblvL.textContent = mode;
78
- unitSpan.textContent = MODE_UNITS[mode];
79
- }
80
- applyMode();
81
- lblv.addEventListener("click", () => {
82
- const state = client.state;
83
- do {
84
- modeIndex = (modeIndex + 1) % MODES.length;
85
- mode = MODES[modeIndex];
86
- } while (mode === "mem" && state.heapUsedMB === void 0 || mode === "gpu" && !state.gpuModeEnabled);
87
- applyMode();
88
- });
89
- let cachedW = 0;
90
- let cachedH = 0;
91
- const resizeObserver = new ResizeObserver(([entry]) => {
92
- if (!entry) return;
93
- const r = entry.contentRect;
94
- const dpr = window.devicePixelRatio || 1;
95
- const w = Math.max(1, Math.round(r.width * dpr));
96
- const h = Math.max(1, Math.round(r.height * dpr));
97
- if (canvas.width !== w || canvas.height !== h) {
98
- canvas.width = w;
99
- canvas.height = h;
100
- }
101
- cachedW = w;
102
- cachedH = h;
103
- });
104
- resizeObserver.observe(canvas);
105
- const MODE_LABEL = { fps: "fps", ms: "ms", gpu: "gpu", mem: "mem" };
106
- const MODE_VALUE_UNIT = { fps: "FPS", ms: "MS", gpu: "MS", mem: "MB" };
107
- const MODE_RANGE_UNIT = { fps: "FPS", ms: "MS", gpu: "GPU", mem: "MB" };
108
- const MODE_DECIMALS = { fps: 0, ms: 1, gpu: 1, mem: 0 };
109
- const MODE_SCALE = { fps: 1, ms: 10, gpu: 10, mem: 1 };
110
- const fmtCache = {
111
- fps: { key: NaN, str: "" },
112
- ms: { key: NaN, str: "" },
113
- gpu: { key: NaN, str: "" },
114
- mem: { key: NaN, str: "" }
115
- };
116
- function fmtCached(m, v) {
117
- const key = Math.round(v * MODE_SCALE[m]);
118
- const c = fmtCache[m];
119
- if (c.key === key) return c.str;
120
- c.key = key;
121
- c.str = v.toFixed(MODE_DECIMALS[m]);
122
- return c.str;
123
- }
124
- function seriesFor(m) {
125
- const s = client.state.series;
126
- switch (m) {
127
- case "fps":
128
- return s.fps;
129
- case "ms":
130
- return s.cpuMs;
131
- case "gpu":
132
- return s.gpuMs;
133
- case "mem":
134
- return s.heapUsedMB;
135
- }
136
- }
137
- const prevLabel = { fps: 0, ms: 0, gpu: 0, mem: 0 };
138
- const currLabel = { fps: 0, ms: 0, gpu: 0, mem: 0 };
139
- const hasLabel = { fps: false, ms: false, gpu: false, mem: false };
140
- let batchCount = 0;
141
- let lastBatchAt = performance.now();
142
- let prevRingWrite = 0;
143
- const snap = (m, v) => {
144
- if (v === void 0) return;
145
- prevLabel[m] = hasLabel[m] ? currLabel[m] : v;
146
- currLabel[m] = v;
147
- hasLabel[m] = true;
148
- };
149
- const unsubscribe = client.addListener((s) => {
150
- if (s.heapLimitMB !== void 0) graphMax.mem = s.heapLimitMB;
151
- snap("fps", s.fps);
152
- snap("ms", s.cpuMs);
153
- snap("gpu", s.gpuMs);
154
- snap("mem", s.heapUsedMB);
155
- const fps = s.series.fps;
156
- const size = fps.data.length;
157
- const delta = (fps.write - prevRingWrite + size) % size;
158
- if (delta > 0) {
159
- batchCount = delta;
160
- prevRingWrite = fps.write;
161
- lastBatchAt = performance.now();
162
- }
163
- });
164
- let rafId = 0;
165
- let disposed = false;
166
- function setText(node, next, cache) {
167
- if (cache.v === next) return;
168
- node.textContent = next;
169
- cache.v = next;
170
- }
171
- const labelCache = { v: "" };
172
- const valueCache = { v: "" };
173
- const unitCache = { v: "" };
174
- const titleCache = { v: "" };
175
- function update() {
176
- if (disposed) return;
177
- if (cachedW === 0 || cachedH === 0 || gfx === null) return;
178
- const now = performance.now();
179
- const t = Math.min(1, Math.max(0, (now - lastBatchAt) / STATS_BATCH_MS));
180
- const ring = seriesFor(mode);
181
- const size = ring.data.length;
182
- const max = graphMax[mode] || 1;
183
- const phase = batchCount * (1 - t);
184
- const intPhase = Math.floor(phase);
185
- const fracPhase = phase - intPhase;
186
- const w = cachedW;
187
- const h = cachedH;
188
- gfx.clearRect(0, 0, w, h);
189
- const color = MODE_COLORS[mode];
190
- const samplePixelW = w / (BUFFER_SIZE - 1);
191
- let mn = Infinity;
192
- let mx = 0;
193
- const baseRingIdx = ring.write - intPhase - BUFFER_SIZE + size * 2;
194
- gfx.beginPath();
195
- for (let i = -1; i < BUFFER_SIZE; i++) {
196
- const ringIdx = (baseRingIdx + i) % size;
197
- const v = ring.data[ringIdx];
198
- if (i >= 0) {
199
- if (v < mn) mn = v;
200
- if (v > mx) mx = v;
201
- }
202
- const x = (i + fracPhase) * samplePixelW;
203
- const yRaw = h - v / max * h * 0.85 - h * 0.05;
204
- const y = yRaw < h * 0.05 ? h * 0.05 : yRaw;
205
- if (i === -1) gfx.moveTo(x, y);
206
- else gfx.lineTo(x, y);
207
- }
208
- gfx.lineTo(w, h);
209
- gfx.lineTo(0, h);
210
- gfx.closePath();
211
- gfx.fillStyle = color;
212
- gfx.globalAlpha = 0.33;
213
- gfx.fill();
214
- gfx.globalAlpha = 1;
215
- gfx.beginPath();
216
- for (let i = -1; i < BUFFER_SIZE; i++) {
217
- const ringIdx = (baseRingIdx + i) % size;
218
- const v = ring.data[ringIdx];
219
- const x = (i + fracPhase) * samplePixelW;
220
- const yRaw = h - v / max * h * 0.85 - h * 0.05;
221
- const y = yRaw < h * 0.05 ? h * 0.05 : yRaw;
222
- if (i === -1) gfx.moveTo(x, y);
223
- else gfx.lineTo(x, y);
224
- }
225
- gfx.strokeStyle = color;
226
- gfx.lineWidth = Math.max(1, window.devicePixelRatio || 1);
227
- gfx.stroke();
228
- if (hasLabel[mode]) {
229
- sessionMin[mode] = Math.min(sessionMin[mode], mn);
230
- sessionMax[mode] = Math.max(sessionMax[mode], mx);
231
- }
232
- const has = hasLabel[mode];
233
- const lerped = prevLabel[mode] + (currLabel[mode] - prevLabel[mode]) * t;
234
- const sMn = sessionMin[mode];
235
- const sMx = sessionMax[mode];
236
- setText(lblvL, MODE_LABEL[mode], labelCache);
237
- setText(valueSpan, has ? fmtCached(mode, lerped) : "--", valueCache);
238
- setText(unitSpan, MODE_VALUE_UNIT[mode], unitCache);
239
- const nextTitle = sMn !== Infinity ? `${MODE_RANGE_UNIT[mode]} range: ${Math.round(sMn)}\u2013${Math.round(sMx)}` : "";
240
- if (titleCache.v !== nextTitle) {
241
- lblvL.title = nextTitle;
242
- titleCache.v = nextTitle;
243
- }
244
- }
245
- if (driver === "raf") {
246
- const autoFrame = () => {
247
- if (disposed) return;
248
- rafId = requestAnimationFrame(autoFrame);
249
- update();
250
- };
251
- rafId = requestAnimationFrame(autoFrame);
252
- }
253
- return {
254
- element: bladeEl,
255
- // In `'raf'` mode the internal loop already runs, so public `update()`
256
- // is a no-op — we don't want to paint twice per frame when the host
257
- // also (redundantly) drives it. In `'manual'` mode this is the only
258
- // way the graph gets painted.
259
- update: driver === "manual" ? update : () => {
260
- },
261
- dispose() {
262
- disposed = true;
263
- if (rafId !== 0) cancelAnimationFrame(rafId);
264
- unsubscribe();
265
- resizeObserver.disconnect();
266
- blade.dispose();
267
- }
268
- };
27
+ const { rows = 2, driver = "raf" } = options;
28
+ let mode = "fps";
29
+ let modeIndex = 0;
30
+ const graphMax = {
31
+ fps: 120,
32
+ ms: 16.67,
33
+ gpu: 16.67,
34
+ mem: 256
35
+ };
36
+ const sessionMin = {
37
+ fps: Infinity,
38
+ ms: Infinity,
39
+ gpu: Infinity,
40
+ mem: Infinity
41
+ };
42
+ const sessionMax = {
43
+ fps: 0,
44
+ ms: 0,
45
+ gpu: 0,
46
+ mem: 0
47
+ };
48
+ const lblv = document.createElement("div");
49
+ lblv.className = "tp-lblv";
50
+ lblv.style.cursor = "pointer";
51
+ const lblvL = document.createElement("div");
52
+ lblvL.className = "tp-lblv_l";
53
+ lblvL.textContent = "fps";
54
+ lblv.appendChild(lblvL);
55
+ const lblvV = document.createElement("div");
56
+ lblvV.className = "tp-lblv_v";
57
+ lblv.appendChild(lblvV);
58
+ const fpsv = document.createElement("div");
59
+ fpsv.className = "tp-fpsv";
60
+ lblvV.appendChild(fpsv);
61
+ const fpsvG = document.createElement("div");
62
+ fpsvG.className = "tp-fpsv_g";
63
+ fpsv.appendChild(fpsvG);
64
+ const grlv = document.createElement("div");
65
+ grlv.className = "tp-grlv";
66
+ fpsvG.appendChild(grlv);
67
+ const canvas = document.createElement("canvas");
68
+ canvas.classList.add("tp-grlv_g");
69
+ canvas.style.cssText = `display:block;width:100%;height:calc(var(--cnt-usz, 20px) * ${rows})`;
70
+ canvas.width = 1;
71
+ canvas.height = 1;
72
+ grlv.appendChild(canvas);
73
+ const gfx = canvas.getContext("2d");
74
+ const fpsvL = document.createElement("div");
75
+ fpsvL.className = "tp-fpsv_l";
76
+ fpsvL.style.cssText = "background:rgba(0,2,28,0.65);padding:1px 4px;border-radius:2px;";
77
+ fpsv.appendChild(fpsvL);
78
+ const valueSpan = document.createElement("span");
79
+ valueSpan.className = "tp-fpsv_v";
80
+ valueSpan.textContent = "--";
81
+ fpsvL.appendChild(valueSpan);
82
+ const unitSpan = document.createElement("span");
83
+ unitSpan.className = "tp-fpsv_u";
84
+ unitSpan.textContent = "FPS";
85
+ fpsvL.appendChild(unitSpan);
86
+ const blade = parent.addBlade({ view: "separator" });
87
+ const bladeEl = blade.element;
88
+ bladeEl.innerHTML = "";
89
+ bladeEl.className = "tp-cntv";
90
+ bladeEl.style.cssText = "";
91
+ bladeEl.appendChild(lblv);
92
+ function applyMode() {
93
+ lblvL.textContent = mode;
94
+ unitSpan.textContent = MODE_UNITS[mode];
95
+ }
96
+ applyMode();
97
+ lblv.addEventListener("click", () => {
98
+ const state = client.state;
99
+ do {
100
+ modeIndex = (modeIndex + 1) % MODES.length;
101
+ mode = MODES[modeIndex];
102
+ } while (mode === "mem" && state.heapUsedMB === void 0 || mode === "gpu" && !state.gpuModeEnabled);
103
+ applyMode();
104
+ });
105
+ let cachedW = 0;
106
+ let cachedH = 0;
107
+ const resizeObserver = new ResizeObserver(([entry]) => {
108
+ if (!entry) return;
109
+ const r = entry.contentRect;
110
+ const dpr = window.devicePixelRatio || 1;
111
+ const w = Math.max(1, Math.round(r.width * dpr));
112
+ const h = Math.max(1, Math.round(r.height * dpr));
113
+ if (canvas.width !== w || canvas.height !== h) {
114
+ canvas.width = w;
115
+ canvas.height = h;
116
+ }
117
+ cachedW = w;
118
+ cachedH = h;
119
+ });
120
+ resizeObserver.observe(canvas);
121
+ const MODE_LABEL = {
122
+ fps: "fps",
123
+ ms: "ms",
124
+ gpu: "gpu",
125
+ mem: "mem"
126
+ };
127
+ const MODE_VALUE_UNIT = {
128
+ fps: "FPS",
129
+ ms: "MS",
130
+ gpu: "MS",
131
+ mem: "MB"
132
+ };
133
+ const MODE_RANGE_UNIT = {
134
+ fps: "FPS",
135
+ ms: "MS",
136
+ gpu: "GPU",
137
+ mem: "MB"
138
+ };
139
+ const MODE_DECIMALS = {
140
+ fps: 0,
141
+ ms: 1,
142
+ gpu: 1,
143
+ mem: 0
144
+ };
145
+ const MODE_SCALE = {
146
+ fps: 1,
147
+ ms: 10,
148
+ gpu: 10,
149
+ mem: 1
150
+ };
151
+ const fmtCache = {
152
+ fps: {
153
+ key: NaN,
154
+ str: ""
155
+ },
156
+ ms: {
157
+ key: NaN,
158
+ str: ""
159
+ },
160
+ gpu: {
161
+ key: NaN,
162
+ str: ""
163
+ },
164
+ mem: {
165
+ key: NaN,
166
+ str: ""
167
+ }
168
+ };
169
+ function fmtCached(m, v) {
170
+ const key = Math.round(v * MODE_SCALE[m]);
171
+ const c = fmtCache[m];
172
+ if (c.key === key) return c.str;
173
+ c.key = key;
174
+ c.str = v.toFixed(MODE_DECIMALS[m]);
175
+ return c.str;
176
+ }
177
+ function seriesFor(m) {
178
+ const s = client.state.series;
179
+ switch (m) {
180
+ case "fps": return s.fps;
181
+ case "ms": return s.cpuMs;
182
+ case "gpu": return s.gpuMs;
183
+ case "mem": return s.heapUsedMB;
184
+ }
185
+ }
186
+ const prevLabel = {
187
+ fps: 0,
188
+ ms: 0,
189
+ gpu: 0,
190
+ mem: 0
191
+ };
192
+ const currLabel = {
193
+ fps: 0,
194
+ ms: 0,
195
+ gpu: 0,
196
+ mem: 0
197
+ };
198
+ const hasLabel = {
199
+ fps: false,
200
+ ms: false,
201
+ gpu: false,
202
+ mem: false
203
+ };
204
+ let batchCount = 0;
205
+ let lastBatchAt = performance.now();
206
+ let prevRingWrite = 0;
207
+ const snap = (m, v) => {
208
+ if (v === void 0) return;
209
+ prevLabel[m] = hasLabel[m] ? currLabel[m] : v;
210
+ currLabel[m] = v;
211
+ hasLabel[m] = true;
212
+ };
213
+ const unsubscribe = client.addListener((s) => {
214
+ if (s.heapLimitMB !== void 0) graphMax.mem = s.heapLimitMB;
215
+ snap("fps", s.fps);
216
+ snap("ms", s.cpuMs);
217
+ snap("gpu", s.gpuMs);
218
+ snap("mem", s.heapUsedMB);
219
+ const fps = s.series.fps;
220
+ const size = fps.data.length;
221
+ const delta = (fps.write - prevRingWrite + size) % size;
222
+ if (delta > 0) {
223
+ batchCount = delta;
224
+ prevRingWrite = fps.write;
225
+ lastBatchAt = performance.now();
226
+ }
227
+ });
228
+ let rafId = 0;
229
+ let disposed = false;
230
+ function setText(node, next, cache) {
231
+ if (cache.v === next) return;
232
+ node.textContent = next;
233
+ cache.v = next;
234
+ }
235
+ const labelCache = { v: "" };
236
+ const valueCache = { v: "" };
237
+ const unitCache = { v: "" };
238
+ const titleCache = { v: "" };
239
+ function update() {
240
+ if (disposed) return;
241
+ if (cachedW === 0 || cachedH === 0 || gfx === null) return;
242
+ const now = performance.now();
243
+ const t = Math.min(1, Math.max(0, (now - lastBatchAt) / STATS_BATCH_MS));
244
+ const ring = seriesFor(mode);
245
+ const size = ring.data.length;
246
+ const max = graphMax[mode] || 1;
247
+ const phase = batchCount * (1 - t);
248
+ const intPhase = Math.floor(phase);
249
+ const fracPhase = phase - intPhase;
250
+ const w = cachedW;
251
+ const h = cachedH;
252
+ gfx.clearRect(0, 0, w, h);
253
+ const color = MODE_COLORS[mode];
254
+ const samplePixelW = w / (BUFFER_SIZE - 1);
255
+ let mn = Infinity;
256
+ let mx = 0;
257
+ const baseRingIdx = ring.write - intPhase - BUFFER_SIZE + size * 2;
258
+ gfx.beginPath();
259
+ for (let i = -1; i < BUFFER_SIZE; i++) {
260
+ const ringIdx = (baseRingIdx + i) % size;
261
+ const v = ring.data[ringIdx];
262
+ if (i >= 0) {
263
+ if (v < mn) mn = v;
264
+ if (v > mx) mx = v;
265
+ }
266
+ const x = (i + fracPhase) * samplePixelW;
267
+ const yRaw = h - v / max * h * .85 - h * .05;
268
+ const y = yRaw < h * .05 ? h * .05 : yRaw;
269
+ if (i === -1) gfx.moveTo(x, y);
270
+ else gfx.lineTo(x, y);
271
+ }
272
+ gfx.lineTo(w, h);
273
+ gfx.lineTo(0, h);
274
+ gfx.closePath();
275
+ gfx.fillStyle = color;
276
+ gfx.globalAlpha = .33;
277
+ gfx.fill();
278
+ gfx.globalAlpha = 1;
279
+ gfx.beginPath();
280
+ for (let i = -1; i < BUFFER_SIZE; i++) {
281
+ const ringIdx = (baseRingIdx + i) % size;
282
+ const v = ring.data[ringIdx];
283
+ const x = (i + fracPhase) * samplePixelW;
284
+ const yRaw = h - v / max * h * .85 - h * .05;
285
+ const y = yRaw < h * .05 ? h * .05 : yRaw;
286
+ if (i === -1) gfx.moveTo(x, y);
287
+ else gfx.lineTo(x, y);
288
+ }
289
+ gfx.strokeStyle = color;
290
+ gfx.lineWidth = Math.max(1, window.devicePixelRatio || 1);
291
+ gfx.stroke();
292
+ if (hasLabel[mode]) {
293
+ sessionMin[mode] = Math.min(sessionMin[mode], mn);
294
+ sessionMax[mode] = Math.max(sessionMax[mode], mx);
295
+ }
296
+ const has = hasLabel[mode];
297
+ const lerped = prevLabel[mode] + (currLabel[mode] - prevLabel[mode]) * t;
298
+ const sMn = sessionMin[mode];
299
+ const sMx = sessionMax[mode];
300
+ setText(lblvL, MODE_LABEL[mode], labelCache);
301
+ setText(valueSpan, has ? fmtCached(mode, lerped) : "--", valueCache);
302
+ setText(unitSpan, MODE_VALUE_UNIT[mode], unitCache);
303
+ const nextTitle = sMn !== Infinity ? `${MODE_RANGE_UNIT[mode]} range: ${Math.round(sMn)}–${Math.round(sMx)}` : "";
304
+ if (titleCache.v !== nextTitle) {
305
+ lblvL.title = nextTitle;
306
+ titleCache.v = nextTitle;
307
+ }
308
+ }
309
+ if (driver === "raf") {
310
+ const autoFrame = () => {
311
+ if (disposed) return;
312
+ rafId = requestAnimationFrame(autoFrame);
313
+ update();
314
+ };
315
+ rafId = requestAnimationFrame(autoFrame);
316
+ }
317
+ return {
318
+ element: bladeEl,
319
+ update: driver === "manual" ? update : () => {},
320
+ dispose() {
321
+ disposed = true;
322
+ if (rafId !== 0) cancelAnimationFrame(rafId);
323
+ unsubscribe();
324
+ resizeObserver.disconnect();
325
+ blade.dispose();
326
+ }
327
+ };
269
328
  }
270
- export {
271
- addStatsGraph
272
- };
329
+ //#endregion
330
+ export { addStatsGraph };
331
+
273
332
  //# sourceMappingURL=stats-graph.js.map