Skip to content

Commit 783b150

Browse files
committed
Merge pull request godotengine#97585 from rsubtil/feature-dap_object_inspection
Support object inspection through DAP
2 parents 84768ab + 9dd7a8a commit 783b150

File tree

6 files changed

+262
-22
lines changed

6 files changed

+262
-22
lines changed

editor/debugger/debug_adapter/debug_adapter_parser.cpp

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
#include "debug_adapter_parser.h"
3232

33+
#include "editor/debugger/debug_adapter/debug_adapter_types.h"
3334
#include "editor/debugger/editor_debugger_node.h"
3435
#include "editor/debugger/script_editor_debugger.h"
3536
#include "editor/export/editor_export_platform.h"
@@ -442,26 +443,34 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
442443
return Dictionary();
443444
}
444445

445-
Dictionary response = prepare_success_response(p_params), body;
446-
response["body"] = body;
447-
448446
Dictionary args = p_params["arguments"];
449447
int variable_id = args["variablesReference"];
450448

451-
HashMap<int, Array>::Iterator E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id);
449+
if (HashMap<int, Array>::Iterator E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id); E) {
450+
Dictionary response = prepare_success_response(p_params);
451+
Dictionary body;
452+
response["body"] = body;
452453

453-
if (E) {
454454
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
455455
for (int i = 0; i < E->value.size(); i++) {
456456
Dictionary variable = E->value[i];
457457
variable.erase("type");
458458
}
459459
}
460+
460461
body["variables"] = E ? E->value : Array();
461462
return response;
462463
} else {
463-
return Dictionary();
464+
// If the requested variable is an object, it needs to be requested from the debuggee.
465+
ObjectID object_id = DebugAdapterProtocol::get_singleton()->search_object_id(variable_id);
466+
467+
if (object_id.is_null()) {
468+
return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
469+
}
470+
471+
DebugAdapterProtocol::get_singleton()->request_remote_object(object_id);
464472
}
473+
return Dictionary();
465474
}
466475

467476
Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const {
@@ -479,16 +488,27 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const {
479488
}
480489

481490
Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const {
482-
Dictionary response = prepare_success_response(p_params), body;
483-
response["body"] = body;
484-
485491
Dictionary args = p_params["arguments"];
492+
String expression = args["expression"];
493+
int frame_id = args.has("frameId") ? static_cast<int>(args["frameId"]) : DebugAdapterProtocol::get_singleton()->_current_frame;
486494

487-
String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]);
488-
body["result"] = value;
489-
body["variablesReference"] = 0;
495+
if (HashMap<String, DAP::Variable>::Iterator E = DebugAdapterProtocol::get_singleton()->eval_list.find(expression); E) {
496+
Dictionary response = prepare_success_response(p_params);
497+
Dictionary body;
498+
response["body"] = body;
490499

491-
return response;
500+
DAP::Variable var = E->value;
501+
502+
body["result"] = var.value;
503+
body["variablesReference"] = var.variablesReference;
504+
505+
// Since an evaluation can alter the state of the debuggee, they are volatile, and should only be used once
506+
DebugAdapterProtocol::get_singleton()->eval_list.erase(E->key);
507+
return response;
508+
} else {
509+
DebugAdapterProtocol::get_singleton()->request_remote_evaluate(expression, frame_id);
510+
}
511+
return Dictionary();
492512
}
493513

