[Editor] Better expose EditorDebuggerPlugin.

Now splitted into two classes:
- EditorDebuggerPlugin (RefCounted).
- EditorDebuggerSession (abstract).

This allows the EditorPlugin to be in control of the debugger plugin
lifecycle, be notified when sessions are created, and customize each of
them independently.

We should slowly transition the various profilers and captures in
ScriptEditorDebugger to their own plugins, and decouple
ScriptEditorDebugger from it's UI part (making it the "real"
EditorDebuggerSession potentially dropping the wrappers).
This commit is contained in:
Fabio Alessandrelli 2022-10-03 15:21:25 +02:00
parent 471c42ee1f
commit d568b25e36
12 changed files with 371 additions and 202 deletions

View file

@ -1,88 +1,88 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="EditorDebuggerPlugin" inherits="Control" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="EditorDebuggerPlugin" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A base class to implement debugger plugins.
</brief_description>
<description>
[EditorDebuggerPlugin] provides functions related to the editor side of the debugger.
You don't need to instantiate this class; that is automatically handled by the debugger. [Control] nodes can be added as child nodes to provide a GUI for the plugin.
Do not free or reparent this node, otherwise it becomes unusable.
To use [EditorDebuggerPlugin], register it using the [method EditorPlugin.add_debugger_plugin] method first.
To interact with the debugger, an instance of this class must be added to the editor via [method EditorPlugin.add_debugger_plugin].
Once added, the [method _setup_session] callback will be called for every [EditorDebuggerSession] available to the plugin, and when new ones are created (the sessions may be inactive during this stage).
You can retrieve the available [EditorDebuggerSession]s via [method get_sessions] or get a specific one via [method get_session].
[codeblocks]
[gdscript]
@tool
extends EditorPlugin
class ExampleEditorDebugger extends EditorDebuggerPlugin:
func _has_capture(prefix):
# Return true if you wish to handle message with this prefix.
return prefix == "my_plugin"
func _capture(message, data, session_id):
if message == "my_plugin:ping":
get_session(session_id).send_message("my_plugin:echo", data)
func _setup_session(session_id):
# Add a new tab in the debugger session UI containing a label.
var label = Label.new()
label.name = "Example plugin"
label.text = "Example plugin"
var session = get_session(session_id)
# Listens to the session started and stopped signals.
session.started.connect(func (): print("Session started"))
session.stopped.connect(func (): print("Session stopped"))
session.add_session_tab(label)
var debugger = ExampleEditorDebugger.new()
func _enter_tree():
add_debugger_plugin(debugger)
func _exit_tree():
remove_debugger_plugin(debugger)
[/gdscript]
[/codeblocks]
</description>
<tutorials>
</tutorials>
<methods>
<method name="has_capture">
<method name="_capture" qualifiers="virtual">
<return type="bool" />
<param index="0" name="name" type="StringName" />
<description>
Returns [code]true[/code] if a message capture with given name is present otherwise [code]false[/code].
</description>
</method>
<method name="is_breaked">
<return type="bool" />
<description>
Returns [code]true[/code] if the game is in break state otherwise [code]false[/code].
</description>
</method>
<method name="is_debuggable">
<return type="bool" />
<description>
Returns [code]true[/code] if the game can be debugged otherwise [code]false[/code].
</description>
</method>
<method name="is_session_active">
<return type="bool" />
<description>
Returns [code]true[/code] if there is an instance of the game running with the attached debugger otherwise [code]false[/code].
</description>
</method>
<method name="register_message_capture">
<return type="void" />
<param index="0" name="name" type="StringName" />
<param index="1" name="callable" type="Callable" />
<description>
Registers a message capture with given [param name]. If [param name] is "my_message" then messages starting with "my_message:" will be called with the given callable.
Callable must accept a message string and a data array as argument. If the message and data are valid then callable must return [code]true[/code] otherwise [code]false[/code].
</description>
</method>
<method name="send_message">
<return type="void" />
<param index="0" name="message" type="String" />
<param index="1" name="data" type="Array" />
<param index="2" name="session_id" type="int" />
<description>
Sends a message with given [param message] and [param data] array.
Override this method to process incoming messages. The [param session_id] is the ID of the [EditorDebuggerSession] that received the message (which you can retrieve via [method get_session]).
</description>
</method>
<method name="unregister_message_capture">
<return type="void" />
<param index="0" name="name" type="StringName" />
<method name="_has_capture" qualifiers="virtual const">
<return type="bool" />
<param index="0" name="capture" type="String" />
<description>
Unregisters the message capture with given name.
Override this method to enable receiving messages from the debugger. If [param capture] is "my_message" then messages starting with "my_message:" will be passes to the [method _capture] method.
</description>
</method>
<method name="_setup_session" qualifiers="virtual">
<return type="void" />
<param index="0" name="session_id" type="int" />
<description>
Override this method to be notified whenever a new [EditorDebuggerSession] is created (the session may be inactive during this stage).
</description>
</method>
<method name="get_session">
<return type="EditorDebuggerSession" />
<param index="0" name="id" type="int" />
<description>
Returns the [EditorDebuggerSession] with the given [param id].
</description>
</method>
<method name="get_sessions">
<return type="Array" />
<description>
Returns an array of [EditorDebuggerSession] currently available to this debugger plugin.
Note: Not sessions in the array may be inactive, check their state via [method EditorDebuggerSession.is_active]
</description>
</method>
</methods>
<signals>
<signal name="breaked">
<param index="0" name="can_debug" type="bool" />
<description>
Emitted when the game enters a break state.
</description>
</signal>
<signal name="continued">
<description>
Emitted when the game exists a break state.
</description>
</signal>
<signal name="started">
<description>
Emitted when the debugging starts.
</description>
</signal>
<signal name="stopped">
<description>
Emitted when the debugging stops.
</description>
</signal>
</signals>
</class>

