summaryrefslogtreecommitdiff
path: root/core/debugger
diff options
context:
space:
mode:
Diffstat (limited to 'core/debugger')
-rw-r--r--core/debugger/SCsub5
-rw-r--r--core/debugger/debugger_marshalls.cpp329
-rw-r--r--core/debugger/debugger_marshalls.h175
-rw-r--r--core/debugger/engine_debugger.cpp186
-rw-r--r--core/debugger/engine_debugger.h130
-rw-r--r--core/debugger/local_debugger.cpp416
-rw-r--r--core/debugger/local_debugger.h60
-rw-r--r--core/debugger/remote_debugger.cpp935
-rw-r--r--core/debugger/remote_debugger.h114
-rw-r--r--core/debugger/remote_debugger_peer.cpp242
-rw-r--r--core/debugger/remote_debugger_peer.h94
-rw-r--r--core/debugger/script_debugger.cpp123
-rw-r--r--core/debugger/script_debugger.h80
13 files changed, 2889 insertions, 0 deletions
diff --git a/core/debugger/SCsub b/core/debugger/SCsub
new file mode 100644
index 0000000000..1c5f954470
--- /dev/null
+++ b/core/debugger/SCsub
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+Import('env')
+
+env.add_source_files(env.core_sources, "*.cpp")
diff --git a/core/debugger/debugger_marshalls.cpp b/core/debugger/debugger_marshalls.cpp
new file mode 100644
index 0000000000..4bccf0805f
--- /dev/null
+++ b/core/debugger/debugger_marshalls.cpp
@@ -0,0 +1,329 @@
+/*************************************************************************/
+/* debugger_marshalls.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "debugger_marshalls.h"
+
+#include "core/io/marshalls.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. Exptected 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. Exptected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
+
+Array DebuggerMarshalls::ResourceUsage::serialize() {
+ infos.sort();
+
+ Array arr;
+ arr.push_back(infos.size() * 4);
+ for (List<ResourceInfo>::Element *E = infos.front(); E; E = E->next()) {
+ arr.push_back(E->get().path);
+ arr.push_back(E->get().format);
+ arr.push_back(E->get().type);
+ arr.push_back(E->get().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 + 3, "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);
+ for (int i = 0; i < frames.size(); i++) {
+ arr.push_back(frames[i].file);
+ arr.push_back(frames[i].line);
+ arr.push_back(frames[i].func);
+ }
+ return arr;
+}
+
+bool DebuggerMarshalls::ScriptStackDump::deserialize(const Array &p_arr) {
+ CHECK_SIZE(p_arr, 1, "ScriptStackDump");
+ uint32_t size = p_arr[0];
+ CHECK_SIZE(p_arr, size, "ScriptStackDump");
+ int idx = 1;
+ for (uint32_t i = 0; i < size / 3; i++) {
+ ScriptLanguage::StackInfo sf;
+ sf.file = p_arr[idx];
+ sf.line = p_arr[idx + 1];
+ sf.func = p_arr[idx + 2];
+ frames.push_back(sf);
+ idx += 3;
+ }
+ CHECK_END(p_arr, idx, "ScriptStackDump");
+ return true;
+}
+
+Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
+ Array arr;
+ arr.push_back(name);
+ arr.push_back(type);
+
+ Variant var = value;
+ if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) {
+ var = Variant();
+ }
+
+ int len = 0;
+ Error err = encode_variant(var, NULL, len, true);
+ if (err != OK)
+ ERR_PRINT("Failed to encode variant.");
+
+ if (len > max_size) {
+ arr.push_back(Variant());
+ } else {
+ arr.push_back(var);
+ }
+ return arr;
+}
+
+bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) {
+ CHECK_SIZE(p_arr, 3, "ScriptStackVariable");
+ name = p_arr[0];
+ type = p_arr[1];
+ value = p_arr[2];
+ CHECK_END(p_arr, 3, "ScriptStackVariable");
+ return true;
+}
+
+Array DebuggerMarshalls::OutputError::serialize() {
+ Array arr;
+ arr.push_back(hr);
+ arr.push_back(min);
+ arr.push_back(sec);
+ arr.push_back(msec);
+ arr.push_back(source_file);
+ arr.push_back(source_func);
+ arr.push_back(source_line);
+ arr.push_back(error);
+ arr.push_back(error_descr);
+ arr.push_back(warning);
+ unsigned int size = callstack.size();
+ const ScriptLanguage::StackInfo *r = callstack.ptr();
+ arr.push_back(size * 3);
+ for (int i = 0; i < callstack.size(); i++) {
+ arr.push_back(r[i].file);
+ arr.push_back(r[i].func);
+ arr.push_back(r[i].line);
+ }
+ return arr;
+}
+
+bool DebuggerMarshalls::OutputError::deserialize(const Array &p_arr) {
+ CHECK_SIZE(p_arr, 11, "OutputError");
+ hr = p_arr[0];
+ min = p_arr[1];
+ sec = p_arr[2];
+ msec = p_arr[3];
+ source_file = p_arr[4];
+ source_func = p_arr[5];
+ source_line = p_arr[6];
+ error = p_arr[7];
+ error_descr = p_arr[8];
+ warning = p_arr[9];
+ unsigned int stack_size = p_arr[10];
+ CHECK_SIZE(p_arr, stack_size, "OutputError");
+ int idx = 11;
+ callstack.resize(stack_size / 3);
+ ScriptLanguage::StackInfo *w = callstack.ptrw();
+ for (unsigned int i = 0; i < stack_size / 3; i++) {
+ w[i].file = p_arr[idx];
+ w[i].func = p_arr[idx + 1];
+ w[i].line = p_arr[idx + 2];
+ idx += 3;
+ }
+ 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);
+ VS::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;
+}
diff --git a/core/debugger/debugger_marshalls.h b/core/debugger/debugger_marshalls.h
new file mode 100644
index 0000000000..4c15adc555
--- /dev/null
+++ b/core/debugger/debugger_marshalls.h
@@ -0,0 +1,175 @@
+/*************************************************************************/
+/* debugger_marshalls.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef DEBUGGER_MARSHARLLS_H
+#define DEBUGGER_MARSHARLLS_H
+
+#include "core/script_language.h"
+#include "servers/visual_server.h"
+
+struct DebuggerMarshalls {
+
+ // Memory usage
+ struct ResourceInfo {
+ String path;
+ String format;
+ String type;
+ RID id;
+ int vram;
+ bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; }
+ ResourceInfo() {
+ vram = 0;
+ }
+ };
+
+ 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;
+ float self_time = 0;
+ float total_time = 0;
+ };
+
+ // Servers profiler
+ struct ServerFunctionInfo {
+ StringName name;
+ float time = 0;
+ };
+
+ struct ServerInfo {
+ StringName name;
+ List<ServerFunctionInfo> functions;
+ };
+
+ struct ServersProfilerFrame {
+ int frame_number = 0;
+ float frame_time = 0;
+ float idle_time = 0;
+ float physics_time = 0;
+ float physics_frame_time = 0;
+ float script_time = 0;
+ List<ServerInfo> servers;
+ Vector<ScriptFunctionInfo> script_functions;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+ struct ScriptStackVariable {
+ String name;
+ Variant value;
+ int type;
+ ScriptStackVariable() {
+ type = -1;
+ }
+
+ Array serialize(int max_size = 1 << 20); // 1 MiB default.
+ bool deserialize(const Array &p_arr);
+ };
+
+ struct ScriptStackDump {
+ List<ScriptLanguage::StackInfo> frames;
+ ScriptStackDump() {}
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+ struct OutputError {
+ int hr;
+ int min;
+ int sec;
+ int msec;
+ String source_file;
+ String source_func;
+ int source_line;
+ String error;
+ String error_descr;
+ bool warning;
+ Vector<ScriptLanguage::StackInfo> callstack;
+
+ OutputError() {
+ hr = -1;
+ min = -1;
+ sec = -1;
+ msec = -1;
+ source_line = -1;
+ warning = false;
+ }
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+ // Visual Profiler
+ struct VisualProfilerFrame {
+ uint64_t frame_number;
+ Vector<VS::FrameProfileArea> areas;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+};
+
+#endif // DEBUGGER_MARSHARLLS_H
diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp
new file mode 100644
index 0000000000..305802e5f2
--- /dev/null
+++ b/core/debugger/engine_debugger.cpp
@@ -0,0 +1,186 @@
+/*************************************************************************/
+/* engine_debugger.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "engine_debugger.h"
+
+#include "core/debugger/local_debugger.h"
+#include "core/debugger/remote_debugger.h"
+#include "core/debugger/script_debugger.h"
+#include "core/os/os.h"
+
+EngineDebugger *EngineDebugger::singleton = NULL;
+ScriptDebugger *EngineDebugger::script_debugger = NULL;
+
+Map<StringName, EngineDebugger::Profiler> EngineDebugger::profilers;
+Map<StringName, EngineDebugger::Capture> EngineDebugger::captures;
+
+void EngineDebugger::register_profiler(const StringName &p_name, const Profiler &p_func) {
+ ERR_FAIL_COND_MSG(profilers.has(p_name), "Profiler already registered: " + p_name);
+ profilers.insert(p_name, p_func);
+}
+
+void EngineDebugger::unregister_profiler(const StringName &p_name) {
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
+ Profiler &p = profilers[p_name];
+ if (p.active && p.toggle) {
+ p.toggle(p.data, false, Array());
+ p.active = false;
+ }
+ profilers.erase(p_name);
+}
+
+void EngineDebugger::register_message_capture(const StringName &p_name, Capture p_func) {
+ ERR_FAIL_COND_MSG(captures.has(p_name), "Capture already registered: " + p_name);
+ captures.insert(p_name, p_func);
+}
+
+void EngineDebugger::unregister_message_capture(const StringName &p_name) {
+ ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
+ captures.erase(p_name);
+}
+
+void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) {
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't change profiler state, no profiler: " + p_name);
+ Profiler &p = profilers[p_name];
+ if (p.toggle) {
+ p.toggle(p.data, p_enabled, p_opts);
+ }
+ p.active = p_enabled;
+}
+
+void EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) {
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't add frame data, no profiler: " + p_name);
+ Profiler &p = profilers[p_name];
+ if (p.add) {
+ p.add(p.data, p_data);
+ }
+}
+
+bool EngineDebugger::is_profiling(const StringName &p_name) {
+ return profilers.has(p_name) && profilers[p_name].active;
+}
+
+bool EngineDebugger::has_profiler(const StringName &p_name) {
+ return profilers.has(p_name);
+}
+
+bool EngineDebugger::has_capture(const StringName &p_name) {
+ return captures.has(p_name);
+}
+
+Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured) {
+ r_captured = false;
+ ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, "Capture not registered: " + p_name);
+ const Capture &cap = captures[p_name];
+ return cap.capture(cap.data, p_msg, p_args, r_captured);
+}
+
+void EngineDebugger::line_poll() {
+ // The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught
+ if (poll_every % 2048 == 0)
+ poll_events(false);
+ poll_every++;
+}
+
+void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_idle_ticks, uint64_t p_physics_ticks, float p_physics_frame_time) {
+ frame_time = USEC_TO_SEC(p_frame_ticks);
+ idle_time = USEC_TO_SEC(p_idle_ticks);
+ physics_time = USEC_TO_SEC(p_physics_ticks);
+ physics_frame_time = p_physics_frame_time;
+ // Notify tick to running profilers
+ for (Map<StringName, Profiler>::Element *E = profilers.front(); E; E = E->next()) {
+ Profiler &p = E->get();
+ if (!p.active || !p.tick)
+ continue;
+ p.tick(p.data, frame_time, idle_time, physics_time, physics_frame_time);
+ }
+ singleton->poll_events(true);
+}
+
+void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, Vector<String> p_breakpoints) {
+ if (p_uri.empty())
+ return;
+ if (p_uri == "local://") {
+ singleton = memnew(LocalDebugger);
+ script_debugger = memnew(ScriptDebugger);
+ // Tell the OS that we want to handle termination signals.
+ OS::get_singleton()->initialize_debugging();
+ } else {
+ singleton = RemoteDebugger::create_for_uri(p_uri);
+ if (!singleton)
+ return;
+ script_debugger = memnew(ScriptDebugger);
+ // Notify editor of our pid (to allow focus stealing).
+ Array msg;
+ msg.push_back(OS::get_singleton()->get_process_id());
+ singleton->send_message("set_pid", msg);
+ }
+ if (!singleton)
+ return;
+
+ // There is a debugger, parse breakpoints.
+ ScriptDebugger *script_debugger = singleton->get_script_debugger();
+ script_debugger->set_skip_breakpoints(p_skip_breakpoints);
+
+ for (int i = 0; i < p_breakpoints.size(); i++) {
+
+ String bp = p_breakpoints[i];
+ int sp = bp.find_last(":");
+ ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format.");
+
+ script_debugger->insert_breakpoint(bp.substr(sp + 1, bp.length()).to_int(), bp.substr(0, sp));
+ }
+}
+
+void EngineDebugger::deinitialize() {
+ if (!singleton)
+ return;
+
+ // Stop all profilers
+ for (Map<StringName, Profiler>::Element *E = profilers.front(); E; E = E->next()) {
+ if (E->get().active)
+ singleton->profiler_enable(E->key(), false);
+ }
+
+ // Flush any remaining message
+ singleton->poll_events(false);
+
+ memdelete(singleton);
+ singleton = NULL;
+ profilers.clear();
+ captures.clear();
+}
+
+EngineDebugger::~EngineDebugger() {
+ if (script_debugger)
+ memdelete(script_debugger);
+ script_debugger = NULL;
+ singleton = NULL;
+}
diff --git a/core/debugger/engine_debugger.h b/core/debugger/engine_debugger.h
new file mode 100644
index 0000000000..9e01aeba18
--- /dev/null
+++ b/core/debugger/engine_debugger.h
@@ -0,0 +1,130 @@
+/*************************************************************************/
+/* engine_debugger.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef ENGINE_DEBUGGER_H
+#define ENGINE_DEBUGGER_H
+
+#include "core/array.h"
+#include "core/map.h"
+#include "core/string_name.h"
+#include "core/ustring.h"
+#include "core/variant.h"
+#include "core/vector.h"
+
+class ScriptDebugger;
+
+class EngineDebugger {
+public:
+ typedef void (*ProfilingToggle)(void *p_user, bool p_enable, const Array &p_opts);
+ typedef void (*ProfilingTick)(void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time);
+ typedef void (*ProfilingAdd)(void *p_user, const Array &p_arr);
+ typedef Error (*CaptureFunc)(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
+
+ class Profiler {
+ friend class EngineDebugger;
+
+ ProfilingToggle toggle = NULL;
+ ProfilingAdd add = NULL;
+ ProfilingTick tick = NULL;
+ void *data = NULL;
+ bool active = false;
+
+ public:
+ Profiler() {}
+ Profiler(void *p_data, ProfilingToggle p_toggle, ProfilingAdd p_add, ProfilingTick p_tick) {
+ data = p_data;
+ toggle = p_toggle;
+ add = p_add;
+ tick = p_tick;
+ }
+ };
+
+ class Capture {
+ friend class EngineDebugger;
+
+ CaptureFunc capture = NULL;
+ void *data = NULL;
+
+ public:
+ Capture() {}
+ Capture(void *p_data, CaptureFunc p_capture) {
+ data = p_data;
+ capture = p_capture;
+ }
+ };
+
+private:
+ float frame_time = 0.0;
+ float idle_time = 0.0;
+ float physics_time = 0.0;
+ float physics_frame_time = 0.0;
+
+ uint32_t poll_every = 0;
+
+protected:
+ static EngineDebugger *singleton;
+ static ScriptDebugger *script_debugger;
+
+ static Map<StringName, Profiler> profilers;
+ static Map<StringName, Capture> captures;
+
+public:
+ _FORCE_INLINE_ static EngineDebugger *get_singleton() { return singleton; }
+ _FORCE_INLINE_ static bool is_active() { return singleton != NULL && script_debugger != NULL; }
+
+ _FORCE_INLINE_ static ScriptDebugger *get_script_debugger() { return script_debugger; };
+
+ static void initialize(const String &p_uri, bool p_skip_breakpoints, Vector<String> p_breakpoints);
+ static void deinitialize();
+ static void register_profiler(const StringName &p_name, const Profiler &p_profiler);
+ static void unregister_profiler(const StringName &p_name);
+ static bool is_profiling(const StringName &p_name);
+ static bool has_profiler(const StringName &p_name);
+ static void profiler_add_frame_data(const StringName &p_name, const Array &p_data);
+
+ static void register_message_capture(const StringName &p_name, Capture p_func);
+ static void unregister_message_capture(const StringName &p_name);
+ static bool has_capture(const StringName &p_name);
+
+ void iteration(uint64_t p_frame_ticks, uint64_t p_idle_ticks, uint64_t p_physics_ticks, float p_physics_frame_time);
+ void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array());
+ Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured);
+
+ void line_poll();
+
+ virtual void poll_events(bool p_is_idle) {}
+ virtual void send_message(const String &p_msg, const Array &p_data) = 0;
+ virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) = 0;
+ virtual void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false) = 0;
+
+ virtual ~EngineDebugger();
+};
+
+#endif // ENGINE_DEBUGGER_H
diff --git a/core/debugger/local_debugger.cpp b/core/debugger/local_debugger.cpp
new file mode 100644
index 0000000000..913d3fc031
--- /dev/null
+++ b/core/debugger/local_debugger.cpp
@@ -0,0 +1,416 @@
+/*************************************************************************/
+/* local_debugger.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "local_debugger.h"
+
+#include "core/debugger/script_debugger.h"
+#include "core/os/os.h"
+#include "scene/main/scene_tree.h"
+
+struct LocalDebugger::ScriptsProfiler {
+ struct ProfileInfoSort {
+
+ bool operator()(const ScriptLanguage::ProfilingInfo &A, const ScriptLanguage::ProfilingInfo &B) const {
+ return A.total_time > B.total_time;
+ }
+ };
+
+ float frame_time = 0;
+ uint64_t idle_accum = 0;
+ Vector<ScriptLanguage::ProfilingInfo> pinfo;
+
+ void toggle(bool p_enable, const Array &p_opts) {
+ if (p_enable) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->profiling_start();
+ }
+
+ print_line("BEGIN PROFILING");
+ pinfo.resize(32768);
+ } else {
+ _print_frame_data(true);
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->profiling_stop();
+ }
+ }
+ }
+
+ void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
+ frame_time = p_frame_time;
+ _print_frame_data(false);
+ }
+
+ void _print_frame_data(bool p_accumulated) {
+ uint64_t diff = OS::get_singleton()->get_ticks_usec() - idle_accum;
+
+ if (!p_accumulated && diff < 1000000) //show every one second
+ return;
+
+ idle_accum = OS::get_singleton()->get_ticks_usec();
+
+ 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(&pinfo.write[ofs], pinfo.size() - ofs);
+ else
+ ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&pinfo.write[ofs], pinfo.size() - ofs);
+ }
+
+ SortArray<ScriptLanguage::ProfilingInfo, ProfileInfoSort> sort;
+ sort.sort(pinfo.ptrw(), ofs);
+
+ // compute total script frame time
+ uint64_t script_time_us = 0;
+ for (int i = 0; i < ofs; i++) {
+
+ script_time_us += pinfo[i].self_time;
+ }
+ float script_time = USEC_TO_SEC(script_time_us);
+ float total_time = p_accumulated ? script_time : frame_time;
+
+ if (!p_accumulated) {
+ print_line("FRAME: total: " + rtos(total_time) + " script: " + rtos(script_time) + "/" + itos(script_time * 100 / total_time) + " %");
+ } else {
+ print_line("ACCUMULATED: total: " + rtos(total_time));
+ }
+
+ for (int i = 0; i < ofs; i++) {
+
+ print_line(itos(i) + ":" + pinfo[i].signature);
+ float tt = USEC_TO_SEC(pinfo[i].total_time);
+ float st = USEC_TO_SEC(pinfo[i].self_time);
+ print_line("\ttotal: " + rtos(tt) + "/" + itos(tt * 100 / total_time) + " % \tself: " + rtos(st) + "/" + itos(st * 100 / total_time) + " % tcalls: " + itos(pinfo[i].call_count));
+ }
+ }
+
+ ScriptsProfiler() {
+ idle_accum = OS::get_singleton()->get_ticks_usec();
+ }
+};
+
+void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
+
+ ScriptLanguage *script_lang = script_debugger->get_break_language();
+
+ if (!target_function.empty()) {
+ String current_function = script_lang->debug_get_stack_level_function(0);
+ if (current_function != target_function) {
+ script_debugger->set_depth(0);
+ script_debugger->set_lines_left(1);
+ return;
+ }
+ target_function = "";
+ }
+
+ print_line("\nDebugger Break, Reason: '" + script_lang->debug_get_error() + "'");
+ print_line("*Frame " + itos(0) + " - " + script_lang->debug_get_stack_level_source(0) + ":" + itos(script_lang->debug_get_stack_level_line(0)) + " in function '" + script_lang->debug_get_stack_level_function(0) + "'");
+ print_line("Enter \"help\" for assistance.");
+ int current_frame = 0;
+ int total_frames = script_lang->debug_get_stack_level_count();
+ while (true) {
+
+ OS::get_singleton()->print("debug> ");
+ String line = OS::get_singleton()->get_stdin_string().strip_edges();
+
+ // Cache options
+ String variable_prefix = options["variable_prefix"];
+
+ if (line == "") {
+ print_line("\nDebugger Break, Reason: '" + script_lang->debug_get_error() + "'");
+ print_line("*Frame " + itos(current_frame) + " - " + script_lang->debug_get_stack_level_source(current_frame) + ":" + itos(script_lang->debug_get_stack_level_line(current_frame)) + " in function '" + script_lang->debug_get_stack_level_function(current_frame) + "'");
+ print_line("Enter \"help\" for assistance.");
+ } else if (line == "c" || line == "continue")
+ break;
+ else if (line == "bt" || line == "breakpoint") {
+
+ for (int i = 0; i < total_frames; i++) {
+
+ String cfi = (current_frame == i) ? "*" : " "; //current frame indicator
+ print_line(cfi + "Frame " + itos(i) + " - " + script_lang->debug_get_stack_level_source(i) + ":" + itos(script_lang->debug_get_stack_level_line(i)) + " in function '" + script_lang->debug_get_stack_level_function(i) + "'");
+ }
+
+ } else if (line.begins_with("fr") || line.begins_with("frame")) {
+
+ if (line.get_slice_count(" ") == 1) {
+ print_line("*Frame " + itos(current_frame) + " - " + script_lang->debug_get_stack_level_source(current_frame) + ":" + itos(script_lang->debug_get_stack_level_line(current_frame)) + " in function '" + script_lang->debug_get_stack_level_function(current_frame) + "'");
+ } else {
+ int frame = line.get_slicec(' ', 1).to_int();
+ if (frame < 0 || frame >= total_frames) {
+ print_line("Error: Invalid frame.");
+ } else {
+ current_frame = frame;
+ print_line("*Frame " + itos(frame) + " - " + script_lang->debug_get_stack_level_source(frame) + ":" + itos(script_lang->debug_get_stack_level_line(frame)) + " in function '" + script_lang->debug_get_stack_level_function(frame) + "'");
+ }
+ }
+
+ } else if (line.begins_with("set")) {
+
+ if (line.get_slice_count(" ") == 1) {
+
+ for (Map<String, String>::Element *E = options.front(); E; E = E->next()) {
+ print_line("\t" + E->key() + "=" + E->value());
+ }
+
+ } else {
+ String key_value = line.get_slicec(' ', 1);
+ int value_pos = key_value.find("=");
+
+ if (value_pos < 0) {
+ print_line("Error: Invalid set format. Use: set key=value");
+ } else {
+
+ String key = key_value.left(value_pos);
+
+ if (!options.has(key)) {
+ print_line("Error: Unknown option " + key);
+ } else {
+
+ // Allow explicit tab character
+ String value = key_value.right(value_pos + 1).replace("\\t", "\t");
+
+ options[key] = value;
+ }
+ }
+ }
+
+ } else if (line == "lv" || line == "locals") {
+
+ List<String> locals;
+ List<Variant> values;
+ script_lang->debug_get_stack_level_locals(current_frame, &locals, &values);
+ print_variables(locals, values, variable_prefix);
+
+ } else if (line == "gv" || line == "globals") {
+
+ List<String> globals;
+ List<Variant> values;
+ script_lang->debug_get_globals(&globals, &values);
+ print_variables(globals, values, variable_prefix);
+
+ } else if (line == "mv" || line == "members") {
+
+ List<String> members;
+ List<Variant> values;
+ script_lang->debug_get_stack_level_members(current_frame, &members, &values);
+ print_variables(members, values, variable_prefix);
+
+ } else if (line.begins_with("p") || line.begins_with("print")) {
+
+ if (line.get_slice_count(" ") <= 1) {
+ print_line("Usage: print <expre>");
+ } else {
+
+ String expr = line.get_slicec(' ', 2);
+ String res = script_lang->debug_parse_stack_level_expression(current_frame, expr);
+ print_line(res);
+ }
+
+ } else if (line == "s" || line == "step") {
+
+ script_debugger->set_depth(-1);
+ script_debugger->set_lines_left(1);
+ break;
+ } else if (line == "n" || line == "next") {
+
+ script_debugger->set_depth(0);
+ script_debugger->set_lines_left(1);
+ break;
+ } else if (line == "fin" || line == "finish") {
+
+ String current_function = script_lang->debug_get_stack_level_function(0);
+
+ for (int i = 0; i < total_frames; i++) {
+ target_function = script_lang->debug_get_stack_level_function(i);
+ if (target_function != current_function) {
+ script_debugger->set_depth(0);
+ script_debugger->set_lines_left(1);
+ return;
+ }
+ }
+
+ print_line("Error: Reached last frame.");
+ target_function = "";
+
+ } else if (line.begins_with("br") || line.begins_with("break")) {
+
+ if (line.get_slice_count(" ") <= 1) {
+
+ const Map<int, Set<StringName> > &breakpoints = script_debugger->get_breakpoints();
+ if (breakpoints.size() == 0) {
+ print_line("No Breakpoints.");
+ continue;
+ }
+
+ print_line("Breakpoint(s): " + itos(breakpoints.size()));
+ for (Map<int, Set<StringName> >::Element *E = breakpoints.front(); E; E = E->next()) {
+ print_line("\t" + String(E->value().front()->get()) + ":" + itos(E->key()));
+ }
+
+ } else {
+
+ Pair<String, int> breakpoint = to_breakpoint(line);
+
+ String source = breakpoint.first;
+ int linenr = breakpoint.second;
+
+ if (source.empty())
+ continue;
+
+ script_debugger->insert_breakpoint(linenr, source);
+
+ print_line("Added breakpoint at " + source + ":" + itos(linenr));
+ }
+
+ } else if (line == "q" || line == "quit") {
+
+ // Do not stop again on quit
+ script_debugger->clear_breakpoints();
+ script_debugger->set_depth(-1);
+ script_debugger->set_lines_left(-1);
+
+ SceneTree::get_singleton()->quit();
+ break;
+ } else if (line.begins_with("delete")) {
+
+ if (line.get_slice_count(" ") <= 1) {
+ script_debugger->clear_breakpoints();
+ } else {
+
+ Pair<String, int> breakpoint = to_breakpoint(line);
+
+ String source = breakpoint.first;
+ int linenr = breakpoint.second;
+
+ if (source.empty())
+ continue;
+
+ script_debugger->remove_breakpoint(linenr, source);
+
+ print_line("Removed breakpoint at " + source + ":" + itos(linenr));
+ }
+
+ } else if (line == "h" || line == "help") {
+
+ print_line("Built-In Debugger command list:\n");
+ print_line("\tc,continue\t\t Continue execution.");
+ print_line("\tbt,backtrace\t\t Show stack trace (frames).");
+ print_line("\tfr,frame <frame>:\t Change current frame.");
+ print_line("\tlv,locals\t\t Show local variables for current frame.");
+ print_line("\tmv,members\t\t Show member variables for \"this\" in frame.");
+ print_line("\tgv,globals\t\t Show global variables.");
+ print_line("\tp,print <expr>\t\t Execute and print variable in expression.");
+ print_line("\ts,step\t\t\t Step to next line.");
+ print_line("\tn,next\t\t\t Next line.");
+ print_line("\tfin,finish\t\t Step out of current frame.");
+ print_line("\tbr,break [source:line]\t List all breakpoints or place a breakpoint.");
+ print_line("\tdelete [source:line]:\t Delete one/all breakpoints.");
+ print_line("\tset [key=value]:\t List all options, or set one.");
+ print_line("\tq,quit\t\t\t Quit application.");
+ } else {
+ print_line("Error: Invalid command, enter \"help\" for assistance.");
+ }
+ }
+}
+
+void LocalDebugger::print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix) {
+
+ String value;
+ Vector<String> value_lines;
+ const List<Variant>::Element *V = values.front();
+ for (const List<String>::Element *E = names.front(); E; E = E->next()) {
+
+ value = String(V->get());
+
+ if (variable_prefix.empty()) {
+ print_line(E->get() + ": " + String(V->get()));
+ } else {
+
+ print_line(E->get() + ":");
+ value_lines = value.split("\n");
+ for (int i = 0; i < value_lines.size(); ++i) {
+ print_line(variable_prefix + value_lines[i]);
+ }
+ }
+
+ V = V->next();
+ }
+}
+
+Pair<String, int> LocalDebugger::to_breakpoint(const String &p_line) {
+
+ String breakpoint_part = p_line.get_slicec(' ', 1);
+ Pair<String, int> breakpoint;
+
+ int last_colon = breakpoint_part.rfind(":");
+ if (last_colon < 0) {
+ print_line("Error: Invalid breakpoint format. Expected [source:line]");
+ return breakpoint;
+ }
+
+ breakpoint.first = script_debugger->breakpoint_find_source(breakpoint_part.left(last_colon).strip_edges());
+ breakpoint.second = breakpoint_part.right(last_colon).strip_edges().to_int();
+
+ return breakpoint;
+}
+
+void LocalDebugger::send_message(const String &p_message, const Array &p_args) {
+
+ // This needs to be cleaned up entirely.
+ // print_line("MESSAGE: '" + p_message + "' - " + String(Variant(p_args)));
+}
+
+void LocalDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) {
+
+ print_line("ERROR: '" + (p_descr.empty() ? p_err : p_descr) + "'");
+}
+
+LocalDebugger::LocalDebugger() {
+
+ options["variable_prefix"] = "";
+
+ // Bind scripts profiler.
+ scripts_profiler = memnew(ScriptsProfiler);
+ Profiler scr_prof(
+ scripts_profiler,
+ [](void *p_user, bool p_enable, const Array &p_opts) {
+ ((ScriptsProfiler *)p_user)->toggle(p_enable, p_opts);
+ },
+ NULL,
+ [](void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
+ ((ScriptsProfiler *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time);
+ });
+ register_profiler("scripts", scr_prof);
+}
+
+LocalDebugger::~LocalDebugger() {
+ unregister_profiler("scripts");
+ if (scripts_profiler)
+ memdelete(scripts_profiler);
+}
diff --git a/core/debugger/local_debugger.h b/core/debugger/local_debugger.h
new file mode 100644
index 0000000000..e299df0546
--- /dev/null
+++ b/core/debugger/local_debugger.h
@@ -0,0 +1,60 @@
+/*************************************************************************/
+/* local_debugger.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef LOCAL_DEBUGGER_H
+#define LOCAL_DEBUGGER_H
+
+#include "core/debugger/engine_debugger.h"
+#include "core/list.h"
+#include "core/script_language.h"
+
+class LocalDebugger : public EngineDebugger {
+
+private:
+ struct ScriptsProfiler;
+
+ ScriptsProfiler *scripts_profiler = NULL;
+
+ String target_function;
+ Map<String, String> options;
+
+ Pair<String, int> to_breakpoint(const String &p_line);
+ void print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix);
+
+public:
+ void debug(bool p_can_continue, bool p_is_error_breakpoint);
+ void send_message(const String &p_message, const Array &p_args);
+ void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type);
+
+ LocalDebugger();
+ ~LocalDebugger();
+};
+
+#endif // LOCAL_DEBUGGER_H
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
new file mode 100644
index 0000000000..7952391a27
--- /dev/null
+++ b/core/debugger/remote_debugger.cpp
@@ -0,0 +1,935 @@
+/*************************************************************************/
+/* remote_debugger.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "remote_debugger.h"
+
+#include "core/debugger/debugger_marshalls.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/script_debugger.h"
+#include "core/os/input.h"
+#include "core/os/os.h"
+#include "core/project_settings.h"
+#include "core/script_language.h"
+#include "scene/main/node.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, float p_frame_time, float p_idle_time, float p_physics_time, float 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;
+ 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;
+
+ Map<ObjectID, NodeInfo> multiplayer_node_data;
+ uint64_t last_profile_time = 0;
+
+ NetworkProfiler() {}
+
+ int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
+ int total_bandwidth = 0;
+
+ uint32_t timestamp = OS::get_singleton()->get_ticks_msec();
+ uint32_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 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;
+ }
+
+ void toggle(bool p_enable, const Array &p_opts) {
+ multiplayer_node_data.clear();
+
+ 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() < 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();
+ }
+ }
+ }
+
+ void tick(float p_frame_time, float p_idle_time, float p_physics_time, float 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("network:bandwidth", arr);
+ }
+ if (pt - last_profile_time > 100) {
+ last_profile_time = pt;
+ DebuggerMarshalls::NetworkProfilerFrame frame;
+ for (Map<ObjectID, NodeInfo>::Element *E = multiplayer_node_data.front(); E; E = E->next()) {
+ frame.infos.push_back(E->get());
+ }
+ 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();
+ }
+ }
+ }
+
+ 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;
+
+ float frame_time = 0;
+ float idle_time = 0;
+ float physics_time = 0;
+ float 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(float p_frame_time, float p_idle_time, float p_physics_time, float 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_frames_drawn();
+ 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) {
+ VS::get_singleton()->set_frame_profiling_enabled(p_enable);
+ }
+
+ void add(const Array &p_data) {}
+
+ void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
+ Vector<VS::FrameProfileArea> profile_areas = VS::get_singleton()->get_frame_profile();
+ DebuggerMarshalls::VisualProfilerFrame frame;
+ if (!profile_areas.size())
+ return;
+
+ frame.frame_number = VS::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 {
+
+ Object *performance = NULL;
+ int last_perf_time = 0;
+
+ void toggle(bool p_enable, const Array &p_opts) {}
+ void add(const Array &p_data) {}
+ void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
+ if (!performance)
+ return;
+
+ uint64_t pt = OS::get_singleton()->get_ticks_msec();
+ if (pt - last_perf_time < 1000)
+ return;
+ last_perf_time = pt;
+ int max = performance->get("MONITOR_MAX");
+ Array arr;
+ arr.resize(max);
+ for (int i = 0; i < max; i++) {
+ arr[i] = performance->call("get_monitor", i);
+ }
+ EngineDebugger::get_singleton()->send_message("performance:profile_frame", arr);
+ }
+
+ PerformanceProfiler(Object *p_performance) {
+ performance = p_performance;
+ }
+};
+
+void RemoteDebugger::_send_resource_usage() {
+
+ DebuggerMarshalls::ResourceUsage usage;
+
+ List<VS::TextureInfo> tinfo;
+ VS::get_singleton()->texture_debug_usage(&tinfo);
+
+ for (List<VS::TextureInfo>::Element *E = tinfo.front(); E; E = E->next()) {
+
+ DebuggerMarshalls::ResourceInfo info;
+ info.path = E->get().path;
+ info.vram = E->get().bytes;
+ info.id = E->get().texture;
+ info.type = "Texture";
+ if (E->get().depth == 0) {
+ info.format = itos(E->get().width) + "x" + itos(E->get().height) + " " + Image::get_format_name(E->get().format);
+ } else {
+ info.format = itos(E->get().width) + "x" + itos(E->get().height) + "x" + itos(E->get().depth) + " " + Image::get_format_name(E->get().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);
+ msg.push_back(p_data);
+ Error err = peer->put_message(msg);
+ if (err != OK)
+ n_messages_dropped++;
+ return err;
+}
+
+void RemoteDebugger::_err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, ErrorHandlerType p_type) {
+
+ if (p_type == ERR_HANDLER_SCRIPT)
+ return; //ignore script errors, those go through debugger
+
+ RemoteDebugger *rd = (RemoteDebugger *)p_this;
+ if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) // Can't handle recursive errors during flush.
+ return;
+
+ Vector<ScriptLanguage::StackInfo> si;
+
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ si = ScriptServer::get_language(i)->debug_get_current_stack_info();
+ if (si.size())
+ break;
+ }
+
+ // send_error will lock internally.
+ rd->script_debugger->send_error(p_func, p_file, p_line, p_err, p_descr, p_type, si);
+}
+
+void RemoteDebugger::_print_handler(void *p_this, const String &p_string, bool p_error) {
+
+ RemoteDebugger *rd = (RemoteDebugger *)p_this;
+
+ if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) // Can't handle recursive prints during flush.
+ return;
+
+ String s = p_string;
+ int allowed_chars = MIN(MAX(rd->max_chars_per_second - rd->char_count, 0), s.length());
+
+ if (allowed_chars == 0)
+ return;
+
+ if (allowed_chars < s.length()) {
+ s = s.substr(0, allowed_chars);
+ }
+
+ MutexLock lock(rd->mutex);
+
+ rd->char_count += allowed_chars;
+ bool overflowed = rd->char_count >= rd->max_chars_per_second;
+ if (rd->is_peer_connected()) {
+ if (overflowed)
+ s += "[...]";
+ rd->output_strings.push_back(s);
+
+ if (overflowed) {
+ rd->output_strings.push_back("[output overflow, print less text!]");
+ }
+ }
+}
+
+RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String &p_what, const String &p_descr) {
+ ErrorMessage oe;
+ oe.error = p_what;
+ oe.error_descr = p_descr;
+ oe.warning = false;
+ uint64_t time = OS::get_singleton()->get_ticks_msec();
+ oe.hr = time / 3600000;
+ oe.min = (time / 60000) % 60;
+ oe.sec = (time / 1000) % 60;
+ oe.msec = time % 1000;
+ return oe;
+}
+
+void RemoteDebugger::flush_output() {
+ flush_thread = Thread::get_caller_id();
+ flushing = true;
+ MutexLock lock(mutex);
+ if (!is_peer_connected())
+ return;
+
+ if (n_messages_dropped > 0) {
+ ErrorMessage err_msg = _create_overflow_error("TOO_MANY_MESSAGES", "Too many messages! " + String::num_int64(n_messages_dropped) + " messages were dropped. Profiling might misbheave, try raising 'network/limits/debugger/max_queued_messages' in project setting.");
+ if (_put_msg("error", err_msg.serialize()) == OK)
+ n_messages_dropped = 0;
+ }
+
+ if (output_strings.size()) {
+
+ // Join output strings so we generate less messages.
+ Vector<String> strings;
+ strings.resize(output_strings.size());
+ String *w = strings.ptrw();
+ for (int i = 0; i < output_strings.size(); i++) {
+ w[i] = output_strings[i];
+ }
+
+ Array arr;
+ arr.push_back(strings);
+ _put_msg("output", arr);
+ output_strings.clear();
+ }
+
+ while (errors.size()) {
+ ErrorMessage oe = errors.front()->get();
+ _put_msg("error", oe.serialize());
+ errors.pop_front();
+ }
+
+ // Update limits
+ uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000;
+
+ if (ticks - last_reset > 1000) {
+ last_reset = ticks;
+ char_count = 0;
+ err_count = 0;
+ n_errors_dropped = 0;
+ warn_count = 0;
+ n_warnings_dropped = 0;
+ }
+ flushing = false;
+}
+
+void RemoteDebugger::send_message(const String &p_message, const Array &p_args) {
+
+ MutexLock lock(mutex);
+ if (is_peer_connected()) {
+ _put_msg(p_message, p_args);
+ }
+}
+
+void RemoteDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) {
+
+ ErrorMessage oe;
+ oe.error = p_err;
+ oe.error_descr = p_descr;
+ oe.source_file = p_file;
+ oe.source_line = p_line;
+ oe.source_func = p_func;
+ oe.warning = p_type == ERR_HANDLER_WARNING;
+ uint64_t time = OS::get_singleton()->get_ticks_msec();
+ oe.hr = time / 3600000;
+ oe.min = (time / 60000) % 60;
+ oe.sec = (time / 1000) % 60;
+ oe.msec = time % 1000;
+ oe.callstack.append_array(script_debugger->get_error_stack_info());
+
+ if (flushing && Thread::get_caller_id() == flush_thread) // Can't handle recursive errors during flush.
+ return;
+
+ MutexLock lock(mutex);
+
+ if (oe.warning) {
+ warn_count++;
+ } else {
+ err_count++;
+ }
+
+ if (is_peer_connected()) {
+
+ if (oe.warning) {
+ if (warn_count > max_warnings_per_second) {
+ n_warnings_dropped++;
+ if (n_warnings_dropped == 1) {
+ // Only print one message about dropping per second
+ ErrorMessage overflow = _create_overflow_error("TOO_MANY_WARNINGS", "Too many warnings! Ignoring warnings for up to 1 second.");
+ errors.push_back(overflow);
+ }
+ } else {
+ errors.push_back(oe);
+ }
+ } else {
+ if (err_count > max_errors_per_second) {
+ n_errors_dropped++;
+ if (n_errors_dropped == 1) {
+ // Only print one message about dropping per second
+ ErrorMessage overflow = _create_overflow_error("TOO_MANY_ERRORS", "Too many errors! Ignoring errors for up to 1 second.");
+ errors.push_back(overflow);
+ }
+ } else {
+ errors.push_back(oe);
+ }
+ }
+ }
+}
+
+void RemoteDebugger::_send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type) {
+ DebuggerMarshalls::ScriptStackVariable stvar;
+ List<String>::Element *E = p_names.front();
+ List<Variant>::Element *F = p_vals.front();
+ while (E) {
+ stvar.name = E->get();
+ stvar.value = F->get();
+ stvar.type = p_type;
+ send_message("stack_frame_var", stvar.serialize());
+ E = E->next();
+ F = F->next();
+ }
+}
+
+Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, bool &r_captured) {
+ const int idx = p_msg.find(":");
+ r_captured = false;
+ if (idx < 0) { // No prefix, unknown message.
+ return OK;
+ }
+ const String cap = p_msg.substr(0, idx);
+ if (!has_capture(cap))
+ return ERR_UNAVAILABLE; // Unknown message...
+ const String msg = p_msg.substr(idx + 1);
+ return capture_parse(cap, msg, p_data, r_captured);
+}
+
+void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
+
+ //this function is called when there is a debugger break (bug on script)
+ //or when execution is paused from editor
+
+ if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint)
+ return;
+
+ ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway.");
+
+ ScriptLanguage *script_lang = script_debugger->get_break_language();
+ const String error_str = script_lang ? script_lang->debug_get_error() : "";
+ Array msg;
+ msg.push_back(p_can_continue);
+ msg.push_back(error_str);
+ 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();
+
+ if (peer->has_message()) {
+
+ Array cmd = peer->get_message();
+
+ ERR_CONTINUE(cmd.size() != 2);
+ ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
+ ERR_CONTINUE(cmd[1].get_type() != Variant::ARRAY);
+
+ String command = cmd[0];
+ Array data = cmd[1];
+
+ if (command == "step") {
+ script_debugger->set_depth(-1);
+ script_debugger->set_lines_left(1);
+ break;
+
+ } else if (command == "next") {
+ script_debugger->set_depth(0);
+ script_debugger->set_lines_left(1);
+ break;
+
+ } else if (command == "continue") {
+ script_debugger->set_depth(-1);
+ script_debugger->set_lines_left(-1);
+ OS::get_singleton()->move_window_to_foreground();
+ break;
+
+ } else if (command == "break") {
+ ERR_PRINT("Got break when already broke!");
+ break;
+
+ } else if (command == "get_stack_dump") {
+ ERR_FAIL_COND(!script_lang);
+ DebuggerMarshalls::ScriptStackDump dump;
+ int slc = script_lang->debug_get_stack_level_count();
+ for (int i = 0; i < slc; i++) {
+ ScriptLanguage::StackInfo frame;
+ frame.file = script_lang->debug_get_stack_level_source(i);
+ frame.line = script_lang->debug_get_stack_level_line(i);
+ frame.func = script_lang->debug_get_stack_level_function(i);
+ dump.frames.push_back(frame);
+ }
+ send_message("stack_dump", dump.serialize());
+
+ } else if (command == "get_stack_frame_vars") {
+ ERR_FAIL_COND(data.size() != 1);
+ ERR_FAIL_COND(!script_lang);
+ int lv = data[0];
+
+ List<String> members;
+ List<Variant> member_vals;
+ if (ScriptInstance *inst = script_lang->debug_get_stack_level_instance(lv)) {
+ members.push_back("self");
+ member_vals.push_back(inst->get_owner());
+ }
+ script_lang->debug_get_stack_level_members(lv, &members, &member_vals);
+ ERR_FAIL_COND(members.size() != member_vals.size());
+
+ List<String> locals;
+ List<Variant> local_vals;
+ script_lang->debug_get_stack_level_locals(lv, &locals, &local_vals);
+ ERR_FAIL_COND(locals.size() != local_vals.size());
+
+ List<String> globals;
+ List<Variant> globals_vals;
+ script_lang->debug_get_globals(&globals, &globals_vals);
+ ERR_FAIL_COND(globals.size() != globals_vals.size());
+
+ send_message("stack_frame_vars", Array());
+ _send_stack_vars(locals, local_vals, 0);
+ _send_stack_vars(members, member_vals, 1);
+ _send_stack_vars(globals, globals_vals, 2);
+
+ } else if (command == "reload_scripts") {
+ reload_all_scripts = true;
+
+ } else if (command == "breakpoint") {
+ ERR_FAIL_COND(data.size() < 3);
+ bool set = data[2];
+ if (set)
+ script_debugger->insert_breakpoint(data[1], data[0]);
+ else
+ script_debugger->remove_breakpoint(data[1], data[0]);
+
+ } else if (command == "set_skip_breakpoints") {
+ ERR_FAIL_COND(data.size() < 1);
+ script_debugger->set_skip_breakpoints(data[0]);
+ } else {
+ bool captured = false;
+ ERR_CONTINUE(_try_capture(command, data, captured) != OK);
+ if (!captured)
+ WARN_PRINT("Unknown message received from debugger: " + command);
+ }
+ } else {
+ 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;
+ VisualServer::get_singleton()->sync();
+ if (VisualServer::get_singleton()->has_changed()) {
+ VisualServer::get_singleton()->draw(true, loop_time_sec * Engine::get_singleton()->get_time_scale());
+ }
+ }
+
+ send_message("debug_exit", Array());
+
+ if (mouse_mode != Input::MOUSE_MODE_VISIBLE)
+ Input::get_singleton()->set_mouse_mode(mouse_mode);
+}
+
+void RemoteDebugger::poll_events(bool p_is_idle) {
+ if (peer.is_null())
+ return;
+
+ flush_output();
+ peer->poll();
+ while (peer->has_message()) {
+
+ Array arr = peer->get_message();
+
+ ERR_CONTINUE(arr.size() != 2);
+ ERR_CONTINUE(arr[0].get_type() != Variant::STRING);
+ ERR_CONTINUE(arr[1].get_type() != Variant::ARRAY);
+
+ const String cmd = arr[0];
+ const int idx = cmd.find(":");
+ bool parsed = false;
+ if (idx < 0) { // Not prefix, use scripts capture.
+ capture_parse("core", cmd, arr[1], parsed);
+ continue;
+ }
+
+ const String cap = cmd.substr(0, idx);
+ if (!has_capture(cap))
+ continue; // Unknown message...
+
+ const String msg = cmd.substr(idx + 1);
+ capture_parse(cap, msg, arr[1], parsed);
+ }
+
+ // Reload scripts during idle poll only.
+ if (p_is_idle && reload_all_scripts) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->reload_all_scripts();
+ }
+ reload_all_scripts = false;
+ }
+}
+
+Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bool &r_captured) {
+ r_captured = true;
+ if (p_cmd == "reload_scripts") {
+ reload_all_scripts = true;
+
+ } else if (p_cmd == "breakpoint") {
+ ERR_FAIL_COND_V(p_data.size() < 3, ERR_INVALID_DATA);
+ bool set = p_data[2];
+ if (set)
+ script_debugger->insert_breakpoint(p_data[1], p_data[0]);
+ else
+ script_debugger->remove_breakpoint(p_data[1], p_data[0]);
+
+ } 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 {
+ r_captured = false;
+ }
+ return OK;
+}
+
+Error RemoteDebugger::_profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured) {
+ r_captured = false;
+ ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(p_data[0].get_type() != Variant::BOOL, ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(!has_profiler(p_cmd), ERR_UNAVAILABLE);
+ Array opts;
+ if (p_data.size() > 1) { // Optional profiler parameters.
+ ERR_FAIL_COND_V(p_data[1].get_type() != Variant::ARRAY, ERR_INVALID_DATA);
+ opts = p_data[1];
+ }
+ r_captured = true;
+ profiler_enable(p_cmd, p_data[0], opts);
+ return OK;
+}
+
+RemoteDebugger *RemoteDebugger::create_for_uri(const String &p_uri) {
+ Ref<RemoteDebuggerPeer> peer = RemoteDebuggerPeer::create_from_uri(p_uri);
+ if (peer.is_valid())
+ return memnew(RemoteDebugger(peer));
+ return NULL;
+}
+
+RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
+ peer = p_peer;
+ max_chars_per_second = GLOBAL_GET("network/limits/debugger/max_chars_per_second");
+ 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);
+
+ // Perfromance Profiler
+ Object *perf = Engine::get_singleton()->get_singleton_object("Performance");
+ if (perf) {
+ performance_profiler = memnew(PerformanceProfiler(perf));
+ _bind_profiler("performance", performance_profiler);
+ profiler_enable("performance", true);
+ }
+
+ // Core and profiler captures.
+ Capture core_cap(this,
+ [](void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
+ return ((RemoteDebugger *)p_user)->_core_capture(p_cmd, p_data, r_captured);
+ });
+ register_message_capture("core", core_cap);
+ Capture profiler_cap(this,
+ [](void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
+ return ((RemoteDebugger *)p_user)->_profiler_capture(p_cmd, p_data, r_captured);
+ });
+ register_message_capture("profiler", profiler_cap);
+
+ // Error handlers
+ phl.printfunc = _print_handler;
+ phl.userdata = this;
+ add_print_handler(&phl);
+
+ eh.errfunc = _err_handler;
+ eh.userdata = this;
+ add_error_handler(&eh);
+}
+
+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);
+}
diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h
new file mode 100644
index 0000000000..83789c67f9
--- /dev/null
+++ b/core/debugger/remote_debugger.h
@@ -0,0 +1,114 @@
+/*************************************************************************/
+/* remote_debugger.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef REMOTE_DEBUGGER_H
+#define REMOTE_DEBUGGER_H
+
+#include "core/array.h"
+#include "core/debugger/debugger_marshalls.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/remote_debugger_peer.h"
+#include "core/object.h"
+#include "core/string_name.h"
+#include "core/ustring.h"
+
+class RemoteDebugger : public EngineDebugger {
+
+private:
+ typedef DebuggerMarshalls::OutputError ErrorMessage;
+
+ struct NetworkProfiler;
+ struct ServersProfiler;
+ struct ScriptsProfiler;
+ struct VisualProfiler;
+ struct PerformanceProfiler;
+
+ NetworkProfiler *network_profiler = NULL;
+ ServersProfiler *servers_profiler = NULL;
+ VisualProfiler *visual_profiler = NULL;
+ PerformanceProfiler *performance_profiler = NULL;
+
+ Ref<RemoteDebuggerPeer> peer;
+
+ List<String> output_strings;
+ List<ErrorMessage> errors;
+
+ int n_messages_dropped = 0;
+ int max_errors_per_second = 0;
+ int max_chars_per_second = 0;
+ int max_warnings_per_second = 0;
+ int n_errors_dropped = 0;
+ int n_warnings_dropped = 0;
+ int char_count = 0;
+ int err_count = 0;
+ int warn_count = 0;
+ int last_reset = 0;
+ bool reload_all_scripts = false;
+
+ // Make handlers and send_message thread safe.
+ Mutex mutex;
+ bool flushing = false;
+ Thread::ID flush_thread = 0;
+
+ PrintHandlerList phl;
+ static void _print_handler(void *p_this, const String &p_string, bool p_error);
+ ErrorHandlerList eh;
+ static void _err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, ErrorHandlerType p_type);
+
+ ErrorMessage _create_overflow_error(const String &p_what, const String &p_descr);
+ Error _put_msg(String p_message, Array p_data);
+
+ 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);
+ Error _core_capture(const String &p_cmd, const Array &p_data, bool &r_captured);
+
+ template <typename T>
+ void _bind_profiler(const String &p_name, T *p_prof);
+ Error _try_capture(const String &p_name, const Array &p_data, bool &r_captured);
+
+public:
+ static RemoteDebugger *create_for_uri(const String &p_uri);
+
+ // Overrides
+ void poll_events(bool p_is_idle);
+ void send_message(const String &p_message, const Array &p_args);
+ void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type);
+ void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false);
+
+ RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer);
+ ~RemoteDebugger();
+};
+
+#endif // REMOTE_DEBUGGER_H
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
new file mode 100644
index 0000000000..42c2c8e309
--- /dev/null
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -0,0 +1,242 @@
+/*************************************************************************/
+/* remote_debugger_peer.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "remote_debugger_peer.h"
+
+#include "core/io/marshalls.h"
+#include "core/os/os.h"
+#include "core/project_settings.h"
+
+bool RemoteDebuggerPeerTCP::is_peer_connected() {
+ return connected;
+}
+
+bool RemoteDebuggerPeerTCP::has_message() {
+ return in_queue.size() > 0;
+}
+
+Array RemoteDebuggerPeerTCP::get_message() {
+ MutexLock lock(mutex);
+ ERR_FAIL_COND_V(!has_message(), Array());
+ Array out = in_queue[0];
+ in_queue.pop_front();
+ return out;
+}
+
+Error RemoteDebuggerPeerTCP::put_message(const Array &p_arr) {
+ MutexLock lock(mutex);
+ if (out_queue.size() >= max_queued_messages)
+ return ERR_OUT_OF_MEMORY;
+
+ out_queue.push_back(p_arr);
+ return OK;
+}
+
+int RemoteDebuggerPeerTCP::get_max_message_size() const {
+ return 8 << 20; // 8 MiB
+}
+
+void RemoteDebuggerPeerTCP::close() {
+ if (thread) {
+ running = false;
+ Thread::wait_to_finish(thread);
+ memdelete(thread);
+ thread = NULL;
+ }
+ tcp_client->disconnect_from_host();
+ out_buf.resize(0);
+ in_buf.resize(0);
+}
+
+RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_tcp) {
+ // This means remote debugger takes 16 MiB just because it exists...
+ in_buf.resize((8 << 20) + 4); // 8 MiB should be way more than enough (need 4 extra bytes for encoding packet size).
+ out_buf.resize(8 << 20); // 8 MiB should be way more than enough
+ tcp_client = p_tcp;
+ if (tcp_client.is_valid()) { // Attaching to an already connected stream.
+ connected = true;
+#ifndef NO_THREADS
+ running = true;
+ thread = Thread::create(_thread_func, this);
+#endif
+ } else {
+ tcp_client.instance();
+ }
+}
+
+RemoteDebuggerPeerTCP::~RemoteDebuggerPeerTCP() {
+ close();
+}
+
+void RemoteDebuggerPeerTCP::_write_out() {
+ while (tcp_client->poll(NetSocket::POLL_TYPE_OUT) == OK) {
+ uint8_t *buf = out_buf.ptrw();
+ if (out_left <= 0) {
+ if (out_queue.size() == 0)
+ break; // Nothing left to send
+ mutex.lock();
+ Variant var = out_queue[0];
+ out_queue.pop_front();
+ mutex.unlock();
+ int size = 0;
+ Error err = encode_variant(var, NULL, size);
+ ERR_CONTINUE(err != OK || size > out_buf.size() - 4); // 4 bytes separator.
+ encode_uint32(size, buf);
+ encode_variant(var, buf + 4, size);
+ out_left = size + 4;
+ out_pos = 0;
+ }
+ int sent = 0;
+ tcp_client->put_partial_data(buf + out_pos, out_left, sent);
+ out_left -= sent;
+ out_pos += sent;
+ }
+}
+
+void RemoteDebuggerPeerTCP::_read_in() {
+ while (tcp_client->poll(NetSocket::POLL_TYPE_IN) == OK) {
+ uint8_t *buf = in_buf.ptrw();
+ if (in_left <= 0) {
+ if (in_queue.size() > max_queued_messages) {
+ break; // Too many messages already in queue.
+ }
+ if (tcp_client->get_available_bytes() < 4) {
+ break; // Need 4 more bytes.
+ }
+ uint32_t size = 0;
+ int read = 0;
+ Error err = tcp_client->get_partial_data((uint8_t *)&size, 4, read);
+ ERR_CONTINUE(read != 4 || err != OK || size > (uint32_t)in_buf.size());
+ in_left = size;
+ in_pos = 0;
+ }
+ int read = 0;
+ tcp_client->get_partial_data(buf + in_pos, in_left, read);
+ in_left -= read;
+ in_pos += read;
+ if (in_left == 0) {
+ Variant var;
+ Error err = decode_variant(var, buf, in_pos, &read);
+ ERR_CONTINUE(read != in_pos || err != OK);
+ ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array.");
+ mutex.lock();
+ in_queue.push_back(var);
+ mutex.unlock();
+ }
+ }
+}
+
+Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_port) {
+
+ IP_Address ip;
+ if (p_host.is_valid_ip_address())
+ ip = p_host;
+ else
+ ip = IP::get_singleton()->resolve_hostname(p_host);
+
+ int port = p_port;
+
+ const int tries = 6;
+ int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 };
+
+ tcp_client->connect_to_host(ip, port);
+
+ for (int i = 0; i < tries; i++) {
+
+ if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) {
+ print_verbose("Remote Debugger: Connected!");
+ break;
+ } else {
+
+ const int ms = waits[i];
+ OS::get_singleton()->delay_usec(ms * 1000);
+ print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec.");
+ };
+ };
+
+ if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
+
+ ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + ".");
+ return FAILED;
+ };
+ connected = true;
+#ifndef NO_THREADS
+ running = true;
+ thread = Thread::create(_thread_func, this);
+#endif
+ return OK;
+}
+
+void RemoteDebuggerPeerTCP::_thread_func(void *p_ud) {
+ RemoteDebuggerPeerTCP *peer = (RemoteDebuggerPeerTCP *)p_ud;
+ while (peer->running && peer->is_peer_connected()) {
+ peer->_poll();
+ if (!peer->is_peer_connected())
+ break;
+ peer->tcp_client->poll(NetSocket::POLL_TYPE_IN_OUT, 1);
+ }
+}
+
+void RemoteDebuggerPeerTCP::poll() {
+#ifdef NO_THREADS
+ _poll();
+#endif
+}
+
+void RemoteDebuggerPeerTCP::_poll() {
+ if (connected) {
+ _write_out();
+ _read_in();
+ connected = tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
+ }
+}
+
+Ref<RemoteDebuggerPeer> RemoteDebuggerPeer::create_from_uri(const String p_uri) {
+ if (!p_uri.begins_with("tcp://"))
+ return Ref<RemoteDebuggerPeer>(); // Only TCP supported for now, more to come.
+
+ String debug_host = p_uri.replace("tcp://", "");
+ uint16_t debug_port = 6007;
+
+ if (debug_host.find(":") != -1) {
+ int sep_pos = debug_host.find_last(":");
+ debug_port = debug_host.substr(sep_pos + 1).to_int();
+ debug_host = debug_host.substr(0, sep_pos);
+ }
+ Ref<RemoteDebuggerPeerTCP> peer = Ref<RemoteDebuggerPeer>(memnew(RemoteDebuggerPeerTCP));
+ Error err = peer->connect_to_host(debug_host, debug_port);
+ if (err != OK)
+ return Ref<RemoteDebuggerPeer>();
+ return peer;
+}
+
+RemoteDebuggerPeer::RemoteDebuggerPeer() {
+ max_queued_messages = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages");
+}
diff --git a/core/debugger/remote_debugger_peer.h b/core/debugger/remote_debugger_peer.h
new file mode 100644
index 0000000000..6fc3214a51
--- /dev/null
+++ b/core/debugger/remote_debugger_peer.h
@@ -0,0 +1,94 @@
+/*************************************************************************/
+/* remote_debugger_peer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef REMOTE_DEBUGGER_PEER_H
+#define REMOTE_DEBUGGER_PEER_H
+
+#include "core/io/stream_peer_tcp.h"
+#include "core/os/mutex.h"
+#include "core/os/thread.h"
+#include "core/reference.h"
+#include "core/ustring.h"
+
+class RemoteDebuggerPeer : public Reference {
+protected:
+ int max_queued_messages = 4096;
+
+public:
+ static Ref<RemoteDebuggerPeer> create_from_uri(const String p_uri);
+ virtual bool is_peer_connected() = 0;
+ virtual bool has_message() = 0;
+ virtual Error put_message(const Array &p_arr) = 0;
+ virtual Array get_message() = 0;
+ virtual void close() = 0;
+ virtual void poll() = 0;
+ virtual int get_max_message_size() const = 0;
+
+ RemoteDebuggerPeer();
+};
+
+class RemoteDebuggerPeerTCP : public RemoteDebuggerPeer {
+private:
+ Ref<StreamPeerTCP> tcp_client;
+ Mutex mutex;
+ Thread *thread = NULL;
+ List<Array> in_queue;
+ List<Array> out_queue;
+ int out_left = 0;
+ int out_pos = 0;
+ Vector<uint8_t> out_buf;
+ int in_left = 0;
+ int in_pos = 0;
+ Vector<uint8_t> in_buf;
+ bool connected = false;
+ bool running = false;
+
+ static void _thread_func(void *p_ud);
+
+ void _poll();
+ void _write_out();
+ void _read_in();
+
+public:
+ Error connect_to_host(const String &p_host, uint16_t p_port);
+
+ void poll();
+ bool is_peer_connected();
+ bool has_message();
+ Array get_message();
+ Error put_message(const Array &p_arr);
+ int get_max_message_size() const;
+ void close();
+
+ RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_stream = Ref<StreamPeerTCP>());
+ ~RemoteDebuggerPeerTCP();
+};
+
+#endif // REMOTE_DEBUGGER_PEER_H
diff --git a/core/debugger/script_debugger.cpp b/core/debugger/script_debugger.cpp
new file mode 100644
index 0000000000..935ad01d80
--- /dev/null
+++ b/core/debugger/script_debugger.cpp
@@ -0,0 +1,123 @@
+/*************************************************************************/
+/* script_debugger.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "script_debugger.h"
+
+#include "core/debugger/engine_debugger.h"
+
+void ScriptDebugger::set_lines_left(int p_left) {
+
+ lines_left = p_left;
+}
+
+int ScriptDebugger::get_lines_left() const {
+
+ return lines_left;
+}
+
+void ScriptDebugger::set_depth(int p_depth) {
+
+ depth = p_depth;
+}
+
+int ScriptDebugger::get_depth() const {
+
+ return depth;
+}
+
+void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
+
+ if (!breakpoints.has(p_line))
+ breakpoints[p_line] = Set<StringName>();
+ breakpoints[p_line].insert(p_source);
+}
+
+void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
+
+ if (!breakpoints.has(p_line))
+ return;
+
+ breakpoints[p_line].erase(p_source);
+ if (breakpoints[p_line].size() == 0)
+ breakpoints.erase(p_line);
+}
+bool ScriptDebugger::is_breakpoint(int p_line, const StringName &p_source) const {
+
+ if (!breakpoints.has(p_line))
+ return false;
+ return breakpoints[p_line].has(p_source);
+}
+bool ScriptDebugger::is_breakpoint_line(int p_line) const {
+
+ return breakpoints.has(p_line);
+}
+
+String ScriptDebugger::breakpoint_find_source(const String &p_source) const {
+
+ return p_source;
+}
+
+void ScriptDebugger::clear_breakpoints() {
+
+ breakpoints.clear();
+}
+
+void ScriptDebugger::set_skip_breakpoints(bool p_skip_breakpoints) {
+
+ skip_breakpoints = p_skip_breakpoints;
+}
+
+bool ScriptDebugger::is_skipping_breakpoints() {
+
+ return skip_breakpoints;
+}
+
+void ScriptDebugger::debug(ScriptLanguage *p_lang, bool p_can_continue, bool p_is_error_breakpoint) {
+ ScriptLanguage *prev = break_lang;
+ break_lang = p_lang;
+ EngineDebugger::get_singleton()->debug(p_can_continue, p_is_error_breakpoint);
+ break_lang = prev;
+}
+
+void ScriptDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<StackInfo> &p_stack_info) {
+ // Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way.
+ error_stack_info.append_array(p_stack_info);
+ EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_type);
+ error_stack_info.resize(0);
+}
+
+Vector<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const {
+ return error_stack_info;
+}
+
+ScriptLanguage *ScriptDebugger::get_break_language() const {
+
+ return break_lang;
+}
diff --git a/core/debugger/script_debugger.h b/core/debugger/script_debugger.h
new file mode 100644
index 0000000000..d8ddf353bf
--- /dev/null
+++ b/core/debugger/script_debugger.h
@@ -0,0 +1,80 @@
+/*************************************************************************/
+/* script_debugger.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SCRIPT_DEBUGGER_H
+#define SCRIPT_DEBUGGER_H
+
+#include "core/map.h"
+#include "core/script_language.h"
+#include "core/set.h"
+#include "core/string_name.h"
+#include "core/vector.h"
+
+class ScriptDebugger {
+
+ typedef ScriptLanguage::StackInfo StackInfo;
+
+ int lines_left = -1;
+ int depth = -1;
+ bool skip_breakpoints = false;
+
+ Map<int, Set<StringName> > breakpoints;
+
+ ScriptLanguage *break_lang = NULL;
+ Vector<StackInfo> error_stack_info;
+
+public:
+ void set_lines_left(int p_left);
+ int get_lines_left() const;
+
+ void set_depth(int p_depth);
+ int get_depth() const;
+
+ String breakpoint_find_source(const String &p_source) const;
+ void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; }
+ ScriptLanguage *get_break_language() { return break_lang; }
+ void set_skip_breakpoints(bool p_skip_breakpoints);
+ bool is_skipping_breakpoints();
+ void insert_breakpoint(int p_line, const StringName &p_source);
+ void remove_breakpoint(int p_line, const StringName &p_source);
+ bool is_breakpoint(int p_line, const StringName &p_source) const;
+ bool is_breakpoint_line(int p_line) const;
+ void clear_breakpoints();
+ const Map<int, Set<StringName> > &get_breakpoints() const { return breakpoints; }
+
+ void debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false);
+ ScriptLanguage *get_break_language() const;
+
+ void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<StackInfo> &p_stack_info);
+ Vector<StackInfo> get_error_stack_info() const;
+ ScriptDebugger() {}
+};
+
+#endif