Browse Source

Merge pull request #57715 from Faless/debugger/4.x_core_includes_and_servers

[Debugger] Move most profilers to ServersDebugger, fix core includes.
Rémi Verschelde 3 years ago
parent
commit
d22ac13bc2

+ 10 - 55
core/core_bind.cpp

@@ -2376,21 +2376,18 @@ bool EngineDebugger::is_active() {
 	return ::EngineDebugger::is_active();
 }
 
-void EngineDebugger::register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) {
-	ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler already registered: " + p_name);
-	profilers.insert(p_name, ProfilerCallable(p_toggle, p_add, p_tick));
-	ProfilerCallable &p = profilers[p_name];
-	::EngineDebugger::Profiler profiler(
-			&p,
-			&EngineDebugger::call_toggle,
-			&EngineDebugger::call_add,
-			&EngineDebugger::call_tick);
-	::EngineDebugger::register_profiler(p_name, profiler);
+void EngineDebugger::register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler) {
+	ERR_FAIL_COND(p_profiler.is_null());
+	ERR_FAIL_COND_MSG(p_profiler->is_bound(), "Profiler already registered.");
+	ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler name already in use: " + p_name);
+	Error err = p_profiler->bind(p_name);
+	ERR_FAIL_COND_MSG(err != OK, "Profiler failed to register with error: " + itos(err));
+	profilers.insert(p_name, p_profiler);
 }
 
 void EngineDebugger::unregister_profiler(const StringName &p_name) {
 	ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
-	::EngineDebugger::unregister_profiler(p_name);
+	profilers[p_name]->unbind();
 	profilers.erase(p_name);
 }
 
@@ -2435,45 +2432,6 @@ void EngineDebugger::send_message(const String &p_msg, const Array &p_data) {
 	::EngineDebugger::get_singleton()->send_message(p_msg, p_data);
 }
 
-void EngineDebugger::call_toggle(void *p_user, bool p_enable, const Array &p_opts) {
-	Callable &toggle = ((ProfilerCallable *)p_user)->callable_toggle;
-	if (toggle.is_null()) {
-		return;
-	}
-	Variant enable = p_enable, opts = p_opts;
-	const Variant *args[2] = { &enable, &opts };
-	Variant retval;
-	Callable::CallError err;
-	toggle.call(args, 2, retval, err);
-	ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'toggle' to callable: " + Variant::get_callable_error_text(toggle, args, 2, err));
-}
-
-void EngineDebugger::call_add(void *p_user, const Array &p_data) {
-	Callable &add = ((ProfilerCallable *)p_user)->callable_add;
-	if (add.is_null()) {
-		return;
-	}
-	Variant data = p_data;
-	const Variant *args[1] = { &data };
-	Variant retval;
-	Callable::CallError err;
-	add.call(args, 1, retval, err);
-	ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'add' to callable: " + Variant::get_callable_error_text(add, args, 1, err));
-}
-
-void EngineDebugger::call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
-	Callable &tick = ((ProfilerCallable *)p_user)->callable_tick;
-	if (tick.is_null()) {
-		return;
-	}
-	Variant frame_time = p_frame_time, idle_time = p_idle_time, physics_time = p_physics_time, physics_frame_time = p_physics_frame_time;
-	const Variant *args[4] = { &frame_time, &idle_time, &physics_time, &physics_frame_time };
-	Variant retval;
-	Callable::CallError err;
-	tick.call(args, 4, retval, err);
-	ERR_FAIL_COND_MSG(err.error != Callable::CallError::CALL_OK, "Error calling 'tick' to callable: " + Variant::get_callable_error_text(tick, args, 4, err));
-}
-
 Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
 	Callable &capture = *(Callable *)p_user;
 	if (capture.is_null()) {
@@ -2495,10 +2453,6 @@ EngineDebugger::~EngineDebugger() {
 		::EngineDebugger::unregister_message_capture(E.key);
 	}
 	captures.clear();
-	for (const KeyValue<StringName, ProfilerCallable> &E : profilers) {
-		::EngineDebugger::unregister_profiler(E.key);
-	}
-	profilers.clear();
 }
 
 EngineDebugger *EngineDebugger::singleton = nullptr;
@@ -2506,8 +2460,9 @@ EngineDebugger *EngineDebugger::singleton = nullptr;
 void EngineDebugger::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("is_active"), &EngineDebugger::is_active);
 
-	ClassDB::bind_method(D_METHOD("register_profiler", "name", "toggle", "add", "tick"), &EngineDebugger::register_profiler);
+	ClassDB::bind_method(D_METHOD("register_profiler", "name", "profiler"), &EngineDebugger::register_profiler);
 	ClassDB::bind_method(D_METHOD("unregister_profiler", "name"), &EngineDebugger::unregister_profiler);
+
 	ClassDB::bind_method(D_METHOD("is_profiling", "name"), &EngineDebugger::is_profiling);
 	ClassDB::bind_method(D_METHOD("has_profiler", "name"), &EngineDebugger::has_profiler);
 

+ 3 - 22
core/core_bind.h

@@ -31,6 +31,7 @@
 #ifndef CORE_BIND_H
 #define CORE_BIND_H
 
+#include "core/debugger/engine_profiler.h"
 #include "core/io/compression.h"
 #include "core/io/dir_access.h"
 #include "core/io/file_access.h"
@@ -673,25 +674,8 @@ public:
 class EngineDebugger : public Object {
 	GDCLASS(EngineDebugger, Object);
 
-	class ProfilerCallable {
-		friend class EngineDebugger;
-
-		Callable callable_toggle;
-		Callable callable_add;
-		Callable callable_tick;
-
-	public:
-		ProfilerCallable() {}
-
-		ProfilerCallable(const Callable &p_toggle, const Callable &p_add, const Callable &p_tick) {
-			callable_toggle = p_toggle;
-			callable_add = p_add;
-			callable_tick = p_tick;
-		}
-	};
-
 	Map<StringName, Callable> captures;
-	Map<StringName, ProfilerCallable> profilers;
+	Map<StringName, Ref<EngineProfiler>> profilers;
 
 protected:
 	static void _bind_methods();
@@ -702,7 +686,7 @@ public:
 
 	bool is_active();
 
-	void register_profiler(const StringName &p_name, const Callable &p_toggle, const Callable &p_add, const Callable &p_tick);
+	void register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler);
 	void unregister_profiler(const StringName &p_name);
 	bool is_profiling(const StringName &p_name);
 	bool has_profiler(const StringName &p_name);
@@ -715,9 +699,6 @@ public:
 
 	void send_message(const String &p_msg, const Array &p_data);
 
-	static void call_toggle(void *p_user, bool p_enable, const Array &p_opts);
-	static void call_add(void *p_user, const Array &p_data);
-	static void call_tick(void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time);
 	static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured);
 
 	EngineDebugger() { singleton = this; }

+ 0 - 183
core/debugger/debugger_marshalls.cpp

@@ -35,159 +35,6 @@
 #define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
 #define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
 
