Merge pull request #97585 from rsubtil/feature-dap_object_inspection
Support object inspection through DAP
This commit is contained in:
commit
783b15078d
6 changed files with 262 additions and 22 deletions
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
#include "debug_adapter_parser.h"
|
#include "debug_adapter_parser.h"
|
||||||
|
|
||||||
|
#include "editor/debugger/debug_adapter/debug_adapter_types.h"
|
||||||
#include "editor/debugger/editor_debugger_node.h"
|
#include "editor/debugger/editor_debugger_node.h"
|
||||||
#include "editor/debugger/script_editor_debugger.h"
|
#include "editor/debugger/script_editor_debugger.h"
|
||||||
#include "editor/export/editor_export_platform.h"
|
#include "editor/export/editor_export_platform.h"
|
||||||
|
@ -442,26 +443,34 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
|
||||||
return Dictionary();
|
return Dictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary response = prepare_success_response(p_params), body;
|
|
||||||
response["body"] = body;
|
|
||||||
|
|
||||||
Dictionary args = p_params["arguments"];
|
Dictionary args = p_params["arguments"];
|
||||||
int variable_id = args["variablesReference"];
|
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);
|
||||||
|
Dictionary body;
|
||||||
|
response["body"] = body;
|
||||||
|
|
||||||
if (E) {
|
|
||||||
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
|
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
|
||||||
for (int i = 0; i < E->value.size(); i++) {
|
for (int i = 0; i < E->value.size(); i++) {
|
||||||
Dictionary variable = E->value[i];
|
Dictionary variable = E->value[i];
|
||||||
variable.erase("type");
|
variable.erase("type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body["variables"] = E ? E->value : Array();
|
body["variables"] = E ? E->value : Array();
|
||||||
return response;
|
return response;
|
||||||
} else {
|
} else {
|
||||||
return Dictionary();
|
// If the requested variable is an object, it needs to be requested from the debuggee.
|
||||||
|
ObjectID object_id = DebugAdapterProtocol::get_singleton()->search_object_id(variable_id);
|
||||||
|
|
||||||
|
if (object_id.is_null()) {
|
||||||
|
return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugAdapterProtocol::get_singleton()->request_remote_object(object_id);
|
||||||
}
|
}
|
||||||
|
return Dictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const {
|
Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const {
|
||||||
|
@ -479,16 +488,27 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const {
|
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"];
|
Dictionary args = p_params["arguments"];
|
||||||
|
String expression = args["expression"];
|
||||||
|
int frame_id = args.has("frameId") ? static_cast<int>(args["frameId"]) : DebugAdapterProtocol::get_singleton()->_current_frame;
|
||||||
|
|
||||||
String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]);
|
if (HashMap<String, DAP::Variable>::Iterator E = DebugAdapterProtocol::get_singleton()->eval_list.find(expression); E) {
|
||||||
body["result"] = value;
|
Dictionary response = prepare_success_response(p_params);
|
||||||
body["variablesReference"] = 0;
|
Dictionary body;
|
||||||
|
response["body"] = body;
|
||||||
|
|
||||||
return response;
|
DAP::Variable var = E->value;
|
||||||
|
|
||||||
|
body["result"] = var.value;
|
||||||
|
body["variablesReference"] = var.variablesReference;
|
||||||
|
|
||||||
|
// Since an evaluation can alter the state of the debuggee, they are volatile, and should only be used once
|
||||||
|
DebugAdapterProtocol::get_singleton()->eval_list.erase(E->key);
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
DebugAdapterProtocol::get_singleton()->request_remote_evaluate(expression, frame_id);
|
||||||
|
}
|
||||||
|
return Dictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {
|
Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {
|
||||||
|
|
|
@ -33,8 +33,8 @@
|
||||||
#include "core/config/project_settings.h"
|
#include "core/config/project_settings.h"
|
||||||
#include "core/debugger/debugger_marshalls.h"
|
#include "core/debugger/debugger_marshalls.h"
|
||||||
#include "core/io/json.h"
|
#include "core/io/json.h"
|
||||||
|
#include "core/io/marshalls.h"
|
||||||
#include "editor/debugger/script_editor_debugger.h"
|
#include "editor/debugger/script_editor_debugger.h"
|
||||||
#include "editor/doc_tools.h"
|
|
||||||
#include "editor/editor_log.h"
|
#include "editor/editor_log.h"
|
||||||
#include "editor/editor_node.h"
|
#include "editor/editor_node.h"
|
||||||
#include "editor/editor_settings.h"
|
#include "editor/editor_settings.h"
|
||||||
|
@ -186,6 +186,8 @@ void DebugAdapterProtocol::reset_stack_info() {
|
||||||
|
|
||||||
stackframe_list.clear();
|
stackframe_list.clear();
|
||||||
variable_list.clear();
|
variable_list.clear();
|
||||||
|
object_list.clear();
|
||||||
|
object_pending_set.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
|
int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
|
||||||
|
@ -671,12 +673,194 @@ int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
|
||||||
variable_list.insert(id, arr);
|
variable_list.insert(id, arr);
|
||||||
return id;
|
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 requesting the object.
|
||||||
|
int id = variable_id++;
|
||||||
|
object_list.insert(object_id, id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// Simple atomic stuff, or too complex to be manipulated
|
// Simple atomic stuff, or too complex to be manipulated
|
||||||
return 0;
|
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;
|
||||||
|
Array script_constants;
|
||||||
|
Array script_node;
|
||||||
|
DAP::Variable node_type;
|
||||||
|
Array node_properties;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the script categories, in reverse order to be at the front of the array:
|
||||||
|
// ( [members; constants; node; category1; category2; ...] )
|
||||||
|
if (!script_node.is_empty()) {
|
||||||
|
DAP::Variable node;
|
||||||
|
node.name = "Node";
|
||||||
|
node.type = "Category";
|
||||||
|
node.value = itos(script_node.size());
|
||||||
|
node.variablesReference = variable_id++;
|
||||||
|
variable_list.insert(node.variablesReference, script_node);
|
||||||
|
properties.push_front(node.to_json());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!script_constants.is_empty()) {
|
||||||
|
DAP::Variable constants;
|
||||||
|
constants.name = "Constants";
|
||||||
|
constants.type = "Category";
|
||||||
|
constants.value = itos(script_constants.size());
|
||||||
|
constants.variablesReference = variable_id++;
|
||||||
|
variable_list.insert(constants.variablesReference, script_constants);
|
||||||
|
properties.push_front(constants.to_json());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!script_members.is_empty()) {
|
||||||
|
DAP::Variable members;
|
||||||
|
members.name = "Members";
|
||||||
|
members.type = "Category";
|
||||||
|
members.value = itos(script_members.size());
|
||||||
|
members.variablesReference = variable_id++;
|
||||||
|
variable_list.insert(members.variablesReference, script_members);
|
||||||
|
properties.push_front(members.to_json());
|
||||||
|
}
|
||||||
|
|
||||||
|
ERR_FAIL_COND(!object_list.has(object_id));
|
||||||
|
variable_list.insert(object_list[object_id], properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugAdapterProtocol::parse_evaluation(DebuggerMarshalls::ScriptStackVariable &p_var) {
|
||||||
|
// If the eval is not on the pending list, we weren't expecting it. Ignore it.
|
||||||
|
String eval = p_var.name;
|
||||||
|
if (!eval_pending_list.erase(eval)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DAP::Variable variable;
|
||||||
|
variable.name = p_var.name;
|
||||||
|
variable.value = p_var.value;
|
||||||
|
variable.type = Variant::get_type_name(p_var.value.get_type());
|
||||||
|
variable.variablesReference = parse_variant(p_var.value);
|
||||||
|
|
||||||
|
eval_list.insert(variable.name, variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Variant DebugAdapterProtocol::parse_object_variable(const SceneDebuggerObject::SceneDebuggerProperty &p_property) {
|
||||||
|
const PropertyInfo &info = p_property.first;
|
||||||
|
const Variant &value = p_property.second;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectID DebugAdapterProtocol::search_object_id(DAPVarID p_var_id) {
|
||||||
|
for (const KeyValue<ObjectID, DAPVarID> &E : object_list) {
|
||||||
|
if (E.value == p_var_id) {
|
||||||
|
return E.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ObjectID();
|
||||||
|
}
|
||||||
|
|
||||||
|
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::request_remote_evaluate(const String &p_eval, int p_stack_frame) {
|
||||||
|
// If the eval is already on the pending list, we don't need to request it again
|
||||||
|
if (eval_pending_list.has(p_eval)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorDebuggerNode::get_singleton()->get_default_debugger()->request_remote_evaluate(p_eval, p_stack_frame);
|
||||||
|
eval_pending_list.insert(p_eval);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool DebugAdapterProtocol::process_message(const String &p_text) {
|
bool DebugAdapterProtocol::process_message(const String &p_text) {
|
||||||
JSON json;
|
JSON json;
|
||||||
ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Malformed message!");
|
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
|
||||||
return;
|
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);
|
||||||
|
} else if (p_msg == "evaluation_return") {
|
||||||
|
// An evaluation was requested from the debuggee; parse it.
|
||||||
|
DebuggerMarshalls::ScriptStackVariable remote_evaluation;
|
||||||
|
remote_evaluation.deserialize(p_data);
|
||||||
|
|
||||||
|
parse_evaluation(remote_evaluation);
|
||||||
|
}
|
||||||
|
|
||||||
notify_custom_data(p_msg, p_data);
|
notify_custom_data(p_msg, p_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,12 +31,13 @@
|
||||||
#ifndef DEBUG_ADAPTER_PROTOCOL_H
|
#ifndef DEBUG_ADAPTER_PROTOCOL_H
|
||||||
#define DEBUG_ADAPTER_PROTOCOL_H
|
#define DEBUG_ADAPTER_PROTOCOL_H
|
||||||
|
|
||||||
#include "core/io/stream_peer.h"
|
#include "core/debugger/debugger_marshalls.h"
|
||||||
#include "core/io/stream_peer_tcp.h"
|
#include "core/io/stream_peer_tcp.h"
|
||||||
#include "core/io/tcp_server.h"
|
#include "core/io/tcp_server.h"
|
||||||
|
|
||||||
#include "debug_adapter_parser.h"
|
#include "debug_adapter_parser.h"
|
||||||
#include "debug_adapter_types.h"
|
#include "debug_adapter_types.h"
|
||||||
|
#include "scene/debugger/scene_debugger.h"
|
||||||
|
|
||||||
#define DAP_MAX_BUFFER_SIZE 4194304 // 4MB
|
#define DAP_MAX_BUFFER_SIZE 4194304 // 4MB
|
||||||
#define DAP_MAX_CLIENTS 8
|
#define DAP_MAX_CLIENTS 8
|
||||||
|
@ -75,6 +76,8 @@ class DebugAdapterProtocol : public Object {
|
||||||
|
|
||||||
friend class DebugAdapterParser;
|
friend class DebugAdapterParser;
|
||||||
|
|
||||||
|
using DAPVarID = int;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static DebugAdapterProtocol *singleton;
|
static DebugAdapterProtocol *singleton;
|
||||||
DebugAdapterParser *parser = nullptr;
|
DebugAdapterParser *parser = nullptr;
|
||||||
|
@ -99,6 +102,13 @@ private:
|
||||||
void reset_stack_info();
|
void reset_stack_info();
|
||||||
|
|
||||||
int parse_variant(const Variant &p_var);
|
int parse_variant(const Variant &p_var);
|
||||||
|
void parse_object(SceneDebuggerObject &p_obj);
|
||||||
|
const Variant parse_object_variable(const SceneDebuggerObject::SceneDebuggerProperty &p_property);
|
||||||
|
void parse_evaluation(DebuggerMarshalls::ScriptStackVariable &p_var);
|
||||||
|
|
||||||
|
ObjectID search_object_id(DAPVarID p_var_id);
|
||||||
|
bool request_remote_object(const ObjectID &p_object_id);
|
||||||
|
bool request_remote_evaluate(const String &p_eval, int p_stack_frame);
|
||||||
|
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
bool _processing_breakpoint = false;
|
bool _processing_breakpoint = false;
|
||||||
|
@ -106,7 +116,7 @@ private:
|
||||||
bool _processing_stackdump = false;
|
bool _processing_stackdump = false;
|
||||||
int _remaining_vars = 0;
|
int _remaining_vars = 0;
|
||||||
int _current_frame = 0;
|
int _current_frame = 0;
|
||||||
uint64_t _request_timeout = 1000;
|
uint64_t _request_timeout = 5000;
|
||||||
bool _sync_breakpoints = false;
|
bool _sync_breakpoints = false;
|
||||||
|
|
||||||
String _current_request;
|
String _current_request;
|
||||||
|
@ -114,10 +124,16 @@ private:
|
||||||
|
|
||||||
int breakpoint_id = 0;
|
int breakpoint_id = 0;
|
||||||
int stackframe_id = 0;
|
int stackframe_id = 0;
|
||||||
int variable_id = 0;
|
DAPVarID variable_id = 0;
|
||||||
List<DAP::Breakpoint> breakpoint_list;
|
List<DAP::Breakpoint> breakpoint_list;
|
||||||
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame> stackframe_list;
|
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame> stackframe_list;
|
||||||
HashMap<int, Array> variable_list;
|
HashMap<DAPVarID, Array> variable_list;
|
||||||
|
|
||||||
|
HashMap<ObjectID, DAPVarID> object_list;
|
||||||
|
HashSet<ObjectID> object_pending_set;
|
||||||
|
|
||||||
|
HashMap<String, DAP::Variable> eval_list;
|
||||||
|
HashSet<String> eval_pending_list;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
friend class DebugAdapterServer;
|
friend class DebugAdapterServer;
|
||||||
|
|
|
@ -64,10 +64,7 @@ void EditorExpressionEvaluator::_evaluate() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Array expr_data;
|
editor_debugger->request_remote_evaluate(expression, editor_debugger->get_stack_script_frame());
|
||||||
expr_data.push_back(expression);
|
|
||||||
expr_data.push_back(editor_debugger->get_stack_script_frame());
|
|
||||||
editor_debugger->send_message("evaluate", expr_data);
|
|
||||||
|
|
||||||
expression_input->clear();
|
expression_input->clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,6 +253,13 @@ const SceneDebuggerTree *ScriptEditorDebugger::get_remote_tree() {
|
||||||
return scene_tree;
|
return scene_tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptEditorDebugger::request_remote_evaluate(const String &p_expression, int p_stack_frame) {
|
||||||
|
Array msg;
|
||||||
|
msg.push_back(p_expression);
|
||||||
|
msg.push_back(p_stack_frame);
|
||||||
|
_put_msg("evaluate", msg);
|
||||||
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value) {
|
void ScriptEditorDebugger::update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value) {
|
||||||
Array msg;
|
Array msg;
|
||||||
msg.push_back(p_obj_id);
|
msg.push_back(p_obj_id);
|
||||||
|
|
|
@ -254,6 +254,8 @@ public:
|
||||||
void request_remote_tree();
|
void request_remote_tree();
|
||||||
const SceneDebuggerTree *get_remote_tree();
|
const SceneDebuggerTree *get_remote_tree();
|
||||||
|
|
||||||
|
void request_remote_evaluate(const String &p_expression, int p_stack_frame);
|
||||||
|
|
||||||
void start(Ref<RemoteDebuggerPeer> p_peer);
|
void start(Ref<RemoteDebuggerPeer> p_peer);
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue