Browse Source

[MP] Move engine and editor profilers to a plugin.

Also refactor the editor plugin out of the ReplicationEditor.
Fabio Alessandrelli 2 năm trước cách đây
mục cha
commit
67265d14f7

+ 0 - 87
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) {

+ 0 - 2
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;

+ 1 - 23
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();
@@ -967,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;
@@ -1873,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);

+ 0 - 3
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;

+ 2 - 2
editor/debugger/editor_network_profiler.cpp → 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 {

+ 6 - 2
editor/debugger/editor_network_profiler.h → 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();
 

+ 140 - 0
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();
+	}
+}

+ 81 - 0
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

+ 5 - 63
modules/multiplayer/editor/replication_editor_plugin.cpp → 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();
-	}
-}

+ 4 - 27
modules/multiplayer/editor/replication_editor_plugin.h → 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

+ 194 - 0
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());
+	}
+}

+ 95 - 0
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

+ 6 - 3
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();
 }

+ 0 - 77
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));

+ 0 - 19
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: