diff options
Diffstat (limited to 'editor/debugger')
19 files changed, 987 insertions, 164 deletions
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp index 945291b163..485d58f4a3 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp @@ -33,12 +33,15 @@ #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::prepare_success_response); + 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); @@ -46,10 +49,13 @@ void DebugAdapterParser::_bind_methods() { 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 { @@ -80,13 +86,31 @@ Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params 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; - 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}\""; } message.id = err_type; @@ -114,10 +138,35 @@ Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const 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_launch(const Dictionary &p_params) { +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; @@ -126,17 +175,85 @@ Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) { 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(); } - EditorNode::get_singleton()->run_play(); + 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(); @@ -189,8 +306,8 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const Array arr; DebugAdapterProtocol *dap = DebugAdapterProtocol::get_singleton(); - for (Map<DAP::StackFrame, List<int>>::Element *E = dap->stackframe_list.front(); E; E = E->next()) { - DAP::StackFrame sf = E->key(); + for (const KeyValue<DAP::StackFrame, List<int>> &E : dap->stackframe_list) { + DAP::StackFrame sf = E.key; if (!lines_at_one) { sf.line--; } @@ -205,7 +322,7 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const return response; } -Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) { +Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const { Dictionary response = prepare_success_response(p_params), body; response["body"] = body; @@ -230,14 +347,30 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) { lines.push_back(breakpoint.line + !lines_at_one); } - EditorDebuggerNode::get_singleton()->set_breakpoints(ProjectSettings::get_singleton()->localize_path(source.path), lines); Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines); body["breakpoints"] = updated_breakpoints; return response; } -Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) { +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; @@ -291,7 +424,14 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const { 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 { @@ -313,6 +453,29 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const { 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"; @@ -423,3 +586,25 @@ Dictionary DebugAdapterParser::ev_output(const String &p_message) const { 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 index b86b37d067..4c93464e39 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.h +++ b/editor/debugger/debug_adapter/debug_adapter_parser.h @@ -44,7 +44,7 @@ class DebugAdapterParser : public Object { private: friend DebugAdapterProtocol; - _FORCE_INLINE_ bool is_valid_path(const String &p_path) { + _FORCE_INLINE_ bool is_valid_path(const String &p_path) const { return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path()); } @@ -60,17 +60,23 @@ protected: public: // Requests Dictionary req_initialize(const Dictionary &p_params) const; - Dictionary req_launch(const Dictionary &p_params); + 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); - Dictionary req_scopes(const Dictionary &p_params); + 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; @@ -83,6 +89,8 @@ public: 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 index 0482271432..36fbf8adf1 100644 --- a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp @@ -94,11 +94,17 @@ Error DAPeer::handle_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; @@ -180,12 +186,485 @@ void DebugAdapterProtocol::reset_stack_info() { 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; + const String type_scalar = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT); + DAP::Variable x, y; + x.name = "x"; + y.name = "y"; + x.type = type_scalar; + y.type = type_scalar; + 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; + const String type_scalar = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT); + DAP::Variable x, y, w, h; + x.name = "x"; + y.name = "y"; + w.name = "w"; + h.name = "h"; + x.type = type_scalar; + y.type = type_scalar; + w.type = type_scalar; + h.type = type_scalar; + 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; + const String type_scalar = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT); + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = type_scalar; + y.type = type_scalar; + z.type = type_scalar; + 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; + const String type_vec2 = Variant::get_type_name(Variant::VECTOR2); + DAP::Variable x, y, origin; + x.name = "x"; + y.name = "y"; + origin.name = "origin"; + x.type = type_vec2; + y.type = type_vec2; + origin.type = type_vec2; + 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; + const String type_float = Variant::get_type_name(Variant::FLOAT); + DAP::Variable x, y, z, w; + x.name = "x"; + y.name = "y"; + z.name = "z"; + w.name = "w"; + x.type = type_float; + y.type = type_float; + z.type = type_float; + w.type = type_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; + const String type_vec3 = Variant::get_type_name(Variant::VECTOR3); + DAP::Variable position, size; + position.name = "position"; + size.name = "size"; + position.type = type_vec3; + size.type = type_vec3; + 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; + const String type_vec2 = Variant::get_type_name(Variant::VECTOR2); + DAP::Variable x, y, z; + x.name = "x"; + y.name = "y"; + z.name = "z"; + x.type = type_vec2; + y.type = type_vec2; + z.type = type_vec2; + 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; + const String type_float = Variant::get_type_name(Variant::FLOAT); + DAP::Variable r, g, b, a; + r.name = "r"; + g.name = "g"; + b.name = "b"; + a.name = "a"; + r.type = type_float; + g.type = type_float; + b.type = type_float; + a.type = type_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)) { @@ -211,7 +690,7 @@ void DebugAdapterProtocol::notify_initialized() { } void DebugAdapterProtocol::notify_process() { - String launch_mode = _current_request.is_empty() ? "launch" : _current_request; + 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()) { @@ -222,7 +701,7 @@ void DebugAdapterProtocol::notify_process() { 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_peer == E->get()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { continue; } E->get()->res_queue.push_back(event); @@ -232,7 +711,7 @@ void DebugAdapterProtocol::notify_terminated() { 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_peer == E->get()) { + if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) { continue; } E->get()->res_queue.push_back(event); @@ -286,25 +765,46 @@ void DebugAdapterProtocol::notify_output(const String &p_message) { } } +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.verified = true; - breakpoint.source.path = p_path; - breakpoint.source.compute_checksums(); breakpoint.line = p_lines[i]; + breakpoint.source.path = p_path; - List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint); - if (E) { - breakpoint.id = E->get().id; - } else { - breakpoint.id = breakpoint_id++; - breakpoint_list.push_back(breakpoint); - } + ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array()); + updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json()); + } - updated_breakpoints.push_back(breakpoint.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; @@ -347,6 +847,29 @@ void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool _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 @@ -424,11 +947,21 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) { 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(); @@ -459,6 +992,8 @@ void DebugAdapterProtocol::poll() { } 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); } @@ -484,12 +1019,15 @@ DebugAdapterProtocol::DebugAdapterProtocol() { 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() { diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h index 6b542033ed..d4291992bf 100644 --- a/editor/debugger/debug_adapter/debug_adapter_protocol.h +++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h @@ -52,12 +52,17 @@ struct DAPeer : RefCounted { 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(); @@ -82,20 +87,26 @@ private: 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; @@ -108,6 +119,8 @@ private: 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; } @@ -126,8 +139,10 @@ public: 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_breakpoints); + Array update_breakpoints(const String &p_path, const Array &p_lines); void poll(); Error start(int p_port, const IPAddress &p_bind_ip); diff --git a/editor/debugger/debug_adapter/debug_adapter_server.cpp b/editor/debugger/debug_adapter/debug_adapter_server.cpp index f9092a1791..4775e2c8b0 100644 --- a/editor/debugger/debug_adapter/debug_adapter_server.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_server.cpp @@ -36,7 +36,8 @@ DebugAdapterServer::DebugAdapterServer() { _EDITOR_DEF("network/debug_adapter/remote_port", remote_port); - _EDITOR_DEF("network/debug_adapter/use_thread", use_thread); + _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) { @@ -50,16 +51,17 @@ void DebugAdapterServer::_notification(int p_what) { 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 && !use_thread && !polling) { + 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"); - bool use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread"); - if (remote_port != this->remote_port || use_thread != this->use_thread) { + if (remote_port != this->remote_port) { this->stop(); this->start(); } @@ -67,35 +69,16 @@ void DebugAdapterServer::_notification(int p_what) { } } -void DebugAdapterServer::thread_func(void *p_userdata) { - DebugAdapterServer *self = static_cast<DebugAdapterServer *>(p_userdata); - while (self->thread_running) { - // Poll 20 times per second - self->protocol.poll(); - OS::get_singleton()->delay_usec(50000); - } -} - void DebugAdapterServer::start() { remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port"); - use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread"); if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) { EditorNode::get_log()->add_message("--- Debug adapter server started ---", EditorLog::MSG_TYPE_EDITOR); - if (use_thread) { - thread_running = true; - thread.start(DebugAdapterServer::thread_func, this); - } - set_process_internal(!use_thread); + set_process_internal(true); started = true; } } void DebugAdapterServer::stop() { - if (use_thread) { - ERR_FAIL_COND(!thread.is_started()); - thread_running = false; - thread.wait_to_finish(); - } 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 index f8a38965cc..c449403cc2 100644 --- a/editor/debugger/debug_adapter/debug_adapter_server.h +++ b/editor/debugger/debug_adapter/debug_adapter_server.h @@ -39,11 +39,9 @@ class DebugAdapterServer : public EditorPlugin { DebugAdapterProtocol protocol; - Thread thread; int remote_port = 6006; bool thread_running = false; bool started = false; - bool use_thread = false; bool polling = false; static void thread_func(void *p_userdata); diff --git a/editor/debugger/debug_adapter/debug_adapter_types.h b/editor/debugger/debug_adapter/debug_adapter_types.h index aa9ab1adcd..5156c91d14 100644 --- a/editor/debugger/debug_adapter/debug_adapter_types.h +++ b/editor/debugger/debug_adapter/debug_adapter_types.h @@ -38,7 +38,11 @@ namespace DAP { enum ErrorType { UNKNOWN, - WRONG_PATH + WRONG_PATH, + NOT_RUNNING, + TIMEOUT, + UNKNOWN_PLATFORM, + MISSING_DEVICE }; struct Checksum { @@ -118,10 +122,14 @@ struct Breakpoint { 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; } diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index a1eb71235c..9346b8f1af 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -55,9 +55,14 @@ 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 PropertyInfo &E : prop_list) { - p_list->push_back(E); + p_list->clear(); // Sorry, no want category. + for (const PropertyInfo &prop : prop_list) { + if (prop.name == "script") { + // Skip the script property, it's always added by the non-virtual method. + continue; + } + + p_list->push_back(prop); } } @@ -200,12 +205,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 a9cb1a0131..8ea028a7de 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -59,7 +59,7 @@ EditorDebuggerNode::EditorDebuggerNode() { 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); + tabs->set_tab_alignment(TabContainer::ALIGNMENT_LEFT); tabs->set_tabs_visible(false); tabs->connect("tab_changed", callable_mp(this, &EditorDebuggerNode::_debugger_changed)); add_child(tabs); @@ -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,21 @@ ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const { return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(0)); } -Error EditorDebuggerNode::start(const String &p_protocol) { +String EditorDebuggerNode::get_server_uri() const { + ERR_FAIL_COND_V(server.is_null(), ""); + return server->get_uri(); +} + +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; } @@ -264,15 +270,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(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(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(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 +338,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(); @@ -487,6 +498,8 @@ 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) { @@ -494,8 +507,8 @@ void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) { set_breakpoint(p_path, p_lines[i], true); } - for (Map<Breakpoint, bool>::Element *E = breakpoints.front(); E; E = E->next()) { - Breakpoint b = E->key(); + 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); } diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h index 39a95326be..135122db68 100644 --- a/editor/debugger/editor_debugger_node.h +++ b/editor/debugger/editor_debugger_node.h @@ -188,8 +188,9 @@ public: void set_camera_override(CameraOverride p_override); CameraOverride get_camera_override(); - Error start(const String &p_protocol = "tcp://"); + String get_server_uri() const; + Error start(const String &p_uri = "tcp://"); void stop(); void add_debugger_plugin(const Ref<Script> &p_script); diff --git a/editor/debugger/editor_debugger_server.cpp b/editor/debugger/editor_debugger_server.cpp index e8524e0702..34904d55aa 100644 --- a/editor/debugger/editor_debugger_server.cpp +++ b/editor/debugger/editor_debugger_server.cpp @@ -41,15 +41,18 @@ class EditorDebuggerServerTCP : public EditorDebuggerServer { private: Ref<TCPServer> server; + String endpoint; public: static EditorDebuggerServer *create(const String &p_protocol); - virtual void poll() {} - virtual Error start(); - virtual void stop(); - virtual bool is_active() const; - virtual bool is_connection_available() const; - virtual Ref<RemoteDebuggerPeer> take_connection(); + + virtual void poll() override {} + virtual String get_uri() const override; + virtual Error start(const String &p_uri) override; + virtual void stop() override; + virtual bool is_active() const override; + virtual bool is_connection_available() const override; + virtual Ref<RemoteDebuggerPeer> take_connection() override; EditorDebuggerServerTCP(); }; @@ -63,14 +66,42 @@ EditorDebuggerServerTCP::EditorDebuggerServerTCP() { server.instantiate(); } -Error EditorDebuggerServerTCP::start() { - int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); - const Error err = server->listen(remote_port); - if (err != OK) { - EditorNode::get_log()->add_message(String("Error listening on port ") + itos(remote_port), EditorLog::MSG_TYPE_ERROR); - return err; +String EditorDebuggerServerTCP::get_uri() const { + return endpoint; +} + +Error EditorDebuggerServerTCP::start(const String &p_uri) { + // Default host and port + String bind_host = (String)EditorSettings::get_singleton()->get("network/debug/remote_host"); + int bind_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); + + // Optionally override + 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); } - return err; + + // Try listening on ports + const int max_attempts = 5; + for (int attempt = 1;; ++attempt) { + const Error err = server->listen(bind_port, bind_host); + if (err == OK) { + break; + } + if (attempt >= max_attempts) { + EditorNode::get_log()->add_message(vformat("Cannot listen on port %d, remote debugging unavailable.", bind_port), EditorLog::MSG_TYPE_ERROR); + return err; + } + int last_port = bind_port++; + EditorNode::get_log()->add_message(vformat("Cannot listen on port %d, trying %d instead.", last_port, bind_port), EditorLog::MSG_TYPE_WARNING); + } + + // Endpoint that the client should connect to + endpoint = vformat("tcp://%s:%d", bind_host, bind_port); + + return OK; } void EditorDebuggerServerTCP::stop() { diff --git a/editor/debugger/editor_debugger_server.h b/editor/debugger/editor_debugger_server.h index 6216d0df3d..6a4ca895d1 100644 --- a/editor/debugger/editor_debugger_server.h +++ b/editor/debugger/editor_debugger_server.h @@ -47,8 +47,10 @@ public: static void register_protocol_handler(const String &p_protocol, CreateServerFunc p_func); static EditorDebuggerServer *create(const String &p_protocol); + + virtual String get_uri() const = 0; 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 1feab98948..be369aa0ca 100644 --- a/editor/debugger/editor_debugger_tree.cpp +++ b/editor/debugger/editor_debugger_tree.cpp @@ -107,7 +107,7 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position) { item_menu->clear(); 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->set_position(get_screen_position() + get_local_mouse_position()); item_menu->popup(); } diff --git a/editor/debugger/editor_network_profiler.cpp b/editor/debugger/editor_network_profiler.cpp index 9479fbd5d4..8b1f0085d5 100644 --- a/editor/debugger/editor_network_profiler.cpp +++ b/editor/debugger/editor_network_profiler.cpp @@ -56,18 +56,18 @@ 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_alignment(j, j > 0 ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_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)); } } @@ -149,7 +149,7 @@ EditorNetworkProfiler::EditorNetworkProfiler() { incoming_bandwidth_text = memnew(LineEdit); incoming_bandwidth_text->set_editable(false); incoming_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); - incoming_bandwidth_text->set_align(LineEdit::Align::ALIGN_RIGHT); + incoming_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); hb->add_child(incoming_bandwidth_text); Control *down_up_spacer = memnew(Control); @@ -163,7 +163,7 @@ EditorNetworkProfiler::EditorNetworkProfiler() { outgoing_bandwidth_text = memnew(LineEdit); outgoing_bandwidth_text->set_editable(false); outgoing_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); - outgoing_bandwidth_text->set_align(LineEdit::Align::ALIGN_RIGHT); + outgoing_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); hb->add_child(outgoing_bandwidth_text); // Set initial texts in the incoming/outgoing bandwidth labels diff --git a/editor/debugger/editor_performance_profiler.cpp b/editor/debugger/editor_performance_profiler.cpp index 08ed675d16..6106f1755d 100644 --- a/editor/debugger/editor_performance_profiler.cpp +++ b/editor/debugger/editor_performance_profiler.cpp @@ -132,14 +132,14 @@ void EditorPerformanceProfiler::_monitor_draw() { rect.size -= graph_style_box->get_minimum_size(); 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); + monitor_draw->draw_string(graph_font, rect.position + Point2(0, graph_font->get_ascent(font_size)), current.item->get_text(0), HORIZONTAL_ALIGNMENT_LEFT, rect.size.x, font_size, draw_color); draw_color.a = 0.9f; float value_position = rect.size.width - graph_font->get_string_size(current.item->get_text(1), font_size).width; if (value_position < 0) { value_position = 0; } - monitor_draw->draw_string(graph_font, rect.position + Point2(value_position, graph_font->get_ascent(font_size)), current.item->get_text(1), HALIGN_LEFT, rect.size.x, font_size, draw_color); + monitor_draw->draw_string(graph_font, rect.position + Point2(value_position, graph_font->get_ascent(font_size)), current.item->get_text(1), HORIZONTAL_ALIGNMENT_LEFT, rect.size.x, font_size, draw_color); rect.position.y += graph_font->get_height(font_size); rect.size.height -= graph_font->get_height(font_size); @@ -152,12 +152,12 @@ void EditorPerformanceProfiler::_monitor_draw() { Color horizontal_line_color; horizontal_line_color.set_hsv(draw_color.get_h(), draw_color.get_s() * 0.5f, draw_color.get_v() * 0.5f, 0.3f); monitor_draw->draw_line(rect.position, rect.position + Vector2(rect.size.width, 0), horizontal_line_color, Math::round(EDSCALE)); - monitor_draw->draw_string(graph_font, rect.position + Vector2(0, graph_font->get_ascent(font_size)), _create_label(current.max, current.type), HALIGN_LEFT, rect.size.width, font_size, horizontal_line_color); + monitor_draw->draw_string(graph_font, rect.position + Vector2(0, graph_font->get_ascent(font_size)), _create_label(current.max, current.type), HORIZONTAL_ALIGNMENT_LEFT, rect.size.width, font_size, horizontal_line_color); for (int j = 0; j < line_count; j++) { Vector2 y_offset = Vector2(0, rect.size.height * (1.0f - float(j) / float(line_count))); monitor_draw->draw_line(rect.position + y_offset, rect.position + Vector2(rect.size.width, 0) + y_offset, horizontal_line_color, Math::round(EDSCALE)); - monitor_draw->draw_string(graph_font, rect.position - Vector2(0, graph_font->get_descent(font_size)) + y_offset, _create_label(current.max * float(j) / float(line_count), current.type), HALIGN_LEFT, rect.size.width, font_size, horizontal_line_color); + monitor_draw->draw_string(graph_font, rect.position - Vector2(0, graph_font->get_descent(font_size)) + y_offset, _create_label(current.max * float(j) / float(line_count), current.type), HORIZONTAL_ALIGNMENT_LEFT, rect.size.width, font_size, horizontal_line_color); } } @@ -191,7 +191,7 @@ void EditorPerformanceProfiler::_monitor_draw() { if (text_top_left_position.y < 0) { text_top_left_position.y = h2 + MARKER_MARGIN; } - monitor_draw->draw_string(graph_font, rect.position + text_top_left_position + Point2(0, graph_font->get_ascent(font_size)), label, HALIGN_LEFT, rect.size.x, font_size, line_color); + monitor_draw->draw_string(graph_font, rect.position + text_top_left_position + Point2(0, graph_font->get_ascent(font_size)), label, HORIZONTAL_ALIGNMENT_LEFT, rect.size.x, font_size, line_color); } prev = h2; e = e->next(); @@ -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)) { @@ -378,8 +378,8 @@ EditorPerformanceProfiler::EditorPerformanceProfiler() { info_message = memnew(Label); 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_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); + info_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); 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); diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index fa9c9f61f5..2bdacb51b8 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -355,7 +355,7 @@ void EditorProfiler::_update_frame() { item->set_metadata(0, it.signature); item->set_metadata(1, it.script); item->set_metadata(2, it.line); - item->set_text_align(2, TreeItem::ALIGN_RIGHT); + item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_RIGHT); item->set_tooltip(0, it.name + "\n" + it.script + ":" + itos(it.line)); float time = dtime == DISPLAY_SELF_TIME ? it.self : it.total; @@ -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) @@ -515,11 +515,11 @@ Vector<Vector<String>> EditorProfiler::get_data_as_csv() const { if (!m.valid) { continue; } - for (Map<StringName, Metric::Category *>::Element *E = m.category_ptrs.front(); E; E = E->next()) { - possible_signatures.insert(E->key()); + for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) { + possible_signatures.insert(E.key); } - for (Map<StringName, Metric::Category::Item *>::Element *E = m.item_ptrs.front(); E; E = E->next()) { - possible_signatures.insert(E->key()); + for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) { + possible_signatures.insert(E.key); } } @@ -557,11 +557,11 @@ Vector<Vector<String>> EditorProfiler::get_data_as_csv() const { values.clear(); values.resize(possible_signatures.size()); - for (Map<StringName, Metric::Category *>::Element *E = m.category_ptrs.front(); E; E = E->next()) { - values.write[sig_map[E->key()]] = String::num_real(E->value()->total_time); + for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) { + values.write[sig_map[E.key]] = String::num_real(E.value->total_time); } - for (Map<StringName, Metric::Category::Item *>::Element *E = m.item_ptrs.front(); E; E = E->next()) { - values.write[sig_map[E->key()]] = String::num_real(E->value()->total); + 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); diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index f17ad0d36c..a4e4ed4020 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -370,8 +370,8 @@ void EditorVisualProfiler::_update_frame(bool p_focus_selected) { float total_gpu = E->get_metadata(2); total_cpu += cpu_time; total_gpu += gpu_time; - E->set_metadata(1, cpu_time); - E->set_metadata(2, gpu_time); + E->set_metadata(1, total_cpu); + E->set_metadata(2, total_gpu); } category->set_icon(0, track_icon); @@ -462,7 +462,7 @@ void EditorVisualProfiler::_graph_tex_draw() { graph->draw_line(Vector2(0, frame_y), Vector2(half_width, frame_y), Color(1, 1, 1, 0.3)); String limit_str = String::num(graph_limit, 2); - graph->draw_string(font, Vector2(half_width - font->get_string_size(limit_str, font_size).x - 2, frame_y - 2), limit_str, HALIGN_LEFT, -1, font_size, Color(1, 1, 1, 0.6)); + graph->draw_string(font, Vector2(half_width - font->get_string_size(limit_str, font_size).x - 2, frame_y - 2), limit_str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 0.6)); } if (graph_height_gpu > 0) { @@ -473,11 +473,11 @@ void EditorVisualProfiler::_graph_tex_draw() { graph->draw_line(Vector2(half_width, frame_y), Vector2(graph->get_size().x, frame_y), Color(1, 1, 1, 0.3)); String limit_str = String::num(graph_limit, 2); - graph->draw_string(font, Vector2(half_width * 2 - font->get_string_size(limit_str, font_size).x - 2, frame_y - 2), limit_str, HALIGN_LEFT, -1, font_size, Color(1, 1, 1, 0.6)); + graph->draw_string(font, Vector2(half_width * 2 - font->get_string_size(limit_str, font_size).x - 2, frame_y - 2), limit_str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 0.6)); } - graph->draw_string(font, Vector2(font->get_string_size("X", font_size).x, font->get_ascent(font_size) + 2), "CPU:", HALIGN_LEFT, -1, font_size, Color(1, 1, 1, 0.8)); - graph->draw_string(font, Vector2(font->get_string_size("X", font_size).x + graph->get_size().width / 2, font->get_ascent(font_size) + 2), "GPU:", HALIGN_LEFT, -1, font_size, Color(1, 1, 1, 0.8)); + graph->draw_string(font, Vector2(font->get_string_size("X", font_size).x, font->get_ascent(font_size) + 2), "CPU:", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 0.8)); + graph->draw_string(font, Vector2(font->get_string_size("X", font_size).x + graph->get_size().width / 2, font->get_ascent(font_size) + 2), "GPU:", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 0.8)); /* if (hover_metric != -1 && frame_metrics[hover_metric].valid) { @@ -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; diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 2a5013893f..4f7dc78017 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -79,7 +79,7 @@ void ScriptEditorDebugger::_put_msg(String p_message, Array p_data) { void ScriptEditorDebugger::debug_copy() { String msg = reason->get_text(); - if (msg == "") { + if (msg.is_empty()) { return; } DisplayServer::get_singleton()->clipboard_set(msg); @@ -147,7 +147,7 @@ void ScriptEditorDebugger::update_tabs() { } 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) { @@ -296,6 +296,7 @@ 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()); @@ -311,7 +312,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da if (is_move_to_foreground()) { DisplayServer::get_singleton()->window_move_to_foreground(); } - if (error != "") { + if (!error.is_empty()) { tabs->set_current_tab(0); } profiler->set_enabled(false); @@ -396,15 +397,15 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da s->select(0); } } - emit_signal("stack_dump", stack_dump_info); + 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("stack_frame_vars", p_data[0]); + emit_signal(SNAME("stack_frame_vars"), p_data[0]); } else if (p_msg == "stack_frame_var") { inspector->add_stack_variable(p_data); - emit_signal("stack_frame_var", p_data); + emit_signal(SNAME("stack_frame_var"), p_data); } else if (p_msg == "output") { ERR_FAIL_COND(p_data.size() != 2); @@ -433,7 +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("output", output_strings[i]); + emit_signal(SNAME("output"), output_strings[i]); } } else if (p_msg == "performance:profile_frame") { Vector<float> frame_data; @@ -498,7 +499,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da error->set_icon(0, get_theme_icon(oe.warning ? "Warning" : "Error", "EditorIcons")); error->set_text(0, time); - error->set_text_align(0, TreeItem::ALIGN_LEFT); + error->set_text_alignment(0, HORIZONTAL_ALIGNMENT_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) { @@ -519,7 +524,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da TreeItem *cpp_cond = error_tree->create_item(error); cpp_cond->set_text(0, "<" + TTR("C++ Error") + ">"); cpp_cond->set_text(1, oe.error); - cpp_cond->set_text_align(0, TreeItem::ALIGN_LEFT); + cpp_cond->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); tooltip += TTR("C++ Error:") + " " + oe.error + "\n"; if (source_is_project_file) { cpp_cond->set_metadata(0, source_meta); @@ -537,7 +542,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da TreeItem *cpp_source = error_tree->create_item(error); cpp_source->set_text(0, "<" + (source_is_project_file ? TTR("Source") : TTR("C++ Source")) + ">"); cpp_source->set_text(1, source_txt); - cpp_source->set_text_align(0, TreeItem::ALIGN_LEFT); + cpp_source->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); tooltip += (source_is_project_file ? TTR("Source:") : TTR("C++ Source:")) + " " + source_txt + "\n"; // Set metadata to highlight error line in scripts. @@ -560,7 +565,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da if (i == 0) { stack_trace->set_text(0, "<" + TTR("Stack Trace") + ">"); - stack_trace->set_text_align(0, TreeItem::ALIGN_LEFT); + stack_trace->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); error->set_metadata(0, meta); tooltip += TTR("Stack Trace:") + "\n"; } @@ -573,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 { @@ -872,9 +883,18 @@ void ScriptEditorDebugger::_clear_execution() { 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; @@ -1063,7 +1083,7 @@ void ScriptEditorDebugger::_method_changed(Object *p_base, const StringName &p_n Resource *res = Object::cast_to<Resource>(p_base); - if (res && res->get_path() != String()) { + if (res && !res->get_path().is_empty()) { String respath = res->get_path(); int pathid = _get_res_path_cache(respath); @@ -1093,7 +1113,7 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p if (p_value.is_ref()) { Ref<Resource> res = p_value; - if (res.is_valid() && res->get_path() != String()) { + if (res.is_valid() && !res->get_path().is_empty()) { Array msg; msg.push_back(pathid); msg.push_back(p_property); @@ -1113,13 +1133,13 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p Resource *res = Object::cast_to<Resource>(p_base); - if (res && res->get_path() != String()) { + if (res && !res->get_path().is_empty()) { String respath = res->get_path(); int pathid = _get_res_path_cache(respath); if (p_value.is_ref()) { Ref<Resource> res2 = p_value; - if (res2.is_valid() && res2->get_path() != String()) { + if (res2.is_valid() && !res2->get_path().is_empty()) { Array msg; msg.push_back(pathid); msg.push_back(p_property); @@ -1224,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(""); } @@ -1390,12 +1410,17 @@ 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(SNAME("ActionCopy"), SNAME("EditorIcons")), TTR("Copy Error"), ACTION_COPY_ERROR); @@ -1403,7 +1428,7 @@ void ScriptEditorDebugger::_error_tree_item_rmb_selected(const Vector2 &p_pos) { } if (item_menu->get_item_count() > 0) { - item_menu->set_position(error_tree->get_global_position() + p_pos); + item_menu->set_position(error_tree->get_screen_position() + p_pos); item_menu->popup(); } } @@ -1503,6 +1528,9 @@ void ScriptEditorDebugger::_bind_methods() { 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) { @@ -1546,7 +1574,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { editor = p_editor; tabs = memnew(TabContainer); - tabs->set_tab_align(TabContainer::ALIGN_LEFT); + tabs->set_tab_alignment(TabContainer::ALIGNMENT_LEFT); 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)); @@ -1645,28 +1673,31 @@ 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); - - 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); + error_hbox->add_child(space); + + 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); diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 499dda86da..76209aef46 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -94,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; @@ -205,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(); |