@zenfg/inspector 0.1.0-beta.2 → 0.1.0-beta.4
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/GUIDE.md +182 -0
- package/README.md +60 -26
- package/THEMING.md +124 -0
- package/dist/FrameGraphInspector.d.ts +24 -8
- package/dist/FrameGraphInspector.d.ts.map +1 -1
- package/dist/FrameGraphInspector.js +151 -65
- package/dist/FrameGraphInspector.js.map +1 -1
- package/dist/capture.d.ts +5 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +2 -0
- package/dist/capture.js.map +1 -0
- package/dist/debugCaptureModel.d.ts +28 -4
- package/dist/debugCaptureModel.d.ts.map +1 -1
- package/dist/debugCaptureModel.js +72 -16
- package/dist/debugCaptureModel.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/panelAliasAnalysis.d.ts +1 -0
- package/dist/panelAliasAnalysis.d.ts.map +1 -1
- package/dist/panelAliasAnalysis.js +23 -54
- package/dist/panelAliasAnalysis.js.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.d.ts +18 -3
- package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
- package/dist/panelCytoscapeGraphRenderer.js +183 -41
- package/dist/panelCytoscapeGraphRenderer.js.map +1 -1
- package/dist/panelDetailLayout.d.ts +27 -0
- package/dist/panelDetailLayout.d.ts.map +1 -0
- package/dist/panelDetailLayout.js +174 -0
- package/dist/panelDetailLayout.js.map +1 -0
- package/dist/panelDetailStyles.d.ts +2 -0
- package/dist/panelDetailStyles.d.ts.map +1 -0
- package/dist/panelDetailStyles.js +57 -0
- package/dist/panelDetailStyles.js.map +1 -0
- package/dist/panelDiagnosticsView.d.ts +10 -1
- package/dist/panelDiagnosticsView.d.ts.map +1 -1
- package/dist/panelDiagnosticsView.js +167 -58
- package/dist/panelDiagnosticsView.js.map +1 -1
- package/dist/panelDomHelpers.d.ts.map +1 -1
- package/dist/panelDomHelpers.js +6 -5
- package/dist/panelDomHelpers.js.map +1 -1
- package/dist/panelGraphLayout.d.ts +3 -2
- package/dist/panelGraphLayout.d.ts.map +1 -1
- package/dist/panelGraphLayout.js +20 -5
- package/dist/panelGraphLayout.js.map +1 -1
- package/dist/panelGraphRenderer.d.ts +10 -0
- package/dist/panelGraphRenderer.d.ts.map +1 -1
- package/dist/panelGraphRenderer.js.map +1 -1
- package/dist/panelGraphScene.d.ts +15 -26
- package/dist/panelGraphScene.d.ts.map +1 -1
- package/dist/panelGraphScene.js +191 -208
- package/dist/panelGraphScene.js.map +1 -1
- package/dist/panelGraphSearch.d.ts +16 -0
- package/dist/panelGraphSearch.d.ts.map +1 -0
- package/dist/panelGraphSearch.js +93 -0
- package/dist/panelGraphSearch.js.map +1 -0
- package/dist/panelGraphView.d.ts +82 -0
- package/dist/panelGraphView.d.ts.map +1 -1
- package/dist/panelGraphView.js +42 -9
- package/dist/panelGraphView.js.map +1 -1
- package/dist/panelGraphVisuals.d.ts +18 -6
- package/dist/panelGraphVisuals.d.ts.map +1 -1
- package/dist/panelGraphVisuals.js +156 -116
- package/dist/panelGraphVisuals.js.map +1 -1
- package/dist/panelInspectorView.d.ts +13 -0
- package/dist/panelInspectorView.d.ts.map +1 -1
- package/dist/panelInspectorView.js +326 -46
- package/dist/panelInspectorView.js.map +1 -1
- package/dist/panelListStyles.d.ts +3 -0
- package/dist/panelListStyles.d.ts.map +1 -0
- package/dist/panelListStyles.js +155 -0
- package/dist/panelListStyles.js.map +1 -0
- package/dist/panelMemoryStyles.d.ts +3 -0
- package/dist/panelMemoryStyles.d.ts.map +1 -0
- package/dist/panelMemoryStyles.js +42 -0
- package/dist/panelMemoryStyles.js.map +1 -0
- package/dist/panelMemoryView.d.ts +13 -1
- package/dist/panelMemoryView.d.ts.map +1 -1
- package/dist/panelMemoryView.js +161 -41
- package/dist/panelMemoryView.js.map +1 -1
- package/dist/panelPassesView.d.ts +15 -0
- package/dist/panelPassesView.d.ts.map +1 -1
- package/dist/panelPassesView.js +202 -72
- package/dist/panelPassesView.js.map +1 -1
- package/dist/panelRawView.d.ts +15 -0
- package/dist/panelRawView.d.ts.map +1 -0
- package/dist/panelRawView.js +105 -0
- package/dist/panelRawView.js.map +1 -0
- package/dist/panelResourcesView.d.ts +8 -0
- package/dist/panelResourcesView.d.ts.map +1 -1
- package/dist/panelResourcesView.js +62 -31
- package/dist/panelResourcesView.js.map +1 -1
- package/dist/panelSelection.d.ts +9 -1
- package/dist/panelSelection.d.ts.map +1 -1
- package/dist/panelSelection.js +36 -6
- package/dist/panelSelection.js.map +1 -1
- package/dist/panelTheme.d.ts +13 -0
- package/dist/panelTheme.d.ts.map +1 -0
- package/dist/panelTheme.js +104 -0
- package/dist/panelTheme.js.map +1 -0
- package/dist/panelTypes.d.ts +18 -4
- package/dist/panelTypes.d.ts.map +1 -1
- package/dist/panelTypes.js.map +1 -1
- package/dist/panelVisualTheme.d.ts +176 -71
- package/dist/panelVisualTheme.d.ts.map +1 -1
- package/dist/panelVisualTheme.js +34 -36
- package/dist/panelVisualTheme.js.map +1 -1
- package/dist/panelWorkbenchHelpers.d.ts +12 -1
- package/dist/panelWorkbenchHelpers.d.ts.map +1 -1
- package/dist/panelWorkbenchHelpers.js +63 -2
- package/dist/panelWorkbenchHelpers.js.map +1 -1
- package/dist/panelWorkbenchStyles.d.ts +2 -0
- package/dist/panelWorkbenchStyles.d.ts.map +1 -0
- package/dist/panelWorkbenchStyles.js +101 -0
- package/dist/panelWorkbenchStyles.js.map +1 -0
- package/dist/panelWorkbenchView.d.ts +16 -0
- package/dist/panelWorkbenchView.d.ts.map +1 -1
- package/dist/panelWorkbenchView.js +228 -50
- package/dist/panelWorkbenchView.js.map +1 -1
- package/dist/query.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +161 -205
- package/dist/styles.js.map +1 -1
- package/dist/theme.d.ts +22 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +10 -0
- package/dist/theme.js.map +1 -0
- package/dist/themeDefinitions.d.ts +251 -0
- package/dist/themeDefinitions.d.ts.map +1 -0
- package/dist/themeDefinitions.js +96 -0
- package/dist/themeDefinitions.js.map +1 -0
- package/dist/themePalette.d.ts +66 -0
- package/dist/themePalette.d.ts.map +1 -0
- package/dist/themePalette.js +37 -0
- package/dist/themePalette.js.map +1 -0
- package/dist/themes.css +165 -0
- package/package.json +44 -10
- package/src/FrameGraphInspector.ts +145 -68
- package/src/capture.ts +4 -0
- package/src/debugCaptureModel.ts +97 -21
- package/src/index.ts +6 -2
- package/src/panelAliasAnalysis.ts +24 -70
- package/src/panelCytoscapeGraphRenderer.ts +177 -40
- package/src/panelDetailLayout.ts +155 -0
- package/src/panelDetailStyles.ts +56 -0
- package/src/panelDiagnosticsView.ts +167 -86
- package/src/panelDomHelpers.ts +4 -5
- package/src/panelGraphLayout.ts +20 -5
- package/src/panelGraphRenderer.ts +7 -0
- package/src/panelGraphScene.ts +204 -292
- package/src/panelGraphSearch.ts +96 -0
- package/src/panelGraphView.ts +41 -9
- package/src/panelGraphVisuals.ts +152 -118
- package/src/panelInspectorView.ts +315 -48
- package/src/panelListStyles.ts +154 -0
- package/src/panelMemoryStyles.ts +41 -0
- package/src/panelMemoryView.ts +178 -53
- package/src/panelPassesView.ts +190 -137
- package/src/panelRawView.ts +99 -0
- package/src/panelResourcesView.ts +69 -56
- package/src/panelSelection.ts +39 -7
- package/src/panelTheme.ts +85 -0
- package/src/panelTypes.ts +16 -4
- package/src/panelVisualTheme.ts +38 -38
- package/src/panelWorkbenchHelpers.ts +62 -4
- package/src/panelWorkbenchStyles.ts +100 -0
- package/src/panelWorkbenchView.ts +201 -52
- package/src/query.ts +11 -11
- package/src/styles.ts +162 -205
- package/src/theme.ts +24 -0
- package/src/themeDefinitions.ts +103 -0
- package/src/themePalette.ts +44 -0
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
import { labelNode, labelResource } from "./panelDomHelpers.js";
|
|
2
2
|
import { createPanelIcon } from "./panelIcons.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { RawDetailView } from "./panelRawView.js";
|
|
4
|
+
import { resolveNodeSelection, resolveSelectedCanonicalDetail } from "./panelSelection.js";
|
|
5
|
+
import { createRelationButton, formatEstimatedBytes, formatResourceDescriptor, formatTimingCoverage, enableTabKeyboard, writeClipboardText, selectionKey, groupPath, resourceLabel, } from "./panelWorkbenchHelpers.js";
|
|
6
|
+
function formatAccessFacts(access) {
|
|
7
|
+
return [
|
|
8
|
+
access.mode, access.access,
|
|
9
|
+
...(access.mode === 'write' ? [access.contents, access.producesValue ? 'produces a value' : 'does not produce a value'] : []),
|
|
10
|
+
...(access.bufferRange ? [`bytes ${access.bufferRange.offset}–${access.bufferRange.size === undefined ? 'end' : access.bufferRange.offset + access.bufferRange.size}`] : []),
|
|
11
|
+
...(access.textureRegion ? [formatTextureRegion(access.textureRegion)] : []),
|
|
12
|
+
...(access.textureViewId ? [`view ${access.textureViewId}`] : []),
|
|
13
|
+
].join(' · ');
|
|
14
|
+
}
|
|
15
|
+
function formatTextureRegion(region) {
|
|
16
|
+
const end = (start, count) => start === undefined ? 'unknown' : count === undefined ? `${start}–end` : `${start}–${start + count - 1}`;
|
|
17
|
+
return `mip ${end(region.baseMipLevel, region.mipLevelCount)} · ${region.baseDepthSlice === undefined
|
|
18
|
+
? `layers ${end(region.baseArrayLayer, region.arrayLayerCount)}`
|
|
19
|
+
: `depth ${end(region.baseDepthSlice, region.depthSliceCount)}`} · aspect ${region.aspect}`;
|
|
20
|
+
}
|
|
21
|
+
function formatRootRange(root) {
|
|
22
|
+
if (!root.range)
|
|
23
|
+
return 'Range unavailable';
|
|
24
|
+
if (root.range.kind === 'buffer')
|
|
25
|
+
return `bytes ${root.range.offset}–${root.range.offset + root.range.size}`;
|
|
26
|
+
return root.range.regions.map(formatTextureRegion).join('; ');
|
|
27
|
+
}
|
|
5
28
|
export class InspectorView {
|
|
6
29
|
callbacks;
|
|
7
30
|
onOpenChange;
|
|
@@ -13,7 +36,9 @@ export class InspectorView {
|
|
|
13
36
|
snapshot;
|
|
14
37
|
selected;
|
|
15
38
|
activeTab = 'summary';
|
|
16
|
-
open =
|
|
39
|
+
open = false;
|
|
40
|
+
hoveredLink;
|
|
41
|
+
rawCache;
|
|
17
42
|
constructor(callbacks, onOpenChange, idPrefix) {
|
|
18
43
|
this.callbacks = callbacks;
|
|
19
44
|
this.onOpenChange = onOpenChange;
|
|
@@ -46,6 +71,7 @@ export class InspectorView {
|
|
|
46
71
|
this.tabs.set(tab, button);
|
|
47
72
|
this.tabList.appendChild(button);
|
|
48
73
|
}
|
|
74
|
+
enableTabKeyboard(this.tabList);
|
|
49
75
|
this.content.id = `${idPrefix}-inspector-panel`;
|
|
50
76
|
this.content.className = 'zenfg-inspector-inspector-content';
|
|
51
77
|
this.content.setAttribute('role', 'tabpanel');
|
|
@@ -54,20 +80,35 @@ export class InspectorView {
|
|
|
54
80
|
this.render();
|
|
55
81
|
}
|
|
56
82
|
setSnapshot(snapshot) {
|
|
83
|
+
if (this.snapshot !== snapshot)
|
|
84
|
+
this.rawCache = undefined;
|
|
57
85
|
this.snapshot = snapshot;
|
|
58
86
|
this.render();
|
|
59
87
|
}
|
|
60
88
|
setSelection(selected, reveal = true) {
|
|
89
|
+
if (!selected || !this.selected || selectionKey(selected) !== selectionKey(this.selected))
|
|
90
|
+
this.rawCache = undefined;
|
|
91
|
+
if (selected?.kind === 'resource'
|
|
92
|
+
&& (this.selected?.kind !== 'resource' || this.selected.id !== selected.id))
|
|
93
|
+
this.activeTab = 'summary';
|
|
61
94
|
this.selected = selected;
|
|
62
|
-
if (selected
|
|
95
|
+
if (!selected)
|
|
96
|
+
this.setOpen(false);
|
|
97
|
+
else if (reveal && !this.open) {
|
|
63
98
|
this.setOpen(true);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
64
101
|
this.render();
|
|
65
102
|
}
|
|
66
103
|
setOpen(open) {
|
|
67
104
|
if (this.open === open)
|
|
68
105
|
return;
|
|
106
|
+
if (!open)
|
|
107
|
+
this.clearLinkHover();
|
|
69
108
|
this.open = open;
|
|
70
109
|
this.updateOpenState();
|
|
110
|
+
if (open)
|
|
111
|
+
this.render();
|
|
71
112
|
this.onOpenChange(open);
|
|
72
113
|
}
|
|
73
114
|
get isOpen() {
|
|
@@ -78,10 +119,13 @@ export class InspectorView {
|
|
|
78
119
|
this.root.classList.toggle('open', this.open);
|
|
79
120
|
}
|
|
80
121
|
render() {
|
|
122
|
+
const restoreFocus = this.content.contains(document.activeElement);
|
|
123
|
+
this.clearLinkHover();
|
|
81
124
|
for (const [tab, button] of this.tabs) {
|
|
82
125
|
const active = tab === this.activeTab;
|
|
83
126
|
button.classList.toggle('active', active);
|
|
84
127
|
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
128
|
+
button.tabIndex = active ? 0 : -1;
|
|
85
129
|
if (active)
|
|
86
130
|
this.content.setAttribute('aria-labelledby', button.id);
|
|
87
131
|
}
|
|
@@ -97,33 +141,59 @@ export class InspectorView {
|
|
|
97
141
|
return;
|
|
98
142
|
}
|
|
99
143
|
this.title.textContent = this.selectionTitle(snapshot, selected);
|
|
144
|
+
if (!this.open)
|
|
145
|
+
return;
|
|
100
146
|
if (this.activeTab === 'raw') {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
147
|
+
const key = selectionKey(selected);
|
|
148
|
+
if (this.rawCache?.snapshot !== snapshot || this.rawCache.key !== key) {
|
|
149
|
+
const detail = resolveSelectedCanonicalDetail(snapshot, selected);
|
|
150
|
+
this.rawCache = detail ? { snapshot, key, view: new RawDetailView(detail, Boolean(snapshot.protocol.capture.migration)) } : undefined;
|
|
151
|
+
}
|
|
152
|
+
if (this.rawCache)
|
|
153
|
+
this.content.appendChild(this.rawCache.view.root);
|
|
154
|
+
else
|
|
155
|
+
this.content.textContent = 'Canonical object unavailable in this capture.';
|
|
105
156
|
}
|
|
106
157
|
else if (this.activeTab === 'relations') {
|
|
107
158
|
this.content.appendChild(this.createRelations(snapshot, selected));
|
|
108
159
|
}
|
|
109
160
|
else {
|
|
110
|
-
this.content.
|
|
161
|
+
this.content.append(this.createLocationActions(snapshot, selected), this.createSummary(snapshot, selected));
|
|
162
|
+
const identity = this.selectionId(snapshot, selected);
|
|
163
|
+
if (identity)
|
|
164
|
+
this.content.appendChild(this.summary([['ID', this.copyId(identity)]]));
|
|
165
|
+
const diagnostics = this.createDiagnostics(snapshot, selected);
|
|
166
|
+
if (diagnostics)
|
|
167
|
+
this.content.appendChild(diagnostics);
|
|
168
|
+
if (selected.kind === 'node' || selected.kind === 'culled') {
|
|
169
|
+
const node = snapshot.nodeById.get(selected.id) ?? snapshot.culledById.get(selected.id)?.node;
|
|
170
|
+
if (node) {
|
|
171
|
+
const accesses = document.createElement('details');
|
|
172
|
+
const heading = document.createElement('summary');
|
|
173
|
+
heading.textContent = `Access facts · ${node.reads.length + node.writes.length}`;
|
|
174
|
+
accesses.append(heading, this.accessRelations('Reads', node.reads), this.accessRelations('Writes', node.writes));
|
|
175
|
+
this.content.appendChild(accesses);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
111
178
|
}
|
|
179
|
+
// Selecting a relation replaces its button. Keep keyboard focus in the pane.
|
|
180
|
+
if (restoreFocus)
|
|
181
|
+
this.tabs.get(this.activeTab)?.focus();
|
|
112
182
|
}
|
|
113
183
|
selectionTitle(snapshot, selection) {
|
|
114
184
|
switch (selection.kind) {
|
|
115
185
|
case 'node': {
|
|
116
186
|
const node = snapshot.nodeById.get(selection.id);
|
|
117
|
-
return node ? labelNode(node) :
|
|
187
|
+
return node ? labelNode(node) : selection.id;
|
|
118
188
|
}
|
|
119
189
|
case 'group': return snapshot.groupByPathKey.get(selection.pathKey)?.label ?? 'Group';
|
|
120
190
|
case 'resource': {
|
|
121
191
|
const resource = snapshot.resourceById.get(selection.id);
|
|
122
|
-
return resource ? labelResource(resource) :
|
|
192
|
+
return resource ? labelResource(resource) : selection.id;
|
|
123
193
|
}
|
|
124
|
-
case 'allocation': return `Allocation
|
|
125
|
-
case 'root': return
|
|
126
|
-
case 'culled': return
|
|
194
|
+
case 'allocation': return /^allocation[:#-]/i.test(selection.id) ? selection.id : `Allocation ${selection.id}`;
|
|
195
|
+
case 'root': return 'Output root';
|
|
196
|
+
case 'culled': return `${snapshot.culledById.get(selection.id)?.node.label ?? selection.id} (culled)`;
|
|
127
197
|
case 'segment': return `Segment #${selection.index}`;
|
|
128
198
|
}
|
|
129
199
|
}
|
|
@@ -134,12 +204,21 @@ export class InspectorView {
|
|
|
134
204
|
if (!node)
|
|
135
205
|
return this.summary([]);
|
|
136
206
|
const segment = snapshot.segmentByNodeId.get(node.id);
|
|
207
|
+
const roots = snapshot.roots.filter((root) => root.nodeId === node.id || root.resolution?.producerNodeIds.includes(node.id));
|
|
137
208
|
return this.summary([
|
|
209
|
+
['Compile state', 'Retained · compiler included this pass; execution success is not implied'],
|
|
210
|
+
['Retention', roots.length ? roots.map((root) => root.reason).join(', ') : 'No direct output root recorded; inspect dependencies in Relations'],
|
|
138
211
|
['Kind', node.kind],
|
|
139
|
-
['
|
|
212
|
+
['Execution slot', String(node.order)],
|
|
140
213
|
['Group', groupPath(snapshot, node.debugGroupId)],
|
|
141
214
|
['Segment', segment ? `#${segment.index} ${segment.kind}` : '-'],
|
|
142
|
-
['
|
|
215
|
+
['CPU duration', node.cpuDurationMicros === undefined ? 'Not collected' : (node.cpuDurationMicros / 1000).toFixed(3) + ' ms'],
|
|
216
|
+
['CPU scope', 'Synchronous elapsed time; zero may reflect clock precision.' + (node.kind === 'external-submission' ? ' Includes synchronous external submission.' : '')],
|
|
217
|
+
['GPU duration', node.kind === 'external-submission' ? 'Opaque · external work is not measured'
|
|
218
|
+
: node.kind !== 'render' && node.kind !== 'compute' ? 'Not applicable · this pass kind is not timing eligible'
|
|
219
|
+
: node.gpuDurationMicros === undefined ? `Not collected${snapshot.profiling.status === 'unavailable' ? ` · ${snapshot.profiling.reason}` : ''}`
|
|
220
|
+
: `${(node.gpuDurationMicros / 1000).toFixed(3)} ms`],
|
|
221
|
+
['GPU scope', 'GPU timestamp interval for render/compute passes; excludes external work. Zero may reflect timestamp precision.'],
|
|
143
222
|
['Accesses', `${node.reads.length} reads · ${node.writes.length} writes`],
|
|
144
223
|
['Side effect', node.sideEffect ? 'yes' : 'no'],
|
|
145
224
|
]);
|
|
@@ -152,9 +231,12 @@ export class InspectorView {
|
|
|
152
231
|
['Path', group.path.join(' / ')],
|
|
153
232
|
['Retained', String(group.summary.retainedNodeCount)],
|
|
154
233
|
['Culled', String(group.summary.culledNodeCount)],
|
|
155
|
-
['
|
|
156
|
-
['
|
|
157
|
-
['
|
|
234
|
+
['CPU coverage', formatTimingCoverage(group.summary.cpuTimedNodeCount, group.summary.retainedNodeCount)],
|
|
235
|
+
['CPU pass sum', group.summary.cpuTimedNodeCount ? (group.summary.cpuWorkDurationMicros / 1000).toFixed(3) + ' ms' : 'Not collected'],
|
|
236
|
+
['GPU coverage', formatTimingCoverage(group.summary.timedNodeCount, group.summary.timingEligibleNodeCount)],
|
|
237
|
+
['GPU pass sum', group.summary.timingEligibleNodeCount === 0 ? 'Not applicable' : group.summary.timedNodeCount === 0 ? 'Not collected' : (group.summary.gpuWorkDurationMicros / 1000).toFixed(3) + ' ms'],
|
|
238
|
+
['Opaque passes', `${group.summary.externalSubmissionCount} · excluded from GPU pass sum`],
|
|
239
|
+
['Allocations', snapshot.protocol.memory.allocationReport.status === 'available' ? String(group.summary.physicalAllocationCount) : 'Unknown · allocation report unavailable'],
|
|
158
240
|
['Segments', String(group.summary.executionSegmentCount)],
|
|
159
241
|
]);
|
|
160
242
|
}
|
|
@@ -166,9 +248,14 @@ export class InspectorView {
|
|
|
166
248
|
['Kind / origin', `${resource.kind} · ${resource.origin}`],
|
|
167
249
|
['Group', groupPath(snapshot, resource.debugGroupId)],
|
|
168
250
|
['Descriptor', formatResourceDescriptor(resource)],
|
|
169
|
-
['
|
|
170
|
-
['Lifetime', resource.lifetime ?
|
|
171
|
-
['Allocation', resource.physicalResourceId
|
|
251
|
+
['Resource estimate', `${formatEstimatedBytes(resource.estimatedByteSize)} · logical descriptor estimate`],
|
|
252
|
+
['Lifetime', resource.lifetime ? `execution slots ${resource.lifetime.firstUse}–${resource.lifetime.lastUse} (inclusive)` : 'Unavailable'],
|
|
253
|
+
['Allocation', resource.physicalResourceId !== undefined
|
|
254
|
+
? this.createDetailLink(resource.physicalResourceId, { kind: 'allocation', id: resource.physicalResourceId })
|
|
255
|
+
: resource.origin !== 'transient' ? 'Not applicable · externally owned resource'
|
|
256
|
+
: snapshot.protocol.memory.allocationReport.status === 'unavailable' ? 'Unavailable · allocation report not captured'
|
|
257
|
+
: 'Unallocated'],
|
|
258
|
+
['Initial contents', resource.initialContents ?? 'Unknown'],
|
|
172
259
|
['Usage', resource.usageFlags.join(', ') || '-'],
|
|
173
260
|
]);
|
|
174
261
|
}
|
|
@@ -179,26 +266,33 @@ export class InspectorView {
|
|
|
179
266
|
return this.summary([
|
|
180
267
|
['Kind', allocation.kind],
|
|
181
268
|
['Compatibility class', String(allocation.compatibilityClassId)],
|
|
182
|
-
['
|
|
269
|
+
['Physical estimate', `${formatEstimatedBytes(allocation.estimatedByteSize)} · allocation capacity estimate`],
|
|
183
270
|
['Logical resources', String(allocation.resourceIds.length)],
|
|
184
271
|
['Alias', allocation.resourceIds.length > 1 ? `yes · ×${allocation.resourceIds.length}` : 'single'],
|
|
185
272
|
]);
|
|
186
273
|
}
|
|
187
274
|
case 'root': {
|
|
188
|
-
const root = snapshot.roots
|
|
275
|
+
const root = snapshot.roots.find((root) => root.key === selection.key);
|
|
189
276
|
return this.summary(root ? [
|
|
190
277
|
['Reason', root.reason],
|
|
191
|
-
['Node', root.nodeId === undefined ? '
|
|
192
|
-
['Resource', root.resource ?
|
|
278
|
+
['Node', root.nodeId === undefined ? 'Not applicable' : this.nodeLink(snapshot, root.nodeId)],
|
|
279
|
+
['Resource', root.resource ? this.viewResourceButton(snapshot, root.resource.id) : '-'],
|
|
280
|
+
...(root.reason === 'side-effect' ? [] : [
|
|
281
|
+
['Range', root.range ? formatRootRange(root) : 'Unavailable in Legacy capture'],
|
|
282
|
+
['Initial contents', root.resolution ? String(root.resolution.usesInitialContents) : 'Unavailable in Legacy capture'],
|
|
283
|
+
['Producers', root.resolution ? root.resolution.producerNodeIds.join(', ') || 'None' : 'Unavailable in Legacy capture'],
|
|
284
|
+
]),
|
|
193
285
|
] : []);
|
|
194
286
|
}
|
|
195
287
|
case 'culled': {
|
|
196
|
-
const culled = snapshot.
|
|
288
|
+
const culled = snapshot.culledById.get(selection.id);
|
|
197
289
|
return this.summary(culled ? [
|
|
290
|
+
['Compile state', 'Culled · excluded by the compiler'],
|
|
198
291
|
['Node', labelNode(culled.node)],
|
|
199
292
|
['Kind', culled.node.kind],
|
|
200
293
|
['Group', groupPath(snapshot, culled.node.debugGroupId)],
|
|
201
294
|
['Reason', culled.reason],
|
|
295
|
+
['Execution / segment / CPU / GPU', 'Not applicable · culled passes do not execute'],
|
|
202
296
|
['Accesses', `${culled.node.reads.length} reads · ${culled.node.writes.length} writes`],
|
|
203
297
|
] : []);
|
|
204
298
|
}
|
|
@@ -217,6 +311,7 @@ export class InspectorView {
|
|
|
217
311
|
createRelations(snapshot, selection) {
|
|
218
312
|
const host = document.createElement('div');
|
|
219
313
|
host.className = 'zenfg-inspector-inspector-relations';
|
|
314
|
+
host.appendChild(this.createLocationActions(snapshot, selection));
|
|
220
315
|
switch (selection.kind) {
|
|
221
316
|
case 'node': {
|
|
222
317
|
const node = snapshot.nodeById.get(selection.id);
|
|
@@ -227,13 +322,31 @@ export class InspectorView {
|
|
|
227
322
|
host.appendChild(this.relationGroup('Segment', [
|
|
228
323
|
[`#${segment.index} ${segment.kind}`, { kind: 'segment', index: segment.index }],
|
|
229
324
|
]));
|
|
325
|
+
for (const kind of ['value', 'ordering']) {
|
|
326
|
+
const relations = [];
|
|
327
|
+
for (const edge of snapshot.edges) {
|
|
328
|
+
if (edge.kind !== kind || (edge.fromNodeId !== node.id && edge.toNodeId !== node.id))
|
|
329
|
+
continue;
|
|
330
|
+
const upstream = edge.toNodeId === node.id;
|
|
331
|
+
const otherId = upstream ? edge.fromNodeId : edge.toNodeId;
|
|
332
|
+
const other = resolveNodeSelection(snapshot, otherId);
|
|
333
|
+
if (other)
|
|
334
|
+
relations.push([this.accessNodeLabel(snapshot, otherId), other,
|
|
335
|
+
`${upstream ? 'Upstream' : 'Downstream'} · ${resourceLabel(snapshot, edge.resource.id)} · ${kind} dependency`]);
|
|
336
|
+
}
|
|
337
|
+
if (relations.length)
|
|
338
|
+
host.appendChild(this.relationGroup(kind === 'value' ? 'Value dependencies' : 'Ordering dependencies', relations));
|
|
339
|
+
}
|
|
230
340
|
}
|
|
231
341
|
break;
|
|
232
342
|
}
|
|
233
343
|
case 'group': {
|
|
234
344
|
const group = snapshot.groupByPathKey.get(selection.pathKey);
|
|
235
345
|
if (group) {
|
|
236
|
-
host.append(this.resourceRelations('Inputs', group.summary.inputResources), this.resourceRelations('Outputs', group.summary.outputResources))
|
|
346
|
+
host.append(this.resourceRelations('Inputs', group.summary.inputResources), this.resourceRelations('Outputs', group.summary.outputResources), this.relationGroup('Output roots', group.summary.outputRoots.map((root) => [
|
|
347
|
+
`${root.resource ? labelResource(root.resource) : '-'} · ${root.reason} · ${formatRootRange(root)}`,
|
|
348
|
+
{ kind: 'root', key: root.key },
|
|
349
|
+
])));
|
|
237
350
|
}
|
|
238
351
|
break;
|
|
239
352
|
}
|
|
@@ -245,14 +358,20 @@ export class InspectorView {
|
|
|
245
358
|
const nodeSelection = this.accessNodeSelection(snapshot, access.nodeId);
|
|
246
359
|
if (nodeSelection)
|
|
247
360
|
passRelations.push([
|
|
248
|
-
|
|
361
|
+
this.accessNodeLabel(snapshot, access.nodeId),
|
|
249
362
|
nodeSelection,
|
|
363
|
+
formatAccessFacts(access),
|
|
250
364
|
]);
|
|
251
365
|
}
|
|
252
366
|
host.appendChild(this.relationGroup('Pass accesses', passRelations));
|
|
367
|
+
const roots = snapshot.roots.filter((root) => root.resource?.id === selection.id);
|
|
368
|
+
if (roots.length)
|
|
369
|
+
host.appendChild(this.relationGroup('Output roots', roots.map((root) => [
|
|
370
|
+
`${root.reason} · ${formatRootRange(root)}`, { kind: 'root', key: root.key },
|
|
371
|
+
])));
|
|
253
372
|
if (resource?.physicalResourceId !== undefined)
|
|
254
373
|
host.appendChild(this.relationGroup('Allocation', [
|
|
255
|
-
[
|
|
374
|
+
[resource.physicalResourceId, { kind: 'allocation', id: resource.physicalResourceId }],
|
|
256
375
|
]));
|
|
257
376
|
break;
|
|
258
377
|
}
|
|
@@ -265,19 +384,23 @@ export class InspectorView {
|
|
|
265
384
|
break;
|
|
266
385
|
}
|
|
267
386
|
case 'root': {
|
|
268
|
-
const root = snapshot.roots
|
|
387
|
+
const root = snapshot.roots.find((root) => root.key === selection.key);
|
|
269
388
|
const relations = [];
|
|
270
389
|
if (root?.nodeId !== undefined && snapshot.nodeById.has(root.nodeId))
|
|
271
390
|
relations.push([
|
|
272
391
|
this.accessNodeLabel(snapshot, root.nodeId), { kind: 'node', id: root.nodeId },
|
|
273
392
|
]);
|
|
274
393
|
if (root?.resource)
|
|
275
|
-
|
|
276
|
-
|
|
394
|
+
host.appendChild(this.relationGroup('Resource', [[labelResource(root.resource), { kind: 'resource', id: root.resource.id }]]));
|
|
395
|
+
if (root?.resource && root.resolution?.usesInitialContents)
|
|
396
|
+
relations.push([`Initial contents · ${labelResource(root.resource)}`, { kind: 'resource', id: root.resource.id }]);
|
|
397
|
+
for (const id of root?.resolution?.producerNodeIds ?? [])
|
|
398
|
+
relations.push([this.accessNodeLabel(snapshot, id), { kind: 'node', id }]);
|
|
399
|
+
host.appendChild(this.relationGroup('Output sources', relations));
|
|
277
400
|
break;
|
|
278
401
|
}
|
|
279
402
|
case 'culled': {
|
|
280
|
-
const culled = snapshot.
|
|
403
|
+
const culled = snapshot.culledById.get(selection.id);
|
|
281
404
|
if (culled)
|
|
282
405
|
host.append(this.accessRelations('Reads', culled.node.reads), this.accessRelations('Writes', culled.node.writes));
|
|
283
406
|
break;
|
|
@@ -292,7 +415,7 @@ export class InspectorView {
|
|
|
292
415
|
break;
|
|
293
416
|
}
|
|
294
417
|
}
|
|
295
|
-
if (host.childElementCount ===
|
|
418
|
+
if (host.childElementCount === 1) {
|
|
296
419
|
const empty = document.createElement('p');
|
|
297
420
|
empty.className = 'zenfg-inspector-muted';
|
|
298
421
|
empty.textContent = 'No related objects.';
|
|
@@ -302,8 +425,9 @@ export class InspectorView {
|
|
|
302
425
|
}
|
|
303
426
|
accessRelations(title, accesses) {
|
|
304
427
|
return this.relationGroup(title, accesses.map((access) => [
|
|
305
|
-
|
|
428
|
+
labelResource(access.resource),
|
|
306
429
|
{ kind: 'resource', id: access.resource.id },
|
|
430
|
+
formatAccessFacts(access),
|
|
307
431
|
]));
|
|
308
432
|
}
|
|
309
433
|
resourceRelations(title, resources) {
|
|
@@ -316,8 +440,23 @@ export class InspectorView {
|
|
|
316
440
|
const heading = document.createElement('h3');
|
|
317
441
|
heading.textContent = `${title} ${relations.length}`;
|
|
318
442
|
section.appendChild(heading);
|
|
319
|
-
for (const [label, selection] of relations) {
|
|
320
|
-
|
|
443
|
+
for (const [label, selection, description] of relations) {
|
|
444
|
+
const link = this.createDetailLink(label, selection);
|
|
445
|
+
const entry = document.createElement('div');
|
|
446
|
+
entry.className = 'zenfg-inspector-relation-entry';
|
|
447
|
+
const links = document.createElement('div');
|
|
448
|
+
links.className = 'zenfg-inspector-relation-links';
|
|
449
|
+
links.appendChild(link);
|
|
450
|
+
if (this.callbacks.onReveal)
|
|
451
|
+
links.appendChild(this.revealButton(selection, this.primaryPage(selection)));
|
|
452
|
+
entry.appendChild(links);
|
|
453
|
+
if (description !== undefined) {
|
|
454
|
+
const metadata = document.createElement('span');
|
|
455
|
+
metadata.className = 'zenfg-inspector-muted';
|
|
456
|
+
metadata.textContent = description;
|
|
457
|
+
entry.appendChild(metadata);
|
|
458
|
+
}
|
|
459
|
+
section.appendChild(entry);
|
|
321
460
|
}
|
|
322
461
|
if (relations.length === 0) {
|
|
323
462
|
const empty = document.createElement('span');
|
|
@@ -327,6 +466,31 @@ export class InspectorView {
|
|
|
327
466
|
}
|
|
328
467
|
return section;
|
|
329
468
|
}
|
|
469
|
+
selectRelated(selection) {
|
|
470
|
+
this.clearLinkHover();
|
|
471
|
+
this.callbacks.onSelect(selection);
|
|
472
|
+
}
|
|
473
|
+
viewResourceButton(snapshot, resourceId) {
|
|
474
|
+
return this.createDetailLink(`View resource · ${resourceLabel(snapshot, resourceId)}`, { kind: 'resource', id: resourceId });
|
|
475
|
+
}
|
|
476
|
+
createDetailLink(label, selection) {
|
|
477
|
+
const link = createRelationButton(label, selection, (target) => this.selectRelated(target));
|
|
478
|
+
link.addEventListener('mouseenter', () => {
|
|
479
|
+
this.hoveredLink = link;
|
|
480
|
+
this.callbacks.onHover(selection);
|
|
481
|
+
});
|
|
482
|
+
link.addEventListener('mouseleave', () => {
|
|
483
|
+
if (this.hoveredLink === link)
|
|
484
|
+
this.clearLinkHover();
|
|
485
|
+
});
|
|
486
|
+
return link;
|
|
487
|
+
}
|
|
488
|
+
clearLinkHover() {
|
|
489
|
+
if (!this.hoveredLink)
|
|
490
|
+
return;
|
|
491
|
+
this.hoveredLink = undefined;
|
|
492
|
+
this.callbacks.onHover(undefined);
|
|
493
|
+
}
|
|
330
494
|
summary(rows) {
|
|
331
495
|
const summary = document.createElement('dl');
|
|
332
496
|
summary.className = 'zenfg-inspector-inspector-summary';
|
|
@@ -334,8 +498,13 @@ export class InspectorView {
|
|
|
334
498
|
const term = document.createElement('dt');
|
|
335
499
|
term.textContent = label;
|
|
336
500
|
const description = document.createElement('dd');
|
|
337
|
-
|
|
338
|
-
|
|
501
|
+
if (typeof value === 'string') {
|
|
502
|
+
description.textContent = value;
|
|
503
|
+
description.title = value;
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
description.appendChild(value);
|
|
507
|
+
}
|
|
339
508
|
summary.append(term, description);
|
|
340
509
|
}
|
|
341
510
|
return summary;
|
|
@@ -344,14 +513,125 @@ export class InspectorView {
|
|
|
344
513
|
const node = snapshot.nodeById.get(nodeId);
|
|
345
514
|
if (node)
|
|
346
515
|
return labelNode(node);
|
|
347
|
-
const culled = snapshot.
|
|
348
|
-
return culled ? `${labelNode(culled.node)} (culled)` :
|
|
516
|
+
const culled = snapshot.culledById.get(nodeId);
|
|
517
|
+
return culled ? `${labelNode(culled.node)} (culled)` : nodeId;
|
|
349
518
|
}
|
|
350
519
|
accessNodeSelection(snapshot, nodeId) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
520
|
+
return resolveNodeSelection(snapshot, nodeId);
|
|
521
|
+
}
|
|
522
|
+
nodeLink(snapshot, id) {
|
|
523
|
+
const selected = resolveNodeSelection(snapshot, id);
|
|
524
|
+
return selected ? this.createDetailLink(this.accessNodeLabel(snapshot, id), selected) : id;
|
|
525
|
+
}
|
|
526
|
+
primaryPage(selection) {
|
|
527
|
+
switch (selection.kind) {
|
|
528
|
+
case 'node':
|
|
529
|
+
case 'culled':
|
|
530
|
+
case 'group': return 'passes';
|
|
531
|
+
case 'resource': return 'resources';
|
|
532
|
+
case 'allocation': return 'memory';
|
|
533
|
+
case 'root':
|
|
534
|
+
case 'segment': return 'diagnostics';
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
revealButton(selection, page) {
|
|
538
|
+
const button = document.createElement('button');
|
|
539
|
+
button.type = 'button';
|
|
540
|
+
button.className = 'zenfg-inspector-inline-action';
|
|
541
|
+
button.textContent = `Locate in ${page[0].toUpperCase()}${page.slice(1)}`;
|
|
542
|
+
button.addEventListener('click', () => {
|
|
543
|
+
this.clearLinkHover();
|
|
544
|
+
this.callbacks.onReveal?.(selection, page);
|
|
545
|
+
});
|
|
546
|
+
return button;
|
|
547
|
+
}
|
|
548
|
+
createLocationActions(snapshot, selection) {
|
|
549
|
+
const actions = document.createElement('div');
|
|
550
|
+
actions.className = 'zenfg-inspector-detail-actions';
|
|
551
|
+
if (!this.callbacks.onReveal)
|
|
552
|
+
return actions;
|
|
553
|
+
actions.appendChild(this.revealButton(selection, this.primaryPage(selection)));
|
|
554
|
+
if (selection.kind === 'node' || selection.kind === 'group' || selection.kind === 'resource' || selection.kind === 'root') {
|
|
555
|
+
actions.appendChild(this.revealButton(selection, 'graph'));
|
|
556
|
+
}
|
|
557
|
+
if (selection.kind === 'resource' && snapshot.resourceById.get(selection.id)?.origin === 'transient')
|
|
558
|
+
actions.appendChild(this.revealButton(selection, 'memory'));
|
|
559
|
+
if (selection.kind === 'culled') {
|
|
560
|
+
const note = document.createElement('span');
|
|
561
|
+
note.className = 'zenfg-inspector-muted';
|
|
562
|
+
note.textContent = 'Culled passes are absent from Graph. Locate them in Passes.';
|
|
563
|
+
actions.appendChild(note);
|
|
564
|
+
}
|
|
565
|
+
return actions;
|
|
566
|
+
}
|
|
567
|
+
selectionId(snapshot, selection) {
|
|
568
|
+
if ('id' in selection)
|
|
569
|
+
return selection.id;
|
|
570
|
+
if (selection.kind === 'group')
|
|
571
|
+
return snapshot.groupByPathKey.get(selection.pathKey)?.id;
|
|
572
|
+
if (selection.kind === 'segment')
|
|
573
|
+
return snapshot.segmentByIndex.get(selection.index)?.id;
|
|
574
|
+
return undefined;
|
|
575
|
+
}
|
|
576
|
+
copyId(id) {
|
|
577
|
+
const host = document.createElement('span');
|
|
578
|
+
host.className = 'zenfg-inspector-copy-id';
|
|
579
|
+
const code = document.createElement('code');
|
|
580
|
+
code.textContent = id;
|
|
581
|
+
const button = document.createElement('button');
|
|
582
|
+
button.type = 'button';
|
|
583
|
+
button.textContent = 'Copy ID';
|
|
584
|
+
button.setAttribute('aria-label', 'Copy full object ID');
|
|
585
|
+
button.addEventListener('click', () => {
|
|
586
|
+
void writeClipboardText(id).then(() => { button.textContent = 'Copied'; }, () => { button.textContent = 'Copy failed'; });
|
|
587
|
+
});
|
|
588
|
+
host.append(code, button);
|
|
589
|
+
return host;
|
|
590
|
+
}
|
|
591
|
+
createDiagnostics(snapshot, selection) {
|
|
592
|
+
let diagnostics = [];
|
|
593
|
+
if (selection.kind === 'node' || selection.kind === 'culled')
|
|
594
|
+
diagnostics = snapshot.diagnosticsByNodeId.get(selection.id) ?? [];
|
|
595
|
+
else if (selection.kind === 'resource')
|
|
596
|
+
diagnostics = snapshot.diagnosticsByResourceId.get(selection.id) ?? [];
|
|
597
|
+
else if (selection.kind === 'allocation') {
|
|
598
|
+
const resourceIds = new Set(snapshot.allocationById.get(selection.id)?.resourceIds);
|
|
599
|
+
diagnostics = snapshot.protocol.diagnostics.filter((diagnostic) => diagnostic.resourceId !== undefined && resourceIds.has(diagnostic.resourceId));
|
|
600
|
+
}
|
|
601
|
+
else if (selection.kind === 'group') {
|
|
602
|
+
const groupId = snapshot.groupByPathKey.get(selection.pathKey)?.id;
|
|
603
|
+
const groups = new Set(snapshot.debugGroups.filter((group) => groupId !== undefined && group.ancestorIds.includes(groupId)).map((group) => group.id));
|
|
604
|
+
diagnostics = snapshot.protocol.diagnostics.filter((diagnostic) => {
|
|
605
|
+
const nodeGroup = diagnostic.nodeId ? snapshot.canonicalNodeById.get(diagnostic.nodeId)?.groupId : undefined;
|
|
606
|
+
const resourceGroup = diagnostic.resourceId ? snapshot.resourceById.get(diagnostic.resourceId)?.debugGroupId : undefined;
|
|
607
|
+
return (nodeGroup !== undefined && groups.has(nodeGroup)) || (resourceGroup !== undefined && groups.has(resourceGroup));
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
if (!diagnostics.length)
|
|
611
|
+
return undefined;
|
|
612
|
+
const section = document.createElement('section');
|
|
613
|
+
section.className = 'zenfg-inspector-detail-diagnostics';
|
|
614
|
+
const heading = document.createElement('h3');
|
|
615
|
+
heading.textContent = `Related diagnostics ${diagnostics.length}`;
|
|
616
|
+
section.appendChild(heading);
|
|
617
|
+
for (const diagnostic of diagnostics) {
|
|
618
|
+
const entry = document.createElement('article');
|
|
619
|
+
entry.dataset.severity = diagnostic.severity;
|
|
620
|
+
const label = document.createElement('strong');
|
|
621
|
+
label.textContent = `${diagnostic.severity} · ${diagnostic.code}`;
|
|
622
|
+
const message = document.createElement('p');
|
|
623
|
+
message.textContent = diagnostic.message;
|
|
624
|
+
entry.append(label, message);
|
|
625
|
+
if (diagnostic.nodeId) {
|
|
626
|
+
const node = resolveNodeSelection(snapshot, diagnostic.nodeId);
|
|
627
|
+
if (node)
|
|
628
|
+
entry.appendChild(this.createDetailLink(this.accessNodeLabel(snapshot, diagnostic.nodeId), node));
|
|
629
|
+
}
|
|
630
|
+
if (diagnostic.resourceId)
|
|
631
|
+
entry.appendChild(this.viewResourceButton(snapshot, diagnostic.resourceId));
|
|
632
|
+
section.appendChild(entry);
|
|
633
|
+
}
|
|
634
|
+
return section;
|
|
355
635
|
}
|
|
356
636
|
}
|
|
357
637
|
//# sourceMappingURL=panelInspectorView.js.map
|