Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(debugger): Update filewatch to look for .map and .js and send /reload to MC #248

Merged
merged 8 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ Now, hit "Start Debugging" inside of Visual Studio Code.

As with when you debug against Minecraft clients, you can set breakpoints in your code by clicking on the left-hand side of the editor, on specific lines of code.

#### Minecraft Debugger Home Panel
The Activity Bar icon ![image](/icons/creeper_icon.png) will open the Minecraft Debugger home panel. Here you will find shortcuts for common actions, like opening Settings and showing the Diagnostics panel.

##### Minecraft Command Shortcuts
Add shortcuts for your favorite Minecraft commands.

##### Script Profiler
After setting a local path for saving captures, use `Start Profiler` and `Stop Profiler` to create performance captures of your actively running add-on.

#### Diagnostics Window
When attatched to a game server running Minecraft 1.21.10 or above, the debugger can display high level statistics to help diagnost performance issues.

Expand Down
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@
],
"configuration": {
"properties": {
"minecraft-debugger.reloadOnSourceChanges.enabled": {
"type": "boolean",
"default": false,
"description": "Reload Minecraft on source changes. Watch localRoot or sourceMapRoot."
},
"minecraft-debugger.reloadOnSourceChanges.delay": {
"type": "number",
"default": 100,
"description": "Delay in milliseconds between detecting source changes and reloading Minecraft."
},
"minecraft-debugger.reloadOnSourceChanges.globPattern": {
"type": "string",
"default": "",
"description": "Override the default locations and monitor any workspace files that match the glob pattern."
},
"minecraft-debugger.showDiagnosticViewOnConnect": {
"type": "boolean",
"default": false,
Expand Down
85 changes: 63 additions & 22 deletions src/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
InputBoxOptions,
QuickPickItem,
QuickPickOptions,
RelativePattern,
Uri,
workspace,
window,
Expand Down Expand Up @@ -47,20 +48,20 @@

interface PluginDetails {
name: string;
module_uuid: string;

Check warning on line 51 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Type Property name `module_uuid` must match one of the following formats: camelCase
}

interface ProtocolCapabilities {
type: string;
version: number;
plugins: PluginDetails[];
require_passcode?: boolean;

Check warning on line 58 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Type Property name `require_passcode` must match one of the following formats: camelCase
}

interface ProfilerCapture {
type: string;
capture_base_path: string;

Check warning on line 63 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Type Property name `capture_base_path` must match one of the following formats: camelCase
capture_data: string;

Check warning on line 64 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Type Property name `capture_data` must match one of the following formats: camelCase
}

// Interface for specific launch arguments.
Expand Down Expand Up @@ -99,10 +100,10 @@
// 4 - mc can require a passcode to connect
// 5 - debugger can take mc script profiler captures
enum ProtocolVersion {
_Unknown = 0,

Check warning on line 103 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `_Unknown` trimmed as `Unknown` must match one of the following formats: camelCase
Initial = 1,

Check warning on line 104 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `Initial` must match one of the following formats: camelCase
SupportTargetModuleUuid = 2,

Check warning on line 105 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `SupportTargetModuleUuid` must match one of the following formats: camelCase
SupportTargetSelection = 3,

Check warning on line 106 in src/Session.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Enum Member name `SupportTargetSelection` must match one of the following formats: camelCase
SupportPasscode = 4,
SupportProfilerCaptures = 5,
}
Expand All @@ -128,7 +129,7 @@
private _threads = new Set<number>();
private _requests = new Map<number, PendingResponse>();
private _sourceMaps: SourceMaps = new SourceMaps('');
private _fileWatcher?: FileSystemWatcher;
private _sourceFileWatcher?: FileSystemWatcher;
private _activeThreadId: number = 0; // the one being debugged
private _localRoot: string = '';
private _sourceMapRoot?: string;
Expand Down Expand Up @@ -167,6 +168,11 @@
this._eventEmitter.removeAllListeners('start-profiler');
this._eventEmitter.removeAllListeners('stop-profiler');
this._eventEmitter.removeAllListeners('request-debugger-status');

if (this._sourceFileWatcher) {
this._sourceFileWatcher.dispose();
this._sourceFileWatcher = undefined;
}
}

// ------------------------------------------------------------------------
Expand Down Expand Up @@ -661,7 +667,7 @@
);

// watch for source map changes
this.createSourceMapFileWatcher(this._sourceMapRoot);
this.createSourceMapFileWatcher(this._localRoot, this._sourceMapRoot);

// Now that a connection is established, and capabilities have been delivered, send this event to
// tell VSCode to ask Minecraft/debugee for config data (breakpoints etc).
Expand Down Expand Up @@ -689,11 +695,6 @@
this._connectionSocket.destroy();
}
this._connectionSocket = undefined;

if (this._fileWatcher) {
this._fileWatcher.dispose();
this._fileWatcher = undefined;
}
}

// close and terminate session (could be from debugee request)
Expand Down Expand Up @@ -1015,22 +1016,62 @@
}
}

