diff options
Diffstat (limited to 'editor/debugger')
21 files changed, 2686 insertions, 241 deletions
diff --git a/editor/debugger/SCsub b/editor/debugger/SCsub index 359d04e5df..99f1c888f0 100644 --- a/editor/debugger/SCsub +++ b/editor/debugger/SCsub @@ -3,3 +3,5 @@ Import("env") env.add_source_files(env.editor_sources, "*.cpp") + +SConscript("debug_adapter/SCsub") diff --git a/editor/debugger/debug_adapter/SCsub b/editor/debugger/debug_adapter/SCsub new file mode 100644 index 0000000000..359d04e5df --- /dev/null +++ b/editor/debugger/debug_adapter/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.editor_sources, "*.cpp") diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp new file mode 100644 index 0000000000..485d58f4a3 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp @@ -0,0 +1,610 @@ +/*************************************************************************/ +/* debug_adapter_parser.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "debug_adapter_parser.h" + +#include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" +#include "editor/editor_node.h" +#include "editor/editor_run_native.h" + +void DebugAdapterParser::_bind_methods() { + // Requests + ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize); + ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect); + ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch); + ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach); + ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart); + ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate); + ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::prepare_success_response); + ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause); + ClassDB::bind_method(D_METHOD("req_continue", "params"), &DebugAdapterParser::req_continue); + ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads); + ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace); + ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints); + ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations); + ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes); + ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables); + ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next); + ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn); + ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate); + ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg); +} + +Dictionary DebugAdapterParser::prepare_base_event() const { + Dictionary event; + event["type"] = "event"; + + return event; +} + +Dictionary DebugAdapterParser::prepare_success_response(const Dictionary &p_params) const { + Dictionary response; + response["type"] = "response"; + response["request_seq"] = p_params["seq"]; + response["command"] = p_params["command"]; + response["success"] = true; + + return response; +} + +Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params, DAP::ErrorType err_type, const Dictionary &variables) const { + Dictionary response, body; + response["type"] = "response"; + response["request_seq"] = p_params["seq"]; + response["command"] = p_params["command"]; + response["success"] = false; + response["body"] = body; + + DAP::Message message; + String error, error_desc; + switch (err_type) { + case DAP::ErrorType::WRONG_PATH: + error = "wrong_path"; + error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\""; + break; + case DAP::ErrorType::NOT_RUNNING: + error = "not_running"; + error_desc = "Can't attach to a running session since there isn't one."; + break; + case DAP::ErrorType::TIMEOUT: + error = "timeout"; + error_desc = "Timeout reached while processing a request."; + break; + case DAP::ErrorType::UNKNOWN_PLATFORM: + error = "unknown_platform"; + error_desc = "The specified platform is unknown."; + break; + case DAP::ErrorType::MISSING_DEVICE: + error = "missing_device"; + error_desc = "There's no connected device with specified id."; + break; + case DAP::ErrorType::UNKNOWN: + default: + error = "unknown"; + error_desc = "An unknown error has ocurred when processing the request."; + break; + } + + message.id = err_type; + message.format = error_desc; + message.variables = variables; + response["message"] = error; + body["error"] = message.to_json(); + + return response; +} + +Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params); + Dictionary args = p_params["arguments"]; + + Ref<DAPeer> peer = DebugAdapterProtocol::get_singleton()->get_current_peer(); + + peer->linesStartAt1 = args.get("linesStartAt1", false); + peer->columnsStartAt1 = args.get("columnsStartAt1", false); + peer->supportsVariableType = args.get("supportsVariableType", false); + peer->supportsInvalidatedEvent = args.get("supportsInvalidatedEvent", false); + + DAP::Capabilities caps; + response["body"] = caps.to_json(); + + DebugAdapterProtocol::get_singleton()->notify_initialized(); + + if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) { + // Send all current breakpoints + List<String> breakpoints; + ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); + for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) { + String breakpoint = E->get(); + + String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://" + int line = breakpoint.substr(path.size()).to_int(); + + DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true); + } + } else { + // Remove all current breakpoints + EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints(); + } + + return response; +} + +Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const { + if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) { + EditorNode::get_singleton()->run_stop(); + } + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const { + Dictionary args = p_params["arguments"]; + if (args.has("project") && !is_valid_path(args["project"])) { + Dictionary variables; + variables["clientPath"] = args["project"]; + variables["editorPath"] = ProjectSettings::get_singleton()->get_resource_path(); + return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables); + } + + if (args.has("godot/custom_data")) { + DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"]; + } + + ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); + if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) { + dbg->debug_skip_breakpoints(); + } + + String platform_string = args.get("platform", "host"); + if (platform_string == "host") { + EditorNode::get_singleton()->run_play(); + } else { + int device = args.get("device", -1); + int idx = -1; + if (platform_string == "android") { + for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { + if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") { + idx = i; + break; + } + } + } else if (platform_string == "web") { + for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { + if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "HTML5") { + idx = i; + break; + } + } + } + + if (idx == -1) { + return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM); + } + + EditorNode *editor = EditorNode::get_singleton(); + Error err = platform_string == "android" ? editor->run_play_native(device, idx) : editor->run_play_native(-1, idx); + if (err) { + if (err == ERR_INVALID_PARAMETER && platform_string == "android") { + return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE); + } else { + return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN); + } + } + } + + DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false; + DebugAdapterProtocol::get_singleton()->notify_process(); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const { + ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); + if (!dbg->is_session_active()) { + return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING); + } + + DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true; + DebugAdapterProtocol::get_singleton()->notify_process(); + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const { + // Extract embedded "arguments" so it can be given to req_launch/req_attach + Dictionary params = p_params, args; + args = params["arguments"]; + args = args["arguments"]; + params["arguments"] = args; + + Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params); + if (!response["success"]) { + response["command"] = p_params["command"]; + return response; + } + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const { + EditorNode::get_singleton()->run_stop(); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_pause(const Dictionary &p_params) const { + EditorNode::get_singleton()->get_pause_button()->set_pressed(true); + EditorDebuggerNode::get_singleton()->_paused(); + + DebugAdapterProtocol::get_singleton()->notify_stopped_paused(); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_continue(const Dictionary &p_params) const { + EditorNode::get_singleton()->get_pause_button()->set_pressed(false); + EditorDebuggerNode::get_singleton()->_paused(); + + DebugAdapterProtocol::get_singleton()->notify_continued(); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_threads(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Array arr; + DAP::Thread thread; + + thread.id = 1; // Hardcoded because Godot only supports debugging one thread at the moment + thread.name = "Main"; + arr.push_back(thread.to_json()); + body["threads"] = arr; + + return response; +} + +Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const { + if (DebugAdapterProtocol::get_singleton()->_processing_stackdump) { + return Dictionary(); + } + + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1; + bool columns_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->columnsStartAt1; + + Array arr; + DebugAdapterProtocol *dap = DebugAdapterProtocol::get_singleton(); + for (const KeyValue<DAP::StackFrame, List<int>> &E : dap->stackframe_list) { + DAP::StackFrame sf = E.key; + if (!lines_at_one) { + sf.line--; + } + if (!columns_at_one) { + sf.column--; + } + + arr.push_back(sf.to_json()); + } + + body["stackFrames"] = arr; + return response; +} + +Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + DAP::Source source; + source.from_json(args["source"]); + + bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1; + + if (!is_valid_path(source.path)) { + Dictionary variables; + variables["clientPath"] = source.path; + variables["editorPath"] = ProjectSettings::get_singleton()->get_resource_path(); + return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables); + } + + Array breakpoints = args["breakpoints"], lines; + for (int i = 0; i < breakpoints.size(); i++) { + DAP::SourceBreakpoint breakpoint; + breakpoint.from_json(breakpoints[i]); + + lines.push_back(breakpoint.line + !lines_at_one); + } + + Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines); + body["breakpoints"] = updated_breakpoints; + + return response; +} + +Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + Dictionary args = p_params["arguments"]; + + Array locations; + DAP::BreakpointLocation location; + location.line = args["line"]; + if (args.has("endLine")) { + location.endLine = args["endLine"]; + } + locations.push_back(location.to_json()); + + body["breakpoints"] = locations; + return response; +} + +Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + int frame_id = args["frameId"]; + Array scope_list; + + DAP::StackFrame frame; + frame.id = frame_id; + Map<DAP::StackFrame, List<int>>::Element *E = DebugAdapterProtocol::get_singleton()->stackframe_list.find(frame); + if (E) { + ERR_FAIL_COND_V(E->value().size() != 3, prepare_error_response(p_params, DAP::ErrorType::UNKNOWN)); + for (int i = 0; i < 3; i++) { + DAP::Scope scope; + scope.variablesReference = E->value()[i]; + switch (i) { + case 0: + scope.name = "Locals"; + scope.presentationHint = "locals"; + break; + case 1: + scope.name = "Members"; + scope.presentationHint = "members"; + break; + case 2: + scope.name = "Globals"; + scope.presentationHint = "globals"; + } + + scope_list.push_back(scope.to_json()); + } + } + + EditorDebuggerNode::get_singleton()->get_default_debugger()->request_stack_dump(frame_id); + DebugAdapterProtocol::get_singleton()->_current_frame = frame_id; + + body["scopes"] = scope_list; + return response; +} + +Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const { + // If _remaining_vars > 0, the debugee is still sending a stack dump to the editor. + if (DebugAdapterProtocol::get_singleton()->_remaining_vars > 0) { + return Dictionary(); + } + + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + int variable_id = args["variablesReference"]; + + Map<int, Array>::Element *E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id); + + if (E) { + if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) { + for (int i = 0; i < E->value().size(); i++) { + Dictionary variable = E->value()[i]; + variable.erase("type"); + } + } + body["variables"] = E ? E->value() : Array(); + return response; + } else { + return Dictionary(); + } +} + +Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const { + EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_next(); + DebugAdapterProtocol::get_singleton()->_stepping = true; + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const { + EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_step(); + DebugAdapterProtocol::get_singleton()->_stepping = true; + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const { + Dictionary response = prepare_success_response(p_params), body; + response["body"] = body; + + Dictionary args = p_params["arguments"]; + + String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]); + body["result"] = value; + + return response; +} + +Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const { + Dictionary args = p_params["arguments"]; + + String msg = args["message"]; + Array data = args["data"]; + + EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data); + + return prepare_success_response(p_params); +} + +Dictionary DebugAdapterParser::ev_initialized() const { + Dictionary event = prepare_base_event(); + event["event"] = "initialized"; + + return event; +} + +Dictionary DebugAdapterParser::ev_process(const String &p_command) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "process"; + event["body"] = body; + + body["name"] = OS::get_singleton()->get_executable_path(); + body["startMethod"] = p_command; + + return event; +} + +Dictionary DebugAdapterParser::ev_terminated() const { + Dictionary event = prepare_base_event(); + event["event"] = "terminated"; + + return event; +} + +Dictionary DebugAdapterParser::ev_exited(const int &p_exitcode) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "exited"; + event["body"] = body; + + body["exitCode"] = p_exitcode; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped() const { + Dictionary event = prepare_base_event(), body; + event["event"] = "stopped"; + event["body"] = body; + + body["threadId"] = 1; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped_paused() const { + Dictionary event = ev_stopped(); + Dictionary body = event["body"]; + + body["reason"] = "paused"; + body["description"] = "Paused"; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped_exception(const String &p_error) const { + Dictionary event = ev_stopped(); + Dictionary body = event["body"]; + + body["reason"] = "exception"; + body["description"] = "Exception"; + body["text"] = p_error; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped_breakpoint(const int &p_id) const { + Dictionary event = ev_stopped(); + Dictionary body = event["body"]; + + body["reason"] = "breakpoint"; + body["description"] = "Breakpoint"; + + Array breakpoints; + breakpoints.push_back(p_id); + body["hitBreakpointIds"] = breakpoints; + + return event; +} + +Dictionary DebugAdapterParser::ev_stopped_step() const { + Dictionary event = ev_stopped(); + Dictionary body = event["body"]; + + body["reason"] = "step"; + body["description"] = "Breakpoint"; + + return event; +} + +Dictionary DebugAdapterParser::ev_continued() const { + Dictionary event = prepare_base_event(), body; + event["event"] = "continued"; + event["body"] = body; + + body["threadId"] = 1; + + return event; +} + +Dictionary DebugAdapterParser::ev_output(const String &p_message) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "output"; + event["body"] = body; + + body["category"] = "stdout"; + body["output"] = p_message + "\r\n"; + + return event; +} + +Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "breakpoint"; + event["body"] = body; + + body["reason"] = p_enabled ? "new" : "removed"; + body["breakpoint"] = p_breakpoint.to_json(); + + return event; +} + +Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const { + Dictionary event = prepare_base_event(), body; + event["event"] = "godot/custom_data"; + event["body"] = body; + + body["message"] = p_msg; + body["data"] = p_data; + + return event; +} diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h new file mode 100644 index 0000000000..4c93464e39 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_parser.h @@ -0,0 +1,96 @@ +/*************************************************************************/ +/* debug_adapter_parser.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 DEBUG_ADAPTER_PARSER_H +#define DEBUG_ADAPTER_PARSER_H + +#include "core/config/project_settings.h" +#include "debug_adapter_protocol.h" +#include "debug_adapter_types.h" + +struct DAPeer; +class DebugAdapterProtocol; + +class DebugAdapterParser : public Object { + GDCLASS(DebugAdapterParser, Object); + +private: + friend DebugAdapterProtocol; + + _FORCE_INLINE_ bool is_valid_path(const String &p_path) const { + return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path()); + } + +protected: + static void _bind_methods(); + + Dictionary prepare_base_event() const; + Dictionary prepare_success_response(const Dictionary &p_params) const; + Dictionary prepare_error_response(const Dictionary &p_params, DAP::ErrorType err_type, const Dictionary &variables = Dictionary()) const; + + Dictionary ev_stopped() const; + +public: + // Requests + Dictionary req_initialize(const Dictionary &p_params) const; + Dictionary req_launch(const Dictionary &p_params) const; + Dictionary req_disconnect(const Dictionary &p_params) const; + Dictionary req_attach(const Dictionary &p_params) const; + Dictionary req_restart(const Dictionary &p_params) const; + Dictionary req_terminate(const Dictionary &p_params) const; + Dictionary req_pause(const Dictionary &p_params) const; + Dictionary req_continue(const Dictionary &p_params) const; + Dictionary req_threads(const Dictionary &p_params) const; + Dictionary req_stackTrace(const Dictionary &p_params) const; + Dictionary req_setBreakpoints(const Dictionary &p_params) const; + Dictionary req_breakpointLocations(const Dictionary &p_params) const; + Dictionary req_scopes(const Dictionary &p_params) const; + Dictionary req_variables(const Dictionary &p_params) const; + Dictionary req_next(const Dictionary &p_params) const; + Dictionary req_stepIn(const Dictionary &p_params) const; + Dictionary req_evaluate(const Dictionary &p_params) const; + Dictionary req_godot_put_msg(const Dictionary &p_params) const; + + // Events + Dictionary ev_initialized() const; + Dictionary ev_process(const String &p_command) const; + Dictionary ev_terminated() const; + Dictionary ev_exited(const int &p_exitcode) const; + Dictionary ev_stopped_paused() const; + Dictionary ev_stopped_exception(const String &p_error) const; + Dictionary ev_stopped_breakpoint(const int &p_id) const; + Dictionary ev_stopped_step() const; + Dictionary ev_continued() const; + Dictionary ev_output(const String &p_message) const; + Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const; + Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const; +}; + +#endif diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp new file mode 100644 index 0000000000..2e0e6cb7c8 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp @@ -0,0 +1,1010 @@ +/*************************************************************************/ +/* debug_adapter_protocol.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "debug_adapter_protocol.h" + +#include "core/config/project_settings.h" +#include "core/debugger/debugger_marshalls.h" +#include "core/io/json.h" +#include "editor/debugger/script_editor_debugger.h" +#include "editor/doc_tools.h" +#include "editor/editor_log.h" +#include "editor/editor_node.h" + +DebugAdapterProtocol *DebugAdapterProtocol::singleton = nullptr; + +Error DAPeer::handle_data() { + int read = 0; + // Read headers + if (!has_header) { + if (!connection->get_available_bytes()) { + return OK; + } + while (true) { + if (req_pos >= DAP_MAX_BUFFER_SIZE) { + req_pos = 0; + ERR_FAIL_COND_V_MSG(true, ERR_OUT_OF_MEMORY, "Response header too big"); + } + Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); + if (err != OK) { + return FAILED; + } else if (read != 1) { // Busy, wait until next poll + return ERR_BUSY; + } + char *r = (char *)req_buf; + int l = req_pos; + + // End of headers + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + r[l - 3] = '\0'; // Null terminate to read string + String header; + header.parse_utf8(r); + content_length = header.substr(16).to_int(); + has_header = true; + req_pos = 0; + break; + } + req_pos++; + } + } + if (has_header) { + while (req_pos < content_length) { + if (content_length >= DAP_MAX_BUFFER_SIZE) { + req_pos = 0; + has_header = false; + ERR_FAIL_COND_V_MSG(req_pos >= DAP_MAX_BUFFER_SIZE, ERR_OUT_OF_MEMORY, "Response content too big"); + } + Error err = connection->get_partial_data(&req_buf[req_pos], content_length - req_pos, read); + if (err != OK) { + return FAILED; + } else if (read < content_length - req_pos) { + return ERR_BUSY; + } + req_pos += read; + } + + // Parse data + String msg; + msg.parse_utf8((const char *)req_buf, req_pos); + + // Apply a timestamp if it there's none yet + if (!timestamp) { + timestamp = OS::get_singleton()->get_ticks_msec(); + } + + // Response + if (DebugAdapterProtocol::get_singleton()->process_message(msg)) { + // Reset to read again + req_pos = 0; + has_header = false; + timestamp = 0; + } + } + return OK; +} + +Error DAPeer::send_data() { + while (res_queue.size()) { + Dictionary data = res_queue.front()->get(); + if (!data.has("seq")) { + data["seq"] = ++seq; + } + String formatted_data = format_output(data); + + int data_sent = 0; + while (data_sent < formatted_data.length()) { + int curr_sent = 0; + Error err = connection->put_partial_data((const uint8_t *)formatted_data.utf8().get_data(), formatted_data.size() - data_sent - 1, curr_sent); + if (err != OK) { + return err; + } + data_sent += curr_sent; + } + res_queue.pop_front(); + } + return OK; +} + +String DAPeer::format_output(const Dictionary &p_params) const { + String response = Variant(p_params).to_json_string(); + String header = "Content-Length: "; + CharString charstr = response.utf8(); + size_t len = charstr.length(); + header += itos(len); + header += "\r\n\r\n"; + + return header + response; +} + +Error DebugAdapterProtocol::on_client_connected() { + ERR_FAIL_COND_V_MSG(clients.size() >= DAP_MAX_CLIENTS, FAILED, "Max client limits reached"); + + Ref<StreamPeerTCP> tcp_peer = server->take_connection(); + tcp_peer->set_no_delay(true); + Ref<DAPeer> peer = memnew(DAPeer); + peer->connection = tcp_peer; + clients.push_back(peer); + + EditorDebuggerNode::get_singleton()->get_default_debugger()->set_move_to_foreground(false); + EditorNode::get_log()->add_message("[DAP] Connection Taken", EditorLog::MSG_TYPE_EDITOR); + return OK; +} + +void DebugAdapterProtocol::on_client_disconnected(const Ref<DAPeer> &p_peer) { + clients.erase(p_peer); + if (!clients.size()) { + reset_ids(); + EditorDebuggerNode::get_singleton()->get_default_debugger()->set_move_to_foreground(true); + } + EditorNode::get_log()->add_message("[DAP] Disconnected", EditorLog::MSG_TYPE_EDITOR); +} + +void DebugAdapterProtocol::reset_current_info() { + _current_request = ""; + _current_peer.unref(); +} + +void DebugAdapterProtocol::reset_ids() { + breakpoint_id = 0; + breakpoint_list.clear(); + + reset_stack_info(); +} + +void DebugAdapterProtocol::reset_stack_info() { + stackframe_id = 0; + variable_id = 1; + + stackframe_list.clear(); + variable_list.clear(); +} + +int DebugAdapterProtocol::parse_variant(const Variant &p_var) { + switch (p_var.get_type()) { + case Variant::VECTOR2: + case Variant::VECTOR2I: { + int id = variable_id++; + Vector2 vec = p_var; + DAP::Variable x, y; + x.name = "x"; + y.name = "y"; + x.type = y.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT); + x.value = rtos(vec.x); + y.value = rtos(vec.y); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::RECT2: + case Variant::RECT2I: { + int id = variable_id++; + Rect2 rect = p_var; + DAP::Variable x, y, w, h; + x.name = "x"; + y.name = "y"; + w.name = "w"; + h.name = "h"; + x.type = y.type = w.type = h.type = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT); + x.value = rtos(rect.position.x); + y.value = rtos(rect.position.y); + w.value = rtos(rect.size.x); + h.value = rtos(rect.size.y); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(w.to_json()); + arr.push_back(h.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::VECTOR3: + case Variant::VECTOR3I: { + int id = variable_id++; + Vector3 vec = p_var; + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = y.type = z.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT); + x.value = rtos(vec.x); + y.value = rtos(vec.y); + z.value = rtos(vec.z); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::TRANSFORM2D: { + int id = variable_id++; + Transform2D transform = p_var; + DAP::Variable x, y, origin; + x.name = "x"; + y.name = "y"; + origin.name = "origin"; + x.type = y.type = origin.type = Variant::get_type_name(Variant::VECTOR2); + x.value = transform.elements[0]; + y.value = transform.elements[1]; + origin.value = transform.elements[2]; + x.variablesReference = parse_variant(transform.elements[0]); + y.variablesReference = parse_variant(transform.elements[1]); + origin.variablesReference = parse_variant(transform.elements[2]); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(origin.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::PLANE: { + int id = variable_id++; + Plane plane = p_var; + DAP::Variable d, normal; + d.name = "d"; + normal.name = "normal"; + d.type = Variant::get_type_name(Variant::FLOAT); + normal.type = Variant::get_type_name(Variant::VECTOR3); + d.value = rtos(plane.d); + normal.value = plane.normal; + normal.variablesReference = parse_variant(plane.normal); + + Array arr; + arr.push_back(d.to_json()); + arr.push_back(normal.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::QUATERNION: { + int id = variable_id++; + Quaternion quat = p_var; + DAP::Variable x, y, z, w; + x.name = "x"; + y.name = "y"; + z.name = "z"; + w.name = "w"; + x.type = y.type = z.type = w.type = Variant::get_type_name(Variant::FLOAT); + x.value = rtos(quat.x); + y.value = rtos(quat.y); + z.value = rtos(quat.z); + w.value = rtos(quat.w); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + arr.push_back(w.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::AABB: { + int id = variable_id++; + AABB aabb = p_var; + DAP::Variable position, size; + position.name = "position"; + size.name = "size"; + position.type = size.type = Variant::get_type_name(Variant::VECTOR3); + position.value = aabb.position; + size.value = aabb.size; + position.variablesReference = parse_variant(aabb.position); + size.variablesReference = parse_variant(aabb.size); + + Array arr; + arr.push_back(position.to_json()); + arr.push_back(size.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::BASIS: { + int id = variable_id++; + Basis basis = p_var; + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = y.type = z.type = Variant::get_type_name(Variant::VECTOR2); + x.value = basis.elements[0]; + y.value = basis.elements[1]; + z.value = basis.elements[2]; + x.variablesReference = parse_variant(basis.elements[0]); + y.variablesReference = parse_variant(basis.elements[1]); + z.variablesReference = parse_variant(basis.elements[2]); + + Array arr; + arr.push_back(x.to_json()); + arr.push_back(y.to_json()); + arr.push_back(z.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::TRANSFORM3D: { + int id = variable_id++; + Transform3D transform = p_var; + DAP::Variable basis, origin; + basis.name = "basis"; + origin.name = "origin"; + basis.type = Variant::get_type_name(Variant::BASIS); + origin.type = Variant::get_type_name(Variant::VECTOR3); + basis.value = transform.basis; + origin.value = transform.origin; + basis.variablesReference = parse_variant(transform.basis); + origin.variablesReference = parse_variant(transform.origin); + + Array arr; + arr.push_back(basis.to_json()); + arr.push_back(origin.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::COLOR: { + int id = variable_id++; + Color color = p_var; + DAP::Variable r, g, b, a; + r.name = "r"; + g.name = "g"; + b.name = "b"; + a.name = "a"; + r.type = g.type = b.type = a.type = Variant::get_type_name(Variant::FLOAT); + r.value = rtos(color.r); + g.value = rtos(color.g); + b.value = rtos(color.b); + a.value = rtos(color.a); + + Array arr; + arr.push_back(r.to_json()); + arr.push_back(g.to_json()); + arr.push_back(b.to_json()); + arr.push_back(a.to_json()); + variable_list.insert(id, arr); + return id; + } + case Variant::ARRAY: { + int id = variable_id++; + Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(array[i].get_type()); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::DICTIONARY: { + int id = variable_id++; + Dictionary dictionary = p_var; + Array arr; + + for (int i = 0; i < dictionary.size(); i++) { + DAP::Variable var; + var.name = dictionary.get_key_at_index(i); + Variant value = dictionary.get_value_at_index(i); + var.type = Variant::get_type_name(value.get_type()); + var.value = value; + var.variablesReference = parse_variant(value); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_BYTE_ARRAY: { + int id = variable_id++; + PackedByteArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "byte"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_INT32_ARRAY: { + int id = variable_id++; + PackedInt32Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "int"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_INT64_ARRAY: { + int id = variable_id++; + PackedInt64Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "long"; + var.value = itos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_FLOAT32_ARRAY: { + int id = variable_id++; + PackedFloat32Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "float"; + var.value = rtos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_FLOAT64_ARRAY: { + int id = variable_id++; + PackedFloat64Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = "double"; + var.value = rtos(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_STRING_ARRAY: { + int id = variable_id++; + PackedStringArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::STRING); + var.value = array[i]; + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_VECTOR2_ARRAY: { + int id = variable_id++; + PackedVector2Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::VECTOR2); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_VECTOR3_ARRAY: { + int id = variable_id++; + PackedVector2Array array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::VECTOR3); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + case Variant::PACKED_COLOR_ARRAY: { + int id = variable_id++; + PackedColorArray array = p_var; + DAP::Variable size; + size.name = "size"; + size.type = Variant::get_type_name(Variant::INT); + size.value = itos(array.size()); + + Array arr; + arr.push_back(size.to_json()); + + for (int i = 0; i < array.size(); i++) { + DAP::Variable var; + var.name = itos(i); + var.type = Variant::get_type_name(Variant::COLOR); + var.value = array[i]; + var.variablesReference = parse_variant(array[i]); + arr.push_back(var.to_json()); + } + variable_list.insert(id, arr); + return id; + } + default: + // Simple atomic stuff, or too complex to be manipulated + return 0; + } +} + +bool DebugAdapterProtocol::process_message(const String &p_text) { + JSON json; + ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Mal-formed message!"); + Dictionary params = json.get_data(); + bool completed = true; + + if (OS::get_singleton()->get_ticks_msec() - _current_peer->timestamp > _request_timeout) { + Dictionary response = parser->prepare_error_response(params, DAP::ErrorType::TIMEOUT); + _current_peer->res_queue.push_front(response); + return true; + } + + // Append "req_" to any command received; prevents name clash with existing functions, and possibly exploiting + String command = "req_" + (String)params["command"]; + if (parser->has_method(command)) { + _current_request = params["command"]; + + Array args; + args.push_back(params); + Dictionary response = parser->callv(command, args); + if (!response.is_empty()) { + _current_peer->res_queue.push_front(response); + } else { + completed = false; + } + } + + reset_current_info(); + return completed; +} + +void DebugAdapterProtocol::notify_initialized() { + Dictionary event = parser->ev_initialized(); + _current_peer->res_queue.push_back(event); +} + +void DebugAdapterProtocol::notify_process() { + String launch_mode = _current_peer->attached ? "attach" : "launch"; + + Dictionary event = parser->ev_process(launch_mode); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_terminated() { + Dictionary event = parser->ev_terminated(); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { + continue; + } + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_exited(const int &p_exitcode) { + Dictionary event = parser->ev_exited(p_exitcode); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { + continue; + } + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_stopped_paused() { + Dictionary event = parser->ev_stopped_paused(); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_stopped_exception(const String &p_error) { + Dictionary event = parser->ev_stopped_exception(p_error); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_stopped_breakpoint(const int &p_id) { + Dictionary event = parser->ev_stopped_breakpoint(p_id); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_stopped_step() { + Dictionary event = parser->ev_stopped_step(); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_continued() { + Dictionary event = parser->ev_continued(); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if (_current_request == "continue" && E->get() == _current_peer) { + continue; + } + E->get()->res_queue.push_back(event); + } + + reset_stack_info(); +} + +void DebugAdapterProtocol::notify_output(const String &p_message) { + Dictionary event = parser->ev_output(p_message); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->res_queue.push_back(event); + } +} + +void DebugAdapterProtocol::notify_custom_data(const String &p_msg, const Array &p_data) { + Dictionary event = parser->ev_custom_data(p_msg, p_data); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + Ref<DAPeer> peer = E->get(); + if (peer->supportsCustomData) { + peer->res_queue.push_back(event); + } + } +} + +void DebugAdapterProtocol::notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) { + Dictionary event = parser->ev_breakpoint(p_breakpoint, p_enabled); + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + if (_current_request == "setBreakpoints" && E->get() == _current_peer) { + continue; + } + E->get()->res_queue.push_back(event); + } +} + +Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array &p_lines) { + Array updated_breakpoints; + + // Add breakpoints + for (int i = 0; i < p_lines.size(); i++) { + EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true); + DAP::Breakpoint breakpoint; + breakpoint.line = p_lines[i]; + breakpoint.source.path = p_path; + + ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array()); + updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json()); + } + + // Remove breakpoints + for (List<DAP::Breakpoint>::Element *E = breakpoint_list.front(); E; E = E->next()) { + DAP::Breakpoint b = E->get(); + if (b.source.path == p_path && !p_lines.has(b.line)) { + EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false); + } + } + + return updated_breakpoints; +} + +void DebugAdapterProtocol::on_debug_paused() { + if (EditorNode::get_singleton()->get_pause_button()->is_pressed()) { + notify_stopped_paused(); + } else { + notify_continued(); + } +} + +void DebugAdapterProtocol::on_debug_stopped() { + notify_exited(); + notify_terminated(); +} + +void DebugAdapterProtocol::on_debug_output(const String &p_message) { + notify_output(p_message); +} + +void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump) { + if (!p_reallydid) { + notify_continued(); + return; + } + + if (p_reason == "Breakpoint") { + if (_stepping) { + notify_stopped_step(); + _stepping = false; + } else { + _processing_breakpoint = true; // Wait for stack_dump to find where the breakpoint happened + } + } else { + notify_stopped_exception(p_reason); + } + + _processing_stackdump = p_has_stackdump; +} + +void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) { + DAP::Breakpoint breakpoint; + breakpoint.verified = true; + breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path); + breakpoint.source.compute_checksums(); + breakpoint.line = p_line; + + if (p_enabled) { + // Add the breakpoint + breakpoint.id = breakpoint_id++; + breakpoint_list.push_back(breakpoint); + } else { + // Remove the breakpoint + List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); + if (E) { + breakpoint.id = E->get().id; + breakpoint_list.erase(E); + } + } + + notify_breakpoint(breakpoint, p_enabled); +} + +void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) { + if (_processing_breakpoint && !p_stack_dump.is_empty()) { + // Find existing breakpoint + Dictionary d = p_stack_dump[0]; + DAP::Breakpoint breakpoint; + breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(d["file"]); + breakpoint.line = d["line"]; + + List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); + if (E) { + notify_stopped_breakpoint(E->get().id); + } + + _processing_breakpoint = false; + } + + stackframe_id = 0; + stackframe_list.clear(); + + // Fill in stacktrace information + for (int i = 0; i < p_stack_dump.size(); i++) { + Dictionary stack_info = p_stack_dump[i]; + DAP::StackFrame stackframe; + stackframe.id = stackframe_id++; + stackframe.name = stack_info["function"]; + stackframe.line = stack_info["line"]; + stackframe.column = 0; + stackframe.source.path = ProjectSettings::get_singleton()->globalize_path(stack_info["file"]); + stackframe.source.compute_checksums(); + + // Information for "Locals", "Members" and "Globals" variables respectively + List<int> scope_ids; + for (int j = 0; j < 3; j++) { + scope_ids.push_back(variable_id++); + } + + stackframe_list.insert(stackframe, scope_ids); + } + + _current_frame = 0; + _processing_stackdump = false; +} + +void DebugAdapterProtocol::on_debug_stack_frame_vars(const int &p_size) { + _remaining_vars = p_size; + DAP::StackFrame frame; + frame.id = _current_frame; + ERR_FAIL_COND(!stackframe_list.has(frame)); + List<int> scope_ids = stackframe_list.find(frame)->value(); + for (List<int>::Element *E = scope_ids.front(); E; E = E->next()) { + int variable_id = E->get(); + if (variable_list.has(variable_id)) { + variable_list.find(variable_id)->value().clear(); + } else { + variable_list.insert(variable_id, Array()); + } + } +} + +void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) { + DebuggerMarshalls::ScriptStackVariable stack_var; + stack_var.deserialize(p_data); + + ERR_FAIL_COND(stackframe_list.is_empty()); + DAP::StackFrame frame; + frame.id = _current_frame; + + List<int> scope_ids = stackframe_list.find(frame)->value(); + ERR_FAIL_COND(scope_ids.size() != 3); + ERR_FAIL_INDEX(stack_var.type, 3); + int variable_id = scope_ids[stack_var.type]; + + DAP::Variable variable; + + variable.name = stack_var.name; + variable.value = stack_var.value; + variable.type = Variant::get_type_name(stack_var.value.get_type()); + variable.variablesReference = parse_variant(stack_var.value); + + variable_list.find(variable_id)->value().push_back(variable.to_json()); + _remaining_vars--; +} + +void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_data) { + // Ignore data that is already handled by DAP + if (p_msg == "debug_enter" || p_msg == "debug_exit" || p_msg == "stack_dump" || p_msg == "stack_frame_vars" || p_msg == "stack_frame_var" || p_msg == "output" || p_msg == "request_quit") { + return; + } + + notify_custom_data(p_msg, p_data); +} + +void DebugAdapterProtocol::poll() { + if (server->is_connection_available()) { + on_client_connected(); + } + List<Ref<DAPeer>> to_delete; + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + Ref<DAPeer> peer = E->get(); + StreamPeerTCP::Status status = peer->connection->get_status(); + if (status == StreamPeerTCP::STATUS_NONE || status == StreamPeerTCP::STATUS_ERROR) { + to_delete.push_back(peer); + } else { + _current_peer = peer; + Error err = peer->handle_data(); + if (err != OK && err != ERR_BUSY) { + to_delete.push_back(peer); + } + err = peer->send_data(); + if (err != OK && err != ERR_BUSY) { + to_delete.push_back(peer); + } + } + } + + for (List<Ref<DAPeer>>::Element *E = to_delete.front(); E; E = E->next()) { + on_client_disconnected(E->get()); + } + to_delete.clear(); +} + +Error DebugAdapterProtocol::start(int p_port, const IPAddress &p_bind_ip) { + _request_timeout = (uint64_t)_EDITOR_GET("network/debug_adapter/request_timeout"); + _sync_breakpoints = (bool)_EDITOR_GET("network/debug_adapter/sync_breakpoints"); + _initialized = true; + return server->listen(p_port, p_bind_ip); +} + +void DebugAdapterProtocol::stop() { + for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) { + E->get()->connection->disconnect_from_host(); + } + + clients.clear(); + server->stop(); + _initialized = false; +} + +DebugAdapterProtocol::DebugAdapterProtocol() { + server.instantiate(); + singleton = this; + parser = memnew(DebugAdapterParser); + + reset_ids(); + + EditorNode *node = EditorNode::get_singleton(); + node->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused)); + + EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton(); + debugger_node->connect("breakpoint_toggled", callable_mp(this, &DebugAdapterProtocol::on_debug_breakpoint_toggled)); + + debugger_node->get_default_debugger()->connect("stopped", callable_mp(this, &DebugAdapterProtocol::on_debug_stopped)); + debugger_node->get_default_debugger()->connect("output", callable_mp(this, &DebugAdapterProtocol::on_debug_output)); + debugger_node->get_default_debugger()->connect("breaked", callable_mp(this, &DebugAdapterProtocol::on_debug_breaked)); + debugger_node->get_default_debugger()->connect("stack_dump", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_dump)); + debugger_node->get_default_debugger()->connect("stack_frame_vars", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_vars)); + debugger_node->get_default_debugger()->connect("stack_frame_var", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_var)); + debugger_node->get_default_debugger()->connect("debug_data", callable_mp(this, &DebugAdapterProtocol::on_debug_data)); +} + +DebugAdapterProtocol::~DebugAdapterProtocol() { + memdelete(parser); +} diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h new file mode 100644 index 0000000000..d4291992bf --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h @@ -0,0 +1,155 @@ +/*************************************************************************/ +/* debug_adapter_protocol.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 DEBUG_ADAPTER_PROTOCOL_H +#define DEBUG_ADAPTER_PROTOCOL_H + +#include "core/io/stream_peer.h" +#include "core/io/stream_peer_tcp.h" +#include "core/io/tcp_server.h" + +#include "debug_adapter_parser.h" +#include "debug_adapter_types.h" + +#define DAP_MAX_BUFFER_SIZE 4194304 // 4MB +#define DAP_MAX_CLIENTS 8 + +class DebugAdapterParser; + +struct DAPeer : RefCounted { + Ref<StreamPeerTCP> connection; + + uint8_t req_buf[DAP_MAX_BUFFER_SIZE]; + int req_pos = 0; + bool has_header = false; + int content_length = 0; + List<Dictionary> res_queue; + int seq = 0; + uint64_t timestamp = 0; + + // Client specific info + bool linesStartAt1 = false; + bool columnsStartAt1 = false; + bool supportsVariableType = false; + bool supportsInvalidatedEvent = false; + bool supportsCustomData = false; + + // Internal client info + bool attached = false; + + Error handle_data(); + Error send_data(); + String format_output(const Dictionary &p_params) const; +}; + +class DebugAdapterProtocol : public Object { + GDCLASS(DebugAdapterProtocol, Object) + + friend class DebugAdapterParser; + +private: + static DebugAdapterProtocol *singleton; + DebugAdapterParser *parser; + + List<Ref<DAPeer>> clients; + Ref<TCPServer> server; + + Error on_client_connected(); + void on_client_disconnected(const Ref<DAPeer> &p_peer); + void on_debug_paused(); + void on_debug_stopped(); + void on_debug_output(const String &p_message); + void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump); + void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled); + void on_debug_stack_dump(const Array &p_stack_dump); + void on_debug_stack_frame_vars(const int &p_size); + void on_debug_stack_frame_var(const Array &p_data); + void on_debug_data(const String &p_msg, const Array &p_data); + + void reset_current_info(); + void reset_ids(); + void reset_stack_info(); + + int parse_variant(const Variant &p_var); + + bool _initialized = false; + bool _processing_breakpoint = false; + bool _stepping = false; + bool _processing_stackdump = false; + int _remaining_vars = 0; + int _current_frame = 0; + uint64_t _request_timeout = 1000; + bool _sync_breakpoints = false; + + String _current_request; + Ref<DAPeer> _current_peer; + + int breakpoint_id; + int stackframe_id; + int variable_id; + List<DAP::Breakpoint> breakpoint_list; + Map<DAP::StackFrame, List<int>> stackframe_list; + Map<int, Array> variable_list; + +public: + friend class DebugAdapterServer; + + _FORCE_INLINE_ static DebugAdapterProtocol *get_singleton() { return singleton; } + _FORCE_INLINE_ bool is_active() const { return _initialized && clients.size() > 0; } + + bool process_message(const String &p_text); + + String get_current_request() const { return _current_request; } + Ref<DAPeer> get_current_peer() const { return _current_peer; } + + void notify_initialized(); + void notify_process(); + void notify_terminated(); + void notify_exited(const int &p_exitcode = 0); + void notify_stopped_paused(); + void notify_stopped_exception(const String &p_error); + void notify_stopped_breakpoint(const int &p_id); + void notify_stopped_step(); + void notify_continued(); + void notify_output(const String &p_message); + void notify_custom_data(const String &p_msg, const Array &p_data); + void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled); + + Array update_breakpoints(const String &p_path, const Array &p_lines); + + void poll(); + Error start(int p_port, const IPAddress &p_bind_ip); + void stop(); + + DebugAdapterProtocol(); + ~DebugAdapterProtocol(); +}; + +#endif diff --git a/editor/debugger/debug_adapter/debug_adapter_server.cpp b/editor/debugger/debug_adapter/debug_adapter_server.cpp new file mode 100644 index 0000000000..4775e2c8b0 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_server.cpp @@ -0,0 +1,85 @@ +/*************************************************************************/ +/* debug_adapter_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "debug_adapter_server.h" + +#include "core/os/os.h" +#include "editor/editor_log.h" +#include "editor/editor_node.h" + +DebugAdapterServer::DebugAdapterServer() { + _EDITOR_DEF("network/debug_adapter/remote_port", remote_port); + _EDITOR_DEF("network/debug_adapter/request_timeout", protocol._request_timeout); + _EDITOR_DEF("network/debug_adapter/sync_breakpoints", protocol._sync_breakpoints); +} + +void DebugAdapterServer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + start(); + break; + case NOTIFICATION_EXIT_TREE: + stop(); + break; + case NOTIFICATION_INTERNAL_PROCESS: { + // The main loop can be run again during request processing, which modifies internal state of the protocol. + // Thus, "polling" is needed to prevent it from parsing other requests while the current one isn't finished. + if (started && !polling) { + polling = true; + protocol.poll(); + polling = false; + } + } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + protocol._request_timeout = EditorSettings::get_singleton()->get("network/debug_adapter/request_timeout"); + protocol._sync_breakpoints = EditorSettings::get_singleton()->get("network/debug_adapter/sync_breakpoints"); + int remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); + if (remote_port != this->remote_port) { + this->stop(); + this->start(); + } + } break; + } +} + +void DebugAdapterServer::start() { + remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); + if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) { + EditorNode::get_log()->add_message("--- Debug adapter server started ---", EditorLog::MSG_TYPE_EDITOR); + set_process_internal(true); + started = true; + } +} + +void DebugAdapterServer::stop() { + protocol.stop(); + started = false; + EditorNode::get_log()->add_message("--- Debug adapter server stopped ---", EditorLog::MSG_TYPE_EDITOR); +} diff --git a/editor/debugger/debug_adapter/debug_adapter_server.h b/editor/debugger/debug_adapter/debug_adapter_server.h new file mode 100644 index 0000000000..c449403cc2 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_server.h @@ -0,0 +1,57 @@ +/*************************************************************************/ +/* debug_adapter_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 DEBUG_ADAPTER_SERVER_H +#define DEBUG_ADAPTER_SERVER_H + +#include "debug_adapter_protocol.h" +#include "editor/editor_plugin.h" + +class DebugAdapterServer : public EditorPlugin { + GDCLASS(DebugAdapterServer, EditorPlugin); + + DebugAdapterProtocol protocol; + + int remote_port = 6006; + bool thread_running = false; + bool started = false; + bool polling = false; + static void thread_func(void *p_userdata); + +private: + void _notification(int p_what); + +public: + DebugAdapterServer(); + void start(); + void stop(); +}; + +#endif diff --git a/editor/debugger/debug_adapter/debug_adapter_types.h b/editor/debugger/debug_adapter/debug_adapter_types.h new file mode 100644 index 0000000000..5156c91d14 --- /dev/null +++ b/editor/debugger/debug_adapter/debug_adapter_types.h @@ -0,0 +1,278 @@ +/*************************************************************************/ +/* debug_adapter_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 DEBUG_ADAPTER_TYPES_H +#define DEBUG_ADAPTER_TYPES_H + +#include "core/io/json.h" +#include "core/variant/dictionary.h" + +namespace DAP { + +enum ErrorType { + UNKNOWN, + WRONG_PATH, + NOT_RUNNING, + TIMEOUT, + UNKNOWN_PLATFORM, + MISSING_DEVICE +}; + +struct Checksum { + String algorithm; + String checksum; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["algorithm"] = algorithm; + dict["checksum"] = checksum; + + return dict; + } +}; + +struct Source { +private: + Array _checksums; + +public: + String name; + String path; + + void compute_checksums() { + ERR_FAIL_COND(path.is_empty()); + + // MD5 + Checksum md5; + md5.algorithm = "MD5"; + md5.checksum = FileAccess::get_md5(path); + + // SHA-256 + Checksum sha256; + sha256.algorithm = "SHA256"; + sha256.checksum = FileAccess::get_sha256(path); + + _checksums.push_back(md5.to_json()); + _checksums.push_back(sha256.to_json()); + } + + _FORCE_INLINE_ void from_json(const Dictionary &p_params) { + name = p_params["name"]; + path = p_params["path"]; + _checksums = p_params["checksums"]; + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["path"] = path; + dict["checksums"] = _checksums; + + return dict; + } +}; + +struct Breakpoint { + int id; + bool verified; + Source source; + int line; + + bool operator==(const Breakpoint &p_other) const { + return source.path == p_other.source.path && line == p_other.line; + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["id"] = id; + dict["verified"] = verified; + dict["source"] = source.to_json(); + dict["line"] = line; + + return dict; + } +}; + +struct BreakpointLocation { + int line; + int endLine = -1; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["line"] = line; + if (endLine >= 0) { + dict["endLine"] = endLine; + } + + return dict; + } +}; + +struct Capabilities { + bool supportsConfigurationDoneRequest = true; + bool supportsEvaluateForHovers = true; + bool supportsSetVariable = true; + String supportedChecksumAlgorithms[2] = { "MD5", "SHA256" }; + bool supportsRestartRequest = true; + bool supportsValueFormattingOptions = true; + bool supportTerminateDebuggee = true; + bool supportSuspendDebuggee = true; + bool supportsTerminateRequest = true; + bool supportsBreakpointLocationsRequest = true; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["supportsConfigurationDoneRequest"] = supportsConfigurationDoneRequest; + dict["supportsEvaluateForHovers"] = supportsEvaluateForHovers; + dict["supportsSetVariable"] = supportsSetVariable; + dict["supportsRestartRequest"] = supportsRestartRequest; + dict["supportsValueFormattingOptions"] = supportsValueFormattingOptions; + dict["supportTerminateDebuggee"] = supportTerminateDebuggee; + dict["supportSuspendDebuggee"] = supportSuspendDebuggee; + dict["supportsTerminateRequest"] = supportsTerminateRequest; + dict["supportsBreakpointLocationsRequest"] = supportsBreakpointLocationsRequest; + + Array arr; + arr.push_back(supportedChecksumAlgorithms[0]); + arr.push_back(supportedChecksumAlgorithms[1]); + dict["supportedChecksumAlgorithms"] = arr; + + return dict; + } +}; + +struct Message { + int id; + String format; + bool sendTelemetry = false; // Just in case :) + bool showUser; + Dictionary variables; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["id"] = id; + dict["format"] = format; + dict["sendTelemetry"] = sendTelemetry; + dict["showUser"] = showUser; + dict["variables"] = variables; + + return dict; + } +}; + +struct Scope { + String name; + String presentationHint; + int variablesReference; + bool expensive; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["presentationHint"] = presentationHint; + dict["variablesReference"] = variablesReference; + dict["expensive"] = expensive; + + return dict; + } +}; + +struct SourceBreakpoint { + int line; + + _FORCE_INLINE_ void from_json(const Dictionary &p_params) { + line = p_params["line"]; + } +}; + +struct StackFrame { + int id; + String name; + Source source; + int line; + int column; + + bool operator<(const StackFrame &p_other) const { + return id < p_other.id; + } + + _FORCE_INLINE_ void from_json(const Dictionary &p_params) { + id = p_params["id"]; + name = p_params["name"]; + source.from_json(p_params["source"]); + line = p_params["line"]; + column = p_params["column"]; + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["id"] = id; + dict["name"] = name; + dict["source"] = source.to_json(); + dict["line"] = line; + dict["column"] = column; + + return dict; + } +}; + +struct Thread { + int id; + String name; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["id"] = id; + dict["name"] = name; + + return dict; + } +}; + +struct Variable { + String name; + String value; + String type; + int variablesReference = 0; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + dict["name"] = name; + dict["value"] = value; + dict["type"] = type; + dict["variablesReference"] = variablesReference; + + return dict; + } +}; + +} // namespace DAP + +#endif diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index 6035cc072e..e53f66e72e 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -41,7 +41,7 @@ bool EditorDebuggerRemoteObject::_set(const StringName &p_name, const Variant &p } prop_values[p_name] = p_value; - emit_signal("value_edited", remote_object_id, p_name, p_value); + emit_signal(SNAME("value_edited"), remote_object_id, p_name, p_value); return true; } @@ -56,8 +56,8 @@ bool EditorDebuggerRemoteObject::_get(const StringName &p_name, Variant &r_ret) void EditorDebuggerRemoteObject::_get_property_list(List<PropertyInfo> *p_list) const { p_list->clear(); //sorry, no want category - for (const List<PropertyInfo>::Element *E = prop_list.front(); E; E = E->next()) { - p_list->push_back(E->get()); + for (const PropertyInfo &E : prop_list) { + p_list->push_back(E); } } @@ -114,11 +114,11 @@ void EditorDebuggerInspector::_notification(int p_what) { } void EditorDebuggerInspector::_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) { - emit_signal("object_edited", p_id, p_prop, p_value); + emit_signal(SNAME("object_edited"), p_id, p_prop, p_value); } void EditorDebuggerInspector::_object_selected(ObjectID p_object) { - emit_signal("object_selected", p_object); + emit_signal(SNAME("object_selected"), p_object); } ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) { @@ -190,7 +190,7 @@ ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) { if (old_prop_size == debugObj->prop_list.size() && new_props_added == 0) { //only some may have changed, if so, then update those, if exist for (Set<String>::Element *E = changed.front(); E; E = E->next()) { - emit_signal("object_property_updated", debugObj->remote_object_id, E->get()); + emit_signal(SNAME("object_property_updated"), debugObj->remote_object_id, E->get()); } } else { //full update, because props were added or removed @@ -200,12 +200,12 @@ ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) { } void EditorDebuggerInspector::clear_cache() { - for (Map<ObjectID, EditorDebuggerRemoteObject *>::Element *E = remote_objects.front(); E; E = E->next()) { + for (const KeyValue<ObjectID, EditorDebuggerRemoteObject *> &E : remote_objects) { EditorNode *editor = EditorNode::get_singleton(); - if (editor->get_editor_history()->get_current() == E->value()->get_instance_id()) { + if (editor->get_editor_history()->get_current() == E.value->get_instance_id()) { editor->push_item(nullptr); } - memdelete(E->value()); + memdelete(E.value); } remote_objects.clear(); remote_dependencies.clear(); diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index ded0ee3aa7..391839d639 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -55,8 +55,8 @@ EditorDebuggerNode::EditorDebuggerNode() { singleton = this; } - add_theme_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(SIDE_LEFT)); - add_theme_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(SIDE_RIGHT)); + add_theme_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), SNAME("EditorStyles"))->get_margin(SIDE_LEFT)); + add_theme_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), SNAME("EditorStyles"))->get_margin(SIDE_RIGHT)); tabs = memnew(TabContainer); tabs->set_tab_align(TabContainer::ALIGN_LEFT); @@ -65,7 +65,7 @@ EditorDebuggerNode::EditorDebuggerNode() { add_child(tabs); Ref<StyleBoxEmpty> empty; - empty.instance(); + empty.instantiate(); tabs->add_theme_style_override("panel", empty); auto_switch_remote_scene_tree = EDITOR_DEF("debugger/auto_switch_to_remote_scene_tree", false); @@ -112,7 +112,7 @@ ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() { if (tabs->get_tab_count() > 1) { node->clear_style(); tabs->set_tabs_visible(true); - tabs->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("DebuggerPanel", "EditorStyles")); + tabs->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("DebuggerPanel"), SNAME("EditorStyles"))); } if (!debugger_plugins.is_empty()) { @@ -135,7 +135,7 @@ void EditorDebuggerNode::_stack_frame_selected(int p_debugger) { void EditorDebuggerNode::_error_selected(const String &p_file, int p_line, int p_debugger) { Ref<Script> s = ResourceLoader::load(p_file); - emit_signal("goto_script_line", s, p_line - 1); + emit_signal(SNAME("goto_script_line"), s, p_line - 1); } void EditorDebuggerNode::_text_editor_stack_goto(const ScriptEditorDebugger *p_debugger) { @@ -145,8 +145,8 @@ void EditorDebuggerNode::_text_editor_stack_goto(const ScriptEditorDebugger *p_d } stack_script = ResourceLoader::load(file); const int line = p_debugger->get_stack_script_line() - 1; - emit_signal("goto_script_line", stack_script, line); - emit_signal("set_execution", stack_script, line); + emit_signal(SNAME("goto_script_line"), stack_script, line); + emit_signal(SNAME("set_execution"), stack_script, line); stack_script.unref(); // Why?!? } @@ -164,6 +164,7 @@ void EditorDebuggerNode::_bind_methods() { ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script"))); ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"))); + ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled"))); } EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() { @@ -182,16 +183,16 @@ ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const { return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(0)); } -Error EditorDebuggerNode::start(const String &p_protocol) { +Error EditorDebuggerNode::start(const String &p_uri) { stop(); + ERR_FAIL_COND_V(p_uri.find("://") < 0, ERR_INVALID_PARAMETER); if (EDITOR_GET("run/output/always_open_output_on_play")) { EditorNode::get_singleton()->make_bottom_panel_item_visible(EditorNode::get_log()); } else { EditorNode::get_singleton()->make_bottom_panel_item_visible(this); } - - server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create(p_protocol)); - const Error err = server->start(); + server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create(p_uri.substr(0, p_uri.find("://") + 3))); + const Error err = server->start(p_uri); if (err != OK) { return err; } @@ -226,10 +227,10 @@ void EditorDebuggerNode::_notification(int p_what) { switch (p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { if (tabs->get_tab_count() > 1) { - add_theme_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(SIDE_LEFT)); - add_theme_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(SIDE_RIGHT)); + add_theme_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), SNAME("EditorStyles"))->get_margin(SIDE_LEFT)); + add_theme_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), SNAME("EditorStyles"))->get_margin(SIDE_RIGHT)); - tabs->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox("DebuggerPanel", "EditorStyles")); + tabs->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("DebuggerPanel"), SNAME("EditorStyles"))); } } break; case NOTIFICATION_READY: { @@ -264,15 +265,20 @@ void EditorDebuggerNode::_notification(int p_what) { if (error_count == 0 && warning_count == 0) { debugger_button->set_text(TTR("Debugger")); + debugger_button->remove_theme_color_override("font_color"); debugger_button->set_icon(Ref<Texture2D>()); } else { debugger_button->set_text(TTR("Debugger") + " (" + itos(error_count + warning_count) + ")"); if (error_count >= 1 && warning_count >= 1) { - debugger_button->set_icon(get_theme_icon("ErrorWarning", "EditorIcons")); + debugger_button->set_icon(get_theme_icon(SNAME("ErrorWarning"), SNAME("EditorIcons"))); + // Use error color to represent the highest level of severity reported. + debugger_button->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); } else if (error_count >= 1) { - debugger_button->set_icon(get_theme_icon("Error", "EditorIcons")); + debugger_button->set_icon(get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); + debugger_button->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); } else { - debugger_button->set_icon(get_theme_icon("Warning", "EditorIcons")); + debugger_button->set_icon(get_theme_icon(SNAME("Warning"), SNAME("EditorIcons"))); + debugger_button->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); } } last_error_count = error_count; @@ -327,9 +333,9 @@ void EditorDebuggerNode::_notification(int p_what) { debugger->set_editor_remote_tree(remote_scene_tree); debugger->start(server->take_connection()); // Send breakpoints. - for (Map<Breakpoint, bool>::Element *E = breakpoints.front(); E; E = E->next()) { - const Breakpoint &bp = E->key(); - debugger->set_breakpoint(bp.source, bp.line, E->get()); + for (const KeyValue<Breakpoint, bool> &E : breakpoints) { + const Breakpoint &bp = E.key; + debugger->set_breakpoint(bp.source, bp.line, E.value); } // Will arrive too late, how does the regular run work? debugger->update_live_edit_root(); @@ -359,7 +365,7 @@ void EditorDebuggerNode::_debugger_wants_stop(int p_id) { // Ask editor to kill PID. int pid = get_debugger(p_id)->get_remote_pid(); if (pid) { - EditorNode::get_singleton()->call_deferred("stop_child_process", pid); + EditorNode::get_singleton()->call_deferred(SNAME("stop_child_process"), pid); } } @@ -466,7 +472,7 @@ void EditorDebuggerNode::_paused() { }); } -void EditorDebuggerNode::_breaked(bool p_breaked, bool p_can_debug, int p_debugger) { +void EditorDebuggerNode::_breaked(bool p_breaked, bool p_can_debug, String p_message, bool p_has_stackdump, int p_debugger) { if (get_current_debugger() != get_debugger(p_debugger)) { if (!p_breaked) { return; @@ -475,7 +481,7 @@ void EditorDebuggerNode::_breaked(bool p_breaked, bool p_can_debug, int p_debugg } _break_state_changed(); EditorNode::get_singleton()->get_pause_button()->set_pressed(p_breaked); - emit_signal("breaked", p_breaked, p_can_debug); + emit_signal(SNAME("breaked"), p_breaked, p_can_debug); } bool EditorDebuggerNode::is_skip_breakpoints() const { @@ -487,6 +493,21 @@ void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p _for_all(tabs, [&](ScriptEditorDebugger *dbg) { dbg->set_breakpoint(p_path, p_line, p_enabled); }); + + emit_signal("breakpoint_toggled", p_path, p_line, p_enabled); +} + +void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) { + for (int i = 0; i < p_lines.size(); i++) { + set_breakpoint(p_path, p_lines[i], true); + } + + for (const KeyValue<Breakpoint, bool> &E : breakpoints) { + Breakpoint b = E.key; + if (b.source == p_path && !p_lines.has(b.line)) { + set_breakpoint(p_path, b.line, false); + } + } } void EditorDebuggerNode::reload_scripts() { @@ -642,6 +663,17 @@ void EditorDebuggerNode::live_debug_reparent_node(const NodePath &p_at, const No }); } +void EditorDebuggerNode::set_camera_override(CameraOverride p_override) { + _for_all(tabs, [&](ScriptEditorDebugger *dbg) { + dbg->set_camera_override(p_override); + }); + camera_override = p_override; +} + +EditorDebuggerNode::CameraOverride EditorDebuggerNode::get_camera_override() { + return camera_override; +} + void EditorDebuggerNode::add_debugger_plugin(const Ref<Script> &p_script) { ERR_FAIL_COND_MSG(debugger_plugins.has(p_script), "Debugger plugin already exists."); ERR_FAIL_COND_MSG(p_script.is_null(), "Debugger plugin script is null"); diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h index 3510ac0726..4d9e846834 100644 --- a/editor/debugger/editor_debugger_node.h +++ b/editor/debugger/editor_debugger_node.h @@ -35,6 +35,7 @@ #include "scene/gui/margin_container.h" class Button; +class DebugAdapterParser; class EditorDebuggerTree; class EditorDebuggerRemoteObject; class MenuButton; @@ -109,6 +110,7 @@ private: EditorDebuggerRemoteObject *get_inspected_remote_object(); friend class DebuggerEditorPlugin; + friend class DebugAdapterParser; static EditorDebuggerNode *singleton; EditorDebuggerNode(); @@ -123,13 +125,13 @@ protected: void _save_node_requested(ObjectID p_id, const String &p_file, int p_debugger); void _clear_execution(REF p_script) { - emit_signal("clear_execution", p_script); + emit_signal(SNAME("clear_execution"), p_script); } void _text_editor_stack_goto(const ScriptEditorDebugger *p_debugger); void _stack_frame_selected(int p_debugger); void _error_selected(const String &p_file, int p_line, int p_debugger); - void _breaked(bool p_breaked, bool p_can_debug, int p_debugger); + void _breaked(bool p_breaked, bool p_can_debug, String p_message, bool p_has_stackdump, int p_debugger); void _paused(); void _break_state_changed(); void _menu_option(int p_id); @@ -164,6 +166,7 @@ public: bool is_skip_breakpoints() const; void set_breakpoint(const String &p_path, int p_line, bool p_enabled); + void set_breakpoints(const String &p_path, Array p_lines); void reload_scripts(); // Remote inspector/edit. @@ -182,11 +185,10 @@ public: void live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name); void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos); - // Camera - void set_camera_override(CameraOverride p_override) { camera_override = p_override; } - CameraOverride get_camera_override() { return camera_override; } + void set_camera_override(CameraOverride p_override); + CameraOverride get_camera_override(); - Error start(const String &p_protocol = "tcp://"); + Error start(const String &p_uri = "tcp://"); void stop(); diff --git a/editor/debugger/editor_debugger_server.cpp b/editor/debugger/editor_debugger_server.cpp index 662f247062..8c3833af50 100644 --- a/editor/debugger/editor_debugger_server.cpp +++ b/editor/debugger/editor_debugger_server.cpp @@ -45,7 +45,7 @@ private: public: static EditorDebuggerServer *create(const String &p_protocol); virtual void poll() {} - virtual Error start(); + virtual Error start(const String &p_uri); virtual void stop(); virtual bool is_active() const; virtual bool is_connection_available() const; @@ -60,14 +60,21 @@ EditorDebuggerServer *EditorDebuggerServerTCP::create(const String &p_protocol) } EditorDebuggerServerTCP::EditorDebuggerServerTCP() { - server.instance(); + server.instantiate(); } -Error EditorDebuggerServerTCP::start() { - int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); - const Error err = server->listen(remote_port); +Error EditorDebuggerServerTCP::start(const String &p_uri) { + int bind_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); + String bind_host = (String)EditorSettings::get_singleton()->get("network/debug/remote_host"); + if (!p_uri.is_empty() && p_uri != "tcp://") { + String scheme, path; + Error err = p_uri.parse_url(scheme, bind_host, bind_port, path); + ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER); + } + const Error err = server->listen(bind_port, bind_host); if (err != OK) { - EditorNode::get_log()->add_message(String("Error listening on port ") + itos(remote_port), EditorLog::MSG_TYPE_ERROR); + EditorNode::get_log()->add_message(String("Error listening on port ") + itos(bind_port), EditorLog::MSG_TYPE_ERROR); return err; } return err; diff --git a/editor/debugger/editor_debugger_server.h b/editor/debugger/editor_debugger_server.h index 6458421e7a..844d1a9e5a 100644 --- a/editor/debugger/editor_debugger_server.h +++ b/editor/debugger/editor_debugger_server.h @@ -32,9 +32,9 @@ #define EDITOR_DEBUGGER_CONNECTION_H #include "core/debugger/remote_debugger_peer.h" -#include "core/object/reference.h" +#include "core/object/ref_counted.h" -class EditorDebuggerServer : public Reference { +class EditorDebuggerServer : public RefCounted { public: typedef EditorDebuggerServer *(*CreateServerFunc)(const String &p_uri); @@ -48,7 +48,7 @@ public: static void register_protocol_handler(const String &p_protocol, CreateServerFunc p_func); static EditorDebuggerServer *create(const String &p_protocol); virtual void poll() = 0; - virtual Error start() = 0; + virtual Error start(const String &p_uri = "") = 0; virtual void stop() = 0; virtual bool is_active() const = 0; virtual bool is_connection_available() const = 0; diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp index ec92edc795..1feab98948 100644 --- a/editor/debugger/editor_debugger_tree.cpp +++ b/editor/debugger/editor_debugger_tree.cpp @@ -75,7 +75,7 @@ void EditorDebuggerTree::_scene_tree_selected() { inspected_object_id = uint64_t(item->get_metadata(0)); - emit_signal("object_selected", inspected_object_id, debugger_id); + emit_signal(SNAME("object_selected"), inspected_object_id, debugger_id); } void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) { @@ -105,8 +105,8 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position) { item->select(0); item_menu->clear(); - item_menu->add_icon_item(get_theme_icon("CreateNewSceneFrom", "EditorIcons"), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE); - item_menu->add_icon_item(get_theme_icon("CopyNodePath", "EditorIcons"), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH); + item_menu->add_icon_item(get_theme_icon(SNAME("CreateNewSceneFrom"), SNAME("EditorIcons")), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE); + item_menu->add_icon_item(get_theme_icon(SNAME("CopyNodePath"), SNAME("EditorIcons")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH); item_menu->set_position(get_screen_transform().xform(get_local_mouse_position())); item_menu->popup(); } @@ -211,7 +211,7 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int } debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree if (scroll_item) { - call_deferred("scroll_to_item", scroll_item); + call_deferred(SNAME("scroll_to_item"), scroll_item); } last_filter = filter; updating_scene_tree = false; @@ -279,5 +279,5 @@ void EditorDebuggerTree::_file_selected(const String &p_file) { if (inspected_object_id.is_null()) { return; } - emit_signal("save_node", inspected_object_id, p_file, debugger_id); + emit_signal(SNAME("save_node"), inspected_object_id, p_file, debugger_id); } diff --git a/editor/debugger/editor_network_profiler.cpp b/editor/debugger/editor_network_profiler.cpp index 2d57dff69d..d4385630be 100644 --- a/editor/debugger/editor_network_profiler.cpp +++ b/editor/debugger/editor_network_profiler.cpp @@ -40,14 +40,14 @@ void EditorNetworkProfiler::_bind_methods() { void EditorNetworkProfiler::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - activate->set_icon(get_theme_icon("Play", "EditorIcons")); - clear_button->set_icon(get_theme_icon("Clear", "EditorIcons")); - incoming_bandwidth_text->set_right_icon(get_theme_icon("ArrowDown", "EditorIcons")); - outgoing_bandwidth_text->set_right_icon(get_theme_icon("ArrowUp", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"))); + incoming_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons"))); + outgoing_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons"))); // This needs to be done here to set the faded color when the profiler is first opened - incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color("font_color", "Editor") * Color(1, 1, 1, 0.5)); - outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color("font_color", "Editor") * Color(1, 1, 1, 0.5)); + incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5)); + outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5)); } } @@ -56,30 +56,30 @@ void EditorNetworkProfiler::_update_frame() { TreeItem *root = counters_display->create_item(); - for (Map<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo>::Element *E = nodes_data.front(); E; E = E->next()) { + for (const KeyValue<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> &E : nodes_data) { TreeItem *node = counters_display->create_item(root); for (int j = 0; j < counters_display->get_columns(); ++j) { node->set_text_align(j, j > 0 ? TreeItem::ALIGN_RIGHT : TreeItem::ALIGN_LEFT); } - node->set_text(0, E->get().node_path); - node->set_text(1, E->get().incoming_rpc == 0 ? "-" : itos(E->get().incoming_rpc)); - node->set_text(2, E->get().incoming_rset == 0 ? "-" : itos(E->get().incoming_rset)); - node->set_text(3, E->get().outgoing_rpc == 0 ? "-" : itos(E->get().outgoing_rpc)); - node->set_text(4, E->get().outgoing_rset == 0 ? "-" : itos(E->get().outgoing_rset)); + node->set_text(0, E.value.node_path); + node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc)); + node->set_text(2, E.value.incoming_rset == 0 ? "-" : itos(E.value.incoming_rset)); + node->set_text(3, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc)); + node->set_text(4, E.value.outgoing_rset == 0 ? "-" : itos(E.value.outgoing_rset)); } } void EditorNetworkProfiler::_activate_pressed() { if (activate->is_pressed()) { - activate->set_icon(get_theme_icon("Stop", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); activate->set_text(TTR("Stop")); } else { - activate->set_icon(get_theme_icon("Play", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); activate->set_text(TTR("Start")); } - emit_signal("enable_profiling", activate->is_pressed()); + emit_signal(SNAME("enable_profiling"), activate->is_pressed()); } void EditorNetworkProfiler::_clear_pressed() { @@ -114,10 +114,10 @@ void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) { // Make labels more prominent when the bandwidth is greater than 0 to attract user attention incoming_bandwidth_text->add_theme_color_override( "font_uneditable_color", - get_theme_color("font_color", "Editor") * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5)); + get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5)); outgoing_bandwidth_text->add_theme_color_override( "font_uneditable_color", - get_theme_color("font_color", "Editor") * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5)); + get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5)); } bool EditorNetworkProfiler::is_profiling() { @@ -178,19 +178,24 @@ EditorNetworkProfiler::EditorNetworkProfiler() { counters_display->set_column_titles_visible(true); counters_display->set_column_title(0, TTR("Node")); counters_display->set_column_expand(0, true); - counters_display->set_column_min_width(0, 60 * EDSCALE); + counters_display->set_column_clip_content(0, true); + counters_display->set_column_custom_minimum_width(0, 60 * EDSCALE); counters_display->set_column_title(1, TTR("Incoming RPC")); counters_display->set_column_expand(1, false); - counters_display->set_column_min_width(1, 120 * EDSCALE); + counters_display->set_column_clip_content(1, true); + counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE); counters_display->set_column_title(2, TTR("Incoming RSET")); counters_display->set_column_expand(2, false); - counters_display->set_column_min_width(2, 120 * EDSCALE); + counters_display->set_column_clip_content(2, true); + counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE); counters_display->set_column_title(3, TTR("Outgoing RPC")); counters_display->set_column_expand(3, false); - counters_display->set_column_min_width(3, 120 * EDSCALE); + counters_display->set_column_clip_content(3, true); + counters_display->set_column_custom_minimum_width(3, 120 * EDSCALE); counters_display->set_column_title(4, TTR("Outgoing RSET")); counters_display->set_column_expand(4, false); - counters_display->set_column_min_width(4, 120 * EDSCALE); + counters_display->set_column_clip_content(4, true); + counters_display->set_column_custom_minimum_width(4, 120 * EDSCALE); add_child(counters_display); frame_delay = memnew(Timer); diff --git a/editor/debugger/editor_performance_profiler.cpp b/editor/debugger/editor_performance_profiler.cpp index fc0104c07a..952f46e9a5 100644 --- a/editor/debugger/editor_performance_profiler.cpp +++ b/editor/debugger/editor_performance_profiler.cpp @@ -109,9 +109,9 @@ void EditorPerformanceProfiler::_monitor_draw() { info_message->hide(); - Ref<StyleBox> graph_style_box = get_theme_stylebox("normal", "TextEdit"); - Ref<Font> graph_font = get_theme_font("font", "TextEdit"); - int font_size = get_theme_font_size("font_size", "TextEdit"); + Ref<StyleBox> graph_style_box = get_theme_stylebox(SNAME("normal"), SNAME("TextEdit")); + Ref<Font> graph_font = get_theme_font(SNAME("font"), SNAME("TextEdit")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("TextEdit")); int columns = int(Math::ceil(Math::sqrt(float(active.size())))); int rows = int(Math::ceil(float(active.size()) / float(columns))); @@ -130,7 +130,7 @@ void EditorPerformanceProfiler::_monitor_draw() { rect.position += graph_style_box->get_offset(); rect.size -= graph_style_box->get_minimum_size(); - Color draw_color = get_theme_color("accent_color", "Editor"); + Color draw_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); draw_color.set_hsv(Math::fmod(hue_shift * float(current.frame_index), 0.9f), draw_color.get_s() * 0.9f, draw_color.get_v() * value_multiplier, 0.6f); monitor_draw->draw_string(graph_font, rect.position + Point2(0, graph_font->get_ascent(font_size)), current.item->get_text(0), HALIGN_LEFT, rect.size.x, font_size, draw_color); @@ -249,7 +249,7 @@ TreeItem *EditorPerformanceProfiler::_create_monitor_item(const StringName &p_mo void EditorPerformanceProfiler::_marker_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { Vector<StringName> active; for (OrderedHashMap<StringName, Monitor>::Element i = monitors.front(); i; i = i.next()) { if (i.value().item->is_checked(0)) { @@ -271,7 +271,7 @@ void EditorPerformanceProfiler::_marker_input(const Ref<InputEvent> &p_event) { } else { marker_key = ""; } - Ref<StyleBox> graph_style_box = get_theme_stylebox("normal", "TextEdit"); + Ref<StyleBox> graph_style_box = get_theme_stylebox(SNAME("normal"), SNAME("TextEdit")); rect.position += graph_style_box->get_offset(); rect.size -= graph_style_box->get_minimum_size(); Vector2 point = mb->get_position() - rect.position; @@ -380,7 +380,7 @@ EditorPerformanceProfiler::EditorPerformanceProfiler() { info_message->set_text(TTR("Pick one or more items from the list to display the graph.")); info_message->set_valign(Label::VALIGN_CENTER); info_message->set_align(Label::ALIGN_CENTER); - info_message->set_autowrap(true); + info_message->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); info_message->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); monitor_draw->add_child(info_message); diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index 6befee090b..d08ae1de8a 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -135,11 +135,11 @@ String EditorProfiler::_get_time_as_text(const Metric &m, float p_time, int p_ca } Color EditorProfiler::_get_color_from_signature(const StringName &p_signature) const { - Color bc = get_theme_color("error_color", "Editor"); + Color bc = get_theme_color(SNAME("error_color"), SNAME("Editor")); double rot = ABS(double(p_signature.hash()) / double(0x7FFFFFFF)); Color c; c.set_hsv(rot, bc.get_s(), bc.get_v()); - return c.lerp(get_theme_color("base_color", "Editor"), 0.07); + return c.lerp(get_theme_color(SNAME("base_color"), SNAME("Editor")), 0.07); } void EditorProfiler::_item_edited() { @@ -180,7 +180,7 @@ void EditorProfiler::_update_plot() { } uint8_t *wr = graph_image.ptrw(); - const Color background_color = get_theme_color("dark_color_2", "Editor"); + const Color background_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); // Clear the previous frame and set the background color. for (int i = 0; i < desired_len; i += 4) { @@ -305,17 +305,17 @@ void EditorProfiler::_update_plot() { } Ref<Image> img; - img.instance(); + img.instantiate(); img->create(w, h, false, Image::FORMAT_RGBA8, graph_image); if (reset_texture) { if (graph_texture.is_null()) { - graph_texture.instance(); + graph_texture.instantiate(); } graph_texture->create_from_image(img); } - graph_texture->update(img, true); + graph_texture->update(img); graph->set_texture(graph_texture); graph->update(); @@ -356,7 +356,7 @@ void EditorProfiler::_update_frame() { item->set_metadata(1, it.script); item->set_metadata(2, it.line); item->set_text_align(2, TreeItem::ALIGN_RIGHT); - item->set_tooltip(0, it.script + ":" + itos(it.line)); + item->set_tooltip(0, it.name + "\n" + it.script + ":" + itos(it.line)); float time = dtime == DISPLAY_SELF_TIME ? it.self : it.total; @@ -376,14 +376,14 @@ void EditorProfiler::_update_frame() { void EditorProfiler::_activate_pressed() { if (activate->is_pressed()) { - activate->set_icon(get_theme_icon("Stop", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); activate->set_text(TTR("Stop")); _clear_pressed(); } else { - activate->set_icon(get_theme_icon("Play", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); activate->set_text(TTR("Start")); } - emit_signal("enable_profiling", activate->is_pressed()); + emit_signal(SNAME("enable_profiling"), activate->is_pressed()); } void EditorProfiler::_clear_pressed() { @@ -394,8 +394,8 @@ void EditorProfiler::_clear_pressed() { void EditorProfiler::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { - activate->set_icon(get_theme_icon("Play", "EditorIcons")); - clear_button->set_icon(get_theme_icon("Clear", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"))); } } @@ -438,7 +438,7 @@ void EditorProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseMotion> mm = p_ev; if ( - (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) || + (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) || (mm.is_valid())) { int x = me->get_position().x - 1; x = x * frame_metrics.size() / graph->get_size().width; @@ -453,7 +453,7 @@ void EditorProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) { x = frame_metrics.size() - 1; } - if (mb.is_valid() || mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mb.is_valid() || (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { updating_frame = true; if (x < total_metrics) @@ -462,7 +462,7 @@ void EditorProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) { if (activate->is_pressed()) { if (!seeking) { - emit_signal("break_request"); + emit_signal(SNAME("break_request")); } } @@ -508,23 +508,35 @@ Vector<Vector<String>> EditorProfiler::get_data_as_csv() const { return res; } - // signatures - Vector<String> signatures; - const Vector<EditorProfiler::Metric::Category> &categories = frame_metrics[0].categories; - - for (int j = 0; j < categories.size(); j++) { - const EditorProfiler::Metric::Category &c = categories[j]; - signatures.push_back(c.signature); - - for (int k = 0; k < c.items.size(); k++) { - signatures.push_back(c.items[k].signature); + // Different metrics may contain different number of categories. + Set<StringName> possible_signatures; + for (int i = 0; i < frame_metrics.size(); i++) { + const Metric &m = frame_metrics[i]; + if (!m.valid) { + continue; + } + for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) { + possible_signatures.insert(E.key); + } + for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) { + possible_signatures.insert(E.key); } } + + // Generate CSV header and cache indices. + Map<StringName, int> sig_map; + Vector<String> signatures; + signatures.resize(possible_signatures.size()); + int sig_index = 0; + for (const Set<StringName>::Element *E = possible_signatures.front(); E; E = E->next()) { + signatures.write[sig_index] = E->get(); + sig_map[E->get()] = sig_index; + sig_index++; + } res.push_back(signatures); // values Vector<String> values; - values.resize(signatures.size()); int index = last_metric; @@ -535,20 +547,23 @@ Vector<Vector<String>> EditorProfiler::get_data_as_csv() const { index = 0; } - if (!frame_metrics[index].valid) { + const Metric &m = frame_metrics[index]; + + if (!m.valid) { continue; } - int it = 0; - const Vector<EditorProfiler::Metric::Category> &frame_cat = frame_metrics[index].categories; - for (int j = 0; j < frame_cat.size(); j++) { - const EditorProfiler::Metric::Category &c = frame_cat[j]; - values.write[it++] = String::num_real(c.total_time); + // Don't keep old values since there may be empty cells. + values.clear(); + values.resize(possible_signatures.size()); - for (int k = 0; k < c.items.size(); k++) { - values.write[it++] = String::num_real(c.items[k].total); - } + for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) { + values.write[sig_map[E.key]] = String::num_real(E.value->total_time); + } + for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) { + values.write[sig_map[E.key]] = String::num_real(E.value->total); } + res.push_back(values); } @@ -573,8 +588,8 @@ EditorProfiler::EditorProfiler() { hb->add_child(memnew(Label(TTR("Measure:")))); display_mode = memnew(OptionButton); - display_mode->add_item(TTR("Frame Time (sec)")); - display_mode->add_item(TTR("Average Time (sec)")); + display_mode->add_item(TTR("Frame Time (ms)")); + display_mode->add_item(TTR("Average Time (ms)")); display_mode->add_item(TTR("Frame %")); display_mode->add_item(TTR("Physics Frame %")); display_mode->connect("item_selected", callable_mp(this, &EditorProfiler::_combo_changed)); @@ -586,6 +601,7 @@ EditorProfiler::EditorProfiler() { display_time = memnew(OptionButton); display_time->add_item(TTR("Inclusive")); display_time->add_item(TTR("Self")); + display_time->set_tooltip(TTR("Inclusive: Includes time from other functions called by this function.\nUse this to spot bottlenecks.\n\nSelf: Only count the time spent in the function itself, not in other functions called by that function.\nUse this to find individual functions to optimize.")); display_time->connect("item_selected", callable_mp(this, &EditorProfiler::_combo_changed)); hb->add_child(display_time); @@ -616,13 +632,16 @@ EditorProfiler::EditorProfiler() { variables->set_column_titles_visible(true); variables->set_column_title(0, TTR("Name")); variables->set_column_expand(0, true); - variables->set_column_min_width(0, 60 * EDSCALE); + variables->set_column_clip_content(0, true); + variables->set_column_expand_ratio(0, 60); variables->set_column_title(1, TTR("Time")); variables->set_column_expand(1, false); - variables->set_column_min_width(1, 100 * EDSCALE); + variables->set_column_clip_content(1, true); + variables->set_column_expand_ratio(1, 100); variables->set_column_title(2, TTR("Calls")); variables->set_column_expand(2, false); - variables->set_column_min_width(2, 60 * EDSCALE); + variables->set_column_clip_content(2, true); + variables->set_column_expand_ratio(2, 60); variables->connect("item_edited", callable_mp(this, &EditorProfiler::_item_edited)); graph = memnew(TextureRect); diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index 5bb10b3794..4739458f8e 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -123,11 +123,11 @@ String EditorVisualProfiler::_get_time_as_text(float p_time) { } Color EditorVisualProfiler::_get_color_from_signature(const StringName &p_signature) const { - Color bc = get_theme_color("error_color", "Editor"); + Color bc = get_theme_color(SNAME("error_color"), SNAME("Editor")); double rot = ABS(double(p_signature.hash()) / double(0x7FFFFFFF)); Color c; c.set_hsv(rot, bc.get_s(), bc.get_v()); - return c.lerp(get_theme_color("base_color", "Editor"), 0.07); + return c.lerp(get_theme_color(SNAME("base_color"), SNAME("Editor")), 0.07); } void EditorVisualProfiler::_item_selected() { @@ -299,17 +299,17 @@ void EditorVisualProfiler::_update_plot() { } Ref<Image> img; - img.instance(); + img.instantiate(); img->create(w, h, false, Image::FORMAT_RGBA8, graph_image); if (reset_texture) { if (graph_texture.is_null()) { - graph_texture.instance(); + graph_texture.instantiate(); } graph_texture->create_from_image(img); } - graph_texture->update(img, true); + graph_texture->update(img); graph->set_texture(graph_texture); graph->update(); @@ -318,7 +318,7 @@ void EditorVisualProfiler::_update_plot() { void EditorVisualProfiler::_update_frame(bool p_focus_selected) { int cursor_metric = _get_cursor_index(); - Ref<Texture> track_icon = get_theme_icon("TrackColor", "EditorIcons"); + Ref<Texture> track_icon = get_theme_icon(SNAME("TrackColor"), SNAME("EditorIcons")); ERR_FAIL_INDEX(cursor_metric, frame_metrics.size()); @@ -365,13 +365,13 @@ void EditorVisualProfiler::_update_frame(bool p_focus_selected) { } TreeItem *category = variables->create_item(parent); - for (List<TreeItem *>::Element *E = stack.front(); E; E = E->next()) { - float total_cpu = E->get()->get_metadata(1); - float total_gpu = E->get()->get_metadata(2); + for (TreeItem *E : stack) { + float total_cpu = E->get_metadata(1); + float total_gpu = E->get_metadata(2); total_cpu += cpu_time; total_gpu += gpu_time; - E->get()->set_metadata(1, cpu_time); - E->get()->set_metadata(2, gpu_time); + E->set_metadata(1, total_cpu); + E->set_metadata(2, total_gpu); } category->set_icon(0, track_icon); @@ -392,11 +392,11 @@ void EditorVisualProfiler::_update_frame(bool p_focus_selected) { } } - for (List<TreeItem *>::Element *E = categories.front(); E; E = E->next()) { - float total_cpu = E->get()->get_metadata(1); - float total_gpu = E->get()->get_metadata(2); - E->get()->set_text(1, _get_time_as_text(total_cpu)); - E->get()->set_text(2, _get_time_as_text(total_gpu)); + for (TreeItem *E : categories) { + float total_cpu = E->get_metadata(1); + float total_gpu = E->get_metadata(2); + E->set_text(1, _get_time_as_text(total_cpu)); + E->set_text(2, _get_time_as_text(total_gpu)); } if (ensure_selected) { @@ -407,14 +407,14 @@ void EditorVisualProfiler::_update_frame(bool p_focus_selected) { void EditorVisualProfiler::_activate_pressed() { if (activate->is_pressed()) { - activate->set_icon(get_theme_icon("Stop", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); activate->set_text(TTR("Stop")); _clear_pressed(); //always clear on start } else { - activate->set_icon(get_theme_icon("Play", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); activate->set_text(TTR("Start")); } - emit_signal("enable_profiling", activate->is_pressed()); + emit_signal(SNAME("enable_profiling"), activate->is_pressed()); } void EditorVisualProfiler::_clear_pressed() { @@ -425,11 +425,11 @@ void EditorVisualProfiler::_clear_pressed() { void EditorVisualProfiler::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { if (is_layout_rtl()) { - activate->set_icon(get_theme_icon("PlayBackwards", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("PlayBackwards"), SNAME("EditorIcons"))); } else { - activate->set_icon(get_theme_icon("Play", "EditorIcons")); + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); } - clear_button->set_icon(get_theme_icon("Clear", "EditorIcons")); + clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"))); } } @@ -437,8 +437,8 @@ void EditorVisualProfiler::_graph_tex_draw() { if (last_metric < 0) { return; } - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); + Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label")); + int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); if (seeking) { int max_frames = frame_metrics.size(); int frame = cursor_metric_edit->get_value() - (frame_metrics[last_metric].frame_number - max_frames + 1); @@ -517,7 +517,7 @@ void EditorVisualProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseMotion> mm = p_ev; if ( - (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) || + (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) || (mm.is_valid())) { int half_w = graph->get_size().width / 2; int x = me->get_position().x; @@ -549,7 +549,7 @@ void EditorVisualProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) { hover_metric = -1; } - if (mb.is_valid() || mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mb.is_valid() || (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { //cursor_metric=x; updating_frame = true; @@ -773,13 +773,16 @@ EditorVisualProfiler::EditorVisualProfiler() { variables->set_column_titles_visible(true); variables->set_column_title(0, TTR("Name")); variables->set_column_expand(0, true); - variables->set_column_min_width(0, 60); + variables->set_column_clip_content(0, true); + variables->set_column_custom_minimum_width(0, 60); variables->set_column_title(1, TTR("CPU")); variables->set_column_expand(1, false); - variables->set_column_min_width(1, 60 * EDSCALE); + variables->set_column_clip_content(1, true); + variables->set_column_custom_minimum_width(1, 60 * EDSCALE); variables->set_column_title(2, TTR("GPU")); variables->set_column_expand(2, false); - variables->set_column_min_width(2, 60 * EDSCALE); + variables->set_column_clip_content(2, true); + variables->set_column_custom_minimum_width(2, 60 * EDSCALE); variables->connect("cell_selected", callable_mp(this, &EditorVisualProfiler::_item_selected)); graph = memnew(TextureRect); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 1d95161e6c..b18c225f23 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -37,6 +37,7 @@ #include "core/string/ustring.h" #include "core/version.h" #include "core/version_hash.gen.h" +#include "editor/debugger/debug_adapter/debug_adapter_protocol.h" #include "editor/debugger/editor_network_profiler.h" #include "editor/debugger/editor_performance_profiler.h" #include "editor/debugger/editor_profiler.h" @@ -87,9 +88,9 @@ void ScriptEditorDebugger::debug_copy() { void ScriptEditorDebugger::debug_skip_breakpoints() { skip_breakpoints_value = !skip_breakpoints_value; if (skip_breakpoints_value) { - skip_breakpoints->set_icon(get_theme_icon("DebugSkipBreakpointsOn", "EditorIcons")); + skip_breakpoints->set_icon(get_theme_icon(SNAME("DebugSkipBreakpointsOn"), SNAME("EditorIcons"))); } else { - skip_breakpoints->set_icon(get_theme_icon("DebugSkipBreakpointsOff", "EditorIcons")); + skip_breakpoints->set_icon(get_theme_icon(SNAME("DebugSkipBreakpointsOff"), SNAME("EditorIcons"))); } Array msg; @@ -136,17 +137,17 @@ void ScriptEditorDebugger::update_tabs() { } else { errors_tab->set_name(TTR("Errors") + " (" + itos(error_count + warning_count) + ")"); if (error_count >= 1 && warning_count >= 1) { - tabs->set_tab_icon(errors_tab->get_index(), get_theme_icon("ErrorWarning", "EditorIcons")); + tabs->set_tab_icon(errors_tab->get_index(), get_theme_icon(SNAME("ErrorWarning"), SNAME("EditorIcons"))); } else if (error_count >= 1) { - tabs->set_tab_icon(errors_tab->get_index(), get_theme_icon("Error", "EditorIcons")); + tabs->set_tab_icon(errors_tab->get_index(), get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); } else { - tabs->set_tab_icon(errors_tab->get_index(), get_theme_icon("Warning", "EditorIcons")); + tabs->set_tab_icon(errors_tab->get_index(), get_theme_icon(SNAME("Warning"), SNAME("EditorIcons"))); } } } void ScriptEditorDebugger::clear_style() { - tabs->add_theme_style_override("panel", nullptr); + tabs->remove_theme_style_override("panel"); } void ScriptEditorDebugger::save_node(ObjectID p_id, const String &p_file) { @@ -220,7 +221,7 @@ void ScriptEditorDebugger::_file_selected(const String &p_file) { file->store_csv_line(headers); if (vmem_tree->get_root()) { - TreeItem *ti = vmem_tree->get_root()->get_children(); + TreeItem *ti = vmem_tree->get_root()->get_first_child(); while (ti) { Vector<String> values; values.resize(vmem_tree->get_columns()); @@ -264,7 +265,7 @@ Object *ScriptEditorDebugger::get_remote_object(ObjectID p_id) { } void ScriptEditorDebugger::_remote_object_selected(ObjectID p_id) { - emit_signal("remote_object_requested", p_id); + emit_signal(SNAME("remote_object_requested"), p_id); } void ScriptEditorDebugger::_remote_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) { @@ -273,7 +274,7 @@ void ScriptEditorDebugger::_remote_object_edited(ObjectID p_id, const String &p_ } void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const String &p_property) { - emit_signal("remote_object_property_updated", p_id, p_property); + emit_signal(SNAME("remote_object_property_updated"), p_id, p_property); } void ScriptEditorDebugger::_video_mem_request() { @@ -295,18 +296,22 @@ Size2 ScriptEditorDebugger::get_minimum_size() const { } void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) { + emit_signal(SNAME("debug_data"), p_msg, p_data); if (p_msg == "debug_enter") { _put_msg("get_stack_dump", Array()); - ERR_FAIL_COND(p_data.size() != 2); + ERR_FAIL_COND(p_data.size() != 3); bool can_continue = p_data[0]; String error = p_data[1]; + bool has_stackdump = p_data[2]; breaked = true; can_debug = can_continue; _update_buttons_state(); _set_reason_text(error, MESSAGE_ERROR); - emit_signal("breaked", true, can_continue); - DisplayServer::get_singleton()->window_move_to_foreground(); + emit_signal(SNAME("breaked"), true, can_continue, error, has_stackdump); + if (is_move_to_foreground()) { + DisplayServer::get_singleton()->window_move_to_foreground(); + } if (error != "") { tabs->set_current_tab(0); } @@ -319,7 +324,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da _clear_execution(); _update_buttons_state(); _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS); - emit_signal("breaked", false, false); + emit_signal(SNAME("breaked"), false, false, "", false); profiler->set_enabled(true); profiler->disable_seeking(); } else if (p_msg == "set_pid") { @@ -332,12 +337,12 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else if (p_msg == "scene:scene_tree") { scene_tree->nodes.clear(); scene_tree->deserialize(p_data); - emit_signal("remote_tree_updated"); + emit_signal(SNAME("remote_tree_updated")); _update_buttons_state(); } else if (p_msg == "scene:inspect_object") { ObjectID id = inspector->add_object(p_data); if (id.is_valid()) { - emit_signal("remote_object_updated", id); + emit_signal(SNAME("remote_object_updated"), id); } } else if (p_msg == "memory:usage") { vmem_tree->clear(); @@ -347,18 +352,18 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da uint64_t total = 0; - for (List<DebuggerMarshalls::ResourceInfo>::Element *E = usage.infos.front(); E; E = E->next()) { + for (const DebuggerMarshalls::ResourceInfo &E : usage.infos) { TreeItem *it = vmem_tree->create_item(root); - String type = E->get().type; - int bytes = E->get().vram; - it->set_text(0, E->get().path); + String type = E.type; + int bytes = E.vram; + it->set_text(0, E.path); it->set_text(1, type); - it->set_text(2, E->get().format); + it->set_text(2, E.format); it->set_text(3, String::humanize_size(bytes)); total += bytes; - if (has_theme_icon(type, "EditorIcons")) { - it->set_icon(0, get_theme_icon(type, "EditorIcons")); + if (has_theme_icon(type, SNAME("EditorIcons"))) { + it->set_icon(0, get_theme_icon(type, SNAME("EditorIcons"))); } } @@ -373,6 +378,8 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da inspector->clear_stack_variables(); TreeItem *r = stack_dump->create_item(); + Array stack_dump_info; + for (int i = 0; i < stack.frames.size(); i++) { TreeItem *s = stack_dump->create_item(r); Dictionary d; @@ -380,6 +387,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da d["file"] = stack.frames[i].file; d["function"] = stack.frames[i].func; d["line"] = stack.frames[i].line; + stack_dump_info.push_back(d); s->set_metadata(0, d); String line = itos(i) + " - " + String(d["file"]) + ":" + itos(d["line"]) + " - at function: " + d["function"]; @@ -389,11 +397,15 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da s->select(0); } } + emit_signal(SNAME("stack_dump"), stack_dump_info); } else if (p_msg == "stack_frame_vars") { inspector->clear_stack_variables(); + ERR_FAIL_COND(p_data.size() != 1); + emit_signal(SNAME("stack_frame_vars"), p_data[0]); } else if (p_msg == "stack_frame_var") { inspector->add_stack_variable(p_data); + emit_signal(SNAME("stack_frame_var"), p_data); } else if (p_msg == "output") { ERR_FAIL_COND(p_data.size() != 2); @@ -422,6 +434,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } break; } EditorNode::get_log()->add_message(output_strings[i], msg_type); + emit_signal(SNAME("output"), output_strings[i]); } } else if (p_msg == "performance:profile_frame") { Vector<float> frame_data; @@ -488,6 +501,10 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da error->set_text(0, time); error->set_text_align(0, TreeItem::ALIGN_LEFT); + const Color color = get_theme_color(oe.warning ? SNAME("warning_color") : SNAME("error_color"), SNAME("Editor")); + error->set_custom_color(0, color); + error->set_custom_color(1, color); + String error_title; if (oe.callstack.size() > 0) { // If available, use the script's stack in the error title. @@ -561,6 +578,12 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da error->set_tooltip(0, tooltip); error->set_tooltip(1, tooltip); + if (warning_count == 0 && error_count == 0) { + expand_all_button->set_disabled(false); + collapse_all_button->set_disabled(false); + clear_button->set_disabled(false); + } + if (oe.warning) { warning_count++; } else { @@ -698,7 +721,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da network_profiler->set_bandwidth(p_data[0], p_data[1]); } else if (p_msg == "request_quit") { - emit_signal("stop_requested"); + emit_signal(SNAME("stop_requested")); _stop_and_notify(); } else if (p_msg == "performance:profile_names") { @@ -739,13 +762,13 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da void ScriptEditorDebugger::_set_reason_text(const String &p_reason, MessageType p_type) { switch (p_type) { case MESSAGE_ERROR: - reason->add_theme_color_override("font_color", get_theme_color("error_color", "Editor")); + reason->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); break; case MESSAGE_WARNING: - reason->add_theme_color_override("font_color", get_theme_color("warning_color", "Editor")); + reason->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); break; default: - reason->add_theme_color_override("font_color", get_theme_color("success_color", "Editor")); + reason->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); } reason->set_text(p_reason); reason->set_tooltip(p_reason.word_wrap(80)); @@ -754,21 +777,21 @@ void ScriptEditorDebugger::_set_reason_text(const String &p_reason, MessageType void ScriptEditorDebugger::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - skip_breakpoints->set_icon(get_theme_icon("DebugSkipBreakpointsOff", "EditorIcons")); - copy->set_icon(get_theme_icon("ActionCopy", "EditorIcons")); + skip_breakpoints->set_icon(get_theme_icon(SNAME("DebugSkipBreakpointsOff"), SNAME("EditorIcons"))); + copy->set_icon(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); - step->set_icon(get_theme_icon("DebugStep", "EditorIcons")); - next->set_icon(get_theme_icon("DebugNext", "EditorIcons")); - dobreak->set_icon(get_theme_icon("Pause", "EditorIcons")); - docontinue->set_icon(get_theme_icon("DebugContinue", "EditorIcons")); + step->set_icon(get_theme_icon(SNAME("DebugStep"), SNAME("EditorIcons"))); + next->set_icon(get_theme_icon(SNAME("DebugNext"), SNAME("EditorIcons"))); + dobreak->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); + docontinue->set_icon(get_theme_icon(SNAME("DebugContinue"), SNAME("EditorIcons"))); le_set->connect("pressed", callable_mp(this, &ScriptEditorDebugger::_live_edit_set)); le_clear->connect("pressed", callable_mp(this, &ScriptEditorDebugger::_live_edit_clear)); error_tree->connect("item_selected", callable_mp(this, &ScriptEditorDebugger::_error_selected)); error_tree->connect("item_activated", callable_mp(this, &ScriptEditorDebugger::_error_activated)); - vmem_refresh->set_icon(get_theme_icon("Reload", "EditorIcons")); - vmem_export->set_icon(get_theme_icon("Save", "EditorIcons")); + vmem_refresh->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + vmem_export->set_icon(get_theme_icon(SNAME("Save"), SNAME("EditorIcons"))); - reason->add_theme_color_override("font_color", get_theme_color("error_color", "Editor")); + reason->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); } break; case NOTIFICATION_PROCESS: { @@ -793,7 +816,7 @@ void ScriptEditorDebugger::_notification(int p_what) { } else if (camera_override >= CameraOverride::OVERRIDE_3D_1) { int viewport_idx = camera_override - CameraOverride::OVERRIDE_3D_1; Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(viewport_idx); - Camera3D *const cam = viewport->get_camera(); + Camera3D *const cam = viewport->get_camera_3d(); Array msg; msg.push_back(cam->get_camera_transform()); @@ -831,16 +854,16 @@ void ScriptEditorDebugger::_notification(int p_what) { } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { if (tabs->has_theme_stylebox_override("panel")) { - tabs->add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox("DebuggerPanel", "EditorStyles")); + tabs->add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("DebuggerPanel"), SNAME("EditorStyles"))); } - copy->set_icon(get_theme_icon("ActionCopy", "EditorIcons")); - step->set_icon(get_theme_icon("DebugStep", "EditorIcons")); - next->set_icon(get_theme_icon("DebugNext", "EditorIcons")); - dobreak->set_icon(get_theme_icon("Pause", "EditorIcons")); - docontinue->set_icon(get_theme_icon("DebugContinue", "EditorIcons")); - vmem_refresh->set_icon(get_theme_icon("Reload", "EditorIcons")); - vmem_export->set_icon(get_theme_icon("Save", "EditorIcons")); + copy->set_icon(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); + step->set_icon(get_theme_icon(SNAME("DebugStep"), SNAME("EditorIcons"))); + next->set_icon(get_theme_icon(SNAME("DebugNext"), SNAME("EditorIcons"))); + dobreak->set_icon(get_theme_icon(SNAME("Pause"), SNAME("EditorIcons"))); + docontinue->set_icon(get_theme_icon(SNAME("DebugContinue"), SNAME("EditorIcons"))); + vmem_refresh->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + vmem_export->set_icon(get_theme_icon(SNAME("Save"), SNAME("EditorIcons"))); } break; } } @@ -854,15 +877,24 @@ void ScriptEditorDebugger::_clear_execution() { Dictionary d = ti->get_metadata(0); stack_script = ResourceLoader::load(d["file"]); - emit_signal("clear_execution", stack_script); + emit_signal(SNAME("clear_execution"), stack_script); stack_script.unref(); stack_dump->clear(); inspector->clear_stack_variables(); } +void ScriptEditorDebugger::_set_breakpoint(const String &p_file, const int &p_line, const bool &p_enabled) { + Ref<Script> script = ResourceLoader::load(p_file); + emit_signal("set_breakpoint", script, p_line - 1, p_enabled); + script.unref(); +} + +void ScriptEditorDebugger::_clear_breakpoints() { + emit_signal("clear_breakpoints"); +} + void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) { - error_count = 0; - warning_count = 0; + _clear_errors_list(); stop(); peer = p_peer; @@ -878,7 +910,7 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) { tabs->set_current_tab(0); _set_reason_text(TTR("Debug session started."), MESSAGE_SUCCESS); _update_buttons_state(); - emit_signal("started"); + emit_signal(SNAME("started")); } void ScriptEditorDebugger::_update_buttons_state() { @@ -896,7 +928,7 @@ void ScriptEditorDebugger::_update_buttons_state() { void ScriptEditorDebugger::_stop_and_notify() { stop(); - emit_signal("stopped"); + emit_signal(SNAME("stopped")); _set_reason_text(TTR("Debug session closed."), MESSAGE_WARNING); } @@ -959,15 +991,11 @@ void ScriptEditorDebugger::_profiler_seeked() { } void ScriptEditorDebugger::_stack_dump_frame_selected() { - emit_signal("stack_frame_selected"); + emit_signal(SNAME("stack_frame_selected")); int frame = get_stack_script_frame(); - if (is_session_active() && frame >= 0) { - Array msg; - msg.push_back(frame); - _put_msg("get_stack_frame_vars", msg); - } else { + if (!request_stack_dump(frame)) { inspector->edit(nullptr); } } @@ -1130,6 +1158,14 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p } } +bool ScriptEditorDebugger::is_move_to_foreground() const { + return move_to_foreground; +} + +void ScriptEditorDebugger::set_move_to_foreground(const bool &p_move_to_foreground) { + move_to_foreground = p_move_to_foreground; +} + String ScriptEditorDebugger::get_stack_script_file() const { TreeItem *ti = stack_dump->get_selected(); if (!ti) { @@ -1157,6 +1193,15 @@ int ScriptEditorDebugger::get_stack_script_frame() const { return d["frame"]; } +bool ScriptEditorDebugger::request_stack_dump(const int &p_frame) { + ERR_FAIL_COND_V(!is_session_active() || p_frame < 0, false); + + Array msg; + msg.push_back(p_frame); + _put_msg("get_stack_frame_vars", msg); + return true; +} + void ScriptEditorDebugger::set_live_debugging(bool p_enable) { live_debug = p_enable; } @@ -1199,7 +1244,7 @@ void ScriptEditorDebugger::update_live_edit_root() { Array msg; msg.push_back(np); if (editor->get_edited_scene()) { - msg.push_back(editor->get_edited_scene()->get_filename()); + msg.push_back(editor->get_edited_scene()->get_scene_file_path()); } else { msg.push_back(""); } @@ -1319,7 +1364,7 @@ bool ScriptEditorDebugger::is_skip_breakpoints() { void ScriptEditorDebugger::_error_activated() { TreeItem *selected = error_tree->get_selected(); - TreeItem *ci = selected->get_children(); + TreeItem *ci = selected->get_first_child(); if (ci) { selected->set_collapsed(!selected->is_collapsed()); } @@ -1332,7 +1377,7 @@ void ScriptEditorDebugger::_error_selected() { return; } - emit_signal("error_selected", String(meta[0]), int(meta[1])); + emit_signal(SNAME("error_selected"), String(meta[0]), int(meta[1])); } void ScriptEditorDebugger::_expand_errors_list() { @@ -1341,7 +1386,7 @@ void ScriptEditorDebugger::_expand_errors_list() { return; } - TreeItem *item = root->get_children(); + TreeItem *item = root->get_first_child(); while (item) { item->set_collapsed(false); item = item->get_next(); @@ -1354,7 +1399,7 @@ void ScriptEditorDebugger::_collapse_errors_list() { return; } - TreeItem *item = root->get_children(); + TreeItem *item = root->get_first_child(); while (item) { item->set_collapsed(true); item = item->get_next(); @@ -1365,16 +1410,21 @@ void ScriptEditorDebugger::_clear_errors_list() { error_tree->clear(); error_count = 0; warning_count = 0; + update_tabs(); + + expand_all_button->set_disabled(true); + collapse_all_button->set_disabled(true); + clear_button->set_disabled(true); } // Right click on specific file(s) or folder(s). void ScriptEditorDebugger::_error_tree_item_rmb_selected(const Vector2 &p_pos) { item_menu->clear(); - item_menu->set_size(Size2(1, 1)); + item_menu->reset_size(); if (error_tree->is_anything_selected()) { - item_menu->add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), TTR("Copy Error"), ACTION_COPY_ERROR); - item_menu->add_icon_item(get_theme_icon("Instance", "EditorIcons"), TTR("Open C++ Source on GitHub"), ACTION_OPEN_SOURCE); + item_menu->add_icon_item(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")), TTR("Copy Error"), ACTION_COPY_ERROR); + item_menu->add_icon_item(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Open C++ Source on GitHub"), ACTION_OPEN_SOURCE); } if (item_menu->get_item_count() > 0) { @@ -1393,9 +1443,9 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { String type; - if (ti->get_icon(0) == get_theme_icon("Warning", "EditorIcons")) { + if (ti->get_icon(0) == get_theme_icon(SNAME("Warning"), SNAME("EditorIcons"))) { type = "W "; - } else if (ti->get_icon(0) == get_theme_icon("Error", "EditorIcons")) { + } else if (ti->get_icon(0) == get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))) { type = "E "; } @@ -1403,7 +1453,7 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { int rpad_len = text.length(); text = type + text + ti->get_text(1) + "\n"; - TreeItem *ci = ti->get_children(); + TreeItem *ci = ti->get_first_child(); while (ci) { text += " " + ci->get_text(0).rpad(rpad_len) + ci->get_text(1) + "\n"; ci = ci->get_next(); @@ -1419,7 +1469,7 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { } // We only need the first child here (C++ source stack trace). - TreeItem *ci = ti->get_children(); + TreeItem *ci = ti->get_first_child(); // Parse back the `file:line @ method()` string. const Vector<String> file_line_number = ci->get_text(1).split("@")[0].strip_edges().split(":"); ERR_FAIL_COND_MSG(file_line_number.size() < 2, "Incorrect C++ source stack trace file:line format (please report)."); @@ -1469,11 +1519,18 @@ void ScriptEditorDebugger::_bind_methods() { ADD_SIGNAL(MethodInfo("error_selected", PropertyInfo(Variant::INT, "error"))); ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script"))); - ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"))); + ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"), PropertyInfo(Variant::STRING, "reason"), PropertyInfo(Variant::BOOL, "has_stackdump"))); ADD_SIGNAL(MethodInfo("remote_object_requested", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("remote_object_updated", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("remote_object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"))); ADD_SIGNAL(MethodInfo("remote_tree_updated")); + ADD_SIGNAL(MethodInfo("output")); + ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump"))); + ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars"))); + ADD_SIGNAL(MethodInfo("stack_frame_var", PropertyInfo(Variant::ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("debug_data", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("set_breakpoint", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled"))); + ADD_SIGNAL(MethodInfo("clear_breakpoints")); } void ScriptEditorDebugger::add_debugger_plugin(const Ref<Script> &p_script) { @@ -1518,7 +1575,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { tabs = memnew(TabContainer); tabs->set_tab_align(TabContainer::ALIGN_LEFT); - tabs->add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox("DebuggerPanel", "EditorStyles")); + tabs->add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("DebuggerPanel"), SNAME("EditorStyles"))); tabs->connect("tab_changed", callable_mp(this, &ScriptEditorDebugger::_tab_changed)); add_child(tabs); @@ -1535,7 +1592,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { reason->set_text(""); hbc->add_child(reason); reason->set_h_size_flags(SIZE_EXPAND_FILL); - reason->set_autowrap(true); + reason->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART); reason->set_max_lines_visible(3); reason->set_mouse_filter(Control::MOUSE_FILTER_PASS); @@ -1616,36 +1673,41 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { errors_tab = memnew(VBoxContainer); errors_tab->set_name(TTR("Errors")); - HBoxContainer *errhb = memnew(HBoxContainer); - errors_tab->add_child(errhb); + HBoxContainer *error_hbox = memnew(HBoxContainer); + errors_tab->add_child(error_hbox); - Button *expand_all = memnew(Button); - expand_all->set_text(TTR("Expand All")); - expand_all->connect("pressed", callable_mp(this, &ScriptEditorDebugger::_expand_errors_list)); - errhb->add_child(expand_all); + expand_all_button = memnew(Button); + expand_all_button->set_text(TTR("Expand All")); + expand_all_button->set_disabled(true); + expand_all_button->connect("pressed", callable_mp(this, &ScriptEditorDebugger::_expand_errors_list)); + error_hbox->add_child(expand_all_button); - Button *collapse_all = memnew(Button); - collapse_all->set_text(TTR("Collapse All")); - collapse_all->connect("pressed", callable_mp(this, &ScriptEditorDebugger::_collapse_errors_list)); - errhb->add_child(collapse_all); + collapse_all_button = memnew(Button); + collapse_all_button->set_text(TTR("Collapse All")); + collapse_all_button->set_disabled(true); + collapse_all_button->connect("pressed", callable_mp(this, &ScriptEditorDebugger::_collapse_errors_list)); + error_hbox->add_child(collapse_all_button); Control *space = memnew(Control); space->set_h_size_flags(SIZE_EXPAND_FILL); - errhb->add_child(space); + error_hbox->add_child(space); - clearbutton = memnew(Button); - clearbutton->set_text(TTR("Clear")); - clearbutton->set_h_size_flags(0); - clearbutton->connect("pressed", callable_mp(this, &ScriptEditorDebugger::_clear_errors_list)); - errhb->add_child(clearbutton); + clear_button = memnew(Button); + clear_button->set_text(TTR("Clear")); + clear_button->set_h_size_flags(0); + clear_button->set_disabled(true); + clear_button->connect("pressed", callable_mp(this, &ScriptEditorDebugger::_clear_errors_list)); + error_hbox->add_child(clear_button); error_tree = memnew(Tree); error_tree->set_columns(2); error_tree->set_column_expand(0, false); - error_tree->set_column_min_width(0, 140); + error_tree->set_column_custom_minimum_width(0, 140); + error_tree->set_column_clip_content(0, true); error_tree->set_column_expand(1, true); + error_tree->set_column_clip_content(1, true); error_tree->set_select_mode(Tree::SELECT_ROW); error_tree->set_hide_root(true); @@ -1698,6 +1760,8 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { VBoxContainer *vmem_vb = memnew(VBoxContainer); HBoxContainer *vmem_hb = memnew(HBoxContainer); Label *vmlb = memnew(Label(TTR("List of Video Memory Usage by Resource:") + " ")); + vmlb->set_theme_type_variation("HeaderSmall"); + vmlb->set_h_size_flags(SIZE_EXPAND_FILL); vmem_hb->add_child(vmlb); vmem_hb->add_child(memnew(Label(TTR("Total:") + " "))); @@ -1731,13 +1795,13 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { vmem_tree->set_column_expand(0, true); vmem_tree->set_column_expand(1, false); vmem_tree->set_column_title(1, TTR("Type")); - vmem_tree->set_column_min_width(1, 100 * EDSCALE); + vmem_tree->set_column_custom_minimum_width(1, 100 * EDSCALE); vmem_tree->set_column_expand(2, false); vmem_tree->set_column_title(2, TTR("Format")); - vmem_tree->set_column_min_width(2, 150 * EDSCALE); + vmem_tree->set_column_custom_minimum_width(2, 150 * EDSCALE); vmem_tree->set_column_expand(3, false); vmem_tree->set_column_title(3, TTR("Usage")); - vmem_tree->set_column_min_width(3, 80 * EDSCALE); + vmem_tree->set_column_custom_minimum_width(3, 80 * EDSCALE); vmem_tree->set_hide_root(true); tabs->add_child(vmem_vb); diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index a5731c9f9c..76209aef46 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -55,11 +55,15 @@ class EditorNetworkProfiler; class EditorPerformanceProfiler; class SceneDebuggerTree; class EditorDebuggerPlugin; +class DebugAdapterProtocol; +class DebugAdapterParser; class ScriptEditorDebugger : public MarginContainer { GDCLASS(ScriptEditorDebugger, MarginContainer); friend class EditorDebuggerNode; + friend class DebugAdapterProtocol; + friend class DebugAdapterParser; private: enum MessageType { @@ -90,7 +94,9 @@ private: VBoxContainer *errors_tab; Tree *error_tree; - Button *clearbutton; + Button *expand_all_button; + Button *collapse_all_button; + Button *clear_button; PopupMenu *item_menu; EditorFileDialog *file_dialog; @@ -147,6 +153,7 @@ private: OS::ProcessID remote_pid = 0; bool breaked = false; bool can_debug = false; + bool move_to_foreground = true; bool live_debug; @@ -200,6 +207,9 @@ private: void _clear_execution(); void _stop_and_notify(); + void _set_breakpoint(const String &p_path, const int &p_line, const bool &p_enabled); + void _clear_breakpoints(); + protected: void _notification(int p_what); static void _bind_methods(); @@ -230,12 +240,17 @@ public: bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }; int get_remote_pid() const { return remote_pid; } + bool is_move_to_foreground() const; + void set_move_to_foreground(const bool &p_move_to_foreground); + int get_error_count() const { return error_count; } int get_warning_count() const { return warning_count; } String get_stack_script_file() const; int get_stack_script_line() const; int get_stack_script_frame() const; + bool request_stack_dump(const int &p_frame); + void update_tabs(); void clear_style(); String get_var_value(const String &p_var) const; |