Skip to content

Commit

Permalink
Support object inspection through DAP
Browse files Browse the repository at this point in the history
  • Loading branch information
rsubtil committed Sep 28, 2024
1 parent 4359c28 commit 4af18eb
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 8 deletions.
20 changes: 14 additions & 6 deletions editor/debugger/debug_adapter/debug_adapter_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "debug_adapter_parser.h"

#include "core/object/object_id.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/export/editor_export_platform.h"
Expand Down Expand Up @@ -442,26 +443,33 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
return Dictionary();
}

Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;

Dictionary args = p_params["arguments"];
int variable_id = args["variablesReference"];

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

if (E) {
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
for (int i = 0; i < E->value.size(); i++) {
Dictionary variable = E->value[i];
variable.erase("type");
}
}

body["variables"] = E ? E->value : Array();
return response;
} else {
return Dictionary();
// If the requested variable is an object, it needs to be requested from the debuggee
std::optional<ObjectID> object_id = DebugAdapterProtocol::get_singleton()->search_object_id(variable_id);

if (!object_id) {
return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
}

DebugAdapterProtocol::get_singleton()->request_remote_object(object_id.value());
}
return Dictionary();
}

Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const {
Expand Down
151 changes: 150 additions & 1 deletion editor/debugger/debug_adapter/debug_adapter_protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@

#include "core/config/project_settings.h"
#include "core/debugger/debugger_marshalls.h"
#include "core/error/error_macros.h"
#include "core/io/json.h"
#include "core/io/marshalls.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/doc_tools.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
Expand Down Expand Up @@ -186,6 +187,8 @@ void DebugAdapterProtocol::reset_stack_info() {

stackframe_list.clear();
variable_list.clear();
object_list.clear();
object_pending_set.clear();
}

int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
Expand Down Expand Up @@ -671,12 +674,150 @@ int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
variable_list.insert(id, arr);
return id;
}
case Variant::OBJECT: {
// Objects have to be requested from the debuggee. This has do be done
// in a lazy way, as retrieving object properties takes time.
EncodedObjectAsID *encoded_obj = Object::cast_to<EncodedObjectAsID>(p_var);

// Object may be null; in that case, return early
if (!encoded_obj) {
return 0;
}

// Object may have been already requested
ObjectID object_id = encoded_obj->get_object_id();
if (object_list.has(object_id)) {
return object_list[object_id];
}

// Queue the object to be requested
int id = variable_id++;
object_list.insert(object_id, id);
return id;
}
default:
// Simple atomic stuff, or too complex to be manipulated
return 0;
}
}

void DebugAdapterProtocol::parse_object(SceneDebuggerObject &p_obj) {
// If the object is not on the pending list, we weren't expecting it. Ignore it.
ObjectID object_id = p_obj.id;
if (!object_pending_set.erase(object_id)) {
return;
}

// Populate DAP::Variable's with the object's properties. These properties will be divided by categories.
Array properties;
Array script_members, script_constants, script_node;
DAP::Variable node_type;
Array node_properties;

// Lambda to finish the current category
auto finishCategory = [&node_properties, &node_type, &properties, this]() {
if (!node_properties.is_empty()) {
node_type.value = itos(node_properties.size());
variable_list.insert(node_type.variablesReference, node_properties.duplicate());
properties.push_back(node_type.to_json());
}
};

for (SceneDebuggerObject::SceneDebuggerProperty &property : p_obj.properties) {
PropertyInfo &info = property.first;

// Script members ("Members/" prefix)
if (info.name.begins_with("Members/")) {
info.name = info.name.trim_prefix("Members/");
script_members.push_back(parse_object_variable(property));
}

// Script constants ("Constants/" prefix)
else if (info.name.begins_with("Constants/")) {
info.name = info.name.trim_prefix("Constants/");
script_constants.push_back(parse_object_variable(property));
}

// Script node ("Node/" prefix)
else if (info.name.begins_with("Node/")) {
info.name = info.name.trim_prefix("Node/");
script_node.push_back(parse_object_variable(property));
}

// Regular categories (with type Variant::NIL)
else if (info.type == Variant::NIL) {
finishCategory();

node_type.name = info.name;
node_type.type = "Category";
node_type.variablesReference = variable_id++;
node_properties.clear();
}

// Regular properties
else {
node_properties.push_back(parse_object_variable(property));
}
}

// Add the last category
finishCategory();

// Add the script categories, in reverse order to be at the front of the array
// ( [members; constants; node; category1; category2; ...] )
auto addScriptCategories = [&properties, this](const Array &p_properties, const String &p_name) {
if (!p_properties.is_empty()) {
DAP::Variable var;
var.name = p_name;
var.type = "Category";
var.value = itos(p_properties.size());
var.variablesReference = variable_id++;
variable_list.insert(var.variablesReference, p_properties);
properties.push_front(var.to_json());
}
};

addScriptCategories(script_node, "Node");
addScriptCategories(script_constants, "Constants");
addScriptCategories(script_members, "Members");

ERR_FAIL_COND(!object_list.has(object_id));
variable_list.insert(object_list[object_id], properties);
}

