From 3eda824868e4641723dcb8d286b4193b13c2a7fa Mon Sep 17 00:00:00 2001 From: Alex Denford SBL Date: Fri, 27 Mar 2026 14:57:47 -0700 Subject: [PATCH 1/4] wip --- src/panels/minecraft-diagnostics.ts | 6 + src/session.ts | 2 + src/stats/stats-provider.ts | 29 +++ webview-ui/src/diagnostics_panel/App.tsx | 114 +++++++++--- .../diagnostics_panel/DiagnosticsSchema.ts | 29 +++ .../src/diagnostics_panel/DynamicTab.tsx | 166 ++++++++++++++++++ webview-ui/src/diagnostics_panel/index.tsx | 2 +- .../diagnostics_panel/prefabs/TabPrefab.ts | 2 - 8 files changed, 319 insertions(+), 31 deletions(-) create mode 100644 webview-ui/src/diagnostics_panel/DiagnosticsSchema.ts create mode 100644 webview-ui/src/diagnostics_panel/DynamicTab.tsx diff --git a/src/panels/minecraft-diagnostics.ts b/src/panels/minecraft-diagnostics.ts index b535c058..bce18d0d 100644 --- a/src/panels/minecraft-diagnostics.ts +++ b/src/panels/minecraft-diagnostics.ts @@ -96,6 +96,12 @@ export class MinecraftDiagnosticsPanel { onNotification: (message: string) => { window.showInformationMessage(message); }, + onSchemaReceived: (schema) => { + this._panel.webview.postMessage({ + type: 'diagnostics-schema', + schema, + }); + }, }; this._statsTracker.addStatListener(this._statsCallback); diff --git a/src/session.ts b/src/session.ts index 3b2059a1..3a6ce906 100644 --- a/src/session.ts +++ b/src/session.ts @@ -894,6 +894,8 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { this.handleProtocolEvent(eventMessage as ProtocolCapabilities); } else if (eventMessage.type === 'StatEvent2') { this._statsProvider.setStats(eventMessage as StatMessageModel); + } else if (eventMessage.type === 'SchemaEvent') { + this._statsProvider.setSchema(eventMessage.descriptors); } else if (eventMessage.type === 'ProfilerCapture') { this.handleProfilerCapture(eventMessage as ProfilerCapture); } diff --git a/src/stats/stats-provider.ts b/src/stats/stats-provider.ts index 48c53f80..0b8c664d 100644 --- a/src/stats/stats-provider.ts +++ b/src/stats/stats-provider.ts @@ -26,12 +26,35 @@ export interface StatMessageModel { stats: StatDataModel[]; } +// Mirrors ScriptDiagnosticsDescriptor from C++. Sent once on connect via SchemaEvent. +export interface DiagnosticsTabDescriptor { + name: string; + stat_group_id: string; + data_source: 'server' | 'client' | 'server_script'; + display_type: 'line_chart' | 'stacked_line_chart' | 'stacked_bar_chart' | 'table' | 'multi_column_table' | 'dynamic_properties_table'; + title?: string; + y_label?: string; + tick_range?: number; + value_scalar?: number; + target_value?: number; + key_label?: string; + value_labels?: string[]; + statistic_id?: string; + statistic_ids?: string[]; +} + +export interface SchemaMessageModel { + type: 'SchemaEvent'; + descriptors: DiagnosticsTabDescriptor[]; +} + export interface StatsListener { onStatUpdated?: (stat: StatData) => void; onSpeedUpdated?: (speed: number) => void; onPauseUpdated?: (paused: boolean) => void; onStopped?: () => void; onNotification?: (message: string) => void; + onSchemaReceived?: (schema: DiagnosticsTabDescriptor[]) => void; } export class StatsProvider { @@ -47,6 +70,12 @@ export class StatsProvider { } } + public setSchema(schema: DiagnosticsTabDescriptor[]): void { + this._statListeners.forEach((listener: StatsListener) => { + listener.onSchemaReceived?.(schema); + }); + } + public start(): void { throw new Error('Method not implemented.'); } diff --git a/webview-ui/src/diagnostics_panel/App.tsx b/webview-ui/src/diagnostics_panel/App.tsx index 51f6d232..d41e66fc 100644 --- a/webview-ui/src/diagnostics_panel/App.tsx +++ b/webview-ui/src/diagnostics_panel/App.tsx @@ -1,6 +1,6 @@ // Copyright (C) Microsoft Corporation. All rights reserved. -import { VSCodePanelTab, VSCodePanelView, VSCodePanels } from '@vscode/webview-ui-toolkit/react'; +import { VSCodeDropdown, VSCodeOption, VSCodePanelTab, VSCodePanelView, VSCodePanels } from '@vscode/webview-ui-toolkit/react'; import { StatGroupSelectionBox } from './controls/StatGroupSelectionBox'; import { useCallback, useEffect, useState } from 'react'; import { StatisticType, YAxisStyle, YAxisType, createStatResolver } from './StatisticResolver'; @@ -14,6 +14,8 @@ import { Icons } from './Icons'; import './App.css'; import tabPrefabs from './prefabs'; import { TabPrefabDataSource } from './prefabs/TabPrefab'; +import { DiagnosticsTabDescriptor } from './DiagnosticsSchema'; +import DynamicTab from './DynamicTab'; declare global { interface Window { @@ -53,6 +55,10 @@ function App() { const [currentTab, setCurrentTab] = useState(); const [paused, setPaused] = useState(true); const [speed, setSpeed] = useState(''); + // Dynamic schema received from the game on connect. Empty = use static prefab fallback. + const [schema, setSchema] = useState([]); + // Index of the selected tab in the dynamic dropdown + const [selectedSchemaIndex, setSelectedSchemaIndex] = useState(0); const handlePluginSelection = useCallback((pluginSelectionId: string) => { setSelectedPlugin(() => pluginSelectionId); @@ -69,6 +75,11 @@ function App() { } }, []); + const handleSchemaTabChange = useCallback((e: Event | React.FormEvent): void => { + const target = e.target as HTMLSelectElement; + setSelectedSchemaIndex(target.selectedIndex); + }, []); + useEffect(() => { const handleMessage = (event: MessageEvent) => { const message = event.data; @@ -76,6 +87,9 @@ function App() { setSpeed(`${message.speed}hz`); } else if (message.type === 'pause-updated') { setPaused(message.paused); + } else if (message.type === 'diagnostics-schema') { + setSchema(message.schema as DiagnosticsTabDescriptor[]); + setSelectedSchemaIndex(0); } }; window.addEventListener('message', handleMessage); @@ -84,6 +98,9 @@ function App() { }; }, []); + const usingDynamicSchema = schema.length > 0; + const activeDescriptor = usingDynamicSchema ? schema[selectedSchemaIndex] : undefined; + return (
{window.initialParams.showReplayControls && ( @@ -98,34 +115,75 @@ function App() { svgIcons={Icons} /> )} - handlePanelChange(event as VSCodePanelsChangeEvent)}> - {tabPrefabs.map((tabPrefab, index) => ( - {tabPrefab.name} - ))} - {tabPrefabs.map((tabPrefab, index) => ( - - {tabPrefab.dataSource === TabPrefabDataSource.Client ? ( - - ) : ( -
- )} - {tabPrefab.dataSource === TabPrefabDataSource.ServerScript ? ( - +
+ + + {schema.map((descriptor, index) => ( + {descriptor.name} + ))} + +
+ + {activeDescriptor && ( +
+ {activeDescriptor.data_source === 'client' && ( + + )} + {activeDescriptor.data_source === 'server_script' && ( + + )} + - ) : ( -
- )} - {tabPrefab.content({ selectedClient, selectedPlugin })} - - ))} - +
+ )} +
+ ) : ( + // Static fallback: no schema received (older game version) — render hardcoded prefab tabs + handlePanelChange(event as VSCodePanelsChangeEvent)}> + {tabPrefabs.map((tabPrefab, index) => ( + {tabPrefab.name} + ))} + {tabPrefabs.map((tabPrefab, index) => ( + + {tabPrefab.dataSource === TabPrefabDataSource.Client ? ( + + ) : ( +
+ )} + {tabPrefab.dataSource === TabPrefabDataSource.ServerScript ? ( + + ) : ( +
+ )} + {tabPrefab.content({ selectedClient, selectedPlugin })} + + ))} + + )}
); } diff --git a/webview-ui/src/diagnostics_panel/DiagnosticsSchema.ts b/webview-ui/src/diagnostics_panel/DiagnosticsSchema.ts new file mode 100644 index 00000000..4988c2dd --- /dev/null +++ b/webview-ui/src/diagnostics_panel/DiagnosticsSchema.ts @@ -0,0 +1,29 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. +// TypeScript mirror of ScriptDiagnosticsDescriptor (C++) and its Cereal-serialized wire format. + +export type DiagnosticsDataSource = 'server' | 'client' | 'server_script'; + +export type DiagnosticsDisplayType = + | 'line_chart' + | 'stacked_line_chart' + | 'stacked_bar_chart' + | 'table' + | 'multi_column_table' + | 'dynamic_properties_table'; + +// One-to-one with ScriptDiagnosticsDescriptor fields (snake_case matches cereal wire names). +export interface DiagnosticsTabDescriptor { + name: string; + stat_group_id: string; + data_source: DiagnosticsDataSource; + display_type: DiagnosticsDisplayType; + title?: string; + y_label?: string; + tick_range?: number; + value_scalar?: number; + target_value?: number; + key_label?: string; + value_labels?: string[]; + statistic_id?: string; + statistic_ids?: string[]; +} diff --git a/webview-ui/src/diagnostics_panel/DynamicTab.tsx b/webview-ui/src/diagnostics_panel/DynamicTab.tsx new file mode 100644 index 00000000..f39ba038 --- /dev/null +++ b/webview-ui/src/diagnostics_panel/DynamicTab.tsx @@ -0,0 +1,166 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +// Generic tab renderer driven by a DiagnosticsTabDescriptor from the C++ schema registry. +// Derives the appropriate StatisticProvider and StatisticResolver from the descriptor fields +// and renders the matching control — no new tab files needed for new diagnostics. + +import { useMemo } from 'react'; +import { DiagnosticsTabDescriptor } from './DiagnosticsSchema'; +import { + SimpleStatisticProvider, + MultipleStatisticProvider, + StatisticProvider, +} from './StatisticProvider'; +import { StatisticType, YAxisType, createStatResolver, StatisticResolver } from './StatisticResolver'; +import MinecraftStatisticLineChart from './controls/MinecraftStatisticLineChart'; +import MinecraftStatisticStackedLineChart from './controls/MinecraftStatisticStackedLineChart'; +import MinecraftStatisticStackedBarChart from './controls/MinecraftStatisticStackedBarChart'; +import MinecraftStatisticTable from './controls/MinecraftStatisticTable'; +import MinecraftMultiColumnStatisticTable from './controls/MinecraftMultiColumnStatisticTable'; +import { MinecraftDynamicPropertiesTable } from './controls/MinecraftDynamicPropertiesTable'; + +type DynamicTabProps = { + descriptor: DiagnosticsTabDescriptor; + selectedClient: string; + selectedPlugin: string; +}; + +// Build a StatisticProvider from descriptor fields. +// Client-source tabs use a regex to match the per-player stat group prefix. +function buildProvider( + descriptor: DiagnosticsTabDescriptor, + selectedClient: string, + _selectedPlugin: string +): StatisticProvider { + const { data_source, display_type, stat_group_id, statistic_id, statistic_ids } = descriptor; + + // DynamicPropertiesTable has its own fixed provider shape + if (display_type === 'dynamic_properties_table') { + return new SimpleStatisticProvider({ + statisticId: 'consolidated_data', + statisticParentId: new RegExp(`${stat_group_id}.*`), + }); + } + + // LineChart uses SimpleStatisticProvider (single stat series) + if (display_type === 'line_chart') { + const effectiveStatId = statistic_id ?? stat_group_id; + const parentId: string | RegExp = + data_source === 'client' + ? new RegExp(`.*${selectedClient}_${stat_group_id}`) + : stat_group_id; + return new SimpleStatisticProvider({ + statisticId: effectiveStatId, + statisticParentId: parentId, + }); + } + + // All other chart/table types use MultipleStatisticProvider + const parentId: string | RegExp = + data_source === 'client' + ? new RegExp(`.*${selectedClient}_${stat_group_id}`) + : stat_group_id; + + return new MultipleStatisticProvider({ + statisticParentId: parentId, + statisticIds: statistic_ids, + }); +} + +function buildResolver(descriptor: DiagnosticsTabDescriptor): StatisticResolver { + return createStatResolver({ + type: StatisticType.Absolute, + yAxisType: YAxisType.Absolute, + tickRange: descriptor.tick_range ?? 20 * 10, + valueScalar: descriptor.value_scalar, + }); +} + +export default function DynamicTab({ descriptor, selectedClient, selectedPlugin }: DynamicTabProps) { + const title = descriptor.title ?? descriptor.name; + const yLabel = descriptor.y_label ?? ''; + const tickRange = descriptor.tick_range ?? 20 * 10; + + // Memoize provider and resolver so controls' useEffect deps remain stable across renders. + const provider = useMemo( + () => buildProvider(descriptor, selectedClient, selectedPlugin), + // re-create only when the identity of the tab or the selected client/plugin changes + [descriptor.stat_group_id, descriptor.data_source, descriptor.display_type, + descriptor.statistic_id, descriptor.statistic_ids?.join(','), selectedClient, selectedPlugin] + ); + + const resolver = useMemo( + () => buildResolver(descriptor), + [descriptor.tick_range, descriptor.value_scalar] + ); + + switch (descriptor.display_type) { + case 'line_chart': + return ( + + ); + + case 'stacked_line_chart': + return ( + + ); + + case 'stacked_bar_chart': + return ( + + ); + + case 'table': + return ( + + ); + + case 'multi_column_table': + return ( + + ); + + case 'dynamic_properties_table': + return ( + + ); + + default: + return
Unsupported display type: {descriptor.display_type}
; + } +} diff --git a/webview-ui/src/diagnostics_panel/index.tsx b/webview-ui/src/diagnostics_panel/index.tsx index 9551ae90..9b52539a 100644 --- a/webview-ui/src/diagnostics_panel/index.tsx +++ b/webview-ui/src/diagnostics_panel/index.tsx @@ -5,7 +5,7 @@ import { createRoot } from 'react-dom/client'; import App from './App'; const container = document.getElementById('root'); -const root = createRoot(container!); // createRoot(container!) if you use TypeScript +const root = createRoot(container!); root.render( diff --git a/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts b/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts index ca70a627..12fab43d 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts +++ b/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts @@ -1,5 +1,3 @@ -import { StatisticPrefab } from './StatisticPrefab'; - export type TabPrefabParams = { selectedClient: string; selectedPlugin: string; From ee74ea5a3e1b8dbe35567a170292e2e382f4a443 Mon Sep 17 00:00:00 2001 From: Alex Denford SBL Date: Fri, 10 Apr 2026 14:33:17 -0700 Subject: [PATCH 2/4] remove descriptor changes --- src/stats/stats-provider.ts | 31 +---- webview-ui/package-lock.json | 12 +- webview-ui/src/diagnostics_panel/App.tsx | 114 +++++------------- webview-ui/src/diagnostics_panel/index.tsx | 2 +- .../diagnostics_panel/prefabs/TabPrefab.ts | 2 + 5 files changed, 43 insertions(+), 118 deletions(-) diff --git a/src/stats/stats-provider.ts b/src/stats/stats-provider.ts index 0b8c664d..ddace2ff 100644 --- a/src/stats/stats-provider.ts +++ b/src/stats/stats-provider.ts @@ -26,35 +26,12 @@ export interface StatMessageModel { stats: StatDataModel[]; } -// Mirrors ScriptDiagnosticsDescriptor from C++. Sent once on connect via SchemaEvent. -export interface DiagnosticsTabDescriptor { - name: string; - stat_group_id: string; - data_source: 'server' | 'client' | 'server_script'; - display_type: 'line_chart' | 'stacked_line_chart' | 'stacked_bar_chart' | 'table' | 'multi_column_table' | 'dynamic_properties_table'; - title?: string; - y_label?: string; - tick_range?: number; - value_scalar?: number; - target_value?: number; - key_label?: string; - value_labels?: string[]; - statistic_id?: string; - statistic_ids?: string[]; -} - -export interface SchemaMessageModel { - type: 'SchemaEvent'; - descriptors: DiagnosticsTabDescriptor[]; -} - export interface StatsListener { onStatUpdated?: (stat: StatData) => void; onSpeedUpdated?: (speed: number) => void; onPauseUpdated?: (paused: boolean) => void; onStopped?: () => void; onNotification?: (message: string) => void; - onSchemaReceived?: (schema: DiagnosticsTabDescriptor[]) => void; } export class StatsProvider { @@ -69,13 +46,7 @@ export class StatsProvider { this._fireStatUpdated(stat, stats.tick); } } - - public setSchema(schema: DiagnosticsTabDescriptor[]): void { - this._statListeners.forEach((listener: StatsListener) => { - listener.onSchemaReceived?.(schema); - }); - } - + public start(): void { throw new Error('Method not implemented.'); } diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index 9891a45a..d0dce43b 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -69,6 +69,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -1526,6 +1527,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz", "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -1630,6 +1632,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001629", "electron-to-chromium": "^1.4.796", @@ -2005,6 +2008,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "peer": true, "engines": { "node": ">=12" } @@ -2432,6 +2436,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -2762,6 +2767,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -3708,6 +3714,7 @@ "version": "22.13.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz", "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==", + "peer": true, "requires": { "undici-types": "~6.20.0" } @@ -3789,6 +3796,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001629", "electron-to-chromium": "^1.4.796", @@ -4053,7 +4061,8 @@ "d3-selection": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "peer": true }, "d3-shape": { "version": "3.2.0", @@ -4347,6 +4356,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } diff --git a/webview-ui/src/diagnostics_panel/App.tsx b/webview-ui/src/diagnostics_panel/App.tsx index d41e66fc..51f6d232 100644 --- a/webview-ui/src/diagnostics_panel/App.tsx +++ b/webview-ui/src/diagnostics_panel/App.tsx @@ -1,6 +1,6 @@ // Copyright (C) Microsoft Corporation. All rights reserved. -import { VSCodeDropdown, VSCodeOption, VSCodePanelTab, VSCodePanelView, VSCodePanels } from '@vscode/webview-ui-toolkit/react'; +import { VSCodePanelTab, VSCodePanelView, VSCodePanels } from '@vscode/webview-ui-toolkit/react'; import { StatGroupSelectionBox } from './controls/StatGroupSelectionBox'; import { useCallback, useEffect, useState } from 'react'; import { StatisticType, YAxisStyle, YAxisType, createStatResolver } from './StatisticResolver'; @@ -14,8 +14,6 @@ import { Icons } from './Icons'; import './App.css'; import tabPrefabs from './prefabs'; import { TabPrefabDataSource } from './prefabs/TabPrefab'; -import { DiagnosticsTabDescriptor } from './DiagnosticsSchema'; -import DynamicTab from './DynamicTab'; declare global { interface Window { @@ -55,10 +53,6 @@ function App() { const [currentTab, setCurrentTab] = useState(); const [paused, setPaused] = useState(true); const [speed, setSpeed] = useState(''); - // Dynamic schema received from the game on connect. Empty = use static prefab fallback. - const [schema, setSchema] = useState([]); - // Index of the selected tab in the dynamic dropdown - const [selectedSchemaIndex, setSelectedSchemaIndex] = useState(0); const handlePluginSelection = useCallback((pluginSelectionId: string) => { setSelectedPlugin(() => pluginSelectionId); @@ -75,11 +69,6 @@ function App() { } }, []); - const handleSchemaTabChange = useCallback((e: Event | React.FormEvent): void => { - const target = e.target as HTMLSelectElement; - setSelectedSchemaIndex(target.selectedIndex); - }, []); - useEffect(() => { const handleMessage = (event: MessageEvent) => { const message = event.data; @@ -87,9 +76,6 @@ function App() { setSpeed(`${message.speed}hz`); } else if (message.type === 'pause-updated') { setPaused(message.paused); - } else if (message.type === 'diagnostics-schema') { - setSchema(message.schema as DiagnosticsTabDescriptor[]); - setSelectedSchemaIndex(0); } }; window.addEventListener('message', handleMessage); @@ -98,9 +84,6 @@ function App() { }; }, []); - const usingDynamicSchema = schema.length > 0; - const activeDescriptor = usingDynamicSchema ? schema[selectedSchemaIndex] : undefined; - return (
{window.initialParams.showReplayControls && ( @@ -115,75 +98,34 @@ function App() { svgIcons={Icons} /> )} - - {usingDynamicSchema ? ( - // Dynamic mode: schema received from game — dropdown selector + single DynamicTab -
-
- - - {schema.map((descriptor, index) => ( - {descriptor.name} - ))} - -
- - {activeDescriptor && ( -
- {activeDescriptor.data_source === 'client' && ( - - )} - {activeDescriptor.data_source === 'server_script' && ( - - )} - handlePanelChange(event as VSCodePanelsChangeEvent)}> + {tabPrefabs.map((tabPrefab, index) => ( + {tabPrefab.name} + ))} + {tabPrefabs.map((tabPrefab, index) => ( + + {tabPrefab.dataSource === TabPrefabDataSource.Client ? ( + -
- )} -
- ) : ( - // Static fallback: no schema received (older game version) — render hardcoded prefab tabs - handlePanelChange(event as VSCodePanelsChangeEvent)}> - {tabPrefabs.map((tabPrefab, index) => ( - {tabPrefab.name} - ))} - {tabPrefabs.map((tabPrefab, index) => ( - - {tabPrefab.dataSource === TabPrefabDataSource.Client ? ( - - ) : ( -
- )} - {tabPrefab.dataSource === TabPrefabDataSource.ServerScript ? ( - - ) : ( -
- )} - {tabPrefab.content({ selectedClient, selectedPlugin })} - - ))} - - )} + ) : ( +
+ )} + {tabPrefab.dataSource === TabPrefabDataSource.ServerScript ? ( + + ) : ( +
+ )} + {tabPrefab.content({ selectedClient, selectedPlugin })} + + ))} +
); } diff --git a/webview-ui/src/diagnostics_panel/index.tsx b/webview-ui/src/diagnostics_panel/index.tsx index 9b52539a..9551ae90 100644 --- a/webview-ui/src/diagnostics_panel/index.tsx +++ b/webview-ui/src/diagnostics_panel/index.tsx @@ -5,7 +5,7 @@ import { createRoot } from 'react-dom/client'; import App from './App'; const container = document.getElementById('root'); -const root = createRoot(container!); +const root = createRoot(container!); // createRoot(container!) if you use TypeScript root.render( diff --git a/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts b/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts index 12fab43d..ca70a627 100644 --- a/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts +++ b/webview-ui/src/diagnostics_panel/prefabs/TabPrefab.ts @@ -1,3 +1,5 @@ +import { StatisticPrefab } from './StatisticPrefab'; + export type TabPrefabParams = { selectedClient: string; selectedPlugin: string; From 870c27c16ac58a3e7754e1ee451ae51ede1e56e8 Mon Sep 17 00:00:00 2001 From: Alex Denford SBL Date: Tue, 12 May 2026 11:51:37 -0700 Subject: [PATCH 3/4] fixes --- CONTRIBUTING.md | 11 +- package-lock.json | 82 ++++-------- src/breakpoints-legacy.ts | 5 +- src/debuggee-message-sender.ts | 3 +- src/panels/minecraft-diagnostics.ts | 6 - src/protocol-events.ts | 195 ++++++++++++++++++++++++++++ src/session.ts | 135 ++++++++++--------- 7 files changed, 299 insertions(+), 138 deletions(-) create mode 100644 src/protocol-events.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 441ef5da..470bffc5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,8 +13,17 @@ Some examples of this are: To see the current list of supported types, see [CI.yml](./.github/workflows/ci.yml#L13)'s 'Lint PR Title' task. -# Tests +# Build +To build and use this extension locally, run the following commands: + ``` +cd webview-ui +npm install +``` +Go to the Run and Debug panel (Ctrl-Shift-D) and select "Run Extension". This was automatically build and run the extension. +You can now use the extension as normal (in the new VS Code instance) by attaching to Minecraft to debug. + +# Tests Create tests using vitest. Follow the convention `sourcefilename.test.ts` when adding new files. diff --git a/package-lock.json b/package-lock.json index 9183e6ce..716e8130 100644 --- a/package-lock.json +++ b/package-lock.json @@ -867,7 +867,6 @@ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -883,7 +882,6 @@ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -894,7 +892,6 @@ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -905,7 +902,6 @@ "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -924,7 +920,6 @@ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1035,6 +1030,7 @@ "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.0.0", @@ -2186,6 +2182,7 @@ "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "4.33.0", "@typescript-eslint/types": "4.33.0", @@ -2677,7 +2674,6 @@ "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -2688,24 +2684,21 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", @@ -2713,7 +2706,6 @@ "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -2725,8 +2717,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.12.1", @@ -2734,7 +2725,6 @@ "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -2748,7 +2738,6 @@ "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -2759,7 +2748,6 @@ "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -2769,8 +2757,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.12.1", @@ -2778,7 +2765,6 @@ "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -2796,7 +2782,6 @@ "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", @@ -2811,7 +2796,6 @@ "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-buffer": "1.12.1", @@ -2825,7 +2809,6 @@ "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", @@ -2841,7 +2824,6 @@ "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" @@ -2852,16 +2834,14 @@ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/acorn": { "version": "8.13.0", @@ -2920,6 +2900,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2937,7 +2918,6 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -3279,8 +3259,7 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cac": { "version": "6.7.14", @@ -3352,8 +3331,7 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "CC-BY-4.0", - "peer": true + "license": "CC-BY-4.0" }, "node_modules/chai": { "version": "5.1.2", @@ -3490,7 +3468,6 @@ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -4210,8 +4187,7 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -4547,6 +4523,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4715,6 +4692,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -5600,8 +5578,7 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/globals": { "version": "13.24.0", @@ -6218,7 +6195,6 @@ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -6234,7 +6210,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -6245,7 +6220,6 @@ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -6500,7 +6474,6 @@ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" } @@ -6623,7 +6596,6 @@ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -6685,6 +6657,7 @@ "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -7005,8 +6978,7 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/normalize-package-data": { "version": "6.0.2", @@ -10861,7 +10833,6 @@ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -11239,7 +11210,6 @@ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -11259,6 +11229,7 @@ "integrity": "sha512-Cb0Pm3Ye15u8k/B+7EnusMUSIIucAIEBD3QDRmmclv53KVyqmg1Lb3XPx0AMNxfJZEI+ZT+M+IXDyTrudK6Rew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -11828,7 +11799,6 @@ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -12126,7 +12096,6 @@ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -12138,7 +12107,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12611,7 +12579,6 @@ "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -12631,7 +12598,6 @@ "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", @@ -12666,8 +12632,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/text-table": { "version": "0.2.0", @@ -13001,6 +12966,7 @@ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13130,7 +13096,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.0" @@ -13200,6 +13165,7 @@ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -13779,7 +13745,6 @@ "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -13794,7 +13759,6 @@ "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -13842,7 +13806,6 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -13853,7 +13816,6 @@ "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "acorn": "^8" } diff --git a/src/breakpoints-legacy.ts b/src/breakpoints-legacy.ts index 0756fe07..8d368385 100644 --- a/src/breakpoints-legacy.ts +++ b/src/breakpoints-legacy.ts @@ -4,6 +4,7 @@ import { DebugProtocol } from '@vscode/debugprotocol'; import { IBreakpointsHandler } from './ibreakpoints-handler'; import { SourceMaps } from './source-maps'; import { IDebuggeeMessageSender } from './debuggee-message-sender'; +import { BreakpointsMessage, OutgoingEventType } from './protocol-events'; import * as path from 'path'; // respond to a setBreakPointsRequest from session, deprecated. @@ -67,8 +68,8 @@ export class BreakpointsLegacy implements IBreakpointsHandler { // send full set of breakpoints for each generated file, a message per file for (const [generatedRemoteLocalPath, generatedBreakpoints] of generatedBreakpointsMap) { - const envelope = { - type: 'breakpoints', + const envelope: BreakpointsMessage = { + type: OutgoingEventType.breakpoints, breakpoints: { path: generatedRemoteLocalPath, breakpoints: generatedBreakpoints.length ? generatedBreakpoints : undefined, diff --git a/src/debuggee-message-sender.ts b/src/debuggee-message-sender.ts index 73ea598b..03707083 100644 --- a/src/debuggee-message-sender.ts +++ b/src/debuggee-message-sender.ts @@ -1,9 +1,10 @@ // Copyright (C) Microsoft Corporation. All rights reserved. import { DebugProtocol } from '@vscode/debugprotocol'; +import { OutgoingDebuggeeMessage } from './protocol-events'; // Interface for sending debugger messages and requests to MC export interface IDebuggeeMessageSender { - sendDebuggeeMessage(envelope: unknown): void; + sendDebuggeeMessage(envelope: OutgoingDebuggeeMessage): void; sendDebugeeRequestAsync(response: DebugProtocol.Response, args: unknown): Promise; } diff --git a/src/panels/minecraft-diagnostics.ts b/src/panels/minecraft-diagnostics.ts index bce18d0d..b535c058 100644 --- a/src/panels/minecraft-diagnostics.ts +++ b/src/panels/minecraft-diagnostics.ts @@ -96,12 +96,6 @@ export class MinecraftDiagnosticsPanel { onNotification: (message: string) => { window.showInformationMessage(message); }, - onSchemaReceived: (schema) => { - this._panel.webview.postMessage({ - type: 'diagnostics-schema', - schema, - }); - }, }; this._statsTracker.addStatListener(this._statsCallback); diff --git a/src/protocol-events.ts b/src/protocol-events.ts new file mode 100644 index 00000000..a3d61646 --- /dev/null +++ b/src/protocol-events.ts @@ -0,0 +1,195 @@ +// Copyright (C) Microsoft Corporation. All rights reserved. + +import { LogLevel } from '@vscode/debugadapter/lib/logger'; +import { DebugProtocol } from '@vscode/debugprotocol'; +import { StatMessageModel } from './stats/stats-provider'; + +// protocol version history +// 1 - initial version +// 2 - add targetModuleUuid to protocol event +// 3 - add array of plugins and target module ids to incoming protocol event +// 4 - mc can require a passcode to connect +// 5 - debugger can take mc script profiler captures +// 6 - breakpoints as request, MC can reject +// 7 - New serialization tech (use Cereal) + +export enum ProtocolVersion { + _Unknown = 0, + Initial = 1, + SupportTargetModuleUuid = 2, + SupportTargetSelection = 3, + SupportPasscode = 4, + SupportProfilerCaptures = 5, + SupportBreakpointsAsRequest = 6, + SupportCerealSerialization = 7, +} + +export const DEBUGGER_PROTOCOL_VERSION = ProtocolVersion.SupportCerealSerialization; + +// ------------------------------------------------------------------------- +// Interfaces for event message payloads (received from the debugee) +// ------------------------------------------------------------------------- +export const IncomingEventType = { + stopped: 'StoppedEvent', + thread: 'ThreadEvent', + print: 'PrintEvent', + notification: 'NotificationEvent', + protocol: 'ProtocolEvent', + stat2: 'StatEvent2', + schema: 'SchemaEvent', + profilerCapture: 'ProfilerCapture', +} as const; + +export interface PluginDetails { + name: string; + module_uuid: string; +} + +export interface ProtocolCapabilities { + type: typeof IncomingEventType.protocol; + version: number; + plugins: PluginDetails[]; + require_passcode?: boolean; +} + +export interface ProfilerCapture { + type: typeof IncomingEventType.profilerCapture; + capture_base_path: string; + capture_data: string; +} + +export interface StoppedEventMessage { + type: typeof IncomingEventType.stopped; + reason: string; + thread: number; +} + +export interface ThreadEventMessage { + type: typeof IncomingEventType.thread; + reason: string; + thread: number; +} + +export interface PrintEventMessage { + type: typeof IncomingEventType.print; + message: string; + logLevel: LogLevel; +} + +export interface NotificationEventMessage { + type: typeof IncomingEventType.notification; + message: string; + logLevel: LogLevel; +} + +export type IncomingDebuggeeMessage = + | PluginDetails + | ProtocolCapabilities + | ProfilerCapture + | StoppedEventMessage + | ThreadEventMessage + | PrintEventMessage + | NotificationEventMessage; + + + +// ------------------------------------------------------------------------- +// Interfaces for outbound message payloads (sent to the debugee) +// ------------------------------------------------------------------------- +export const OutgoingEventType = { + protocol: 'protocol', + minecraftCommand: 'minecraftCommand', + startProfiler: 'startProfiler', + stopProfiler: 'stopProfiler', + stopOnException: 'stopOnException', + resume: 'resume', + request: 'request', + breakpoints: 'breakpoints', +} as const; + +export interface ProtocolResponse { + type: typeof OutgoingEventType.protocol; + version: number; + target_module_uuid?: string; + passcode?: string; +} + +export interface MinecraftCommandLegacyMessage { + type: typeof OutgoingEventType.minecraftCommand; + command: string; + dimension_type: string; +} + +export interface MinecraftCommandMessage { + type: typeof OutgoingEventType.minecraftCommand; + command: { command: string; dimension_type: string }; +} + +export interface StartProfilerMessage { + type: typeof OutgoingEventType.startProfiler; + profiler: { target_module_uuid?: string }; +} + +export interface StopProfilerMessage { + type: typeof OutgoingEventType.stopProfiler; + profiler: { captures_path: string; target_module_uuid?: string }; +} + +export interface StopOnExceptionMessage { + type: typeof OutgoingEventType.stopOnException; + stopOnException: boolean; +} + +export interface ResumeMessage { + type: typeof OutgoingEventType.resume; +} + +export interface RequestMessage { + type: typeof OutgoingEventType.request; + request: { request_seq: number; command: string; args: unknown }; +} + +export interface BreakpointsMessage { + type: typeof OutgoingEventType.breakpoints; + breakpoints: { path: string; breakpoints: DebugProtocol.SourceBreakpoint[] | undefined }; +} + +export type OutgoingDebuggeeMessage = + | ProtocolResponse + | MinecraftCommandLegacyMessage + | MinecraftCommandMessage + | StartProfilerMessage + | StopProfilerMessage + | StopOnExceptionMessage + | ResumeMessage + | RequestMessage + | BreakpointsMessage; + + + +// Re-export for callers that need these alongside event types +export type { StatMessageModel }; + +// ------------------------------------------------------------------------- +// Registry that maps event type name strings to handler callbacks +// ------------------------------------------------------------------------- + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type DebuggeeEventHandler = (event: any) => void; + +export class DebuggeeEventRegistry { + private readonly _handlers = new Map(); + + public register(eventType: string, handler: DebuggeeEventHandler): void { + this._handlers.set(eventType, handler); + } + + public dispatch(eventMessage: { type: string }): boolean { + const handler = this._handlers.get(eventMessage.type); + if (handler) { + handler(eventMessage); + return true; + } + return false; + } +} diff --git a/src/session.ts b/src/session.ts index 3a6ce906..cd94e474 100644 --- a/src/session.ts +++ b/src/session.ts @@ -42,6 +42,22 @@ import { IBreakpointsHandler } from './ibreakpoints-handler'; import { injectSourceMapIntoProfilerCapture } from './profiler-utils'; import { isUUID } from './utils'; import { MessageStreamParser } from './message-stream-parser'; +import { + DebuggeeEventRegistry, + DEBUGGER_PROTOCOL_VERSION, + IncomingEventType, + NotificationEventMessage, + OutgoingDebuggeeMessage, + OutgoingEventType, + PluginDetails, + PrintEventMessage, + ProfilerCapture, + ProtocolCapabilities, + ProtocolVersion, + RequestMessage, + StoppedEventMessage, + ThreadEventMessage, +} from './protocol-events'; import { SourceMaps } from './source-maps'; import { StatMessageModel, StatsProvider } from './stats/stats-provider'; @@ -55,24 +71,6 @@ export interface ModuleMapping { [moduleName: string]: string; } -interface PluginDetails { - name: string; - module_uuid: string; -} - -interface ProtocolCapabilities { - type: string; - version: number; - plugins: PluginDetails[]; - require_passcode?: boolean; -} - -interface ProfilerCapture { - type: string; - capture_base_path: string; - capture_data: string; -} - // Interface for specific launch arguments. // See package.json for schema. interface IAttachRequestArguments extends DebugProtocol.AttachRequestArguments { @@ -108,23 +106,6 @@ interface DebuggerStackFrame { column: number; } -// protocol version history -// 1 - initial version -// 2 - add targetModuleUuid to protocol event -// 3 - add array of plugins and target module ids to incoming protocol event -// 4 - mc can require a passcode to connect -// 5 - debugger can take mc script profiler captures -// 6 - breakpoints as request, MC can reject -enum ProtocolVersion { - _Unknown = 0, - Initial = 1, - SupportTargetModuleUuid = 2, - SupportTargetSelection = 3, - SupportPasscode = 4, - SupportProfilerCaptures = 5, - SupportBreakpointsAsRequest = 6, -} - // capabilites based on protocol version export interface MinecraftCapabilities { supportsCommands: boolean; @@ -135,10 +116,9 @@ export interface MinecraftCapabilities { // The Debug Adapter for 'minecraft-js' // export class Session extends DebugSession implements IDebuggeeMessageSender { - private readonly _debuggerProtocolVersion = ProtocolVersion.SupportBreakpointsAsRequest; + private readonly _connectionRetryAttempts = 3; private readonly _connectionRetryWaitMs = 500; - private _debugeeServer?: Server; // when listening for incoming connections private _connectionSocket?: Socket; private _connected = false; @@ -162,6 +142,7 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { supportsBreakpointsAsRequest: false, }; private _passcode?: string; + private _eventRegistry: DebuggeeEventRegistry; // external communication private _homeViewProvider: HomeViewProvider; @@ -178,12 +159,45 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { this.setDebuggerLinesStartAt1(true); this.setDebuggerColumnsStartAt1(true); + this._eventRegistry = new DebuggeeEventRegistry(); + this.registerServerEvents(); + this._eventEmitter.on('run-minecraft-command', this.onRunMinecraftCommand.bind(this)); 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)); } + // Use this to register new events that are handled from the debugee (Minecraft) + // For example you want to send new arbitary data to the debugger that doesn't fit into the existing event types (such as a live stat) + // then you can create a new event type in protocol-events.ts, have Minecraft send that event with the new data, and then register a handler + // for that event here to handle the incoming data and do something with it (e.g. update the home view, send a notification, etc). + private registerServerEvents() { + this._eventRegistry.register(IncomingEventType.stopped, (msg: StoppedEventMessage) => { + this.trackThreadChanges(msg.reason, msg.thread); + this.sendEvent(new StoppedEvent(msg.reason, msg.thread)); + }); + this._eventRegistry.register(IncomingEventType.thread, (msg: ThreadEventMessage) => { + this.trackThreadChanges(msg.reason, msg.thread); + this.sendEvent(new ThreadEvent(msg.reason, msg.thread)); + }); + this._eventRegistry.register(IncomingEventType.print, (msg: PrintEventMessage) => { + this.handlePrintEvent(msg.message, msg.logLevel); + }); + this._eventRegistry.register(IncomingEventType.notification, (msg: NotificationEventMessage) => { + this.showNotification(msg.message, msg.logLevel); + }); + this._eventRegistry.register(IncomingEventType.protocol, (msg: ProtocolCapabilities) => { + this.handleProtocolEvent(msg); + }); + this._eventRegistry.register(IncomingEventType.stat2, (msg: StatMessageModel) => { + this._statsProvider.setStats(msg); + }); + this._eventRegistry.register(IncomingEventType.profilerCapture, (msg: ProfilerCapture) => { + this.handleProfilerCapture(msg); + }); + } + public dispose(): void { this._eventEmitter.removeAllListeners('run-minecraft-command'); this._eventEmitter.removeAllListeners('start-profiler'); @@ -203,13 +217,13 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { private onRunMinecraftCommand(command: string): void { if (this._clientProtocolVersion < ProtocolVersion.SupportProfilerCaptures) { this.sendDebuggeeMessage({ - type: 'minecraftCommand', + type: OutgoingEventType.minecraftCommand, command: command, dimension_type: 'overworld', }); } else { this.sendDebuggeeMessage({ - type: 'minecraftCommand', + type: OutgoingEventType.minecraftCommand, command: { command: command, dimension_type: 'overworld', @@ -220,7 +234,7 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { private onStartProfiler(): void { this.sendDebuggeeMessage({ - type: 'startProfiler', + type: OutgoingEventType.startProfiler, profiler: { target_module_uuid: this._targetModuleUuid, }, @@ -229,7 +243,7 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { private onStopProfiler(capturesBasePath: string): void { this.sendDebuggeeMessage({ - type: 'stopProfiler', + type: OutgoingEventType.stopProfiler, profiler: { captures_path: capturesBasePath, target_module_uuid: this._targetModuleUuid, @@ -430,7 +444,7 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { args: DebugProtocol.SetExceptionBreakpointsArguments ): void { this.sendDebuggeeMessage({ - type: 'stopOnException', + type: OutgoingEventType.stopOnException, stopOnException: args.filters.length > 0, // there's only 1 type for now so no need to look at which one it is }); @@ -439,7 +453,7 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): void { this.sendDebuggeeMessage({ - type: 'resume', + type: OutgoingEventType.resume, }); this.sendResponse(response); @@ -730,7 +744,7 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { // respond with protocol version and chosen debugee target this.sendDebuggeeMessage({ - type: 'protocol', + type: OutgoingEventType.protocol, version: protocolVersion, target_module_uuid: targetModuleUuid, passcode: passcode, @@ -838,9 +852,9 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { this.sendDebuggeeMessage(this.makeRequestPayload(requestSeq, response.command, args)); } - private makeRequestPayload(requestSeq: number, responseCommand: string, args: any) { - const envelope = { - type: 'request', + private makeRequestPayload(requestSeq: number, responseCommand: string, args: unknown): RequestMessage { + const envelope: RequestMessage = { + type: OutgoingEventType.request, request: { request_seq: requestSeq, command: responseCommand, @@ -850,7 +864,7 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { return envelope; } - public sendDebuggeeMessage(envelope: unknown): void { + public sendDebuggeeMessage(envelope: OutgoingDebuggeeMessage): void { if (!this._connectionSocket) { return; } @@ -876,29 +890,14 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { } else if (envelope.type === 'response') { this.handleDebugeeResponse(envelope); } + else { + this.log(`Debugee message error: Unknown message type: ${envelope?.type ?? "NO TYPE"}`, LogLevel.Error); + } } // Debugee (MC) has sent an event. private handleDebugeeEvent(eventMessage: any) { - if (eventMessage.type === 'StoppedEvent') { - this.trackThreadChanges(eventMessage.reason, eventMessage.thread); - this.sendEvent(new StoppedEvent(eventMessage.reason, eventMessage.thread)); - } else if (eventMessage.type === 'ThreadEvent') { - this.trackThreadChanges(eventMessage.reason, eventMessage.thread); - this.sendEvent(new ThreadEvent(eventMessage.reason, eventMessage.thread)); - } else if (eventMessage.type === 'PrintEvent') { - this.handlePrintEvent(eventMessage.message, eventMessage.logLevel); - } else if (eventMessage.type === 'NotificationEvent') { - this.showNotification(eventMessage.message, eventMessage.logLevel); - } else if (eventMessage.type === 'ProtocolEvent') { - this.handleProtocolEvent(eventMessage as ProtocolCapabilities); - } else if (eventMessage.type === 'StatEvent2') { - this._statsProvider.setStats(eventMessage as StatMessageModel); - } else if (eventMessage.type === 'SchemaEvent') { - this._statsProvider.setSchema(eventMessage.descriptors); - } else if (eventMessage.type === 'ProfilerCapture') { - this.handleProfilerCapture(eventMessage as ProfilerCapture); - } + this._eventRegistry.dispatch(eventMessage); } private async handlePrintEvent(message: string, logLevel: LogLevel) { @@ -967,7 +966,7 @@ export class Session extends DebugSession implements IDebuggeeMessageSender { // handle protocol capabilities here... // can fail connection on errors // - if (this._debuggerProtocolVersion < protocolCapabilities.version) { + if (DEBUGGER_PROTOCOL_VERSION < protocolCapabilities.version) { this.terminateSession( `protocol unsupported. Upgrade Debugger Extension. Protocol Version: ${protocolCapabilities.version} is not supported by the current version of the Debugger.`, LogLevel.Error From 4553d18ea01538948013c77c1c97e0e701368ac2 Mon Sep 17 00:00:00 2001 From: Alex Denford SBL Date: Fri, 22 May 2026 16:34:32 -0700 Subject: [PATCH 4/4] merge --- src/protocol-events.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/protocol-events.ts b/src/protocol-events.ts index a3d61646..092f11bb 100644 --- a/src/protocol-events.ts +++ b/src/protocol-events.ts @@ -11,7 +11,8 @@ import { StatMessageModel } from './stats/stats-provider'; // 4 - mc can require a passcode to connect // 5 - debugger can take mc script profiler captures // 6 - breakpoints as request, MC can reject -// 7 - New serialization tech (use Cereal) +// 7 - support for debugger requests, MC can reject or respond with args +// 8 - New serialization tech (use Cereal) export enum ProtocolVersion { _Unknown = 0, @@ -21,7 +22,8 @@ export enum ProtocolVersion { SupportPasscode = 4, SupportProfilerCaptures = 5, SupportBreakpointsAsRequest = 6, - SupportCerealSerialization = 7, + SupportDebuggerRequests = 7, + SupportCerealSerialization = 8, } export const DEBUGGER_PROTOCOL_VERSION = ProtocolVersion.SupportCerealSerialization;