private createSourceMapFileWatcher(sourceMapRoot?: string) {
if (this._fileWatcher) {
this._fileWatcher.dispose();
this._fileWatcher = undefined;
private createSourceMapFileWatcher(localRoot: string, sourceMapRoot?: string) {
if (this._sourceFileWatcher) {
this._sourceFileWatcher.dispose();
this._sourceFileWatcher = undefined;
}
if (sourceMapRoot) {
this._fileWatcher = workspace.createFileSystemWatcher('**/*.{map}', false, false, false);
this._fileWatcher.onDidChange(uri => {
this._sourceMaps.reset();
});
this._fileWatcher.onDidCreate(uri => {
this._sourceMaps.reset();
});
this._fileWatcher.onDidDelete(uri => {
this._sourceMaps.reset();
});

const config = workspace.getConfiguration('minecraft-debugger');
const reloadOnSourceChangesEnabled = config.get<boolean>('reloadOnSourceChanges.enabled');
if (!reloadOnSourceChangesEnabled) {
return;
}

const reloadOnSourceChangesDelay = Math.max(config.get<number>('reloadOnSourceChanges.delay') || 0, 0);
const reloadOnSourceChangesGlobPattern = config.get<string>('reloadOnSourceChanges.globPattern');

// Either monitor the build output (TS->JS) by looking at .map and .js files in sourceMapRoot,
// or monitor .js files directly if not using TS or source maps by looking at localRoot,
// or monitor a specific glob pattern for all files within the workspace.
let globPattern: RelativePattern | undefined = undefined;
if (reloadOnSourceChangesGlobPattern) {
const workspaceFolders = workspace.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) {
globPattern = new RelativePattern(workspaceFolders[0].uri.fsPath || '', reloadOnSourceChangesGlobPattern);
}
}
else if (sourceMapRoot) {
globPattern = new RelativePattern(sourceMapRoot, '**/*.{map,js}');
}
else if (localRoot) {
globPattern = new RelativePattern(localRoot, '**/*.js');
}

if (globPattern) {
this._sourceFileWatcher = workspace.createFileSystemWatcher(globPattern, false, false, false);
}

const doReload = (): void => {
this._sourceMaps.clearCache();
this.onRunMinecraftCommand('say §aPerforming Auto-Reload');
this.onRunMinecraftCommand('reload');
};

const debounce = (func: () => void, wait: number): (() => void) => {
let timeout: NodeJS.Timeout;
return () => {
clearTimeout(timeout);
timeout = setTimeout(func, wait);
};
};
Comment on lines +1061 to +1067
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: extra to helper function?


const debouncedReload = debounce(doReload, reloadOnSourceChangesDelay);

if (this._sourceFileWatcher) {
this._sourceFileWatcher.onDidChange(debouncedReload);
this._sourceFileWatcher.onDidCreate(debouncedReload);
this._sourceFileWatcher.onDidDelete(debouncedReload);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/SourceMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export class SourceMaps {
: SourceMapConsumer.LEAST_UPPER_BOUND;
}

public reset() {
public clearCache() {
this._sourceMapCache.reset();
}

Expand Down
4 changes: 4 additions & 0 deletions src/panels/HomeViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class HomeViewProvider implements vscode.WebviewViewProvider {
vscode.commands.executeCommand('minecraft-debugger.showMinecraftDiagnostics');
break;
}
case 'show-settings': {
vscode.commands.executeCommand('workbench.action.openSettings', '@ext:mojang-studios.minecraft-debugger');
break;
}
case 'run-minecraft-command': {
if (!message.command || message.command.trim() === '') {
vscode.window.showErrorMessage('Minecraft Command Shortcut can not be empty.');
Expand Down
11 changes: 8 additions & 3 deletions webview-ui/src/home_panel/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

// Copyright (C) Microsoft Corporation. All rights reserved.

import { useEffect, useRef, useState } from 'react';
import { useEffect, useState } from 'react';
import CommandSection from './controls/CommandSection'
import { CommandButton, CommandHandlers, getCommandHandlers } from './handlers/CommandHandlers';
import DiagnosticSection from './controls/DiagnosticsSection';
import GeneralSection from './controls/GeneralSection';
import ProfilerSection from './controls/ProfilerSection';
import { CaptureItem, ProfilerHandlers, getProfilerHandlers } from './handlers/ProfilerHandlers';
import StatusSection from './controls/StatusSection';
Expand All @@ -22,6 +22,10 @@ const onShowDiagnosticsPanel = () => {
vscode.postMessage({ type: 'show-diagnostics' });
};

const onShowSettings = () => {
vscode.postMessage({ type: 'show-settings' });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of these days we should look up how to make these events more type-safe/shared strings.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only real way is if we publish a package that specifies these, and we generate them from some metadata we spit out from the game. IMO, not worth the effort.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we converted this to a workspace we could use a shared (unpublished) package right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I think I misunderstood initially a goal to share with the debugger protocol, but this is between the extension and the webview-ui. Yes you are correct, we can solve it with an intermediate package that way.

};

const onRunCommand = (command: string) => {
vscode.postMessage({ type: 'run-minecraft-command', command: command });
};
Expand Down Expand Up @@ -121,8 +125,9 @@ const App = () => {
<StatusSection
debuggerConnected={debuggerConnected}
/>
<DiagnosticSection
<GeneralSection
onShowDiagnosticsPanel={onShowDiagnosticsPanel}
onShowSettings={onShowSettings}
/>
<CommandSection
debuggerConnected={debuggerConnected}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@
import React from 'react';
import { VSCodeButton } from '@vscode/webview-ui-toolkit/react';

interface DiagnosticsSectionProps {
interface GeneralSectionProps {
onShowDiagnosticsPanel: () => void;
onShowSettings(): void;
}

const DiagnosticsSection: React.FC<DiagnosticsSectionProps> = ({ onShowDiagnosticsPanel }) => {
const GeneralSection: React.FC<GeneralSectionProps> = ({ onShowDiagnosticsPanel, onShowSettings }) => {
return (
<div className="section">
<h3 className="title">Diagnostics</h3>
<h3 className="title">Actions</h3>
<VSCodeButton className="standard-button" onClick={onShowDiagnosticsPanel}>
Show Diagnostics
</VSCodeButton>
<VSCodeButton className="standard-button" onClick={onShowSettings}>
Show Settings
</VSCodeButton>
</div>
);
};

export default DiagnosticsSection;
export default GeneralSection;
Loading