-Array DebuggerMarshalls::ResourceUsage::serialize() {
-	infos.sort();
-
-	Array arr;
-	arr.push_back(infos.size() * 4);
-	for (const ResourceInfo &E : infos) {
-		arr.push_back(E.path);
-		arr.push_back(E.format);
-		arr.push_back(E.type);
-		arr.push_back(E.vram);
-	}
-	return arr;
-}
-
-bool DebuggerMarshalls::ResourceUsage::deserialize(const Array &p_arr) {
-	CHECK_SIZE(p_arr, 1, "ResourceUsage");
-	uint32_t size = p_arr[0];
-	CHECK_SIZE(p_arr, size, "ResourceUsage");
-	int idx = 1;
-	for (uint32_t i = 0; i < size / 4; i++) {
-		ResourceInfo info;
-		info.path = p_arr[idx];
-		info.format = p_arr[idx + 1];
-		info.type = p_arr[idx + 2];
-		info.vram = p_arr[idx + 3];
-		infos.push_back(info);
-	}
-	CHECK_END(p_arr, idx, "ResourceUsage");
-	return true;
-}
-
-Array DebuggerMarshalls::ScriptFunctionSignature::serialize() {
-	Array arr;
-	arr.push_back(name);
-	arr.push_back(id);
-	return arr;
-}
-
-bool DebuggerMarshalls::ScriptFunctionSignature::deserialize(const Array &p_arr) {
-	CHECK_SIZE(p_arr, 2, "ScriptFunctionSignature");
-	name = p_arr[0];
-	id = p_arr[1];
-	CHECK_END(p_arr, 2, "ScriptFunctionSignature");
-	return true;
-}
-
-Array DebuggerMarshalls::NetworkProfilerFrame::serialize() {
-	Array arr;
-	arr.push_back(infos.size() * 6);
-	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].incoming_rset);
-		arr.push_back(infos[i].outgoing_rpc);
-		arr.push_back(infos[i].outgoing_rset);
-	}
-	return arr;
-}
-
-bool DebuggerMarshalls::NetworkProfilerFrame::deserialize(const Array &p_arr) {
-	CHECK_SIZE(p_arr, 1, "NetworkProfilerFrame");
-	uint32_t size = p_arr[0];
-	CHECK_SIZE(p_arr, size, "NetworkProfilerFrame");
-	infos.resize(size);
-	int idx = 1;
-	for (uint32_t i = 0; i < size / 6; ++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].incoming_rset = p_arr[idx + 3];
-		infos.write[i].outgoing_rpc = p_arr[idx + 4];
-		infos.write[i].outgoing_rset = p_arr[idx + 5];
-	}
-	CHECK_END(p_arr, idx, "NetworkProfilerFrame");
-	return true;
-}
-
-Array DebuggerMarshalls::ServersProfilerFrame::serialize() {
-	Array arr;
-	arr.push_back(frame_number);
-	arr.push_back(frame_time);
-	arr.push_back(idle_time);
-	arr.push_back(physics_time);
-	arr.push_back(physics_frame_time);
-	arr.push_back(script_time);
-
-	arr.push_back(servers.size());
-	for (int i = 0; i < servers.size(); i++) {
-		ServerInfo &s = servers[i];
-		arr.push_back(s.name);
-		arr.push_back(s.functions.size() * 2);
-		for (int j = 0; j < s.functions.size(); j++) {
-			ServerFunctionInfo &f = s.functions[j];
-			arr.push_back(f.name);
-			arr.push_back(f.time);
-		}
-	}
-
-	arr.push_back(script_functions.size() * 4);
-	for (int i = 0; i < script_functions.size(); i++) {
-		arr.push_back(script_functions[i].sig_id);
-		arr.push_back(script_functions[i].call_count);
-		arr.push_back(script_functions[i].self_time);
-		arr.push_back(script_functions[i].total_time);
-	}
-	return arr;
-}
-
-bool DebuggerMarshalls::ServersProfilerFrame::deserialize(const Array &p_arr) {
-	CHECK_SIZE(p_arr, 7, "ServersProfilerFrame");
-	frame_number = p_arr[0];
-	frame_time = p_arr[1];
-	idle_time = p_arr[2];
-	physics_time = p_arr[3];
-	physics_frame_time = p_arr[4];
-	script_time = p_arr[5];
-	int servers_size = p_arr[6];
-	int idx = 7;
-	while (servers_size) {
-		CHECK_SIZE(p_arr, idx + 2, "ServersProfilerFrame");
-		servers_size--;
-		ServerInfo si;
-		si.name = p_arr[idx];
-		int sub_data_size = p_arr[idx + 1];
-		idx += 2;
-		CHECK_SIZE(p_arr, idx + sub_data_size, "ServersProfilerFrame");
-		for (int j = 0; j < sub_data_size / 2; j++) {
-			ServerFunctionInfo sf;
-			sf.name = p_arr[idx];
-			sf.time = p_arr[idx + 1];
-			idx += 2;
-			si.functions.push_back(sf);
-		}
-		servers.push_back(si);
-	}
-	CHECK_SIZE(p_arr, idx + 1, "ServersProfilerFrame");
-	int func_size = p_arr[idx];
-	idx += 1;
-	CHECK_SIZE(p_arr, idx + func_size, "ServersProfilerFrame");
-	for (int i = 0; i < func_size / 4; i++) {
-		ScriptFunctionInfo fi;
-		fi.sig_id = p_arr[idx];
-		fi.call_count = p_arr[idx + 1];
-		fi.self_time = p_arr[idx + 2];
-		fi.total_time = p_arr[idx + 3];
-		script_functions.push_back(fi);
-		idx += 4;
-	}
-	CHECK_END(p_arr, idx, "ServersProfilerFrame");
-	return true;
-}
-
 Array DebuggerMarshalls::ScriptStackDump::serialize() {
 	Array arr;
 	arr.push_back(frames.size() * 3);
@@ -298,33 +145,3 @@ bool DebuggerMarshalls::OutputError::deserialize(const Array &p_arr) {
 	CHECK_END(p_arr, idx, "OutputError");
 	return true;
 }
-
-Array DebuggerMarshalls::VisualProfilerFrame::serialize() {
-	Array arr;
-	arr.push_back(frame_number);
-	arr.push_back(areas.size() * 3);
-	for (int i = 0; i < areas.size(); i++) {
-		arr.push_back(areas[i].name);
-		arr.push_back(areas[i].cpu_msec);
-		arr.push_back(areas[i].gpu_msec);
-	}
-	return arr;
-}
-
-bool DebuggerMarshalls::VisualProfilerFrame::deserialize(const Array &p_arr) {
-	CHECK_SIZE(p_arr, 2, "VisualProfilerFrame");
-	frame_number = p_arr[0];
-	int size = p_arr[1];
-	CHECK_SIZE(p_arr, size, "VisualProfilerFrame");
-	int idx = 2;
-	areas.resize(size / 3);
-	RS::FrameProfileArea *w = areas.ptrw();
-	for (int i = 0; i < size / 3; i++) {
-		w[i].name = p_arr[idx];
-		w[i].cpu_msec = p_arr[idx + 1];
-		w[i].gpu_msec = p_arr[idx + 2];
-		idx += 3;
-	}
-	CHECK_END(p_arr, idx, "VisualProfilerFrame");
-	return true;
-}

+ 0 - 87
core/debugger/debugger_marshalls.h

@@ -32,86 +32,8 @@
 #define DEBUGGER_MARSHARLLS_H
 
 #include "core/object/script_language.h"
-#include "servers/rendering_server.h"
 
 struct DebuggerMarshalls {
-	// Memory usage
-	struct ResourceInfo {
-		String path;
-		String format;
-		String type;
-		RID id;
-		int vram = 0;
-		bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; }
-	};
-
-	struct ResourceUsage {
-		List<ResourceInfo> infos;
-
-		Array serialize();
-		bool deserialize(const Array &p_arr);
-	};
-
-	// Network profiler
-	struct MultiplayerNodeInfo {
-		ObjectID node;
-		String node_path;
-		int incoming_rpc = 0;
-		int incoming_rset = 0;
-		int outgoing_rpc = 0;
-		int outgoing_rset = 0;
-	};
-
-	struct NetworkProfilerFrame {
-		Vector<MultiplayerNodeInfo> infos;
-
-		Array serialize();
-		bool deserialize(const Array &p_arr);
-	};
-
-	// Script Profiler
-	class ScriptFunctionSignature {
-	public:
-		StringName name;
-		int id = -1;
-
-		Array serialize();
-		bool deserialize(const Array &p_arr);
-	};
-
-	struct ScriptFunctionInfo {
-		StringName name;
-		int sig_id = -1;
-		int call_count = 0;
-		double self_time = 0;
-		double total_time = 0;
-	};
-
-	// Servers profiler
-	struct ServerFunctionInfo {
-		StringName name;
-		double time = 0;
-	};
-
-	struct ServerInfo {
-		StringName name;
-		List<ServerFunctionInfo> functions;
-	};
-
-	struct ServersProfilerFrame {
-		int frame_number = 0;
-		double frame_time = 0;
-		double idle_time = 0;
-		double physics_time = 0;
-		double physics_frame_time = 0;
-		double script_time = 0;
-		List<ServerInfo> servers;
-		Vector<ScriptFunctionInfo> script_functions;
-
-		Array serialize();
-		bool deserialize(const Array &p_arr);
-	};
-
 	struct ScriptStackVariable {
 		String name;
 		Variant value;
@@ -145,15 +67,6 @@ struct DebuggerMarshalls {
 		Array serialize();
 		bool deserialize(const Array &p_arr);
 	};
-
-	// Visual Profiler
-	struct VisualProfilerFrame {
-		uint64_t frame_number = 0;
-		Vector<RS::FrameProfileArea> areas;
-
-		Array serialize();
-		bool deserialize(const Array &p_arr);
-	};
 };
 
 #endif // DEBUGGER_MARSHARLLS_H

+ 82 - 0
core/debugger/engine_profiler.cpp

@@ -0,0 +1,82 @@
+/*************************************************************************/
+/*  engine_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 "engine_profiler.h"
+
+#include "core/debugger/engine_debugger.h"
+
+void EngineProfiler::_bind_methods() {
+	GDVIRTUAL_BIND(_toggle, "enable", "options");
+	GDVIRTUAL_BIND(_add_frame, "data");
+	GDVIRTUAL_BIND(_tick, "frame_time", "idle_time", "physics_time", "physics_frame_time");
+}
+
+void EngineProfiler::toggle(bool p_enable, const Array &p_array) {
+	GDVIRTUAL_CALL(_toggle, p_enable, p_array);
+}
+
+void EngineProfiler::add(const Array &p_data) {
+	GDVIRTUAL_CALL(_add_frame, p_data);
+}
+
+void EngineProfiler::tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
+	GDVIRTUAL_CALL(_tick, p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time);
+}
+
+Error EngineProfiler::bind(const String &p_name) {
+	ERR_FAIL_COND_V(is_bound(), ERR_ALREADY_IN_USE);
+	EngineDebugger::Profiler prof(
+			this,
+			[](void *p_user, bool p_enable, const Array &p_opts) {
+				((EngineProfiler *)p_user)->toggle(p_enable, p_opts);
+			},
+			[](void *p_user, const Array &p_data) {
+				((EngineProfiler *)p_user)->add(p_data);
+			},
+			[](void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
+				((EngineProfiler *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time);
+			});
+	registration = p_name;
+	EngineDebugger::register_profiler(p_name, prof);
+	return OK;
+}
+
+Error EngineProfiler::unbind() {
+	ERR_FAIL_COND_V(!is_bound(), ERR_UNCONFIGURED);
+	EngineDebugger::unregister_profiler(registration);
+	registration.clear();
+	return OK;
+}
+
+EngineProfiler::~EngineProfiler() {
+	if (is_bound()) {
+		unbind();
+	}
+}

+ 65 - 0
core/debugger/engine_profiler.h

@@ -0,0 +1,65 @@
+/*************************************************************************/
+/*  engine_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 ENGINE_PROFILER_H
+#define ENGINE_PROFILER_H
+
+#include "core/object/ref_counted.h"
+
+#include "core/object/gdvirtual.gen.inc"
+#include "core/object/script_language.h"
+
+class EngineProfiler : public RefCounted {
+	GDCLASS(EngineProfiler, RefCounted);
+
+private:
+	String registration;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual void toggle(bool p_enable, const Array &p_opts);
+	virtual void add(const Array &p_data);
+	virtual void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time);
+
+	Error bind(const String &p_name);
+	Error unbind();
+	bool is_bound() const { return registration.length() > 0; }
+
+	GDVIRTUAL2(_toggle, bool, Array);
+	GDVIRTUAL1(_add_frame, Array);
+	GDVIRTUAL4(_tick, double, double, double, double);
+
+	EngineProfiler() {}
+	virtual ~EngineProfiler();
+};
+
+#endif // ENGINE_PROFILER_H

+ 23 - 331
core/debugger/remote_debugger.cpp

@@ -33,32 +33,13 @@
 #include "core/config/project_settings.h"
 #include "core/debugger/debugger_marshalls.h"
 #include "core/debugger/engine_debugger.h"
+#include "core/debugger/engine_profiler.h"
 #include "core/debugger/script_debugger.h"
 #include "core/input/input.h"
 #include "core/object/script_language.h"
 #include "core/os/os.h"
-#include "scene/main/node.h"
-#include "servers/display_server.h"
-
-template <typename T>
-void RemoteDebugger::_bind_profiler(const String &p_name, T *p_prof) {
-	EngineDebugger::Profiler prof(
-			p_prof,
-			[](void *p_user, bool p_enable, const Array &p_opts) {
-				((T *)p_user)->toggle(p_enable, p_opts);
-			},
-			[](void *p_user, const Array &p_data) {
-				((T *)p_user)->add(p_data);
-			},
-			[](void *p_user, double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
-				((T *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time);
-			});
-	EngineDebugger::register_profiler(p_name, prof);
-}
 
-struct RemoteDebugger::NetworkProfiler {
-public:
-	typedef DebuggerMarshalls::MultiplayerNodeInfo NodeInfo;
+class RemoteDebugger::MultiplayerProfiler : public EngineProfiler {
 	struct BandwidthFrame {
 		uint32_t timestamp;
 		int packet_size;
@@ -70,11 +51,6 @@ public:
 	Vector<BandwidthFrame> bandwidth_out;
 	uint64_t last_bandwidth_time = 0;
 
-	Map<ObjectID, NodeInfo> multiplayer_node_data;
-	uint64_t last_profile_time = 0;
-
-	NetworkProfiler() {}
-
 	int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
 		ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
 		int total_bandwidth = 0;
@@ -96,22 +72,8 @@ public:
 		return total_bandwidth;
 	}
 
-	void init_node(const ObjectID p_node) {
-		if (multiplayer_node_data.has(p_node)) {
-			return;
-		}
-		multiplayer_node_data.insert(p_node, DebuggerMarshalls::MultiplayerNodeInfo());
-		multiplayer_node_data[p_node].node = p_node;
-		multiplayer_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
-		multiplayer_node_data[p_node].incoming_rpc = 0;
-		multiplayer_node_data[p_node].incoming_rset = 0;
-		multiplayer_node_data[p_node].outgoing_rpc = 0;
-		multiplayer_node_data[p_node].outgoing_rset = 0;
-	}
-
+public:
 	void toggle(bool p_enable, const Array &p_opts) {
-		multiplayer_node_data.clear();
-
 		if (!p_enable) {
 			bandwidth_in.clear();
 			bandwidth_out.clear();
@@ -130,37 +92,18 @@ public:
 	}
 
 	void add(const Array &p_data) {
-		ERR_FAIL_COND(p_data.size() < 1);
-		const String type = p_data[0];
-		if (type == "node") {
-			ERR_FAIL_COND(p_data.size() < 3);
-			const ObjectID id = p_data[1];
-			const String what = p_data[2];
-			init_node(id);
-			NodeInfo &info = multiplayer_node_data[id];
-			if (what == "rpc_in") {
-				info.incoming_rpc++;
-			} else if (what == "rpc_out") {
-				info.outgoing_rpc++;
-			} else if (what == "rset_in") {
-				info.incoming_rset = 0;
-			} else if (what == "rset_out") {
-				info.outgoing_rset++;
-			}
-		} else if (type == "bandwidth") {
-			ERR_FAIL_COND(p_data.size() < 4);
-			const String inout = p_data[1];
-			int time = p_data[2];
-			int size = p_data[3];
-			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();
-			}
+		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();
 		}
 	}
 
@@ -174,208 +117,17 @@ public:
 			Array arr;
 			arr.push_back(incoming_bandwidth);
 			arr.push_back(outgoing_bandwidth);
-			EngineDebugger::get_singleton()->send_message("network:bandwidth", arr);
-		}
-		if (pt - last_profile_time > 100) {
-			last_profile_time = pt;
-			DebuggerMarshalls::NetworkProfilerFrame frame;
-			for (const KeyValue<ObjectID, NodeInfo> &E : multiplayer_node_data) {
-				frame.infos.push_back(E.value);
-			}
-			multiplayer_node_data.clear();
-			EngineDebugger::get_singleton()->send_message("network:profile_frame", frame.serialize());
-		}
-	}
-};
-
-struct RemoteDebugger::ScriptsProfiler {
-	typedef DebuggerMarshalls::ScriptFunctionSignature FunctionSignature;
-	typedef DebuggerMarshalls::ScriptFunctionInfo FunctionInfo;
-	struct ProfileInfoSort {
-		bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const {
-			return A->total_time < B->total_time;
-		}
-	};
-	Vector<ScriptLanguage::ProfilingInfo> info;
-	Vector<ScriptLanguage::ProfilingInfo *> ptrs;
-	Map<StringName, int> sig_map;
-	int max_frame_functions = 16;
-
-	void toggle(bool p_enable, const Array &p_opts) {
-		if (p_enable) {
-			sig_map.clear();
-			for (int i = 0; i < ScriptServer::get_language_count(); i++) {
-				ScriptServer::get_language(i)->profiling_start();
-			}
-			if (p_opts.size() == 1 && p_opts[0].get_type() == Variant::INT) {
-				max_frame_functions = MAX(0, int(p_opts[0]));
-			}
-		} else {
-			for (int i = 0; i < ScriptServer::get_language_count(); i++) {
-				ScriptServer::get_language(i)->profiling_stop();
-			}
+			EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr);
 		}
 	}
-
-	void write_frame_data(Vector<FunctionInfo> &r_funcs, uint64_t &r_total, bool p_accumulated) {
-		int ofs = 0;
-		for (int i = 0; i < ScriptServer::get_language_count(); i++) {
-			if (p_accumulated) {
-				ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&info.write[ofs], info.size() - ofs);
-			} else {
-				ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&info.write[ofs], info.size() - ofs);
-			}
-		}
-
-		for (int i = 0; i < ofs; i++) {
-			ptrs.write[i] = &info.write[i];
-		}
-
-		SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa;
-		sa.sort(ptrs.ptrw(), ofs);
-
-		int to_send = MIN(ofs, max_frame_functions);
-
-		// Check signatures first, and compute total time.
-		r_total = 0;
-		for (int i = 0; i < to_send; i++) {
-			if (!sig_map.has(ptrs[i]->signature)) {
-				int idx = sig_map.size();
-				FunctionSignature sig;
-				sig.name = ptrs[i]->signature;
-				sig.id = idx;
-				EngineDebugger::get_singleton()->send_message("servers:function_signature", sig.serialize());
-				sig_map[ptrs[i]->signature] = idx;
-			}
-			r_total += ptrs[i]->self_time;
-		}
-
-		// Send frame, script time, functions information then
-		r_funcs.resize(to_send);
-
-		FunctionInfo *w = r_funcs.ptrw();
-		for (int i = 0; i < to_send; i++) {
-			if (sig_map.has(ptrs[i]->signature)) {
-				w[i].sig_id = sig_map[ptrs[i]->signature];
-			}
-			w[i].call_count = ptrs[i]->call_count;
-			w[i].total_time = ptrs[i]->total_time / 1000000.0;
-			w[i].self_time = ptrs[i]->self_time / 1000000.0;
-		}
-	}
-
-	ScriptsProfiler() {
-		info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
-		ptrs.resize(info.size());
-	}
 };
 
-struct RemoteDebugger::ServersProfiler {
-	bool skip_profile_frame = false;
-	typedef DebuggerMarshalls::ServerInfo ServerInfo;
-	typedef DebuggerMarshalls::ServerFunctionInfo ServerFunctionInfo;
-
-	Map<StringName, ServerInfo> server_data;
-	ScriptsProfiler scripts_profiler;
-
-	double frame_time = 0;
-	double idle_time = 0;
-	double physics_time = 0;
-	double physics_frame_time = 0;
-
-	void toggle(bool p_enable, const Array &p_opts) {
-		skip_profile_frame = false;
-		if (p_enable) {
-			server_data.clear(); // Clear old profiling data.
-		} else {
-			_send_frame_data(true); // Send final frame.
-		}
-		scripts_profiler.toggle(p_enable, p_opts);
-	}
-
-	void add(const Array &p_data) {
-		String name = p_data[0];
-		if (!server_data.has(name)) {
-			ServerInfo info;
-			info.name = name;
-			server_data[name] = info;
-		}
-		ServerInfo &srv = server_data[name];
-
-		ServerFunctionInfo fi;
-		fi.name = p_data[1];
-		fi.time = p_data[2];
-		srv.functions.push_back(fi);
-	}
-
-	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
-		frame_time = p_frame_time;
-		idle_time = p_idle_time;
-		physics_time = p_physics_time;
-		physics_frame_time = p_physics_frame_time;
-		_send_frame_data(false);
-	}
-
-	void _send_frame_data(bool p_final) {
-		DebuggerMarshalls::ServersProfilerFrame frame;
-		frame.frame_number = Engine::get_singleton()->get_process_frames();
-		frame.frame_time = frame_time;
-		frame.idle_time = idle_time;
-		frame.physics_time = physics_time;
-		frame.physics_frame_time = physics_frame_time;
-		Map<StringName, ServerInfo>::Element *E = server_data.front();
-		while (E) {
-			if (!p_final) {
-				frame.servers.push_back(E->get());
-			}
-			E->get().functions.clear();
-			E = E->next();
-		}
-		uint64_t time = 0;
-		scripts_profiler.write_frame_data(frame.script_functions, time, p_final);
-		frame.script_time = USEC_TO_SEC(time);
-		if (skip_profile_frame) {
-			skip_profile_frame = false;
-			return;
-		}
-		if (p_final) {
-			EngineDebugger::get_singleton()->send_message("servers:profile_total", frame.serialize());
-		} else {
-			EngineDebugger::get_singleton()->send_message("servers:profile_frame", frame.serialize());
-		}
-	}
-};
-
-struct RemoteDebugger::VisualProfiler {
-	typedef DebuggerMarshalls::ServerInfo ServerInfo;
-	typedef DebuggerMarshalls::ServerFunctionInfo ServerFunctionInfo;
-
-	Map<StringName, ServerInfo> server_data;
-
-	void toggle(bool p_enable, const Array &p_opts) {
-		RS::get_singleton()->set_frame_profiling_enabled(p_enable);
-	}
-
-	void add(const Array &p_data) {}
-
-	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
-		Vector<RS::FrameProfileArea> profile_areas = RS::get_singleton()->get_frame_profile();
-		DebuggerMarshalls::VisualProfilerFrame frame;
-		if (!profile_areas.size()) {
-			return;
-		}
-
-		frame.frame_number = RS::get_singleton()->get_frame_profile_frame();
-		frame.areas.append_array(profile_areas);
-		EngineDebugger::get_singleton()->send_message("visual:profile_frame", frame.serialize());
-	}
-};
-
-struct RemoteDebugger::PerformanceProfiler {
+class RemoteDebugger::PerformanceProfiler : public EngineProfiler {
 	Object *performance = nullptr;
 	int last_perf_time = 0;
 	uint64_t last_monitor_modification_time = 0;
 
+public:
 	void toggle(bool p_enable, const Array &p_opts) {}
 	void add(const Array &p_data) {}
 	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
@@ -421,29 +173,6 @@ struct RemoteDebugger::PerformanceProfiler {
 	}
 };
 
-void RemoteDebugger::_send_resource_usage() {
-	DebuggerMarshalls::ResourceUsage usage;
-
-	List<RS::TextureInfo> tinfo;
-	RS::get_singleton()->texture_debug_usage(&tinfo);
-
-	for (const RS::TextureInfo &E : tinfo) {
-		DebuggerMarshalls::ResourceInfo info;
-		info.path = E.path;
-		info.vram = E.bytes;
-		info.id = E.texture;
-		info.type = "Texture";
-		if (E.depth == 0) {
-			info.format = itos(E.width) + "x" + itos(E.height) + " " + Image::get_format_name(E.format);
-		} else {
-			info.format = itos(E.width) + "x" + itos(E.height) + "x" + itos(E.depth) + " " + Image::get_format_name(E.format);
-		}
-		usage.infos.push_back(info);
-	}
-
-	EngineDebugger::get_singleton()->send_message("memory:usage", usage.serialize());
-}
-
 Error RemoteDebugger::_put_msg(String p_message, Array p_data) {
 	Array msg;
 	msg.push_back(p_message);
@@ -710,18 +439,12 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
 	msg.push_back(script_lang->debug_get_stack_level_count() > 0);
 	send_message("debug_enter", msg);
 
-	servers_profiler->skip_profile_frame = true; // Avoid frame time spike in debug.
-
 	Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode();
 	if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
 		Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
 	}
 
-	uint64_t loop_begin_usec = 0;
-	uint64_t loop_time_sec = 0;
 	while (is_peer_connected()) {
-		loop_begin_usec = OS::get_singleton()->get_ticks_usec();
-
 		flush_output();
 		peer->poll();
 
@@ -748,7 +471,6 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
 			} else if (command == "continue") {
 				script_debugger->set_depth(-1);
 				script_debugger->set_lines_left(-1);
-				DisplayServer::get_singleton()->window_move_to_foreground();
 				break;
 
 			} else if (command == "break") {
@@ -824,13 +546,6 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
 			OS::get_singleton()->delay_usec(10000);
 			OS::get_singleton()->process_and_drop_events();
 		}
-
-		// This is for the camera override to stay live even when the game is paused from the editor
-		loop_time_sec = (OS::get_singleton()->get_ticks_usec() - loop_begin_usec) / 1000000.0f;
-		RenderingServer::get_singleton()->sync();
-		if (RenderingServer::get_singleton()->has_changed()) {
-			RenderingServer::get_singleton()->draw(true, loop_time_sec * Engine::get_singleton()->get_time_scale());
-		}
 	}
 
 	send_message("debug_exit", Array());
@@ -897,8 +612,6 @@ Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bo
 	} else if (p_cmd == "set_skip_breakpoints") {
 		ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA);
 		script_debugger->set_skip_breakpoints(p_data[0]);
-	} else if (p_cmd == "memory") {
-		_send_resource_usage();
 	} else if (p_cmd == "break") {
 		script_debugger->debug(script_debugger->get_break_language());
 	} else {
@@ -928,23 +641,15 @@ 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");
 
-	// Network Profiler
-	network_profiler = memnew(NetworkProfiler);
-	_bind_profiler("network", network_profiler);
-
-	// Servers Profiler (audio/physics/...)
-	servers_profiler = memnew(ServersProfiler);
-	_bind_profiler("servers", servers_profiler);
-
-	// Visual Profiler (cpu/gpu times)
-	visual_profiler = memnew(VisualProfiler);
-	_bind_profiler("visual", visual_profiler);
+	// Multiplayer Profiler
+	multiplayer_profiler.instantiate();
+	multiplayer_profiler->bind("multiplayer");
 
 	// Performance Profiler
 	Object *perf = Engine::get_singleton()->get_singleton_object("Performance");
 	if (perf) {
-		performance_profiler = memnew(PerformanceProfiler(perf));
-		_bind_profiler("performance", performance_profiler);
+		performance_profiler = Ref<PerformanceProfiler>(memnew(PerformanceProfiler(perf)));
+		performance_profiler->bind("performance");
 		profiler_enable("performance", true);
 	}
 
@@ -973,17 +678,4 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
 RemoteDebugger::~RemoteDebugger() {
 	remove_print_handler(&phl);
 	remove_error_handler(&eh);
-
-	EngineDebugger::get_singleton()->unregister_profiler("servers");
-	EngineDebugger::get_singleton()->unregister_profiler("network");
-	EngineDebugger::get_singleton()->unregister_profiler("visual");
-	if (EngineDebugger::has_profiler("performance")) {
-		EngineDebugger::get_singleton()->unregister_profiler("performance");
-	}
-	memdelete(servers_profiler);
-	memdelete(network_profiler);
-	memdelete(visual_profiler);
-	if (performance_profiler) {
-		memdelete(performance_profiler);
-	}
 }

+ 4 - 10
core/debugger/remote_debugger.h

@@ -49,16 +49,11 @@ public:
 private:
 	typedef DebuggerMarshalls::OutputError ErrorMessage;
 
-	struct NetworkProfiler;
-	struct ServersProfiler;
-	struct ScriptsProfiler;
-	struct VisualProfiler;
-	struct PerformanceProfiler;
+	class MultiplayerProfiler;
+	class PerformanceProfiler;
 
-	NetworkProfiler *network_profiler = nullptr;
-	ServersProfiler *servers_profiler = nullptr;
-	VisualProfiler *visual_profiler = nullptr;
-	PerformanceProfiler *performance_profiler = nullptr;
+	Ref<MultiplayerProfiler> multiplayer_profiler;
+	Ref<PerformanceProfiler> performance_profiler;
 
 	Ref<RemoteDebuggerPeer> peer;
 
@@ -97,7 +92,6 @@ private:
 	bool is_peer_connected() { return peer->is_peer_connected(); }
 	void flush_output();
 
-	void _send_resource_usage();
 	void _send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type);
 
 	Error _profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured);

+ 0 - 1
core/multiplayer/multiplayer_api.cpp

@@ -47,7 +47,6 @@ MultiplayerCacheInterface *(*MultiplayerAPI::create_default_cache_interface)(Mul
 void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) {
 	if (EngineDebugger::is_profiling("multiplayer")) {
 		Array values;
-		values.push_back("bandwidth");
 		values.push_back(p_inout);
 		values.push_back(OS::get_singleton()->get_ticks_msec());
 		values.push_back(p_size);

+ 3 - 0
core/register_core_types.cpp

@@ -37,6 +37,7 @@
 #include "core/crypto/aes_context.h"
 #include "core/crypto/crypto.h"
 #include "core/crypto/hashing_context.h"
+#include "core/debugger/engine_profiler.h"
 #include "core/extension/native_extension.h"
 #include "core/extension/native_extension_manager.h"
 #include "core/input/input.h"
@@ -237,6 +238,8 @@ void register_core_types() {
 
 	GDREGISTER_VIRTUAL_CLASS(ResourceUID);
 
+	GDREGISTER_CLASS(EngineProfiler);
+
 	resource_uid = memnew(ResourceUID);
 
 	native_extension_manager = memnew(NativeExtensionManager);

+ 2 - 7
doc/classes/EngineDebugger.xml

@@ -65,14 +65,9 @@
 		<method name="register_profiler">
 			<return type="void" />
 			<argument index="0" name="name" type="StringName" />
-			<argument index="1" name="toggle" type="Callable" />
-			<argument index="2" name="add" type="Callable" />
-			<argument index="3" name="tick" type="Callable" />
+			<argument index="1" name="profiler" type="EngineProfiler" />
 			<description>
-				Registers a profiler with the given [code]name[/code].
-				[code]toggle[/code] callable is called when the profiler is enabled/disabled. It must take an argument array as an argument.
-				[code]add[/code] callable is called when data is added to profiler using [method EngineDebugger.profiler_add_frame_data]. It must take a data array as argument.
-				[code]tick[/code] callable is called at every active profiler iteration. It must take frame time, idle time, physics time, and physics idle time as arguments.
+				Registers a profiler with the given [code]name[/code]. See [EngineProfiler] for more informations.
 			</description>
 		</method>
 		<method name="send_message">

+ 39 - 0
doc/classes/EngineProfiler.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EngineProfiler" inherits="RefCounted" version="4.0">
+	<brief_description>
+		Base class for creating custom profilers.
+	</brief_description>
+	<description>
+		This class can be used to implement custom profilers that are able to interact with the engine and editor debugger.
+		See [EngineDebugger] and [EditorDebuggerPlugin] for more informations.
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+		<method name="_add_frame" qualifiers="virtual">
+			<return type="void" />
+			<argument index="0" name="data" type="Array" />
+			<description>
+				Called when data is added to profiler using [method EngineDebugger.profiler_add_frame_data].
+			</description>
+		</method>
+		<method name="_tick" qualifiers="virtual">
+			<return type="void" />
+			<argument index="0" name="frame_time" type="float" />
+			<argument index="1" name="idle_time" type="float" />
+			<argument index="2" name="physics_time" type="float" />
+			<argument index="3" name="physics_frame_time" type="float" />
+			<description>
+				Called once every engine iteration when the profiler is active with informations about the current frame.
+			</description>
+		</method>
+		<method name="_toggle" qualifiers="virtual">
+			<return type="void" />
+			<argument index="0" name="enable" type="bool" />
+			<argument index="1" name="options" type="Array" />
+			<description>
+				Called when the profiler is enabled/disabled, along with a set of [code]options[/code].
+			</description>
+		</method>
+	</methods>
+</class>

+ 5 - 17
editor/debugger/editor_network_profiler.cpp

@@ -56,7 +56,7 @@ void EditorNetworkProfiler::_update_frame() {
 
 	TreeItem *root = counters_display->create_item();
 
-	for (const KeyValue<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> &E : nodes_data) {
+	for (const KeyValue<ObjectID, SceneDebugger::RPCNodeInfo> &E : nodes_data) {
 		TreeItem *node = counters_display->create_item(root);
 
 		for (int j = 0; j < counters_display->get_columns(); ++j) {
@@ -65,9 +65,7 @@ void EditorNetworkProfiler::_update_frame() {
 
 		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.incoming_rset == 0 ? "-" : itos(E.value.incoming_rset));
-		node->set_text(3, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc));
-		node->set_text(4, E.value.outgoing_rset == 0 ? "-" : itos(E.value.outgoing_rset));
+		node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc));
 	}
 }
 
@@ -91,14 +89,12 @@ void EditorNetworkProfiler::_clear_pressed() {
 	}
 }
 
-void EditorNetworkProfiler::add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame) {
+void EditorNetworkProfiler::add_node_frame_data(const SceneDebugger::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].incoming_rset += p_frame.incoming_rset;
 		nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc;
-		nodes_data[p_frame.node].outgoing_rset += p_frame.outgoing_rset;
 	}
 
 	if (frame_delay->is_stopped()) {
@@ -174,7 +170,7 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
 	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(5);
+	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);
@@ -184,18 +180,10 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
 	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("Incoming RSET"));
+	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);
-	counters_display->set_column_title(3, TTR("Outgoing RPC"));
-	counters_display->set_column_expand(3, false);
-	counters_display->set_column_clip_content(3, true);
-	counters_display->set_column_custom_minimum_width(3, 120 * EDSCALE);
-	counters_display->set_column_title(4, TTR("Outgoing RSET"));
-	counters_display->set_column_expand(4, false);
-	counters_display->set_column_clip_content(4, true);
-	counters_display->set_column_custom_minimum_width(4, 120 * EDSCALE);
 	add_child(counters_display);
 
 	frame_delay = memnew(Timer);

+ 3 - 3
editor/debugger/editor_network_profiler.h

@@ -31,7 +31,7 @@
 #ifndef EDITORNETWORKPROFILER_H
 #define EDITORNETWORKPROFILER_H
 
-#include "core/debugger/debugger_marshalls.h"
+#include "scene/debugger/scene_debugger.h"
 #include "scene/gui/box_container.h"
 #include "scene/gui/button.h"
 #include "scene/gui/label.h"
@@ -50,7 +50,7 @@ private:
 
 	Timer *frame_delay;
 
-	Map<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> nodes_data;
+	Map<ObjectID, SceneDebugger::RPCNodeInfo> nodes_data;
 
 	void _update_frame();
 
@@ -62,7 +62,7 @@ protected:
 	static void _bind_methods();
 
 public:
-	void add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame);
+	void add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame);
 	void set_bandwidth(int p_incoming, int p_outgoing);
 	bool is_profiling();
 

+ 18 - 12
editor/debugger/script_editor_debugger.cpp

@@ -63,6 +63,7 @@
 #include "scene/gui/texture_button.h"
 #include "scene/gui/tree.h"
 #include "scene/resources/packed_scene.h"
+#include "servers/debugger/servers_debugger.h"
 #include "servers/display_server.h"
 
 using CameraOverride = EditorDebuggerNode::CameraOverride;
@@ -127,6 +128,7 @@ void ScriptEditorDebugger::debug_continue() {
 
 	_clear_execution();
 	_put_msg("continue", Array());
+	_put_msg("servers:foreground", Array());
 }
 
 void ScriptEditorDebugger::update_tabs() {
@@ -277,7 +279,7 @@ void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const
 }
 
 void ScriptEditorDebugger::_video_mem_request() {
-	_put_msg("core:memory", Array());
+	_put_msg("servers:memory", Array());
 }
 
 void ScriptEditorDebugger::_video_mem_export() {
@@ -343,15 +345,15 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 		if (id.is_valid()) {
 			emit_signal(SNAME("remote_object_updated"), id);
 		}
-	} else if (p_msg == "memory:usage") {
+	} else if (p_msg == "servers:memory_usage") {
 		vmem_tree->clear();
 		TreeItem *root = vmem_tree->create_item();
-		DebuggerMarshalls::ResourceUsage usage;
+		ServersDebugger::ResourceUsage usage;
 		usage.deserialize(p_data);
 
 		uint64_t total = 0;
 
-		for (const DebuggerMarshalls::ResourceInfo &E : usage.infos) {
+		for (const ServersDebugger::ResourceInfo &E : usage.infos) {
 			TreeItem *it = vmem_tree->create_item(root);
 			String type = E.type;
 			int bytes = E.vram;
@@ -444,7 +446,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 		performance_profiler->add_profile_frame(frame_data);
 
 	} else if (p_msg == "visual:profile_frame") {
-		DebuggerMarshalls::VisualProfilerFrame frame;
+		ServersDebugger::VisualProfilerFrame frame;
 		frame.deserialize(p_data);
 
 		EditorVisualProfiler::Metric metric;
@@ -591,13 +593,13 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 
 	} else if (p_msg == "servers:function_signature") {
 		// Cache a profiler signature.
-		DebuggerMarshalls::ScriptFunctionSignature sig;
+		ServersDebugger::ScriptFunctionSignature sig;
 		sig.deserialize(p_data);
 		profiler_signature[sig.id] = sig.name;
 
 	} else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") {
 		EditorProfiler::Metric metric;
-		DebuggerMarshalls::ServersProfilerFrame frame;
+		ServersDebugger::ServersProfilerFrame frame;
 		frame.deserialize(p_data);
 		metric.valid = true;
 		metric.frame_number = frame.frame_number;
@@ -641,7 +643,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 		}
 
 		for (int i = 0; i < frame.servers.size(); i++) {
-			const DebuggerMarshalls::ServerInfo &srv = frame.servers[i];
+			const ServersDebugger::ServerInfo &srv = frame.servers[i];
 			EditorProfiler::Metric::Category c;
 			const String name = srv.name;
 			c.name = name.capitalize();
@@ -708,14 +710,14 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 			profiler->add_frame_metric(metric, true);
 		}
 
-	} else if (p_msg == "network:profile_frame") {
-		DebuggerMarshalls::NetworkProfilerFrame frame;
+	} 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 == "network:bandwidth") {
+	} else if (p_msg == "multiplayer:bandwidth") {
 		ERR_FAIL_COND(p_data.size() < 2);
 		network_profiler->set_bandwidth(p_data[0], p_data[1]);
 
@@ -832,6 +834,9 @@ void ScriptEditorDebugger::_notification(int p_what) {
 					msg.push_back(cam->get_far());
 					_put_msg("scene:override_camera_3D:transform", msg);
 				}
+				if (breaked) {
+					_put_msg("servers:draw", Array());
+				}
 			}
 
 			const uint64_t until = OS::get_singleton()->get_ticks_msec() + 20;
@@ -970,7 +975,8 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) {
 	data.push_back(p_enable);
 	switch (p_type) {
 		case PROFILER_NETWORK:
-			_put_msg("profiler:network", data);
+			_put_msg("profiler:multiplayer", data);
+			_put_msg("profiler:rpc", data);
 			break;
 		case PROFILER_VISUAL:
 			_put_msg("profiler:visual", data);

+ 2 - 2
main/main.cpp

@@ -2789,8 +2789,6 @@ void Main::cleanup(bool p_force) {
 		ERR_FAIL_COND(!_start_success);
 	}
 
-	EngineDebugger::deinitialize();
-
 	ResourceLoader::remove_custom_loaders();
 	ResourceSaver::remove_custom_savers();
 
@@ -2833,6 +2831,8 @@ void Main::cleanup(bool p_force) {
 	unregister_scene_types();
 	unregister_server_types();
 
+	EngineDebugger::deinitialize();
+
 	if (xr_server) {
 		memdelete(xr_server);
 	}

+ 97 - 6
scene/debugger/scene_debugger.cpp

@@ -31,30 +31,121 @@
 #include "scene_debugger.h"
 
 #include "core/debugger/engine_debugger.h"
+#include "core/debugger/engine_profiler.h"
 #include "core/io/marshalls.h"
 #include "core/object/script_language.h"
 #include "scene/main/scene_tree.h"
 #include "scene/main/window.h"
 #include "scene/resources/packed_scene.h"
 
-void SceneDebugger::initialize() {
+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 {
+	Map<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_idle_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));
 #endif
 }
 
-void SceneDebugger::deinitialize() {
+SceneDebugger::~SceneDebugger() {
 #ifdef DEBUG_ENABLED
 	if (LiveEditor::singleton) {
-		// Should be removed automatically when deiniting debugger, but just in case
-		if (EngineDebugger::has_capture("scene")) {
-			EngineDebugger::unregister_message_capture("scene");
-		}
+		EngineDebugger::unregister_message_capture("scene");
 		memdelete(LiveEditor::singleton);
 		LiveEditor::singleton = nullptr;
 	}
 #endif
+	singleton = nullptr;
+}
+
+void SceneDebugger::initialize() {
+	if (EngineDebugger::is_active()) {
+		memnew(SceneDebugger);
+	}
+}
+
+void SceneDebugger::deinitialize() {
+	if (singleton) {
+		memdelete(singleton);
+	}
 }
 
 #ifdef DEBUG_ENABLED

+ 28 - 0
scene/debugger/scene_debugger.h

@@ -32,6 +32,7 @@
 #define SCENE_DEBUGGER_H
 
 #include "core/object/class_db.h"
+#include "core/object/ref_counted.h"
 #include "core/string/ustring.h"
 #include "core/templates/pair.h"
 #include "core/variant/array.h"
@@ -40,10 +41,37 @@ class Script;
 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:
 	static void initialize();
 	static void deinitialize();
 
+	~SceneDebugger();
+
 #ifdef DEBUG_ENABLED
 private:
 	static void _save_node(ObjectID id, const String &p_path);

+ 8 - 0
scene/multiplayer/scene_cache_interface.cpp

@@ -107,6 +107,10 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
 	Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer();
 	ERR_FAIL_COND(multiplayer_peer.is_null());
 
+#ifdef DEBUG_ENABLED
+	multiplayer->profile_bandwidth("out", packet.size());
+#endif
+
 	multiplayer_peer->set_transfer_channel(0);
 	multiplayer_peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);
 	multiplayer_peer->set_target_peer(p_from);
@@ -188,6 +192,10 @@ bool SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Path
 		Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer();
 		ERR_FAIL_COND_V(multiplayer_peer.is_null(), false);
 
+#ifdef DEBUG_ENABLED
+		multiplayer->profile_bandwidth("out", packet.size() * peers_to_add.size());
+#endif
+
 		for (int &E : peers_to_add) {
 			multiplayer_peer->set_target_peer(E); // To all of you.
 			multiplayer_peer->set_transfer_channel(0);

+ 5 - 0
scene/multiplayer/scene_replication_interface.cpp

@@ -147,6 +147,11 @@ Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size,
 	ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER);
 	ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED);
 	ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
+
+#ifdef DEBUG_ENABLED
+	multiplayer->profile_bandwidth("out", p_size);
+#endif
+
 	Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
 	peer->set_target_peer(p_peer);
 	peer->set_transfer_channel(0);

+ 4 - 5
scene/multiplayer/scene_rpc_interface.cpp

@@ -46,12 +46,11 @@ void SceneRPCInterface::make_default() {
 
 #ifdef DEBUG_ENABLED
 _FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) {
-	if (EngineDebugger::is_profiling("multiplayer")) {
+	if (EngineDebugger::is_profiling("rpc")) {
 		Array values;
-		values.push_back("node");
 		values.push_back(p_id);
 		values.push_back(p_what);
-		EngineDebugger::profiler_add_frame_data("multiplayer", values);
+		EngineDebugger::profiler_add_frame_data("rpc", values);
 	}
 }
 #else
@@ -268,7 +267,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i
 	argp.resize(argc);
 
 #ifdef DEBUG_ENABLED
-	_profile_node_data("in_rpc", p_node->get_instance_id());
+	_profile_node_data("rpc_in", p_node->get_instance_id());
 #endif
 
 	int out;
@@ -471,7 +470,7 @@ void SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_m
 
 	if (p_peer_id != node_id) {
 #ifdef DEBUG_ENABLED
-		_profile_node_data("out_rpc", node->get_instance_id());
+		_profile_node_data("rpc_out", node->get_instance_id());
 #endif
 
 		_send_rpc(node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount);

+ 1 - 0
servers/SCsub

@@ -12,6 +12,7 @@ SConscript("physics_2d/SCsub")
 SConscript("rendering/SCsub")
 SConscript("audio/SCsub")
 SConscript("text/SCsub")
+SConscript("debugger/SCsub")
 
 lib = env.add_library("servers", env.servers_sources)
 

+ 5 - 0
servers/debugger/SCsub

@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env.add_source_files(env.servers_sources, "*.cpp")

+ 463 - 0
servers/debugger/servers_debugger.cpp

@@ -0,0 +1,463 @@
+/*************************************************************************/
+/*  servers_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 "servers_debugger.h"
+
+#include "core/config/project_settings.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/engine_profiler.h"
+#include "core/io/marshalls.h"
+#include "servers/display_server.h"
+
+#define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
+#define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
+
+Array ServersDebugger::ResourceUsage::serialize() {
+	infos.sort();
+
+	Array arr;
+	arr.push_back(infos.size() * 4);
+	for (const ResourceInfo &E : infos) {
+		arr.push_back(E.path);
+		arr.push_back(E.format);
+		arr.push_back(E.type);
+		arr.push_back(E.vram);
+	}
+	return arr;
+}
+
+bool ServersDebugger::ResourceUsage::deserialize(const Array &p_arr) {
+	CHECK_SIZE(p_arr, 1, "ResourceUsage");
+	uint32_t size = p_arr[0];
+	CHECK_SIZE(p_arr, size, "ResourceUsage");
+	int idx = 1;
+	for (uint32_t i = 0; i < size / 4; i++) {
+		ResourceInfo info;
+		info.path = p_arr[idx];
+		info.format = p_arr[idx + 1];
+		info.type = p_arr[idx + 2];
+		info.vram = p_arr[idx + 3];
+		infos.push_back(info);
+	}
+	CHECK_END(p_arr, idx, "ResourceUsage");
+	return true;
+}
+
+Array ServersDebugger::ScriptFunctionSignature::serialize() {
+	Array arr;
+	arr.push_back(name);
+	arr.push_back(id);
+	return arr;
+}
+
+bool ServersDebugger::ScriptFunctionSignature::deserialize(const Array &p_arr) {
+	CHECK_SIZE(p_arr, 2, "ScriptFunctionSignature");
+	name = p_arr[0];
+	id = p_arr[1];
+	CHECK_END(p_arr, 2, "ScriptFunctionSignature");
+	return true;
+}
+
+Array ServersDebugger::ServersProfilerFrame::serialize() {
+	Array arr;
+	arr.push_back(frame_number);
+	arr.push_back(frame_time);
+	arr.push_back(idle_time);
+	arr.push_back(physics_time);
+	arr.push_back(physics_frame_time);
+	arr.push_back(script_time);
+
+	arr.push_back(servers.size());
+	for (int i = 0; i < servers.size(); i++) {
+		ServerInfo &s = servers[i];
+		arr.push_back(s.name);
+		arr.push_back(s.functions.size() * 2);
+		for (int j = 0; j < s.functions.size(); j++) {
+			ServerFunctionInfo &f = s.functions[j];
+			arr.push_back(f.name);
+			arr.push_back(f.time);
+		}
+	}
+
+	arr.push_back(script_functions.size() * 4);
+	for (int i = 0; i < script_functions.size(); i++) {
+		arr.push_back(script_functions[i].sig_id);
+		arr.push_back(script_functions[i].call_count);
+		arr.push_back(script_functions[i].self_time);
+		arr.push_back(script_functions[i].total_time);
+	}
+	return arr;
+}
+
+bool ServersDebugger::ServersProfilerFrame::deserialize(const Array &p_arr) {
+	CHECK_SIZE(p_arr, 7, "ServersProfilerFrame");
+	frame_number = p_arr[0];
+	frame_time = p_arr[1];
+	idle_time = p_arr[2];
+	physics_time = p_arr[3];
+	physics_frame_time = p_arr[4];
+	script_time = p_arr[5];
+	int servers_size = p_arr[6];
+	int idx = 7;
+	while (servers_size) {
+		CHECK_SIZE(p_arr, idx + 2, "ServersProfilerFrame");
+		servers_size--;
+		ServerInfo si;
+		si.name = p_arr[idx];
+		int sub_data_size = p_arr[idx + 1];
+		idx += 2;
+		CHECK_SIZE(p_arr, idx + sub_data_size, "ServersProfilerFrame");
+		for (int j = 0; j < sub_data_size / 2; j++) {
+			ServerFunctionInfo sf;
+			sf.name = p_arr[idx];
+			sf.time = p_arr[idx + 1];
+			idx += 2;
+			si.functions.push_back(sf);
+		}
+		servers.push_back(si);
+	}
+	CHECK_SIZE(p_arr, idx + 1, "ServersProfilerFrame");
+	int func_size = p_arr[idx];
+	idx += 1;
+	CHECK_SIZE(p_arr, idx + func_size, "ServersProfilerFrame");
+	for (int i = 0; i < func_size / 4; i++) {
+		ScriptFunctionInfo fi;
+		fi.sig_id = p_arr[idx];
+		fi.call_count = p_arr[idx + 1];
+		fi.self_time = p_arr[idx + 2];
+		fi.total_time = p_arr[idx + 3];
+		script_functions.push_back(fi);
+		idx += 4;
+	}
+	CHECK_END(p_arr, idx, "ServersProfilerFrame");
+	return true;
+}
+
+Array ServersDebugger::VisualProfilerFrame::serialize() {
+	Array arr;
+	arr.push_back(frame_number);
+	arr.push_back(areas.size() * 3);
+	for (int i = 0; i < areas.size(); i++) {
+		arr.push_back(areas[i].name);
+		arr.push_back(areas[i].cpu_msec);
+		arr.push_back(areas[i].gpu_msec);
+	}
+	return arr;
+}
+
+bool ServersDebugger::VisualProfilerFrame::deserialize(const Array &p_arr) {
+	CHECK_SIZE(p_arr, 2, "VisualProfilerFrame");
+	frame_number = p_arr[0];
+	int size = p_arr[1];
+	CHECK_SIZE(p_arr, size, "VisualProfilerFrame");
+	int idx = 2;
+	areas.resize(size / 3);
+	RS::FrameProfileArea *w = areas.ptrw();
+	for (int i = 0; i < size / 3; i++) {
+		w[i].name = p_arr[idx];
+		w[i].cpu_msec = p_arr[idx + 1];
+		w[i].gpu_msec = p_arr[idx + 2];
+		idx += 3;
+	}
+	CHECK_END(p_arr, idx, "VisualProfilerFrame");
+	return true;
+}
+class ServersDebugger::ScriptsProfiler : public EngineProfiler {
+	typedef ServersDebugger::ScriptFunctionSignature FunctionSignature;
+	typedef ServersDebugger::ScriptFunctionInfo FunctionInfo;
+	struct ProfileInfoSort {
+		bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const {
+			return A->total_time < B->total_time;
+		}
+	};
+	Vector<ScriptLanguage::ProfilingInfo> info;
+	Vector<ScriptLanguage::ProfilingInfo *> ptrs;
+	Map<StringName, int> sig_map;
+	int max_frame_functions = 16;
+
+public:
+	void toggle(bool p_enable, const Array &p_opts) {
+		if (p_enable) {
+			sig_map.clear();
+			for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+				ScriptServer::get_language(i)->profiling_start();
+			}
+			if (p_opts.size() == 1 && p_opts[0].get_type() == Variant::INT) {
+				max_frame_functions = MAX(0, int(p_opts[0]));
+			}
+		} else {
+			for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+				ScriptServer::get_language(i)->profiling_stop();
+			}
+		}
+	}
+
+	void write_frame_data(Vector<FunctionInfo> &r_funcs, uint64_t &r_total, bool p_accumulated) {
+		int ofs = 0;
+		for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+			if (p_accumulated) {
+				ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&info.write[ofs], info.size() - ofs);
+			} else {
+				ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&info.write[ofs], info.size() - ofs);
+			}
+		}
+
+		for (int i = 0; i < ofs; i++) {
+			ptrs.write[i] = &info.write[i];
+		}
+
+		SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa;
+		sa.sort(ptrs.ptrw(), ofs);
+
+		int to_send = MIN(ofs, max_frame_functions);
+
+		// Check signatures first, and compute total time.
+		r_total = 0;
+		for (int i = 0; i < to_send; i++) {
+			if (!sig_map.has(ptrs[i]->signature)) {
+				int idx = sig_map.size();
+				FunctionSignature sig;
+				sig.name = ptrs[i]->signature;
+				sig.id = idx;
+				EngineDebugger::get_singleton()->send_message("servers:function_signature", sig.serialize());
+				sig_map[ptrs[i]->signature] = idx;
+			}
+			r_total += ptrs[i]->self_time;
+		}
+
+		// Send frame, script time, functions information then
+		r_funcs.resize(to_send);
+
+		FunctionInfo *w = r_funcs.ptrw();
+		for (int i = 0; i < to_send; i++) {
+			if (sig_map.has(ptrs[i]->signature)) {
+				w[i].sig_id = sig_map[ptrs[i]->signature];
+			}
+			w[i].call_count = ptrs[i]->call_count;
+			w[i].total_time = ptrs[i]->total_time / 1000000.0;
+			w[i].self_time = ptrs[i]->self_time / 1000000.0;
+		}
+	}
+
+	ScriptsProfiler() {
+		info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
+		ptrs.resize(info.size());
+	}
+};
+
+class ServersDebugger::ServersProfiler : public EngineProfiler {
+	bool skip_profile_frame = false;
+	typedef ServersDebugger::ServerInfo ServerInfo;
+	typedef ServersDebugger::ServerFunctionInfo ServerFunctionInfo;
+
+	Map<StringName, ServerInfo> server_data;
+	ScriptsProfiler scripts_profiler;
+
+	double frame_time = 0;
+	double idle_time = 0;
+	double physics_time = 0;
+	double physics_frame_time = 0;
+
+	void _send_frame_data(bool p_final) {
+		ServersDebugger::ServersProfilerFrame frame;
+		frame.frame_number = Engine::get_singleton()->get_process_frames();
+		frame.frame_time = frame_time;
+		frame.idle_time = idle_time;
+		frame.physics_time = physics_time;
+		frame.physics_frame_time = physics_frame_time;
+		Map<StringName, ServerInfo>::Element *E = server_data.front();
+		while (E) {
+			if (!p_final) {
+				frame.servers.push_back(E->get());
+			}
+			E->get().functions.clear();
+			E = E->next();
+		}
+		uint64_t time = 0;
+		scripts_profiler.write_frame_data(frame.script_functions, time, p_final);
+		frame.script_time = USEC_TO_SEC(time);
+		if (skip_profile_frame) {
+			skip_profile_frame = false;
+			return;
+		}
+		if (p_final) {
+			EngineDebugger::get_singleton()->send_message("servers:profile_total", frame.serialize());
+		} else {
+			EngineDebugger::get_singleton()->send_message("servers:profile_frame", frame.serialize());
+		}
+	}
+
+public:
+	void toggle(bool p_enable, const Array &p_opts) {
+		skip_profile_frame = false;
+		if (p_enable) {
+			server_data.clear(); // Clear old profiling data.
+		} else {
+			_send_frame_data(true); // Send final frame.
+		}
+		scripts_profiler.toggle(p_enable, p_opts);
+	}
+
+	void add(const Array &p_data) {
+		String name = p_data[0];
+		if (!server_data.has(name)) {
+			ServerInfo info;
+			info.name = name;
+			server_data[name] = info;
+		}
+		ServerInfo &srv = server_data[name];
+
+		ServerFunctionInfo fi;
+		fi.name = p_data[1];
+		fi.time = p_data[2];
+		srv.functions.push_back(fi);
+	}
+
+	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
+		frame_time = p_frame_time;
+		idle_time = p_idle_time;
+		physics_time = p_physics_time;
+		physics_frame_time = p_physics_frame_time;
+		_send_frame_data(false);
+	}
+
+	void skip_frame() {
+		skip_profile_frame = true;
+	}
+};
+
+class ServersDebugger::VisualProfiler : public EngineProfiler {
+	typedef ServersDebugger::ServerInfo ServerInfo;
+	typedef ServersDebugger::ServerFunctionInfo ServerFunctionInfo;
+
+	Map<StringName, ServerInfo> server_data;
+
+public:
+	void toggle(bool p_enable, const Array &p_opts) {
+		RS::get_singleton()->set_frame_profiling_enabled(p_enable);
+	}
+
+	void add(const Array &p_data) {}
+
+	void tick(double p_frame_time, double p_idle_time, double p_physics_time, double p_physics_frame_time) {
+		Vector<RS::FrameProfileArea> profile_areas = RS::get_singleton()->get_frame_profile();
+		ServersDebugger::VisualProfilerFrame frame;
+		if (!profile_areas.size()) {
+			return;
+		}
+
+		frame.frame_number = RS::get_singleton()->get_frame_profile_frame();
+		frame.areas.append_array(profile_areas);
+		EngineDebugger::get_singleton()->send_message("visual:profile_frame", frame.serialize());
+	}
+};
+
+ServersDebugger *ServersDebugger::singleton = nullptr;
+
+void ServersDebugger::initialize() {
+	if (EngineDebugger::is_active()) {
+		memnew(ServersDebugger);
+	}
+}
+
+void ServersDebugger::deinitialize() {
+	if (singleton) {
+		memdelete(singleton);
+	}
+}
+
+Error ServersDebugger::_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
+	ERR_FAIL_COND_V(!singleton, ERR_BUG);
+	r_captured = true;
+	if (p_cmd == "memory") {
+		singleton->_send_resource_usage();
+	} else if (p_cmd == "draw") { // Forced redraw.
+		// For camera override to stay live when the game is paused from the editor.
+		double delta = 0.0;
+		if (singleton->last_draw_time) {
+			delta = (OS::get_singleton()->get_ticks_usec() - singleton->last_draw_time) / 1000000.0;
+		}
+		singleton->last_draw_time = OS::get_singleton()->get_ticks_usec();
+		RenderingServer::get_singleton()->sync();
+		if (RenderingServer::get_singleton()->has_changed()) {
+			RenderingServer::get_singleton()->draw(true, delta);
+		}
+	} else if (p_cmd == "foreground") {
+		singleton->last_draw_time = 0.0;
+		DisplayServer::get_singleton()->window_move_to_foreground();
+		singleton->servers_profiler->skip_frame();
+	} else {
+		r_captured = false;
+	}
+	return OK;
+}
+
+void ServersDebugger::_send_resource_usage() {
+	ServersDebugger::ResourceUsage usage;
+
+	List<RS::TextureInfo> tinfo;
+	RS::get_singleton()->texture_debug_usage(&tinfo);
+
+	for (const RS::TextureInfo &E : tinfo) {
+		ServersDebugger::ResourceInfo info;
+		info.path = E.path;
+		info.vram = E.bytes;
+		info.id = E.texture;
+		info.type = "Texture";
+		if (E.depth == 0) {
+			info.format = itos(E.width) + "x" + itos(E.height) + " " + Image::get_format_name(E.format);
+		} else {
+			info.format = itos(E.width) + "x" + itos(E.height) + "x" + itos(E.depth) + " " + Image::get_format_name(E.format);
+		}
+		usage.infos.push_back(info);
+	}
+
+	EngineDebugger::get_singleton()->send_message("servers:memory_usage", usage.serialize());
+}
+
+ServersDebugger::ServersDebugger() {
+	singleton = this;
+
+	// Generic servers profiler (audio/physics/...)
+	servers_profiler.instantiate();
+	servers_profiler->bind("servers");
+
+	// Visual Profiler (cpu/gpu times)
+	visual_profiler.instantiate();
+	visual_profiler->bind("visual");
+
+	EngineDebugger::Capture servers_cap(nullptr, &_capture);
+	EngineDebugger::register_message_capture("servers", servers_cap);
+}
+
+ServersDebugger::~ServersDebugger() {
+	EngineDebugger::unregister_message_capture("servers");
+	singleton = nullptr;
+}

+ 132 - 0
servers/debugger/servers_debugger.h

@@ -0,0 +1,132 @@
+/*************************************************************************/
+/*  servers_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 SERVER_DEBUGGER_H
+#define SERVER_DEBUGGER_H
+
+#include "core/debugger/debugger_marshalls.h"
+
+#include "servers/rendering_server.h"
+
+class ServersDebugger {
+public:
+	// Memory usage
+	struct ResourceInfo {
+		String path;
+		String format;
+		String type;
+		RID id;
+		int vram = 0;
+		bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; }
+	};
+
+	struct ResourceUsage {
+		List<ResourceInfo> infos;
+
+		Array serialize();
+		bool deserialize(const Array &p_arr);
+	};
+
+	// Script Profiler
+	struct ScriptFunctionSignature {
+		StringName name;
+		int id = -1;
+
+		Array serialize();
+		bool deserialize(const Array &p_arr);
+	};
+
+	struct ScriptFunctionInfo {
+		StringName name;
+		int sig_id = -1;
+		int call_count = 0;
+		double self_time = 0;
+		double total_time = 0;
+	};
+
+	// Servers profiler
+	struct ServerFunctionInfo {
+		StringName name;
+		double time = 0;
+	};
+
+	struct ServerInfo {
+		StringName name;
+		List<ServerFunctionInfo> functions;
+	};
+
+	struct ServersProfilerFrame {
+		int frame_number = 0;
+		double frame_time = 0;
+		double idle_time = 0;
+		double physics_time = 0;
+		double physics_frame_time = 0;
+		double script_time = 0;
+		List<ServerInfo> servers;
+		Vector<ScriptFunctionInfo> script_functions;
+
+		Array serialize();
+		bool deserialize(const Array &p_arr);
+	};
+
+	// Visual Profiler
+	struct VisualProfilerFrame {
+		uint64_t frame_number = 0;
+		Vector<RS::FrameProfileArea> areas;
+
+		Array serialize();
+		bool deserialize(const Array &p_arr);
+	};
+
+private:
+	class ScriptsProfiler;
+	class ServersProfiler;
+	class VisualProfiler;
+
+	double last_draw_time = 0.0;
+	Ref<ServersProfiler> servers_profiler;
+	Ref<VisualProfiler> visual_profiler;
+
+	static ServersDebugger *singleton;
+
+	static Error _capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured);
+
+	void _send_resource_usage();
+
+	ServersDebugger();
+
+public:
+	static void initialize();
+	static void deinitialize();
+
+	~ServersDebugger();
+};
+
+#endif // SERVERS_DEBUGGER_H

+ 5 - 0
servers/register_server_types.cpp

@@ -56,6 +56,7 @@
 #include "camera/camera_feed.h"
 #include "camera_server.h"
 #include "core/extension/native_extension_manager.h"
+#include "debugger/servers_debugger.h"
 #include "display_server.h"
 #include "navigation_server_2d.h"
 #include "navigation_server_3d.h"
@@ -223,6 +224,8 @@ void register_server_types() {
 	GDREGISTER_CLASS(PhysicsTestMotionParameters3D);
 	GDREGISTER_CLASS(PhysicsTestMotionResult3D);
 
+	ServersDebugger::initialize();
+
 	// Physics 2D
 	GLOBAL_DEF(PhysicsServer2DManager::setting_property_name, "DEFAULT");
 	ProjectSettings::get_singleton()->set_custom_property_info(PhysicsServer2DManager::setting_property_name, PropertyInfo(Variant::STRING, PhysicsServer2DManager::setting_property_name, PROPERTY_HINT_ENUM, "DEFAULT"));
@@ -241,6 +244,8 @@ void register_server_types() {
 }
 
 void unregister_server_types() {
+	ServersDebugger::deinitialize();
+
 	NativeExtensionManager::get_singleton()->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SERVERS);
 
 	memdelete(shader_types);