494514
Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {

editor/debugger/debug_adapter/debug_adapter_protocol.cpp

Lines changed: 199 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
#include "core/config/project_settings.h"
3434
#include "core/debugger/debugger_marshalls.h"
3535
#include "core/io/json.h"
36+
#include "core/io/marshalls.h"
3637
#include "editor/debugger/script_editor_debugger.h"
37-
#include "editor/doc_tools.h"
3838
#include "editor/editor_log.h"
3939
#include "editor/editor_node.h"
4040
#include "editor/editor_settings.h"
@@ -186,6 +186,8 @@ void DebugAdapterProtocol::reset_stack_info() {
186186

187187
stackframe_list.clear();
188188
variable_list.clear();
189+
object_list.clear();
190+
object_pending_set.clear();
189191
}
190192

191193
int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
@@ -671,12 +673,194 @@ int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
671673
variable_list.insert(id, arr);
672674
return id;
673675
}
676+
case Variant::OBJECT: {
677+
// Objects have to be requested from the debuggee. This has do be done
678+
// in a lazy way, as retrieving object properties takes time.
679+
EncodedObjectAsID *encoded_obj = Object::cast_to<EncodedObjectAsID>(p_var);
680+
681+
// Object may be null; in that case, return early.
682+
if (!encoded_obj) {
683+
return 0;
684+
}
685+
686+
// Object may have been already requested.
687+
ObjectID object_id = encoded_obj->get_object_id();
688+
if (object_list.has(object_id)) {
689+
return object_list[object_id];
690+
}
691+
692+
// Queue requesting the object.
693+
int id = variable_id++;
694+
object_list.insert(object_id, id);
695+
return id;
696+
}
674697
default:
675698
// Simple atomic stuff, or too complex to be manipulated
676699
return 0;
677700
}
678701
}
679702

