mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Refactor Debugger to use a PreviewDebuggerServer abstraction
This will allow the PreviewLaunchers to also start this debugger server (and not only give this ability to the Debugger editor tab). In the future, this also allow the web-app to have a different implementation of a debugger server. Don't show in changelog
This commit is contained in:

committed by
Florian Rival

parent
9c6972ec0a
commit
6326c185f4
@@ -3,7 +3,7 @@ import { t } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import SelectField from '../UI/SelectField';
|
||||
import SelectOption from '../UI/SelectOption';
|
||||
import { type DebuggerId } from '.';
|
||||
import { type DebuggerId } from '../Export/PreviewLauncher.flow';
|
||||
|
||||
type Props = {|
|
||||
selectedId: DebuggerId,
|
||||
|
@@ -10,13 +10,11 @@ import Text from '../UI/Text';
|
||||
import PlaceholderLoader from '../UI/PlaceholderLoader';
|
||||
import PlaceholderMessage from '../UI/PlaceholderMessage';
|
||||
import Background from '../UI/Background';
|
||||
import optionalRequire from '../Utils/OptionalRequire';
|
||||
import EmptyMessage from '../UI/EmptyMessage';
|
||||
const electron = optionalRequire('electron');
|
||||
const ipcRenderer = electron ? electron.ipcRenderer : null;
|
||||
|
||||
//Each game connected to the debugger server is identified by a unique number
|
||||
export type DebuggerId = number;
|
||||
import {
|
||||
type PreviewDebuggerServer,
|
||||
type DebuggerId,
|
||||
} from '../Export/PreviewLauncher.flow';
|
||||
|
||||
export type ProfilerMeasuresSection = {|
|
||||
time: number,
|
||||
@@ -34,13 +32,15 @@ type Props = {|
|
||||
project: gdProject,
|
||||
setToolbar: React.Node => void,
|
||||
isActive: boolean,
|
||||
previewDebuggerServer: PreviewDebuggerServer,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
debuggerServerStarted: boolean,
|
||||
debuggerServerState: 'started' | 'stopped',
|
||||
debuggerServerError: ?any,
|
||||
|
||||
debuggerIds: Array<DebuggerId>,
|
||||
unregisterDebuggerServerCallbacks: ?() => void,
|
||||
|
||||
debuggerGameData: { [DebuggerId]: any },
|
||||
profilerOutputs: { [DebuggerId]: ProfilerOutput },
|
||||
profilingInProgress: { [DebuggerId]: boolean },
|
||||
@@ -53,9 +53,10 @@ type State = {|
|
||||
*/
|
||||
export default class Debugger extends React.Component<Props, State> {
|
||||
state = {
|
||||
debuggerServerStarted: false,
|
||||
debuggerServerState: this.props.previewDebuggerServer.getServerState(),
|
||||
debuggerServerError: null,
|
||||
debuggerIds: [],
|
||||
debuggerIds: this.props.previewDebuggerServer.getExistingDebuggerIds(),
|
||||
unregisterDebuggerServerCallbacks: null,
|
||||
debuggerGameData: {},
|
||||
profilerOutputs: {},
|
||||
profilingInProgress: {},
|
||||
@@ -94,88 +95,71 @@ export default class Debugger extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._removeServerListeners();
|
||||
if (this.state.unregisterDebuggerServerCallbacks) {
|
||||
this.state.unregisterDebuggerServerCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
_removeServerListeners = () => {
|
||||
if (!ipcRenderer) return;
|
||||
|
||||
ipcRenderer.removeAllListeners('debugger-send-message-done');
|
||||
ipcRenderer.removeAllListeners('debugger-error-received');
|
||||
ipcRenderer.removeAllListeners('debugger-connection-closed');
|
||||
ipcRenderer.removeAllListeners('debugger-connection-opened');
|
||||
ipcRenderer.removeAllListeners('debugger-start-server-done');
|
||||
ipcRenderer.removeAllListeners('debugger-message-received');
|
||||
};
|
||||
|
||||
_startServer = () => {
|
||||
if (!ipcRenderer) return;
|
||||
const { previewDebuggerServer } = this.props;
|
||||
const { unregisterDebuggerServerCallbacks } = this.state;
|
||||
if (
|
||||
unregisterDebuggerServerCallbacks &&
|
||||
previewDebuggerServer.getServerState() === 'started'
|
||||
)
|
||||
return; // Server already started and callbacks registered
|
||||
|
||||
this.setState({
|
||||
debuggerServerStarted: false,
|
||||
});
|
||||
this._removeServerListeners();
|
||||
if (unregisterDebuggerServerCallbacks) unregisterDebuggerServerCallbacks(); // Unregister old callbacks, if any
|
||||
|
||||
ipcRenderer.on('debugger-error-received', (event, err) => {
|
||||
this.setState(
|
||||
{
|
||||
debuggerServerError: err,
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.on('debugger-connection-closed', (event, { id }) => {
|
||||
const { debuggerIds, selectedId } = this.state;
|
||||
const remainingDebuggerIds = debuggerIds.filter(
|
||||
debuggerId => debuggerId !== id
|
||||
);
|
||||
this.setState(
|
||||
{
|
||||
debuggerIds: remainingDebuggerIds,
|
||||
selectedId:
|
||||
selectedId !== id
|
||||
? selectedId
|
||||
: remainingDebuggerIds.length
|
||||
? remainingDebuggerIds[remainingDebuggerIds.length - 1]
|
||||
: selectedId,
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.on('debugger-connection-opened', (event, { id }) => {
|
||||
this.setState(
|
||||
{
|
||||
debuggerIds: [...this.state.debuggerIds, id],
|
||||
selectedId: id,
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.on('debugger-start-server-done', event => {
|
||||
this.setState(
|
||||
{
|
||||
debuggerServerStarted: true,
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.on('debugger-message-received', (event, { id, message }) => {
|
||||
console.log('Processing message received for debugger');
|
||||
try {
|
||||
const data = JSON.parse(message);
|
||||
this._handleMessage(id, data);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'Error while parsing message received from debugger client:',
|
||||
e
|
||||
// Register new callbacks
|
||||
const unregisterCallbacks = previewDebuggerServer.registerCallbacks({
|
||||
onErrorReceived: err => {
|
||||
this.setState(
|
||||
{
|
||||
debuggerServerError: err,
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
}
|
||||
},
|
||||
onConnectionClosed: ({ id, debuggerIds }) => {
|
||||
this.setState(
|
||||
({ selectedId }) => ({
|
||||
debuggerIds,
|
||||
selectedId:
|
||||
selectedId !== id
|
||||
? selectedId
|
||||
: debuggerIds.length
|
||||
? debuggerIds[debuggerIds.length - 1]
|
||||
: selectedId,
|
||||
}),
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
},
|
||||
onConnectionOpened: ({ id, debuggerIds }) => {
|
||||
this.setState(
|
||||
{
|
||||
debuggerIds,
|
||||
selectedId: id,
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
},
|
||||
onServerStateChanged: () => {
|
||||
this.setState(
|
||||
{
|
||||
debuggerServerState: previewDebuggerServer.getServerState(),
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
},
|
||||
onHandleParsedMessage: ({ id, parsedMessage }) => {
|
||||
this._handleMessage(id, parsedMessage);
|
||||
},
|
||||
});
|
||||
previewDebuggerServer.startServer();
|
||||
this.setState({
|
||||
unregisterDebuggerServerCallbacks: unregisterCallbacks,
|
||||
});
|
||||
ipcRenderer.send('debugger-start-server');
|
||||
};
|
||||
|
||||
_handleMessage = (id: DebuggerId, data: any) => {
|
||||
@@ -210,42 +194,26 @@ export default class Debugger extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
_play = (id: DebuggerId) => {
|
||||
if (!ipcRenderer) return;
|
||||
|
||||
ipcRenderer.send('debugger-send-message', {
|
||||
id,
|
||||
message: '{"command": "play"}',
|
||||
});
|
||||
const { previewDebuggerServer } = this.props;
|
||||
previewDebuggerServer.sendMessage(id, { command: 'play' });
|
||||
};
|
||||
|
||||
_pause = (id: DebuggerId) => {
|
||||
if (!ipcRenderer) return;
|
||||
|
||||
ipcRenderer.send('debugger-send-message', {
|
||||
id,
|
||||
message: '{"command": "pause"}',
|
||||
});
|
||||
const { previewDebuggerServer } = this.props;
|
||||
previewDebuggerServer.sendMessage(id, { command: 'pause' });
|
||||
};
|
||||
|
||||
_refresh = (id: DebuggerId) => {
|
||||
if (!ipcRenderer) return;
|
||||
|
||||
ipcRenderer.send('debugger-send-message', {
|
||||
id,
|
||||
message: '{"command": "refresh"}',
|
||||
});
|
||||
const { previewDebuggerServer } = this.props;
|
||||
previewDebuggerServer.sendMessage(id, { command: 'refresh' });
|
||||
};
|
||||
|
||||
_edit = (id: DebuggerId, path: Array<string>, newValue: any) => {
|
||||
if (!ipcRenderer) return false;
|
||||
|
||||
ipcRenderer.send('debugger-send-message', {
|
||||
id,
|
||||
message: JSON.stringify({
|
||||
command: 'set',
|
||||
path,
|
||||
newValue,
|
||||
}),
|
||||
const { previewDebuggerServer } = this.props;
|
||||
previewDebuggerServer.sendMessage(id, {
|
||||
command: 'set',
|
||||
path,
|
||||
newValue,
|
||||
});
|
||||
|
||||
setTimeout(() => this._refresh(id), 100);
|
||||
@@ -253,37 +221,28 @@ export default class Debugger extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
_call = (id: DebuggerId, path: Array<string>, args: Array<any>) => {
|
||||
if (!ipcRenderer) return false;
|
||||
|
||||
ipcRenderer.send('debugger-send-message', {
|
||||
const { previewDebuggerServer } = this.props;
|
||||
previewDebuggerServer.sendMessage(
|
||||
id,
|
||||
message: JSON.stringify({
|
||||
JSON.stringify({
|
||||
command: 'call',
|
||||
path,
|
||||
args,
|
||||
}),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
setTimeout(() => this._refresh(id), 100);
|
||||
return true;
|
||||
};
|
||||
|
||||
_startProfiler = (id: DebuggerId) => {
|
||||
if (!ipcRenderer) return;
|
||||
|
||||
ipcRenderer.send('debugger-send-message', {
|
||||
id,
|
||||
message: '{"command": "profiler.start"}',
|
||||
});
|
||||
const { previewDebuggerServer } = this.props;
|
||||
previewDebuggerServer.sendMessage(id, { command: 'profiler.start' });
|
||||
};
|
||||
|
||||
_stopProfiler = (id: DebuggerId) => {
|
||||
if (!ipcRenderer) return;
|
||||
|
||||
ipcRenderer.send('debugger-send-message', {
|
||||
id,
|
||||
message: '{"command": "profiler.stop"}',
|
||||
});
|
||||
const { previewDebuggerServer } = this.props;
|
||||
previewDebuggerServer.sendMessage(id, { command: 'profiler.stop' });
|
||||
};
|
||||
|
||||
_hasSelectedDebugger = () => {
|
||||
@@ -294,7 +253,7 @@ export default class Debugger extends React.Component<Props, State> {
|
||||
render() {
|
||||
const {
|
||||
debuggerServerError,
|
||||
debuggerServerStarted,
|
||||
debuggerServerState,
|
||||
selectedId,
|
||||
debuggerIds,
|
||||
debuggerGameData,
|
||||
@@ -304,7 +263,7 @@ export default class Debugger extends React.Component<Props, State> {
|
||||
|
||||
return (
|
||||
<Background>
|
||||
{!debuggerServerStarted && !debuggerServerError && (
|
||||
{debuggerServerState === 'stopped' && !debuggerServerError && (
|
||||
<PlaceholderMessage>
|
||||
<PlaceholderLoader />
|
||||
<Text>
|
||||
@@ -312,7 +271,7 @@ export default class Debugger extends React.Component<Props, State> {
|
||||
</Text>
|
||||
</PlaceholderMessage>
|
||||
)}
|
||||
{!debuggerServerStarted && debuggerServerError && (
|
||||
{debuggerServerState === 'stopped' && debuggerServerError && (
|
||||
<PlaceholderMessage>
|
||||
<Text>
|
||||
<Trans>
|
||||
@@ -322,7 +281,7 @@ export default class Debugger extends React.Component<Props, State> {
|
||||
</Text>
|
||||
</PlaceholderMessage>
|
||||
)}
|
||||
{debuggerServerStarted && (
|
||||
{debuggerServerState === 'started' && (
|
||||
<Column expand noMargin>
|
||||
<DebuggerSelector
|
||||
selectedId={selectedId}
|
||||
|
@@ -0,0 +1,110 @@
|
||||
// @flow
|
||||
import optionalRequire from '../../../Utils/OptionalRequire';
|
||||
import {
|
||||
type PreviewDebuggerServerCallbacks,
|
||||
type PreviewDebuggerServer,
|
||||
type DebuggerId,
|
||||
} from '../../PreviewLauncher.flow';
|
||||
const electron = optionalRequire('electron');
|
||||
const ipcRenderer = electron ? electron.ipcRenderer : null;
|
||||
|
||||
let debuggerServerState: 'started' | 'stopped' = 'stopped';
|
||||
const callbacksList: Array<PreviewDebuggerServerCallbacks> = [];
|
||||
const debuggerIds: Array<DebuggerId> = [];
|
||||
|
||||
const removeServerListeners = () => {
|
||||
if (!ipcRenderer) return;
|
||||
|
||||
ipcRenderer.removeAllListeners('debugger-send-message-done');
|
||||
ipcRenderer.removeAllListeners('debugger-error-received');
|
||||
ipcRenderer.removeAllListeners('debugger-connection-closed');
|
||||
ipcRenderer.removeAllListeners('debugger-connection-opened');
|
||||
ipcRenderer.removeAllListeners('debugger-start-server-done');
|
||||
ipcRenderer.removeAllListeners('debugger-message-received');
|
||||
};
|
||||
|
||||
/**
|
||||
* A debugger server implemented using Electron (this one is just a bridge to it,
|
||||
* communicating through events with it).
|
||||
*/
|
||||
export const LocalPreviewDebuggerServer: PreviewDebuggerServer = {
|
||||
startServer: () => {
|
||||
if (!ipcRenderer) return;
|
||||
if (debuggerServerState === 'started') return;
|
||||
|
||||
debuggerServerState = 'stopped';
|
||||
removeServerListeners();
|
||||
|
||||
ipcRenderer.on('debugger-error-received', (event, err) => {
|
||||
callbacksList.forEach(({ onErrorReceived }) => onErrorReceived(err));
|
||||
});
|
||||
|
||||
ipcRenderer.on('debugger-connection-closed', (event, { id }) => {
|
||||
const debuggerIdIndex = debuggerIds.indexOf(id);
|
||||
if (debuggerIdIndex !== -1) debuggerIds.splice(debuggerIdIndex, 1);
|
||||
|
||||
callbacksList.forEach(({ onConnectionClosed }) =>
|
||||
onConnectionClosed({
|
||||
id,
|
||||
debuggerIds,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.on('debugger-connection-opened', (event, { id }) => {
|
||||
debuggerIds.push(id);
|
||||
callbacksList.forEach(({ onConnectionOpened }) =>
|
||||
onConnectionOpened({
|
||||
id,
|
||||
debuggerIds,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.on('debugger-start-server-done', event => {
|
||||
console.info('Local preview debugger started');
|
||||
debuggerServerState = 'started';
|
||||
callbacksList.forEach(({ onServerStateChanged }) =>
|
||||
onServerStateChanged()
|
||||
);
|
||||
});
|
||||
|
||||
ipcRenderer.on('debugger-message-received', (event, { id, message }) => {
|
||||
console.info('Processing message received for debugger');
|
||||
try {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
callbacksList.forEach(({ onHandleParsedMessage }) =>
|
||||
onHandleParsedMessage({ id, parsedMessage })
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'Error while parsing message received from debugger client:',
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
ipcRenderer.send('debugger-start-server');
|
||||
},
|
||||
sendMessage: (id: DebuggerId, message: Object) => {
|
||||
if (!ipcRenderer) return;
|
||||
if (debuggerServerState === 'stopped') {
|
||||
console.error('Cannot send message when debugger server is stopped.');
|
||||
return;
|
||||
}
|
||||
|
||||
ipcRenderer.send('debugger-send-message', {
|
||||
id,
|
||||
message: JSON.stringify(message),
|
||||
});
|
||||
},
|
||||
getServerState: () => debuggerServerState,
|
||||
getExistingDebuggerIds: () => debuggerIds,
|
||||
registerCallbacks: (callbacks: PreviewDebuggerServerCallbacks) => {
|
||||
callbacksList.push(callbacks);
|
||||
|
||||
return () => {
|
||||
const callbacksIndex = callbacksList.indexOf(callbacks);
|
||||
if (callbacksIndex !== -1) callbacksList.splice(callbacksIndex, 1);
|
||||
};
|
||||
},
|
||||
};
|
@@ -11,6 +11,7 @@ import assignIn from 'lodash/assignIn';
|
||||
import { type PreviewOptions } from '../../PreviewLauncher.flow';
|
||||
import { findLocalIp } from './LocalIpFinder';
|
||||
import SubscriptionChecker from '../../../Profile/SubscriptionChecker';
|
||||
import { LocalPreviewDebuggerServer } from './LocalPreviewDebuggerServer';
|
||||
const electron = optionalRequire('electron');
|
||||
const path = optionalRequire('path');
|
||||
const ipcRenderer = electron ? electron.ipcRenderer : null;
|
||||
@@ -175,6 +176,8 @@ export default class LocalPreviewLauncher extends React.Component<
|
||||
});
|
||||
};
|
||||
|
||||
getPreviewDebuggerServer() { return LocalPreviewDebuggerServer; }
|
||||
|
||||
_checkSubscription = () => {
|
||||
if (!this._subscriptionChecker) return true;
|
||||
|
||||
|
@@ -12,6 +12,7 @@ export type PreviewOptions = {|
|
||||
export type PreviewLauncherInterface = {
|
||||
launchPreview: (options: PreviewOptions) => Promise<any>,
|
||||
canDoNetworkPreview: () => boolean,
|
||||
+getPreviewDebuggerServer: () => PreviewDebuggerServer,
|
||||
};
|
||||
|
||||
/** The props that PreviewLauncher must support */
|
||||
@@ -28,3 +29,30 @@ export type PreviewLauncherComponent = React.AbstractComponent<
|
||||
PreviewLauncherProps,
|
||||
PreviewLauncherInterface
|
||||
>;
|
||||
|
||||
/** Each game connected to the debugger server is identified by a unique number. */
|
||||
export type DebuggerId = number;
|
||||
|
||||
/** The callbacks for a debugger server used for previews. */
|
||||
export type PreviewDebuggerServerCallbacks = {|
|
||||
onErrorReceived: (err: Error) => void,
|
||||
onServerStateChanged: () => void,
|
||||
onConnectionClosed: ({|
|
||||
id: DebuggerId,
|
||||
debuggerIds: Array<DebuggerId>,
|
||||
|}) => void,
|
||||
onConnectionOpened: ({|
|
||||
id: DebuggerId,
|
||||
debuggerIds: Array<DebuggerId>,
|
||||
|}) => void,
|
||||
onHandleParsedMessage: ({| id: DebuggerId, parsedMessage: Object |}) => void,
|
||||
|};
|
||||
|
||||
/** Interface to run a debugger server for previews. */
|
||||
export type PreviewDebuggerServer = {|
|
||||
startServer: () => void,
|
||||
getServerState: () => 'started' | 'stopped',
|
||||
getExistingDebuggerIds: () => Array<DebuggerId>,
|
||||
sendMessage: (id: DebuggerId, message: Object) => void,
|
||||
registerCallbacks: (callbacks: PreviewDebuggerServerCallbacks) => () => void,
|
||||
|};
|
||||
|
@@ -6,6 +6,9 @@ import {
|
||||
type ResourceSource,
|
||||
type ChooseResourceFunction,
|
||||
} from '../../ResourcesList/ResourceSource.flow';
|
||||
import {
|
||||
type PreviewDebuggerServer,
|
||||
} from '../../Export/PreviewLauncher.flow';
|
||||
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
|
||||
|
||||
export type RenderEditorContainerProps = {|
|
||||
@@ -29,6 +32,7 @@ export type RenderEditorContainerProps = {|
|
||||
layoutName: ?string,
|
||||
externalLayoutName?: ?string
|
||||
) => void,
|
||||
previewDebuggerServer: PreviewDebuggerServer,
|
||||
|
||||
// Opening other editors:
|
||||
onOpenDebugger: () => void,
|
||||
|
@@ -76,6 +76,7 @@ export class DebuggerEditorContainer extends React.Component<
|
||||
project={project}
|
||||
setToolbar={this.props.setToolbar}
|
||||
isActive={this.props.isActive}
|
||||
previewDebuggerServer={this.props.previewDebuggerServer}
|
||||
ref={editor => (this.editor = editor)}
|
||||
/>
|
||||
<SubscriptionChecker
|
||||
|
@@ -1655,6 +1655,9 @@ const MainFrame = (props: Props) => {
|
||||
setPreviewedLayout,
|
||||
onOpenDebugger: openDebugger,
|
||||
onOpenExternalEvents: openExternalEvents,
|
||||
previewDebuggerServer:
|
||||
_previewLauncher.current &&
|
||||
_previewLauncher.current.getPreviewDebuggerServer(),
|
||||
onOpenLayout: name =>
|
||||
openLayout(name, {
|
||||
openEventsEditor: true,
|
||||
|
Reference in New Issue
Block a user