Implemented advanced features of DAP
Respect client "supportsVariableType" capability Implement "breakpointLocations" request Implement "restart" request Implement "evaluate" request Fix error messages not being shown, and improved wrong path message Removed thread option and behavior Implemented detailed inspection of complex variables Fix "const"ness of functions Added a configurable timeout for requests Implement Godot custom data request/event Implement syncing of breakpoints Added support for debugging native platforms
This commit is contained in:
parent
d8a8d32f2e
commit
292ed61c18
|
@ -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::UNKNOWN:
|
||||
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}\"";
|
||||
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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,460 @@ 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;
|
||||
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)) {
|
||||
|
@ -211,7 +665,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 +676,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 +686,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 +740,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());
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
updated_breakpoints.push_back(breakpoint.to_json());
|
||||
}
|
||||
|
||||
return updated_breakpoints;
|
||||
|
@ -347,6 +822,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 +922,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 +967,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 +994,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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
@ -487,6 +488,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) {
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
@ -872,6 +873,16 @@ 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;
|
||||
|
@ -1503,6 +1514,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) {
|
||||
|
|
|
@ -205,6 +205,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();
|
||||
|
|
|
@ -4759,6 +4759,10 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
|
|||
return true;
|
||||
}
|
||||
|
||||
Error EditorNode::run_play_native(int p_idx, int p_platform) {
|
||||
return run_native->run_native(p_idx, p_platform);
|
||||
}
|
||||
|
||||
void EditorNode::run_play() {
|
||||
_menu_option_confirm(RUN_STOP, true);
|
||||
_run(false);
|
||||
|
|
|
@ -893,6 +893,7 @@ public:
|
|||
|
||||
bool ensure_main_scene(bool p_from_native);
|
||||
|
||||
Error run_play_native(int p_idx, int p_platform);
|
||||
void run_play();
|
||||
void run_play_current();
|
||||
void run_play_custom(const String &p_custom);
|
||||
|
|
|
@ -52,8 +52,8 @@ void EditorRunNative::_notification(int p_what) {
|
|||
small_icon.instantiate();
|
||||
small_icon->create_from_image(im);
|
||||
MenuButton *mb = memnew(MenuButton);
|
||||
mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::_run_native), varray(i));
|
||||
mb->connect("pressed", callable_mp(this, &EditorRunNative::_run_native), varray(-1, i));
|
||||
mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::run_native), varray(i));
|
||||
mb->connect("pressed", callable_mp(this, &EditorRunNative::run_native), varray(-1, i));
|
||||
mb->set_icon(small_icon);
|
||||
add_child(mb);
|
||||
menus[i] = mb;
|
||||
|
@ -93,22 +93,22 @@ void EditorRunNative::_notification(int p_what) {
|
|||
}
|
||||
}
|
||||
|
||||
void EditorRunNative::_run_native(int p_idx, int p_platform) {
|
||||
Error EditorRunNative::run_native(int p_idx, int p_platform) {
|
||||
if (!EditorNode::get_singleton()->ensure_main_scene(true)) {
|
||||
resume_idx = p_idx;
|
||||
resume_platform = p_platform;
|
||||
return;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(p_platform);
|
||||
ERR_FAIL_COND(eep.is_null());
|
||||
ERR_FAIL_COND_V(eep.is_null(), ERR_UNAVAILABLE);
|
||||
|
||||
if (p_idx == -1) {
|
||||
if (eep->get_options_count() == 1) {
|
||||
menus[p_platform]->get_popup()->hide();
|
||||
p_idx = 0;
|
||||
} else {
|
||||
return;
|
||||
return ERR_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) {
|
|||
|
||||
if (preset.is_null()) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("No runnable export preset found for this platform.\nPlease add a runnable preset in the Export menu or define an existing preset as runnable."));
|
||||
return;
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
emit_signal(SNAME("native_run"), preset);
|
||||
|
@ -149,11 +149,11 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) {
|
|||
flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION;
|
||||
}
|
||||
|
||||
eep->run(preset, p_idx, flags);
|
||||
return eep->run(preset, p_idx, flags);
|
||||
}
|
||||
|
||||
void EditorRunNative::resume_run_native() {
|
||||
_run_native(resume_idx, resume_platform);
|
||||
run_native(resume_idx, resume_platform);
|
||||
}
|
||||
|
||||
void EditorRunNative::_bind_methods() {
|
||||
|
|
|
@ -43,13 +43,12 @@ class EditorRunNative : public HBoxContainer {
|
|||
int resume_idx;
|
||||
int resume_platform;
|
||||
|
||||
void _run_native(int p_idx, int p_platform);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
Error run_native(int p_idx, int p_platform);
|
||||
bool is_deploy_debug_remote_enabled() const;
|
||||
|
||||
void resume_run_native();
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "core/os/keyboard.h"
|
||||
#include "core/os/os.h"
|
||||
#include "editor/debugger/editor_debugger_node.h"
|
||||
#include "editor/debugger/script_editor_debugger.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_run_script.h"
|
||||
#include "editor/editor_scale.h"
|
||||
|
@ -486,6 +487,29 @@ void ScriptEditor::_clear_execution(REF p_script) {
|
|||
}
|
||||
}
|
||||
|
||||
void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) {
|
||||
Ref<Script> script = Object::cast_to<Script>(*p_script);
|
||||
if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) {
|
||||
if (edit(p_script, p_line, 0, false)) {
|
||||
editor->push_item(p_script.ptr());
|
||||
|
||||
ScriptEditorBase *se = _get_current_editor();
|
||||
if (se) {
|
||||
se->set_breakpoint(p_line, p_enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEditor::_clear_breakpoints() {
|
||||
for (int i = 0; i < tab_container->get_child_count(); i++) {
|
||||
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i));
|
||||
if (se) {
|
||||
se->clear_breakpoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScriptEditorBase *ScriptEditor::_get_current_editor() const {
|
||||
int selected = tab_container->get_current_tab();
|
||||
if (selected < 0 || selected >= tab_container->get_child_count()) {
|
||||
|
@ -3488,6 +3512,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
|
|||
debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution));
|
||||
debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution));
|
||||
debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked));
|
||||
debugger->get_default_debugger()->connect("set_breakpoint", callable_mp(this, &ScriptEditor::_set_breakpoint));
|
||||
debugger->get_default_debugger()->connect("clear_breakpoints", callable_mp(this, &ScriptEditor::_clear_breakpoints));
|
||||
|
||||
menu_hb->add_spacer();
|
||||
|
||||
|
|
|
@ -152,6 +152,8 @@ public:
|
|||
virtual void tag_saved_version() = 0;
|
||||
virtual void reload(bool p_soft) {}
|
||||
virtual Array get_breakpoints() = 0;
|
||||
virtual void set_breakpoint(int p_line, bool p_enabled) = 0;
|
||||
virtual void clear_breakpoints() = 0;
|
||||
virtual void add_callback(const String &p_function, PackedStringArray p_args) = 0;
|
||||
virtual void update_settings() = 0;
|
||||
virtual void set_debugger_active(bool p_active) = 0;
|
||||
|
@ -370,6 +372,8 @@ class ScriptEditor : public PanelContainer {
|
|||
void _breaked(bool p_breaked, bool p_can_debug);
|
||||
void _update_window_menu();
|
||||
void _script_created(Ref<Script> p_script);
|
||||
void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled);
|
||||
void _clear_breakpoints();
|
||||
|
||||
ScriptEditorBase *_get_current_editor() const;
|
||||
Array _get_open_script_editors() const;
|
||||
|
|
|
@ -1370,6 +1370,14 @@ Array ScriptTextEditor::get_breakpoints() {
|
|||
return code_editor->get_text_editor()->get_breakpointed_lines();
|
||||
}
|
||||
|
||||
void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) {
|
||||
code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled);
|
||||
}
|
||||
|
||||
void ScriptTextEditor::clear_breakpoints() {
|
||||
code_editor->get_text_editor()->clear_breakpointed_lines();
|
||||
}
|
||||
|
||||
void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) {
|
||||
code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this);
|
||||
}
|
||||
|
|
|
@ -224,6 +224,8 @@ public:
|
|||
|
||||
virtual void reload(bool p_soft) override;
|
||||
virtual Array get_breakpoints() override;
|
||||
virtual void set_breakpoint(int p_line, bool p_enabled) override;
|
||||
virtual void clear_breakpoints() override;
|
||||
|
||||
virtual void add_callback(const String &p_function, PackedStringArray p_args) override;
|
||||
virtual void update_settings() override;
|
||||
|
|
|
@ -120,6 +120,8 @@ public:
|
|||
virtual void set_edit_state(const Variant &p_state) override;
|
||||
virtual Vector<String> get_functions() override;
|
||||
virtual Array get_breakpoints() override;
|
||||
virtual void set_breakpoint(int p_line, bool p_enabled) override{};
|
||||
virtual void clear_breakpoints() override{};
|
||||
virtual void goto_line(int p_line, bool p_with_error = false) override;
|
||||
void goto_line_selection(int p_line, int p_begin, int p_end);
|
||||
virtual void set_executing_line(int p_line) override;
|
||||
|
|
|
@ -311,6 +311,8 @@ public:
|
|||
virtual void tag_saved_version() override;
|
||||
virtual void reload(bool p_soft) override;
|
||||
virtual Array get_breakpoints() override;
|
||||
virtual void set_breakpoint(int p_line, bool p_enable) override{};
|
||||
virtual void clear_breakpoints() override{};
|
||||
virtual void add_callback(const String &p_function, PackedStringArray p_args) override;
|
||||
virtual void update_settings() override;
|
||||
virtual bool show_members_overview() override;
|
||||
|
|
Loading…
Reference in New Issue