View file

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="EditorDebuggerSession" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A class to interact with the editor debugger.
</brief_description>
<description>
This class cannot be directly instantiated and must be retrieved via a [EditorDebuggerPlugin].
You can add tabs to the session UI via [method add_session_tab], send messages via [method send_message], and toggle [EngineProfiler]s via [method toggle_profiler].
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_session_tab">
<return type="void" />
<param index="0" name="control" type="Control" />
<description>
Adds the given [param control] to the debug session UI in the debugger bottom panel.
</description>
</method>
<method name="is_active">
<return type="bool" />
<description>
Returns [code]true[/code] if the debug session is currently attached to a remote instance.
</description>
</method>
<method name="is_breaked">
<return type="bool" />
<description>
Returns [code]true[/code] if the attached remote instance is currently in the debug loop.
</description>
</method>
<method name="is_debuggable">
<return type="bool" />
<description>
Returns [code]true[/code] if the attached remote instance can be debugged.
</description>
</method>
<method name="remove_session_tab">
<return type="void" />
<param index="0" name="control" type="Control" />
<description>
Removes the given [param control] from the debug session UI in the debugger bottom panel.
</description>
</method>
<method name="send_message">
<return type="void" />
<param index="0" name="message" type="String" />
<param index="1" name="data" type="Array" default="[]" />
<description>
Sends the given [param message] to the attached remote instance, optionally passing additionally [param data]. See [EngineDebugger] for how to retrieve those messages.
</description>
</method>
<method name="toggle_profiler">
<return type="void" />
<param index="0" name="profiler" type="String" />
<param index="1" name="enable" type="bool" />
<param index="2" name="data" type="Array" default="[]" />
<description>
Toggle the given [param profiler] on the attached remote instance, optionally passing additionally [param data]. See [EngineProfiler] for more details.
</description>
</method>
</methods>
<signals>
<signal name="breaked">
<param index="0" name="can_debug" type="bool" />
<description>
Emitted when the attached remote instance enters a break state. If [param can_debug] is [code]true[/code], the remote instance will enter the debug loop.
</description>
</signal>
<signal name="continued">
<description>
Emitted when the attached remote instance exits a break state.
</description>
</signal>
<signal name="started">
<description>
Emitted when a remote instance is attached to this session (i.e. the session becomes active).
</description>
</signal>
<signal name="stopped">
<description>
Emitted when a remote instance is detached from this session (i.e. the session becomes inactive).
</description>
</signal>
</signals>
</class>

View file

@ -415,7 +415,7 @@
</method>
<method name="add_debugger_plugin">
<return type="void" />
<param index="0" name="script" type="Script" />
<param index="0" name="script" type="EditorDebuggerPlugin" />
<description>
Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin].
</description>
@ -599,7 +599,7 @@
</method>
<method name="remove_debugger_plugin">
<return type="void" />
<param index="0" name="script" type="Script" />
<param index="0" name="script" type="EditorDebuggerPlugin" />
<description>
Removes the debugger plugin with given script from the Debugger.
</description>

