diff options
Diffstat (limited to 'editor/debugger')
-rw-r--r-- | editor/debugger/SCsub | 5 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_inspector.cpp | 277 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_inspector.h | 98 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_node.cpp | 591 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_node.h | 177 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_tree.cpp | 274 | ||||
-rw-r--r-- | editor/debugger/editor_debugger_tree.h | 74 | ||||
-rw-r--r-- | editor/debugger/script_editor_debugger.cpp | 1875 | ||||
-rw-r--r-- | editor/debugger/script_editor_debugger.h | 271 |
9 files changed, 3642 insertions, 0 deletions
diff --git a/editor/debugger/SCsub b/editor/debugger/SCsub new file mode 100644 index 0000000000..2b1e889fb0 --- /dev/null +++ b/editor/debugger/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import('env') + +env.add_source_files(env.editor_sources, "*.cpp") diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp new file mode 100644 index 0000000000..35d7dda3f4 --- /dev/null +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -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); +} diff --git a/editor/debugger/editor_debugger_inspector.h b/editor/debugger/editor_debugger_inspector.h new file mode 100644 index 0000000000..e1dfbefcf3 --- /dev/null +++ b/editor/debugger/editor_debugger_inspector.h @@ -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 diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp new file mode 100644 index 0000000000..54708fd908 --- /dev/null +++ b/editor/debugger/editor_debugger_node.cpp @@ -0,0 +1,591 @@ +/*************************************************************************/ +/* 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(TabContainer *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(); + + add_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_LEFT)); + add_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_RIGHT)); + + tabs = memnew(TabContainer); + tabs->set_tab_align(TabContainer::ALIGN_LEFT); + tabs->set_tabs_visible(false); + tabs->connect_compat("tab_changed", this, "_debugger_changed"); + add_child(tabs); + + Ref<StyleBoxEmpty> empty; + empty.instance(); + tabs->add_style_override("panel", empty); + + auto_switch_remote_scene_tree = EDITOR_DEF("debugger/auto_switch_to_remote_scene_tree", false); + _add_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); + + EditorNode *editor = EditorNode::get_singleton(); + 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() { + ScriptEditorDebugger *node = memnew(ScriptEditorDebugger(EditorNode::get_singleton())); + + int id = tabs->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)); + + if (tabs->get_tab_count() > 0) { + get_debugger(0)->clear_style(); + } + + tabs->add_child(node); + + node->set_name("Session " + itos(tabs->get_tab_count())); + if (tabs->get_tab_count() > 1) { + node->clear_style(); + tabs->set_tabs_visible(true); + tabs->add_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); + } + + 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>(tabs->get_tab_control(p_id)); +} + +ScriptEditorDebugger *EditorDebuggerNode::get_current_debugger() const { + return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(tabs->get_current_tab())); +} + +ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const { + return Object::cast_to<ScriptEditorDebugger>(tabs->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(tabs, [&](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_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; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + if (tabs->get_tab_count() > 1) { + add_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_LEFT)); + add_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_RIGHT)); + + tabs->add_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); + } + } 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(tabs, [&](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(tabs, [&](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(tabs, [&](ScriptEditorDebugger *dbg) { + if (debugger || dbg->is_session_active()) + return; + debugger = dbg; + }); + if (debugger == NULL) { + if (tabs->get_tab_count() <= 4) { // Max 4 debugging sessions active. + debugger = _add_debugger(); + } 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(tabs, [&](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(tabs, [&](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; + tabs->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(tabs, [&](ScriptEditorDebugger *dbg) { + dbg->set_breakpoint(p_path, p_line, p_enabled); + }); +} + +void EditorDebuggerNode::reload_scripts() { + _for_all(tabs, [&](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 != tabs->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 != tabs->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 != tabs->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 != tabs->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 != tabs->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->tabs, [&](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->tabs, [&](ScriptEditorDebugger *dbg) { + dbg->_property_changed(p_base, p_property, p_value); + }); +} + +// LiveDebug +void EditorDebuggerNode::set_live_debugging(bool p_enabled) { + + _for_all(tabs, [&](ScriptEditorDebugger *dbg) { + dbg->set_live_debugging(p_enabled); + }); +} +void EditorDebuggerNode::update_live_edit_root() { + _for_all(tabs, [&](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(tabs, [&](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(tabs, [&](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(tabs, [&](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(tabs, [&](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(tabs, [&](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(tabs, [&](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(tabs, [&](ScriptEditorDebugger *dbg) { + dbg->live_debug_reparent_node(p_at, p_new_place, p_new_name, p_at_pos); + }); +} diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h new file mode 100644 index 0000000000..13a1d6dcb3 --- /dev/null +++ b/editor/debugger/editor_debugger_node.h @@ -0,0 +1,177 @@ +/*************************************************************************/ +/* 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 MarginContainer { + + GDCLASS(EditorDebuggerNode, MarginContainer); + +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 source < p_b.source; + 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; + TabContainer *tabs = 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(); + 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 diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp new file mode 100644 index 0000000000..9ba5d0cbe1 --- /dev/null +++ b/editor/debugger/editor_debugger_tree.cpp @@ -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); +} diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h new file mode 100644 index 0000000000..d9084bc596 --- /dev/null +++ b/editor/debugger/editor_debugger_tree.h @@ -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 diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp new file mode 100644 index 0000000000..8feb1ee03a --- /dev/null +++ b/editor/debugger/script_editor_debugger.cpp @@ -0,0 +1,1875 @@ +/*************************************************************************/ +/* script_editor_debugger.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 "script_editor_debugger.h" + +#include "core/io/marshalls.h" +#include "core/project_settings.h" +#include "core/script_debugger_remote.h" +#include "core/ustring.h" +#include "editor/editor_log.h" +#include "editor/editor_network_profiler.h" +#include "editor/editor_node.h" +#include "editor/editor_profiler.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "editor/editor_visual_profiler.h" +#include "editor/plugins/canvas_item_editor_plugin.h" +#include "editor/plugins/spatial_editor_plugin.h" +#include "editor/property_editor.h" +#include "main/performance.h" +#include "scene/debugger/scene_debugger.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/rich_text_label.h" +#include "scene/gui/separator.h" +#include "scene/gui/split_container.h" +#include "scene/gui/tab_container.h" +#include "scene/gui/texture_button.h" +#include "scene/gui/tree.h" +#include "scene/resources/packed_scene.h" + +void ScriptEditorDebugger::_put_msg(String p_message, Array p_data) { + if (is_session_active()) { + Array msg; + msg.push_back(p_message); + msg.push_back(p_data); + ppeer->put_var(msg); + } +} + +void ScriptEditorDebugger::debug_copy() { + String msg = reason->get_text(); + if (msg == "") return; + OS::get_singleton()->set_clipboard(msg); +} + +void ScriptEditorDebugger::debug_skip_breakpoints() { + skip_breakpoints_value = !skip_breakpoints_value; + if (skip_breakpoints_value) + skip_breakpoints->set_icon(get_icon("DebugSkipBreakpointsOn", "EditorIcons")); + else + skip_breakpoints->set_icon(get_icon("DebugSkipBreakpointsOff", "EditorIcons")); + + Array msg; + msg.push_back(skip_breakpoints_value); + _put_msg("set_skip_breakpoints", msg); +} + +void ScriptEditorDebugger::debug_next() { + + ERR_FAIL_COND(!breaked); + + _put_msg("next", Array()); + _clear_execution(); +} +void ScriptEditorDebugger::debug_step() { + + ERR_FAIL_COND(!breaked); + + _put_msg("step", Array()); + _clear_execution(); +} + +void ScriptEditorDebugger::debug_break() { + + ERR_FAIL_COND(breaked); + + _put_msg("break", Array()); +} + +void ScriptEditorDebugger::debug_continue() { + + ERR_FAIL_COND(!breaked); + + // Allow focus stealing only if we actually run this client for security. + if (remote_pid && EditorNode::get_singleton()->has_child_process(remote_pid)) + OS::get_singleton()->enable_for_stealing_focus(remote_pid); + + _clear_execution(); + _put_msg("continue", Array()); +} + +void ScriptEditorDebugger::update_tabs() { + if (error_count == 0 && warning_count == 0) { + errors_tab->set_name(TTR("Errors")); + tabs->set_tab_icon(errors_tab->get_index(), Ref<Texture2D>()); + } else { + errors_tab->set_name(TTR("Errors") + " (" + itos(error_count + warning_count) + ")"); + if (error_count == 0) { + tabs->set_tab_icon(errors_tab->get_index(), get_icon("Warning", "EditorIcons")); + } else { + tabs->set_tab_icon(errors_tab->get_index(), get_icon("Error", "EditorIcons")); + } + } +} + +void ScriptEditorDebugger::clear_style() { + tabs->add_style_override("panel", NULL); +} + +void ScriptEditorDebugger::save_node(ObjectID p_id, const String &p_file) { + Array msg; + msg.push_back(p_id); + msg.push_back(p_file); + _put_msg("save_node", msg); +} + +void ScriptEditorDebugger::_file_selected(const String &p_file) { + Error err; + FileAccessRef file = FileAccess::open(p_file, FileAccess::WRITE, &err); + + if (err != OK) { + ERR_PRINT("Failed to open " + p_file); + return; + } + Vector<String> line; + line.resize(Performance::MONITOR_MAX); + + // signatures + for (int i = 0; i < Performance::MONITOR_MAX; i++) { + line.write[i] = Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)); + } + file->store_csv_line(line); + + // values + List<Vector<float> >::Element *E = perf_history.back(); + while (E) { + + Vector<float> &perf_data = E->get(); + for (int i = 0; i < perf_data.size(); i++) { + + line.write[i] = String::num_real(perf_data[i]); + } + file->store_csv_line(line); + E = E->prev(); + } + file->store_string("\n"); + + Vector<Vector<String> > profiler_data = profiler->get_data_as_csv(); + for (int i = 0; i < profiler_data.size(); i++) { + file->store_csv_line(profiler_data[i]); + } +} + +void ScriptEditorDebugger::request_remote_tree() { + + _put_msg("request_scene_tree", Array()); +} + +const SceneDebuggerTree *ScriptEditorDebugger::get_remote_tree() { + return scene_tree; +} + +void ScriptEditorDebugger::update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value) { + + Array msg; + msg.push_back(p_obj_id); + msg.push_back(p_prop); + msg.push_back(p_value); + _put_msg("set_object_property", msg); +} + +void ScriptEditorDebugger::request_remote_object(ObjectID p_obj_id) { + + ERR_FAIL_COND(p_obj_id.is_null()); + Array msg; + msg.push_back(p_obj_id); + _put_msg("inspect_object", msg); +} + +Object *ScriptEditorDebugger::get_remote_object(ObjectID p_id) { + return inspector->get_object(p_id); +} + +void ScriptEditorDebugger::_remote_object_selected(ObjectID p_id) { + emit_signal("remote_object_requested", p_id); +} + +void ScriptEditorDebugger::_remote_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) { + update_remote_object(p_id, p_prop, p_value); + request_remote_object(p_id); +} + +void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const String &p_property) { + emit_signal("remote_object_property_updated", p_id, p_property); +} + +void ScriptEditorDebugger::_video_mem_request() { + + _put_msg("request_video_mem", Array()); +} + +Size2 ScriptEditorDebugger::get_minimum_size() const { + + Size2 ms = MarginContainer::get_minimum_size(); + ms.y = MAX(ms.y, 250 * EDSCALE); + return ms; +} + +void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) { + + if (p_msg == "debug_enter") { + + _put_msg("get_stack_dump", Array()); + + ERR_FAIL_COND(p_data.size() != 2); + bool can_continue = p_data[0]; + String error = p_data[1]; + breaked = true; + can_debug = can_continue; + _update_buttons_state(); + _set_reason_text(error, MESSAGE_ERROR); + emit_signal("breaked", true, can_continue); + OS::get_singleton()->move_window_to_foreground(); + if (error != "") { + tabs->set_current_tab(0); + } + profiler->set_enabled(false); + inspector->clear_cache(); // Take a chance to force remote objects update. + + } else if (p_msg == "debug_exit") { + + breaked = false; + can_debug = false; + _clear_execution(); + _update_buttons_state(); + _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS); + emit_signal("breaked", false, false); + profiler->set_enabled(true); + profiler->disable_seeking(); + } else if (p_msg == "message:set_pid") { + + ERR_FAIL_COND(p_data.size() < 1); + remote_pid = p_data[0]; + } else if (p_msg == "message:click_ctrl") { + + ERR_FAIL_COND(p_data.size() < 2); + clicked_ctrl->set_text(p_data[0]); + clicked_ctrl_type->set_text(p_data[1]); + } else if (p_msg == "message:scene_tree") { + + scene_tree->nodes.clear(); + scene_tree->deserialize(p_data); + emit_signal("remote_tree_updated"); + _update_buttons_state(); + } else if (p_msg == "message:inspect_object") { + + ObjectID id = inspector->add_object(p_data); + if (id.is_valid()) + emit_signal("remote_object_updated", id); + } else if (p_msg == "message:video_mem") { + + vmem_tree->clear(); + TreeItem *root = vmem_tree->create_item(); + ScriptDebuggerRemote::ResourceUsage usage; + usage.deserialize(p_data); + + int total = 0; + + for (List<ScriptDebuggerRemote::ResourceInfo>::Element *E = usage.infos.front(); E; E = E->next()) { + + TreeItem *it = vmem_tree->create_item(root); + String type = E->get().type; + int bytes = E->get().vram; + it->set_text(0, E->get().path); + it->set_text(1, type); + it->set_text(2, E->get().format); + it->set_text(3, String::humanize_size(bytes)); + total += bytes; + + if (has_icon(type, "EditorIcons")) + it->set_icon(0, get_icon(type, "EditorIcons")); + } + + vmem_total->set_tooltip(TTR("Bytes:") + " " + itos(total)); + vmem_total->set_text(String::humanize_size(total)); + + } else if (p_msg == "stack_dump") { + + ScriptDebuggerRemote::ScriptStackDump stack; + stack.deserialize(p_data); + + stack_dump->clear(); + inspector->clear_stack_variables(); + TreeItem *r = stack_dump->create_item(); + + for (int i = 0; i < stack.frames.size(); i++) { + + TreeItem *s = stack_dump->create_item(r); + Dictionary d; + d["frame"] = i; + d["file"] = stack.frames[i].file; + d["function"] = stack.frames[i].func; + d["line"] = stack.frames[i].line; + s->set_metadata(0, d); + + String line = itos(i) + " - " + String(d["file"]) + ":" + itos(d["line"]) + " - at function: " + d["function"]; + s->set_text(0, line); + + if (i == 0) + s->select(0); + } + } else if (p_msg == "stack_frame_vars") { + + inspector->clear_stack_variables(); + + } else if (p_msg == "stack_frame_var") { + + inspector->add_stack_variable(p_data); + + } else if (p_msg == "output") { + ERR_FAIL_COND(p_data.size() < 1); + String t = p_data[0]; + EditorNode::get_log()->add_message(t); + + } else if (p_msg == "performance") { + Vector<float> p; + p.resize(p_data.size()); + for (int i = 0; i < p_data.size(); i++) { + p.write[i] = p_data[i]; + if (i < perf_items.size()) { + + const float value = p[i]; + String label = rtos(value); + String tooltip = label; + switch (Performance::MonitorType((int)perf_items[i]->get_metadata(1))) { + case Performance::MONITOR_TYPE_MEMORY: { + label = String::humanize_size(value); + tooltip = label; + } break; + case Performance::MONITOR_TYPE_TIME: { + label = rtos(value * 1000).pad_decimals(2) + " ms"; + tooltip = label; + } break; + default: { + tooltip += " " + perf_items[i]->get_text(0); + } break; + } + + perf_items[i]->set_text(1, label); + perf_items[i]->set_tooltip(1, tooltip); + if (p[i] > perf_max[i]) + perf_max.write[i] = p[i]; + } + } + perf_history.push_front(p); + perf_draw->update(); + + } else if (p_msg == "visual_profile") { + // TODO check me. + uint64_t frame = p_data[0]; + Vector<String> names = p_data[1]; + Vector<real_t> values = p_data[2]; + + EditorVisualProfiler::Metric metric; + metric.areas.resize(names.size()); + metric.frame_number = frame; + metric.valid = true; + + { + EditorVisualProfiler::Metric::Area *areas_ptr = metric.areas.ptrw(); + int metric_count = names.size(); + + const String *rs = names.ptr(); + const real_t *rr = values.ptr(); + + for (int i = 0; i < metric_count; i++) { + + areas_ptr[i].name = rs[i]; + areas_ptr[i].cpu_time = rr[i * 2 + 0]; + areas_ptr[i].gpu_time = rr[i * 2 + 1]; + } + } + visual_profiler->add_frame_metric(metric); + + } else if (p_msg == "error") { + + ScriptDebuggerRemote::OutputError oe; + ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message"); + + // Format time. + Array time_vals; + time_vals.push_back(oe.hr); + time_vals.push_back(oe.min); + time_vals.push_back(oe.sec); + time_vals.push_back(oe.msec); + bool e; + String time = String("%d:%02d:%02d:%04d").sprintf(time_vals, &e); + + // Rest of the error data. + bool source_is_project_file = oe.source_file.begins_with("res://"); + + // Metadata to highlight error line in scripts. + Array source_meta; + source_meta.push_back(oe.source_file); + source_meta.push_back(oe.source_line); + + // Create error tree to display above error or warning details. + TreeItem *r = error_tree->get_root(); + if (!r) { + r = error_tree->create_item(); + } + + // Also provide the relevant details as tooltip to quickly check without + // uncollapsing the tree. + String tooltip = oe.warning ? TTR("Warning:") : TTR("Error:"); + + TreeItem *error = error_tree->create_item(r); + error->set_collapsed(true); + + error->set_icon(0, get_icon(oe.warning ? "Warning" : "Error", "EditorIcons")); + error->set_text(0, time); + error->set_text_align(0, TreeItem::ALIGN_LEFT); + + String error_title; + // Include method name, when given, in error title. + if (!oe.source_func.empty()) + error_title += oe.source_func + ": "; + // If we have a (custom) error message, use it as title, and add a C++ Error + // item with the original error condition. + error_title += oe.error_descr.empty() ? oe.error : oe.error_descr; + error->set_text(1, error_title); + tooltip += " " + error_title + "\n"; + + if (!oe.error_descr.empty()) { + // Add item for C++ error condition. + TreeItem *cpp_cond = error_tree->create_item(error); + cpp_cond->set_text(0, "<" + TTR("C++ Error") + ">"); + cpp_cond->set_text(1, oe.error); + cpp_cond->set_text_align(0, TreeItem::ALIGN_LEFT); + tooltip += TTR("C++ Error:") + " " + oe.error + "\n"; + if (source_is_project_file) + cpp_cond->set_metadata(0, source_meta); + } + Vector<uint8_t> v; + v.resize(100); + + // Source of the error. + String source_txt = (source_is_project_file ? oe.source_file.get_file() : oe.source_file) + ":" + itos(oe.source_line); + if (!oe.source_func.empty()) + source_txt += " @ " + oe.source_func + "()"; + + TreeItem *cpp_source = error_tree->create_item(error); + cpp_source->set_text(0, "<" + (source_is_project_file ? TTR("Source") : TTR("C++ Source")) + ">"); + cpp_source->set_text(1, source_txt); + cpp_source->set_text_align(0, TreeItem::ALIGN_LEFT); + tooltip += (source_is_project_file ? TTR("Source:") : TTR("C++ Source:")) + " " + source_txt + "\n"; + + // Set metadata to highlight error line in scripts. + if (source_is_project_file) { + error->set_metadata(0, source_meta); + cpp_source->set_metadata(0, source_meta); + } + + error->set_tooltip(0, tooltip); + error->set_tooltip(1, tooltip); + + // Format stack trace. + // stack_items_count is the number of elements to parse, with 3 items per frame + // of the stack trace (script, method, line). + const ScriptLanguage::StackInfo *infos = oe.callstack.ptr(); + for (unsigned int i = 0; i < (unsigned int)oe.callstack.size(); i++) { + + TreeItem *stack_trace = error_tree->create_item(error); + + Array meta; + meta.push_back(infos[i].file); + meta.push_back(infos[i].line); + stack_trace->set_metadata(0, meta); + + if (i == 0) { + stack_trace->set_text(0, "<" + TTR("Stack Trace") + ">"); + stack_trace->set_text_align(0, TreeItem::ALIGN_LEFT); + error->set_metadata(0, meta); + } + stack_trace->set_text(1, infos[i].file.get_file() + ":" + itos(infos[i].line) + " @ " + infos[i].func + "()"); + } + + if (oe.warning) + warning_count++; + else + error_count++; + + } else if (p_msg == "profile_sig") { + // Cache a profiler signature. + ScriptDebuggerRemote::ProfilerSignature sig; + sig.deserialize(p_data); + profiler_signature[sig.id] = sig.name; + + } else if (p_msg == "profile_frame" || p_msg == "profile_total") { + EditorProfiler::Metric metric; + ScriptDebuggerRemote::ProfilerFrame frame; + frame.deserialize(p_data); + metric.valid = true; + metric.frame_number = frame.frame_number; + metric.frame_time = frame.frame_time; + metric.idle_time = frame.idle_time; + metric.physics_time = frame.physics_time; + metric.physics_frame_time = frame.physics_frame_time; + int frame_data_amount = frame.frames_data.size(); + int frame_function_amount = frame.frame_functions.size(); + + if (frame_data_amount) { + EditorProfiler::Metric::Category frame_time; + frame_time.signature = "category_frame_time"; + frame_time.name = "Frame Time"; + frame_time.total_time = metric.frame_time; + + EditorProfiler::Metric::Category::Item item; + item.calls = 1; + item.line = 0; + + item.name = "Physics Time"; + item.total = metric.physics_time; + item.self = item.total; + item.signature = "physics_time"; + + frame_time.items.push_back(item); + + item.name = "Idle Time"; + item.total = metric.idle_time; + item.self = item.total; + item.signature = "idle_time"; + + frame_time.items.push_back(item); + + item.name = "Physics Frame Time"; + item.total = metric.physics_frame_time; + item.self = item.total; + item.signature = "physics_frame_time"; + + frame_time.items.push_back(item); + + metric.categories.push_back(frame_time); + } + + for (int i = 0; i < frame_data_amount; i++) { + + EditorProfiler::Metric::Category c; + String name = frame.frames_data[i].name; + Array values = frame.frames_data[i].data; + c.name = name.capitalize(); + c.items.resize(values.size() / 2); + c.total_time = 0; + c.signature = "categ::" + name; + for (int j = 0; j < values.size(); j += 2) { + + EditorProfiler::Metric::Category::Item item; + item.calls = 1; + item.line = 0; + item.name = values[j]; + item.self = values[j + 1]; + item.total = item.self; + item.signature = "categ::" + name + "::" + item.name; + item.name = item.name.capitalize(); + c.total_time += item.total; + c.items.write[j / 2] = item; + } + metric.categories.push_back(c); + } + + EditorProfiler::Metric::Category funcs; + funcs.total_time = frame.script_time; + funcs.items.resize(frame_function_amount); + funcs.name = "Script Functions"; + funcs.signature = "script_functions"; + for (int i = 0; i < frame_function_amount; i++) { + + int signature = frame.frame_functions[i].sig_id; + int calls = frame.frame_functions[i].call_count; + float total = frame.frame_functions[i].total_time; + float self = frame.frame_functions[i].self_time; + + EditorProfiler::Metric::Category::Item item; + if (profiler_signature.has(signature)) { + + item.signature = profiler_signature[signature]; + + String name = profiler_signature[signature]; + Vector<String> strings = name.split("::"); + if (strings.size() == 3) { + item.name = strings[2]; + item.script = strings[0]; + item.line = strings[1].to_int(); + } else if (strings.size() == 4) { //Built-in scripts have an :: in their name + item.name = strings[3]; + item.script = strings[0] + "::" + strings[1]; + item.line = strings[2].to_int(); + } + + } else { + item.name = "SigErr " + itos(signature); + } + + item.calls = calls; + item.self = self; + item.total = total; + funcs.items.write[i] = item; + } + + metric.categories.push_back(funcs); + + if (p_msg == "profile_frame") + profiler->add_frame_metric(metric, false); + else + profiler->add_frame_metric(metric, true); + + } else if (p_msg == "network_profile") { + ScriptDebuggerRemote::NetworkProfilerFrame frame; + frame.deserialize(p_data); + for (int i = 0; i < frame.infos.size(); i++) { + network_profiler->add_node_frame_data(frame.infos[i]); + } + } else if (p_msg == "network_bandwidth") { + ERR_FAIL_COND(p_data.size() < 2); + network_profiler->set_bandwidth(p_data[0], p_data[1]); + } else if (p_msg == "kill_me") { + + emit_signal("stop_requested"); + _stop_and_notify(); + } +} + +void ScriptEditorDebugger::_set_reason_text(const String &p_reason, MessageType p_type) { + switch (p_type) { + case MESSAGE_ERROR: + reason->add_color_override("font_color", get_color("error_color", "Editor")); + break; + case MESSAGE_WARNING: + reason->add_color_override("font_color", get_color("warning_color", "Editor")); + break; + default: + reason->add_color_override("font_color", get_color("success_color", "Editor")); + } + reason->set_text(p_reason); + reason->set_tooltip(p_reason.word_wrap(80)); +} + +void ScriptEditorDebugger::_performance_select() { + + perf_draw->update(); +} + +void ScriptEditorDebugger::_performance_draw() { + + Vector<int> which; + for (int i = 0; i < perf_items.size(); i++) { + + if (perf_items[i]->is_checked(0)) + which.push_back(i); + } + + if (which.empty()) { + info_message->show(); + return; + } + + info_message->hide(); + + Ref<StyleBox> graph_sb = get_stylebox("normal", "TextEdit"); + Ref<Font> graph_font = get_font("font", "TextEdit"); + + int cols = Math::ceil(Math::sqrt((float)which.size())); + int rows = Math::ceil((float)which.size() / cols); + if (which.size() == 1) + rows = 1; + + int margin = 3; + int point_sep = 5; + Size2i s = Size2i(perf_draw->get_size()) / Size2i(cols, rows); + for (int i = 0; i < which.size(); i++) { + + Point2i p(i % cols, i / cols); + Rect2i r(p * s, s); + r.position += Point2(margin, margin); + r.size -= Point2(margin, margin) * 2.0; + perf_draw->draw_style_box(graph_sb, r); + r.position += graph_sb->get_offset(); + r.size -= graph_sb->get_minimum_size(); + int pi = which[i]; + Color c = get_color("accent_color", "Editor"); + float h = (float)which[i] / (float)(perf_items.size()); + // Use a darker color on light backgrounds for better visibility + float value_multiplier = EditorSettings::get_singleton()->is_dark_theme() ? 1.4 : 0.55; + c.set_hsv(Math::fmod(h + 0.4, 0.9), c.get_s() * 0.9, c.get_v() * value_multiplier); + + c.a = 0.6; + perf_draw->draw_string(graph_font, r.position + Point2(0, graph_font->get_ascent()), perf_items[pi]->get_text(0), c, r.size.x); + c.a = 0.9; + perf_draw->draw_string(graph_font, r.position + Point2(0, graph_font->get_ascent() + graph_font->get_height()), perf_items[pi]->get_text(1), c, r.size.y); + + float spacing = point_sep / float(cols); + float from = r.size.width; + + List<Vector<float> >::Element *E = perf_history.front(); + float prev = -1; + while (from >= 0 && E) { + + float m = perf_max[pi]; + if (m == 0) + m = 0.00001; + float h2 = E->get()[pi] / m; + h2 = (1.0 - h2) * r.size.y; + + if (E != perf_history.front()) + perf_draw->draw_line(r.position + Point2(from, h2), r.position + Point2(from + spacing, prev), c, Math::round(EDSCALE)); + prev = h2; + E = E->next(); + from -= spacing; + } + } +} + +void ScriptEditorDebugger::_notification(int p_what) { + + switch (p_what) { + + case NOTIFICATION_ENTER_TREE: { + + skip_breakpoints->set_icon(get_icon("DebugSkipBreakpointsOff", "EditorIcons")); + copy->set_icon(get_icon("ActionCopy", "EditorIcons")); + + step->set_icon(get_icon("DebugStep", "EditorIcons")); + next->set_icon(get_icon("DebugNext", "EditorIcons")); + dobreak->set_icon(get_icon("Pause", "EditorIcons")); + docontinue->set_icon(get_icon("DebugContinue", "EditorIcons")); + le_set->connect_compat("pressed", this, "_live_edit_set"); + le_clear->connect_compat("pressed", this, "_live_edit_clear"); + error_tree->connect_compat("item_selected", this, "_error_selected"); + error_tree->connect_compat("item_activated", this, "_error_activated"); + vmem_refresh->set_icon(get_icon("Reload", "EditorIcons")); + + reason->add_color_override("font_color", get_color("error_color", "Editor")); + + } break; + case NOTIFICATION_PROCESS: { + + if (is_session_active()) { + + if (camera_override == OVERRIDE_2D) { + CanvasItemEditor *editor = CanvasItemEditor::get_singleton(); + + Dictionary state = editor->get_state(); + float zoom = state["zoom"]; + Point2 offset = state["ofs"]; + Transform2D transform; + + transform.scale_basis(Size2(zoom, zoom)); + transform.elements[2] = -offset * zoom; + + Array msg; + msg.push_back(transform); + _put_msg("override_camera_2D:transform", msg); + + } else if (camera_override >= OVERRIDE_3D_1) { + int viewport_idx = camera_override - OVERRIDE_3D_1; + SpatialEditorViewport *viewport = SpatialEditor::get_singleton()->get_editor_viewport(viewport_idx); + Camera *const cam = viewport->get_camera(); + + Array msg; + msg.push_back(cam->get_camera_transform()); + if (cam->get_projection() == Camera::PROJECTION_ORTHOGONAL) { + msg.push_back(false); + msg.push_back(cam->get_size()); + } else { + msg.push_back(true); + msg.push_back(cam->get_fov()); + } + msg.push_back(cam->get_znear()); + msg.push_back(cam->get_zfar()); + _put_msg("override_camera_3D:transform", msg); + } + } + + if (!is_session_active()) { + _stop_and_notify(); + break; + }; + + if (ppeer->get_available_packet_count() <= 0) { + break; + }; + + const uint64_t until = OS::get_singleton()->get_ticks_msec() + 20; + + while (ppeer->get_available_packet_count() > 0) { + + Variant cmd; + Error ret = ppeer->get_var(cmd); + if (ret != OK) { + _stop_and_notify(); + ERR_FAIL_MSG("Error decoding variant from peer"); + } + if (cmd.get_type() != Variant::ARRAY) { + _stop_and_notify(); + ERR_FAIL_MSG("Invalid message format received from peer"); + } + Array arr = cmd; + if (arr.size() != 2 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::ARRAY) { + _stop_and_notify(); + ERR_FAIL_MSG("Invalid message format received from peer"); + } + _parse_message(arr[0], arr[1]); + + if (OS::get_singleton()->get_ticks_msec() > until) + break; + } + } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + + if (tabs->has_stylebox_override("panel")) { + tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); + } + + copy->set_icon(get_icon("ActionCopy", "EditorIcons")); + step->set_icon(get_icon("DebugStep", "EditorIcons")); + next->set_icon(get_icon("DebugNext", "EditorIcons")); + dobreak->set_icon(get_icon("Pause", "EditorIcons")); + docontinue->set_icon(get_icon("DebugContinue", "EditorIcons")); + vmem_refresh->set_icon(get_icon("Reload", "EditorIcons")); + } break; + } +} + +void ScriptEditorDebugger::_clear_execution() { + TreeItem *ti = stack_dump->get_selected(); + if (!ti) + return; + + Dictionary d = ti->get_metadata(0); + + stack_script = ResourceLoader::load(d["file"]); + emit_signal("clear_execution", stack_script); + stack_script.unref(); + stack_dump->clear(); + inspector->clear_stack_variables(); +} + +void ScriptEditorDebugger::start(Ref<StreamPeerTCP> p_connection) { + + error_count = 0; + warning_count = 0; + stop(); + + connection = p_connection; + ppeer->set_stream_peer(connection); + + perf_history.clear(); + for (int i = 0; i < Performance::MONITOR_MAX; i++) { + + perf_max.write[i] = 0; + } + + set_process(true); + breaked = false; + can_debug = true; + camera_override = OVERRIDE_NONE; + + tabs->set_current_tab(0); + _set_reason_text(TTR("Debug session started."), MESSAGE_SUCCESS); + _update_buttons_state(); + + if (profiler->is_profiling()) { + _profiler_activate(true); + } + + if (network_profiler->is_profiling()) { + _network_profiler_activate(true); + } +} + +void ScriptEditorDebugger::_update_buttons_state() { + const bool active = is_session_active(); + const bool has_editor_tree = active && editor_remote_tree && editor_remote_tree->get_selected(); + vmem_refresh->set_disabled(!active); + step->set_disabled(!active || !breaked || !can_debug); + next->set_disabled(!active || !breaked || !can_debug); + copy->set_disabled(!active || !breaked); + docontinue->set_disabled(!active || !breaked); + dobreak->set_disabled(!active || breaked); + le_clear->set_disabled(!active); + le_set->set_disabled(!has_editor_tree); +} + +void ScriptEditorDebugger::_stop_and_notify() { + stop(); + emit_signal("stopped"); + _set_reason_text(TTR("Debug session closed."), MESSAGE_WARNING); +} + +void ScriptEditorDebugger::stop() { + + set_process(false); + breaked = false; + can_debug = false; + remote_pid = 0; + _clear_execution(); + + inspector->clear_cache(); + ppeer->set_stream_peer(Ref<StreamPeer>()); + + if (connection.is_valid()) { + connection.unref(); + reason->set_text(""); + reason->set_tooltip(""); + } + + node_path_cache.clear(); + res_path_cache.clear(); + profiler_signature.clear(); + + inspector->edit(NULL); + _update_buttons_state(); +} + +void ScriptEditorDebugger::_profiler_activate(bool p_enable) { + + if (p_enable) { + profiler_signature.clear(); + Array msg; + int max_funcs = EditorSettings::get_singleton()->get("debugger/profiler_frame_max_functions"); + max_funcs = CLAMP(max_funcs, 16, 512); + msg.push_back(max_funcs); + _put_msg("start_profiling", msg); + print_verbose("Starting profiling."); + + } else { + _put_msg("stop_profiling", Array()); + print_verbose("Ending profiling."); + } +} + +void ScriptEditorDebugger::_visual_profiler_activate(bool p_enable) { + + if (!connection.is_valid()) + return; + + if (p_enable) { + profiler_signature.clear(); + _put_msg("start_visual_profiling", Array()); + print_verbose("Starting visual profiling."); + + } else { + _put_msg("stop_visual_profiling", Array()); + print_verbose("Ending visual profiling."); + } +} + +void ScriptEditorDebugger::_network_profiler_activate(bool p_enable) { + + if (p_enable) { + profiler_signature.clear(); + _put_msg("start_network_profiling", Array()); + print_verbose("Starting network profiling."); + + } else { + _put_msg("stop_network_profiling", Array()); + print_verbose("Ending network profiling."); + } +} + +void ScriptEditorDebugger::_profiler_seeked() { + + if (breaked) + return; + debug_break(); +} + +void ScriptEditorDebugger::_stack_dump_frame_selected() { + + emit_signal("stack_frame_selected"); + + int frame = get_stack_script_frame(); + + if (is_session_active() && frame >= 0) { + Array msg; + msg.push_back(frame); + _put_msg("get_stack_frame_vars", msg); + } else { + inspector->edit(NULL); + } +} + +void ScriptEditorDebugger::_export_csv() { + + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file_dialog->popup_centered_ratio(); +} + +String ScriptEditorDebugger::get_var_value(const String &p_var) const { + if (!breaked) + return String(); + return inspector->get_stack_variable(p_var); +} + +int ScriptEditorDebugger::_get_node_path_cache(const NodePath &p_path) { + + const int *r = node_path_cache.getptr(p_path); + if (r) + return *r; + + last_path_id++; + + node_path_cache[p_path] = last_path_id; + Array msg; + msg.push_back(p_path); + msg.push_back(last_path_id); + _put_msg("live_node_path", msg); + + return last_path_id; +} + +int ScriptEditorDebugger::_get_res_path_cache(const String &p_path) { + + Map<String, int>::Element *E = res_path_cache.find(p_path); + + if (E) + return E->get(); + + last_path_id++; + + res_path_cache[p_path] = last_path_id; + Array msg; + msg.push_back(p_path); + msg.push_back(last_path_id); + _put_msg("live_res_path", msg); + + return last_path_id; +} + +void ScriptEditorDebugger::_method_changed(Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE) { + + if (!p_base || !live_debug || !is_session_active() || !editor->get_edited_scene()) + return; + + Node *node = Object::cast_to<Node>(p_base); + + VARIANT_ARGPTRS + + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + //no pointers, sorry + if (argptr[i] && (argptr[i]->get_type() == Variant::OBJECT || argptr[i]->get_type() == Variant::_RID)) + return; + } + + if (node) { + + NodePath path = editor->get_edited_scene()->get_path_to(node); + int pathid = _get_node_path_cache(path); + + Array msg; + msg.push_back(pathid); + msg.push_back(p_name); + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + //no pointers, sorry + msg.push_back(*argptr[i]); + } + _put_msg("live_node_call", msg); + + return; + } + + Resource *res = Object::cast_to<Resource>(p_base); + + if (res && res->get_path() != String()) { + + String respath = res->get_path(); + int pathid = _get_res_path_cache(respath); + + Array msg; + msg.push_back(pathid); + msg.push_back(p_name); + for (int i = 0; i < VARIANT_ARG_MAX; i++) { + //no pointers, sorry + msg.push_back(*argptr[i]); + } + _put_msg("live_res_call", msg); + + return; + } +} + +void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p_property, const Variant &p_value) { + + if (!p_base || !live_debug || !editor->get_edited_scene()) + return; + + Node *node = Object::cast_to<Node>(p_base); + + if (node) { + + NodePath path = editor->get_edited_scene()->get_path_to(node); + int pathid = _get_node_path_cache(path); + + if (p_value.is_ref()) { + Ref<Resource> res = p_value; + if (res.is_valid() && res->get_path() != String()) { + + Array msg; + msg.push_back(pathid); + msg.push_back(p_property); + msg.push_back(res->get_path()); + _put_msg("live_node_prop_res", msg); + } + } else { + + Array msg; + msg.push_back(pathid); + msg.push_back(p_property); + msg.push_back(p_value); + _put_msg("live_node_prop", msg); + } + + return; + } + + Resource *res = Object::cast_to<Resource>(p_base); + + if (res && res->get_path() != String()) { + + String respath = res->get_path(); + int pathid = _get_res_path_cache(respath); + + if (p_value.is_ref()) { + Ref<Resource> res2 = p_value; + if (res2.is_valid() && res2->get_path() != String()) { + + Array msg; + msg.push_back(pathid); + msg.push_back(p_property); + msg.push_back(res2->get_path()); + _put_msg("live_res_prop_res", msg); + } + } else { + + Array msg; + msg.push_back(pathid); + msg.push_back(p_property); + msg.push_back(p_value); + _put_msg("live_res_prop", msg); + } + + return; + } +} + +String ScriptEditorDebugger::get_stack_script_file() const { + TreeItem *ti = stack_dump->get_selected(); + if (!ti) + return ""; + Dictionary d = ti->get_metadata(0); + return d["file"]; +} + +int ScriptEditorDebugger::get_stack_script_line() const { + TreeItem *ti = stack_dump->get_selected(); + if (!ti) + return -1; + Dictionary d = ti->get_metadata(0); + return d["line"]; +} + +int ScriptEditorDebugger::get_stack_script_frame() const { + TreeItem *ti = stack_dump->get_selected(); + if (!ti) + return -1; + Dictionary d = ti->get_metadata(0); + return d["frame"]; +} + +void ScriptEditorDebugger::set_live_debugging(bool p_enable) { + + live_debug = p_enable; +} + +void ScriptEditorDebugger::_live_edit_set() { + + if (!is_session_active() || !editor_remote_tree) + return; + + TreeItem *ti = editor_remote_tree->get_selected(); + if (!ti) + return; + + String path; + + while (ti) { + String lp = ti->get_text(0); + path = "/" + lp + path; + ti = ti->get_parent(); + } + + NodePath np = path; + + editor->get_editor_data().set_edited_scene_live_edit_root(np); + + update_live_edit_root(); +} + +void ScriptEditorDebugger::_live_edit_clear() { + + NodePath np = NodePath("/root"); + editor->get_editor_data().set_edited_scene_live_edit_root(np); + + update_live_edit_root(); +} + +void ScriptEditorDebugger::update_live_edit_root() { + + NodePath np = editor->get_editor_data().get_edited_scene_live_edit_root(); + + Array msg; + msg.push_back(np); + if (editor->get_edited_scene()) + msg.push_back(editor->get_edited_scene()->get_filename()); + else + msg.push_back(""); + _put_msg("live_set_root", msg); + live_edit_root->set_text(np); +} + +void ScriptEditorDebugger::live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name) { + + if (live_debug) { + Array msg; + msg.push_back(p_parent); + msg.push_back(p_type); + msg.push_back(p_name); + _put_msg("live_create_node", msg); + } +} + +void ScriptEditorDebugger::live_debug_instance_node(const NodePath &p_parent, const String &p_path, const String &p_name) { + + if (live_debug) { + Array msg; + msg.push_back(p_parent); + msg.push_back(p_path); + msg.push_back(p_name); + _put_msg("live_instance_node", msg); + } +} +void ScriptEditorDebugger::live_debug_remove_node(const NodePath &p_at) { + + if (live_debug) { + Array msg; + msg.push_back(p_at); + _put_msg("live_remove_node", msg); + } +} +void ScriptEditorDebugger::live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id) { + + if (live_debug) { + Array msg; + msg.push_back(p_at); + msg.push_back(p_keep_id); + _put_msg("live_remove_and_keep_node", msg); + } +} +void ScriptEditorDebugger::live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos) { + + if (live_debug) { + Array msg; + msg.push_back(p_id); + msg.push_back(p_at); + msg.push_back(p_at_pos); + _put_msg("live_restore_node", msg); + } +} +void ScriptEditorDebugger::live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name) { + + if (live_debug) { + Array msg; + msg.push_back(p_at); + msg.push_back(p_new_name); + _put_msg("live_duplicate_node", msg); + } +} +void ScriptEditorDebugger::live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) { + + if (live_debug) { + Array msg; + msg.push_back(p_at); + msg.push_back(p_new_place); + msg.push_back(p_new_name); + msg.push_back(p_at_pos); + _put_msg("live_reparent_node", msg); + } +} + +ScriptEditorDebugger::CameraOverride ScriptEditorDebugger::get_camera_override() const { + return camera_override; +} + +void ScriptEditorDebugger::set_camera_override(CameraOverride p_override) { + + if (p_override == OVERRIDE_2D && camera_override != OVERRIDE_2D) { + Array msg; + msg.push_back(true); + _put_msg("override_camera_2D:set", msg); + } else if (p_override != OVERRIDE_2D && camera_override == OVERRIDE_2D) { + Array msg; + msg.push_back(false); + _put_msg("override_camera_2D:set", msg); + } else if (p_override >= OVERRIDE_3D_1 && camera_override < OVERRIDE_3D_1) { + Array msg; + msg.push_back(true); + _put_msg("override_camera_3D:set", msg); + } else if (p_override < OVERRIDE_3D_1 && camera_override >= OVERRIDE_3D_1) { + Array msg; + msg.push_back(false); + _put_msg("override_camera_3D:set", msg); + } + + camera_override = p_override; +} + +void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool p_enabled) { + + Array msg; + msg.push_back(p_path); + msg.push_back(p_line); + msg.push_back(p_enabled); + _put_msg("breakpoint", msg); +} + +void ScriptEditorDebugger::reload_scripts() { + + _put_msg("reload_scripts", Array()); +} + +bool ScriptEditorDebugger::is_skip_breakpoints() { + return skip_breakpoints_value; +} + +void ScriptEditorDebugger::_error_activated() { + TreeItem *selected = error_tree->get_selected(); + + TreeItem *ci = selected->get_children(); + if (ci) { + selected->set_collapsed(!selected->is_collapsed()); + } +} + +void ScriptEditorDebugger::_error_selected() { + TreeItem *selected = error_tree->get_selected(); + Array meta = selected->get_metadata(0); + if (meta.size() == 0) { + return; + } + + emit_signal("error_selected", String(meta[0]), int(meta[1])); +} + +void ScriptEditorDebugger::_expand_errors_list() { + + TreeItem *root = error_tree->get_root(); + if (!root) + return; + + TreeItem *item = root->get_children(); + while (item) { + item->set_collapsed(false); + item = item->get_next(); + } +} + +void ScriptEditorDebugger::_collapse_errors_list() { + + TreeItem *root = error_tree->get_root(); + if (!root) + return; + + TreeItem *item = root->get_children(); + while (item) { + item->set_collapsed(true); + item = item->get_next(); + } +} + +void ScriptEditorDebugger::_clear_errors_list() { + + error_tree->clear(); + error_count = 0; + warning_count = 0; + _notification(NOTIFICATION_PROCESS); +} + +// Right click on specific file(s) or folder(s). +void ScriptEditorDebugger::_error_tree_item_rmb_selected(const Vector2 &p_pos) { + + item_menu->clear(); + item_menu->set_size(Size2(1, 1)); + + if (error_tree->is_anything_selected()) { + item_menu->add_icon_item(get_icon("ActionCopy", "EditorIcons"), TTR("Copy Error"), 0); + } + + if (item_menu->get_item_count() > 0) { + item_menu->set_position(error_tree->get_global_position() + p_pos); + item_menu->popup(); + } +} + +void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { + TreeItem *ti = error_tree->get_selected(); + while (ti->get_parent() != error_tree->get_root()) + ti = ti->get_parent(); + + String type; + + if (ti->get_icon(0) == get_icon("Warning", "EditorIcons")) { + type = "W "; + } else if (ti->get_icon(0) == get_icon("Error", "EditorIcons")) { + type = "E "; + } + + String text = ti->get_text(0) + " "; + int rpad_len = text.length(); + + text = type + text + ti->get_text(1) + "\n"; + TreeItem *ci = ti->get_children(); + while (ci) { + text += " " + ci->get_text(0).rpad(rpad_len) + ci->get_text(1) + "\n"; + ci = ci->get_next(); + } + + OS::get_singleton()->set_clipboard(text); +} + +void ScriptEditorDebugger::_tab_changed(int p_tab) { + if (tabs->get_tab_title(p_tab) == TTR("Video RAM")) { + // "Video RAM" tab was clicked, refresh the data it's dislaying when entering the tab. + _video_mem_request(); + } +} + +void ScriptEditorDebugger::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_stack_dump_frame_selected"), &ScriptEditorDebugger::_stack_dump_frame_selected); + + ClassDB::bind_method(D_METHOD("debug_skip_breakpoints"), &ScriptEditorDebugger::debug_skip_breakpoints); + ClassDB::bind_method(D_METHOD("debug_copy"), &ScriptEditorDebugger::debug_copy); + + ClassDB::bind_method(D_METHOD("debug_next"), &ScriptEditorDebugger::debug_next); + ClassDB::bind_method(D_METHOD("debug_step"), &ScriptEditorDebugger::debug_step); + ClassDB::bind_method(D_METHOD("debug_break"), &ScriptEditorDebugger::debug_break); + ClassDB::bind_method(D_METHOD("debug_continue"), &ScriptEditorDebugger::debug_continue); + ClassDB::bind_method(D_METHOD("_export_csv"), &ScriptEditorDebugger::_export_csv); + ClassDB::bind_method(D_METHOD("_performance_draw"), &ScriptEditorDebugger::_performance_draw); + ClassDB::bind_method(D_METHOD("_performance_select"), &ScriptEditorDebugger::_performance_select); + ClassDB::bind_method(D_METHOD("_video_mem_request"), &ScriptEditorDebugger::_video_mem_request); + ClassDB::bind_method(D_METHOD("_live_edit_set"), &ScriptEditorDebugger::_live_edit_set); + ClassDB::bind_method(D_METHOD("_live_edit_clear"), &ScriptEditorDebugger::_live_edit_clear); + + ClassDB::bind_method(D_METHOD("_error_selected"), &ScriptEditorDebugger::_error_selected); + ClassDB::bind_method(D_METHOD("_error_activated"), &ScriptEditorDebugger::_error_activated); + ClassDB::bind_method(D_METHOD("_expand_errors_list"), &ScriptEditorDebugger::_expand_errors_list); + ClassDB::bind_method(D_METHOD("_collapse_errors_list"), &ScriptEditorDebugger::_collapse_errors_list); + ClassDB::bind_method(D_METHOD("_profiler_activate"), &ScriptEditorDebugger::_profiler_activate); + ClassDB::bind_method(D_METHOD("_visual_profiler_activate"), &ScriptEditorDebugger::_visual_profiler_activate); + ClassDB::bind_method(D_METHOD("_network_profiler_activate"), &ScriptEditorDebugger::_network_profiler_activate); + ClassDB::bind_method(D_METHOD("_profiler_seeked"), &ScriptEditorDebugger::_profiler_seeked); + ClassDB::bind_method(D_METHOD("_clear_errors_list"), &ScriptEditorDebugger::_clear_errors_list); + + ClassDB::bind_method(D_METHOD("_error_tree_item_rmb_selected"), &ScriptEditorDebugger::_error_tree_item_rmb_selected); + ClassDB::bind_method(D_METHOD("_item_menu_id_pressed"), &ScriptEditorDebugger::_item_menu_id_pressed); + ClassDB::bind_method(D_METHOD("_tab_changed"), &ScriptEditorDebugger::_tab_changed); + ClassDB::bind_method(D_METHOD("_file_selected"), &ScriptEditorDebugger::_file_selected); + ClassDB::bind_method(D_METHOD("_remote_object_selected", "id"), &ScriptEditorDebugger::_remote_object_selected); + ClassDB::bind_method(D_METHOD("_remote_object_edited", "id", "property", "value"), &ScriptEditorDebugger::_remote_object_edited); + ClassDB::bind_method(D_METHOD("_remote_object_property_updated", "id", "property"), &ScriptEditorDebugger::_remote_object_property_updated); + + ClassDB::bind_method(D_METHOD("live_debug_create_node"), &ScriptEditorDebugger::live_debug_create_node); + ClassDB::bind_method(D_METHOD("live_debug_instance_node"), &ScriptEditorDebugger::live_debug_instance_node); + ClassDB::bind_method(D_METHOD("live_debug_remove_node"), &ScriptEditorDebugger::live_debug_remove_node); + ClassDB::bind_method(D_METHOD("live_debug_remove_and_keep_node"), &ScriptEditorDebugger::live_debug_remove_and_keep_node); + ClassDB::bind_method(D_METHOD("live_debug_restore_node"), &ScriptEditorDebugger::live_debug_restore_node); + ClassDB::bind_method(D_METHOD("live_debug_duplicate_node"), &ScriptEditorDebugger::live_debug_duplicate_node); + ClassDB::bind_method(D_METHOD("live_debug_reparent_node"), &ScriptEditorDebugger::live_debug_reparent_node); + ClassDB::bind_method(D_METHOD("request_remote_object", "id"), &ScriptEditorDebugger::request_remote_object); + ClassDB::bind_method(D_METHOD("update_remote_object", "id", "property", "value"), &ScriptEditorDebugger::update_remote_object); + + ADD_SIGNAL(MethodInfo("stopped")); + ADD_SIGNAL(MethodInfo("stop_requested")); + ADD_SIGNAL(MethodInfo("stack_frame_selected", PropertyInfo(Variant::INT, "frame"))); + ADD_SIGNAL(MethodInfo("error_selected", PropertyInfo(Variant::INT, "error"))); + ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"))); + ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script"))); + ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"))); + ADD_SIGNAL(MethodInfo("remote_object_requested", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("remote_object_updated", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("remote_object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"))); + ADD_SIGNAL(MethodInfo("remote_tree_updated")); +} + +ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { + + ppeer = Ref<PacketPeerStream>(memnew(PacketPeerStream)); + ppeer->set_input_buffer_max_size((1024 * 1024 * 8) - 4); // 8 MiB should be enough, minus 4 bytes for separator. + editor = p_editor; + + tabs = memnew(TabContainer); + tabs->set_tab_align(TabContainer::ALIGN_LEFT); + tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); + tabs->connect_compat("tab_changed", this, "_tab_changed"); + + add_child(tabs); + + { //debugger + VBoxContainer *vbc = memnew(VBoxContainer); + vbc->set_name(TTR("Debugger")); + Control *dbg = vbc; + + HBoxContainer *hbc = memnew(HBoxContainer); + vbc->add_child(hbc); + + reason = memnew(Label); + reason->set_text(""); + hbc->add_child(reason); + reason->set_h_size_flags(SIZE_EXPAND_FILL); + reason->set_autowrap(true); + reason->set_max_lines_visible(3); + reason->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + hbc->add_child(memnew(VSeparator)); + + skip_breakpoints = memnew(ToolButton); + hbc->add_child(skip_breakpoints); + skip_breakpoints->set_tooltip(TTR("Skip Breakpoints")); + skip_breakpoints->connect_compat("pressed", this, "debug_skip_breakpoints"); + + hbc->add_child(memnew(VSeparator)); + + copy = memnew(ToolButton); + hbc->add_child(copy); + copy->set_tooltip(TTR("Copy Error")); + copy->connect_compat("pressed", this, "debug_copy"); + + hbc->add_child(memnew(VSeparator)); + + step = memnew(ToolButton); + hbc->add_child(step); + step->set_tooltip(TTR("Step Into")); + step->set_shortcut(ED_GET_SHORTCUT("debugger/step_into")); + step->connect_compat("pressed", this, "debug_step"); + + next = memnew(ToolButton); + hbc->add_child(next); + next->set_tooltip(TTR("Step Over")); + next->set_shortcut(ED_GET_SHORTCUT("debugger/step_over")); + next->connect_compat("pressed", this, "debug_next"); + + hbc->add_child(memnew(VSeparator)); + + dobreak = memnew(ToolButton); + hbc->add_child(dobreak); + dobreak->set_tooltip(TTR("Break")); + dobreak->set_shortcut(ED_GET_SHORTCUT("debugger/break")); + dobreak->connect_compat("pressed", this, "debug_break"); + + docontinue = memnew(ToolButton); + hbc->add_child(docontinue); + docontinue->set_tooltip(TTR("Continue")); + docontinue->set_shortcut(ED_GET_SHORTCUT("debugger/continue")); + docontinue->connect_compat("pressed", this, "debug_continue"); + + HSplitContainer *sc = memnew(HSplitContainer); + vbc->add_child(sc); + sc->set_v_size_flags(SIZE_EXPAND_FILL); + + stack_dump = memnew(Tree); + stack_dump->set_allow_reselect(true); + stack_dump->set_columns(1); + stack_dump->set_column_titles_visible(true); + stack_dump->set_column_title(0, TTR("Stack Frames")); + stack_dump->set_h_size_flags(SIZE_EXPAND_FILL); + stack_dump->set_hide_root(true); + stack_dump->connect_compat("cell_selected", this, "_stack_dump_frame_selected"); + sc->add_child(stack_dump); + + inspector = memnew(EditorDebuggerInspector); + inspector->set_h_size_flags(SIZE_EXPAND_FILL); + inspector->set_enable_capitalize_paths(false); + inspector->set_read_only(true); + inspector->connect_compat("object_selected", this, "_remote_object_selected"); + inspector->connect_compat("object_edited", this, "_remote_object_edited"); + inspector->connect_compat("object_property_updated", this, "_remote_object_property_updated"); + sc->add_child(inspector); + tabs->add_child(dbg); + } + + { //errors + errors_tab = memnew(VBoxContainer); + errors_tab->set_name(TTR("Errors")); + + HBoxContainer *errhb = memnew(HBoxContainer); + errors_tab->add_child(errhb); + + Button *expand_all = memnew(Button); + expand_all->set_text(TTR("Expand All")); + expand_all->connect_compat("pressed", this, "_expand_errors_list"); + errhb->add_child(expand_all); + + Button *collapse_all = memnew(Button); + collapse_all->set_text(TTR("Collapse All")); + collapse_all->connect_compat("pressed", this, "_collapse_errors_list"); + errhb->add_child(collapse_all); + + Control *space = memnew(Control); + space->set_h_size_flags(SIZE_EXPAND_FILL); + errhb->add_child(space); + + clearbutton = memnew(Button); + clearbutton->set_text(TTR("Clear")); + clearbutton->set_h_size_flags(0); + clearbutton->connect_compat("pressed", this, "_clear_errors_list"); + errhb->add_child(clearbutton); + + error_tree = memnew(Tree); + error_tree->set_columns(2); + + error_tree->set_column_expand(0, false); + error_tree->set_column_min_width(0, 140); + + error_tree->set_column_expand(1, true); + + error_tree->set_select_mode(Tree::SELECT_ROW); + error_tree->set_hide_root(true); + error_tree->set_v_size_flags(SIZE_EXPAND_FILL); + error_tree->set_allow_rmb_select(true); + error_tree->connect_compat("item_rmb_selected", this, "_error_tree_item_rmb_selected"); + errors_tab->add_child(error_tree); + + item_menu = memnew(PopupMenu); + item_menu->connect_compat("id_pressed", this, "_item_menu_id_pressed"); + error_tree->add_child(item_menu); + + tabs->add_child(errors_tab); + } + + { // File dialog + file_dialog = memnew(EditorFileDialog); + file_dialog->connect_compat("file_selected", this, "_file_selected"); + add_child(file_dialog); + } + + { //profiler + profiler = memnew(EditorProfiler); + profiler->set_name(TTR("Profiler")); + tabs->add_child(profiler); + profiler->connect_compat("enable_profiling", this, "_profiler_activate"); + profiler->connect_compat("break_request", this, "_profiler_seeked"); + } + + { //frame profiler + visual_profiler = memnew(EditorVisualProfiler); + visual_profiler->set_name(TTR("Visual Profiler")); + tabs->add_child(visual_profiler); + visual_profiler->connect_compat("enable_profiling", this, "_visual_profiler_activate"); + visual_profiler->connect_compat("break_request", this, "_profiler_seeked"); + } + + { //network profiler + network_profiler = memnew(EditorNetworkProfiler); + network_profiler->set_name(TTR("Network Profiler")); + tabs->add_child(network_profiler); + network_profiler->connect_compat("enable_profiling", this, "_network_profiler_activate"); + } + + { //monitors + + HSplitContainer *hsp = memnew(HSplitContainer); + + perf_monitors = memnew(Tree); + perf_monitors->set_columns(2); + perf_monitors->set_column_title(0, TTR("Monitor")); + perf_monitors->set_column_title(1, TTR("Value")); + perf_monitors->set_column_titles_visible(true); + perf_monitors->connect_compat("item_edited", this, "_performance_select"); + hsp->add_child(perf_monitors); + + perf_draw = memnew(Control); + perf_draw->set_clip_contents(true); + perf_draw->connect_compat("draw", this, "_performance_draw"); + hsp->add_child(perf_draw); + + hsp->set_name(TTR("Monitors")); + hsp->set_split_offset(340 * EDSCALE); + tabs->add_child(hsp); + perf_max.resize(Performance::MONITOR_MAX); + + Map<String, TreeItem *> bases; + TreeItem *root = perf_monitors->create_item(); + perf_monitors->set_hide_root(true); + for (int i = 0; i < Performance::MONITOR_MAX; i++) { + + String n = Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)); + Performance::MonitorType mtype = Performance::get_singleton()->get_monitor_type(Performance::Monitor(i)); + String base = n.get_slice("/", 0); + String name = n.get_slice("/", 1); + if (!bases.has(base)) { + TreeItem *b = perf_monitors->create_item(root); + b->set_text(0, base.capitalize()); + b->set_editable(0, false); + b->set_selectable(0, false); + b->set_expand_right(0, true); + bases[base] = b; + } + + TreeItem *it = perf_monitors->create_item(bases[base]); + it->set_metadata(1, mtype); + it->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + it->set_editable(0, true); + it->set_selectable(0, false); + it->set_selectable(1, false); + it->set_text(0, name.capitalize()); + perf_items.push_back(it); + perf_max.write[i] = 0; + } + + info_message = memnew(Label); + info_message->set_text(TTR("Pick one or more items from the list to display the graph.")); + info_message->set_valign(Label::VALIGN_CENTER); + info_message->set_align(Label::ALIGN_CENTER); + info_message->set_autowrap(true); + info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + info_message->set_anchors_and_margins_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); + perf_draw->add_child(info_message); + } + + { //vmem inspect + VBoxContainer *vmem_vb = memnew(VBoxContainer); + HBoxContainer *vmem_hb = memnew(HBoxContainer); + Label *vmlb = memnew(Label(TTR("List of Video Memory Usage by Resource:") + " ")); + vmlb->set_h_size_flags(SIZE_EXPAND_FILL); + vmem_hb->add_child(vmlb); + vmem_hb->add_child(memnew(Label(TTR("Total:") + " "))); + vmem_total = memnew(LineEdit); + vmem_total->set_editable(false); + vmem_total->set_custom_minimum_size(Size2(100, 0) * EDSCALE); + vmem_hb->add_child(vmem_total); + vmem_refresh = memnew(ToolButton); + vmem_hb->add_child(vmem_refresh); + vmem_vb->add_child(vmem_hb); + vmem_refresh->connect_compat("pressed", this, "_video_mem_request"); + + VBoxContainer *vmmc = memnew(VBoxContainer); + vmem_tree = memnew(Tree); + vmem_tree->set_v_size_flags(SIZE_EXPAND_FILL); + vmem_tree->set_h_size_flags(SIZE_EXPAND_FILL); + vmmc->add_child(vmem_tree); + vmmc->set_v_size_flags(SIZE_EXPAND_FILL); + vmem_vb->add_child(vmmc); + + vmem_vb->set_name(TTR("Video RAM")); + vmem_tree->set_columns(4); + vmem_tree->set_column_titles_visible(true); + vmem_tree->set_column_title(0, TTR("Resource Path")); + vmem_tree->set_column_expand(0, true); + vmem_tree->set_column_expand(1, false); + vmem_tree->set_column_title(1, TTR("Type")); + vmem_tree->set_column_min_width(1, 100 * EDSCALE); + vmem_tree->set_column_expand(2, false); + vmem_tree->set_column_title(2, TTR("Format")); + vmem_tree->set_column_min_width(2, 150 * EDSCALE); + vmem_tree->set_column_expand(3, false); + vmem_tree->set_column_title(3, TTR("Usage")); + vmem_tree->set_column_min_width(3, 80 * EDSCALE); + vmem_tree->set_hide_root(true); + + tabs->add_child(vmem_vb); + } + + { // misc + VBoxContainer *misc = memnew(VBoxContainer); + misc->set_name(TTR("Misc")); + tabs->add_child(misc); + + GridContainer *info_left = memnew(GridContainer); + info_left->set_columns(2); + misc->add_child(info_left); + clicked_ctrl = memnew(LineEdit); + clicked_ctrl->set_h_size_flags(SIZE_EXPAND_FILL); + info_left->add_child(memnew(Label(TTR("Clicked Control:")))); + info_left->add_child(clicked_ctrl); + clicked_ctrl_type = memnew(LineEdit); + info_left->add_child(memnew(Label(TTR("Clicked Control Type:")))); + info_left->add_child(clicked_ctrl_type); + + scene_tree = memnew(SceneDebuggerTree); + live_edit_root = memnew(LineEdit); + live_edit_root->set_h_size_flags(SIZE_EXPAND_FILL); + + { + HBoxContainer *lehb = memnew(HBoxContainer); + Label *l = memnew(Label(TTR("Live Edit Root:"))); + info_left->add_child(l); + lehb->add_child(live_edit_root); + le_set = memnew(Button(TTR("Set From Tree"))); + lehb->add_child(le_set); + le_clear = memnew(Button(TTR("Clear"))); + lehb->add_child(le_clear); + info_left->add_child(lehb); + } + + misc->add_child(memnew(VSeparator)); + + HBoxContainer *buttons = memnew(HBoxContainer); + + export_csv = memnew(Button(TTR("Export measures as CSV"))); + export_csv->connect_compat("pressed", this, "_export_csv"); + buttons->add_child(export_csv); + + misc->add_child(buttons); + } + + msgdialog = memnew(AcceptDialog); + add_child(msgdialog); + + live_debug = true; + camera_override = OVERRIDE_NONE; + last_path_id = false; + error_count = 0; + warning_count = 0; + _update_buttons_state(); +} + +ScriptEditorDebugger::~ScriptEditorDebugger() { + + ppeer->set_stream_peer(Ref<StreamPeer>()); + + inspector->clear_cache(); + memdelete(scene_tree); +} diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h new file mode 100644 index 0000000000..fd034ba983 --- /dev/null +++ b/editor/debugger/script_editor_debugger.h @@ -0,0 +1,271 @@ +/*************************************************************************/ +/* 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(); + void clear_style(); + 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 |