703+
void DebugAdapterProtocol::parse_object(SceneDebuggerObject &p_obj) {
704+
// If the object is not on the pending list, we weren't expecting it. Ignore it.
705+
ObjectID object_id = p_obj.id;
706+
if (!object_pending_set.erase(object_id)) {
707+
return;
708+
}
709+
710+
// Populate DAP::Variable's with the object's properties. These properties will be divided by categories.
711+
Array properties;
712+
Array script_members;
713+
Array script_constants;
714+
Array script_node;
715+
DAP::Variable node_type;
716+
Array node_properties;
717+
718+
for (SceneDebuggerObject::SceneDebuggerProperty &property : p_obj.properties) {
719+
PropertyInfo &info = property.first;
720+
721+
// Script members ("Members/" prefix)
722+
if (info.name.begins_with("Members/")) {
723+
info.name = info.name.trim_prefix("Members/");
724+
script_members.push_back(parse_object_variable(property));
725+
}
726+
727+
// Script constants ("Constants/" prefix)
728+
else if (info.name.begins_with("Constants/")) {
729+
info.name = info.name.trim_prefix("Constants/");
730+
script_constants.push_back(parse_object_variable(property));
731+
}
732+
733+
// Script node ("Node/" prefix)
734+
else if (info.name.begins_with("Node/")) {
735+
info.name = info.name.trim_prefix("Node/");
736+
script_node.push_back(parse_object_variable(property));
737+
}
738+
739+
// Regular categories (with type Variant::NIL)
740+
else if (info.type == Variant::NIL) {
741+
if (!node_properties.is_empty()) {
742+
node_type.value = itos(node_properties.size());
743+
variable_list.insert(node_type.variablesReference, node_properties.duplicate());
744+
properties.push_back(node_type.to_json());
745+
}
746+
747+
node_type.name = info.name;
748+
node_type.type = "Category";
749+
node_type.variablesReference = variable_id++;
750+
node_properties.clear();
751+
}
752+
753+
// Regular properties.
754+
else {
755+
node_properties.push_back(parse_object_variable(property));
756+
}
757+
}
758+
759+
// Add the last category.
760+
if (!node_properties.is_empty()) {
761+
node_type.value = itos(node_properties.size());
762+
variable_list.insert(node_type.variablesReference, node_properties.duplicate());
763+
properties.push_back(node_type.to_json());
764+
}
765+
766+
// Add the script categories, in reverse order to be at the front of the array:
767+
// ( [members; constants; node; category1; category2; ...] )
768+
if (!script_node.is_empty()) {
769+
DAP::Variable node;
770+
node.name = "Node";
771+
node.type = "Category";
772+
node.value = itos(script_node.size());
773+
node.variablesReference = variable_id++;
774+
variable_list.insert(node.variablesReference, script_node);
775+
properties.push_front(node.to_json());
776+
}
777+
778+
if (!script_constants.is_empty()) {
779+
DAP::Variable constants;
780+
constants.name = "Constants";
781+
constants.type = "Category";
782+
constants.value = itos(script_constants.size());
783+
constants.variablesReference = variable_id++;
784+
variable_list.insert(constants.variablesReference, script_constants);
785+
properties.push_front(constants.to_json());
786+
}
787+
788+
if (!script_members.is_empty()) {
789+
DAP::Variable members;
790+
members.name = "Members";
791+
members.type = "Category";
792+
members.value = itos(script_members.size());
793+
members.variablesReference = variable_id++;
794+
variable_list.insert(members.variablesReference, script_members);
795+
properties.push_front(members.to_json());
796+
}
797+
798+
ERR_FAIL_COND(!object_list.has(object_id));
799+
variable_list.insert(object_list[object_id], properties);
800+
}
801+
802+
void DebugAdapterProtocol::parse_evaluation(DebuggerMarshalls::ScriptStackVariable &p_var) {
803+
// If the eval is not on the pending list, we weren't expecting it. Ignore it.
804+
String eval = p_var.name;
805+
if (!eval_pending_list.erase(eval)) {
806+
return;
807+
}
808+
809+
DAP::Variable variable;
810+
variable.name = p_var.name;
811+
variable.value = p_var.value;
812+
variable.type = Variant::get_type_name(p_var.value.get_type());
813+
variable.variablesReference = parse_variant(p_var.value);
814+
815+
eval_list.insert(variable.name, variable);
816+
}
817+
818+
const Variant DebugAdapterProtocol::parse_object_variable(const SceneDebuggerObject::SceneDebuggerProperty &p_property) {
819+
const PropertyInfo &info = p_property.first;
820+
const Variant &value = p_property.second;
821+
822+
DAP::Variable var;
823+
var.name = info.name;
824+
var.type = Variant::get_type_name(info.type);
825+
var.value = value;
826+
var.variablesReference = parse_variant(value);
827+
828+
return var.to_json();
829+
}
830+
831+
ObjectID DebugAdapterProtocol::search_object_id(DAPVarID p_var_id) {
832+
for (const KeyValue<ObjectID, DAPVarID> &E : object_list) {
833+
if (E.value == p_var_id) {
834+
return E.key;
835+
}
836+
}
837+
return ObjectID();
838+
}
839+
840+
bool DebugAdapterProtocol::request_remote_object(const ObjectID &p_object_id) {
841+
// If the object is already on the pending list, we don't need to request it again.
842+
if (object_pending_set.has(p_object_id)) {
843+
return false;
844+
}
845+
846+
EditorDebuggerNode::get_singleton()->get_default_debugger()->request_remote_object(p_object_id);
847+
object_pending_set.insert(p_object_id);
848+
849+
return true;
850+
}
851+
852+
bool DebugAdapterProtocol::request_remote_evaluate(const String &p_eval, int p_stack_frame) {
853+
// If the eval is already on the pending list, we don't need to request it again
854+
if (eval_pending_list.has(p_eval)) {
855+
return false;
856+
}
857+
858+
EditorDebuggerNode::get_singleton()->get_default_debugger()->request_remote_evaluate(p_eval, p_stack_frame);
859+
eval_pending_list.insert(p_eval);
860+
861+
return true;
862+
}
863+
680864
bool DebugAdapterProtocol::process_message(const String &p_text) {
681865
JSON json;
682866
ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Malformed message!");
@@ -986,6 +1170,20 @@ void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_dat
9861170
return;
9871171
}
9881172