View file

@ -119,8 +119,8 @@ ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() {
}
if (!debugger_plugins.is_empty()) {
for (const Ref<Script> &i : debugger_plugins) {
node->add_debugger_plugin(i);
for (Ref<EditorDebuggerPlugin> plugin : debugger_plugins) {
plugin->create_session(node);
}
}
@ -723,22 +723,36 @@ EditorDebuggerNode::CameraOverride EditorDebuggerNode::get_camera_override() {
return camera_override;
}
void EditorDebuggerNode::add_debugger_plugin(const Ref<Script> &p_script) {
ERR_FAIL_COND_MSG(debugger_plugins.has(p_script), "Debugger plugin already exists.");
ERR_FAIL_COND_MSG(p_script.is_null(), "Debugger plugin script is null");
ERR_FAIL_COND_MSG(p_script->get_instance_base_type() == StringName(), "Debugger plugin script has error.");
ERR_FAIL_COND_MSG(String(p_script->get_instance_base_type()) != "EditorDebuggerPlugin", "Base type of debugger plugin is not 'EditorDebuggerPlugin'.");
ERR_FAIL_COND_MSG(!p_script->is_tool(), "Debugger plugin script is not in tool mode.");
debugger_plugins.insert(p_script);
void EditorDebuggerNode::add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) {
ERR_FAIL_COND_MSG(p_plugin.is_null(), "Debugger plugin is null.");
ERR_FAIL_COND_MSG(debugger_plugins.has(p_plugin), "Debugger plugin already exists.");
debugger_plugins.insert(p_plugin);
Ref<EditorDebuggerPlugin> plugin = p_plugin;
for (int i = 0; get_debugger(i); i++) {
get_debugger(i)->add_debugger_plugin(p_script);
plugin->create_session(get_debugger(i));
}
}
void EditorDebuggerNode::remove_debugger_plugin(const Ref<Script> &p_script) {
ERR_FAIL_COND_MSG(!debugger_plugins.has(p_script), "Debugger plugin doesn't exists.");
debugger_plugins.erase(p_script);
for (int i = 0; get_debugger(i); i++) {
get_debugger(i)->remove_debugger_plugin(p_script);
}
void EditorDebuggerNode::remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) {
ERR_FAIL_COND_MSG(p_plugin.is_null(), "Debugger plugin is null.");
ERR_FAIL_COND_MSG(!debugger_plugins.has(p_plugin), "Debugger plugin doesn't exists.");
debugger_plugins.erase(p_plugin);
Ref<EditorDebuggerPlugin>(p_plugin)->clear();
}
bool EditorDebuggerNode::plugins_capture(ScriptEditorDebugger *p_debugger, const String &p_message, const Array &p_data) {
int session_index = tabs->get_tab_idx_from_control(p_debugger);
ERR_FAIL_COND_V(session_index < 0, false);
int colon_index = p_message.find_char(':');
ERR_FAIL_COND_V_MSG(colon_index < 1, false, "Invalid message received.");
const String cap = p_message.substr(0, colon_index);
bool parsed = false;
for (Ref<EditorDebuggerPlugin> plugin : debugger_plugins) {
if (plugin->has_capture(cap)) {
parsed |= plugin->capture(p_message, p_data, session_index);
}
}
return parsed;
}

View file

@ -36,6 +36,7 @@
class Button;
class DebugAdapterParser;
class EditorDebuggerPlugin;
class EditorDebuggerTree;
class EditorDebuggerRemoteObject;
class MenuButton;
@ -113,7 +114,7 @@ private:
CameraOverride camera_override = OVERRIDE_NONE;
HashMap<Breakpoint, bool, Breakpoint> breakpoints;
HashSet<Ref<Script>> debugger_plugins;
HashSet<Ref<EditorDebuggerPlugin>> debugger_plugins;
ScriptEditorDebugger *_add_debugger();
EditorDebuggerRemoteObject *get_inspected_remote_object();
@ -205,8 +206,9 @@ public:
Error start(const String &p_uri = "tcp://");
void stop();
void add_debugger_plugin(const Ref<Script> &p_script);
void remove_debugger_plugin(const Ref<Script> &p_script);
bool plugins_capture(ScriptEditorDebugger *p_debugger, const String &p_message, const Array &p_data);
void add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin);
void remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin);
};
#endif // EDITOR_DEBUGGER_NODE_H

