diff options
author | RĂ©mi Verschelde <rverschelde@gmail.com> | 2020-03-09 19:08:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-09 19:08:07 +0100 |
commit | 478337c412f767b908820d7bb56c786bf0b909cb (patch) | |
tree | 019ee1871f24bed0b9c56391830a48583dd15a54 /core | |
parent | 640169da5b20538c2d5c1106957461a3c988e2bc (diff) | |
parent | 3b47eb51e4025fae6dd63eb405dee54c49281a5f (diff) |
Merge pull request #36751 from Faless/debugger/threads_and_profilers
ScriptDebugger refactor, threading, profilers.
Diffstat (limited to 'core')
25 files changed, 2632 insertions, 1944 deletions
diff --git a/core/SCsub b/core/SCsub index 755c5c65c6..b4974196bd 100644 --- a/core/SCsub +++ b/core/SCsub @@ -165,6 +165,7 @@ SConscript('os/SCsub') SConscript('math/SCsub') SConscript('crypto/SCsub') SConscript('io/SCsub') +SConscript('debugger/SCsub') SConscript('bind/SCsub') 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/script_debugger_local.cpp b/core/debugger/local_debugger.cpp index c64638e7ce..913d3fc031 100644 --- a/core/script_debugger_local.cpp +++ b/core/debugger/local_debugger.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* script_debugger_local.cpp */ +/* local_debugger.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,28 +28,112 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "script_debugger_local.h" +#include "local_debugger.h" +#include "core/debugger/script_debugger.h" #include "core/os/os.h" #include "scene/main/scene_tree.h" -void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, bool p_is_error_breakpoint) { +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 = p_script->debug_get_stack_level_function(0); + String current_function = script_lang->debug_get_stack_level_function(0); if (current_function != target_function) { - set_depth(0); - set_lines_left(1); + script_debugger->set_depth(0); + script_debugger->set_lines_left(1); return; } target_function = ""; } - print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'"); - print_line("*Frame " + itos(0) + " - " + p_script->debug_get_stack_level_source(0) + ":" + itos(p_script->debug_get_stack_level_line(0)) + " in function '" + p_script->debug_get_stack_level_function(0) + "'"); + 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 = p_script->debug_get_stack_level_count(); + int total_frames = script_lang->debug_get_stack_level_count(); while (true) { OS::get_singleton()->print("debug> "); @@ -59,8 +143,8 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b String variable_prefix = options["variable_prefix"]; if (line == "") { - print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'"); - print_line("*Frame " + itos(current_frame) + " - " + p_script->debug_get_stack_level_source(current_frame) + ":" + itos(p_script->debug_get_stack_level_line(current_frame)) + " in function '" + p_script->debug_get_stack_level_function(current_frame) + "'"); + 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; @@ -69,20 +153,20 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b for (int i = 0; i < total_frames; i++) { String cfi = (current_frame == i) ? "*" : " "; //current frame indicator - print_line(cfi + "Frame " + itos(i) + " - " + p_script->debug_get_stack_level_source(i) + ":" + itos(p_script->debug_get_stack_level_line(i)) + " in function '" + p_script->debug_get_stack_level_function(i) + "'"); + 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) + " - " + p_script->debug_get_stack_level_source(current_frame) + ":" + itos(p_script->debug_get_stack_level_line(current_frame)) + " in function '" + p_script->debug_get_stack_level_function(current_frame) + "'"); + 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) + " - " + p_script->debug_get_stack_level_source(frame) + ":" + itos(p_script->debug_get_stack_level_line(frame)) + " in function '" + p_script->debug_get_stack_level_function(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) + "'"); } } @@ -120,21 +204,21 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b List<String> locals; List<Variant> values; - p_script->debug_get_stack_level_locals(current_frame, &locals, &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; - p_script->debug_get_globals(&globals, &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; - p_script->debug_get_stack_level_members(current_frame, &members, &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")) { @@ -144,29 +228,29 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b } else { String expr = line.get_slicec(' ', 2); - String res = p_script->debug_parse_stack_level_expression(current_frame, expr); + String res = script_lang->debug_parse_stack_level_expression(current_frame, expr); print_line(res); } } else if (line == "s" || line == "step") { - set_depth(-1); - set_lines_left(1); + script_debugger->set_depth(-1); + script_debugger->set_lines_left(1); break; } else if (line == "n" || line == "next") { - set_depth(0); - set_lines_left(1); + script_debugger->set_depth(0); + script_debugger->set_lines_left(1); break; } else if (line == "fin" || line == "finish") { - String current_function = p_script->debug_get_stack_level_function(0); + String current_function = script_lang->debug_get_stack_level_function(0); for (int i = 0; i < total_frames; i++) { - target_function = p_script->debug_get_stack_level_function(i); + target_function = script_lang->debug_get_stack_level_function(i); if (target_function != current_function) { - set_depth(0); - set_lines_left(1); + script_debugger->set_depth(0); + script_debugger->set_lines_left(1); return; } } @@ -178,7 +262,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b if (line.get_slice_count(" ") <= 1) { - const Map<int, Set<StringName> > &breakpoints = get_breakpoints(); + const Map<int, Set<StringName> > &breakpoints = script_debugger->get_breakpoints(); if (breakpoints.size() == 0) { print_line("No Breakpoints."); continue; @@ -199,7 +283,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b if (source.empty()) continue; - insert_breakpoint(linenr, source); + script_debugger->insert_breakpoint(linenr, source); print_line("Added breakpoint at " + source + ":" + itos(linenr)); } @@ -207,16 +291,16 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b } else if (line == "q" || line == "quit") { // Do not stop again on quit - clear_breakpoints(); - ScriptDebugger::get_singleton()->set_depth(-1); - ScriptDebugger::get_singleton()->set_lines_left(-1); + 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) { - clear_breakpoints(); + script_debugger->clear_breakpoints(); } else { Pair<String, int> breakpoint = to_breakpoint(line); @@ -227,7 +311,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b if (source.empty()) continue; - remove_breakpoint(linenr, source); + script_debugger->remove_breakpoint(linenr, source); print_line("Removed breakpoint at " + source + ":" + itos(linenr)); } @@ -255,7 +339,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b } } -void ScriptDebuggerLocal::print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix) { +void LocalDebugger::print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix) { String value; Vector<String> value_lines; @@ -279,7 +363,7 @@ void ScriptDebuggerLocal::print_variables(const List<String> &names, const List< } } -Pair<String, int> ScriptDebuggerLocal::to_breakpoint(const String &p_line) { +Pair<String, int> LocalDebugger::to_breakpoint(const String &p_line) { String breakpoint_part = p_line.get_slicec(' ', 1); Pair<String, int> breakpoint; @@ -290,135 +374,43 @@ Pair<String, int> ScriptDebuggerLocal::to_breakpoint(const String &p_line) { return breakpoint; } - breakpoint.first = breakpoint_find_source(breakpoint_part.left(last_colon).strip_edges()); + 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; } -struct _ScriptDebuggerLocalProfileInfoSort { - - bool operator()(const ScriptLanguage::ProfilingInfo &A, const ScriptLanguage::ProfilingInfo &B) const { - return A.total_time > B.total_time; - } -}; - -void ScriptDebuggerLocal::profiling_set_frame_times(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; -} - -void ScriptDebuggerLocal::idle_poll() { - - if (!profiling) - return; - - uint64_t diff = OS::get_singleton()->get_ticks_usec() - idle_accum; - - if (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++) { - ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&pinfo.write[ofs], pinfo.size() - ofs); - } - - SortArray<ScriptLanguage::ProfilingInfo, _ScriptDebuggerLocalProfileInfoSort> sort; - sort.sort(pinfo.ptrw(), ofs); - - //falta el 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 = frame_time; - - //print script total - - print_line("FRAME: total: " + rtos(frame_time) + " script: " + rtos(script_time) + "/" + itos(script_time * 100 / 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)); - } -} - -void ScriptDebuggerLocal::profiling_start() { - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->profiling_start(); - } - - print_line("BEGIN PROFILING"); - profiling = true; - pinfo.resize(32768); - frame_time = 0; - physics_time = 0; - idle_time = 0; - physics_frame_time = 0; -} - -void ScriptDebuggerLocal::profiling_end() { - - int ofs = 0; - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&pinfo.write[ofs], pinfo.size() - ofs); - } - - SortArray<ScriptLanguage::ProfilingInfo, _ScriptDebuggerLocalProfileInfoSort> sort; - sort.sort(pinfo.ptrw(), ofs); - - uint64_t total_us = 0; - for (int i = 0; i < ofs; i++) { - total_us += pinfo[i].self_time; - } - - float total_time = total_us / 1000000.0; - - 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_ms: " + rtos(tt) + "\tself_ms: " + rtos(st) + "total%: " + itos(tt * 100 / total_time) + "\tself%: " + itos(st * 100 / total_time) + "\tcalls: " + itos(pinfo[i].call_count)); - } - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->profiling_stop(); - } - - profiling = false; -} - -void ScriptDebuggerLocal::send_message(const String &p_message, const Array &p_args) { +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 ScriptDebuggerLocal::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<ScriptLanguage::StackInfo> &p_stack_info) { +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) + "'"); } -ScriptDebuggerLocal::ScriptDebuggerLocal() { +LocalDebugger::LocalDebugger() { - profiling = false; - idle_accum = OS::get_singleton()->get_ticks_usec(); 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/script_debugger_local.h b/core/debugger/local_debugger.h index 7a64400191..e299df0546 100644 --- a/core/script_debugger_local.h +++ b/core/debugger/local_debugger.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* script_debugger_local.h */ +/* local_debugger.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,40 +28,33 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef SCRIPT_DEBUGGER_LOCAL_H -#define SCRIPT_DEBUGGER_LOCAL_H +#ifndef LOCAL_DEBUGGER_H +#define LOCAL_DEBUGGER_H +#include "core/debugger/engine_debugger.h" #include "core/list.h" #include "core/script_language.h" -class ScriptDebuggerLocal : public ScriptDebugger { +class LocalDebugger : public EngineDebugger { + +private: + struct ScriptsProfiler; + + ScriptsProfiler *scripts_profiler = NULL; - bool profiling; - float frame_time, idle_time, physics_time, physics_frame_time; - uint64_t idle_accum; String target_function; Map<String, String> options; - Vector<ScriptLanguage::ProfilingInfo> pinfo; - 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(ScriptLanguage *p_script, bool p_can_continue, bool p_is_error_breakpoint); - virtual void send_message(const String &p_message, const Array &p_args); - 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, const Vector<ScriptLanguage::StackInfo> &p_stack_info); - - virtual bool is_profiling() const { return profiling; } - virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data) {} - - virtual void idle_poll(); - - virtual void profiling_start(); - virtual void profiling_end(); - virtual void profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time); + 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); - ScriptDebuggerLocal(); + LocalDebugger(); + ~LocalDebugger(); }; -#endif // SCRIPT_DEBUGGER_LOCAL_H +#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 diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index d7c82fddd9..4b864f0dd7 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -30,6 +30,7 @@ #include "multiplayer_api.h" +#include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" #include "scene/main/node.h" #include <stdint.h> @@ -172,6 +173,28 @@ Ref<NetworkedMultiplayerPeer> MultiplayerAPI::get_network_peer() const { return network_peer; } +#ifdef DEBUG_ENABLED +void _profile_node_data(const String &p_what, ObjectID p_id) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("node"); + values.push_back(p_id); + values.push_back(p_what); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +void _profile_bandwidth_data(const String &p_inout, int p_size) { + if (EngineDebugger::is_profiling("multiplayer")) { + Array values; + values.push_back("bandwidth"); + values.push_back(p_inout); + values.push_back(OS::get_singleton()->get_ticks_msec()); + values.push_back(p_size); + EngineDebugger::profiler_add_frame_data("multiplayer", values); + } +} +#endif + // Returns the packet size stripping the node path added when the node is not yet cached. int get_packet_len(uint32_t p_node_target, int p_packet_len) { if (p_node_target & 0x80000000) { @@ -188,11 +211,7 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_ ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); #ifdef DEBUG_ENABLED - if (profiling) { - bandwidth_incoming_data.write[bandwidth_incoming_pointer].timestamp = OS::get_singleton()->get_ticks_msec(); - bandwidth_incoming_data.write[bandwidth_incoming_pointer].packet_size = p_packet_len; - bandwidth_incoming_pointer = (bandwidth_incoming_pointer + 1) % bandwidth_incoming_data.size(); - } + _profile_bandwidth_data("in", p_packet_len); #endif // Extract the `packet_type` from the LSB three bits: @@ -381,11 +400,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, argp.resize(argc); #ifdef DEBUG_ENABLED - if (profiling) { - ObjectID id = p_node->get_instance_id(); - _init_node_profile(id); - profiler_frame_data[id].incoming_rpc += 1; - } + _profile_node_data("in_rpc", p_node->get_instance_id()); #endif if (byte_only) { @@ -437,11 +452,7 @@ void MultiplayerAPI::_process_rset(Node *p_node, const uint16_t p_rpc_property_i ERR_FAIL_COND_MSG(!can_call, "RSET '" + String(name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rset_mode) + ", master is " + itos(p_node->get_network_master()) + "."); #ifdef DEBUG_ENABLED - if (profiling) { - ObjectID id = p_node->get_instance_id(); - _init_node_profile(id); - profiler_frame_data[id].incoming_rset += 1; - } + _profile_node_data("in_rset", p_node->get_instance_id()); #endif Variant value; @@ -912,11 +923,7 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + ((byte_only_or_no_args ? 1 : 0) << BYTE_ONLY_OR_NO_ARGS_SHIFT); #ifdef DEBUG_ENABLED - if (profiling) { - bandwidth_outgoing_data.write[bandwidth_outgoing_pointer].timestamp = OS::get_singleton()->get_ticks_msec(); - bandwidth_outgoing_data.write[bandwidth_outgoing_pointer].packet_size = ofs; - bandwidth_outgoing_pointer = (bandwidth_outgoing_pointer + 1) % bandwidth_outgoing_data.size(); - } + _profile_bandwidth_data("out", ofs); #endif // Take chance and set transfer mode, since all send methods will use it. @@ -1031,11 +1038,7 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const if (!skip_rpc) { #ifdef DEBUG_ENABLED - if (profiling) { - ObjectID id = p_node->get_instance_id(); - _init_node_profile(id); - profiler_frame_data[id].outgoing_rpc += 1; - } + _profile_node_data("out_rpc", p_node->get_instance_id()); #endif _send_rpc(p_node, p_peer_id, p_unreliable, false, p_method, p_arg, p_argcount); @@ -1130,11 +1133,7 @@ void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const } #ifdef DEBUG_ENABLED - if (profiling) { - ObjectID id = p_node->get_instance_id(); - _init_node_profile(id); - profiler_frame_data[id].outgoing_rset += 1; - } + _profile_node_data("out_rset", p_node->get_instance_id()); #endif const Variant *vptr = &p_value; @@ -1220,95 +1219,6 @@ bool MultiplayerAPI::is_object_decoding_allowed() const { return allow_object_decoding; } -void MultiplayerAPI::profiling_start() { -#ifdef DEBUG_ENABLED - profiling = true; - profiler_frame_data.clear(); - - bandwidth_incoming_pointer = 0; - bandwidth_incoming_data.resize(16384); // ~128kB - for (int i = 0; i < bandwidth_incoming_data.size(); ++i) { - bandwidth_incoming_data.write[i].packet_size = -1; - } - - bandwidth_outgoing_pointer = 0; - bandwidth_outgoing_data.resize(16384); // ~128kB - for (int i = 0; i < bandwidth_outgoing_data.size(); ++i) { - bandwidth_outgoing_data.write[i].packet_size = -1; - } -#endif -} - -void MultiplayerAPI::profiling_end() { -#ifdef DEBUG_ENABLED - profiling = false; - bandwidth_incoming_data.clear(); - bandwidth_outgoing_data.clear(); -#endif -} - -int MultiplayerAPI::get_profiling_frame(ProfilingInfo *r_info) { - int i = 0; -#ifdef DEBUG_ENABLED - for (Map<ObjectID, ProfilingInfo>::Element *E = profiler_frame_data.front(); E; E = E->next()) { - r_info[i] = E->get(); - ++i; - } - profiler_frame_data.clear(); -#endif - return i; -} - -int MultiplayerAPI::get_incoming_bandwidth_usage() { -#ifdef DEBUG_ENABLED - return _get_bandwidth_usage(bandwidth_incoming_data, bandwidth_incoming_pointer); -#else - return 0; -#endif -} - -int MultiplayerAPI::get_outgoing_bandwidth_usage() { -#ifdef DEBUG_ENABLED - return _get_bandwidth_usage(bandwidth_outgoing_data, bandwidth_outgoing_pointer); -#else - return 0; -#endif -} - -#ifdef DEBUG_ENABLED -int MultiplayerAPI::_get_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 MultiplayerAPI::_init_node_profile(ObjectID p_node) { - if (profiler_frame_data.has(p_node)) - return; - profiler_frame_data.insert(p_node, ProfilingInfo()); - profiler_frame_data[p_node].node = p_node; - profiler_frame_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path(); - profiler_frame_data[p_node].incoming_rpc = 0; - profiler_frame_data[p_node].incoming_rset = 0; - profiler_frame_data[p_node].outgoing_rpc = 0; - profiler_frame_data[p_node].outgoing_rset = 0; -} -#endif - void MultiplayerAPI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node); ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode"), &MultiplayerAPI::send_bytes, DEFVAL(NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE)); @@ -1352,9 +1262,6 @@ MultiplayerAPI::MultiplayerAPI() : allow_object_decoding(false) { rpc_sender_id = 0; root_node = NULL; -#ifdef DEBUG_ENABLED - profiling = false; -#endif clear(); } diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h index a706a0e450..52f918aefa 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -38,16 +38,6 @@ class MultiplayerAPI : public Reference { GDCLASS(MultiplayerAPI, Reference); -public: - struct ProfilingInfo { - ObjectID node; - String node_path; - int incoming_rpc; - int incoming_rset; - int outgoing_rpc; - int outgoing_rset; - }; - private: //path sent caches struct PathSentCache { @@ -65,23 +55,6 @@ private: Map<int, NodeInfo> nodes; }; -#ifdef DEBUG_ENABLED - struct BandwidthFrame { - uint32_t timestamp; - int packet_size; - }; - - int bandwidth_incoming_pointer; - Vector<BandwidthFrame> bandwidth_incoming_data; - int bandwidth_outgoing_pointer; - Vector<BandwidthFrame> bandwidth_outgoing_data; - Map<ObjectID, ProfilingInfo> profiler_frame_data; - bool profiling; - - void _init_node_profile(ObjectID p_node); - int _get_bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer); -#endif - Ref<NetworkedMultiplayerPeer> network_peer; int rpc_sender_id; Set<int> connected_peers; @@ -169,13 +142,6 @@ public: void set_allow_object_decoding(bool p_enable); bool is_object_decoding_allowed() const; - void profiling_start(); - void profiling_end(); - - int get_profiling_frame(ProfilingInfo *r_info); - int get_incoming_bandwidth_usage(); - int get_outgoing_bandwidth_usage(); - MultiplayerAPI(); ~MultiplayerAPI(); }; diff --git a/core/io/stream_peer_tcp.cpp b/core/io/stream_peer_tcp.cpp index 044431743a..f0c5816d73 100644 --- a/core/io/stream_peer_tcp.cpp +++ b/core/io/stream_peer_tcp.cpp @@ -288,6 +288,11 @@ void StreamPeerTCP::disconnect_from_host() { peer_port = 0; } +Error StreamPeerTCP::poll(NetSocket::PollType p_type, int timeout) { + ERR_FAIL_COND_V(_sock.is_null() || !_sock->is_open(), ERR_UNAVAILABLE); + return _sock->poll(p_type, timeout); +} + Error StreamPeerTCP::put_data(const uint8_t *p_data, int p_bytes) { int total; diff --git a/core/io/stream_peer_tcp.h b/core/io/stream_peer_tcp.h index f16d4a2bd4..327aa3cc3b 100644 --- a/core/io/stream_peer_tcp.h +++ b/core/io/stream_peer_tcp.h @@ -78,6 +78,9 @@ public: void set_no_delay(bool p_enabled); + // Poll functions (wait or check for writable, readable) + Error poll(NetSocket::PollType p_type, int timeout = 0); + // Read/Write from StreamPeer Error put_data(const uint8_t *p_data, int p_bytes); Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent); diff --git a/core/script_debugger_remote.cpp b/core/script_debugger_remote.cpp deleted file mode 100644 index 67375da6e2..0000000000 --- a/core/script_debugger_remote.cpp +++ /dev/null @@ -1,1143 +0,0 @@ -/*************************************************************************/ -/* script_debugger_remote.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_remote.h" - -#include "core/engine.h" -#include "core/io/ip.h" -#include "core/io/marshalls.h" -#include "core/os/input.h" -#include "core/os/os.h" -#include "core/project_settings.h" -#include "servers/visual_server.h" - -#define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. 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 short. Exptected size: " + itos(expected) + ", actual size: " + itos(arr.size())) - -Array ScriptDebuggerRemote::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 ScriptDebuggerRemote::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 ScriptDebuggerRemote::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 ScriptDebuggerRemote::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 ScriptDebuggerRemote::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 ScriptDebuggerRemote::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 ScriptDebuggerRemote::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 ScriptDebuggerRemote::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 ScriptDebuggerRemote::ProfilerSignature::serialize() { - Array arr; - arr.push_back(name); - arr.push_back(id); - return arr; -} - -bool ScriptDebuggerRemote::ProfilerSignature::deserialize(const Array &p_arr) { - CHECK_SIZE(p_arr, 2, "ProfilerSignature"); - name = p_arr[0]; - id = p_arr[1]; - CHECK_END(p_arr, 2, "ProfilerSignature"); - return true; -} - -Array ScriptDebuggerRemote::ProfilerFrame::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(USEC_TO_SEC(script_time)); - - arr.push_back(frames_data.size()); - arr.push_back(frame_functions.size() * 4); - - // Servers profiling info. - for (int i = 0; i < frames_data.size(); i++) { - arr.push_back(frames_data[i].name); // Type (physics/process/audio/...) - arr.push_back(frames_data[i].data.size()); - for (int j = 0; j < frames_data[i].data.size() / 2; j++) { - arr.push_back(frames_data[i].data[2 * j]); // NAME - arr.push_back(frames_data[i].data[2 * j + 1]); // TIME - } - } - for (int i = 0; i < frame_functions.size(); i++) { - arr.push_back(frame_functions[i].sig_id); - arr.push_back(frame_functions[i].call_count); - arr.push_back(frame_functions[i].self_time); - arr.push_back(frame_functions[i].total_time); - } - return arr; -} - -bool ScriptDebuggerRemote::ProfilerFrame::deserialize(const Array &p_arr) { - CHECK_SIZE(p_arr, 8, "ProfilerFrame"); - 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]; - uint32_t frame_data_size = p_arr[6]; - int frame_func_size = p_arr[7]; - int idx = 8; - while (frame_data_size) { - CHECK_SIZE(p_arr, idx + 2, "ProfilerFrame"); - frame_data_size--; - FrameData fd; - fd.name = p_arr[idx]; - int sub_data_size = p_arr[idx + 1]; - idx += 2; - CHECK_SIZE(p_arr, idx + sub_data_size, "ProfilerFrame"); - for (int j = 0; j < sub_data_size / 2; j++) { - fd.data.push_back(p_arr[idx]); // NAME - fd.data.push_back(p_arr[idx + 1]); // TIME - idx += 2; - } - frames_data.push_back(fd); - } - CHECK_SIZE(p_arr, idx + frame_func_size, "ProfilerFrame"); - for (int i = 0; i < frame_func_size / 4; i++) { - FrameFunction ff; - ff.sig_id = p_arr[idx]; - ff.call_count = p_arr[idx + 1]; - ff.self_time = p_arr[idx + 2]; - ff.total_time = p_arr[idx + 3]; - frame_functions.push_back(ff); - idx += 4; - } - CHECK_END(p_arr, idx, "ProfilerFrame"); - return true; -} - -Array ScriptDebuggerRemote::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 ScriptDebuggerRemote::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; -} - -void ScriptDebuggerRemote::_put_msg(String p_message, Array p_data) { - Array msg; - msg.push_back(p_message); - msg.push_back(p_data); - packet_peer_stream->put_var(msg); -} - -bool ScriptDebuggerRemote::is_peer_connected() { - return tcp_client->is_connected_to_host() && tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED; -} - -void ScriptDebuggerRemote::_send_video_memory() { - - ResourceUsage usage; - if (resource_usage_func) - resource_usage_func(&usage); - - _put_msg("message:video_mem", usage.serialize()); -} - -Error ScriptDebuggerRemote::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; - }; - - packet_peer_stream->set_stream_peer(tcp_client); - Array msg; - msg.push_back(OS::get_singleton()->get_process_id()); - send_message("set_pid", msg); - - return OK; -} - -void ScriptDebuggerRemote::_parse_message(const String p_command, const Array &p_data, ScriptLanguage *p_script) { - - if (p_command == "request_video_mem") { - _send_video_memory(); - - } else if (p_command == "start_profiling") { - ERR_FAIL_COND(p_data.size() < 1); - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->profiling_start(); - } - - max_frame_functions = p_data[0]; - profiler_function_signature_map.clear(); - profiling = true; - frame_time = 0; - idle_time = 0; - physics_time = 0; - physics_frame_time = 0; - print_line("PROFILING ALRIGHT!"); - - } else if (p_command == "stop_profiling") { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->profiling_stop(); - } - profiling = false; - _send_profiling_data(false); - print_line("PROFILING END!"); - - } else if (p_command == "start_visual_profiling") { - - visual_profiling = true; - VS::get_singleton()->set_frame_profiling_enabled(true); - } else if (p_command == "stop_visual_profiling") { - - visual_profiling = false; - VS::get_singleton()->set_frame_profiling_enabled(false); - - } else if (p_command == "start_network_profiling") { - - network_profiling = true; - multiplayer->profiling_start(); - - } else if (p_command == "stop_network_profiling") { - - network_profiling = false; - multiplayer->profiling_end(); - - } else if (p_command == "reload_scripts") { - reload_all_scripts = true; - - } else if (p_command == "breakpoint") { - ERR_FAIL_COND(p_data.size() < 3); - bool set = p_data[2]; - if (set) - insert_breakpoint(p_data[1], p_data[0]); - else - remove_breakpoint(p_data[1], p_data[0]); - - } else if (p_command == "set_skip_breakpoints") { - ERR_FAIL_COND(p_data.size() < 1); - skip_breakpoints = p_data[0]; - - } else if (p_command == "get_stack_dump") { - ERR_FAIL_COND(!p_script); - ScriptStackDump dump; - int slc = p_script->debug_get_stack_level_count(); - for (int i = 0; i < slc; i++) { - ScriptLanguage::StackInfo frame; - frame.file = p_script->debug_get_stack_level_source(i); - frame.line = p_script->debug_get_stack_level_line(i); - frame.func = p_script->debug_get_stack_level_function(i); - dump.frames.push_back(frame); - } - _put_msg("stack_dump", dump.serialize()); - - } else if (p_command == "get_stack_frame_vars") { - ERR_FAIL_COND(p_data.size() != 1); - ERR_FAIL_COND(!p_script); - int lv = p_data[0]; - - List<String> members; - List<Variant> member_vals; - if (ScriptInstance *inst = p_script->debug_get_stack_level_instance(lv)) { - members.push_back("self"); - member_vals.push_back(inst->get_owner()); - } - p_script->debug_get_stack_level_members(lv, &members, &member_vals); - ERR_FAIL_COND(members.size() != member_vals.size()); - - List<String> locals; - List<Variant> local_vals; - p_script->debug_get_stack_level_locals(lv, &locals, &local_vals); - ERR_FAIL_COND(locals.size() != local_vals.size()); - - List<String> globals; - List<Variant> globals_vals; - p_script->debug_get_globals(&globals, &globals_vals); - ERR_FAIL_COND(globals.size() != globals_vals.size()); - - _put_msg("stack_frame_vars", Array()); - - ScriptStackVariable stvar; - { //locals - List<String>::Element *E = locals.front(); - List<Variant>::Element *F = local_vals.front(); - while (E) { - stvar.name = E->get(); - stvar.value = F->get(); - stvar.type = 0; - _put_msg("stack_frame_var", stvar.serialize()); - - E = E->next(); - F = F->next(); - } - } - - { //members - List<String>::Element *E = members.front(); - List<Variant>::Element *F = member_vals.front(); - while (E) { - stvar.name = E->get(); - stvar.value = F->get(); - stvar.type = 1; - _put_msg("stack_frame_var", stvar.serialize()); - - E = E->next(); - F = F->next(); - } - } - - { //globals - List<String>::Element *E = globals.front(); - List<Variant>::Element *F = globals_vals.front(); - while (E) { - stvar.name = E->get(); - stvar.value = F->get(); - stvar.type = 2; - _put_msg("stack_frame_var", stvar.serialize()); - - E = E->next(); - F = F->next(); - } - } - - } else { - if (scene_tree_parse_func) { - scene_tree_parse_func(p_command, p_data); - } - // Unknown message... - } -} - -void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, 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 (skip_breakpoints && !p_is_error_breakpoint) - return; - - ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway."); - - Array msg; - msg.push_back(p_can_continue); - msg.push_back(p_script->debug_get_error()); - _put_msg("debug_enter", msg); - - skip_profile_frame = true; // to avoid super long frame time for the frame - - 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 (true) { - loop_begin_usec = OS::get_singleton()->get_ticks_usec(); - - _get_output(); - - if (packet_peer_stream->get_available_packet_count() > 0) { - - Variant var; - Error err = packet_peer_stream->get_var(var); - - ERR_CONTINUE(err != OK); - ERR_CONTINUE(var.get_type() != Variant::ARRAY); - - Array cmd = var; - - 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") { - - set_depth(-1); - set_lines_left(1); - break; - } else if (command == "next") { - - set_depth(0); - set_lines_left(1); - break; - - } else if (command == "continue") { - set_depth(-1); - set_lines_left(-1); - OS::get_singleton()->move_window_to_foreground(); - break; - } else if (command == "break") { - ERR_PRINT("Got break when already broke!"); - break; - } - - _parse_message(command, data, p_script); - } 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()); - } - } - - _put_msg("debug_exit", Array()); - - if (mouse_mode != Input::MOUSE_MODE_VISIBLE) - Input::get_singleton()->set_mouse_mode(mouse_mode); -} - -void ScriptDebuggerRemote::_get_output() { - - MutexLock lock(mutex); - - if (output_strings.size()) { - - locking = true; - - while (output_strings.size()) { - - Array arr; - arr.push_back(output_strings.front()->get()); - _put_msg("output", arr); - output_strings.pop_front(); - } - locking = false; - } - - if (n_messages_dropped > 0) { - Message msg; - msg.message = "Too many messages! " + String::num_int64(n_messages_dropped) + " messages were dropped."; - messages.push_back(msg); - n_messages_dropped = 0; - } - - while (messages.size()) { - locking = true; - Message msg = messages.front()->get(); - _put_msg("message:" + msg.message, msg.data); - messages.pop_front(); - locking = false; - } - - if (n_errors_dropped == 1) { - // Only print one message about dropping per second - OutputError oe; - oe.error = "TOO_MANY_ERRORS"; - oe.error_descr = "Too many errors! Ignoring errors for up to 1 second."; - 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; - errors.push_back(oe); - } - - if (n_warnings_dropped == 1) { - // Only print one message about dropping per second - OutputError oe; - oe.error = "TOO_MANY_WARNINGS"; - oe.error_descr = "Too many warnings! Ignoring warnings for up to 1 second."; - oe.warning = true; - 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; - errors.push_back(oe); - } - - while (errors.size()) { - locking = true; - OutputError oe = errors.front()->get(); - _put_msg("error", oe.serialize()); - errors.pop_front(); - locking = false; - } -} - -void ScriptDebuggerRemote::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(); - poll_every++; -} - -void ScriptDebuggerRemote::_err_handler(void *ud, 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 - - 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; - } - - ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)ud; - sdr->send_error(p_func, p_file, p_line, p_err, p_descr, p_type, si); -} - -void ScriptDebuggerRemote::_poll_events() { - - //this si called from ::idle_poll, happens only when running the game, - //does not get called while on debug break - - while (packet_peer_stream->get_available_packet_count() > 0) { - - _get_output(); - - //send over output_strings - - Variant var; - Error err = packet_peer_stream->get_var(var); - - ERR_CONTINUE(err != OK); - ERR_CONTINUE(var.get_type() != Variant::ARRAY); - - Array cmd = var; - - 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 == "break") { - - if (get_break_language()) - debug(get_break_language()); - } else { - _parse_message(command, data); - } - } -} - -void ScriptDebuggerRemote::_send_profiling_data(bool p_for_frame) { - - int ofs = 0; - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - if (p_for_frame) - ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&profile_info.write[ofs], profile_info.size() - ofs); - else - ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&profile_info.write[ofs], profile_info.size() - ofs); - } - - for (int i = 0; i < ofs; i++) { - profile_info_ptrs.write[i] = &profile_info.write[i]; - } - - SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa; - sa.sort(profile_info_ptrs.ptrw(), ofs); - - int to_send = MIN(ofs, max_frame_functions); - - //check signatures first - uint64_t total_script_time = 0; - - for (int i = 0; i < to_send; i++) { - - if (!profiler_function_signature_map.has(profile_info_ptrs[i]->signature)) { - - int idx = profiler_function_signature_map.size(); - ProfilerSignature sig; - sig.name = profile_info_ptrs[i]->signature; - sig.id = idx; - _put_msg("profile_sig", sig.serialize()); - profiler_function_signature_map[profile_info_ptrs[i]->signature] = idx; - } - - total_script_time += profile_info_ptrs[i]->self_time; - } - - //send frames then - ProfilerFrame metric; - metric.frame_number = Engine::get_singleton()->get_frames_drawn(); - metric.frame_time = frame_time; - metric.idle_time = idle_time; - metric.physics_time = physics_time; - metric.physics_frame_time = physics_frame_time; - metric.script_time = total_script_time; - - // Add script functions information. - metric.frame_functions.resize(to_send); - FrameFunction *w = metric.frame_functions.ptrw(); - for (int i = 0; i < to_send; i++) { - - if (profiler_function_signature_map.has(profile_info_ptrs[i]->signature)) { - w[i].sig_id = profiler_function_signature_map[profile_info_ptrs[i]->signature]; - } - - w[i].call_count = profile_info_ptrs[i]->call_count; - w[i].total_time = profile_info_ptrs[i]->total_time / 1000000.0; - w[i].self_time = profile_info_ptrs[i]->self_time / 1000000.0; - } - if (p_for_frame) { - // Add profile frame data information. - metric.frames_data.append_array(profile_frame_data); - _put_msg("profile_frame", metric.serialize()); - profile_frame_data.clear(); - } else { - _put_msg("profile_total", metric.serialize()); - } -} - -void ScriptDebuggerRemote::idle_poll() { - - // this function is called every frame, except when there is a debugger break (::debug() in this class) - // execution stops and remains in the ::debug function - - _get_output(); - - if (requested_quit) { - - _put_msg("kill_me", Array()); - requested_quit = false; - } - - if (performance) { - - uint64_t pt = OS::get_singleton()->get_ticks_msec(); - if (pt - last_perf_time > 1000) { - - 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); - } - _put_msg("performance", arr); - } - } - - if (visual_profiling) { - Vector<VS::FrameProfileArea> profile_areas = VS::get_singleton()->get_frame_profile(); - if (profile_areas.size()) { - Vector<String> area_names; - Vector<real_t> area_times; - area_names.resize(profile_areas.size()); - area_times.resize(profile_areas.size() * 2); - { - String *area_namesw = area_names.ptrw(); - real_t *area_timesw = area_times.ptrw(); - - for (int i = 0; i < profile_areas.size(); i++) { - area_namesw[i] = profile_areas[i].name; - area_timesw[i * 2 + 0] = profile_areas[i].cpu_msec; - area_timesw[i * 2 + 1] = profile_areas[i].gpu_msec; - } - } - Array msg; - msg.push_back(VS::get_singleton()->get_frame_profile_frame()); - msg.push_back(area_names); - msg.push_back(area_times); - _put_msg("visual_profile", msg); - } - } - - if (profiling) { - - if (skip_profile_frame) { - skip_profile_frame = false; - } else { - //send profiling info normally - _send_profiling_data(true); - } - } - - if (network_profiling) { - uint64_t pt = OS::get_singleton()->get_ticks_msec(); - if (pt - last_net_bandwidth_time > 200) { - last_net_bandwidth_time = pt; - _send_network_bandwidth_usage(); - } - if (pt - last_net_prof_time > 100) { - last_net_prof_time = pt; - _send_network_profiling_data(); - } - } - - if (reload_all_scripts) { - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->reload_all_scripts(); - } - reload_all_scripts = false; - } - - _poll_events(); -} - -void ScriptDebuggerRemote::_send_network_profiling_data() { - ERR_FAIL_COND(multiplayer.is_null()); - - int n_nodes = multiplayer->get_profiling_frame(&network_profile_info.write[0]); - - NetworkProfilerFrame frame; - for (int i = 0; i < n_nodes; i++) { - frame.infos.push_back(network_profile_info[i]); - } - _put_msg("network_profile", frame.serialize()); -} - -void ScriptDebuggerRemote::_send_network_bandwidth_usage() { - ERR_FAIL_COND(multiplayer.is_null()); - - int incoming_bandwidth = multiplayer->get_incoming_bandwidth_usage(); - int outgoing_bandwidth = multiplayer->get_outgoing_bandwidth_usage(); - - Array arr; - arr.push_back(incoming_bandwidth); - arr.push_back(outgoing_bandwidth); - _put_msg("network_bandwidth", arr); -} - -void ScriptDebuggerRemote::send_message(const String &p_message, const Array &p_args) { - - MutexLock lock(mutex); - - if (!locking && is_peer_connected()) { - - if (messages.size() >= max_messages_per_frame) { - n_messages_dropped++; - } else { - Message msg; - msg.message = p_message; - msg.data = p_args; - messages.push_back(msg); - } - } -} - -void ScriptDebuggerRemote::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<ScriptLanguage::StackInfo> &p_stack_info) { - - OutputError 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; - Array cstack; - - uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000; - msec_count += ticks - last_msec; - last_msec = ticks; - - if (msec_count > 1000) { - msec_count = 0; - - err_count = 0; - n_errors_dropped = 0; - warn_count = 0; - n_warnings_dropped = 0; - } - - cstack.resize(p_stack_info.size() * 3); - for (int i = 0; i < p_stack_info.size(); i++) { - cstack[i * 3 + 0] = p_stack_info[i].file; - cstack[i * 3 + 1] = p_stack_info[i].func; - cstack[i * 3 + 2] = p_stack_info[i].line; - } - - //oe.callstack = cstack; - if (oe.warning) { - warn_count++; - } else { - err_count++; - } - - MutexLock lock(mutex); - - if (!locking && is_peer_connected()) { - - if (oe.warning) { - if (warn_count > max_warnings_per_second) { - n_warnings_dropped++; - } else { - errors.push_back(oe); - } - } else { - if (err_count > max_errors_per_second) { - n_errors_dropped++; - } else { - errors.push_back(oe); - } - } - } -} - -void ScriptDebuggerRemote::_print_handler(void *p_this, const String &p_string, bool p_error) { - - ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)p_this; - - uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000; - sdr->msec_count += ticks - sdr->last_msec; - sdr->last_msec = ticks; - - if (sdr->msec_count > 1000) { - sdr->char_count = 0; - sdr->msec_count = 0; - } - - String s = p_string; - int allowed_chars = MIN(MAX(sdr->max_cps - sdr->char_count, 0), s.length()); - - if (allowed_chars == 0) - return; - - if (allowed_chars < s.length()) { - s = s.substr(0, allowed_chars); - } - - sdr->char_count += allowed_chars; - bool overflowed = sdr->char_count >= sdr->max_cps; - - { - MutexLock lock(sdr->mutex); - - if (!sdr->locking && sdr->is_peer_connected()) { - - if (overflowed) - s += "[...]"; - - sdr->output_strings.push_back(s); - - if (overflowed) { - sdr->output_strings.push_back("[output overflow, print less text!]"); - } - } - } -} - -void ScriptDebuggerRemote::request_quit() { - - requested_quit = true; -} - -void ScriptDebuggerRemote::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { - multiplayer = p_multiplayer; -} - -bool ScriptDebuggerRemote::is_profiling() const { - - return profiling; -} -void ScriptDebuggerRemote::add_profiling_frame_data(const StringName &p_name, const Array &p_data) { - - int idx = -1; - for (int i = 0; i < profile_frame_data.size(); i++) { - if (profile_frame_data[i].name == p_name) { - idx = i; - break; - } - } - - FrameData fd; - fd.name = p_name; - fd.data = p_data; - - if (idx == -1) { - profile_frame_data.push_back(fd); - } else { - profile_frame_data.write[idx] = fd; - } -} - -void ScriptDebuggerRemote::profiling_start() { - //ignores this, uses it via connection -} - -void ScriptDebuggerRemote::profiling_end() { - //ignores this, uses it via connection -} - -void ScriptDebuggerRemote::profiling_set_frame_times(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; -} - -void ScriptDebuggerRemote::set_skip_breakpoints(bool p_skip_breakpoints) { - skip_breakpoints = p_skip_breakpoints; -} - -ScriptDebuggerRemote::ResourceUsageFunc ScriptDebuggerRemote::resource_usage_func = NULL; -ScriptDebuggerRemote::ParseMessageFunc ScriptDebuggerRemote::scene_tree_parse_func = NULL; - -ScriptDebuggerRemote::ScriptDebuggerRemote() : - profiling(false), - visual_profiling(false), - network_profiling(false), - max_frame_functions(16), - skip_profile_frame(false), - reload_all_scripts(false), - tcp_client(Ref<StreamPeerTCP>(memnew(StreamPeerTCP))), - packet_peer_stream(Ref<PacketPeerStream>(memnew(PacketPeerStream))), - last_perf_time(0), - last_net_prof_time(0), - last_net_bandwidth_time(0), - performance(Engine::get_singleton()->get_singleton_object("Performance")), - requested_quit(false), - max_messages_per_frame(GLOBAL_GET("network/limits/debugger_stdout/max_messages_per_frame")), - n_messages_dropped(0), - max_errors_per_second(GLOBAL_GET("network/limits/debugger_stdout/max_errors_per_second")), - max_warnings_per_second(GLOBAL_GET("network/limits/debugger_stdout/max_warnings_per_second")), - n_errors_dropped(0), - max_cps(GLOBAL_GET("network/limits/debugger_stdout/max_chars_per_second")), - char_count(0), - err_count(0), - warn_count(0), - last_msec(0), - msec_count(0), - locking(false), - poll_every(0) { - - packet_peer_stream->set_stream_peer(tcp_client); - packet_peer_stream->set_output_buffer_max_size((1024 * 1024 * 8) - 4); // 8 MiB should be way more than enough, minus 4 bytes for separator. - - phl.printfunc = _print_handler; - phl.userdata = this; - add_print_handler(&phl); - - eh.errfunc = _err_handler; - eh.userdata = this; - add_error_handler(&eh); - - profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions")); - network_profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions")); - profile_info_ptrs.resize(profile_info.size()); -} - -ScriptDebuggerRemote::~ScriptDebuggerRemote() { - - remove_print_handler(&phl); - remove_error_handler(&eh); -} diff --git a/core/script_debugger_remote.h b/core/script_debugger_remote.h deleted file mode 100644 index b7a309b2f9..0000000000 --- a/core/script_debugger_remote.h +++ /dev/null @@ -1,316 +0,0 @@ -/*************************************************************************/ -/* script_debugger_remote.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_REMOTE_H -#define SCRIPT_DEBUGGER_REMOTE_H - -#include "core/io/packet_peer.h" -#include "core/io/stream_peer_tcp.h" -#include "core/list.h" -#include "core/os/os.h" -#include "core/script_language.h" - -class ScriptDebuggerRemote : public ScriptDebugger { - -public: - class ResourceInfo { - public: - 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; - } - }; - - class ResourceUsage { - public: - List<ResourceInfo> infos; - - Array serialize(); - bool deserialize(const Array &p_arr); - }; - - class FrameInfo { - public: - StringName name; - float self_time; - float total_time; - - FrameInfo() { - self_time = 0; - total_time = 0; - } - }; - - class FrameFunction { - public: - int sig_id; - int call_count; - StringName name; - float self_time; - float total_time; - - FrameFunction() { - sig_id = -1; - call_count = 0; - self_time = 0; - total_time = 0; - } - }; - - class ScriptStackVariable { - public: - 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); - }; - - class ScriptStackDump { - public: - List<ScriptLanguage::StackInfo> frames; - ScriptStackDump() {} - - Array serialize(); - bool deserialize(const Array &p_arr); - }; - - class Message { - - public: - String message; - Array data; - - Message() {} - }; - - class OutputError { - public: - 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); - }; - - struct FrameData { - - StringName name; - Array data; - }; - - class ProfilerSignature { - public: - StringName name; - int id; - - Array serialize(); - bool deserialize(const Array &p_arr); - - ProfilerSignature() { - id = -1; - }; - }; - - class ProfilerFrame { - public: - int frame_number; - float frame_time; - float idle_time; - float physics_time; - float physics_frame_time; - float script_time; - - Vector<FrameData> frames_data; - Vector<FrameFunction> frame_functions; - - ProfilerFrame() { - frame_number = 0; - frame_time = 0; - idle_time = 0; - physics_time = 0; - physics_frame_time = 0; - } - - Array serialize(); - bool deserialize(const Array &p_arr); - }; - - class NetworkProfilerFrame { - public: - Vector<MultiplayerAPI::ProfilingInfo> infos; - - Array serialize(); - bool deserialize(const Array &p_arr); - - NetworkProfilerFrame(){}; - }; - -protected: - struct ProfileInfoSort { - - bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const { - return A->total_time < B->total_time; - } - }; - - Vector<ScriptLanguage::ProfilingInfo> profile_info; - Vector<ScriptLanguage::ProfilingInfo *> profile_info_ptrs; - Vector<MultiplayerAPI::ProfilingInfo> network_profile_info; - - Map<StringName, int> profiler_function_signature_map; - float frame_time, idle_time, physics_time, physics_frame_time; - - bool profiling; - bool visual_profiling; - bool network_profiling; - int max_frame_functions; - bool skip_profile_frame; - bool reload_all_scripts; - - Ref<StreamPeerTCP> tcp_client; - Ref<PacketPeerStream> packet_peer_stream; - - uint64_t last_perf_time; - uint64_t last_net_prof_time; - uint64_t last_net_bandwidth_time; - Object *performance; - bool requested_quit; - Mutex mutex; - - List<String> output_strings; - List<Message> messages; - int max_messages_per_frame; - int n_messages_dropped; - List<OutputError> errors; - int max_errors_per_second; - int max_warnings_per_second; - int n_errors_dropped; - int n_warnings_dropped; - - int max_cps; - int char_count; - int err_count; - int warn_count; - uint64_t last_msec; - uint64_t msec_count; - - bool locking; //hack to avoid a deadloop - static void _print_handler(void *p_this, const String &p_string, bool p_error); - - PrintHandlerList phl; - - void _get_output(); - void _poll_events(); - uint32_t poll_every; - - void _parse_message(const String p_command, const Array &p_data, ScriptLanguage *p_script = NULL); - - void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value); - - void _send_object_id(ObjectID p_id); - void _send_video_memory(); - - Ref<MultiplayerAPI> multiplayer; - - ErrorHandlerList eh; - static void _err_handler(void *, const char *, const char *, int p_line, const char *, const char *, ErrorHandlerType p_type); - - void _put_msg(String p_message, Array p_data); - void _send_profiling_data(bool p_for_frame); - void _send_network_profiling_data(); - void _send_network_bandwidth_usage(); - - Vector<FrameData> profile_frame_data; - - bool skip_breakpoints; - -public: - typedef void (*ResourceUsageFunc)(ResourceUsage *); - typedef Error (*ParseMessageFunc)(const String &p_name, const Array &p_msg); // Returns true if something was found (stopping propagation). - - static ResourceUsageFunc resource_usage_func; - static ParseMessageFunc scene_tree_parse_func; // Could be made into list, extensible... - - Error connect_to_host(const String &p_host, uint16_t p_port); - bool is_peer_connected(); - virtual void debug(ScriptLanguage *p_script, bool p_can_continue = true, bool p_is_error_breakpoint = false); - virtual void idle_poll(); - virtual void line_poll(); - - virtual bool is_remote() const { return true; } - virtual void request_quit(); - - virtual void send_message(const String &p_message, const Array &p_args); - 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, const Vector<ScriptLanguage::StackInfo> &p_stack_info); - - virtual void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer); - - virtual bool is_profiling() const; - virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data); - - virtual void profiling_start(); - virtual void profiling_end(); - virtual void profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time); - - virtual void set_skip_breakpoints(bool p_skip_breakpoints); - - ScriptDebuggerRemote(); - ~ScriptDebuggerRemote(); -}; - -#endif // SCRIPT_DEBUGGER_REMOTE_H diff --git a/core/script_language.cpp b/core/script_language.cpp index 0b00247502..428f363b17 100644 --- a/core/script_language.cpp +++ b/core/script_language.cpp @@ -31,6 +31,8 @@ #include "script_language.h" #include "core/core_string_names.h" +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" #include "core/project_settings.h" #include <stdint.h> @@ -46,8 +48,8 @@ void Script::_notification(int p_what) { if (p_what == NOTIFICATION_POSTINITIALIZE) { - if (ScriptDebugger::get_singleton()) - ScriptDebugger::get_singleton()->set_break_language(get_language()); + if (EngineDebugger::is_active()) + EngineDebugger::get_script_debugger()->set_break_language(get_language()); } } @@ -356,89 +358,6 @@ ScriptCodeCompletionCache::ScriptCodeCompletionCache() { void ScriptLanguage::frame() { } -ScriptDebugger *ScriptDebugger::singleton = NULL; - -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::idle_poll() { -} - -void ScriptDebugger::line_poll() { -} - -void ScriptDebugger::set_break_language(ScriptLanguage *p_lang) { - - break_lang = p_lang; -} - -ScriptLanguage *ScriptDebugger::get_break_language() const { - - return break_lang; -} - -ScriptDebugger::ScriptDebugger() { - - singleton = this; - lines_left = -1; - depth = -1; - break_lang = NULL; -} - bool PlaceHolderScriptInstance::set(const StringName &p_name, const Variant &p_value) { if (script->is_placeholder_fallback_enabled()) diff --git a/core/script_language.h b/core/script_language.h index 48570ae546..f1b809425b 100644 --- a/core/script_language.h +++ b/core/script_language.h @@ -350,6 +350,11 @@ public: virtual void thread_exit() {} /* DEBUGGER FUNCTIONS */ + struct StackInfo { + String file; + String func; + int line; + }; virtual String debug_get_error() const = 0; virtual int debug_get_stack_level_count() const = 0; @@ -362,12 +367,6 @@ public: virtual void debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) = 0; virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) = 0; - struct StackInfo { - String file; - String func; - int line; - }; - virtual Vector<StackInfo> debug_get_current_stack_info() { return Vector<StackInfo>(); } virtual void reload_all_scripts() = 0; @@ -460,56 +459,4 @@ public: PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner); ~PlaceHolderScriptInstance(); }; - -class ScriptDebugger { - - int lines_left; - int depth; - - static ScriptDebugger *singleton; - Map<int, Set<StringName> > breakpoints; - - ScriptLanguage *break_lang; - -public: - _FORCE_INLINE_ static ScriptDebugger *get_singleton() { return singleton; } - 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 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; } - - virtual void debug(ScriptLanguage *p_script, bool p_can_continue = true, bool p_is_error_breakpoint = false) = 0; - virtual void idle_poll(); - virtual void line_poll(); - - void set_break_language(ScriptLanguage *p_lang); - ScriptLanguage *get_break_language() const; - - virtual void send_message(const String &p_message, const Array &p_args) = 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, const Vector<ScriptLanguage::StackInfo> &p_stack_info) = 0; - - virtual bool is_remote() const { return false; } - virtual void request_quit() {} - - virtual void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) {} - - virtual bool is_profiling() const = 0; - virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data) = 0; - virtual void profiling_start() = 0; - virtual void profiling_end() = 0; - virtual void profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) = 0; - - ScriptDebugger(); - virtual ~ScriptDebugger() { singleton = NULL; } -}; - #endif diff --git a/core/variant.cpp b/core/variant.cpp index 550974363b..b82602a5a4 100644 --- a/core/variant.cpp +++ b/core/variant.cpp @@ -31,6 +31,7 @@ #include "variant.h" #include "core/core_string_names.h" +#include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" #include "core/math/math_funcs.h" #include "core/print_string.h" @@ -2221,7 +2222,7 @@ Variant::operator RID() const { return RID(); } else if (type == OBJECT && _get_obj().obj) { #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton()) { + if (EngineDebugger::is_active()) { ERR_FAIL_COND_V_MSG(ObjectDB::get_instance(_get_obj().id) == nullptr, RID(), "Invalid pointer (object was freed)."); }; #endif diff --git a/core/variant_call.cpp b/core/variant_call.cpp index 99cfc7ed3c..f00aad33fc 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -33,10 +33,10 @@ #include "core/color_names.inc" #include "core/core_string_names.h" #include "core/crypto/crypto_core.h" +#include "core/debugger/engine_debugger.h" #include "core/io/compression.h" #include "core/object.h" #include "core/os/os.h" -#include "core/script_language.h" typedef void (*VariantFunc)(Variant &r_ret, Variant &p_self, const Variant **p_args); typedef void (*VariantConstructFunc)(Variant &r_ret, const Variant **p_args); @@ -1213,7 +1213,7 @@ void Variant::call_ptr(const StringName &p_method, const Variant **p_args, int p return; } #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; return; } diff --git a/core/variant_op.cpp b/core/variant_op.cpp index 36d1278929..88f6ce19a2 100644 --- a/core/variant_op.cpp +++ b/core/variant_op.cpp @@ -31,8 +31,8 @@ #include "variant.h" #include "core/core_string_names.h" +#include "core/debugger/engine_debugger.h" #include "core/object.h" -#include "core/script_language.h" #define CASE_TYPE_ALL(PREFIX, OP) \ CASE_TYPE(PREFIX, OP, INT) \ @@ -1739,7 +1739,7 @@ void Variant::set_named(const StringName &p_index, const Variant &p_value, bool #ifdef DEBUG_ENABLED if (!_get_obj().obj) { break; - } else if (ScriptDebugger::get_singleton() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + } else if (EngineDebugger::is_active() && ObjectDB::get_instance(_get_obj().id) == nullptr) { break; } @@ -1941,7 +1941,7 @@ Variant Variant::get_named(const StringName &p_index, bool *r_valid) const { return "Instance base is null."; } else { - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { if (r_valid) *r_valid = false; return "Attempted use of stray pointer object."; @@ -2556,7 +2556,7 @@ void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid) if (obj) { #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { WARN_PRINT("Attempted use of previously freed pointer object."); valid = false; @@ -3011,7 +3011,7 @@ Variant Variant::get(const Variant &p_index, bool *r_valid) const { #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { valid = false; return "Attempted get on previously freed instance."; } @@ -3076,7 +3076,7 @@ bool Variant::in(const Variant &p_index, bool *r_valid) const { bool valid = false; #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { if (r_valid) { *r_valid = false; } @@ -3405,7 +3405,7 @@ void Variant::get_property_list(List<PropertyInfo> *p_list) const { if (obj) { #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { WARN_PRINT("Attempted get_property list on previously freed instance."); return; } @@ -3490,7 +3490,7 @@ bool Variant::iter_init(Variant &r_iter, bool &valid) const { #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { valid = false; return false; } @@ -3676,7 +3676,7 @@ bool Variant::iter_next(Variant &r_iter, bool &valid) const { #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { valid = false; return false; } @@ -3855,7 +3855,7 @@ Variant Variant::iter_get(const Variant &r_iter, bool &r_valid) const { return Variant(); } #ifdef DEBUG_ENABLED - if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) { r_valid = false; return Variant(); } |