1173+
if (p_msg == "scene:inspect_object") {
1174+
// An object was requested from the debuggee; parse it.
1175+
SceneDebuggerObject remote_obj;
1176+
remote_obj.deserialize(p_data);
1177+
1178+
parse_object(remote_obj);
1179+
} else if (p_msg == "evaluation_return") {
1180+
// An evaluation was requested from the debuggee; parse it.
1181+
DebuggerMarshalls::ScriptStackVariable remote_evaluation;
1182+
remote_evaluation.deserialize(p_data);
1183+
1184+
parse_evaluation(remote_evaluation);
1185+
}
1186+
9891187
notify_custom_data(p_msg, p_data);
9901188
}
9911189

editor/debugger/debug_adapter/debug_adapter_protocol.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@
3131
#ifndef DEBUG_ADAPTER_PROTOCOL_H
3232
#define DEBUG_ADAPTER_PROTOCOL_H
3333

34-
#include "core/io/stream_peer.h"
34+
#include "core/debugger/debugger_marshalls.h"
3535
#include "core/io/stream_peer_tcp.h"
3636
#include "core/io/tcp_server.h"
3737

3838
#include "debug_adapter_parser.h"
3939
#include "debug_adapter_types.h"
40+
#include "scene/debugger/scene_debugger.h"
4041

4142
#define DAP_MAX_BUFFER_SIZE 4194304 // 4MB
4243
#define DAP_MAX_CLIENTS 8
@@ -75,6 +76,8 @@ class DebugAdapterProtocol : public Object {
7576

7677
friend class DebugAdapterParser;
7778

79+
using DAPVarID = int;
80+
7881
private:
7982
static DebugAdapterProtocol *singleton;
8083
DebugAdapterParser *parser = nullptr;
@@ -99,25 +102,38 @@ class DebugAdapterProtocol : public Object {
99102
void reset_stack_info();
100103

101104
int parse_variant(const Variant &p_var);
105+
void parse_object(SceneDebuggerObject &p_obj);
106+
const Variant parse_object_variable(const SceneDebuggerObject::SceneDebuggerProperty &p_property);
107+
void parse_evaluation(DebuggerMarshalls::ScriptStackVariable &p_var);
108+
109+
ObjectID search_object_id(DAPVarID p_var_id);
110+
bool request_remote_object(const ObjectID &p_object_id);
111+
bool request_remote_evaluate(const String &p_eval, int p_stack_frame);
102112

103113
bool _initialized = false;
104114
bool _processing_breakpoint = false;
105115
bool _stepping = false;
106116
bool _processing_stackdump = false;
107117
int _remaining_vars = 0;
108118
int _current_frame = 0;
109-
uint64_t _request_timeout = 1000;
119+
uint64_t _request_timeout = 5000;
110120
bool _sync_breakpoints = false;
111121

112122
String _current_request;
113123
Ref<DAPeer> _current_peer;
114124

115125
int breakpoint_id = 0;
116126
int stackframe_id = 0;
117-
int variable_id = 0;
127+
DAPVarID variable_id = 0;
118128
List<DAP::Breakpoint> breakpoint_list;
119129
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame> stackframe_list;
120-
HashMap<int, Array> variable_list;
130+
HashMap<DAPVarID, Array> variable_list;
131+
132+
HashMap<ObjectID, DAPVarID> object_list;
133+
HashSet<ObjectID> object_pending_set;
134+
135+
HashMap<String, DAP::Variable> eval_list;
136+
HashSet<String> eval_pending_list;
121137

122138
public:
123139
friend class DebugAdapterServer;

editor/debugger/editor_expression_evaluator.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ void EditorExpressionEvaluator::_evaluate() {
6464
return;
6565
}
6666

67-
Array expr_data;
68-
expr_data.push_back(expression);
69-
expr_data.push_back(editor_debugger->get_stack_script_frame());
70-
editor_debugger->send_message("evaluate", expr_data);
67+
editor_debugger->request_remote_evaluate(expression, editor_debugger->get_stack_script_frame());
7168

7269
expression_input->clear();
7370
}

0 commit comments

Comments
 (0)