const Variant DebugAdapterProtocol::parse_object_variable(const SceneDebuggerObject::SceneDebuggerProperty &p_property) {
auto &[info, value] = p_property;

DAP::Variable var;
var.name = info.name;
var.type = Variant::get_type_name(info.type);
var.value = value;
var.variablesReference = parse_variant(value);

return var.to_json();
}

std::optional<ObjectID> DebugAdapterProtocol::search_object_id(int p_var_id) {
for (const auto &[object_id, var_id] : object_list) {
if (var_id == p_var_id) {
return object_id;
}
}
return std::nullopt;
}

bool DebugAdapterProtocol::request_remote_object(const ObjectID &p_object_id) {
// If the object is already on the pending list, we don't need to request it again.
if (object_pending_set.has(p_object_id)) {
return false;
}

EditorDebuggerNode::get_singleton()->get_default_debugger()->request_remote_object(p_object_id);
object_pending_set.insert(p_object_id);

return true;
}

bool DebugAdapterProtocol::process_message(const String &p_text) {
JSON json;
ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Malformed message!");
Expand Down Expand Up @@ -986,6 +1127,14 @@ void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_dat
return;
}

if (p_msg == "scene:inspect_object") {
// An object was requested from the debuggee; parse it
SceneDebuggerObject remote_obj;
remote_obj.deserialize(p_data);

parse_object(remote_obj);
}

notify_custom_data(p_msg, p_data);
}

Expand Down
13 changes: 12 additions & 1 deletion editor/debugger/debug_adapter/debug_adapter_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@
#ifndef DEBUG_ADAPTER_PROTOCOL_H
#define DEBUG_ADAPTER_PROTOCOL_H

#include "core/io/stream_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"

#include "core/object/object_id.h"
#include "debug_adapter_parser.h"
#include "debug_adapter_types.h"
#include "scene/debugger/scene_debugger.h"

#include <optional>

#define DAP_MAX_BUFFER_SIZE 4194304 // 4MB
#define DAP_MAX_CLIENTS 8
Expand Down Expand Up @@ -99,6 +102,11 @@ class DebugAdapterProtocol : public Object {
void reset_stack_info();

int parse_variant(const Variant &p_var);
void parse_object(SceneDebuggerObject &p_obj);
const Variant parse_object_variable(const SceneDebuggerObject::SceneDebuggerProperty &p_property);

std::optional<ObjectID> search_object_id(int p_var_id);
bool request_remote_object(const ObjectID &p_object_id);

bool _initialized = false;
bool _processing_breakpoint = false;
Expand All @@ -119,6 +127,9 @@ class DebugAdapterProtocol : public Object {
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame> stackframe_list;
HashMap<int, Array> variable_list;

HashMap<ObjectID, int> object_list;
HashSet<ObjectID> object_pending_set;

public:
friend class DebugAdapterServer;

Expand Down

0 comments on commit 4af18eb

Please sign in to comment.