From 28a19cd0a67c8c594ee1424bec8cc1bc189240e0 Mon Sep 17 00:00:00 2001 From: Alex Denford SBL Date: Tue, 4 Aug 2026 17:41:02 -0700 Subject: [PATCH 1/3] implementation --- icons/button_disabled.png | Bin 0 -> 4460 bytes icons/button_enabled.png | Bin 0 -> 4130 bytes src/extension.ts | 4 +- src/panels/minecraft-diagnostics.ts | 60 ++++++- src/protocol-events.ts | 14 +- src/session.ts | 38 +++++ webview-ui/src/diagnostics_panel/App.css | 39 +++++ webview-ui/src/diagnostics_panel/App.tsx | 177 ++++++++++++++------- webview-ui/src/diagnostics_panel/Icons.tsx | 9 ++ webview-ui/vite.config.ts | 3 + 10 files changed, 277 insertions(+), 67 deletions(-) create mode 100644 icons/button_disabled.png create mode 100644 icons/button_enabled.png diff --git a/icons/button_disabled.png b/icons/button_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..c8c64a16059f2374f8bf06e1590de1dd6ccc32d0 GIT binary patch literal 4460 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 { - MinecraftDiagnosticsPanel.render(context.extensionUri, liveStatsProvider, eventEmitter); + MinecraftDiagnosticsPanel.render(context.extensionUri, liveStatsProvider, eventEmitter, context.globalState); }); const replayDiagnosticsCommand = vscode.commands.registerCommand( @@ -98,7 +98,7 @@ export function activate(context: vscode.ExtensionContext): void { return; } const replayStats = new ReplayStatsProvider(fileUri[0].fsPath); - MinecraftDiagnosticsPanel.render(context.extensionUri, replayStats, eventEmitter); + MinecraftDiagnosticsPanel.render(context.extensionUri, replayStats, eventEmitter, context.globalState); }, ); diff --git a/src/panels/minecraft-diagnostics.ts b/src/panels/minecraft-diagnostics.ts index b680d19d..5bce9493 100644 --- a/src/panels/minecraft-diagnostics.ts +++ b/src/panels/minecraft-diagnostics.ts @@ -1,6 +1,6 @@ // Copyright (C) Microsoft Corporation. All rights reserved. -import { Disposable, Webview, WebviewPanel, window, workspace, Uri, ViewColumn } from 'vscode'; +import { Disposable, Memento, Webview, WebviewPanel, window, workspace, Uri, ViewColumn } from 'vscode'; import { EventEmitter } from 'stream'; import { getUri } from '../utilities/getUri'; import { getNonce } from '../utilities/getNonce'; @@ -8,6 +8,9 @@ import { DebuggerRequestHandler } from '../requests/debugger-request-handler'; import { StatData, StatsListener, StatsProvider } from '../stats/stats-provider'; import { DiagnosticsTabDescriptor } from '../diagnostics-schema'; +const DIAGNOSTICS_TAB_STATES_KEY = 'minecraftDiagnosticsTabStates'; +type DiagnosticsTabStates = Record; + export class MinecraftDiagnosticsPanel { private static activeDiagnosticsPanels: MinecraftDiagnosticsPanel[] = []; @@ -24,6 +27,7 @@ export class MinecraftDiagnosticsPanel { statsTracker: StatsProvider, eventEmitter: EventEmitter, debuggerRequestHandler: DebuggerRequestHandler, + private readonly _globalState: Memento, ) { this._panel = panel; this._statsTracker = statsTracker; @@ -39,6 +43,7 @@ export class MinecraftDiagnosticsPanel { this._panel.webview, extensionUri, statsTracker.manualControl(), + this.getDiagnosticsTabStates(), ); // Handle events from the webview panel @@ -50,6 +55,7 @@ export class MinecraftDiagnosticsPanel { this._panel.webview, extensionUri, statsTracker.manualControl(), + this.getDiagnosticsTabStates(), ); break; case 'pause': @@ -75,6 +81,12 @@ export class MinecraftDiagnosticsPanel { case 'debugger-request': this._debuggerRequestHandler.handleDebuggerRequest(message.request, message.args); break; + case 'set-diagnostics-active': + this.handleDiagnosticsActiveMessage(message); + break; + case 'sync-diagnostics-tabs': + this._eventEmitter.emit('sync-diagnostics-tabs', message.states); + break; case 'export-data': void this.handleExportDataMessage(message); break; @@ -132,6 +144,33 @@ export class MinecraftDiagnosticsPanel { this._statsTracker.addStatListener(this._statsCallback); } + private getDiagnosticsTabStates(): DiagnosticsTabStates { + const states = this._globalState.get(DIAGNOSTICS_TAB_STATES_KEY, {}); + if (states === null || typeof states !== 'object' || Array.isArray(states)) { + return {}; + } + + return Object.fromEntries( + Object.entries(states).filter(([tabName, active]) => tabName.trim() !== '' && typeof active === 'boolean'), + ); + } + + private handleDiagnosticsActiveMessage(message: any): void { + if (typeof message.tabName !== 'string' || message.tabName.trim() === '' || typeof message.active !== 'boolean') { + return; + } + + const states = this.getDiagnosticsTabStates(); + states[message.tabName] = message.active; + void this._globalState.update(DIAGNOSTICS_TAB_STATES_KEY, states); + this._eventEmitter.emit('set-diagnostics-active', message.tabName, message.active); + this._panel.webview.postMessage({ + type: 'diagnostics-tab-state', + tabName: message.tabName, + active: message.active, + }); + } + private async handleExportDataMessage(message: any): Promise { if (typeof message.content !== 'string') { console.error('Received export-data message without a valid content string.'); @@ -161,7 +200,12 @@ export class MinecraftDiagnosticsPanel { window.showInformationMessage(`Exported diagnostics data to ${outputUri.fsPath}.`); } - public static render(extensionUri: Uri, statsTracker: StatsProvider, eventEmitter: EventEmitter): void { + public static render( + extensionUri: Uri, + statsTracker: StatsProvider, + eventEmitter: EventEmitter, + globalState: Memento, + ): void { const statsTrackerId = statsTracker.uniqueId; const existingPanel = MinecraftDiagnosticsPanel.activeDiagnosticsPanels.find( panel => panel._statsTracker.uniqueId === statsTrackerId, @@ -189,6 +233,7 @@ export class MinecraftDiagnosticsPanel { statsTracker, eventEmitter, new DebuggerRequestHandler(panel.webview), + globalState, ), ); } @@ -217,7 +262,12 @@ export class MinecraftDiagnosticsPanel { } } - private _getWebviewContent(webview: Webview, extensionUri: Uri, showReplayControls: boolean) { + private _getWebviewContent( + webview: Webview, + extensionUri: Uri, + showReplayControls: boolean, + diagnosticsTabStates: DiagnosticsTabStates, + ) { // The CSS file from the React build output const stylesUri = getUri(webview, extensionUri, ['webview-ui', 'build', 'assets', 'diagnosticsPanel.css']); // The JS file from the React build output @@ -231,11 +281,11 @@ export class MinecraftDiagnosticsPanel { - + Minecraft Diagnostics diff --git a/src/protocol-events.ts b/src/protocol-events.ts index abba5f76..150e945e 100644 --- a/src/protocol-events.ts +++ b/src/protocol-events.ts @@ -16,6 +16,7 @@ import { DiagnosticsTabDescriptor } from './diagnostics-schema'; // 8 - New serialization tech (use Cereal) // 9 - Added support for MC C++/native driven stat descriptors/schemas for UI display // 10 - Added is_empty_tab to DiagnosticsTabDescriptor +// 11 - Added per-tab diagnostics activation export enum ProtocolVersion { _Unknown = 0, @@ -29,9 +30,10 @@ export enum ProtocolVersion { SupportCerealSerialization = 8, SupportNativeDescriptors = 9, SupportEmptyTabs = 10, + SupportDiagnosticsSetActive = 11, } -export const DEBUGGER_PROTOCOL_VERSION = ProtocolVersion.SupportEmptyTabs; +export const DEBUGGER_PROTOCOL_VERSION = ProtocolVersion.SupportDiagnosticsSetActive; // ------------------------------------------------------------------------- // Interfaces for event message payloads (received from the debugee) @@ -131,7 +133,8 @@ export enum OutgoingEventType { Resume = 'resume', Request = 'request', Breakpoints = 'breakpoints', - DebuggerRequest = 'debugger-request' + DebuggerRequest = 'debugger-request', + DiagnosticsSetActive = 'diagnostics-set-active', } export interface ProtocolResponse { @@ -182,6 +185,12 @@ export interface ResumeMessage { type: OutgoingEventType.Resume; } +export interface DiagnosticsSetActiveMessage { + type: OutgoingEventType.DiagnosticsSetActive; + tab_name: string; + active: boolean; +} + export interface RequestMessage { type: OutgoingEventType.Request; request: { request_seq: number; command: string; args: unknown }; @@ -224,6 +233,7 @@ export type OutgoingDebuggeeMessage = | StopProfilerMessage | StopOnExceptionMessage | ResumeMessage + | DiagnosticsSetActiveMessage | RequestMessage | BreakpointsLegacyMessage | BreakpointsMessage diff --git a/src/session.ts b/src/session.ts index d06705af..a23b92d8 100644 --- a/src/session.ts +++ b/src/session.ts @@ -172,6 +172,8 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { this._eventEmitter.on('start-profiler', this.onStartProfiler.bind(this)); this._eventEmitter.on('stop-profiler', this.onStopProfiler.bind(this)); this._eventEmitter.on('request-debugger-status', this.onRequestDebuggerStatus.bind(this)); + this._eventEmitter.on('set-diagnostics-active', this.onSetDiagnosticsActive.bind(this)); + this._eventEmitter.on('sync-diagnostics-tabs', this.onSyncDiagnosticsTabs.bind(this)); } // Use this to register new events that are handled from the debugee (Minecraft) @@ -212,6 +214,8 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { this._eventEmitter.removeAllListeners('start-profiler'); this._eventEmitter.removeAllListeners('stop-profiler'); this._eventEmitter.removeAllListeners('request-debugger-status'); + this._eventEmitter.removeAllListeners('set-diagnostics-active'); + this._eventEmitter.removeAllListeners('sync-diagnostics-tabs'); if (this._sourceFileWatcher) { this._sourceFileWatcher.dispose(); @@ -277,6 +281,40 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { } } + private onSetDiagnosticsActive(tabName: unknown, active: unknown): void { + if (typeof tabName !== 'string' || tabName.trim() === '' || typeof active !== 'boolean') { + return; + } + + this.sendDiagnosticsSetActive(tabName, active); + } + + private onSyncDiagnosticsTabs(tabStates: unknown): void { + if (tabStates === null || typeof tabStates !== 'object' || Array.isArray(tabStates)) { + return; + } + + for (const [tabName, active] of Object.entries(tabStates)) { + if (typeof active !== 'boolean' || tabName.trim() === '') { + continue; + } + + this.sendDiagnosticsSetActive(tabName, active); + } + } + + private sendDiagnosticsSetActive(tabName: string, active: boolean): void { + if (this._clientProtocolVersion < ProtocolVersion.SupportDiagnosticsSetActive) { + return; + } + + this.sendDebuggeeMessage({ + type: OutgoingEventType.DiagnosticsSetActive, + tab_name: tabName, + active, + }); + } + private writeProfilerCaptureToFile( captureData: string, capturePath: string, diff --git a/webview-ui/src/diagnostics_panel/App.css b/webview-ui/src/diagnostics_panel/App.css index 4818ea50..1f1a737a 100644 --- a/webview-ui/src/diagnostics_panel/App.css +++ b/webview-ui/src/diagnostics_panel/App.css @@ -23,6 +23,11 @@ main { border-right: 1px solid var(--vscode-editorGroup-border); } +.vertical-tab-row { + display: flex; + align-items: stretch; +} + .vertical-tab-item { background: transparent; border: none; @@ -35,6 +40,32 @@ main { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + flex: 1; +} + +.diagnostics-tab-toggle { + border: none; + border-left: 1px solid var(--vscode-editorGroup-border); + background: transparent; + color: var(--vscode-descriptionForeground); + cursor: pointer; + font-size: 10px; + min-width: 34px; +} + +.diagnostics-tab-toggle:hover:not(:disabled) { + background-color: var(--vscode-list-hoverBackground); + color: var(--vscode-list-hoverForeground); +} + +.diagnostics-tab-toggle:disabled { + cursor: wait; + opacity: 0.7; +} + +.diagnostics-tab-disabled { + color: var(--vscode-descriptionForeground); + padding: 24px; } .vertical-tab-item:hover { @@ -42,6 +73,14 @@ main { color: var(--vscode-list-hoverForeground); } +.vertical-tab-item.disabled { + color: var(--vscode-descriptionForeground); +} + +.vertical-tab-item.disabled:hover { + color: var(--vscode-descriptionForeground); +} + .vertical-tab-item.active { background-color: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); diff --git a/webview-ui/src/diagnostics_panel/App.tsx b/webview-ui/src/diagnostics_panel/App.tsx index eaa23ac4..7e8ebea9 100644 --- a/webview-ui/src/diagnostics_panel/App.tsx +++ b/webview-ui/src/diagnostics_panel/App.tsx @@ -63,9 +63,13 @@ const CLIENT_SELECTION_HELP_TOOLTIP = function App() { const [selectedPlugin, setSelectedPlugin] = useState(''); const [selectedClient, setSelectedClient] = useState(''); - const [currentTab, setCurrentTab] = useState('tab-0'); + const [currentTab, setCurrentTab] = useState(sortedTabPrefabs[0]?.name ?? ''); const [paused, setPaused] = useState(true); const [speed, setSpeed] = useState(''); + const [enabledTabs, setEnabledTabs] = useState>( + window.initialParams.diagnosticsTabStates ?? {}, + ); + const [pendingTabs, setPendingTabs] = useState>({}); // Dynamic schema received from the game on connect. Merged into the prefab tab list. const [schema, setSchema] = useState([]); @@ -88,6 +92,13 @@ function App() { handleDebuggerRequestResult(message); } else if (message.type === 'diagnostics-schema') { setSchema(message.schema as DiagnosticsTabDescriptor[]); + } else if (message.type === 'diagnostics-tab-state' && typeof message.tabName === 'string') { + setEnabledTabs(previous => ({ ...previous, [message.tabName]: message.active === true })); + setPendingTabs(previous => { + const next = { ...previous }; + delete next[message.tabName]; + return next; + }); } }; window.addEventListener('message', handleMessage); @@ -116,6 +127,22 @@ function App() { return merged; }, [schema]); + useEffect(() => { + if (!window.initialParams.showReplayControls) { + const states = Object.fromEntries(mergedTabs.map(tab => [tab.name, enabledTabs[tab.name] === true])); + vscode.postMessage({ type: 'sync-diagnostics-tabs', states }); + } + }, [mergedTabs]); + + const setTabActive = (tabName: string, active: boolean) => { + if (window.initialParams.showReplayControls || pendingTabs[tabName] !== undefined) { + return; + } + + setPendingTabs(previous => ({ ...previous, [tabName]: active })); + vscode.postMessage({ type: 'set-diagnostics-active', tabName, active }); + }; + return (
{window.initialParams.showReplayControls && ( @@ -132,73 +159,107 @@ function App() { )}
- {mergedTabs.map((tab, index) => ( - + {mergedTabs.map(tab => ( +
+ + {!window.initialParams.showReplayControls && ( + + )} +
))}
- {mergedTabs.map((tab, index) => ( + {mergedTabs.map(tab => (
- {tab.kind === 'prefab' ? ( - <> - {tab.tab.dataSource === TabPrefabDataSource.Client ? ( - - ) : ( -
- )} - {tab.tab.dataSource === TabPrefabDataSource.ServerScript ? ( - - ) : ( -
- )} - - - ) : (!tab.descriptor.is_empty_tab && ( - <> - {tab.descriptor.data_source === 'client' && ( - - )} - {tab.descriptor.data_source === 'server_script' && ( - + {tab.tab.dataSource === TabPrefabDataSource.Client ? ( + + ) : ( +
+ )} + {tab.tab.dataSource === TabPrefabDataSource.ServerScript ? ( + + ) : ( +
+ )} + - )} - - - ))} + + ) : ( + !tab.descriptor.is_empty_tab && ( + <> + {tab.descriptor.data_source === 'client' && ( + + )} + {tab.descriptor.data_source === 'server_script' && ( + + )} + + + ) + ) + ) : ( +
+ Enable this tab to start receiving its diagnostics data. +
+ )}
))}
diff --git a/webview-ui/src/diagnostics_panel/Icons.tsx b/webview-ui/src/diagnostics_panel/Icons.tsx index c92a42ce..1df15409 100644 --- a/webview-ui/src/diagnostics_panel/Icons.tsx +++ b/webview-ui/src/diagnostics_panel/Icons.tsx @@ -1,6 +1,15 @@ // Copyright (C) Microsoft Corporation. All rights reserved. +import buttonDisabled from '../../../icons/button_disabled.png'; +import buttonEnabled from '../../../icons/button_enabled.png'; + export const Icons = { + enabled: ( + Enabled + ), + disabled: ( + Disabled + ), restart: ( Date: Fri, 7 Aug 2026 08:48:02 -0700 Subject: [PATCH 2/3] tech --- src/panels/minecraft-diagnostics.ts | 10 ++++- src/protocol-events.ts | 3 +- src/session.ts | 34 ++++++++--------- webview-ui/src/diagnostics_panel/App.tsx | 35 +++++++++++++++--- .../diagnostics_panel/StatisticProvider.tsx | 37 +++++++++++++++---- .../prefabs/StatisticPrefab.ts | 1 + .../diagnostics_panel/prefabs/TabPrefab.ts | 3 ++ .../prefabs/tabs/ServerBandwidth.tsx | 9 ++++- .../prefabs/tabs/ServerMemory.tsx | 16 ++++++-- .../prefabs/tabs/ServerPackets.tsx | 22 +++++++++-- .../prefabs/tabs/ServerTiming.tsx | 10 ++++- .../diagnostics_panel/prefabs/tabs/World.tsx | 14 ++++++- 12 files changed, 144 insertions(+), 50 deletions(-) diff --git a/src/panels/minecraft-diagnostics.ts b/src/panels/minecraft-diagnostics.ts index 5bce9493..eafc0684 100644 --- a/src/panels/minecraft-diagnostics.ts +++ b/src/panels/minecraft-diagnostics.ts @@ -156,14 +156,20 @@ export class MinecraftDiagnosticsPanel { } private handleDiagnosticsActiveMessage(message: any): void { - if (typeof message.tabName !== 'string' || message.tabName.trim() === '' || typeof message.active !== 'boolean') { + if ( + typeof message.tabName !== 'string' || + message.tabName.trim() === '' || + !Array.isArray(message.collectorNames) || + message.collectorNames.some((name: unknown) => typeof name !== 'string' || name.trim() === '') || + typeof message.active !== 'boolean' + ) { return; } const states = this.getDiagnosticsTabStates(); states[message.tabName] = message.active; void this._globalState.update(DIAGNOSTICS_TAB_STATES_KEY, states); - this._eventEmitter.emit('set-diagnostics-active', message.tabName, message.active); + this._eventEmitter.emit('set-diagnostics-active', message.collectorNames, message.active); this._panel.webview.postMessage({ type: 'diagnostics-tab-state', tabName: message.tabName, diff --git a/src/protocol-events.ts b/src/protocol-events.ts index 150e945e..00b9f8a5 100644 --- a/src/protocol-events.ts +++ b/src/protocol-events.ts @@ -187,10 +187,9 @@ export interface ResumeMessage { export interface DiagnosticsSetActiveMessage { type: OutgoingEventType.DiagnosticsSetActive; - tab_name: string; + collector_names: string[]; active: boolean; } - export interface RequestMessage { type: OutgoingEventType.Request; request: { request_seq: number; command: string; args: unknown }; diff --git a/src/session.ts b/src/session.ts index a23b92d8..595732cb 100644 --- a/src/session.ts +++ b/src/session.ts @@ -281,36 +281,34 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { } } - private onSetDiagnosticsActive(tabName: unknown, active: unknown): void { - if (typeof tabName !== 'string' || tabName.trim() === '' || typeof active !== 'boolean') { - return; - } - - this.sendDiagnosticsSetActive(tabName, active); + private onSetDiagnosticsActive(collectorNames: string[], active: boolean): void { + this.sendDiagnosticsSetActive(collectorNames, active); } - private onSyncDiagnosticsTabs(tabStates: unknown): void { - if (tabStates === null || typeof tabStates !== 'object' || Array.isArray(tabStates)) { - return; - } - - for (const [tabName, active] of Object.entries(tabStates)) { - if (typeof active !== 'boolean' || tabName.trim() === '') { - continue; - } + private onSyncDiagnosticsTabs(collectorStates: Record): void { + const activeCollectors = Object.entries(collectorStates) + .filter(([, active]) => active) + .map(([collectorName]) => collectorName); + const inactiveCollectors = Object.entries(collectorStates) + .filter(([, active]) => !active) + .map(([collectorName]) => collectorName); - this.sendDiagnosticsSetActive(tabName, active); + if (activeCollectors.length > 0) { + this.sendDiagnosticsSetActive(activeCollectors, true); + } + if (inactiveCollectors.length > 0) { + this.sendDiagnosticsSetActive(inactiveCollectors, false); } } - private sendDiagnosticsSetActive(tabName: string, active: boolean): void { + private sendDiagnosticsSetActive(collectorNames: string[], active: boolean): void { if (this._clientProtocolVersion < ProtocolVersion.SupportDiagnosticsSetActive) { return; } this.sendDebuggeeMessage({ type: OutgoingEventType.DiagnosticsSetActive, - tab_name: tabName, + collector_names: collectorNames, active, }); } diff --git a/webview-ui/src/diagnostics_panel/App.tsx b/webview-ui/src/diagnostics_panel/App.tsx index 7e8ebea9..20714555 100644 --- a/webview-ui/src/diagnostics_panel/App.tsx +++ b/webview-ui/src/diagnostics_panel/App.tsx @@ -23,8 +23,8 @@ const sortedTabPrefabs = [...tabPrefabs].sort((a, b) => a.name.localeCompare(b.n // A tab entry is either a hardcoded prefab or a dynamic descriptor received from the game. type MergedTab = - | { kind: 'prefab'; name: string; tab: TabPrefab } - | { kind: 'dynamic'; name: string; descriptor: DiagnosticsTabDescriptor }; + | { kind: 'prefab'; name: string; tab: TabPrefab; collectorNames: string[] } + | { kind: 'dynamic'; name: string; descriptor: DiagnosticsTabDescriptor; collectorNames: string[] }; declare global { interface Window { @@ -114,13 +114,24 @@ function App() { kind: 'prefab' as const, name: tab.name, tab, + collectorNames: (tab.collectors?.map(collector => collector.collectorName).filter(collectorName => collectorName !== undefined) ?? []) })); for (const descriptor of schema) { const existingIndex = merged.findIndex(t => t.name === descriptor.name); if (existingIndex !== -1) { - merged[existingIndex] = { kind: 'dynamic' as const, name: descriptor.name, descriptor }; + merged[existingIndex] = { + kind: 'dynamic' as const, + name: descriptor.name, + descriptor, + collectorNames: [descriptor.stat_group_id], + }; } else { - merged.push({ kind: 'dynamic' as const, name: descriptor.name, descriptor }); + merged.push({ + kind: 'dynamic' as const, + name: descriptor.name, + descriptor, + collectorNames: [descriptor.stat_group_id], + }); } } merged.sort((a, b) => a.name.localeCompare(b.name)); @@ -129,7 +140,12 @@ function App() { useEffect(() => { if (!window.initialParams.showReplayControls) { - const states = Object.fromEntries(mergedTabs.map(tab => [tab.name, enabledTabs[tab.name] === true])); + const states: Record = {}; + for (const tab of mergedTabs) { + for (const collectorName of tab.collectorNames) { + states[collectorName] = states[collectorName] === true || enabledTabs[tab.name] === true; + } + } vscode.postMessage({ type: 'sync-diagnostics-tabs', states }); } }, [mergedTabs]); @@ -140,7 +156,14 @@ function App() { } setPendingTabs(previous => ({ ...previous, [tabName]: active })); - vscode.postMessage({ type: 'set-diagnostics-active', tabName, active }); + + const tab = mergedTabs.find(candidate => candidate.name === tabName); + vscode.postMessage({ + type: 'set-diagnostics-active', + tabName, + collectorNames: tab?.collectorNames ?? [], + active, + }); }; return ( diff --git a/webview-ui/src/diagnostics_panel/StatisticProvider.tsx b/webview-ui/src/diagnostics_panel/StatisticProvider.tsx index 6443ba8b..fe9b983a 100644 --- a/webview-ui/src/diagnostics_panel/StatisticProvider.tsx +++ b/webview-ui/src/diagnostics_panel/StatisticProvider.tsx @@ -1,5 +1,7 @@ // Copyright (C) Microsoft Corporation. All rights reserved. +import type { StatisticPrefab } from './prefabs/StatisticPrefab'; + export interface StatisticUpdatedMessage { type: 'statistic-updated'; should_aggregate: boolean; @@ -61,25 +63,38 @@ export class StatisticProvider { interface SimpleStatisticProviderOptions { statisticId: string; - statisticParentId: string | RegExp; + statisticParentId: string | RegExp | StatisticPrefab; } export class SimpleStatisticProvider extends StatisticProvider { constructor(private options: SimpleStatisticProviderOptions) { super(); } + private getStatisticParentId(): string | RegExp | undefined { + if (typeof this.options.statisticParentId === 'object' && !(this.options.statisticParentId instanceof RegExp)) { + return this.options.statisticParentId.collectorName; + } + return this.options.statisticParentId; + } + protected _handleEvent(event: StatisticUpdatedMessage) { + const statisticParentId = this.getStatisticParentId(); + + if (statisticParentId === undefined) { + return; + } + // Check event type if (event.id !== this.options.statisticId) { return; } // Check for wrong group - if (this.options.statisticParentId instanceof RegExp) { - if (!this.options.statisticParentId.test(event.group_full_id)) { + if (statisticParentId instanceof RegExp) { + if (!statisticParentId.test(event.group_full_id)) { return; } - } else if (event.group !== this.options.statisticParentId) { + } else if (event.group !== statisticParentId && !(event.group === '' && event.id === statisticParentId)) { return; } @@ -93,7 +108,7 @@ export class SimpleStatisticProvider extends StatisticProvider { interface MultipleStatisticProviderOptions { statisticIds?: string[]; // If not included, all stats will be included - statisticParentId: string | RegExp; + statisticParentId: string | RegExp | StatisticPrefab; valuesFilter?: (event: StatisticUpdatedMessage) => boolean; } // Used for things like stacked bar charts @@ -103,17 +118,23 @@ export class MultipleStatisticProvider extends StatisticProvider { } protected _handleEvent(event: StatisticUpdatedMessage) { + const statisticParentId = + typeof this.options.statisticParentId === 'object' && + !(this.options.statisticParentId instanceof RegExp) + ? this.options.statisticParentId.collectorName + : this.options.statisticParentId; + // Check event type if (this.options.statisticIds !== undefined && this.options.statisticIds.indexOf(event.id) === -1) { return; } // Check for wrong group - if (this.options.statisticParentId instanceof RegExp) { - if (!this.options.statisticParentId.test(event.group_full_id)) { + if (statisticParentId instanceof RegExp) { + if (!statisticParentId.test(event.group_full_id)) { return; } - } else if (event.group !== this.options.statisticParentId) { + } else if (event.group !== statisticParentId) { return; } diff --git a/webview-ui/src/diagnostics_panel/prefabs/StatisticPrefab.ts b/webview-ui/src/diagnostics_panel/prefabs/StatisticPrefab.ts index bfb7af38..8e4ea09a 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/StatisticPrefab.ts +++ b/webview-ui/src/diagnostics_panel/prefabs/StatisticPrefab.ts @@ -4,5 +4,6 @@ import { ReactNode } from 'react'; export interface StatisticPrefab { name: string; + collectorName: string; reactNode: ReactNode; } diff --git a/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts b/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts index e8cd3c3a..7b0ea092 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts +++ b/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts @@ -1,3 +1,5 @@ +import type { StatisticPrefab } from './StatisticPrefab'; + export type TabPrefabParams = { selectedClient: string; selectedPlugin: string; @@ -13,5 +15,6 @@ export enum TabPrefabDataSource { export interface TabPrefab { name: string; dataSource: TabPrefabDataSource; + collectors?: StatisticPrefab[]; content: (params: TabPrefabParams) => JSX.Element; } diff --git a/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerBandwidth.tsx b/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerBandwidth.tsx index 749087e8..34717b93 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerBandwidth.tsx +++ b/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerBandwidth.tsx @@ -5,8 +5,11 @@ import { StatisticType, YAxisType } from '../../StatisticResolver'; import { TabPrefab, TabPrefabDataSource } from '../TabPrefab'; import { generateRowsFromStatsPrefabs } from '../utilities'; +const PACKETS_COLLECTOR = 'packets'; + const packetDataReceived: StatisticPrefab = { name: 'Packet Data Received', + collectorName: PACKETS_COLLECTOR, reactNode: ( { return generateRowsFromStatsPrefabs([[packetDataReceived, packetDataSent]]); }, diff --git a/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerMemory.tsx b/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerMemory.tsx index 668c13cd..3166451e 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerMemory.tsx +++ b/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerMemory.tsx @@ -5,14 +5,18 @@ import { StatisticType, YAxisType } from '../../StatisticResolver'; import { TabPrefab, TabPrefabDataSource } from '../TabPrefab'; import { generateRowsFromStatsPrefabs } from '../utilities'; +const APP_MEMORY_COLLECTOR = 'app_memory'; +const RUNTIME_MEMORY_COLLECTOR = 'runtime_memory'; + const AppMemoryUsage: StatisticPrefab = { name: 'App Memory Usage', + collectorName: APP_MEMORY_COLLECTOR, reactNode: ( { return generateRowsFromStatsPrefabs([ [AppMemoryUsage, AppMemoryFree], diff --git a/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerPackets.tsx b/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerPackets.tsx index 2ca7b541..eace06d0 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerPackets.tsx +++ b/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerPackets.tsx @@ -6,8 +6,13 @@ import { TabPrefab, TabPrefabDataSource } from '../TabPrefab'; import { generateRowsFromStatsPrefabs } from '../utilities'; import MinecraftStatisticStackedBarChart from '../../controls/MinecraftStatisticStackedBarChart'; +const PACKETS_COLLECTOR = 'packets'; +const NETWORKING_PACKETS_DETAILS_COLLECTOR = 'networking_packets_details'; +const NETWORKING_PACKETS_DETAILS_PATTERN = new RegExp(`${NETWORKING_PACKETS_DETAILS_COLLECTOR}_.*`); + const packetsReceivedLineChart: StatisticPrefab = { name: 'Packets Received (Line)', + collectorName: PACKETS_COLLECTOR, reactNode: ( { return generateRowsFromStatsPrefabs([ [packetsReceivedLineChart, packetsSentLineChart], diff --git a/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerTiming.tsx b/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerTiming.tsx index db5af90b..1946ede4 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerTiming.tsx +++ b/webview-ui/src/diagnostics_panel/prefabs/tabs/ServerTiming.tsx @@ -5,15 +5,19 @@ import { StatisticType, YAxisType, createStatResolver } from '../../StatisticRes import { TabPrefab, TabPrefabDataSource } from '../TabPrefab'; import { generateRowsFromStatsPrefabs } from '../utilities'; +const SERVER_TICK_TIMINGS_COLLECTOR = 'server_tick_timings'; +const COMMANDS_COLLECTOR = 'commands'; + const ServerTickTimings: StatisticPrefab = { name: 'Server Tick Timings', + collectorName: SERVER_TICK_TIMINGS_COLLECTOR, reactNode: ( { return generateRowsFromStatsPrefabs([[ServerTickTimings], [CommandsRan]]); }, diff --git a/webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx b/webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx index f1ba78c1..7ca3689e 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx +++ b/webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx @@ -2,17 +2,23 @@ import MinecraftStatisticLineChart from '../../controls/MinecraftStatisticLineCh import MinecraftStatisticStackedLineChart from '../../controls/MinecraftStatisticStackedLineChart'; import { StatisticPrefab } from '../StatisticPrefab'; import { SimpleStatisticProvider, NestedStatisticProvider } from '../../StatisticProvider'; -import { StatisticType, YAxisType, NestedStatResolver, createStatResolver } from '../../StatisticResolver'; +import { StatisticType, YAxisStyle, YAxisType, NestedStatResolver, createStatResolver } from '../../StatisticResolver'; import { TabPrefab, TabPrefabDataSource } from '../TabPrefab'; import { generateRowsFromStatsPrefabs } from '../utilities'; +const ENTITIES_COLLECTOR = 'entities'; +const CHUNKS_COLLECTOR = 'chunks'; + const entityCount: StatisticPrefab = { name: 'Entity Count', + collectorName: ENTITIES_COLLECTOR, reactNode: ( ), @@ -43,6 +52,7 @@ const loadedChunks: StatisticPrefab = { const statsTab: TabPrefab = { name: 'World', dataSource: TabPrefabDataSource.Server, + collectors: [entityCount, loadedChunks], content: () => { return generateRowsFromStatsPrefabs([[entityCount], [loadedChunks]]); }, From 64ea854dc83e4b2e3541691f69531370de7baecd Mon Sep 17 00:00:00 2001 From: Alex Denford SBL Date: Fri, 7 Aug 2026 09:07:13 -0700 Subject: [PATCH 3/3] Update MinecraftStatisticStackedLineChart.tsx --- webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx b/webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx index 7ca3689e..bea703c9 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx +++ b/webview-ui/src/diagnostics_panel/prefabs/tabs/World.tsx @@ -42,8 +42,6 @@ const loadedChunks: StatisticPrefab = { yAxisType: YAxisType.Absolute, }) )} - yAxisStyle={YAxisStyle.SquareRoot} - showSectionLabels yLabel="Number of Chunks" /> ),