You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-10 13:00:37 +00:00
Huge Debugger/EditorDebugger refactor.
This commit is contained in:
5
editor/debugger/SCsub
Normal file
5
editor/debugger/SCsub
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
Import('env')
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
||||
277
editor/debugger/editor_debugger_inspector.cpp
Normal file
277
editor/debugger/editor_debugger_inspector.cpp
Normal file
@@ -0,0 +1,277 @@
|
||||
/*************************************************************************/
|
||||
/* editor_debugger_inspector.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "editor_debugger_inspector.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/script_debugger_remote.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "scene/debugger/scene_debugger.h"
|
||||
|
||||
bool EditorDebuggerRemoteObject::_set(const StringName &p_name, const Variant &p_value) {
|
||||
|
||||
if (!editable || !prop_values.has(p_name) || String(p_name).begins_with("Constants/"))
|
||||
return false;
|
||||
|
||||
prop_values[p_name] = p_value;
|
||||
emit_signal("value_edited", remote_object_id, p_name, p_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorDebuggerRemoteObject::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
|
||||
if (!prop_values.has(p_name))
|
||||
return false;
|
||||
|
||||
r_ret = prop_values[p_name];
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorDebuggerRemoteObject::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
|
||||
p_list->clear(); //sorry, no want category
|
||||
for (const List<PropertyInfo>::Element *E = prop_list.front(); E; E = E->next()) {
|
||||
p_list->push_back(E->get());
|
||||
}
|
||||
}
|
||||
|
||||
String EditorDebuggerRemoteObject::get_title() {
|
||||
if (remote_object_id.is_valid())
|
||||
return TTR("Remote ") + String(type_name) + ": " + itos(remote_object_id);
|
||||
else
|
||||
return "<null>";
|
||||
}
|
||||
|
||||
Variant EditorDebuggerRemoteObject::get_variant(const StringName &p_name) {
|
||||
Variant var;
|
||||
_get(p_name, var);
|
||||
return var;
|
||||
}
|
||||
|
||||
void EditorDebuggerRemoteObject::_bind_methods() {
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_title"), &EditorDebuggerRemoteObject::get_title);
|
||||
ClassDB::bind_method(D_METHOD("get_variant"), &EditorDebuggerRemoteObject::get_variant);
|
||||
ClassDB::bind_method(D_METHOD("clear"), &EditorDebuggerRemoteObject::clear);
|
||||
ClassDB::bind_method(D_METHOD("get_remote_object_id"), &EditorDebuggerRemoteObject::get_remote_object_id);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("value_edited", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value")));
|
||||
}
|
||||
|
||||
EditorDebuggerInspector::EditorDebuggerInspector() {
|
||||
variables = memnew(EditorDebuggerRemoteObject);
|
||||
variables->editable = false;
|
||||
}
|
||||
|
||||
EditorDebuggerInspector::~EditorDebuggerInspector() {
|
||||
memdelete(variables);
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_object_edited", "name", "value"), &EditorDebuggerInspector::_object_edited);
|
||||
ClassDB::bind_method(D_METHOD("_object_selected", "id"), &EditorDebuggerInspector::_object_selected);
|
||||
ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "id")));
|
||||
ADD_SIGNAL(MethodInfo("object_edited", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value")));
|
||||
ADD_SIGNAL(MethodInfo("object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property")));
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE:
|
||||
connect_compat("object_id_selected", this, "_object_selected");
|
||||
break;
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
edit(variables);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) {
|
||||
|
||||
emit_signal("object_edited", p_id, p_prop, p_value);
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::_object_selected(ObjectID p_object) {
|
||||
|
||||
emit_signal("object_selected", p_object);
|
||||
}
|
||||
|
||||
ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) {
|
||||
EditorDebuggerRemoteObject *debugObj = NULL;
|
||||
|
||||
SceneDebuggerObject obj;
|
||||
obj.deserialize(p_arr);
|
||||
ERR_FAIL_COND_V(obj.id.is_null(), ObjectID());
|
||||
|
||||
if (remote_objects.has(obj.id)) {
|
||||
debugObj = remote_objects[obj.id];
|
||||
} else {
|
||||
debugObj = memnew(EditorDebuggerRemoteObject);
|
||||
debugObj->remote_object_id = obj.id;
|
||||
debugObj->type_name = obj.class_name;
|
||||
remote_objects[obj.id] = debugObj;
|
||||
debugObj->connect_compat("value_edited", this, "_object_edited");
|
||||
}
|
||||
|
||||
int old_prop_size = debugObj->prop_list.size();
|
||||
|
||||
debugObj->prop_list.clear();
|
||||
int new_props_added = 0;
|
||||
Set<String> changed;
|
||||
for (int i = 0; i < obj.properties.size(); i++) {
|
||||
|
||||
PropertyInfo &pinfo = obj.properties[i].first;
|
||||
Variant &var = obj.properties[i].second;
|
||||
|
||||
if (pinfo.type == Variant::OBJECT) {
|
||||
if (var.get_type() == Variant::STRING) {
|
||||
String path = var;
|
||||
if (path.find("::") != -1) {
|
||||
// built-in resource
|
||||
String base_path = path.get_slice("::", 0);
|
||||
if (ResourceLoader::get_resource_type(base_path) == "PackedScene") {
|
||||
if (!EditorNode::get_singleton()->is_scene_open(base_path)) {
|
||||
EditorNode::get_singleton()->load_scene(base_path);
|
||||
}
|
||||
} else {
|
||||
EditorNode::get_singleton()->load_resource(base_path);
|
||||
}
|
||||
}
|
||||
var = ResourceLoader::load(path);
|
||||
|
||||
if (pinfo.hint_string == "Script") {
|
||||
if (debugObj->get_script() != var) {
|
||||
debugObj->set_script(REF());
|
||||
Ref<Script> script(var);
|
||||
if (!script.is_null()) {
|
||||
ScriptInstance *script_instance = script->placeholder_instance_create(debugObj);
|
||||
debugObj->set_script_and_instance(var, script_instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//always add the property, since props may have been added or removed
|
||||
debugObj->prop_list.push_back(pinfo);
|
||||
|
||||
if (!debugObj->prop_values.has(pinfo.name)) {
|
||||
new_props_added++;
|
||||
debugObj->prop_values[pinfo.name] = var;
|
||||
} else {
|
||||
|
||||
if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, debugObj->prop_values[pinfo.name], var))) {
|
||||
debugObj->prop_values[pinfo.name] = var;
|
||||
changed.insert(pinfo.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (old_prop_size == debugObj->prop_list.size() && new_props_added == 0) {
|
||||
//only some may have changed, if so, then update those, if exist
|
||||
for (Set<String>::Element *E = changed.front(); E; E = E->next()) {
|
||||
emit_signal("object_property_updated", debugObj->remote_object_id, E->get());
|
||||
}
|
||||
} else {
|
||||
//full update, because props were added or removed
|
||||
debugObj->update();
|
||||
}
|
||||
return obj.id;
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::clear_cache() {
|
||||
for (Map<ObjectID, EditorDebuggerRemoteObject *>::Element *E = remote_objects.front(); E; E = E->next()) {
|
||||
EditorNode *editor = EditorNode::get_singleton();
|
||||
if (editor->get_editor_history()->get_current() == E->value()->get_instance_id()) {
|
||||
editor->push_item(NULL);
|
||||
}
|
||||
memdelete(E->value());
|
||||
}
|
||||
remote_objects.clear();
|
||||
}
|
||||
|
||||
Object *EditorDebuggerInspector::get_object(ObjectID p_id) {
|
||||
if (remote_objects.has(p_id))
|
||||
return remote_objects[p_id];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
|
||||
|
||||
ScriptDebuggerRemote::ScriptStackVariable var;
|
||||
var.deserialize(p_array);
|
||||
String n = var.name;
|
||||
Variant v = var.value;
|
||||
|
||||
PropertyHint h = PROPERTY_HINT_NONE;
|
||||
String hs = String();
|
||||
|
||||
if (v.get_type() == Variant::OBJECT) {
|
||||
v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id();
|
||||
h = PROPERTY_HINT_OBJECT_ID;
|
||||
hs = "Object";
|
||||
}
|
||||
String type;
|
||||
switch (var.type) {
|
||||
case 0:
|
||||
type = "Locals/";
|
||||
break;
|
||||
case 1:
|
||||
type = "Members/";
|
||||
break;
|
||||
case 2:
|
||||
type = "Globals/";
|
||||
break;
|
||||
default:
|
||||
type = "Unknown/";
|
||||
}
|
||||
|
||||
PropertyInfo pinfo;
|
||||
pinfo.name = type + n;
|
||||
pinfo.type = v.get_type();
|
||||
pinfo.hint = h;
|
||||
pinfo.hint_string = hs;
|
||||
|
||||
variables->prop_list.push_back(pinfo);
|
||||
variables->prop_values[type + n] = v;
|
||||
variables->update();
|
||||
edit(variables);
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::clear_stack_variables() {
|
||||
variables->clear();
|
||||
variables->update();
|
||||
}
|
||||
|
||||
String EditorDebuggerInspector::get_stack_variable(const String &p_var) {
|
||||
return variables->get_variant(p_var);
|
||||
}
|
||||
98
editor/debugger/editor_debugger_inspector.h
Normal file
98
editor/debugger/editor_debugger_inspector.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*************************************************************************/
|
||||
/* editor_debugger_inspector.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef EDITOR_DEBUGGER_INSPECTOR_H
|
||||
#define EDITOR_DEBUGGER_INSPECTOR_H
|
||||
#include "editor/editor_inspector.h"
|
||||
|
||||
class EditorDebuggerRemoteObject : public Object {
|
||||
|
||||
GDCLASS(EditorDebuggerRemoteObject, Object);
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
bool editable = false;
|
||||
ObjectID remote_object_id;
|
||||
String type_name;
|
||||
List<PropertyInfo> prop_list;
|
||||
Map<StringName, Variant> prop_values;
|
||||
|
||||
ObjectID get_remote_object_id() { return remote_object_id; };
|
||||
String get_title();
|
||||
|
||||
Variant get_variant(const StringName &p_name);
|
||||
|
||||
void clear() {
|
||||
prop_list.clear();
|
||||
prop_values.clear();
|
||||
}
|
||||
|
||||
void update() { _change_notify(); }
|
||||
|
||||
EditorDebuggerRemoteObject(){};
|
||||
};
|
||||
|
||||
class EditorDebuggerInspector : public EditorInspector {
|
||||
|
||||
GDCLASS(EditorDebuggerInspector, EditorInspector);
|
||||
|
||||
private:
|
||||
ObjectID inspected_object_id;
|
||||
Map<ObjectID, EditorDebuggerRemoteObject *> remote_objects;
|
||||
EditorDebuggerRemoteObject *variables;
|
||||
|
||||
void _object_selected(ObjectID p_object);
|
||||
void _object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
EditorDebuggerInspector();
|
||||
~EditorDebuggerInspector();
|
||||
|
||||
// Remote Object cache
|
||||
ObjectID add_object(const Array &p_arr);
|
||||
Object *get_object(ObjectID p_id);
|
||||
void clear_cache();
|
||||
|
||||
// Stack Dump variables
|
||||
String get_stack_variable(const String &p_var);
|
||||
void add_stack_variable(const Array &p_arr);
|
||||
void clear_stack_variables();
|
||||
};
|
||||
|
||||
#endif // EDITOR_DEBUGGER_INSPECTOR_H
|
||||
564
editor/debugger/editor_debugger_node.cpp
Normal file
564
editor/debugger/editor_debugger_node.cpp
Normal file
@@ -0,0 +1,564 @@
|
||||
/*************************************************************************/
|
||||
/* editor_debugger_node.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "editor_debugger_node.h"
|
||||
|
||||
#include "editor/debugger/editor_debugger_tree.h"
|
||||
#include "editor/editor_log.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/plugins/script_editor_plugin.h"
|
||||
|
||||
template <typename Func>
|
||||
void _for_all(EditorDebuggerNode *p_node, const Func &p_func) {
|
||||
for (int i = 0; i < p_node->get_tab_count(); i++) {
|
||||
ScriptEditorDebugger *dbg = Object::cast_to<ScriptEditorDebugger>(p_node->get_tab_control(i));
|
||||
ERR_FAIL_COND(!dbg);
|
||||
p_func(dbg);
|
||||
}
|
||||
}
|
||||
|
||||
EditorDebuggerNode *EditorDebuggerNode::singleton = NULL;
|
||||
|
||||
EditorDebuggerNode::EditorDebuggerNode() {
|
||||
if (!singleton)
|
||||
singleton = this;
|
||||
server.instance();
|
||||
EditorNode *editor = EditorNode::get_singleton();
|
||||
set_tab_align(TabAlign::ALIGN_LEFT);
|
||||
add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles"));
|
||||
add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles"));
|
||||
add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles"));
|
||||
|
||||
auto_switch_remote_scene_tree = EDITOR_DEF("debugger/auto_switch_to_remote_scene_tree", false);
|
||||
_add_debugger("Debugger");
|
||||
|
||||
// Remote scene tree
|
||||
remote_scene_tree = memnew(EditorDebuggerTree);
|
||||
remote_scene_tree->connect_compat("object_selected", this, "_remote_object_requested");
|
||||
remote_scene_tree->connect_compat("save_node", this, "_save_node_requested");
|
||||
EditorNode::get_singleton()->get_scene_tree_dock()->add_remote_tree_editor(remote_scene_tree);
|
||||
EditorNode::get_singleton()->get_scene_tree_dock()->connect_compat("remote_tree_selected", this, "request_remote_tree");
|
||||
|
||||
remote_scene_tree_timeout = EDITOR_DEF("debugger/remote_scene_tree_refresh_interval", 1.0);
|
||||
inspect_edited_object_timeout = EDITOR_DEF("debugger/remote_inspect_refresh_interval", 0.2);
|
||||
|
||||
editor->get_undo_redo()->set_method_notify_callback(_method_changeds, this);
|
||||
editor->get_undo_redo()->set_property_notify_callback(_property_changeds, this);
|
||||
editor->get_pause_button()->connect_compat("pressed", this, "_paused");
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::_add_debugger(String p_name) {
|
||||
ScriptEditorDebugger *node = memnew(ScriptEditorDebugger(EditorNode::get_singleton()));
|
||||
node->set_name(p_name);
|
||||
int id = get_tab_count();
|
||||
node->connect_compat("stop_requested", this, "_debugger_wants_stop", varray(id));
|
||||
node->connect_compat("stopped", this, "_debugger_stopped", varray(id));
|
||||
node->connect_compat("stack_frame_selected", this, "_stack_frame_selected", varray(id));
|
||||
node->connect_compat("error_selected", this, "_error_selected", varray(id));
|
||||
node->connect_compat("clear_execution", this, "_clear_execution");
|
||||
node->connect_compat("breaked", this, "_breaked", varray(id));
|
||||
node->connect_compat("remote_tree_updated", this, "_remote_tree_updated", varray(id));
|
||||
node->connect_compat("remote_object_updated", this, "_remote_object_updated", varray(id));
|
||||
node->connect_compat("remote_object_property_updated", this, "_remote_object_property_updated", varray(id));
|
||||
node->connect_compat("remote_object_requested", this, "_remote_object_requested", varray(id));
|
||||
add_child(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_stack_frame_selected(int p_debugger) {
|
||||
const ScriptEditorDebugger *dbg = get_debugger(p_debugger);
|
||||
ERR_FAIL_COND(!dbg);
|
||||
if (dbg != get_current_debugger())
|
||||
return;
|
||||
_text_editor_stack_goto(dbg);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_error_selected(const String &p_file, int p_line, int p_debugger) {
|
||||
Ref<Script> s = ResourceLoader::load(p_file);
|
||||
emit_signal("goto_script_line", s, p_line - 1);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_text_editor_stack_goto(const ScriptEditorDebugger *p_debugger) {
|
||||
const String file = p_debugger->get_stack_script_file();
|
||||
if (file.empty())
|
||||
return;
|
||||
stack_script = ResourceLoader::load(file);
|
||||
const int line = p_debugger->get_stack_script_line() - 1;
|
||||
emit_signal("goto_script_line", stack_script, line);
|
||||
emit_signal("set_execution", stack_script, line);
|
||||
stack_script.unref(); // Why?!?
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_bind_methods() {
|
||||
ClassDB::bind_method("_menu_option", &EditorDebuggerNode::_menu_option);
|
||||
ClassDB::bind_method("_debugger_stopped", &EditorDebuggerNode::_debugger_stopped);
|
||||
ClassDB::bind_method("_debugger_wants_stop", &EditorDebuggerNode::_debugger_wants_stop);
|
||||
ClassDB::bind_method("_debugger_changed", &EditorDebuggerNode::_debugger_changed);
|
||||
ClassDB::bind_method("_stack_frame_selected", &EditorDebuggerNode::_stack_frame_selected);
|
||||
ClassDB::bind_method("_error_selected", &EditorDebuggerNode::_error_selected);
|
||||
ClassDB::bind_method("_clear_execution", &EditorDebuggerNode::_clear_execution);
|
||||
ClassDB::bind_method("_breaked", &EditorDebuggerNode::_breaked);
|
||||
ClassDB::bind_method("start", &EditorDebuggerNode::start);
|
||||
ClassDB::bind_method("stop", &EditorDebuggerNode::stop);
|
||||
ClassDB::bind_method("_paused", &EditorDebuggerNode::_paused);
|
||||
ClassDB::bind_method("request_remote_tree", &EditorDebuggerNode::request_remote_tree);
|
||||
ClassDB::bind_method("_remote_tree_updated", &EditorDebuggerNode::_remote_tree_updated);
|
||||
ClassDB::bind_method("_remote_object_updated", &EditorDebuggerNode::_remote_object_updated);
|
||||
ClassDB::bind_method("_remote_object_property_updated", &EditorDebuggerNode::_remote_object_property_updated);
|
||||
ClassDB::bind_method("_remote_object_requested", &EditorDebuggerNode::_remote_object_requested);
|
||||
ClassDB::bind_method("_save_node_requested", &EditorDebuggerNode::_save_node_requested);
|
||||
|
||||
// LiveDebug.
|
||||
ClassDB::bind_method("live_debug_create_node", &EditorDebuggerNode::live_debug_create_node);
|
||||
ClassDB::bind_method("live_debug_instance_node", &EditorDebuggerNode::live_debug_instance_node);
|
||||
ClassDB::bind_method("live_debug_remove_node", &EditorDebuggerNode::live_debug_remove_node);
|
||||
ClassDB::bind_method("live_debug_remove_and_keep_node", &EditorDebuggerNode::live_debug_remove_and_keep_node);
|
||||
ClassDB::bind_method("live_debug_restore_node", &EditorDebuggerNode::live_debug_restore_node);
|
||||
ClassDB::bind_method("live_debug_duplicate_node", &EditorDebuggerNode::live_debug_duplicate_node);
|
||||
ClassDB::bind_method("live_debug_reparent_node", &EditorDebuggerNode::live_debug_reparent_node);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("goto_script_line"));
|
||||
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")));
|
||||
}
|
||||
|
||||
EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() {
|
||||
return Object::cast_to<EditorDebuggerRemoteObject>(ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_history()->get_current()));
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::get_debugger(int p_id) const {
|
||||
return Object::cast_to<ScriptEditorDebugger>(get_tab_control(p_id));
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::get_current_debugger() const {
|
||||
return Object::cast_to<ScriptEditorDebugger>(get_tab_control(get_current_tab()));
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const {
|
||||
return Object::cast_to<ScriptEditorDebugger>(get_tab_control(0));
|
||||
}
|
||||
|
||||
Error EditorDebuggerNode::start() {
|
||||
stop();
|
||||
if (EDITOR_GET("run/output/always_open_output_on_play")) {
|
||||
EditorNode::get_singleton()->make_bottom_panel_item_visible(EditorNode::get_log());
|
||||
} else {
|
||||
EditorNode::get_singleton()->make_bottom_panel_item_visible(this);
|
||||
}
|
||||
|
||||
int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port");
|
||||
const Error err = server->listen(remote_port);
|
||||
if (err != OK) {
|
||||
EditorNode::get_log()->add_message(String("Error listening on port ") + itos(remote_port), EditorLog::MSG_TYPE_ERROR);
|
||||
return err;
|
||||
}
|
||||
set_process(true);
|
||||
EditorNode::get_log()->add_message("--- Debugging process started ---", EditorLog::MSG_TYPE_EDITOR);
|
||||
return OK;
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::stop() {
|
||||
if (server->is_listening()) {
|
||||
server->stop();
|
||||
EditorNode::get_log()->add_message("--- Debugging process stopped ---", EditorLog::MSG_TYPE_EDITOR);
|
||||
}
|
||||
// Also close all debugging sessions.
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
if (dbg->is_session_active())
|
||||
dbg->stop();
|
||||
});
|
||||
_break_state_changed();
|
||||
if (hide_on_stop) {
|
||||
if (is_visible_in_tree())
|
||||
EditorNode::get_singleton()->hide_bottom_panel();
|
||||
}
|
||||
breakpoints.clear();
|
||||
set_process(false);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
connect_compat("tab_changed", this, "_debugger_changed");
|
||||
} break;
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
EditorNode::get_singleton()->connect_compat("play_pressed", this, "start");
|
||||
EditorNode::get_singleton()->connect_compat("stop_pressed", this, "stop");
|
||||
} break;
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
EditorNode::get_singleton()->disconnect_compat("play_pressed", this, "start");
|
||||
EditorNode::get_singleton()->disconnect_compat("stop_pressed", this, "stop");
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (p_what != NOTIFICATION_PROCESS || !server->is_listening())
|
||||
return;
|
||||
|
||||
// Errors and warnings
|
||||
int error_count = 0;
|
||||
int warning_count = 0;
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
error_count += dbg->get_error_count();
|
||||
warning_count += dbg->get_warning_count();
|
||||
});
|
||||
|
||||
if (error_count != last_error_count || warning_count != last_warning_count) {
|
||||
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->update_tabs();
|
||||
});
|
||||
|
||||
if (error_count == 0 && warning_count == 0) {
|
||||
debugger_button->set_text(TTR("Debugger"));
|
||||
debugger_button->set_icon(Ref<Texture2D>());
|
||||
} else {
|
||||
debugger_button->set_text(TTR("Debugger") + " (" + itos(error_count + warning_count) + ")");
|
||||
if (error_count == 0) {
|
||||
debugger_button->set_icon(get_icon("Warning", "EditorIcons"));
|
||||
} else {
|
||||
debugger_button->set_icon(get_icon("Error", "EditorIcons"));
|
||||
}
|
||||
}
|
||||
last_error_count = error_count;
|
||||
last_warning_count = warning_count;
|
||||
}
|
||||
|
||||
// Remote scene tree update
|
||||
remote_scene_tree_timeout -= get_process_delta_time();
|
||||
if (remote_scene_tree_timeout < 0) {
|
||||
remote_scene_tree_timeout = EditorSettings::get_singleton()->get("debugger/remote_scene_tree_refresh_interval");
|
||||
if (remote_scene_tree->is_visible_in_tree()) {
|
||||
get_current_debugger()->request_remote_tree();
|
||||
}
|
||||
}
|
||||
|
||||
// Remote inspector update
|
||||
inspect_edited_object_timeout -= get_process_delta_time();
|
||||
if (inspect_edited_object_timeout < 0) {
|
||||
inspect_edited_object_timeout = EditorSettings::get_singleton()->get("debugger/remote_inspect_refresh_interval");
|
||||
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) {
|
||||
get_current_debugger()->request_remote_object(obj->remote_object_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Take connections.
|
||||
if (server->is_connection_available()) {
|
||||
ScriptEditorDebugger *debugger = NULL;
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
if (debugger || dbg->is_session_active())
|
||||
return;
|
||||
debugger = dbg;
|
||||
});
|
||||
if (debugger == NULL) {
|
||||
if (get_tab_count() <= 4) { // Max 4 debugging sessions active.
|
||||
debugger = _add_debugger("Session " + itos(get_tab_count()));
|
||||
} else {
|
||||
// We already have too many sessions, disconnecting new clients to prevent it from hanging.
|
||||
// (Not keeping a reference to the connection will disconnect it)
|
||||
server->take_connection();
|
||||
return; // Can't add, stop here.
|
||||
}
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->get_pause_button()->set_disabled(false);
|
||||
// Switch to remote tree view if so desired.
|
||||
auto_switch_remote_scene_tree = (bool)EditorSettings::get_singleton()->get("debugger/auto_switch_to_remote_scene_tree");
|
||||
if (auto_switch_remote_scene_tree) {
|
||||
EditorNode::get_singleton()->get_scene_tree_dock()->show_remote_tree();
|
||||
}
|
||||
// Good to go.
|
||||
EditorNode::get_singleton()->get_scene_tree_dock()->show_tab_buttons();
|
||||
debugger->set_editor_remote_tree(remote_scene_tree);
|
||||
debugger->start(server->take_connection());
|
||||
// Send breakpoints.
|
||||
for (Map<Breakpoint, bool>::Element *E = breakpoints.front(); E; E = E->next()) {
|
||||
const Breakpoint &bp = E->key();
|
||||
debugger->set_breakpoint(bp.source, bp.line, E->get());
|
||||
} // Will arrive too late, how does the regular run work?
|
||||
|
||||
debugger->update_live_edit_root();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_debugger_stopped(int p_id) {
|
||||
ScriptEditorDebugger *dbg = get_debugger(p_id);
|
||||
ERR_FAIL_COND(!dbg);
|
||||
|
||||
bool found = false;
|
||||
_for_all(this, [&](ScriptEditorDebugger *p_debugger) {
|
||||
if (p_debugger->is_session_active())
|
||||
found = true;
|
||||
});
|
||||
if (!found) {
|
||||
EditorNode::get_singleton()->get_pause_button()->set_pressed(false);
|
||||
EditorNode::get_singleton()->get_pause_button()->set_disabled(true);
|
||||
EditorNode::get_singleton()->get_scene_tree_dock()->hide_remote_tree();
|
||||
EditorNode::get_singleton()->get_scene_tree_dock()->hide_tab_buttons();
|
||||
EditorNode::get_singleton()->notify_all_debug_sessions_exited();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_debugger_wants_stop(int p_id) {
|
||||
// Ask editor to kill PID.
|
||||
int pid = get_debugger(p_id)->get_remote_pid();
|
||||
if (pid)
|
||||
EditorNode::get_singleton()->call_deferred("stop_child_process", pid);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_debugger_changed(int p_tab) {
|
||||
if (get_inspected_remote_object()) {
|
||||
// Clear inspected object, you can only inspect objects in selected debugger.
|
||||
// Hopefully, in the future, we will have one inspector per debugger.
|
||||
EditorNode::get_singleton()->push_item(NULL);
|
||||
}
|
||||
if (remote_scene_tree->is_visible_in_tree()) {
|
||||
get_current_debugger()->request_remote_tree();
|
||||
}
|
||||
if (get_current_debugger()->is_breaked()) {
|
||||
_text_editor_stack_goto(get_current_debugger());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::set_script_debug_button(MenuButton *p_button) {
|
||||
script_menu = p_button;
|
||||
script_menu->set_text(TTR("Debug"));
|
||||
script_menu->set_switch_on_hover(true);
|
||||
PopupMenu *p = script_menu->get_popup();
|
||||
p->set_hide_on_window_lose_focus(true);
|
||||
p->add_shortcut(ED_GET_SHORTCUT("debugger/step_into"), DEBUG_STEP);
|
||||
p->add_shortcut(ED_GET_SHORTCUT("debugger/step_over"), DEBUG_NEXT);
|
||||
p->add_separator();
|
||||
p->add_shortcut(ED_GET_SHORTCUT("debugger/break"), DEBUG_BREAK);
|
||||
p->add_shortcut(ED_GET_SHORTCUT("debugger/continue"), DEBUG_CONTINUE);
|
||||
p->add_separator();
|
||||
p->add_check_shortcut(ED_GET_SHORTCUT("debugger/keep_debugger_open"), DEBUG_SHOW_KEEP_OPEN);
|
||||
p->add_check_shortcut(ED_GET_SHORTCUT("debugger/debug_with_external_editor"), DEBUG_WITH_EXTERNAL_EDITOR);
|
||||
p->connect_compat("id_pressed", this, "_menu_option");
|
||||
|
||||
_break_state_changed();
|
||||
script_menu->show();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_break_state_changed() {
|
||||
const bool breaked = get_current_debugger()->is_breaked();
|
||||
const bool can_debug = get_current_debugger()->is_debuggable();
|
||||
if (breaked) // Show debugger.
|
||||
EditorNode::get_singleton()->make_bottom_panel_item_visible(this);
|
||||
|
||||
// Update script menu.
|
||||
if (!script_menu)
|
||||
return;
|
||||
PopupMenu *p = script_menu->get_popup();
|
||||
p->set_item_disabled(p->get_item_index(DEBUG_NEXT), !(breaked && can_debug));
|
||||
p->set_item_disabled(p->get_item_index(DEBUG_STEP), !(breaked && can_debug));
|
||||
p->set_item_disabled(p->get_item_index(DEBUG_BREAK), breaked);
|
||||
p->set_item_disabled(p->get_item_index(DEBUG_CONTINUE), !breaked);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_menu_option(int p_id) {
|
||||
switch (p_id) {
|
||||
case DEBUG_NEXT: {
|
||||
debug_next();
|
||||
} break;
|
||||
case DEBUG_STEP: {
|
||||
debug_step();
|
||||
} break;
|
||||
case DEBUG_BREAK: {
|
||||
debug_break();
|
||||
} break;
|
||||
case DEBUG_CONTINUE: {
|
||||
debug_continue();
|
||||
} break;
|
||||
|
||||
case DEBUG_SHOW_KEEP_OPEN: {
|
||||
bool visible = script_menu->get_popup()->is_item_checked(script_menu->get_popup()->get_item_index(DEBUG_SHOW_KEEP_OPEN));
|
||||
hide_on_stop = visible;
|
||||
script_menu->get_popup()->set_item_checked(script_menu->get_popup()->get_item_index(DEBUG_SHOW_KEEP_OPEN), !visible);
|
||||
} break;
|
||||
case DEBUG_WITH_EXTERNAL_EDITOR: {
|
||||
bool checked = !script_menu->get_popup()->is_item_checked(script_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR));
|
||||
debug_with_external_editor = checked;
|
||||
script_menu->get_popup()->set_item_checked(script_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR), checked);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_paused() {
|
||||
const bool paused = EditorNode::get_singleton()->get_pause_button()->is_pressed();
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
if (paused && !dbg->is_breaked()) {
|
||||
dbg->debug_break();
|
||||
} else if (!paused && dbg->is_breaked()) {
|
||||
dbg->debug_continue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_breaked(bool p_breaked, bool p_can_debug, int p_debugger) {
|
||||
if (get_current_debugger() != get_debugger(p_debugger)) {
|
||||
if (!p_breaked)
|
||||
return;
|
||||
set_current_tab(p_debugger);
|
||||
}
|
||||
_break_state_changed();
|
||||
EditorNode::get_singleton()->get_pause_button()->set_pressed(p_breaked);
|
||||
emit_signal("breaked", p_breaked, p_can_debug);
|
||||
}
|
||||
|
||||
bool EditorDebuggerNode::is_skip_breakpoints() const {
|
||||
return get_default_debugger()->is_skip_breakpoints();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p_enabled) {
|
||||
breakpoints[Breakpoint(p_path, p_line)] = p_enabled;
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->set_breakpoint(p_path, p_line, p_enabled);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::reload_scripts() {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->reload_scripts();
|
||||
});
|
||||
}
|
||||
|
||||
// LiveEdit/Inspector
|
||||
void EditorDebuggerNode::request_remote_tree() {
|
||||
get_current_debugger()->request_remote_tree();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_tree_updated(int p_debugger) {
|
||||
if (p_debugger != get_current_tab())
|
||||
return;
|
||||
remote_scene_tree->clear();
|
||||
remote_scene_tree->update_scene_tree(get_current_debugger()->get_remote_tree(), p_debugger);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_object_updated(ObjectID p_id, int p_debugger) {
|
||||
if (p_debugger != get_current_tab())
|
||||
return;
|
||||
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) {
|
||||
if (obj->remote_object_id == p_id)
|
||||
return; // Already being edited
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->push_item(get_current_debugger()->get_remote_object(p_id));
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger) {
|
||||
if (p_debugger != get_current_tab())
|
||||
return;
|
||||
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) {
|
||||
if (obj->remote_object_id != p_id)
|
||||
return;
|
||||
EditorNode::get_singleton()->get_inspector()->update_property(p_property);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_object_requested(ObjectID p_id, int p_debugger) {
|
||||
if (p_debugger != get_current_tab())
|
||||
return;
|
||||
inspect_edited_object_timeout = 0.7; // Temporarily disable timeout to avoid multiple requests.
|
||||
get_current_debugger()->request_remote_object(p_id);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_save_node_requested(ObjectID p_id, const String &p_file, int p_debugger) {
|
||||
if (p_debugger != get_current_tab())
|
||||
return;
|
||||
get_current_debugger()->save_node(p_id, p_file);
|
||||
}
|
||||
|
||||
// Remote inspector/edit.
|
||||
void EditorDebuggerNode::_method_changeds(void *p_ud, Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE) {
|
||||
if (!singleton)
|
||||
return;
|
||||
_for_all(singleton, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->_method_changed(p_base, p_name, VARIANT_ARG_PASS);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value) {
|
||||
if (!singleton)
|
||||
return;
|
||||
_for_all(singleton, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->_property_changed(p_base, p_property, p_value);
|
||||
});
|
||||
}
|
||||
|
||||
// LiveDebug
|
||||
void EditorDebuggerNode::set_live_debugging(bool p_enabled) {
|
||||
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->set_live_debugging(p_enabled);
|
||||
});
|
||||
}
|
||||
void EditorDebuggerNode::update_live_edit_root() {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->update_live_edit_root();
|
||||
});
|
||||
}
|
||||
void EditorDebuggerNode::live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name) {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_create_node(p_parent, p_type, p_name);
|
||||
});
|
||||
}
|
||||
void EditorDebuggerNode::live_debug_instance_node(const NodePath &p_parent, const String &p_path, const String &p_name) {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_instance_node(p_parent, p_path, p_name);
|
||||
});
|
||||
}
|
||||
void EditorDebuggerNode::live_debug_remove_node(const NodePath &p_at) {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_remove_node(p_at);
|
||||
});
|
||||
}
|
||||
void EditorDebuggerNode::live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id) {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_remove_and_keep_node(p_at, p_keep_id);
|
||||
});
|
||||
}
|
||||
void EditorDebuggerNode::live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos) {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_restore_node(p_id, p_at, p_at_pos);
|
||||
});
|
||||
}
|
||||
void EditorDebuggerNode::live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name) {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_duplicate_node(p_at, p_new_name);
|
||||
});
|
||||
}
|
||||
void EditorDebuggerNode::live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) {
|
||||
_for_all(this, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_reparent_node(p_at, p_new_place, p_new_name, p_at_pos);
|
||||
});
|
||||
}
|
||||
176
editor/debugger/editor_debugger_node.h
Normal file
176
editor/debugger/editor_debugger_node.h
Normal file
@@ -0,0 +1,176 @@
|
||||
/*************************************************************************/
|
||||
/* editor_debugger_node.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef EDITOR_DEBUGGER_NODE_H
|
||||
#define EDITOR_DEBUGGER_NODE_H
|
||||
|
||||
#include "core/io/tcp_server.h"
|
||||
#include "editor/debugger/script_editor_debugger.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
|
||||
class EditorDebuggerTree;
|
||||
|
||||
class EditorDebuggerNode : public TabContainer {
|
||||
|
||||
GDCLASS(EditorDebuggerNode, TabContainer);
|
||||
|
||||
private:
|
||||
enum Options {
|
||||
DEBUG_NEXT,
|
||||
DEBUG_STEP,
|
||||
DEBUG_BREAK,
|
||||
DEBUG_CONTINUE,
|
||||
DEBUG_SHOW_KEEP_OPEN,
|
||||
DEBUG_WITH_EXTERNAL_EDITOR,
|
||||
};
|
||||
|
||||
class Breakpoint {
|
||||
public:
|
||||
String source;
|
||||
int line = 0;
|
||||
|
||||
bool operator<(const Breakpoint &p_b) const {
|
||||
if (line == p_b.line)
|
||||
return line < p_b.line;
|
||||
return line < p_b.line;
|
||||
}
|
||||
|
||||
Breakpoint(){};
|
||||
|
||||
Breakpoint(const String &p_source, int p_line) {
|
||||
line = p_line;
|
||||
source = p_source;
|
||||
}
|
||||
};
|
||||
|
||||
Ref<TCP_Server> server = NULL;
|
||||
Button *debugger_button = NULL;
|
||||
MenuButton *script_menu = NULL;
|
||||
|
||||
Ref<Script> stack_script; // Why?!?
|
||||
|
||||
int last_error_count = 0;
|
||||
int last_warning_count = 0;
|
||||
|
||||
float inspect_edited_object_timeout = 0;
|
||||
EditorDebuggerTree *remote_scene_tree = NULL;
|
||||
float remote_scene_tree_timeout = 0.0;
|
||||
bool auto_switch_remote_scene_tree = false;
|
||||
bool debug_with_external_editor = false;
|
||||
bool hide_on_stop = true;
|
||||
ScriptEditorDebugger::CameraOverride camera_override = ScriptEditorDebugger::OVERRIDE_NONE;
|
||||
Map<Breakpoint, bool> breakpoints;
|
||||
|
||||
ScriptEditorDebugger *_add_debugger(String p_name);
|
||||
EditorDebuggerRemoteObject *get_inspected_remote_object();
|
||||
|
||||
friend class DebuggerEditorPlugin;
|
||||
static EditorDebuggerNode *singleton;
|
||||
EditorDebuggerNode();
|
||||
|
||||
protected:
|
||||
void _debugger_stopped(int p_id);
|
||||
void _debugger_wants_stop(int p_id);
|
||||
void _debugger_changed(int p_tab);
|
||||
void _remote_tree_updated(int p_debugger);
|
||||
void _remote_object_updated(ObjectID p_id, int p_debugger);
|
||||
void _remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger);
|
||||
void _remote_object_requested(ObjectID p_id, int p_debugger);
|
||||
void _save_node_requested(ObjectID p_id, const String &p_file, int p_debugger);
|
||||
|
||||
void _clear_execution(REF p_script) {
|
||||
emit_signal("clear_execution", p_script);
|
||||
}
|
||||
|
||||
void _text_editor_stack_goto(const ScriptEditorDebugger *p_debugger);
|
||||
void _stack_frame_selected(int p_debugger);
|
||||
void _error_selected(const String &p_file, int p_line, int p_debugger);
|
||||
void _breaked(bool p_breaked, bool p_can_debug, int p_debugger);
|
||||
void _paused();
|
||||
void _break_state_changed();
|
||||
void _menu_option(int p_id);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorDebuggerNode *get_singleton() { return singleton; }
|
||||
|
||||
ScriptEditorDebugger *get_current_debugger() const;
|
||||
ScriptEditorDebugger *get_default_debugger() const;
|
||||
ScriptEditorDebugger *get_debugger(int p_debugger) const;
|
||||
|
||||
void debug_next() { get_default_debugger()->debug_next(); }
|
||||
void debug_step() { get_default_debugger()->debug_step(); }
|
||||
void debug_break() { get_default_debugger()->debug_break(); }
|
||||
void debug_continue() { get_default_debugger()->debug_continue(); }
|
||||
|
||||
void set_script_debug_button(MenuButton *p_button);
|
||||
|
||||
void set_tool_button(Button *p_button) {
|
||||
debugger_button = p_button;
|
||||
}
|
||||
|
||||
String get_var_value(const String &p_var) const { return get_default_debugger()->get_var_value(p_var); }
|
||||
Ref<Script> get_dump_stack_script() const { return stack_script; } // Why do we need this?
|
||||
|
||||
bool get_debug_with_external_editor() { return debug_with_external_editor; }
|
||||
|
||||
bool is_skip_breakpoints() const;
|
||||
void set_breakpoint(const String &p_path, int p_line, bool p_enabled);
|
||||
void reload_scripts();
|
||||
|
||||
// Remote inspector/edit.
|
||||
void request_remote_tree();
|
||||
static void _method_changeds(void *p_ud, Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE);
|
||||
static void _property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value);
|
||||
|
||||
// LiveDebug
|
||||
void set_live_debugging(bool p_enabled);
|
||||
void update_live_edit_root();
|
||||
void live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name);
|
||||
void live_debug_instance_node(const NodePath &p_parent, const String &p_path, const String &p_name);
|
||||
void live_debug_remove_node(const NodePath &p_at);
|
||||
void live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id);
|
||||
void live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos);
|
||||
void live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name);
|
||||
void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos);
|
||||
|
||||
// Camera
|
||||
void set_camera_override(ScriptEditorDebugger::CameraOverride p_override) { camera_override = p_override; }
|
||||
ScriptEditorDebugger::CameraOverride get_camera_override() { return camera_override; }
|
||||
|
||||
Error start();
|
||||
|
||||
void stop();
|
||||
};
|
||||
#endif // EDITOR_DEBUGGER_NODE_H
|
||||
274
editor/debugger/editor_debugger_tree.cpp
Normal file
274
editor/debugger/editor_debugger_tree.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
/*************************************************************************/
|
||||
/* editor_debugger_tree.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "editor_debugger_tree.h"
|
||||
|
||||
#include "editor/editor_node.h"
|
||||
#include "scene/debugger/scene_debugger.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
|
||||
EditorDebuggerTree::EditorDebuggerTree() {
|
||||
set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
set_allow_rmb_select(true);
|
||||
|
||||
// Popup
|
||||
item_menu = memnew(PopupMenu);
|
||||
item_menu->connect_compat("id_pressed", this, "_item_menu_id_pressed");
|
||||
add_child(item_menu);
|
||||
|
||||
// File Dialog
|
||||
file_dialog = memnew(EditorFileDialog);
|
||||
file_dialog->connect_compat("file_selected", this, "_file_selected");
|
||||
add_child(file_dialog);
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_POSTINITIALIZE) {
|
||||
connect_compat("cell_selected", this, "_scene_tree_selected");
|
||||
connect_compat("item_collapsed", this, "_scene_tree_folded");
|
||||
connect_compat("item_rmb_selected", this, "_scene_tree_rmb_selected");
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_scene_tree_selected"), &EditorDebuggerTree::_scene_tree_selected);
|
||||
ClassDB::bind_method(D_METHOD("_scene_tree_folded"), &EditorDebuggerTree::_scene_tree_folded);
|
||||
ClassDB::bind_method(D_METHOD("_scene_tree_rmb_selected"), &EditorDebuggerTree::_scene_tree_rmb_selected);
|
||||
ClassDB::bind_method(D_METHOD("_item_menu_id_pressed"), &EditorDebuggerTree::_item_menu_id_pressed);
|
||||
ClassDB::bind_method(D_METHOD("_file_selected"), &EditorDebuggerTree::_file_selected);
|
||||
ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::INT, "debugger")));
|
||||
ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger")));
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_scene_tree_selected() {
|
||||
|
||||
if (updating_scene_tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = get_selected();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
inspected_object_id = uint64_t(item->get_metadata(0));
|
||||
|
||||
emit_signal("object_selected", inspected_object_id, debugger_id);
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) {
|
||||
|
||||
if (updating_scene_tree) {
|
||||
|
||||
return;
|
||||
}
|
||||
TreeItem *item = Object::cast_to<TreeItem>(p_obj);
|
||||
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
ObjectID id = ObjectID(uint64_t(item->get_metadata(0)));
|
||||
if (unfold_cache.has(id)) {
|
||||
unfold_cache.erase(id);
|
||||
} else {
|
||||
unfold_cache.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position) {
|
||||
|
||||
TreeItem *item = get_item_at_position(p_position);
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
item->select(0);
|
||||
|
||||
item_menu->clear();
|
||||
item_menu->add_icon_item(get_icon("CreateNewSceneFrom", "EditorIcons"), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE);
|
||||
item_menu->add_icon_item(get_icon("CopyNodePath", "EditorIcons"), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH);
|
||||
item_menu->set_global_position(get_global_mouse_position());
|
||||
item_menu->popup();
|
||||
}
|
||||
|
||||
/// Populates inspect_scene_tree given data in nodes as a flat list, encoded depth first.
|
||||
///
|
||||
/// Given a nodes array like [R,A,B,C,D,E] the following Tree will be generated, assuming
|
||||
/// filter is an empty String, R and A child count are 2, B is 1 and C, D and E are 0.
|
||||
///
|
||||
/// R
|
||||
/// |-A
|
||||
/// | |-B
|
||||
/// | | |-C
|
||||
/// | |
|
||||
/// | |-D
|
||||
/// |
|
||||
/// |-E
|
||||
///
|
||||
void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) {
|
||||
updating_scene_tree = true;
|
||||
const String last_path = get_selected_path();
|
||||
const String filter = EditorNode::get_singleton()->get_scene_tree_dock()->get_filter();
|
||||
|
||||
// Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
|
||||
List<Pair<TreeItem *, int> > parents;
|
||||
for (int i = 0; i < p_tree->nodes.size(); i++) {
|
||||
TreeItem *parent = NULL;
|
||||
if (parents.size()) { // Find last parent.
|
||||
Pair<TreeItem *, int> &p = parents[0];
|
||||
parent = p.first;
|
||||
if (!(--p.second)) { // If no child left, remove it.
|
||||
parents.pop_front();
|
||||
}
|
||||
}
|
||||
// Add this node.
|
||||
const SceneDebuggerTree::RemoteNode &node = p_tree->nodes[i];
|
||||
TreeItem *item = create_item(parent);
|
||||
item->set_text(0, node.name);
|
||||
item->set_tooltip(0, TTR("Type:") + " " + node.type_name);
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(node.type_name, "");
|
||||
if (icon.is_valid()) {
|
||||
item->set_icon(0, icon);
|
||||
}
|
||||
item->set_metadata(0, node.id);
|
||||
|
||||
// Set current item as collapsed if necessary (root is never collapsed)
|
||||
if (parent) {
|
||||
if (!unfold_cache.has(node.id)) {
|
||||
item->set_collapsed(true);
|
||||
}
|
||||
}
|
||||
// Select previously selected node.
|
||||
if (debugger_id == p_debugger) { // Can use remote id.
|
||||
if (node.id == inspected_object_id) {
|
||||
item->select(0);
|
||||
}
|
||||
} else { // Must use path
|
||||
if (last_path == _get_path(item)) {
|
||||
updating_scene_tree = false; // Force emission of new selection
|
||||
item->select(0);
|
||||
updating_scene_tree = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add in front of the parents stack if children are expected.
|
||||
if (node.child_count) {
|
||||
parents.push_front(Pair<TreeItem *, int>(item, node.child_count));
|
||||
} else {
|
||||
// Apply filters.
|
||||
while (parent) {
|
||||
const bool had_siblings = item->get_prev() || item->get_next();
|
||||
if (filter.is_subsequence_ofi(item->get_text(0)))
|
||||
break; // Filter matches, must survive.
|
||||
parent->remove_child(item);
|
||||
memdelete(item);
|
||||
if (had_siblings)
|
||||
break; // Parent must survive.
|
||||
item = parent;
|
||||
parent = item->get_parent();
|
||||
// Check if parent expects more children.
|
||||
for (int j = 0; j < parents.size(); j++) {
|
||||
if (parents[j].first == item) {
|
||||
parent = NULL;
|
||||
break; // Might have more children.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree
|
||||
updating_scene_tree = false;
|
||||
}
|
||||
|
||||
String EditorDebuggerTree::get_selected_path() {
|
||||
if (!get_selected())
|
||||
return "";
|
||||
return _get_path(get_selected());
|
||||
}
|
||||
|
||||
String EditorDebuggerTree::_get_path(TreeItem *p_item) {
|
||||
ERR_FAIL_COND_V(!p_item, "");
|
||||
|
||||
if (p_item->get_parent() == NULL) {
|
||||
return "/root";
|
||||
}
|
||||
String text = p_item->get_text(0);
|
||||
TreeItem *cur = p_item->get_parent();
|
||||
while (cur) {
|
||||
text = cur->get_text(0) + "/" + text;
|
||||
cur = cur->get_parent();
|
||||
}
|
||||
return "/" + text;
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {
|
||||
|
||||
switch (p_option) {
|
||||
|
||||
case ITEM_MENU_SAVE_REMOTE_NODE: {
|
||||
|
||||
file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
|
||||
file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE);
|
||||
|
||||
List<String> extensions;
|
||||
Ref<PackedScene> sd = memnew(PackedScene);
|
||||
ResourceSaver::get_recognized_extensions(sd, &extensions);
|
||||
file_dialog->clear_filters();
|
||||
for (int i = 0; i < extensions.size(); i++) {
|
||||
file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper());
|
||||
}
|
||||
|
||||
file_dialog->popup_centered_ratio();
|
||||
} break;
|
||||
case ITEM_MENU_COPY_NODE_PATH: {
|
||||
|
||||
String text = get_selected_path();
|
||||
if (text.empty()) {
|
||||
return;
|
||||
} else if (text == "/root") {
|
||||
text = ".";
|
||||
} else {
|
||||
text = text.replace("/root/", "");
|
||||
int slash = text.find("/");
|
||||
if (slash < 0) {
|
||||
text = ".";
|
||||
} else {
|
||||
text = text.substr(slash + 1);
|
||||
}
|
||||
}
|
||||
OS::get_singleton()->set_clipboard(text);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_file_selected(const String &p_file) {
|
||||
if (inspected_object_id.is_null())
|
||||
return;
|
||||
emit_signal("save_node", inspected_object_id, p_file, debugger_id);
|
||||
}
|
||||
74
editor/debugger/editor_debugger_tree.h
Normal file
74
editor/debugger/editor_debugger_tree.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*************************************************************************/
|
||||
/* editor_debugger_tree.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
#ifndef EDITOR_DEBUGGER_TREE_H
|
||||
#define EDITOR_DEBUGGER_TREE_H
|
||||
|
||||
class SceneDebuggerTree;
|
||||
class EditorFileDialog;
|
||||
|
||||
class EditorDebuggerTree : public Tree {
|
||||
|
||||
GDCLASS(EditorDebuggerTree, Tree);
|
||||
|
||||
private:
|
||||
enum ItemMenu {
|
||||
ITEM_MENU_SAVE_REMOTE_NODE,
|
||||
ITEM_MENU_COPY_NODE_PATH,
|
||||
};
|
||||
|
||||
ObjectID inspected_object_id;
|
||||
int debugger_id = 0;
|
||||
bool updating_scene_tree = false;
|
||||
Set<ObjectID> unfold_cache;
|
||||
PopupMenu *item_menu = NULL;
|
||||
EditorFileDialog *file_dialog = NULL;
|
||||
|
||||
String _get_path(TreeItem *p_item);
|
||||
void _scene_tree_folded(Object *p_obj);
|
||||
void _scene_tree_selected();
|
||||
void _scene_tree_rmb_selected(const Vector2 &p_position);
|
||||
void _item_menu_id_pressed(int p_option);
|
||||
void _file_selected(const String &p_file);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
String get_selected_path();
|
||||
ObjectID get_selected_object();
|
||||
int get_current_debugger(); // Would love to have one tree for every debugger.
|
||||
void update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger);
|
||||
EditorDebuggerTree();
|
||||
};
|
||||
#endif // EDITOR_DEBUGGER_TREE_H
|
||||
1879
editor/debugger/script_editor_debugger.cpp
Normal file
1879
editor/debugger/script_editor_debugger.cpp
Normal file
File diff suppressed because it is too large
Load Diff
270
editor/debugger/script_editor_debugger.h
Normal file
270
editor/debugger/script_editor_debugger.h
Normal file
@@ -0,0 +1,270 @@
|
||||
/*************************************************************************/
|
||||
/* script_editor_debugger.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef SCRIPT_EDITOR_DEBUGGER_H
|
||||
#define SCRIPT_EDITOR_DEBUGGER_H
|
||||
|
||||
#include "core/io/packet_peer.h"
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
#include "editor/debugger/editor_debugger_inspector.h"
|
||||
#include "editor/editor_inspector.h"
|
||||
#include "editor/property_editor.h"
|
||||
#include "scene/3d/camera.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
|
||||
class Tree;
|
||||
class EditorNode;
|
||||
class LineEdit;
|
||||
class TabContainer;
|
||||
class RichTextLabel;
|
||||
class TextureButton;
|
||||
class AcceptDialog;
|
||||
class TreeItem;
|
||||
class HSplitContainer;
|
||||
class ItemList;
|
||||
class EditorProfiler;
|
||||
class EditorVisualProfiler;
|
||||
class EditorNetworkProfiler;
|
||||
class SceneDebuggerTree;
|
||||
|
||||
class ScriptEditorDebugger : public MarginContainer {
|
||||
|
||||
GDCLASS(ScriptEditorDebugger, MarginContainer);
|
||||
|
||||
friend class EditorDebuggerNode;
|
||||
|
||||
public:
|
||||
enum CameraOverride {
|
||||
OVERRIDE_NONE,
|
||||
OVERRIDE_2D,
|
||||
OVERRIDE_3D_1, // 3D Viewport 1
|
||||
OVERRIDE_3D_2, // 3D Viewport 2
|
||||
OVERRIDE_3D_3, // 3D Viewport 3
|
||||
OVERRIDE_3D_4 // 3D Viewport 4
|
||||
};
|
||||
|
||||
private:
|
||||
enum MessageType {
|
||||
MESSAGE_ERROR,
|
||||
MESSAGE_WARNING,
|
||||
MESSAGE_SUCCESS,
|
||||
};
|
||||
|
||||
AcceptDialog *msgdialog;
|
||||
|
||||
LineEdit *clicked_ctrl;
|
||||
LineEdit *clicked_ctrl_type;
|
||||
LineEdit *live_edit_root;
|
||||
Button *le_set;
|
||||
Button *le_clear;
|
||||
Button *export_csv;
|
||||
|
||||
VBoxContainer *errors_tab;
|
||||
Tree *error_tree;
|
||||
Button *clearbutton;
|
||||
PopupMenu *item_menu;
|
||||
|
||||
EditorFileDialog *file_dialog;
|
||||
|
||||
int error_count;
|
||||
int warning_count;
|
||||
|
||||
bool skip_breakpoints_value = false;
|
||||
Ref<Script> stack_script;
|
||||
|
||||
TabContainer *tabs;
|
||||
|
||||
Label *reason;
|
||||
|
||||
Button *skip_breakpoints;
|
||||
Button *copy;
|
||||
Button *step;
|
||||
Button *next;
|
||||
Button *dobreak;
|
||||
Button *docontinue;
|
||||
// Reference to "Remote" tab in scene tree. Needed by _live_edit_set and buttons state.
|
||||
// Each debugger should have it's tree in the future I guess.
|
||||
const Tree *editor_remote_tree = NULL;
|
||||
|
||||
List<Vector<float> > perf_history;
|
||||
Vector<float> perf_max;
|
||||
Vector<TreeItem *> perf_items;
|
||||
|
||||
Map<int, String> profiler_signature;
|
||||
|
||||
Tree *perf_monitors;
|
||||
Control *perf_draw;
|
||||
Label *info_message;
|
||||
|
||||
Tree *vmem_tree;
|
||||
Button *vmem_refresh;
|
||||
LineEdit *vmem_total;
|
||||
|
||||
Tree *stack_dump;
|
||||
EditorDebuggerInspector *inspector;
|
||||
SceneDebuggerTree *scene_tree;
|
||||
|
||||
Ref<StreamPeerTCP> connection;
|
||||
Ref<PacketPeerStream> ppeer;
|
||||
|
||||
HashMap<NodePath, int> node_path_cache;
|
||||
int last_path_id;
|
||||
Map<String, int> res_path_cache;
|
||||
|
||||
EditorProfiler *profiler;
|
||||
EditorVisualProfiler *visual_profiler;
|
||||
EditorNetworkProfiler *network_profiler;
|
||||
|
||||
EditorNode *editor;
|
||||
|
||||
OS::ProcessID remote_pid = 0;
|
||||
bool breaked = false;
|
||||
bool can_debug = false;
|
||||
|
||||
bool live_debug;
|
||||
|
||||
CameraOverride camera_override;
|
||||
|
||||
void _performance_draw();
|
||||
void _performance_select();
|
||||
void _stack_dump_frame_selected();
|
||||
|
||||
void _file_selected(const String &p_file);
|
||||
void _parse_message(const String &p_msg, const Array &p_data);
|
||||
void _set_reason_text(const String &p_reason, MessageType p_type);
|
||||
void _update_buttons_state();
|
||||
void _remote_object_selected(ObjectID p_object);
|
||||
void _remote_object_edited(ObjectID, const String &p_prop, const Variant &p_value);
|
||||
void _remote_object_property_updated(ObjectID p_id, const String &p_property);
|
||||
|
||||
void _video_mem_request();
|
||||
|
||||
int _get_node_path_cache(const NodePath &p_path);
|
||||
|
||||
int _get_res_path_cache(const String &p_path);
|
||||
|
||||
void _live_edit_set();
|
||||
void _live_edit_clear();
|
||||
|
||||
void _method_changed(Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE);
|
||||
void _property_changed(Object *p_base, const StringName &p_property, const Variant &p_value);
|
||||
|
||||
void _error_activated();
|
||||
void _error_selected();
|
||||
|
||||
void _expand_errors_list();
|
||||
void _collapse_errors_list();
|
||||
|
||||
void _visual_profiler_activate(bool p_enable);
|
||||
void _profiler_activate(bool p_enable);
|
||||
void _profiler_seeked();
|
||||
|
||||
void _network_profiler_activate(bool p_enable);
|
||||
|
||||
void _clear_errors_list();
|
||||
|
||||
void _error_tree_item_rmb_selected(const Vector2 &p_pos);
|
||||
void _item_menu_id_pressed(int p_option);
|
||||
void _tab_changed(int p_tab);
|
||||
|
||||
void _put_msg(String p_message, Array p_data);
|
||||
void _export_csv();
|
||||
|
||||
void _clear_execution();
|
||||
void _stop_and_notify();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void request_remote_object(ObjectID p_obj_id);
|
||||
void update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value);
|
||||
Object *get_remote_object(ObjectID p_id);
|
||||
|
||||
// Needed by _live_edit_set, buttons state.
|
||||
void set_editor_remote_tree(const Tree *p_tree) { editor_remote_tree = p_tree; }
|
||||
|
||||
void request_remote_tree();
|
||||
const SceneDebuggerTree *get_remote_tree();
|
||||
|
||||
void start(Ref<StreamPeerTCP> p_connection);
|
||||
void stop();
|
||||
|
||||
void debug_skip_breakpoints();
|
||||
void debug_copy();
|
||||
|
||||
void debug_next();
|
||||
void debug_step();
|
||||
void debug_break();
|
||||
void debug_continue();
|
||||
bool is_breaked() const { return breaked; }
|
||||
bool is_debuggable() const { return can_debug; }
|
||||
bool is_session_active() { return connection.is_valid() && connection->is_connected_to_host(); };
|
||||
int get_remote_pid() const { return remote_pid; }
|
||||
|
||||
int get_error_count() const { return error_count; }
|
||||
int get_warning_count() const { return warning_count; }
|
||||
String get_stack_script_file() const;
|
||||
int get_stack_script_line() const;
|
||||
int get_stack_script_frame() const;
|
||||
|
||||
void update_tabs();
|
||||
String get_var_value(const String &p_var) const;
|
||||
|
||||
void save_node(ObjectID p_id, const String &p_file);
|
||||
void set_live_debugging(bool p_enable);
|
||||
|
||||
void live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name);
|
||||
void live_debug_instance_node(const NodePath &p_parent, const String &p_path, const String &p_name);
|
||||
void live_debug_remove_node(const NodePath &p_at);
|
||||
void live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id);
|
||||
void live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos);
|
||||
void live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name);
|
||||
void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos);
|
||||
|
||||
CameraOverride get_camera_override() const;
|
||||
void set_camera_override(CameraOverride p_override);
|
||||
|
||||
void set_breakpoint(const String &p_path, int p_line, bool p_enabled);
|
||||
|
||||
void update_live_edit_root();
|
||||
|
||||
void reload_scripts();
|
||||
|
||||
bool is_skip_breakpoints();
|
||||
|
||||
virtual Size2 get_minimum_size() const;
|
||||
ScriptEditorDebugger(EditorNode *p_editor = NULL);
|
||||
~ScriptEditorDebugger();
|
||||
};
|
||||
|
||||
#endif // SCRIPT_EDITOR_DEBUGGER_H
|
||||
Reference in New Issue
Block a user