View file

@ -741,22 +741,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
int colon_index = p_msg.find_char(':');
ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received");
bool parsed = false;
const String cap = p_msg.substr(0, colon_index);
HashMap<StringName, Callable>::Iterator element = captures.find(cap);
if (element) {
Callable &c = element->value;
ERR_FAIL_COND_MSG(c.is_null(), "Invalid callable registered: " + cap);
Variant cmd = p_msg.substr(colon_index + 1), cmd_data = p_data;
const Variant *args[2] = { &cmd, &cmd_data };
Variant retval;
Callable::CallError err;
c.callp(args, 2, retval, err);
ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'capture' to callable: " + Variant::get_callable_error_text(c, args, 2, err));
ERR_FAIL_COND_MSG(retval.get_type() != Variant::BOOL, "Error calling 'capture' to callable: " + String(c) + ". Return type is not bool.");
parsed = retval;
}
bool parsed = EditorDebuggerNode::get_singleton()->plugins_capture(this, p_msg, p_data);
if (!parsed) {
WARN_PRINT("unknown message " + p_msg);
}
@ -1658,41 +1643,25 @@ void ScriptEditorDebugger::_bind_methods() {
ADD_SIGNAL(MethodInfo("errors_cleared"));
}
void ScriptEditorDebugger::add_debugger_plugin(const Ref<Script> &p_script) {
if (!debugger_plugins.has(p_script)) {
EditorDebuggerPlugin *plugin = memnew(EditorDebuggerPlugin());
plugin->attach_debugger(this);
plugin->set_script(p_script);
tabs->add_child(plugin);
debugger_plugins.insert(p_script, plugin);
}
void ScriptEditorDebugger::add_debugger_tab(Control *p_control) {
tabs->add_child(p_control);
}
void ScriptEditorDebugger::remove_debugger_plugin(const Ref<Script> &p_script) {
if (debugger_plugins.has(p_script)) {
tabs->remove_child(debugger_plugins[p_script]);
debugger_plugins[p_script]->detach_debugger(false);
memdelete(debugger_plugins[p_script]);
debugger_plugins.erase(p_script);
}
void ScriptEditorDebugger::remove_debugger_tab(Control *p_control) {
int idx = tabs->get_tab_idx_from_control(p_control);
ERR_FAIL_COND(idx < 0);
p_control->queue_free();
}
void ScriptEditorDebugger::send_message(const String &p_message, const Array &p_args) {
_put_msg(p_message, p_args);
}
void ScriptEditorDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) {
ERR_FAIL_COND_MSG(has_capture(p_name), "Capture already registered: " + p_name);
captures.insert(p_name, p_callable);
}
void ScriptEditorDebugger::unregister_message_capture(const StringName &p_name) {
ERR_FAIL_COND_MSG(!has_capture(p_name), "Capture not registered: " + p_name);
captures.erase(p_name);
}
bool ScriptEditorDebugger::has_capture(const StringName &p_name) {
return captures.has(p_name);
void ScriptEditorDebugger::toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data) {
Array msg_data;
msg_data.push_back(p_enable);
msg_data.append_array(p_data);
_put_msg("profiler:" + p_profiler, msg_data);
}
ScriptEditorDebugger::ScriptEditorDebugger() {

View file

@ -163,10 +163,6 @@ private:
EditorDebuggerNode::CameraOverride camera_override;
HashMap<Ref<Script>, EditorDebuggerPlugin *> debugger_plugins;
HashMap<StringName, Callable> captures;
void _stack_dump_frame_selected();
void _file_selected(const String &p_file);
@ -286,14 +282,11 @@ public:
virtual Size2 get_minimum_size() const override;
void add_debugger_plugin(const Ref<Script> &p_script);
void remove_debugger_plugin(const Ref<Script> &p_script);
void add_debugger_tab(Control *p_control);
void remove_debugger_tab(Control *p_control);
void send_message(const String &p_message, const Array &p_args);
void register_message_capture(const StringName &p_name, const Callable &p_callable);
void unregister_message_capture(const StringName &p_name);
bool has_capture(const StringName &p_name);
void toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data);
ScriptEditorDebugger();
~ScriptEditorDebugger();

View file

@ -4174,6 +4174,7 @@ void EditorNode::register_editor_types() {
GDREGISTER_CLASS(EditorScenePostImport);
GDREGISTER_CLASS(EditorCommandPalette);
GDREGISTER_CLASS(EditorDebuggerPlugin);
GDREGISTER_ABSTRACT_CLASS(EditorDebuggerSession);
}
void EditorNode::unregister_editor_types() {

View file

@ -43,6 +43,7 @@
#include "editor/import/editor_import_plugin.h"
#include "editor/import/resource_importer_scene.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/project_settings_editor.h"
@ -841,12 +842,12 @@ ScriptCreateDialog *EditorPlugin::get_script_create_dialog() {
return SceneTreeDock::get_singleton()->get_script_create_dialog();
}
void EditorPlugin::add_debugger_plugin(const Ref<Script> &p_script) {
EditorDebuggerNode::get_singleton()->add_debugger_plugin(p_script);
void EditorPlugin::add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) {
EditorDebuggerNode::get_singleton()->add_debugger_plugin(p_plugin);
}
void EditorPlugin::remove_debugger_plugin(const Ref<Script> &p_script) {
EditorDebuggerNode::get_singleton()->remove_debugger_plugin(p_script);
void EditorPlugin::remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) {
EditorDebuggerNode::get_singleton()->remove_debugger_plugin(p_plugin);
}
void EditorPlugin::_editor_project_settings_changed() {

View file

@ -39,6 +39,7 @@ class Node3D;
class Button;
class PopupMenu;
class EditorCommandPalette;
class EditorDebuggerPlugin;
class EditorExport;
class EditorExportPlugin;
class EditorFileSystem;
@ -302,8 +303,8 @@ public:
void add_autoload_singleton(const String &p_name, const String &p_path);
void remove_autoload_singleton(const String &p_name);
void add_debugger_plugin(const Ref<Script> &p_script);
void remove_debugger_plugin(const Ref<Script> &p_script);
void add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin);
void remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin);
void enable_plugin();
void disable_plugin();

View file

@ -32,7 +32,7 @@
#include "editor/debugger/script_editor_debugger.h"
void EditorDebuggerPlugin::_breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump) {
void EditorDebuggerSession::_breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump) {
if (p_really_did) {
emit_signal(SNAME("breaked"), p_can_debug);
} else {
@ -40,22 +40,22 @@ void EditorDebuggerPlugin::_breaked(bool p_really_did, bool p_can_debug, String
}
}
void EditorDebuggerPlugin::_started() {
void EditorDebuggerSession::_started() {
emit_signal(SNAME("started"));
}
void EditorDebuggerPlugin::_stopped() {
void EditorDebuggerSession::_stopped() {
emit_signal(SNAME("stopped"));
}
void EditorDebuggerPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EditorDebuggerPlugin::send_message);
ClassDB::bind_method(D_METHOD("register_message_capture", "name", "callable"), &EditorDebuggerPlugin::register_message_capture);
ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &EditorDebuggerPlugin::unregister_message_capture);
ClassDB::bind_method(D_METHOD("has_capture", "name"), &EditorDebuggerPlugin::has_capture);
ClassDB::bind_method(D_METHOD("is_breaked"), &EditorDebuggerPlugin::is_breaked);
ClassDB::bind_method(D_METHOD("is_debuggable"), &EditorDebuggerPlugin::is_debuggable);
ClassDB::bind_method(D_METHOD("is_session_active"), &EditorDebuggerPlugin::is_session_active);
void EditorDebuggerSession::_bind_methods() {
ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EditorDebuggerSession::send_message, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("toggle_profiler", "profiler", "enable", "data"), &EditorDebuggerSession::toggle_profiler, DEFVAL(Array()));
ClassDB::bind_method(D_METHOD("is_breaked"), &EditorDebuggerSession::is_breaked);
ClassDB::bind_method(D_METHOD("is_debuggable"), &EditorDebuggerSession::is_debuggable);
ClassDB::bind_method(D_METHOD("is_active"), &EditorDebuggerSession::is_active);
ClassDB::bind_method(D_METHOD("add_session_tab", "control"), &EditorDebuggerSession::add_session_tab);
ClassDB::bind_method(D_METHOD("remove_session_tab", "control"), &EditorDebuggerSession::remove_session_tab);
ADD_SIGNAL(MethodInfo("started"));
ADD_SIGNAL(MethodInfo("stopped"));
@ -63,62 +63,131 @@ void EditorDebuggerPlugin::_bind_methods() {
ADD_SIGNAL(MethodInfo("continued"));
}
void EditorDebuggerPlugin::attach_debugger(ScriptEditorDebugger *p_debugger) {
debugger = p_debugger;
if (debugger) {
debugger->connect("started", callable_mp(this, &EditorDebuggerPlugin::_started));
debugger->connect("stopped", callable_mp(this, &EditorDebuggerPlugin::_stopped));
debugger->connect("breaked", callable_mp(this, &EditorDebuggerPlugin::_breaked));
}
void EditorDebuggerSession::add_session_tab(Control *p_tab) {
ERR_FAIL_COND(!p_tab || !debugger);
debugger->add_debugger_tab(p_tab);
tabs.insert(p_tab);
}
void EditorDebuggerPlugin::detach_debugger(bool p_call_debugger) {
if (debugger) {
debugger->disconnect("started", callable_mp(this, &EditorDebuggerPlugin::_started));
debugger->disconnect("stopped", callable_mp(this, &EditorDebuggerPlugin::_stopped));
debugger->disconnect("breaked", callable_mp(this, &EditorDebuggerPlugin::_breaked));
if (p_call_debugger && get_script_instance()) {
debugger->remove_debugger_plugin(get_script_instance()->get_script());
}
debugger = nullptr;
}
void EditorDebuggerSession::remove_session_tab(Control *p_tab) {
ERR_FAIL_COND(!p_tab || !debugger);
debugger->remove_debugger_tab(p_tab);
tabs.erase(p_tab);
}
void EditorDebuggerPlugin::send_message(const String &p_message, const Array &p_args) {
void EditorDebuggerSession::send_message(const String &p_message, const Array &p_args) {
ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger");
debugger->send_message(p_message, p_args);
}
void EditorDebuggerPlugin::register_message_capture(const StringName &p_name, const Callable &p_callable) {
void EditorDebuggerSession::toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data) {
ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger");
debugger->register_message_capture(p_name, p_callable);
debugger->toggle_profiler(p_profiler, p_enable, p_data);
}
void EditorDebuggerPlugin::unregister_message_capture(const StringName &p_name) {
ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger");
debugger->unregister_message_capture(p_name);
}
bool EditorDebuggerPlugin::has_capture(const StringName &p_name) {
ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger");
return debugger->has_capture(p_name);
}
bool EditorDebuggerPlugin::is_breaked() {
bool EditorDebuggerSession::is_breaked() {
ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger");
return debugger->is_breaked();
}
bool EditorDebuggerPlugin::is_debuggable() {
bool EditorDebuggerSession::is_debuggable() {
ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger");
return debugger->is_debuggable();
}
bool EditorDebuggerPlugin::is_session_active() {
bool EditorDebuggerSession::is_active() {
ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger");
return debugger->is_session_active();
}
EditorDebuggerPlugin::~EditorDebuggerPlugin() {
detach_debugger(true);
void EditorDebuggerSession::detach_debugger() {
if (!debugger) {
return;
}
debugger->disconnect("started", callable_mp(this, &EditorDebuggerSession::_started));
debugger->disconnect("stopped", callable_mp(this, &EditorDebuggerSession::_stopped));
debugger->disconnect("breaked", callable_mp(this, &EditorDebuggerSession::_breaked));
debugger->disconnect("tree_exited", callable_mp(this, &EditorDebuggerSession::_debugger_gone_away));
for (Control *tab : tabs) {
debugger->remove_debugger_tab(tab);
}
tabs.clear();
debugger = nullptr;
}
void EditorDebuggerSession::_debugger_gone_away() {
debugger = nullptr;
tabs.clear();
}
EditorDebuggerSession::EditorDebuggerSession(ScriptEditorDebugger *p_debugger) {
ERR_FAIL_COND(!p_debugger);
debugger = p_debugger;
debugger->connect("started", callable_mp(this, &EditorDebuggerSession::_started));
debugger->connect("stopped", callable_mp(this, &EditorDebuggerSession::_stopped));
debugger->connect("breaked", callable_mp(this, &EditorDebuggerSession::_breaked));
debugger->connect("tree_exited", callable_mp(this, &EditorDebuggerSession::_debugger_gone_away), CONNECT_ONE_SHOT);
}
EditorDebuggerSession::~EditorDebuggerSession() {
detach_debugger();
}
/// EditorDebuggerPlugin
EditorDebuggerPlugin::~EditorDebuggerPlugin() {
clear();
}
void EditorDebuggerPlugin::clear() {
for (int i = 0; i < sessions.size(); i++) {
sessions[i]->detach_debugger();
}
sessions.clear();
}
void EditorDebuggerPlugin::create_session(ScriptEditorDebugger *p_debugger) {
sessions.push_back(Ref<EditorDebuggerSession>(memnew(EditorDebuggerSession(p_debugger))));
setup_session(sessions.size() - 1);
}
void EditorDebuggerPlugin::setup_session(int p_idx) {
GDVIRTUAL_CALL(_setup_session, p_idx);
}
Ref<EditorDebuggerSession> EditorDebuggerPlugin::get_session(int p_idx) {
ERR_FAIL_INDEX_V(p_idx, sessions.size(), nullptr);
return sessions[p_idx];
}
Array EditorDebuggerPlugin::get_sessions() {
Array ret;
for (int i = 0; i < sessions.size(); i++) {
ret.push_back(sessions[i]);
}
return ret;
}
bool EditorDebuggerPlugin::has_capture(const String &p_message) const {
bool ret = false;
if (GDVIRTUAL_CALL(_has_capture, p_message, ret)) {
return ret;
}
return false;
}
bool EditorDebuggerPlugin::capture(const String &p_message, const Array &p_data, int p_session_id) {
bool ret = false;
if (GDVIRTUAL_CALL(_capture, p_message, p_data, p_session_id, ret)) {
return ret;
}
return false;
}
void EditorDebuggerPlugin::_bind_methods() {
GDVIRTUAL_BIND(_setup_session, "session_id");
GDVIRTUAL_BIND(_has_capture, "capture");
GDVIRTUAL_BIND(_capture, "message", "data", "session_id");
ClassDB::bind_method(D_METHOD("get_session", "id"), &EditorDebuggerPlugin::get_session);
ClassDB::bind_method(D_METHOD("get_sessions"), &EditorDebuggerPlugin::get_sessions);
}

View file

@ -35,29 +35,62 @@
class ScriptEditorDebugger;
class EditorDebuggerPlugin : public Control {
GDCLASS(EditorDebuggerPlugin, Control);
class EditorDebuggerSession : public RefCounted {
GDCLASS(EditorDebuggerSession, RefCounted);
private:
HashSet<Control *> tabs;
ScriptEditorDebugger *debugger = nullptr;
void _breaked(bool p_really_did, bool p_can_debug, String p_message, bool p_has_stackdump);
void _started();
void _stopped();
void _debugger_gone_away();
protected:
static void _bind_methods();
public:
void attach_debugger(ScriptEditorDebugger *p_debugger);
void detach_debugger(bool p_call_debugger);
void send_message(const String &p_message, const Array &p_args);
void register_message_capture(const StringName &p_name, const Callable &p_callable);
void unregister_message_capture(const StringName &p_name);
bool has_capture(const StringName &p_name);
void detach_debugger();
void add_session_tab(Control *p_tab);
void remove_session_tab(Control *p_tab);
void send_message(const String &p_message, const Array &p_args = Array());
void toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data = Array());
bool is_breaked();
bool is_debuggable();
bool is_session_active();
bool is_active();
EditorDebuggerSession(ScriptEditorDebugger *p_debugger);
~EditorDebuggerSession();
};
class EditorDebuggerPlugin : public RefCounted {
GDCLASS(EditorDebuggerPlugin, RefCounted);
private:
List<Ref<EditorDebuggerSession>> sessions;
protected:
static void _bind_methods();
public:
void create_session(ScriptEditorDebugger *p_debugger);
void clear();
virtual void setup_session(int p_idx);
virtual bool capture(const String &p_message, const Array &p_data, int p_session);
virtual bool has_capture(const String &p_capture) const;
Ref<EditorDebuggerSession> get_session(int p_session_id);
Array get_sessions();
GDVIRTUAL3R(bool, _capture, const String &, const Array &, int);
GDVIRTUAL1RC(bool, _has_capture, const String &);
GDVIRTUAL1(_setup_session, int);
EditorDebuggerPlugin() {}
~EditorDebuggerPlugin();
};