summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRémi Verschelde <rverschelde@gmail.com>2022-11-14 16:37:11 +0100
committerRémi Verschelde <rverschelde@gmail.com>2022-11-14 16:37:11 +0100
commit2f573f211ef821f293a0c9f1f0121d471215933d (patch)
tree00b7505b44198cd065cf02a80a676c62adc035cd
parent471c42ee1fe2c84b4f248e7194a2b10b562ff508 (diff)
parent67265d14f7aac8fdd3f7394495a9bf2eef972810 (diff)
Merge pull request #66938 from Faless/mp/4.x_debugger_split
[Editor] Better expose editor debugger plugins, use it in the multiplayer module.
-rw-r--r--core/debugger/remote_debugger.cpp87
-rw-r--r--core/debugger/remote_debugger.h2
-rw-r--r--doc/classes/EditorDebuggerPlugin.xml116
-rw-r--r--doc/classes/EditorDebuggerSession.xml86
-rw-r--r--doc/classes/EditorPlugin.xml4
-rw-r--r--editor/debugger/editor_debugger_node.cpp44
-rw-r--r--editor/debugger/editor_debugger_node.h8
-rw-r--r--editor/debugger/script_editor_debugger.cpp79
-rw-r--r--editor/debugger/script_editor_debugger.h16
-rw-r--r--editor/editor_node.cpp1
-rw-r--r--editor/editor_plugin.cpp9
-rw-r--r--editor/editor_plugin.h5
-rw-r--r--editor/plugins/editor_debugger_plugin.cpp159
-rw-r--r--editor/plugins/editor_debugger_plugin.h51
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp (renamed from editor/debugger/editor_network_profiler.cpp)4
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.h (renamed from editor/debugger/editor_network_profiler.h)8
-rw-r--r--modules/multiplayer/editor/multiplayer_editor_plugin.cpp140
-rw-r--r--modules/multiplayer/editor/multiplayer_editor_plugin.h81
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp (renamed from modules/multiplayer/editor/replication_editor_plugin.cpp)68
-rw-r--r--modules/multiplayer/editor/replication_editor.h (renamed from modules/multiplayer/editor/replication_editor_plugin.h)31
-rw-r--r--modules/multiplayer/multiplayer_debugger.cpp194
-rw-r--r--modules/multiplayer/multiplayer_debugger.h95
-rw-r--r--modules/multiplayer/register_types.cpp9
-rw-r--r--scene/debugger/scene_debugger.cpp77
-rw-r--r--scene/debugger/scene_debugger.h19
25 files changed, 894 insertions, 499 deletions
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index 23ee977df4..a2e3fab82c 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -39,89 +39,6 @@
#include "core/object/script_language.h"
#include "core/os/os.h"
-class RemoteDebugger::MultiplayerProfiler : public EngineProfiler {
- struct BandwidthFrame {
- uint32_t timestamp;
- int packet_size;
- };
-
- int bandwidth_in_ptr = 0;
- Vector<BandwidthFrame> bandwidth_in;
- int bandwidth_out_ptr = 0;
- Vector<BandwidthFrame> bandwidth_out;
- uint64_t last_bandwidth_time = 0;
-
- int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
- ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
- int total_bandwidth = 0;
-
- uint64_t timestamp = OS::get_singleton()->get_ticks_msec();
- uint64_t final_timestamp = timestamp - 1000;
-
- int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size();
-
- while (i != p_pointer && p_buffer[i].packet_size > 0) {
- if (p_buffer[i].timestamp < final_timestamp) {
- return total_bandwidth;
- }
- total_bandwidth += p_buffer[i].packet_size;
- i = (i + p_buffer.size() - 1) % p_buffer.size();
- }
-
- ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate.");
- return total_bandwidth;
- }
-
-public:
- void toggle(bool p_enable, const Array &p_opts) {
- if (!p_enable) {
- bandwidth_in.clear();
- bandwidth_out.clear();
- } else {
- bandwidth_in_ptr = 0;
- bandwidth_in.resize(16384); // ~128kB
- for (int i = 0; i < bandwidth_in.size(); ++i) {
- bandwidth_in.write[i].packet_size = -1;
- }
- bandwidth_out_ptr = 0;
- bandwidth_out.resize(16384); // ~128kB
- for (int i = 0; i < bandwidth_out.size(); ++i) {
- bandwidth_out.write[i].packet_size = -1;
- }
- }
- }
-
- void add(const Array &p_data) {
- ERR_FAIL_COND(p_data.size() < 3);
- const String inout = p_data[0];
- int time = p_data[1];
- int size = p_data[2];
- if (inout == "in") {
- bandwidth_in.write[bandwidth_in_ptr].timestamp = time;
- bandwidth_in.write[bandwidth_in_ptr].packet_size = size;
- bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size();
- } else if (inout == "out") {
- bandwidth_out.write[bandwidth_out_ptr].timestamp = time;
- bandwidth_out.write[bandwidth_out_ptr].packet_size = size;
- bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();
- }
- }
-
- void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
- uint64_t pt = OS::get_singleton()->get_ticks_msec();
- if (pt - last_bandwidth_time > 200) {
- last_bandwidth_time = pt;
- int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr);
- int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr);
-
- Array arr;
- arr.push_back(incoming_bandwidth);
- arr.push_back(outgoing_bandwidth);
- EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr);
- }
- }
-};
-
class RemoteDebugger::PerformanceProfiler : public EngineProfiler {
Object *performance = nullptr;
int last_perf_time = 0;
@@ -659,10 +576,6 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
max_errors_per_second = GLOBAL_GET("network/limits/debugger/max_errors_per_second");
max_warnings_per_second = GLOBAL_GET("network/limits/debugger/max_warnings_per_second");
- // Multiplayer Profiler
- multiplayer_profiler.instantiate();
- multiplayer_profiler->bind("multiplayer");
-
// Performance Profiler
Object *perf = Engine::get_singleton()->get_singleton_object("Performance");
if (perf) {
diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h
index fe4bbe86ea..944229d361 100644
--- a/core/debugger/remote_debugger.h
+++ b/core/debugger/remote_debugger.h
@@ -50,10 +50,8 @@ public:
private:
typedef DebuggerMarshalls::OutputError ErrorMessage;
- class MultiplayerProfiler;
class PerformanceProfiler;
- Ref<MultiplayerProfiler> multiplayer_profiler;
Ref<PerformanceProfiler> performance_profiler;
Ref<RemoteDebuggerPeer> peer;
diff --git a/doc/classes/EditorDebuggerPlugin.xml b/doc/classes/EditorDebuggerPlugin.xml
index c3e0a995c6..10da1edd56 100644
--- a/doc/classes/EditorDebuggerPlugin.xml
+++ b/doc/classes/EditorDebuggerPlugin.xml
@@ -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">
- <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">
+ <method name="_capture" qualifiers="virtual">
<return type="bool" />
+ <param index="0" name="message" type="String" />
+ <param index="1" name="data" type="Array" />
+ <param index="2" name="session_id" type="int" />
<description>
- Returns [code]true[/code] if the game can be debugged otherwise [code]false[/code].
+ 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="is_session_active">
+ <method name="_has_capture" qualifiers="virtual const">
<return type="bool" />
+ <param index="0" name="capture" type="String" />
<description>
- Returns [code]true[/code] if there is an instance of the game running with the attached debugger otherwise [code]false[/code].
+ 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="register_message_capture">
+ <method name="_setup_session" qualifiers="virtual">
<return type="void" />
- <param index="0" name="name" type="StringName" />
- <param index="1" name="callable" type="Callable" />
+ <param index="0" name="session_id" type="int" />
<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].
+ Override this method to be notified whenever a new [EditorDebuggerSession] is created (the session may be inactive during this stage).
</description>
</method>
- <method name="send_message">
- <return type="void" />
- <param index="0" name="message" type="String" />
- <param index="1" name="data" type="Array" />
+ <method name="get_session">
+ <return type="EditorDebuggerSession" />
+ <param index="0" name="id" type="int" />
<description>
- Sends a message with given [param message] and [param data] array.
+ Returns the [EditorDebuggerSession] with the given [param id].
</description>
</method>
- <method name="unregister_message_capture">
- <return type="void" />
- <param index="0" name="name" type="StringName" />
+ <method name="get_sessions">
+ <return type="Array" />
<description>
- Unregisters the message capture with given name.
+ 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>
diff --git a/doc/classes/EditorDebuggerSession.xml b/doc/classes/EditorDebuggerSession.xml
new file mode 100644
index 0000000000..faf528c143
--- /dev/null
+++ b/doc/classes/EditorDebuggerSession.xml
@@ -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>
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index 2289a373bf..806588d100 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -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>
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index 68aff328ed..49fbaa81bc 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -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;
}
diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h
index 305f18a652..259efeb47b 100644
--- a/editor/debugger/editor_debugger_node.h
+++ b/editor/debugger/editor_debugger_node.h
@@ -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
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index f1f34b8ebb..fd77ad784a 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -37,7 +37,6 @@
#include "core/string/ustring.h"
#include "core/version.h"
#include "editor/debugger/debug_adapter/debug_adapter_protocol.h"
-#include "editor/debugger/editor_network_profiler.h"
#include "editor/debugger/editor_performance_profiler.h"
#include "editor/debugger/editor_profiler.h"
#include "editor/debugger/editor_visual_profiler.h"
@@ -52,6 +51,7 @@
#include "editor/plugins/node_3d_editor_plugin.h"
#include "main/performance.h"
#include "scene/3d/camera_3d.h"
+#include "scene/debugger/scene_debugger.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
@@ -713,17 +713,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
profiler->add_frame_metric(metric, true);
}
- } else if (p_msg == "multiplayer:rpc") {
- SceneDebugger::RPCProfilerFrame frame;
- frame.deserialize(p_data);
- for (int i = 0; i < frame.infos.size(); i++) {
- network_profiler->add_node_frame_data(frame.infos[i]);
- }
-
- } else if (p_msg == "multiplayer:bandwidth") {
- ERR_FAIL_COND(p_data.size() < 2);
- network_profiler->set_bandwidth(p_data[0], p_data[1]);
-
} else if (p_msg == "request_quit") {
emit_signal(SNAME("stop_requested"));
_stop_and_notify();
@@ -741,22 +730,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);
}
@@ -982,10 +956,6 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) {
Array msg_data;
msg_data.push_back(p_enable);
switch (p_type) {
- case PROFILER_NETWORK:
- _put_msg("profiler:multiplayer", msg_data);
- _put_msg("profiler:rpc", msg_data);
- break;
case PROFILER_VISUAL:
_put_msg("profiler:visual", msg_data);
break;
@@ -1658,41 +1628,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() {
@@ -1904,13 +1858,6 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
visual_profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_profiler_activate).bind(PROFILER_VISUAL));
}
- { //network profiler
- network_profiler = memnew(EditorNetworkProfiler);
- network_profiler->set_name(TTR("Network Profiler"));
- tabs->add_child(network_profiler);
- network_profiler->connect("enable_profiling", callable_mp(this, &ScriptEditorDebugger::_profiler_activate).bind(PROFILER_NETWORK));
- }
-
{ //monitors
performance_profiler = memnew(EditorPerformanceProfiler);
tabs->add_child(performance_profiler);
diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h
index aa0a50ff03..22d6e48294 100644
--- a/editor/debugger/script_editor_debugger.h
+++ b/editor/debugger/script_editor_debugger.h
@@ -50,7 +50,6 @@ class ItemList;
class EditorProfiler;
class EditorFileDialog;
class EditorVisualProfiler;
-class EditorNetworkProfiler;
class EditorPerformanceProfiler;
class SceneDebuggerTree;
class EditorDebuggerPlugin;
@@ -72,7 +71,6 @@ private:
};
enum ProfilerType {
- PROFILER_NETWORK,
PROFILER_VISUAL,
PROFILER_SCRIPTS_SERVERS
};
@@ -151,7 +149,6 @@ private:
EditorProfiler *profiler = nullptr;
EditorVisualProfiler *visual_profiler = nullptr;
- EditorNetworkProfiler *network_profiler = nullptr;
EditorPerformanceProfiler *performance_profiler = nullptr;
OS::ProcessID remote_pid = 0;
@@ -163,10 +160,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 +279,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();
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 227f35597c..24ed0fff93 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -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() {
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index 683731f982..b18e5c7457 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -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() {
diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h
index 2d8f0c47cb..22674e4bc8 100644
--- a/editor/editor_plugin.h
+++ b/editor/editor_plugin.h
@@ -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();
diff --git a/editor/plugins/editor_debugger_plugin.cpp b/editor/plugins/editor_debugger_plugin.cpp
index 4ce3d7cfd5..5dd3038c0e 100644
--- a/editor/plugins/editor_debugger_plugin.cpp
+++ b/editor/plugins/editor_debugger_plugin.cpp
@@ -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();
}
+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() {
- detach_debugger(true);
+ 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);
}
diff --git a/editor/plugins/editor_debugger_plugin.h b/editor/plugins/editor_debugger_plugin.h
index b602c36912..46f8f17cc2 100644
--- a/editor/plugins/editor_debugger_plugin.h
+++ b/editor/plugins/editor_debugger_plugin.h
@@ -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();
};
diff --git a/editor/debugger/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index 8c18eba71d..a7e5b80b66 100644
--- a/editor/debugger/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -59,7 +59,7 @@ void EditorNetworkProfiler::_update_frame() {
TreeItem *root = counters_display->create_item();
- for (const KeyValue<ObjectID, SceneDebugger::RPCNodeInfo> &E : nodes_data) {
+ for (const KeyValue<ObjectID, RPCNodeInfo> &E : nodes_data) {
TreeItem *node = counters_display->create_item(root);
for (int j = 0; j < counters_display->get_columns(); ++j) {
@@ -92,7 +92,7 @@ void EditorNetworkProfiler::_clear_pressed() {
}
}
-void EditorNetworkProfiler::add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame) {
+void EditorNetworkProfiler::add_node_frame_data(const RPCNodeInfo p_frame) {
if (!nodes_data.has(p_frame.node)) {
nodes_data.insert(p_frame.node, p_frame);
} else {
diff --git a/editor/debugger/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h
index aea7ce3eec..98d12e3c0a 100644
--- a/editor/debugger/editor_network_profiler.h
+++ b/modules/multiplayer/editor/editor_network_profiler.h
@@ -38,10 +38,14 @@
#include "scene/gui/split_container.h"
#include "scene/gui/tree.h"
+#include "../multiplayer_debugger.h"
+
class EditorNetworkProfiler : public VBoxContainer {
GDCLASS(EditorNetworkProfiler, VBoxContainer)
private:
+ using RPCNodeInfo = MultiplayerDebugger::RPCNodeInfo;
+
Button *activate = nullptr;
Button *clear_button = nullptr;
Tree *counters_display = nullptr;
@@ -50,7 +54,7 @@ private:
Timer *frame_delay = nullptr;
- HashMap<ObjectID, SceneDebugger::RPCNodeInfo> nodes_data;
+ HashMap<ObjectID, RPCNodeInfo> nodes_data;
void _update_frame();
@@ -62,7 +66,7 @@ protected:
static void _bind_methods();
public:
- void add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame);
+ void add_node_frame_data(const RPCNodeInfo p_frame);
void set_bandwidth(int p_incoming, int p_outgoing);
bool is_profiling();
diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
new file mode 100644
index 0000000000..00b1537827
--- /dev/null
+++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
@@ -0,0 +1,140 @@
+/*************************************************************************/
+/* multiplayer_editor_plugin.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "multiplayer_editor_plugin.h"
+
+#include "../multiplayer_synchronizer.h"
+#include "editor_network_profiler.h"
+#include "replication_editor.h"
+
+#include "editor/editor_node.h"
+
+bool MultiplayerEditorDebugger::has_capture(const String &p_capture) const {
+ return p_capture == "multiplayer";
+}
+
+bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_data, int p_session) {
+ ERR_FAIL_COND_V(!profilers.has(p_session), false);
+ EditorNetworkProfiler *profiler = profilers[p_session];
+ if (p_message == "multiplayer:rpc") {
+ MultiplayerDebugger::RPCFrame frame;
+ frame.deserialize(p_data);
+ for (int i = 0; i < frame.infos.size(); i++) {
+ profiler->add_node_frame_data(frame.infos[i]);
+ }
+ return true;
+
+ } else if (p_message == "multiplayer:bandwidth") {
+ ERR_FAIL_COND_V(p_data.size() < 2, false);
+ profiler->set_bandwidth(p_data[0], p_data[1]);
+ return true;
+ }
+ return false;
+}
+
+void MultiplayerEditorDebugger::_profiler_activate(bool p_enable, int p_session_id) {
+ Ref<EditorDebuggerSession> session = get_session(p_session_id);
+ ERR_FAIL_COND(session.is_null());
+ session->toggle_profiler("multiplayer", p_enable);
+ session->toggle_profiler("rpc", p_enable);
+}
+
+void MultiplayerEditorDebugger::setup_session(int p_session_id) {
+ Ref<EditorDebuggerSession> session = get_session(p_session_id);
+ ERR_FAIL_COND(session.is_null());
+ EditorNetworkProfiler *profiler = memnew(EditorNetworkProfiler);
+ profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id));
+ profiler->set_name(TTR("Network Profiler"));
+ session->add_session_tab(profiler);
+ profilers[p_session_id] = profiler;
+}
+
+MultiplayerEditorPlugin::MultiplayerEditorPlugin() {
+ repl_editor = memnew(ReplicationEditor);
+ button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor);
+ button->hide();
+ repl_editor->get_pin()->connect("pressed", callable_mp(this, &MultiplayerEditorPlugin::_pinned));
+ debugger.instantiate();
+}
+
+MultiplayerEditorPlugin::~MultiplayerEditorPlugin() {
+}
+
+void MultiplayerEditorPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ get_tree()->connect("node_removed", callable_mp(this, &MultiplayerEditorPlugin::_node_removed));
+ add_debugger_plugin(debugger);
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ remove_debugger_plugin(debugger);
+ }
+ }
+}
+
+void MultiplayerEditorPlugin::_node_removed(Node *p_node) {
+ if (p_node && p_node == repl_editor->get_current()) {
+ repl_editor->edit(nullptr);
+ if (repl_editor->is_visible_in_tree()) {
+ EditorNode::get_singleton()->hide_bottom_panel();
+ }
+ button->hide();
+ repl_editor->get_pin()->set_pressed(false);
+ }
+}
+
+void MultiplayerEditorPlugin::_pinned() {
+ if (!repl_editor->get_pin()->is_pressed()) {
+ if (repl_editor->is_visible_in_tree()) {
+ EditorNode::get_singleton()->hide_bottom_panel();
+ }
+ button->hide();
+ }
+}
+
+void MultiplayerEditorPlugin::edit(Object *p_object) {
+ repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object));
+}
+
+bool MultiplayerEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("MultiplayerSynchronizer");
+}
+
+void MultiplayerEditorPlugin::make_visible(bool p_visible) {
+ if (p_visible) {
+ button->show();
+ EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor);
+ } else if (!repl_editor->get_pin()->is_pressed()) {
+ if (repl_editor->is_visible_in_tree()) {
+ EditorNode::get_singleton()->hide_bottom_panel();
+ }
+ button->hide();
+ }
+}
diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.h b/modules/multiplayer/editor/multiplayer_editor_plugin.h
new file mode 100644
index 0000000000..6d1514cdb1
--- /dev/null
+++ b/modules/multiplayer/editor/multiplayer_editor_plugin.h
@@ -0,0 +1,81 @@
+/*************************************************************************/
+/* multiplayer_editor_plugin.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 MULTIPLAYER_EDITOR_PLUGIN_H
+#define MULTIPLAYER_EDITOR_PLUGIN_H
+
+#include "editor/editor_plugin.h"
+
+#include "editor/plugins/editor_debugger_plugin.h"
+
+class EditorNetworkProfiler;
+class MultiplayerEditorDebugger : public EditorDebuggerPlugin {
+ GDCLASS(MultiplayerEditorDebugger, EditorDebuggerPlugin);
+
+private:
+ HashMap<int, EditorNetworkProfiler *> profilers;
+
+ void _profiler_activate(bool p_enable, int p_session_id);
+
+public:
+ virtual bool has_capture(const String &p_capture) const override;
+ virtual bool capture(const String &p_message, const Array &p_data, int p_index) override;
+ virtual void setup_session(int p_session_id) override;
+
+ MultiplayerEditorDebugger() {}
+};
+
+class ReplicationEditor;
+
+class MultiplayerEditorPlugin : public EditorPlugin {
+ GDCLASS(MultiplayerEditorPlugin, EditorPlugin);
+
+private:
+ Button *button = nullptr;
+ ReplicationEditor *repl_editor = nullptr;
+ Ref<MultiplayerEditorDebugger> debugger;
+
+ void _node_removed(Node *p_node);
+
+ void _pinned();
+
+protected:
+ void _notification(int p_what);
+
+public:
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+ virtual void make_visible(bool p_visible) override;
+
+ MultiplayerEditorPlugin();
+ ~MultiplayerEditorPlugin();
+};
+
+#endif // MULTIPLAYER_EDITOR_PLUGIN_H
diff --git a/modules/multiplayer/editor/replication_editor_plugin.cpp b/modules/multiplayer/editor/replication_editor.cpp
index de10420652..4ba9cd14f0 100644
--- a/modules/multiplayer/editor/replication_editor_plugin.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* replication_editor_plugin.cpp */
+/* replication_editor.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,7 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "replication_editor_plugin.h"
+#include "replication_editor.h"
+
+#include "../multiplayer_synchronizer.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
@@ -37,7 +39,6 @@
#include "editor/inspector_dock.h"
#include "editor/property_selector.h"
#include "editor/scene_tree_editor.h"
-#include "modules/multiplayer/multiplayer_synchronizer.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/separator.h"
#include "scene/gui/tree.h"
@@ -141,7 +142,7 @@ void ReplicationEditor::_add_sync_property(String p_path) {
return;
}
- Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo();
+ Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_singleton()->get_undo_redo();
undo_redo->create_action(TTR("Add property to synchronizer"));
if (config.is_null()) {
@@ -493,62 +494,3 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn,
item->set_checked(2, p_sync);
item->set_editable(2, true);
}
-
-/// ReplicationEditorPlugin
-ReplicationEditorPlugin::ReplicationEditorPlugin() {
- repl_editor = memnew(ReplicationEditor);
- button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor);
- button->hide();
- repl_editor->get_pin()->connect("pressed", callable_mp(this, &ReplicationEditorPlugin::_pinned));
-}
-
-ReplicationEditorPlugin::~ReplicationEditorPlugin() {
-}
-
-void ReplicationEditorPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- get_tree()->connect("node_removed", callable_mp(this, &ReplicationEditorPlugin::_node_removed));
- } break;
- }
-}
-
-void ReplicationEditorPlugin::_node_removed(Node *p_node) {
- if (p_node && p_node == repl_editor->get_current()) {
- repl_editor->edit(nullptr);
- if (repl_editor->is_visible_in_tree()) {
- EditorNode::get_singleton()->hide_bottom_panel();
- }
- button->hide();
- repl_editor->get_pin()->set_pressed(false);
- }
-}
-
-void ReplicationEditorPlugin::_pinned() {
- if (!repl_editor->get_pin()->is_pressed()) {
- if (repl_editor->is_visible_in_tree()) {
- EditorNode::get_singleton()->hide_bottom_panel();
- }
- button->hide();
- }
-}
-
-void ReplicationEditorPlugin::edit(Object *p_object) {
- repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object));
-}
-
-bool ReplicationEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("MultiplayerSynchronizer");
-}
-
-void ReplicationEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- button->show();
- EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor);
- } else if (!repl_editor->get_pin()->is_pressed()) {
- if (repl_editor->is_visible_in_tree()) {
- EditorNode::get_singleton()->hide_bottom_panel();
- }
- button->hide();
- }
-}
diff --git a/modules/multiplayer/editor/replication_editor_plugin.h b/modules/multiplayer/editor/replication_editor.h
index 6c40a99293..8a48e8dbe7 100644
--- a/modules/multiplayer/editor/replication_editor_plugin.h
+++ b/modules/multiplayer/editor/replication_editor.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* replication_editor_plugin.h */
+/* replication_editor.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef REPLICATION_EDITOR_PLUGIN_H
-#define REPLICATION_EDITOR_PLUGIN_H
+#ifndef REPLICATION_EDITOR_H
+#define REPLICATION_EDITOR_H
#include "editor/editor_plugin.h"
#include "modules/multiplayer/scene_replication_config.h"
@@ -105,27 +105,4 @@ public:
~ReplicationEditor() {}
};
-class ReplicationEditorPlugin : public EditorPlugin {
- GDCLASS(ReplicationEditorPlugin, EditorPlugin);
-
-private:
- Button *button = nullptr;
- ReplicationEditor *repl_editor = nullptr;
-
- void _node_removed(Node *p_node);
-
- void _pinned();
-
-protected:
- void _notification(int p_what);
-
-public:
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- ReplicationEditorPlugin();
- ~ReplicationEditorPlugin();
-};
-
-#endif // REPLICATION_EDITOR_PLUGIN_H
+#endif // REPLICATION_EDITOR_H
diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp
new file mode 100644
index 0000000000..3d22af04dc
--- /dev/null
+++ b/modules/multiplayer/multiplayer_debugger.cpp
@@ -0,0 +1,194 @@
+/*************************************************************************/
+/* multiplayer_debugger.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "multiplayer_debugger.h"
+
+#include "core/debugger/engine_debugger.h"
+#include "scene/main/node.h"
+
+List<Ref<EngineProfiler>> multiplayer_profilers;
+
+void MultiplayerDebugger::initialize() {
+ Ref<BandwidthProfiler> bandwidth;
+ bandwidth.instantiate();
+ bandwidth->bind("multiplayer");
+ multiplayer_profilers.push_back(bandwidth);
+
+ Ref<RPCProfiler> rpc_profiler;
+ rpc_profiler.instantiate();
+ rpc_profiler->bind("rpc");
+ multiplayer_profilers.push_back(rpc_profiler);
+}
+
+void MultiplayerDebugger::deinitialize() {
+ multiplayer_profilers.clear();
+}
+
+// BandwidthProfiler
+
+int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
+ ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
+ int total_bandwidth = 0;
+
+ uint64_t timestamp = OS::get_singleton()->get_ticks_msec();
+ uint64_t final_timestamp = timestamp - 1000;
+
+ int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size();
+
+ while (i != p_pointer && p_buffer[i].packet_size > 0) {
+ if (p_buffer[i].timestamp < final_timestamp) {
+ return total_bandwidth;
+ }
+ total_bandwidth += p_buffer[i].packet_size;
+ i = (i + p_buffer.size() - 1) % p_buffer.size();
+ }
+
+ ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate.");
+ return total_bandwidth;
+}
+
+void MultiplayerDebugger::BandwidthProfiler::toggle(bool p_enable, const Array &p_opts) {
+ if (!p_enable) {
+ bandwidth_in.clear();
+ bandwidth_out.clear();
+ } else {
+ bandwidth_in_ptr = 0;
+ bandwidth_in.resize(16384); // ~128kB
+ for (int i = 0; i < bandwidth_in.size(); ++i) {
+ bandwidth_in.write[i].packet_size = -1;
+ }
+ bandwidth_out_ptr = 0;
+ bandwidth_out.resize(16384); // ~128kB
+ for (int i = 0; i < bandwidth_out.size(); ++i) {
+ bandwidth_out.write[i].packet_size = -1;
+ }
+ }
+}
+
+void MultiplayerDebugger::BandwidthProfiler::add(const Array &p_data) {
+ ERR_FAIL_COND(p_data.size() < 3);
+ const String inout = p_data[0];
+ int time = p_data[1];
+ int size = p_data[2];
+ if (inout == "in") {
+ bandwidth_in.write[bandwidth_in_ptr].timestamp = time;
+ bandwidth_in.write[bandwidth_in_ptr].packet_size = size;
+ bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size();
+ } else if (inout == "out") {
+ bandwidth_out.write[bandwidth_out_ptr].timestamp = time;
+ bandwidth_out.write[bandwidth_out_ptr].packet_size = size;
+ bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();
+ }
+}
+
+void MultiplayerDebugger::BandwidthProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
+ uint64_t pt = OS::get_singleton()->get_ticks_msec();
+ if (pt - last_bandwidth_time > 200) {
+ last_bandwidth_time = pt;
+ int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr);
+ int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr);
+
+ Array arr;
+ arr.push_back(incoming_bandwidth);
+ arr.push_back(outgoing_bandwidth);
+ EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr);
+ }
+}
+
+// RPCProfiler
+
+Array MultiplayerDebugger::RPCFrame::serialize() {
+ Array arr;
+ arr.push_back(infos.size() * 4);
+ for (int i = 0; i < infos.size(); ++i) {
+ arr.push_back(uint64_t(infos[i].node));
+ arr.push_back(infos[i].node_path);
+ arr.push_back(infos[i].incoming_rpc);
+ arr.push_back(infos[i].outgoing_rpc);
+ }
+ return arr;
+}
+
+bool MultiplayerDebugger::RPCFrame::deserialize(const Array &p_arr) {
+ ERR_FAIL_COND_V(p_arr.size() < 1, false);
+ uint32_t size = p_arr[0];
+ ERR_FAIL_COND_V(size % 4, false);
+ ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
+ infos.resize(size / 4);
+ int idx = 1;
+ for (uint32_t i = 0; i < size / 4; ++i) {
+ infos.write[i].node = uint64_t(p_arr[idx]);
+ infos.write[i].node_path = p_arr[idx + 1];
+ infos.write[i].incoming_rpc = p_arr[idx + 2];
+ infos.write[i].outgoing_rpc = p_arr[idx + 3];
+ }
+ return true;
+}
+
+void MultiplayerDebugger::RPCProfiler::init_node(const ObjectID p_node) {
+ if (rpc_node_data.has(p_node)) {
+ return;
+ }
+ rpc_node_data.insert(p_node, RPCNodeInfo());
+ rpc_node_data[p_node].node = p_node;
+ rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
+ rpc_node_data[p_node].incoming_rpc = 0;
+ rpc_node_data[p_node].outgoing_rpc = 0;
+}
+
+void MultiplayerDebugger::RPCProfiler::toggle(bool p_enable, const Array &p_opts) {
+ rpc_node_data.clear();
+}
+
+void MultiplayerDebugger::RPCProfiler::add(const Array &p_data) {
+ ERR_FAIL_COND(p_data.size() < 2);
+ const ObjectID id = p_data[0];
+ const String what = p_data[1];
+ init_node(id);
+ RPCNodeInfo &info = rpc_node_data[id];
+ if (what == "rpc_in") {
+ info.incoming_rpc++;
+ } else if (what == "rpc_out") {
+ info.outgoing_rpc++;
+ }
+}
+
+void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
+ uint64_t pt = OS::get_singleton()->get_ticks_msec();
+ if (pt - last_profile_time > 100) {
+ last_profile_time = pt;
+ RPCFrame frame;
+ for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) {
+ frame.infos.push_back(E.value);
+ }
+ rpc_node_data.clear();
+ EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize());
+ }
+}
diff --git a/modules/multiplayer/multiplayer_debugger.h b/modules/multiplayer/multiplayer_debugger.h
new file mode 100644
index 0000000000..4efd1da016
--- /dev/null
+++ b/modules/multiplayer/multiplayer_debugger.h
@@ -0,0 +1,95 @@
+/*************************************************************************/
+/* multiplayer_debugger.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 MULTIPLAYER_DEBUGGER_H
+#define MULTIPLAYER_DEBUGGER_H
+
+#include "core/debugger/engine_profiler.h"
+
+#include "core/os/os.h"
+
+class MultiplayerDebugger {
+public:
+ struct RPCNodeInfo {
+ ObjectID node;
+ String node_path;
+ int incoming_rpc = 0;
+ int outgoing_rpc = 0;
+ };
+
+ struct RPCFrame {
+ Vector<RPCNodeInfo> infos;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+private:
+ class BandwidthProfiler : public EngineProfiler {
+ protected:
+ struct BandwidthFrame {
+ uint32_t timestamp;
+ int packet_size;
+ };
+
+ int bandwidth_in_ptr = 0;
+ Vector<BandwidthFrame> bandwidth_in;
+ int bandwidth_out_ptr = 0;
+ Vector<BandwidthFrame> bandwidth_out;
+ uint64_t last_bandwidth_time = 0;
+
+ int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer);
+
+ public:
+ void toggle(bool p_enable, const Array &p_opts);
+ void add(const Array &p_data);
+ void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time);
+ };
+
+ class RPCProfiler : public EngineProfiler {
+ public:
+ private:
+ HashMap<ObjectID, RPCNodeInfo> rpc_node_data;
+ uint64_t last_profile_time = 0;
+
+ void init_node(const ObjectID p_node);
+
+ public:
+ void toggle(bool p_enable, const Array &p_opts);
+ void add(const Array &p_data);
+ void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time);
+ };
+
+public:
+ static void initialize();
+ static void deinitialize();
+};
+
+#endif // MULTIPLAYER_DEBUGGER_H
diff --git a/modules/multiplayer/register_types.cpp b/modules/multiplayer/register_types.cpp
index a2c524da80..2bf1041029 100644
--- a/modules/multiplayer/register_types.cpp
+++ b/modules/multiplayer/register_types.cpp
@@ -36,9 +36,10 @@
#include "scene_replication_interface.h"
#include "scene_rpc_interface.h"
+#include "multiplayer_debugger.h"
+
#ifdef TOOLS_ENABLED
-#include "editor/editor_plugin.h"
-#include "editor/replication_editor_plugin.h"
+#include "editor/multiplayer_editor_plugin.h"
#endif
void initialize_multiplayer_module(ModuleInitializationLevel p_level) {
@@ -48,13 +49,15 @@ void initialize_multiplayer_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(MultiplayerSynchronizer);
GDREGISTER_CLASS(SceneMultiplayer);
MultiplayerAPI::set_default_interface("SceneMultiplayer");
+ MultiplayerDebugger::initialize();
}
#ifdef TOOLS_ENABLED
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
- EditorPlugins::add_by_type<ReplicationEditorPlugin>();
+ EditorPlugins::add_by_type<MultiplayerEditorPlugin>();
}
#endif
}
void uninitialize_multiplayer_module(ModuleInitializationLevel p_level) {
+ MultiplayerDebugger::deinitialize();
}
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index b4bdda9ecb..df27eecce7 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -39,87 +39,10 @@
#include "scene/main/window.h"
#include "scene/resources/packed_scene.h"
-Array SceneDebugger::RPCProfilerFrame::serialize() {
- Array arr;
- arr.push_back(infos.size() * 4);
- for (int i = 0; i < infos.size(); ++i) {
- arr.push_back(uint64_t(infos[i].node));
- arr.push_back(infos[i].node_path);
- arr.push_back(infos[i].incoming_rpc);
- arr.push_back(infos[i].outgoing_rpc);
- }
- return arr;
-}
-
-bool SceneDebugger::RPCProfilerFrame::deserialize(const Array &p_arr) {
- ERR_FAIL_COND_V(p_arr.size() < 1, false);
- uint32_t size = p_arr[0];
- ERR_FAIL_COND_V(size % 4, false);
- ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
- infos.resize(size / 4);
- int idx = 1;
- for (uint32_t i = 0; i < size / 4; ++i) {
- infos.write[i].node = uint64_t(p_arr[idx]);
- infos.write[i].node_path = p_arr[idx + 1];
- infos.write[i].incoming_rpc = p_arr[idx + 2];
- infos.write[i].outgoing_rpc = p_arr[idx + 3];
- }
- return true;
-}
-
-class SceneDebugger::RPCProfiler : public EngineProfiler {
- HashMap<ObjectID, RPCNodeInfo> rpc_node_data;
- uint64_t last_profile_time = 0;
-
- void init_node(const ObjectID p_node) {
- if (rpc_node_data.has(p_node)) {
- return;
- }
- rpc_node_data.insert(p_node, RPCNodeInfo());
- rpc_node_data[p_node].node = p_node;
- rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
- rpc_node_data[p_node].incoming_rpc = 0;
- rpc_node_data[p_node].outgoing_rpc = 0;
- }
-
-public:
- void toggle(bool p_enable, const Array &p_opts) {
- rpc_node_data.clear();
- }
-
- void add(const Array &p_data) {
- ERR_FAIL_COND(p_data.size() < 2);
- const ObjectID id = p_data[0];
- const String what = p_data[1];
- init_node(id);
- RPCNodeInfo &info = rpc_node_data[id];
- if (what == "rpc_in") {
- info.incoming_rpc++;
- } else if (what == "rpc_out") {
- info.outgoing_rpc++;
- }
- }
-
- void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
- uint64_t pt = OS::get_singleton()->get_ticks_msec();
- if (pt - last_profile_time > 100) {
- last_profile_time = pt;
- RPCProfilerFrame frame;
- for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) {
- frame.infos.push_back(E.value);
- }
- rpc_node_data.clear();
- EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize());
- }
- }
-};
-
SceneDebugger *SceneDebugger::singleton = nullptr;
SceneDebugger::SceneDebugger() {
singleton = this;
- rpc_profiler.instantiate();
- rpc_profiler->bind("rpc");
#ifdef DEBUG_ENABLED
LiveEditor::singleton = memnew(LiveEditor);
EngineDebugger::register_message_capture("scene", EngineDebugger::Capture(nullptr, SceneDebugger::parse_message));
diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h
index fe35446aae..0428bfcc2e 100644
--- a/scene/debugger/scene_debugger.h
+++ b/scene/debugger/scene_debugger.h
@@ -42,28 +42,9 @@ class Node;
class SceneDebugger {
public:
- // RPC profiler
- struct RPCNodeInfo {
- ObjectID node;
- String node_path;
- int incoming_rpc = 0;
- int outgoing_rpc = 0;
- };
-
- struct RPCProfilerFrame {
- Vector<RPCNodeInfo> infos;
-
- Array serialize();
- bool deserialize(const Array &p_arr);
- };
-
private:
- class RPCProfiler;
-
static SceneDebugger *singleton;
- Ref<RPCProfiler> rpc_profiler;
-
SceneDebugger();
public: