Merge pull request #51639 from Ev1lbl0w/gsoc21-dap
Implement more advanced features for DAP
This commit is contained in:
commit
58c30b2626
20 changed files with 845 additions and 67 deletions
|
@ -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();
|
||||
|
|
|
@ -4770,6 +4770,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);
|
||||
|
|
|
@ -898,6 +898,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"
|
||||
|
@ -479,6 +480,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()) {
|
||||
|
@ -3484,6 +3508,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();
|
||||
|
||||
|
|
|
@ -155,6 +155,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;
|
||||
|
@ -374,6 +376,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);
|
||||
}
|
||||
|
|
|
@ -225,6 +225,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;
|
||||
|
|
|
@ -121,6 +121,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 a new issue