From f1a29a255f2305dabdd691b305faecba90263eac Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Mon, 10 Jun 2024 20:38:02 -0400 Subject: [PATCH] GH-285 Add EditorDebugger Integration --- src/common/signal_utils.cpp | 30 ++ src/common/signal_utils.h | 34 ++ src/editor/graph/graph_edit.cpp | 2 - src/editor/graph/graph_node.cpp | 109 ++++++- src/editor/graph/graph_node.h | 9 + src/editor/main_view.cpp | 89 +++++- src/editor/main_view.h | 22 ++ .../orchestrator_editor_debugger_plugin.cpp | 97 ++++++ .../orchestrator_editor_debugger_plugin.h | 73 +++++ .../plugins/orchestrator_editor_plugin.cpp | 28 ++ .../plugins/orchestrator_editor_plugin.h | 15 +- src/editor/register_editor_types.cpp | 4 + src/editor/script_editor_cache.cpp | 137 ++++++++ src/editor/script_editor_cache.h | 82 +++++ src/editor/script_view.cpp | 31 ++ src/editor/script_view.h | 14 + src/script/instances/node_instance.h | 1 + src/script/instances/script_instance.cpp | 2 +- src/script/instances/script_instance.h | 3 + src/script/language.cpp | 301 +++++++++++++++++- src/script/language.h | 39 +++ src/script/node.cpp | 14 + src/script/node.h | 26 ++ .../nodes/functions/call_member_function.cpp | 1 + src/script/nodes/functions/event.cpp | 2 +- src/script/nodes/utilities/self.cpp | 2 +- src/script/script.cpp | 11 +- src/script/script.h | 3 - .../serialization/binary_loader_instance.cpp | 6 +- .../serialization/binary_saver_instance.cpp | 4 +- src/script/vm/execution_context.h | 8 + src/script/vm/script_state.cpp | 1 + src/script/vm/script_state.h | 1 + src/script/vm/script_vm.cpp | 84 ++++- src/script/vm/script_vm.h | 5 +- 35 files changed, 1238 insertions(+), 52 deletions(-) create mode 100644 src/common/signal_utils.cpp create mode 100644 src/common/signal_utils.h create mode 100644 src/editor/plugins/orchestrator_editor_debugger_plugin.cpp create mode 100644 src/editor/plugins/orchestrator_editor_debugger_plugin.h create mode 100644 src/editor/script_editor_cache.cpp create mode 100644 src/editor/script_editor_cache.h diff --git a/src/common/signal_utils.cpp b/src/common/signal_utils.cpp new file mode 100644 index 00000000..ee0d1179 --- /dev/null +++ b/src/common/signal_utils.cpp @@ -0,0 +1,30 @@ +// This file is part of the Godot Orchestrator project. +// +// Copyright (c) 2023-present Vahera Studios LLC and its contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "common/signal_utils.h" + +namespace SignalUtils +{ + bool disconnect_if(Object* p_object, const StringName& p_signal, const Callable& p_callable) + { + if (p_object->is_connected(p_signal, p_callable)) + { + p_object->disconnect(p_signal, p_callable); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/common/signal_utils.h b/src/common/signal_utils.h new file mode 100644 index 00000000..21250959 --- /dev/null +++ b/src/common/signal_utils.h @@ -0,0 +1,34 @@ +// This file is part of the Godot Orchestrator project. +// +// Copyright (c) 2023-present Vahera Studios LLC and its contributors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef ORCHESTRATOR_SIGNAL_UTILS_H +#define ORCHESTRATOR_SIGNAL_UTILS_H + +#include + +using namespace godot; + +namespace SignalUtils +{ + /// Disconnects the signal from the object if the callable is connected + /// @param p_object the object to disconnect the signal from + /// @param p_signal the signal name to disconnect + /// @param p_callable the callable to disconnect + /// @return true if the signal was disconnected, false otherwise + bool disconnect_if(Object* p_object, const StringName& p_signal, const Callable& p_callable); +} + +#endif // ORCHESTRATOR_SIGNAL_UTILS_H \ No newline at end of file diff --git a/src/editor/graph/graph_edit.cpp b/src/editor/graph/graph_edit.cpp index 734854ca..abeb9962 100644 --- a/src/editor/graph/graph_edit.cpp +++ b/src/editor/graph/graph_edit.cpp @@ -189,8 +189,6 @@ void OrchestratorGraphEdit::_notification(int p_what) _grid_pattern->connect("item_selected", callable_mp(this, &OrchestratorGraphEdit::_on_grid_style_selected)); get_menu_hbox()->add_child(_grid_pattern); get_menu_hbox()->move_child(_grid_pattern, 5); - get_menu_hbox()->get_child(4)->connect("toggled", callable_mp(this, &OrchestratorGraphEdit::_on_show_grid)); - set_grid_pattern(GRID_PATTERN_LINES); #endif diff --git a/src/editor/graph/graph_node.cpp b/src/editor/graph/graph_node.cpp index 868e489b..7fef4d97 100644 --- a/src/editor/graph/graph_node.cpp +++ b/src/editor/graph/graph_node.cpp @@ -19,7 +19,9 @@ #include "common/logger.h" #include "common/scene_utils.h" #include "common/settings.h" +#include "editor/plugins/orchestrator_editor_debugger_plugin.h" #include "editor/plugins/orchestrator_editor_plugin.h" +#include "editor/script_editor_cache.h" #include "graph_edit.h" #include "graph_node_pin.h" #include "script/nodes/editable_pin_node.h" @@ -32,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -49,6 +50,19 @@ OrchestratorGraphNode::OrchestratorGraphNode(OrchestratorGraphEdit* p_graph, con set_v_size_flags(SIZE_EXPAND_FILL); set_meta("__script_node", p_node); + Ref cache = OrchestratorPlugin::get_singleton()->get_script_editor_cache(); + if (cache.is_valid()) + { + #if GODOT_VERSION >= 0x040300 + if (cache->is_node_disabled_breakpoint(_node->get_orchestration()->get_self()->get_path(), _node->get_id())) + _set_breakpoint_state(OScriptNode::BreakpointFlags::BREAKPOINT_DISABLED); + else if (cache->is_node_breakpoint(_node->get_orchestration()->get_self()->get_path(), _node->get_id())) + _set_breakpoint_state(OScriptNode::BreakpointFlags::BREAKPOINT_ENABLED); + else + _set_breakpoint_state(OScriptNode::BreakpointFlags::BREAKPOINT_NONE); + #endif + } + _update_tooltip(); } @@ -276,6 +290,26 @@ void OrchestratorGraphNode::_update_indicators() notification->set_tooltip_text("Node is experimental and behavior may change without notice."); _indicators->add_child(notification); } + + #if GODOT_VERSION >= 0x040300 + if (_node->has_breakpoint()) + { + TextureRect* breakpoint = memnew(TextureRect); + breakpoint->set_custom_minimum_size(Vector2(0, 24)); + breakpoint->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + if (!_node->has_disabled_breakpoint()) + { + breakpoint->set_texture(SceneUtils::get_editor_icon("DebugSkipBreakpointsOff")); + breakpoint->set_tooltip_text("Debugger will break when processing this node"); + } + else + { + breakpoint->set_texture(SceneUtils::get_editor_icon("DebugSkipBreakpointsOn")); + breakpoint->set_tooltip_text("Debugger will skip the breakpoint when processing this node"); + } + _indicators->add_child(breakpoint); + } + #endif } void OrchestratorGraphNode::_update_titlebar() @@ -445,11 +479,18 @@ void OrchestratorGraphNode::_show_context_menu(const Vector2& p_position) if (!call_script_function.is_valid()) _context_menu->set_item_disabled(_context_menu->get_item_index(CM_EXPAND_NODE), true); - - // todo: support breakpoints (See Trello) - // _context_menu->add_separator("Breakpoints"); - // _context_menu->add_item("Toggle Breakpoint", CM_TOGGLE_BREAKPOINT, KEY_F9); - // _context_menu->add_item("Add Breakpoint", CM_ADD_BREAKPOINT); + #if GODOT_VERSION >= 0x040300 + _context_menu->add_separator("Breakpoints"); + _context_menu->add_item("Toggle Breakpoint", CM_TOGGLE_BREAKPOINT, KEY_F9); + if (_node->has_breakpoint()) + { + _context_menu->add_item("Remove breakpoint", CM_REMOVE_BREAKPOINT); + if (_node->has_disabled_breakpoint()) + _context_menu->add_item("Enable breakpoint", CM_ADD_BREAKPOINT); + else + _context_menu->add_item("Disable breakpoint", CM_DISABLE_BREAKPOINT); + } + #endif _context_menu->add_separator("Documentation"); _context_menu->add_icon_item(SceneUtils::get_editor_icon("Help"), "View Documentation", CM_VIEW_DOCUMENTATION); @@ -469,6 +510,29 @@ void OrchestratorGraphNode::_simulate_action_pressed(const String& p_action_name get_graph()->execute_action(p_action_name); } +#if GODOT_VERSION >= 0x040300 +void OrchestratorGraphNode::_set_breakpoint_state(OScriptNode::BreakpointFlags p_flag) +{ + _node->set_breakpoint_flag(p_flag); + + OrchestratorEditorDebuggerPlugin* debugger = OrchestratorEditorDebuggerPlugin::get_singleton(); + if (!debugger) + return; + + const int node_id = _node->get_id(); + const String path = _node->get_orchestration()->get_self()->get_path(); + + debugger->set_breakpoint(path, node_id, p_flag != OScriptNode::BreakpointFlags::BREAKPOINT_NONE); + + Ref cache = OrchestratorPlugin::get_singleton()->get_script_editor_cache(); + if (cache.is_valid()) + { + cache->set_breakpoint(path, node_id, p_flag == OScriptNode::BreakpointFlags::BREAKPOINT_ENABLED); + cache->set_disabled_breakpoint(path, node_id, p_flag != OScriptNode::BreakpointFlags::BREAKPOINT_DISABLED); + } +} +#endif + void OrchestratorGraphNode::_on_changed() { // Notifications can bubble up to the OrchestratorGraphNode from either the OrchestratorGraphNodePin @@ -671,6 +735,39 @@ void OrchestratorGraphNode::_on_context_menu_selection(int p_id) get_graph()->emit_signal("expand_node", _node->get_id()); break; } + #if GODOT_VERSION >= 0x040300 + case CM_TOGGLE_BREAKPOINT: + { + // An OScriptNode registers the breakpoint data from the current open session. + // This data is transient and is lost across restarts, although GDScript persists + // this and we should implement this too. + // + // What we need to integrate with is EditorDebugNode. It is what will be responsible + // for coordinating the breakpoint communication with the EngineDebugger that runs + // in the separate prcoess that runs the game in F5. It uses Local/Remove debuggers. + if (_node->has_breakpoint()) + _set_breakpoint_state(OScriptNode::BreakpointFlags::BREAKPOINT_NONE); + else + _set_breakpoint_state(OScriptNode::BreakpointFlags::BREAKPOINT_ENABLED); + break; + } + case CM_ADD_BREAKPOINT: + case CM_ENABLE_BREAKPOINT: + { + _set_breakpoint_state(OScriptNode::BreakpointFlags::BREAKPOINT_ENABLED); + break; + } + case CM_REMOVE_BREAKPOINT: + { + _set_breakpoint_state(OScriptNode::BreakpointFlags::BREAKPOINT_NONE); + break; + } + case CM_DISABLE_BREAKPOINT: + { + _set_breakpoint_state(OScriptNode::BreakpointFlags::BREAKPOINT_DISABLED); + break; + } + #endif #ifdef _DEBUG case CM_SHOW_DETAILS: { diff --git a/src/editor/graph/graph_node.h b/src/editor/graph/graph_node.h index 4adb66b6..fd0a6806 100644 --- a/src/editor/graph/graph_node.h +++ b/src/editor/graph/graph_node.h @@ -17,6 +17,7 @@ #ifndef ORCHESTRATOR_GRAPH_NODE_H #define ORCHESTRATOR_GRAPH_NODE_H +#include "common/version.h" #include "script/node.h" #include @@ -57,6 +58,9 @@ class OrchestratorGraphNode : public GraphNode CM_RENAME, CM_TOGGLE_BREAKPOINT, CM_ADD_BREAKPOINT, + CM_ENABLE_BREAKPOINT, + CM_REMOVE_BREAKPOINT, + CM_DISABLE_BREAKPOINT, CM_VIEW_DOCUMENTATION, CM_COLLAPSE_FUNCTION, CM_EXPAND_NODE, @@ -207,6 +211,11 @@ class OrchestratorGraphNode : public GraphNode /// @param p_action_name the action to simulate void _simulate_action_pressed(const String& p_action_name); + #if GODOT_VERSION >= 0x040300 + /// Set the breakpoint state + void _set_breakpoint_state(OScriptNode::BreakpointFlags p_flag); + #endif + private: /// Called when the graph node is moved /// @param p_old_pos old position diff --git a/src/editor/main_view.cpp b/src/editor/main_view.cpp index d2f8c2af..d7c32cdd 100644 --- a/src/editor/main_view.cpp +++ b/src/editor/main_view.cpp @@ -16,9 +16,9 @@ // #include "main_view.h" -#include "common/settings.h" #include "common/scene_utils.h" -#include "common/version.h" +#include "common/settings.h" +#include "common/signal_utils.h" #include "editor/about_dialog.h" #include "editor/graph/graph_edit.h" #include "editor/plugins/orchestrator_editor_plugin.h" @@ -289,6 +289,13 @@ void OrchestratorMainView::_notification(int p_what) { if (Node* scene_tabs = editor_node->find_child("*EditorSceneTabs*", true, false)) scene_tabs->connect("tab_changed", callable_mp(this, &OrchestratorMainView::_on_scene_tab_changed)); + + #if GODOT_VERSION >= 0x040300 + OrchestratorEditorDebuggerPlugin* debugger = OrchestratorEditorDebuggerPlugin::get_singleton(); + debugger->connect("goto_script_line", callable_mp(this, &OrchestratorMainView::_on_goto_script_line)); + debugger->connect("breakpoints_cleared_in_tree", callable_mp(this, &OrchestratorMainView::_clear_all_breakpoints)); + debugger->connect("breakpoint_set_in_tree", callable_mp(this, &OrchestratorMainView::_set_breakpoint)); + #endif } FileSystemDock* dock = _plugin->get_editor_interface()->get_file_system_dock(); @@ -299,20 +306,21 @@ void OrchestratorMainView::_notification(int p_what) else if (p_what == NOTIFICATION_EXIT_TREE) { FileSystemDock* dock = _plugin->get_editor_interface()->get_file_system_dock(); - if (dock->is_connected("file_removed", callable_mp(this, &OrchestratorMainView::_on_file_removed))) - dock->disconnect("file_removed", callable_mp(this, &OrchestratorMainView::_on_file_removed)); - if (dock->is_connected("folder_removed", callable_mp(this, &OrchestratorMainView::_on_folder_removed))) - dock->disconnect("folder_removed", callable_mp(this, &OrchestratorMainView::_on_folder_removed)); - if (dock->is_connected("files_moved", callable_mp(this, &OrchestratorMainView::_on_files_moved))) - dock->disconnect("files_moved", callable_mp(this, &OrchestratorMainView::_on_files_moved)); + SignalUtils::disconnect_if(dock, "file_removed", callable_mp(this, &OrchestratorMainView::_on_file_removed)); + SignalUtils::disconnect_if(dock, "folder_removed", callable_mp(this, &OrchestratorMainView::_on_folder_removed)); + SignalUtils::disconnect_if(dock, "files_moved", callable_mp(this, &OrchestratorMainView::_on_files_moved)); + + #if GODOT_VERSION >= 0x040300 + OrchestratorEditorDebuggerPlugin* debugger = OrchestratorEditorDebuggerPlugin::get_singleton(); + SignalUtils::disconnect_if(debugger, "goto_script_line", callable_mp(this, &OrchestratorMainView::_on_goto_script_line)); + SignalUtils::disconnect_if(debugger, "breakpoints_cleared_in_tree", callable_mp(this, &OrchestratorMainView::_clear_all_breakpoints)); + SignalUtils::disconnect_if(debugger, "breakpoint_set_in_tree", callable_mp(this, &OrchestratorMainView::_set_breakpoint)); + #endif if (Node* editor_node = get_tree()->get_root()->get_child(0)) { if (Node* scene_tabs = editor_node->find_child("*EditorSceneTabs*", true, false)) - { - if (scene_tabs->is_connected("tab_changed", callable_mp(this, &OrchestratorMainView::_on_scene_tab_changed))) - scene_tabs->disconnect("tab_changed", callable_mp(this, &OrchestratorMainView::_on_scene_tab_changed)); - } + SignalUtils::disconnect_if(scene_tabs, "tab_changed", callable_mp(this, &OrchestratorMainView::_on_scene_tab_changed)); } } } @@ -368,6 +376,16 @@ void OrchestratorMainView::edit_script(const Ref& p_script) call_deferred("emit_signal", "toggle_component_panel", _right_panel_visible); } +PackedStringArray OrchestratorMainView::get_breakpoints() const +{ + PackedStringArray breakpoints; + #if GODOT_VERSION >= 0x040300 + for (const ScriptFile& file : _script_files) + breakpoints.append_array(file.editor->get_breakpoints()); + #endif + return breakpoints; +} + void OrchestratorMainView::apply_changes() { for (const ScriptFile& file : _script_files) @@ -969,3 +987,50 @@ void OrchestratorMainView::_on_recent_history_selected(int p_index) _update_files_list(); } } + +#if GODOT_VERSION >= 0x040300 +void OrchestratorMainView::_on_goto_script_line(const Ref