diff options
-rw-r--r-- | core/bind/core_bind.cpp | 152 | ||||
-rw-r--r-- | core/bind/core_bind.h | 54 | ||||
-rw-r--r-- | core/register_core_types.cpp | 5 | ||||
-rw-r--r-- | doc/classes/@GlobalScope.xml | 3 | ||||
-rw-r--r-- | doc/classes/EditorDebuggerPlugin.xml | 103 | ||||
-rw-r--r-- | doc/classes/EditorPlugin.xml | 18 | ||||
-rw-r--r-- | doc/classes/EngineDebugger.xml | 132 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_node.cpp | 27 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_node.h | 5 | ||||
-rw-r--r-- | editor/debugger/script_editor_debugger.cpp | 63 | ||||
-rw-r--r-- | editor/debugger/script_editor_debugger.h | 15 | ||||
-rw-r--r-- | editor/editor_node.cpp | 2 | ||||
-rw-r--r-- | editor/editor_plugin.cpp | 10 | ||||
-rw-r--r-- | editor/editor_plugin.h | 4 | ||||
-rw-r--r-- | editor/plugins/editor_debugger_plugin.cpp | 124 | ||||
-rw-r--r-- | editor/plugins/editor_debugger_plugin.h | 64 |
16 files changed, 780 insertions, 1 deletions
diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index 045d7d5872..489ff762c9 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -31,6 +31,7 @@ #include "core_bind.h" #include "core/crypto/crypto_core.h" +#include "core/debugger/engine_debugger.h" #include "core/io/file_access_compressed.h" #include "core/io/file_access_encrypted.h" #include "core/io/json.h" @@ -2467,3 +2468,154 @@ Ref<JSONParseResult> _JSON::parse(const String &p_json) { } _JSON *_JSON::singleton = nullptr; + +////// _EngineDebugger ////// + +void _EngineDebugger::_bind_methods() { + ClassDB::bind_method(D_METHOD("is_active"), &_EngineDebugger::is_active); + + ClassDB::bind_method(D_METHOD("register_profiler", "name", "toggle", "add", "tick"), &_EngineDebugger::register_profiler); + ClassDB::bind_method(D_METHOD("unregister_profiler", "name"), &_EngineDebugger::unregister_profiler); + ClassDB::bind_method(D_METHOD("is_profiling", "name"), &_EngineDebugger::is_profiling); + ClassDB::bind_method(D_METHOD("has_profiler", "name"), &_EngineDebugger::has_profiler); + + ClassDB::bind_method(D_METHOD("profiler_add_frame_data", "name", "data"), &_EngineDebugger::profiler_add_frame_data); + ClassDB::bind_method(D_METHOD("profiler_enable", "name", "enable", "arguments"), &_EngineDebugger::profiler_enable, DEFVAL(Array())); + + ClassDB::bind_method(D_METHOD("register_message_capture", "name", "callable"), &_EngineDebugger::register_message_capture); + ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &_EngineDebugger::unregister_message_capture); + ClassDB::bind_method(D_METHOD("has_capture", "name"), &_EngineDebugger::has_capture); + + ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &_EngineDebugger::send_message); +} + +bool _EngineDebugger::is_active() { + return EngineDebugger::is_active(); +} + +void _EngineDebugger::register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) { + ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler already registered: " + p_name); + profilers.insert(p_name, ProfilerCallable(p_toggle, p_add, p_tick)); + ProfilerCallable &p = profilers[p_name]; + EngineDebugger::Profiler profiler( + &p, + &_EngineDebugger::call_toggle, + &_EngineDebugger::call_add, + &_EngineDebugger::call_tick); + EngineDebugger::register_profiler(p_name, profiler); +} + +void _EngineDebugger::unregister_profiler(const StringName &p_name) { + ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name); + EngineDebugger::unregister_profiler(p_name); + profilers.erase(p_name); +} + +bool _EngineDebugger::_EngineDebugger::is_profiling(const StringName &p_name) { + return EngineDebugger::is_profiling(p_name); +} + +bool _EngineDebugger::has_profiler(const StringName &p_name) { + return EngineDebugger::has_profiler(p_name); +} + +void _EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) { + EngineDebugger::profiler_add_frame_data(p_name, p_data); +} + +void _EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) { + if (EngineDebugger::get_singleton()) { + EngineDebugger::get_singleton()->profiler_enable(p_name, p_enabled, p_opts); + } +} + +void _EngineDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) { + ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), "Capture already registered: " + p_name); + captures.insert(p_name, p_callable); + Callable &c = captures[p_name]; + EngineDebugger::Capture capture(&c, &_EngineDebugger::call_capture); + EngineDebugger::register_message_capture(p_name, capture); +} + +void _EngineDebugger::unregister_message_capture(const StringName &p_name) { + ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name); + EngineDebugger::unregister_message_capture(p_name); + captures.erase(p_name); +} + +bool _EngineDebugger::has_capture(const StringName &p_name) { + return EngineDebugger::has_capture(p_name); +} + +void _EngineDebugger::send_message(const String &p_msg, const Array &p_data) { + ERR_FAIL_COND_MSG(!EngineDebugger::is_active(), "Can't send message. No active debugger"); + EngineDebugger::get_singleton()->send_message(p_msg, p_data); +} + +void _EngineDebugger::call_toggle(void *p_user, bool p_enable, const Array &p_opts) { + Callable &toggle = ((ProfilerCallable *)p_user)->callable_toggle; + if (toggle.is_null()) { + return; + } + Variant enable = p_enable, opts = p_opts; + const Variant *args[2] = { &enable, &opts }; + Variant retval; + Callable::CallError err; + toggle.call(args, 2, retval, err); + ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'toggle' to callable: " + Variant::get_callable_error_text(toggle, args, 2, err)); +} + +void _EngineDebugger::call_add(void *p_user, const Array &p_data) { + Callable &add = ((ProfilerCallable *)p_user)->callable_add; + if (add.is_null()) { + return; + } + Variant data = p_data; + const Variant *args[1] = { &data }; + Variant retval; + Callable::CallError err; + add.call(args, 1, retval, err); + ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'add' to callable: " + Variant::get_callable_error_text(add, args, 1, err)); +} + +void _EngineDebugger::call_tick(void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + Callable &tick = ((ProfilerCallable *)p_user)->callable_tick; + if (tick.is_null()) { + return; + } + Variant frame_time = p_frame_time, idle_time = p_idle_time, physics_time = p_physics_time, physics_frame_time = p_physics_frame_time; + const Variant *args[4] = { &frame_time, &idle_time, &physics_time, &physics_frame_time }; + Variant retval; + Callable::CallError err; + tick.call(args, 4, retval, err); + ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'tick' to callable: " + Variant::get_callable_error_text(tick, args, 4, err)); +} + +Error _EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { + Callable &capture = *(Callable *)p_user; + if (capture.is_null()) { + return FAILED; + } + Variant cmd = p_cmd, data = p_data; + const Variant *args[2] = { &cmd, &data }; + Variant retval; + Callable::CallError err; + capture.call(args, 2, retval, err); + ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, "Error calling 'capture' to callable: " + Variant::get_callable_error_text(capture, args, 2, err)); + ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, "Error calling 'capture' to callable: " + String(capture) + ". Return type is not bool."); + r_captured = retval; + return OK; +} + +_EngineDebugger::~_EngineDebugger() { + for (Map<StringName, Callable>::Element *E = captures.front(); E; E = E->next()) { + EngineDebugger::unregister_message_capture(E->key()); + } + captures.clear(); + for (Map<StringName, ProfilerCallable>::Element *E = profilers.front(); E; E = E->next()) { + EngineDebugger::unregister_profiler(E->key()); + } + profilers.clear(); +} + +_EngineDebugger *_EngineDebugger::singleton = nullptr; diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index a1fedf1bb8..a59fcda60c 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -715,4 +715,58 @@ public: _JSON() { singleton = this; } }; +class _EngineDebugger : public Object { + GDCLASS(_EngineDebugger, Object); + + class ProfilerCallable { + friend class _EngineDebugger; + + Callable callable_toggle; + Callable callable_add; + Callable callable_tick; + + public: + ProfilerCallable() {} + + ProfilerCallable(const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) { + callable_toggle = p_toggle; + callable_add = p_add; + callable_tick = p_tick; + } + }; + + Map<StringName, Callable> captures; + Map<StringName, ProfilerCallable> profilers; + +protected: + static void _bind_methods(); + static _EngineDebugger *singleton; + +public: + static _EngineDebugger *get_singleton() { return singleton; } + + bool is_active(); + + void register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick); + void unregister_profiler(const StringName &p_name); + bool is_profiling(const StringName &p_name); + bool has_profiler(const StringName &p_name); + void profiler_add_frame_data(const StringName &p_name, const Array &p_data); + void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array()); + + 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 send_message(const String &p_msg, const Array &p_data); + + static void call_toggle(void *p_user, bool p_enable, const Array &p_opts); + static void call_add(void *p_user, const Array &p_data); + static void call_tick(void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time); + static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured); + + _EngineDebugger() { singleton = this; } + ~_EngineDebugger(); +}; + #endif // CORE_BIND_H diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 5dac42cacb..4f094dd6c6 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -86,6 +86,7 @@ static _Engine *_engine = nullptr; static _ClassDB *_classdb = nullptr; static _Marshalls *_marshalls = nullptr; static _JSON *_json = nullptr; +static _EngineDebugger *_engine_debugger = nullptr; static IP *ip = nullptr; @@ -227,6 +228,7 @@ void register_core_types() { _classdb = memnew(_ClassDB); _marshalls = memnew(_Marshalls); _json = memnew(_JSON); + _engine_debugger = memnew(_EngineDebugger); } void register_core_settings() { @@ -256,6 +258,7 @@ void register_core_singletons() { ClassDB::register_class<InputMap>(); ClassDB::register_class<_JSON>(); ClassDB::register_class<Expression>(); + ClassDB::register_class<_EngineDebugger>(); Engine::get_singleton()->add_singleton(Engine::Singleton("ProjectSettings", ProjectSettings::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("IP", IP::get_singleton())); @@ -271,6 +274,7 @@ void register_core_singletons() { Engine::get_singleton()->add_singleton(Engine::Singleton("Input", Input::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("InputMap", InputMap::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("JSON", _JSON::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("EngineDebugger", _EngineDebugger::get_singleton())); } void unregister_core_types() { @@ -281,6 +285,7 @@ void unregister_core_types() { memdelete(_classdb); memdelete(_marshalls); memdelete(_json); + memdelete(_engine_debugger); memdelete(_geometry_2d); memdelete(_geometry_3d); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 7f7df33471..570bf191c7 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -27,6 +27,9 @@ <member name="Engine" type="Engine" setter="" getter=""> The [Engine] singleton. </member> + <member name="EngineDebugger" type="EngineDebugger" setter="" getter=""> + The [EngineDebugger] singleton. + </member> <member name="Geometry2D" type="Geometry2D" setter="" getter=""> The [Geometry2D] singleton. </member> diff --git a/doc/classes/EditorDebuggerPlugin.xml b/doc/classes/EditorDebuggerPlugin.xml new file mode 100644 index 0000000000..b97933e582 --- /dev/null +++ b/doc/classes/EditorDebuggerPlugin.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorDebuggerPlugin" inherits="Control" version="4.0"> + <brief_description> + A base class to implement debugger plugins. + </brief_description> + <description> + All debugger plugin scripts must extend [EditorDebuggerPlugin]. It provides functions related to editor side of debugger. + You don't need to instantiate this class. That is handled by the debugger itself. [Control] nodes can be added as child nodes to provide a GUI front-end for the plugin. + Do not queue_free/reparent it's instance otherwise the instance becomes unusable. + </description> + <tutorials> + </tutorials> + <methods> + <method name="has_capture"> + <return type="bool"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <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"> + </return> + <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"> + </return> + <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"> + </return> + <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"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <argument index="1" name="callable" type="Callable"> + </argument> + <description> + Registers a message capture with given [code]name[/code]. If [code]name[/code] 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"> + </return> + <argument index="0" name="message" type="String"> + </argument> + <argument index="1" name="data" type="Array"> + </argument> + <description> + Sends a message with given [code]message[/code] and [code]data[/code] array. + </description> + </method> + <method name="unregister_message_capture"> + <return type="void"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <description> + Unregisters the message capture with given name. + </description> + </method> + </methods> + <signals> + <signal name="breaked"> + <argument index="0" name="can_debug" type="bool"> + </argument> + <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> + <constants> + </constants> +</class> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 99fe9b4bb5..36076d8909 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -76,6 +76,15 @@ During run-time, this will be a simple object with a script so this function does not need to be called then. </description> </method> + <method name="add_debugger_plugin"> + <return type="void"> + </return> + <argument index="0" name="script" type="Script"> + </argument> + <description> + Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin]. + </description> + </method> <method name="add_export_plugin"> <return type="void"> </return> @@ -424,6 +433,15 @@ Removes a custom type added by [method add_custom_type]. </description> </method> + <method name="remove_debugger_plugin"> + <return type="void"> + </return> + <argument index="0" name="script" type="Script"> + </argument> + <description> + Removes the debugger plugin with given script fromm the Debugger. + </description> + </method> <method name="remove_export_plugin"> <return type="void"> </return> diff --git a/doc/classes/EngineDebugger.xml b/doc/classes/EngineDebugger.xml new file mode 100644 index 0000000000..7db36b89d0 --- /dev/null +++ b/doc/classes/EngineDebugger.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EngineDebugger" inherits="Object" version="4.0"> + <brief_description> + Exposes the internal debugger. + </brief_description> + <description> + [EngineDebugger] handles the communication between the editor and the running game. It is active in the running game. Messages can be sent/received through it. It also manages the profilers. + </description> + <tutorials> + </tutorials> + <methods> + <method name="has_capture"> + <return type="bool"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <description> + Returns [code]true[/code] if a capture with the given name is present otherwise [code]false[/code]. + </description> + </method> + <method name="has_profiler"> + <return type="bool"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <description> + Returns [code]true[/code] if a profiler with the given name is present otherwise [code]false[/code]. + </description> + </method> + <method name="is_active"> + <return type="bool"> + </return> + <description> + Returns [code]true[/code] if the debugger is active otherwise [code]false[/code]. + </description> + </method> + <method name="is_profiling"> + <return type="bool"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <description> + Returns [code]true[/code] if a profiler with the given name is present and active otherwise [code]false[/code]. + </description> + </method> + <method name="profiler_add_frame_data"> + <return type="void"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <argument index="1" name="data" type="Array"> + </argument> + <description> + Calls the [code]add[/code] callable of the profiler with given [code]name[/code] and [code]data[/code]. + </description> + </method> + <method name="profiler_enable"> + <return type="void"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <argument index="1" name="enable" type="bool"> + </argument> + <argument index="2" name="arguments" type="Array" default="[ ]"> + </argument> + <description> + Calls the [code]toggle[/code] callable of the profiler with given [code]name[/code] and [code]arguments[/code]. Enables/Disables the same profiler depending on [code]enable[/code] argument. + </description> + </method> + <method name="register_message_capture"> + <return type="void"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <argument index="1" name="callable" type="Callable"> + </argument> + <description> + Registers a message capture with given [code]name[/code]. If [code]name[/code] 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="register_profiler"> + <return type="void"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <argument index="1" name="toggle" type="Callable"> + </argument> + <argument index="2" name="add" type="Callable"> + </argument> + <argument index="3" name="tick" type="Callable"> + </argument> + <description> + Registers a profiler with the given [code]name[/code]. + [code]toggle[/code] callable is called when the profiler is enabled/disabled. It must take an argument array as an argument. + [code]add[/code] callable is called when data is added to profiler using [method EngineDebugger.profiler_add_frame_data]. It must take a data array as argument. + [code]tick[/code] callable is called at every active profiler iteration. It must take frame time, idle time, physics time, and physics idle time as arguments. + </description> + </method> + <method name="send_message"> + <return type="void"> + </return> + <argument index="0" name="message" type="String"> + </argument> + <argument index="1" name="data" type="Array"> + </argument> + <description> + Sends a message with given [code]message[/code] and [code]data[/code] array. + </description> + </method> + <method name="unregister_message_capture"> + <return type="void"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <description> + Unregisters the message capture with given [code]name[/code]. + </description> + </method> + <method name="unregister_profiler"> + <return type="void"> + </return> + <argument index="0" name="name" type="StringName"> + </argument> + <description> + Unregisters a profiler with given [code]name[/code]. + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index a9c18138d8..b461ac4f35 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -34,6 +34,7 @@ #include "editor/debugger/script_editor_debugger.h" #include "editor/editor_log.h" #include "editor/editor_node.h" +#include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "scene/gui/menu_button.h" #include "scene/gui/tab_container.h" @@ -114,6 +115,12 @@ ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() { tabs->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("DebuggerPanel", "EditorStyles")); } + if (!debugger_plugins.empty()) { + for (Set<Ref<Script>>::Element *i = debugger_plugins.front(); i; i = i->next()) { + node->add_debugger_plugin(i->get()); + } + } + return node; } @@ -618,3 +625,23 @@ void EditorDebuggerNode::live_debug_reparent_node(const NodePath &p_at, const No dbg->live_debug_reparent_node(p_at, p_new_place, p_new_name, p_at_pos); }); } + +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(String(p_script->get_instance_base_type()) == "", "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); + for (int i = 0; get_debugger(i); i++) { + get_debugger(i)->add_debugger_plugin(p_script); + } +} + +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); + } +} diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h index ff9601c026..8d70a7f961 100644 --- a/editor/debugger/editor_debugger_node.h +++ b/editor/debugger/editor_debugger_node.h @@ -103,6 +103,8 @@ private: CameraOverride camera_override = OVERRIDE_NONE; Map<Breakpoint, bool> breakpoints; + Set<Ref<Script>> debugger_plugins; + ScriptEditorDebugger *_add_debugger(); EditorDebuggerRemoteObject *get_inspected_remote_object(); @@ -186,5 +188,8 @@ public: Error start(const String &p_protocol = "tcp://"); void stop(); + + void add_debugger_plugin(const Ref<Script> &p_script); + void remove_debugger_plugin(const Ref<Script> &p_script); }; #endif // EDITOR_DEBUGGER_NODE_H diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 49bf068be7..1fca95b6da 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -44,6 +44,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.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/property_editor.h" #include "main/performance.h" @@ -701,7 +702,28 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da performance_profiler->update_monitors(monitors); } else { - WARN_PRINT("unknown message " + p_msg); + 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); + Map<StringName, Callable>::Element *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), data = p_data; + const Variant *args[2] = { &cmd, &data }; + Variant retval; + Callable::CallError err; + c.call(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; + } + + if (!parsed) { + WARN_PRINT("unknown message " + p_msg); + } } } @@ -847,6 +869,7 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) { tabs->set_current_tab(0); _set_reason_text(TTR("Debug session started."), MESSAGE_SUCCESS); _update_buttons_state(); + emit_signal("started"); } void ScriptEditorDebugger::_update_buttons_state() { @@ -1395,6 +1418,7 @@ void ScriptEditorDebugger::_bind_methods() { ClassDB::bind_method(D_METHOD("request_remote_object", "id"), &ScriptEditorDebugger::request_remote_object); ClassDB::bind_method(D_METHOD("update_remote_object", "id", "property", "value"), &ScriptEditorDebugger::update_remote_object); + ADD_SIGNAL(MethodInfo("started")); ADD_SIGNAL(MethodInfo("stopped")); ADD_SIGNAL(MethodInfo("stop_requested")); ADD_SIGNAL(MethodInfo("stack_frame_selected", PropertyInfo(Variant::INT, "frame"))); @@ -1408,6 +1432,43 @@ void ScriptEditorDebugger::_bind_methods() { ADD_SIGNAL(MethodInfo("remote_tree_updated")); } +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::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::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); +} + ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { editor = p_editor; diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 6e5699e929..56b34e8e8c 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -54,6 +54,7 @@ class EditorVisualProfiler; class EditorNetworkProfiler; class EditorPerformanceProfiler; class SceneDebuggerTree; +class EditorDebuggerPlugin; class ScriptEditorDebugger : public MarginContainer { GDCLASS(ScriptEditorDebugger, MarginContainer); @@ -146,6 +147,10 @@ private: EditorDebuggerNode::CameraOverride camera_override; + Map<Ref<Script>, EditorDebuggerPlugin *> debugger_plugins; + + Map<StringName, Callable> captures; + void _stack_dump_frame_selected(); void _file_selected(const String &p_file); @@ -253,6 +258,16 @@ public: bool is_skip_breakpoints(); 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 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); + ScriptEditorDebugger(EditorNode *p_editor = nullptr); ~ScriptEditorDebugger(); }; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 381ff88890..d6f32c81c8 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -123,6 +123,7 @@ #include "editor/plugins/cpu_particles_3d_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" #include "editor/plugins/debugger_editor_plugin.h" +#include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/editor_preview_plugins.h" #include "editor/plugins/gi_probe_editor_plugin.h" #include "editor/plugins/gpu_particles_2d_editor_plugin.h" @@ -3622,6 +3623,7 @@ void EditorNode::register_editor_types() { // FIXME: Is this stuff obsolete, or should it be ported to new APIs? ClassDB::register_class<EditorScenePostImport>(); //ClassDB::register_type<EditorImportExport>(); + ClassDB::register_class<EditorDebuggerPlugin>(); } void EditorNode::unregister_editor_types() { diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index da0a0827d2..bce46b719a 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -811,6 +811,14 @@ ScriptCreateDialog *EditorPlugin::get_script_create_dialog() { return EditorNode::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::remove_debugger_plugin(const Ref<Script> &p_script) { + EditorDebuggerNode::get_singleton()->remove_debugger_plugin(p_script); +} + void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_control_to_container", "container", "control"), &EditorPlugin::add_control_to_container); ClassDB::bind_method(D_METHOD("add_control_to_bottom_panel", "control", "title"), &EditorPlugin::add_control_to_bottom_panel); @@ -851,6 +859,8 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorPlugin::get_editor_interface); ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog); + ClassDB::bind_method(D_METHOD("add_debugger_plugin", "script"), &EditorPlugin::add_debugger_plugin); + ClassDB::bind_method(D_METHOD("remove_debugger_plugin", "script"), &EditorPlugin::remove_debugger_plugin); ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::BOOL, "forward_canvas_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); ClassDB::add_virtual_method(get_class_static(), MethodInfo("forward_canvas_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 685f69bf3f..c7803f73c9 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -33,6 +33,7 @@ #include "core/io/config_file.h" #include "core/undo_redo.h" +#include "editor/debugger/editor_debugger_node.h" #include "editor/editor_inspector.h" #include "editor/editor_translation_parser.h" #include "editor/import/editor_import_plugin.h" @@ -249,6 +250,9 @@ 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 enable_plugin(); void disable_plugin(); diff --git a/editor/plugins/editor_debugger_plugin.cpp b/editor/plugins/editor_debugger_plugin.cpp new file mode 100644 index 0000000000..b775e871e2 --- /dev/null +++ b/editor/plugins/editor_debugger_plugin.cpp @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* editor_debugger_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_debugger_plugin.h" + +#include "editor/debugger/script_editor_debugger.h" + +void EditorDebuggerPlugin::_breaked(bool p_really_did, bool p_can_debug) { + if (p_really_did) { + emit_signal("breaked", p_can_debug); + } else { + emit_signal("continued"); + } +} + +void EditorDebuggerPlugin::_started() { + emit_signal("started"); +} + +void EditorDebuggerPlugin::_stopped() { + emit_signal("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); + + ADD_SIGNAL(MethodInfo("started")); + ADD_SIGNAL(MethodInfo("stopped")); + ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "can_debug"))); + 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 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 EditorDebuggerPlugin::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) { + ERR_FAIL_COND_MSG(!debugger, "Plugin is not attached to debugger"); + debugger->register_message_capture(p_name, p_callable); +} + +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() { + ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); + return debugger->is_breaked(); +} + +bool EditorDebuggerPlugin::is_debuggable() { + ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); + return debugger->is_debuggable(); +} + +bool EditorDebuggerPlugin::is_session_active() { + ERR_FAIL_COND_V_MSG(!debugger, false, "Plugin is not attached to debugger"); + return debugger->is_session_active(); +} + +EditorDebuggerPlugin::~EditorDebuggerPlugin() { + detach_debugger(true); +} diff --git a/editor/plugins/editor_debugger_plugin.h b/editor/plugins/editor_debugger_plugin.h new file mode 100644 index 0000000000..5536f2daa2 --- /dev/null +++ b/editor/plugins/editor_debugger_plugin.h @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* editor_debugger_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_DEBUGGER_PLUGIN_H +#define EDITOR_DEBUGGER_PLUGIN_H + +#include "scene/gui/control.h" + +class ScriptEditorDebugger; + +class EditorDebuggerPlugin : public Control { + GDCLASS(EditorDebuggerPlugin, Control); + +private: + ScriptEditorDebugger *debugger; + + void _breaked(bool p_really_did, bool p_can_debug); + void _started(); + void _stopped(); + +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); + bool is_breaked(); + bool is_debuggable(); + bool is_session_active(); + ~EditorDebuggerPlugin(); +}; + +#endif // EDITOR_DEBUGGER_PLUGIN_H |