summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp197
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.h76
-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
9 files changed, 798 insertions, 93 deletions
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
new file mode 100644
index 0000000000..a7e5b80b66
--- /dev/null
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -0,0 +1,197 @@
+/*************************************************************************/
+/* editor_network_profiler.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 "editor_network_profiler.h"
+
+#include "core/os/os.h"
+#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
+
+void EditorNetworkProfiler::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
+}
+
+void EditorNetworkProfiler::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
+ clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")));
+ incoming_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons")));
+ outgoing_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons")));
+
+ // This needs to be done here to set the faded color when the profiler is first opened
+ incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5));
+ outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5));
+ } break;
+ }
+}
+
+void EditorNetworkProfiler::_update_frame() {
+ counters_display->clear();
+
+ TreeItem *root = counters_display->create_item();
+
+ 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) {
+ node->set_text_alignment(j, j > 0 ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT);
+ }
+
+ node->set_text(0, E.value.node_path);
+ node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc));
+ node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc));
+ }
+}
+
+void EditorNetworkProfiler::_activate_pressed() {
+ if (activate->is_pressed()) {
+ activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
+ activate->set_text(TTR("Stop"));
+ } else {
+ activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
+ activate->set_text(TTR("Start"));
+ }
+ emit_signal(SNAME("enable_profiling"), activate->is_pressed());
+}
+
+void EditorNetworkProfiler::_clear_pressed() {
+ nodes_data.clear();
+ set_bandwidth(0, 0);
+ if (frame_delay->is_stopped()) {
+ frame_delay->set_wait_time(0.1);
+ frame_delay->start();
+ }
+}
+
+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 {
+ nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc;
+ nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc;
+ }
+
+ if (frame_delay->is_stopped()) {
+ frame_delay->set_wait_time(0.1);
+ frame_delay->start();
+ }
+}
+
+void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) {
+ incoming_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_incoming)));
+ outgoing_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_outgoing)));
+
+ // Make labels more prominent when the bandwidth is greater than 0 to attract user attention
+ incoming_bandwidth_text->add_theme_color_override(
+ "font_uneditable_color",
+ get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5));
+ outgoing_bandwidth_text->add_theme_color_override(
+ "font_uneditable_color",
+ get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5));
+}
+
+bool EditorNetworkProfiler::is_profiling() {
+ return activate->is_pressed();
+}
+
+EditorNetworkProfiler::EditorNetworkProfiler() {
+ HBoxContainer *hb = memnew(HBoxContainer);
+ hb->add_theme_constant_override("separation", 8 * EDSCALE);
+ add_child(hb);
+
+ activate = memnew(Button);
+ activate->set_toggle_mode(true);
+ activate->set_text(TTR("Start"));
+ activate->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_activate_pressed));
+ hb->add_child(activate);
+
+ clear_button = memnew(Button);
+ clear_button->set_text(TTR("Clear"));
+ clear_button->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_clear_pressed));
+ hb->add_child(clear_button);
+
+ hb->add_spacer();
+
+ Label *lb = memnew(Label);
+ lb->set_text(TTR("Down"));
+ hb->add_child(lb);
+
+ incoming_bandwidth_text = memnew(LineEdit);
+ incoming_bandwidth_text->set_editable(false);
+ incoming_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
+ incoming_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
+ hb->add_child(incoming_bandwidth_text);
+
+ Control *down_up_spacer = memnew(Control);
+ down_up_spacer->set_custom_minimum_size(Size2(30, 0) * EDSCALE);
+ hb->add_child(down_up_spacer);
+
+ lb = memnew(Label);
+ lb->set_text(TTR("Up"));
+ hb->add_child(lb);
+
+ outgoing_bandwidth_text = memnew(LineEdit);
+ outgoing_bandwidth_text->set_editable(false);
+ outgoing_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
+ outgoing_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
+ hb->add_child(outgoing_bandwidth_text);
+
+ // Set initial texts in the incoming/outgoing bandwidth labels
+ set_bandwidth(0, 0);
+
+ counters_display = memnew(Tree);
+ counters_display->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
+ counters_display->set_v_size_flags(SIZE_EXPAND_FILL);
+ counters_display->set_hide_folding(true);
+ counters_display->set_hide_root(true);
+ counters_display->set_columns(3);
+ counters_display->set_column_titles_visible(true);
+ counters_display->set_column_title(0, TTR("Node"));
+ counters_display->set_column_expand(0, true);
+ counters_display->set_column_clip_content(0, true);
+ counters_display->set_column_custom_minimum_width(0, 60 * EDSCALE);
+ counters_display->set_column_title(1, TTR("Incoming RPC"));
+ counters_display->set_column_expand(1, false);
+ counters_display->set_column_clip_content(1, true);
+ counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE);
+ counters_display->set_column_title(2, TTR("Outgoing RPC"));
+ counters_display->set_column_expand(2, false);
+ counters_display->set_column_clip_content(2, true);
+ counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE);
+ add_child(counters_display);
+
+ frame_delay = memnew(Timer);
+ frame_delay->set_wait_time(0.1);
+ frame_delay->set_one_shot(true);
+ add_child(frame_delay);
+ frame_delay->connect("timeout", callable_mp(this, &EditorNetworkProfiler::_update_frame));
+}
diff --git a/modules/multiplayer/editor/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h
new file mode 100644
index 0000000000..98d12e3c0a
--- /dev/null
+++ b/modules/multiplayer/editor/editor_network_profiler.h
@@ -0,0 +1,76 @@
+/*************************************************************************/
+/* editor_network_profiler.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 EDITOR_NETWORK_PROFILER_H
+#define EDITOR_NETWORK_PROFILER_H
+
+#include "scene/debugger/scene_debugger.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/label.h"
+#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;
+ LineEdit *incoming_bandwidth_text = nullptr;
+ LineEdit *outgoing_bandwidth_text = nullptr;
+
+ Timer *frame_delay = nullptr;
+
+ HashMap<ObjectID, RPCNodeInfo> nodes_data;
+
+ void _update_frame();
+
+ void _activate_pressed();
+ void _clear_pressed();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void add_node_frame_data(const RPCNodeInfo p_frame);
+ void set_bandwidth(int p_incoming, int p_outgoing);
+ bool is_profiling();
+
+ EditorNetworkProfiler();
+};
+
+#endif // EDITOR_NETWORK_PROFILER_H
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();
}