@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,347 +1,366 @@
1
+ //#region src/registry-view.ts
1
2
  const BAR_HEIGHT = 24;
2
3
  const BAR_WIDTH = 240;
3
4
  const FILL_BY_KIND = {
4
- float: "#47cca9",
5
- int: "#47cc6a",
6
- uint: "#ffa347",
7
- bits: "#d94c87",
8
- float2: "#9d7aff",
9
- float3: "#9d7aff",
10
- float4: "#9d7aff"
5
+ float: "#47cca9",
6
+ int: "#47cc6a",
7
+ uint: "#ffa347",
8
+ bits: "#d94c87",
9
+ float2: "#9d7aff",
10
+ float3: "#9d7aff",
11
+ float4: "#9d7aff"
11
12
  };
12
13
  function splitName(name) {
13
- const dot = name.indexOf(".");
14
- if (dot === -1) return { group: "ungrouped", short: name };
15
- return { group: name.slice(0, dot), short: name.slice(dot + 1) };
14
+ const dot = name.indexOf(".");
15
+ if (dot === -1) return {
16
+ group: "ungrouped",
17
+ short: name
18
+ };
19
+ return {
20
+ group: name.slice(0, dot),
21
+ short: name.slice(dot + 1)
22
+ };
16
23
  }
17
24
  function addRegistryView(parent, client) {
18
- const blade = parent.addBlade({ view: "separator" });
19
- const bladeEl = blade.element;
20
- bladeEl.innerHTML = "";
21
- bladeEl.className = "tp-cntv";
22
- bladeEl.style.cssText = "display:none;flex-direction:column;background:rgba(0,0,0,0.3)";
23
- const header = document.createElement("div");
24
- header.style.cssText = [
25
- "display:grid",
26
- "grid-template-columns:2em 1fr 2em",
27
- "align-items:center",
28
- "padding:4px 6px 6px",
29
- "font-size:11px",
30
- "color:var(--tp-label-foreground-color)",
31
- "user-select:none",
32
- "-webkit-user-select:none",
33
- "font-variant-numeric:tabular-nums",
34
- "cursor:pointer"
35
- ].join(";");
36
- const arrowStyle = "cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;text-align:center";
37
- const prevBtn = document.createElement("span");
38
- prevBtn.textContent = "\u25C0";
39
- prevBtn.style.cssText = arrowStyle;
40
- prevBtn.setAttribute("role", "button");
41
- prevBtn.setAttribute("aria-label", "Previous registry group");
42
- const groupLabel = document.createElement("span");
43
- groupLabel.style.cssText = "font-weight:500;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;pointer-events:none";
44
- const nextBtn = document.createElement("span");
45
- nextBtn.textContent = "\u25B6";
46
- nextBtn.style.cssText = arrowStyle;
47
- nextBtn.setAttribute("role", "button");
48
- nextBtn.setAttribute("aria-label", "Next registry group");
49
- header.appendChild(prevBtn);
50
- header.appendChild(groupLabel);
51
- header.appendChild(nextBtn);
52
- const body = document.createElement("div");
53
- body.style.cssText = "display:flex;flex-direction:column;gap:4px;padding:2px 6px 6px";
54
- bladeEl.appendChild(header);
55
- bladeEl.appendChild(body);
56
- const groups = /* @__PURE__ */ new Map();
57
- let activeGroup = null;
58
- let collapsed = true;
59
- body.style.display = "none";
60
- function syncFilter() {
61
- if (collapsed || activeGroup === null) {
62
- client.setRegistry([]);
63
- return;
64
- }
65
- const g = groups.get(activeGroup);
66
- if (!g) {
67
- client.setRegistry([]);
68
- return;
69
- }
70
- const names = [];
71
- for (const row of g.entries.values()) names.push(row.name);
72
- client.setRegistry(names);
73
- }
74
- const toggleCollapse = (e) => {
75
- e.stopPropagation();
76
- collapsed = !collapsed;
77
- body.style.display = collapsed ? "none" : "flex";
78
- if (!collapsed && activeGroup !== null) {
79
- const g = groups.get(activeGroup);
80
- if (g) {
81
- g.container.style.display = "flex";
82
- for (const row of g.entries.values()) row.lastVersion = -1;
83
- }
84
- }
85
- syncFilter();
86
- };
87
- header.addEventListener("click", toggleCollapse);
88
- prevBtn.addEventListener("click", (e) => {
89
- e.stopPropagation();
90
- cycle(-1);
91
- });
92
- nextBtn.addEventListener("click", (e) => {
93
- e.stopPropagation();
94
- cycle(1);
95
- });
96
- function ensureGroup(name) {
97
- let g = groups.get(name);
98
- if (g) return g;
99
- const container = document.createElement("div");
100
- container.style.cssText = "display:none;flex-direction:column;gap:4px";
101
- body.appendChild(container);
102
- g = { group: name, container, entries: /* @__PURE__ */ new Map() };
103
- groups.set(name, g);
104
- return g;
105
- }
106
- function destroyGroup(name) {
107
- const g = groups.get(name);
108
- if (!g) return;
109
- g.container.remove();
110
- groups.delete(name);
111
- if (activeGroup === name) activeGroup = null;
112
- }
113
- function ensureEntryRow(g, name, shortName) {
114
- const existing = g.entries.get(shortName);
115
- if (existing) return existing;
116
- const row = document.createElement("div");
117
- row.style.cssText = "display:flex;flex-direction:column;gap:2px";
118
- const head = document.createElement("div");
119
- head.style.cssText = [
120
- "display:flex",
121
- "align-items:baseline",
122
- "gap:6px",
123
- "font-size:10px",
124
- "line-height:1.2",
125
- "font-family:var(--tp-base-font-family, ui-monospace, monospace)",
126
- "font-variant-numeric:tabular-nums",
127
- "color:var(--tp-monitor-foreground-color)"
128
- ].join(";");
129
- const nameLabel = document.createElement("span");
130
- nameLabel.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--tp-label-foreground-color)";
131
- nameLabel.textContent = shortName;
132
- const valueLabel = document.createElement("span");
133
- valueLabel.style.cssText = "opacity:0.75;white-space:nowrap";
134
- head.appendChild(nameLabel);
135
- head.appendChild(valueLabel);
136
- const canvas = document.createElement("canvas");
137
- canvas.width = BAR_WIDTH;
138
- canvas.height = BAR_HEIGHT;
139
- canvas.style.cssText = `width:100%;height:${BAR_HEIGHT}px;background:rgba(0,2,28,0.45);border-radius:2px`;
140
- row.appendChild(head);
141
- row.appendChild(canvas);
142
- g.container.appendChild(row);
143
- const entry = {
144
- name,
145
- shortName,
146
- row,
147
- nameLabel,
148
- valueLabel,
149
- canvas,
150
- ctx: canvas.getContext("2d"),
151
- lastVersion: -1
152
- };
153
- g.entries.set(shortName, entry);
154
- return entry;
155
- }
156
- function destroyEntryRow(g, shortName) {
157
- const row = g.entries.get(shortName);
158
- if (!row) return;
159
- row.row.remove();
160
- g.entries.delete(shortName);
161
- }
162
- function setActiveGroup(name) {
163
- activeGroup = name;
164
- for (const [groupName, g] of groups) {
165
- g.container.style.display = groupName === name && !collapsed ? "flex" : "none";
166
- }
167
- groupLabel.textContent = name ?? "\u2014";
168
- if (name !== null) {
169
- const g = groups.get(name);
170
- if (g) for (const row of g.entries.values()) row.lastVersion = -1;
171
- }
172
- syncFilter();
173
- }
174
- function cycle(delta) {
175
- const names = Array.from(groups.keys());
176
- if (names.length === 0) return;
177
- const idx = activeGroup !== null ? names.indexOf(activeGroup) : -1;
178
- const next = names[(idx + delta + names.length) % names.length];
179
- setActiveGroup(next);
180
- }
181
- function renderBars(row, kind, sample, isSigned) {
182
- const ctx = row.ctx;
183
- const w = row.canvas.width;
184
- const h = row.canvas.height;
185
- let min = Infinity;
186
- let max = -Infinity;
187
- let sum = 0;
188
- const n = sample.length;
189
- for (let i = 0; i < n; i++) {
190
- const v = sample[i];
191
- if (v < min) min = v;
192
- if (v > max) max = v;
193
- sum += v;
194
- }
195
- const mean = n > 0 ? sum / n : 0;
196
- if (!ctx) return { min, max, mean };
197
- ctx.clearRect(0, 0, w, h);
198
- if (n === 0 || !Number.isFinite(min) || !Number.isFinite(max)) return { min, max: 0, mean };
199
- const zeroY = isSigned ? h / 2 : h;
200
- const scale = isSigned ? h / 2 / Math.max(Math.abs(min), Math.abs(max) || 1) : h / (max || 1);
201
- ctx.fillStyle = FILL_BY_KIND[kind] ?? "#47cca9";
202
- const bw = Math.max(1, w / n);
203
- for (let i = 0; i < n; i++) {
204
- const v = sample[i];
205
- const x = Math.floor(i / n * w);
206
- const bh = v * scale;
207
- if (isSigned) {
208
- if (bh >= 0) ctx.fillRect(x, zeroY - bh, Math.ceil(bw), bh);
209
- else ctx.fillRect(x, zeroY, Math.ceil(bw), -bh);
210
- } else {
211
- ctx.fillRect(x, zeroY - bh, Math.ceil(bw), bh);
212
- }
213
- }
214
- if (isSigned) {
215
- ctx.fillStyle = "rgba(240, 237, 216, 0.2)";
216
- ctx.fillRect(0, zeroY, w, 1);
217
- }
218
- return { min, max, mean };
219
- }
220
- function renderBits(row, sample, count) {
221
- const ctx = row.ctx;
222
- const w = row.canvas.width;
223
- const h = row.canvas.height;
224
- if (!ctx) return { ones: 0, total: 0 };
225
- ctx.clearRect(0, 0, w, h);
226
- const totalBits = count * 32;
227
- if (totalBits === 0) return { ones: 0, total: 0 };
228
- let ones = 0;
229
- const px = w / totalBits;
230
- ctx.fillStyle = FILL_BY_KIND.bits;
231
- for (let i = 0; i < count; i++) {
232
- const word = sample[i] >>> 0;
233
- for (let b = 0; b < 32; b++) {
234
- if (word >>> b & 1) {
235
- ones++;
236
- const bitIdx = i * 32 + b;
237
- const x = Math.floor(bitIdx * px);
238
- ctx.fillRect(x, 2, Math.max(1, Math.ceil(px)), h - 4);
239
- }
240
- }
241
- }
242
- return { ones, total: totalBits };
243
- }
244
- function renderEntry(row, entry) {
245
- if (row.lastVersion === entry.version) return;
246
- row.lastVersion = entry.version;
247
- row.nameLabel.title = `${entry.name} \xB7 ${entry.kind} \xB7 ${entry.count}`;
248
- const kind = entry.kind;
249
- if (kind === "bits") {
250
- const { ones, total } = renderBits(row, entry.sample, entry.count);
251
- row.valueLabel.textContent = `${ones} / ${total}`;
252
- return;
253
- }
254
- if (kind === "float2" || kind === "float3" || kind === "float4") {
255
- const stride = kind === "float2" ? 2 : kind === "float3" ? 3 : 4;
256
- const n = Math.floor(entry.sample.length / stride);
257
- const mags = new Float32Array(n);
258
- for (let i = 0; i < n; i++) {
259
- let sq = 0;
260
- for (let c = 0; c < stride; c++) {
261
- const v = entry.sample[i * stride + c];
262
- sq += v * v;
263
- }
264
- mags[i] = Math.sqrt(sq);
265
- }
266
- const stats2 = renderBars(row, kind, mags, false);
267
- row.valueLabel.textContent = `${n}\xD7${stride} \u03BC=${stats2.mean.toFixed(2)}`;
268
- return;
269
- }
270
- const isSigned = kind === "int" || kind === "float";
271
- const stats = renderBars(row, kind, entry.sample, isSigned);
272
- const fmt = (v) => {
273
- if (!Number.isFinite(v)) return "\u2014";
274
- if (kind === "uint" || kind === "int") return Math.round(v).toString();
275
- return v.toFixed(2);
276
- };
277
- row.valueLabel.textContent = `\u03BC=${fmt(stats.mean)} ${fmt(stats.min)}\u2013${fmt(stats.max)}`;
278
- }
279
- const unsubscribe = client.addListener((state) => {
280
- const registry = state.registry;
281
- const seen = /* @__PURE__ */ new Map();
282
- for (const [name] of registry) {
283
- const { group, short } = splitName(name);
284
- let bucket = seen.get(group);
285
- if (!bucket) {
286
- bucket = /* @__PURE__ */ new Set();
287
- seen.set(group, bucket);
288
- }
289
- bucket.add(short);
290
- }
291
- for (const [groupName, g] of groups) {
292
- const bucket = seen.get(groupName);
293
- if (!bucket) {
294
- destroyGroup(groupName);
295
- continue;
296
- }
297
- for (const shortName of g.entries.keys()) {
298
- if (!bucket.has(shortName)) destroyEntryRow(g, shortName);
299
- }
300
- }
301
- for (const [name] of registry) {
302
- const { group, short } = splitName(name);
303
- const g = ensureGroup(group);
304
- ensureEntryRow(g, name, short);
305
- }
306
- if (groups.size === 0) {
307
- activeGroup = null;
308
- groupLabel.textContent = "\u2014";
309
- } else if (activeGroup === null || !groups.has(activeGroup)) {
310
- setActiveGroup(groups.keys().next().value);
311
- } else {
312
- groupLabel.textContent = activeGroup;
313
- const g = groups.get(activeGroup);
314
- g.container.style.display = collapsed ? "none" : "flex";
315
- }
316
- if (!collapsed && activeGroup !== null) {
317
- const g = groups.get(activeGroup);
318
- if (g) {
319
- for (const [shortName, row] of g.entries) {
320
- const entry = registry.get(`${activeGroup}.${shortName}`) ?? registry.get(shortName);
321
- if (entry) renderEntry(row, entry);
322
- }
323
- }
324
- }
325
- const multi = groups.size > 1;
326
- const arrowOpacity = multi ? "0.8" : "0.25";
327
- const arrowPointer = multi ? "pointer" : "default";
328
- prevBtn.style.opacity = arrowOpacity;
329
- nextBtn.style.opacity = arrowOpacity;
330
- prevBtn.style.cursor = arrowPointer;
331
- nextBtn.style.cursor = arrowPointer;
332
- bladeEl.style.display = registry.size > 0 ? "flex" : "none";
333
- syncFilter();
334
- });
335
- return {
336
- element: bladeEl,
337
- dispose() {
338
- unsubscribe();
339
- groups.clear();
340
- blade.dispose();
341
- }
342
- };
25
+ const blade = parent.addBlade({ view: "separator" });
26
+ const bladeEl = blade.element;
27
+ bladeEl.innerHTML = "";
28
+ bladeEl.className = "tp-cntv";
29
+ bladeEl.style.cssText = "display:none;flex-direction:column;background:rgba(0,0,0,0.3)";
30
+ const header = document.createElement("div");
31
+ header.style.cssText = [
32
+ "display:grid",
33
+ "grid-template-columns:2em 1fr 2em",
34
+ "align-items:center",
35
+ "padding:4px 6px 6px",
36
+ "font-size:11px",
37
+ "color:var(--tp-label-foreground-color)",
38
+ "user-select:none",
39
+ "-webkit-user-select:none",
40
+ "font-variant-numeric:tabular-nums",
41
+ "cursor:pointer"
42
+ ].join(";");
43
+ const arrowStyle = "cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;text-align:center";
44
+ const prevBtn = document.createElement("span");
45
+ prevBtn.textContent = "";
46
+ prevBtn.style.cssText = arrowStyle;
47
+ prevBtn.setAttribute("role", "button");
48
+ prevBtn.setAttribute("aria-label", "Previous registry group");
49
+ const groupLabel = document.createElement("span");
50
+ groupLabel.style.cssText = "font-weight:500;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;pointer-events:none";
51
+ const nextBtn = document.createElement("span");
52
+ nextBtn.textContent = "";
53
+ nextBtn.style.cssText = arrowStyle;
54
+ nextBtn.setAttribute("role", "button");
55
+ nextBtn.setAttribute("aria-label", "Next registry group");
56
+ header.appendChild(prevBtn);
57
+ header.appendChild(groupLabel);
58
+ header.appendChild(nextBtn);
59
+ const body = document.createElement("div");
60
+ body.style.cssText = "display:flex;flex-direction:column;gap:4px;padding:2px 6px 6px";
61
+ bladeEl.appendChild(header);
62
+ bladeEl.appendChild(body);
63
+ const groups = /* @__PURE__ */ new Map();
64
+ let activeGroup = null;
65
+ let collapsed = true;
66
+ body.style.display = "none";
67
+ /** Push the currently-visible entry names up to the client. */
68
+ function syncFilter() {
69
+ if (collapsed || activeGroup === null) {
70
+ client.setRegistry([]);
71
+ return;
72
+ }
73
+ const g = groups.get(activeGroup);
74
+ if (!g) {
75
+ client.setRegistry([]);
76
+ return;
77
+ }
78
+ const names = [];
79
+ for (const row of g.entries.values()) names.push(row.name);
80
+ client.setRegistry(names);
81
+ }
82
+ const toggleCollapse = (e) => {
83
+ e.stopPropagation();
84
+ collapsed = !collapsed;
85
+ body.style.display = collapsed ? "none" : "flex";
86
+ if (!collapsed && activeGroup !== null) {
87
+ const g = groups.get(activeGroup);
88
+ if (g) {
89
+ g.container.style.display = "flex";
90
+ for (const row of g.entries.values()) row.lastVersion = -1;
91
+ }
92
+ }
93
+ syncFilter();
94
+ };
95
+ header.addEventListener("click", toggleCollapse);
96
+ prevBtn.addEventListener("click", (e) => {
97
+ e.stopPropagation();
98
+ cycle(-1);
99
+ });
100
+ nextBtn.addEventListener("click", (e) => {
101
+ e.stopPropagation();
102
+ cycle(1);
103
+ });
104
+ function ensureGroup(name) {
105
+ let g = groups.get(name);
106
+ if (g) return g;
107
+ const container = document.createElement("div");
108
+ container.style.cssText = "display:none;flex-direction:column;gap:4px";
109
+ body.appendChild(container);
110
+ g = {
111
+ group: name,
112
+ container,
113
+ entries: /* @__PURE__ */ new Map()
114
+ };
115
+ groups.set(name, g);
116
+ return g;
117
+ }
118
+ function destroyGroup(name) {
119
+ const g = groups.get(name);
120
+ if (!g) return;
121
+ g.container.remove();
122
+ groups.delete(name);
123
+ if (activeGroup === name) activeGroup = null;
124
+ }
125
+ function ensureEntryRow(g, name, shortName) {
126
+ const existing = g.entries.get(shortName);
127
+ if (existing) return existing;
128
+ const row = document.createElement("div");
129
+ row.style.cssText = "display:flex;flex-direction:column;gap:2px";
130
+ const head = document.createElement("div");
131
+ head.style.cssText = [
132
+ "display:flex",
133
+ "align-items:baseline",
134
+ "gap:6px",
135
+ "font-size:10px",
136
+ "line-height:1.2",
137
+ "font-family:var(--tp-base-font-family, ui-monospace, monospace)",
138
+ "font-variant-numeric:tabular-nums",
139
+ "color:var(--tp-monitor-foreground-color)"
140
+ ].join(";");
141
+ const nameLabel = document.createElement("span");
142
+ nameLabel.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--tp-label-foreground-color)";
143
+ nameLabel.textContent = shortName;
144
+ const valueLabel = document.createElement("span");
145
+ valueLabel.style.cssText = "opacity:0.75;white-space:nowrap";
146
+ head.appendChild(nameLabel);
147
+ head.appendChild(valueLabel);
148
+ const canvas = document.createElement("canvas");
149
+ canvas.width = BAR_WIDTH;
150
+ canvas.height = BAR_HEIGHT;
151
+ canvas.style.cssText = `width:100%;height:${BAR_HEIGHT}px;background:rgba(0,2,28,0.45);border-radius:2px`;
152
+ row.appendChild(head);
153
+ row.appendChild(canvas);
154
+ g.container.appendChild(row);
155
+ const entry = {
156
+ name,
157
+ shortName,
158
+ row,
159
+ nameLabel,
160
+ valueLabel,
161
+ canvas,
162
+ ctx: canvas.getContext("2d"),
163
+ lastVersion: -1
164
+ };
165
+ g.entries.set(shortName, entry);
166
+ return entry;
167
+ }
168
+ function destroyEntryRow(g, shortName) {
169
+ const row = g.entries.get(shortName);
170
+ if (!row) return;
171
+ row.row.remove();
172
+ g.entries.delete(shortName);
173
+ }
174
+ function setActiveGroup(name) {
175
+ activeGroup = name;
176
+ for (const [groupName, g] of groups) g.container.style.display = groupName === name && !collapsed ? "flex" : "none";
177
+ groupLabel.textContent = name ?? "—";
178
+ if (name !== null) {
179
+ const g = groups.get(name);
180
+ if (g) for (const row of g.entries.values()) row.lastVersion = -1;
181
+ }
182
+ syncFilter();
183
+ }
184
+ function cycle(delta) {
185
+ const names = Array.from(groups.keys());
186
+ if (names.length === 0) return;
187
+ const next = names[((activeGroup !== null ? names.indexOf(activeGroup) : -1) + delta + names.length) % names.length];
188
+ setActiveGroup(next);
189
+ }
190
+ function renderBars(row, kind, sample, isSigned) {
191
+ const ctx = row.ctx;
192
+ const w = row.canvas.width;
193
+ const h = row.canvas.height;
194
+ let min = Infinity;
195
+ let max = -Infinity;
196
+ let sum = 0;
197
+ const n = sample.length;
198
+ for (let i = 0; i < n; i++) {
199
+ const v = sample[i];
200
+ if (v < min) min = v;
201
+ if (v > max) max = v;
202
+ sum += v;
203
+ }
204
+ const mean = n > 0 ? sum / n : 0;
205
+ if (!ctx) return {
206
+ min,
207
+ max,
208
+ mean
209
+ };
210
+ ctx.clearRect(0, 0, w, h);
211
+ if (n === 0 || !Number.isFinite(min) || !Number.isFinite(max)) return {
212
+ min,
213
+ max: 0,
214
+ mean
215
+ };
216
+ const zeroY = isSigned ? h / 2 : h;
217
+ const scale = isSigned ? h / 2 / Math.max(Math.abs(min), Math.abs(max) || 1) : h / (max || 1);
218
+ ctx.fillStyle = FILL_BY_KIND[kind] ?? "#47cca9";
219
+ const bw = Math.max(1, w / n);
220
+ for (let i = 0; i < n; i++) {
221
+ const v = sample[i];
222
+ const x = Math.floor(i / n * w);
223
+ const bh = v * scale;
224
+ if (isSigned) if (bh >= 0) ctx.fillRect(x, zeroY - bh, Math.ceil(bw), bh);
225
+ else ctx.fillRect(x, zeroY, Math.ceil(bw), -bh);
226
+ else ctx.fillRect(x, zeroY - bh, Math.ceil(bw), bh);
227
+ }
228
+ if (isSigned) {
229
+ ctx.fillStyle = "rgba(240, 237, 216, 0.2)";
230
+ ctx.fillRect(0, zeroY, w, 1);
231
+ }
232
+ return {
233
+ min,
234
+ max,
235
+ mean
236
+ };
237
+ }
238
+ function renderBits(row, sample, count) {
239
+ const ctx = row.ctx;
240
+ const w = row.canvas.width;
241
+ const h = row.canvas.height;
242
+ if (!ctx) return {
243
+ ones: 0,
244
+ total: 0
245
+ };
246
+ ctx.clearRect(0, 0, w, h);
247
+ const totalBits = count * 32;
248
+ if (totalBits === 0) return {
249
+ ones: 0,
250
+ total: 0
251
+ };
252
+ let ones = 0;
253
+ const px = w / totalBits;
254
+ ctx.fillStyle = FILL_BY_KIND.bits;
255
+ for (let i = 0; i < count; i++) {
256
+ const word = sample[i] >>> 0;
257
+ for (let b = 0; b < 32; b++) if (word >>> b & 1) {
258
+ ones++;
259
+ const bitIdx = i * 32 + b;
260
+ const x = Math.floor(bitIdx * px);
261
+ ctx.fillRect(x, 2, Math.max(1, Math.ceil(px)), h - 4);
262
+ }
263
+ }
264
+ return {
265
+ ones,
266
+ total: totalBits
267
+ };
268
+ }
269
+ function renderEntry(row, entry) {
270
+ if (row.lastVersion === entry.version) return;
271
+ row.lastVersion = entry.version;
272
+ row.nameLabel.title = `${entry.name} · ${entry.kind} · ${entry.count}`;
273
+ const kind = entry.kind;
274
+ if (kind === "bits") {
275
+ const { ones, total } = renderBits(row, entry.sample, entry.count);
276
+ row.valueLabel.textContent = `${ones} / ${total}`;
277
+ return;
278
+ }
279
+ if (kind === "float2" || kind === "float3" || kind === "float4") {
280
+ const stride = kind === "float2" ? 2 : kind === "float3" ? 3 : 4;
281
+ const n = Math.floor(entry.sample.length / stride);
282
+ const mags = new Float32Array(n);
283
+ for (let i = 0; i < n; i++) {
284
+ let sq = 0;
285
+ for (let c = 0; c < stride; c++) {
286
+ const v = entry.sample[i * stride + c];
287
+ sq += v * v;
288
+ }
289
+ mags[i] = Math.sqrt(sq);
290
+ }
291
+ const stats = renderBars(row, kind, mags, false);
292
+ row.valueLabel.textContent = `${n}×${stride} μ=${stats.mean.toFixed(2)}`;
293
+ return;
294
+ }
295
+ const isSigned = kind === "int" || kind === "float";
296
+ const stats = renderBars(row, kind, entry.sample, isSigned);
297
+ const fmt = (v) => {
298
+ if (!Number.isFinite(v)) return "—";
299
+ if (kind === "uint" || kind === "int") return Math.round(v).toString();
300
+ return v.toFixed(2);
301
+ };
302
+ row.valueLabel.textContent = `μ=${fmt(stats.mean)} ${fmt(stats.min)}–${fmt(stats.max)}`;
303
+ }
304
+ const unsubscribe = client.addListener((state) => {
305
+ const registry = state.registry;
306
+ const seen = /* @__PURE__ */ new Map();
307
+ for (const [name] of registry) {
308
+ const { group, short } = splitName(name);
309
+ let bucket = seen.get(group);
310
+ if (!bucket) {
311
+ bucket = /* @__PURE__ */ new Set();
312
+ seen.set(group, bucket);
313
+ }
314
+ bucket.add(short);
315
+ }
316
+ for (const [groupName, g] of groups) {
317
+ const bucket = seen.get(groupName);
318
+ if (!bucket) {
319
+ destroyGroup(groupName);
320
+ continue;
321
+ }
322
+ for (const shortName of g.entries.keys()) if (!bucket.has(shortName)) destroyEntryRow(g, shortName);
323
+ }
324
+ for (const [name] of registry) {
325
+ const { group, short } = splitName(name);
326
+ ensureEntryRow(ensureGroup(group), name, short);
327
+ }
328
+ if (groups.size === 0) {
329
+ activeGroup = null;
330
+ groupLabel.textContent = "—";
331
+ } else if (activeGroup === null || !groups.has(activeGroup)) setActiveGroup(groups.keys().next().value);
332
+ else {
333
+ groupLabel.textContent = activeGroup;
334
+ const g = groups.get(activeGroup);
335
+ g.container.style.display = collapsed ? "none" : "flex";
336
+ }
337
+ if (!collapsed && activeGroup !== null) {
338
+ const g = groups.get(activeGroup);
339
+ if (g) for (const [shortName, row] of g.entries) {
340
+ const entry = registry.get(`${activeGroup}.${shortName}`) ?? registry.get(shortName);
341
+ if (entry) renderEntry(row, entry);
342
+ }
343
+ }
344
+ const multi = groups.size > 1;
345
+ const arrowOpacity = multi ? "0.8" : "0.25";
346
+ const arrowPointer = multi ? "pointer" : "default";
347
+ prevBtn.style.opacity = arrowOpacity;
348
+ nextBtn.style.opacity = arrowOpacity;
349
+ prevBtn.style.cursor = arrowPointer;
350
+ nextBtn.style.cursor = arrowPointer;
351
+ bladeEl.style.display = registry.size > 0 ? "flex" : "none";
352
+ syncFilter();
353
+ });
354
+ return {
355
+ element: bladeEl,
356
+ dispose() {
357
+ unsubscribe();
358
+ groups.clear();
359
+ blade.dispose();
360
+ }
361
+ };
343
362
  }
344
- export {
345
- addRegistryView
346
- };
363
+ //#endregion
364
+ export { addRegistryView };
365
+
347
366
  //# sourceMappingURL=registry-view.js.map