diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 9da5ac01..aef2dbf6 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -201,6 +201,7 @@ void OrchestratorSettings::_register_settings() _settings.emplace_back(COLOR_NO_ALPHA_SETTING("ui/node_colors/scene", Color(0.208, 0.141, 0.282))); _settings.emplace_back(COLOR_NO_ALPHA_SETTING("ui/node_colors/signals", Color(0.353, 0.0, 0.0))); _settings.emplace_back(COLOR_NO_ALPHA_SETTING("ui/node_colors/variable", Color(0.259, 0.177, 0.249))); + _settings.emplace_back(COLOR_NO_ALPHA_SETTING("ui/node_colors/local_variable", Color(0.174, 0.114, 0.167))); _settings.emplace_back(COLOR_NO_ALPHA_SETTING("ui/node_colors/type_cast", Color(0.009, 0.221, 0.203))); _settings.emplace_back(COLOR_NO_ALPHA_SETTING("ui/node_colors/comment", Color(0.4, 0.4, 0.4))); diff --git a/src/editor/component_panels/local_variables_panel.cpp b/src/editor/component_panels/local_variables_panel.cpp new file mode 100644 index 00000000..fe01ce15 --- /dev/null +++ b/src/editor/component_panels/local_variables_panel.cpp @@ -0,0 +1,299 @@ +// This file is part of the Godot Orchestrator project. +// +// Copyright (c) 2023-present Crater Crash 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 "editor/component_panels/local_variables_panel.h" + +#include "common/callable_lambda.h" +#include "common/macros.h" +#include "common/scene_utils.h" +#include "editor/plugins/inspector_plugins.h" +#include "editor/plugins/orchestrator_editor_plugin.h" + +#include +#include +#include + +void OrchestratorScriptLocalVariablesComponentPanel::_create_variable_item(TreeItem* p_parent, const Ref& p_variable) +{ + TreeItem* category = nullptr; + for (TreeItem* child = p_parent->get_first_child(); child; child = child->get_next()) + { + if (_get_tree_item_name(child).match(p_variable->get_category())) + { + category = child; + break; + } + } + + if (p_variable->is_grouped_by_category()) + { + if (!category) + { + category = _create_item(p_parent, p_variable->get_category(), p_variable->get_category(), ""); + category->set_selectable(0, false); + } + } + else + category = p_parent; + + TreeItem* item = _create_item(category, p_variable->get_variable_name(), p_variable->get_variable_name(), "MemberProperty"); + + item->add_button(0, SceneUtils::get_class_icon(p_variable->get_variable_type_name()), 0); + item->set_button_tooltip_text(0, 0, "Change variable type"); + + if (!p_variable->get_description().is_empty()) + { + const String tooltip = p_variable->get_variable_name() + "\n\n" + p_variable->get_description(); + item->set_tooltip_text(0, SceneUtils::create_wrapped_tooltip_text(tooltip)); + } +} + +PackedStringArray OrchestratorScriptLocalVariablesComponentPanel::_get_existing_names() const +{ + PackedStringArray names; + if (_function.is_valid()) + { + for (const Ref& variable : _function->get_local_variables()) + names.push_back(variable->get_variable_name()); + } + return names; +} + +String OrchestratorScriptLocalVariablesComponentPanel::_get_tooltip_text() const +{ + return "A local variable represents some temporary data that will exist only within the function.\n\n" + "Drag a local variable from the component view onto the function graph area to select whether " + "to create a get/set node or use the action menu to find the get/set option for the variable.\n\n" + "Selecting a local variable in the component view displays the variable details in the inspector."; +} + +String OrchestratorScriptLocalVariablesComponentPanel::_get_remove_confirm_text(TreeItem* p_item) const +{ + return "Removing a local variable will remove all nodes that get or set the variable."; +} + +bool OrchestratorScriptLocalVariablesComponentPanel::_populate_context_menu(TreeItem* p_item) +{ + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_VARIABLE); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_VARIABLE); + return true; +} + +void OrchestratorScriptLocalVariablesComponentPanel::_handle_context_menu(int p_id) +{ + switch (p_id) + { + case CM_RENAME_VARIABLE: + _edit_selected_tree_item(); + break; + case CM_REMOVE_VARIABLE: + _confirm_removal(_tree->get_selected()); + break; + } +} + +bool OrchestratorScriptLocalVariablesComponentPanel::_handle_add_new_item(const String& p_name) +{ + if (!_function.is_valid()) + return false; + + // Add the new variable and update the components display + return _function->create_local_variable(p_name).is_valid(); +} + +void OrchestratorScriptLocalVariablesComponentPanel::_handle_item_selected() +{ + if (!_function.is_valid()) + return; + + TreeItem* item = _tree->get_selected(); + + Ref variable = _function->get_local_variable(_get_tree_item_name(item)); + EditorInterface::get_singleton()->edit_resource(variable); +} + +void OrchestratorScriptLocalVariablesComponentPanel::_handle_item_activated(TreeItem* p_item) +{ + if (!_function.is_valid()) + return; + + Ref variable = _function->get_local_variable(_get_tree_item_name(p_item)); + EditorInterface::get_singleton()->edit_resource(variable); +} + +bool OrchestratorScriptLocalVariablesComponentPanel::_handle_item_renamed(const String& p_old_name, const String& p_new_name) +{ + if (_get_existing_names().has(p_new_name)) + { + _show_notification("A local variable with the name '" + p_new_name + "' already exists."); + return false; + } + + if (!p_new_name.is_valid_identifier()) + { + _show_invalid_name("local variable", false); + return false; + } + + if (!_function.is_valid()) + return false; + + return _function->rename_local_variable(p_old_name, p_new_name); +} + +void OrchestratorScriptLocalVariablesComponentPanel::_handle_remove(TreeItem* p_item) +{ + if (!_function.is_valid()) + return; + + const String variable_name = _get_tree_item_name(p_item); + _function->remove_local_variable(variable_name); +} + +void OrchestratorScriptLocalVariablesComponentPanel::_handle_button_clicked(TreeItem* p_item, int p_column, int p_id, int p_mouse_button) +{ + if (!_function.is_valid()) + return; + + const String variable_name = _get_tree_item_name(p_item); + const Ref variable = _function->get_local_variable(variable_name); + if (!variable.is_valid()) + return; + + _tree->set_selected(p_item, 0); + + if (p_column == 0 && p_id == 0) + { + // Type clicked + Ref plugin = OrchestratorPlugin::get_singleton() + ->get_editor_inspector_plugin(); + + if (plugin.is_valid()) + plugin->edit_classification(variable.ptr()); + } +} + +Dictionary OrchestratorScriptLocalVariablesComponentPanel::_handle_drag_data(const Vector2& p_position) +{ + Dictionary data; + + TreeItem* selected = _tree->get_selected(); + if (selected) + { + data["type"] = "local_variable"; + data["local_variables"] = Array::make(_get_tree_item_name(selected)); + } + return data; +} + +void OrchestratorScriptLocalVariablesComponentPanel::update() +{ + _clear_tree(); + + Callable callback = callable_mp(this, &OrchestratorScriptLocalVariablesComponentPanel::_update_variables); + + // Make sure all variables are disconnected + if (_function.is_valid()) + { + for (const Ref& variable : _function->get_local_variables()) + ODISCONNECT(variable, "changed", callback); + } + + if (_function.is_valid()) + { + Vector> variables = _function->get_local_variables(); + if (!variables.is_empty()) + { + PackedStringArray sorted_categorized_names; + PackedStringArray sorted_uncategorized_names; + + HashMap> categorized; + HashMap> uncategorized; + HashMap categorized_names; + + for (const Ref& variable : variables) + { + const String variable_name = variable->get_variable_name(); + + if (variable->is_grouped_by_category()) + { + const String category = variable->get_category().to_lower(); + const String sort_name = vformat("%s/%s", category, variable_name.to_lower()); + + categorized[variable_name] = variable; + categorized_names[sort_name] = variable_name; + + sorted_categorized_names.push_back(sort_name); + } + else + { + uncategorized[variable_name] = variable; + sorted_uncategorized_names.push_back(variable_name); + } + } + + // Sort names + sorted_categorized_names.sort(); + sorted_uncategorized_names.sort(); + + TreeItem* root = _tree->get_root(); + for (const String& name : sorted_categorized_names) + { + const String& variable_name = categorized_names[name]; + + const Ref& variable = categorized[variable_name]; + if (variable.is_valid()) + OCONNECT(variable, "changed", callback); + + _create_variable_item(root, variable); + } + + for (const String& name : sorted_uncategorized_names) + { + const Ref& variable = uncategorized[name]; + if (variable.is_valid()) + OCONNECT(variable, "changed", callback); + + _create_variable_item(root, variable); + } + } + } + + if (_tree->get_root()->get_child_count() == 0) + { + TreeItem* item = _tree->get_root()->create_child(); + item->set_text(0, "No variables defined"); + item->set_selectable(0, false); + return; + } + + OrchestratorScriptComponentPanel::update(); +} + +void OrchestratorScriptLocalVariablesComponentPanel::set_function(const Ref& p_function) +{ + _function = p_function; + update(); +} + +void OrchestratorScriptLocalVariablesComponentPanel::_bind_methods() +{ +} + +OrchestratorScriptLocalVariablesComponentPanel::OrchestratorScriptLocalVariablesComponentPanel(Orchestration* p_orchestration) + : OrchestratorScriptComponentPanel("Local Variables", p_orchestration) +{ +} diff --git a/src/editor/component_panels/local_variables_panel.h b/src/editor/component_panels/local_variables_panel.h new file mode 100644 index 00000000..6f9162d9 --- /dev/null +++ b/src/editor/component_panels/local_variables_panel.h @@ -0,0 +1,74 @@ +// This file is part of the Godot Orchestrator project. +// +// Copyright (c) 2023-present Crater Crash 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_SCRIPT_LOCAL_VARIABLES_COMPONENT_PANEL_H +#define ORCHESTRATOR_SCRIPT_LOCAL_VARIABLES_COMPONENT_PANEL_H + +#include "editor/component_panels/component_panel.h" + +class OrchestratorScriptLocalVariablesComponentPanel : public OrchestratorScriptComponentPanel +{ + GDCLASS(OrchestratorScriptLocalVariablesComponentPanel, OrchestratorScriptComponentPanel); + static void _bind_methods(); + + enum ContextMenuIds + { + CM_RENAME_VARIABLE, + CM_REMOVE_VARIABLE + }; + + void _update_variables() { update(); } + +protected: + Ref _function; //! Source function + + //~ Begin OrchestratorScriptViewSection Interface + String _get_unique_name_prefix() const override { return "NewLocalVar"; } + PackedStringArray _get_existing_names() const override; + String _get_tooltip_text() const override; + String _get_remove_confirm_text(TreeItem* p_item) const override; + String _get_item_name() const override { return "LocalVariable"; } + bool _populate_context_menu(TreeItem* p_item) override; + void _handle_context_menu(int p_id) override; + bool _handle_add_new_item(const String& p_name) override; + void _handle_item_selected() override; + void _handle_item_activated(TreeItem* p_item) override; + bool _handle_item_renamed(const String& p_old_name, const String& p_new_name) override; + void _handle_remove(TreeItem* p_item) override; + void _handle_button_clicked(TreeItem* p_item, int p_column, int p_id, int p_mouse_button) override; + Dictionary _handle_drag_data(const Vector2& p_position) override; + //~ End OrchestratorScriptViewSection Interface + + void _create_variable_item(TreeItem* p_parent, const Ref& p_variable); + + /// Default constructor + OrchestratorScriptLocalVariablesComponentPanel() = default; + +public: + //~ Begin OrchestratorScriptViewSection Interface + void update() override; + //~ End OrchestratorScriptViewSection Interface + + /// Set the function source for the local variables + /// @param p_function the source function + void set_function(const Ref& p_function); + + /// Constructs a local variable component panel + /// @param p_orchestration the orchestration + explicit OrchestratorScriptLocalVariablesComponentPanel(Orchestration* p_orchestration); +}; + +#endif // ORCHESTRATOR_SCRIPT_LOCAL_VARIABLES_COMPONENT_PANEL_H \ No newline at end of file diff --git a/src/editor/editor_viewport.cpp b/src/editor/editor_viewport.cpp index a295a890..04e3143a 100644 --- a/src/editor/editor_viewport.cpp +++ b/src/editor/editor_viewport.cpp @@ -47,6 +47,10 @@ void OrchestratorEditorViewport::_graph_opened(OrchestratorGraphEdit* p_graph) p_graph->connect("validation_requested", callable_mp(this, &OrchestratorEditorViewport::build).bind(true)); } +void OrchestratorEditorViewport::_graph_selected(OrchestratorGraphEdit* p_graph) +{ +} + void OrchestratorEditorViewport::_resolve_node_set_connections(const Vector>& p_nodes, NodeSetConnections& r_connections) { // Create a map of the nodes @@ -120,6 +124,15 @@ void OrchestratorEditorViewport::_close_tab_requested(int p_tab_index) _close_tab(p_tab_index); } +void OrchestratorEditorViewport::_tab_changed(int p_tab_index) +{ + if (p_tab_index >= 0 && p_tab_index < _tabs->get_tab_count()) + { + if (OrchestratorGraphEdit* graph = Object::cast_to(_tabs->get_child(p_tab_index))) + _graph_selected(graph); + } +} + void OrchestratorEditorViewport::_graph_nodes_changed() { _update_components(); @@ -386,6 +399,7 @@ void OrchestratorEditorViewport::_notification(int p_what) _tabs = memnew(TabContainer); _tabs->get_tab_bar()->set_tab_close_display_policy(TabBar::CLOSE_BUTTON_SHOW_ACTIVE_ONLY); _tabs->get_tab_bar()->connect("tab_close_pressed", callable_mp(this, &OrchestratorEditorViewport::_close_tab_requested)); + _tabs->get_tab_bar()->connect("tab_changed", callable_mp(this, &OrchestratorEditorViewport::_tab_changed)); margin->add_child(_tabs); _scroll_container = memnew(ScrollContainer); diff --git a/src/editor/editor_viewport.h b/src/editor/editor_viewport.h index be097dce..048b7d8b 100644 --- a/src/editor/editor_viewport.h +++ b/src/editor/editor_viewport.h @@ -88,6 +88,10 @@ class OrchestratorEditorViewport : public HSplitContainer /// @param p_graph the graph edit that was opened virtual void _graph_opened(OrchestratorGraphEdit* p_graph); + /// Allows for performing actions when a graph that is opened is selected + /// @param p_graph the graph edit that was selected + virtual void _graph_selected(OrchestratorGraphEdit* p_graph); + /// Allows a viewport to control whether a graph can be closed /// @param p_graph the graph to inspect /// @return true if the graph can be closed, false otherwise @@ -106,6 +110,10 @@ class OrchestratorEditorViewport : public HSplitContainer /// @param p_tab_index the tab requesting closure void _close_tab_requested(int p_tab_index); + /// Called when a tab is changed + /// @param p_tab_index the new tab index + void _tab_changed(int p_tab_index); + /// Called when the graph's node set has changed void _graph_nodes_changed(); diff --git a/src/editor/graph/graph_edit.cpp b/src/editor/graph/graph_edit.cpp index a4140e95..da16887b 100644 --- a/src/editor/graph/graph_edit.cpp +++ b/src/editor/graph/graph_edit.cpp @@ -731,18 +731,31 @@ bool OrchestratorGraphEdit::_can_drop_data(const Vector2& p_position, const Vari allowed_types.push_back("files"); allowed_types.push_back("obj_property"); allowed_types.push_back("variable"); + allowed_types.push_back("local_variable"); allowed_types.push_back("signal"); allowed_types.push_back("function"); if (allowed_types.has(type)) { + Ref variable; + if (type == "variable") { const String variable_name = String(Array(data["variables"])[0]); - const Ref variable = _script_graph->get_orchestration()->get_variable(variable_name); - if (variable.is_valid() && !variable->is_constant()) - _show_drag_hint("Use Ctrl to drop a Setter, Shift to drop a Getter"); + variable = _script_graph->get_orchestration()->get_variable(variable_name); + } + else if (type == "local_variable") + { + if (!_script_graph->is_function()) + return false; + + const String variable_name = String(Array(data["local_variables"])[0]); + variable = _script_graph->get_local_variable(variable_name); } + + if (variable.is_valid() && !variable->is_constant()) + _show_drag_hint("Use Ctrl to drop a Setter, Shift to drop a Getter"); + return true; } @@ -781,17 +794,9 @@ void OrchestratorGraphEdit::_drop_data(const Vector2& p_position, const Variant& else if (type == "files") { const Array files = data["files"]; + const String file_name = String(files[0]); - // Create context-menu to specify variable get or set choice - _context_menu->clear(); - _context_menu->add_separator("File " + String(files[0])); - _context_menu->add_item("Get Path", CM_FILE_GET_PATH); - _context_menu->add_item("Preload ", CM_FILE_PRELOAD); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_FILE_GET_PATH), String(files[0])); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_FILE_PRELOAD), String(files[0])); - _context_menu->reset_size(); - _context_menu->set_position(get_screen_position() + p_position); - _context_menu->popup(); + _show_file_drop_menu(p_position, file_name); } else if (type == "obj_property") { @@ -820,39 +825,15 @@ void OrchestratorGraphEdit::_drop_data(const Vector2& p_position, const Variant& { // Get the property's current value and seed that as the pin's value const Variant value = object->get(property_name); - - // Create context-menu handlers - Ref get_handler(memnew(OrchestratorGraphNodeSpawnerPropertyGet(pi, path))); - Ref set_handler(memnew(OrchestratorGraphNodeSpawnerPropertySet(pi, path, value))); - - // Create context-menu to specify variable get or set choice - _context_menu->clear(); - _context_menu->add_separator("Property " + pi.name); - _context_menu->add_item("Get " + pi.name, CM_PROPERTY_GET); - _context_menu->add_item("Set " + pi.name, CM_PROPERTY_SET); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_PROPERTY_GET), get_handler); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_PROPERTY_SET), set_handler); - _context_menu->reset_size(); - _context_menu->set_position(get_screen_position() + p_position); - _context_menu->popup(); + _show_property_drop_menu(p_position, pi, path, value); } } else if (type == "function") { if (data.has("functions")) { - MethodInfo method = DictionaryUtils::to_method(data["functions"]); - - // Create context-menu to specify variable get or set choice - _context_menu->clear(); - _context_menu->add_separator("Function " + method.name); - _context_menu->add_item("Add Call Function", CM_FUNCTION_CALL); - _context_menu->add_item("Add Callable", CM_FUNCTION_CALLABLE); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_FUNCTION_CALL), data["functions"]); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_FUNCTION_CALLABLE), data["functions"]); - _context_menu->reset_size(); - _context_menu->set_position(get_screen_position() + p_position); - _context_menu->popup(); + const MethodInfo method = DictionaryUtils::to_method(data["functions"]); + _show_function_drop_menu(p_position, method, data["functions"]); } } else if (type == "variable") @@ -866,44 +847,25 @@ void OrchestratorGraphEdit::_drop_data(const Vector2& p_position, const Variant& return; const bool is_constant = variable->is_constant(); - const bool is_validated = variable->get_variable_type() == Variant::OBJECT; + const bool is_validated = variable->supports_validated_getter(); - if (Input::get_singleton()->is_key_pressed(KEY_CTRL)) - { - if (!is_constant) - { - Ref setter(memnew(OrchestratorGraphNodeSpawnerVariableSet(variable_name))); - setter->execute(this, _saved_mouse_position); - } - } - else if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) - { - Ref getter(memnew(OrchestratorGraphNodeSpawnerVariableGet(variable_name))); - getter->execute(this, _saved_mouse_position); - } - else - { - _context_menu->clear(); - _context_menu->add_separator("Variable " + variable_name); - _context_menu->add_item("Get " + variable_name, CM_VARIABLE_GET); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_VARIABLE_GET), memnew(OrchestratorGraphNodeSpawnerVariableGet(variable_name))); + if (!_has_variable_drop_shortcut(variable_name, false)) + _show_variable_drop_menu(p_position, variable_name, is_validated, is_constant, false); + } + else if (type == "local_variable") + { + _hide_drag_hint(); - if (is_validated) - { - _context_menu->add_item("Get " + variable_name + " with validation", CM_VARIABLE_GET_VALIDATED); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_VARIABLE_GET_VALIDATED), memnew(OrchestratorGraphNodeSpawnerVariableGet(variable_name, true))); - } + const String variable_name = String(Array(data["local_variables"])[0]); - if (!is_constant) - { - _context_menu->add_item("Set " + variable_name, CM_VARIABLE_SET); - _context_menu->set_item_metadata(_context_menu->get_item_index(CM_VARIABLE_SET), memnew(OrchestratorGraphNodeSpawnerVariableSet(variable_name))); - } + const Ref variable = _script_graph->get_local_variable(variable_name); + if (!variable.is_valid()) + return; - _context_menu->reset_size(); - _context_menu->set_position(get_screen_position() + p_position); - _context_menu->popup(); - } + const bool is_validated = variable->supports_validated_getter(); + + if (!_has_variable_drop_shortcut(variable_name, true)) + _show_variable_drop_menu(p_position, variable_name, is_validated, false, true); } else if (type == "signal") { @@ -916,6 +878,102 @@ void OrchestratorGraphEdit::_drop_data(const Vector2& p_position, const Variant& } } +void OrchestratorGraphEdit::_show_file_drop_menu(const Vector2& p_position, const String& p_file_name) +{ + // Create context-menu to specify variable get or set choice + _context_menu->clear(); + _context_menu->add_separator("File " + p_file_name); + _context_menu->add_item("Get Path", CM_FILE_GET_PATH); + _context_menu->add_item("Preload ", CM_FILE_PRELOAD); + _context_menu->set_item_metadata(_context_menu->get_item_index(CM_FILE_GET_PATH), p_file_name); + _context_menu->set_item_metadata(_context_menu->get_item_index(CM_FILE_PRELOAD), p_file_name); + _context_menu->reset_size(); + _context_menu->set_position(get_screen_position() + p_position); + _context_menu->popup(); +} + +void OrchestratorGraphEdit::_show_property_drop_menu(const Vector2& p_position, const PropertyInfo& p_property, const NodePath& p_node_path, const Variant& p_value) +{ + // Create context-menu handlers + Ref get_handler(memnew(OrchestratorGraphNodeSpawnerPropertyGet(p_property, p_node_path))); + Ref set_handler(memnew(OrchestratorGraphNodeSpawnerPropertySet(p_property, p_node_path, p_value))); + + // Create context-menu to specify variable get or set choice + _context_menu->clear(); + _context_menu->add_separator("Property " + p_property.name); + _context_menu->add_item("Get " + p_property.name, CM_PROPERTY_GET); + _context_menu->add_item("Set " + p_property.name, CM_PROPERTY_SET); + _context_menu->set_item_metadata(_context_menu->get_item_index(CM_PROPERTY_GET), get_handler); + _context_menu->set_item_metadata(_context_menu->get_item_index(CM_PROPERTY_SET), set_handler); + _context_menu->reset_size(); + _context_menu->set_position(get_screen_position() + p_position); + _context_menu->popup(); +} + +void OrchestratorGraphEdit::_show_function_drop_menu(const Vector2& p_position, const MethodInfo& p_method, const Variant& p_handler) +{ + // Create context-menu to specify variable get or set choice + _context_menu->clear(); + _context_menu->add_separator("Function " + p_method.name); + _context_menu->add_item("Add Call Function", CM_FUNCTION_CALL); + _context_menu->add_item("Add Callable", CM_FUNCTION_CALLABLE); + _context_menu->set_item_metadata(_context_menu->get_item_index(CM_FUNCTION_CALL) ,p_handler); + _context_menu->set_item_metadata(_context_menu->get_item_index(CM_FUNCTION_CALLABLE), p_handler); + _context_menu->reset_size(); + _context_menu->set_position(get_screen_position() + p_position); + _context_menu->popup(); +} + +void OrchestratorGraphEdit::_show_variable_drop_menu(const Vector2& p_position, const String& p_variable_name, bool p_validated, bool p_constant, bool p_local) +{ + const int32_t get_id = p_local ? CM_LOCAL_VARIABLE_GET : CM_VARIABLE_GET; + const int32_t set_id = p_local ? CM_LOCAL_VARIABLE_SET : CM_VARIABLE_SET; + const int32_t validated_get_id = p_local ? CM_LOCAL_VARIABLE_GET_VALIDATED : CM_VARIABLE_GET_VALIDATED; + + _context_menu->clear(); + _context_menu->add_separator(vformat("%sVariable %s", p_local ? "Local " : "", p_variable_name)); + _context_menu->add_item("Get " + p_variable_name, get_id); + const Variant get_handler = memnew(OrchestratorGraphNodeSpawnerVariableGet(p_variable_name, false, p_local)); + _context_menu->set_item_metadata(_context_menu->get_item_index(get_id), get_handler); + + if (p_validated) + { + _context_menu->add_item("Get " + p_variable_name + " with validation", validated_get_id); + const Variant validated_get_handler = memnew(OrchestratorGraphNodeSpawnerVariableGet(p_variable_name, true, p_local)); + _context_menu->set_item_metadata(_context_menu->get_item_index(validated_get_id), validated_get_handler); + } + + if (!p_constant) + { + _context_menu->add_item("Set " + p_variable_name, set_id); + const Variant set_handler = memnew(OrchestratorGraphNodeSpawnerVariableSet(p_variable_name, p_local)); + _context_menu->set_item_metadata(_context_menu->get_item_index(set_id), set_handler); + } + + _context_menu->reset_size(); + _context_menu->set_position(get_screen_position() + p_position); + _context_menu->popup(); +} + +bool OrchestratorGraphEdit::_has_variable_drop_shortcut(const String& p_variable_name, bool p_local) +{ + if (Input::get_singleton()->is_key_pressed(KEY_CTRL)) + { + Ref setter(memnew(OrchestratorGraphNodeSpawnerVariableSet(p_variable_name, p_local))); + setter->execute(this, _saved_mouse_position); + return true; + } + + if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) + { + Ref getter(memnew(OrchestratorGraphNodeSpawnerVariableGet(p_variable_name, false, p_local))); + getter->execute(this, _saved_mouse_position); + return true; + } + + return false; +} + void OrchestratorGraphEdit::_confirm_yes_no(const String& p_text, const String& p_title, Callable p_confirm_callback) { ConfirmationDialog* dialog = memnew(ConfirmationDialog); @@ -1915,6 +1973,9 @@ void OrchestratorGraphEdit::_on_context_menu_selection(int p_id) case CM_VARIABLE_SET: case CM_PROPERTY_GET: case CM_PROPERTY_SET: + case CM_LOCAL_VARIABLE_GET: + case CM_LOCAL_VARIABLE_GET_VALIDATED: + case CM_LOCAL_VARIABLE_SET: { int index = _context_menu->get_item_index(p_id); Ref handler = _context_menu->get_item_metadata(index); diff --git a/src/editor/graph/graph_edit.h b/src/editor/graph/graph_edit.h index 71ff34a6..fbcb8d46 100644 --- a/src/editor/graph/graph_edit.h +++ b/src/editor/graph/graph_edit.h @@ -67,8 +67,12 @@ class OrchestratorGraphEdit : public GraphEdit CM_VARIABLE_GET, CM_VARIABLE_GET_VALIDATED, CM_VARIABLE_SET, + CM_LOCAL_VARIABLE_GET, + CM_LOCAL_VARIABLE_GET_VALIDATED, + CM_LOCAL_VARIABLE_SET, CM_PROPERTY_GET, CM_PROPERTY_SET, + CM_SPAWN_HANDLER, CM_FILE_GET_PATH, CM_FILE_PRELOAD, CM_FUNCTION_CALL, @@ -270,6 +274,38 @@ class OrchestratorGraphEdit : public GraphEdit void sync(); private: + /// Show the file drop menu + /// @param p_position the position within the parent to show the context window + /// @param p_file_name the file name + void _show_file_drop_menu(const Vector2& p_position, const String& p_file_name); + + /// Show the property drop context menu + /// @param p_position the position within the parent to show the context window + /// @param p_property the property details + /// @param p_node_path the node path + /// @param p_value the value + void _show_property_drop_menu(const Vector2& p_position, const PropertyInfo& p_property, const NodePath& p_node_path, const Variant& p_value); + + /// Show the function drop context menu + /// @param p_position the position within the parent to show the context window + /// @param p_method the method details + /// @param p_handler the context menu spawner handler + void _show_function_drop_menu(const Vector2& p_position, const MethodInfo& p_method, const Variant& p_handler); + + /// Show variable drop context menu + /// @param p_position the position within the parent to show the context window + /// @param p_variable_name the variable name + /// @param p_validated whether the variable is validated + /// @param p_constant whether the variable is read-only, aka constant + /// @param p_local whether the variable is a local variable + void _show_variable_drop_menu(const Vector2& p_position, const String& p_variable_name, bool p_validated, bool p_constant, bool p_local); + + /// Check whether the variable drop shortcut is executed + /// @param p_variable_name the variable name + /// @param p_local whether the variable is a local variable + /// @return true if the shortcut was fired and the node auto-spawned, false otherwise + bool _has_variable_drop_shortcut(const String& p_variable_name, bool p_local); + /// Displays a yes/no confirmation dialog to the user. /// @param p_text the text to be shown. /// @param p_title the confirmation window title text diff --git a/src/editor/graph/graph_node.cpp b/src/editor/graph/graph_node.cpp index 9510d58f..b5ddffe4 100644 --- a/src/editor/graph/graph_node.cpp +++ b/src/editor/graph/graph_node.cpp @@ -276,6 +276,16 @@ void OrchestratorGraphNode::_update_indicators() _indicators->add_child(notification); } + if (_node->get_flags().has_flag(OScriptNode::ScriptNodeFlags::DEPRECATED)) + { + TextureRect* notification = memnew(TextureRect); + notification->set_texture(SceneUtils::get_editor_icon("Skeleton3D")); + notification->set_custom_minimum_size(Vector2(0, 24)); + notification->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + notification->set_tooltip_text("Node is deprecated and will be removed in the future."); + _indicators->add_child(notification); + } + #if GODOT_VERSION >= 0x040300 if (_node->has_breakpoint()) { @@ -512,14 +522,31 @@ void OrchestratorGraphNode::_show_context_menu(const Vector2& p_position) if (multi_selections) _context_menu->set_item_tooltip(_context_menu->get_item_index(CM_VIEW_DOCUMENTATION), "Select a single node to view documentation."); - Ref variable_get = _node; - if (variable_get.is_valid() && variable_get->can_be_validated()) + const Ref variable_get = _node; + const Ref local_variable_get = _node; + + if (variable_get.is_valid() || local_variable_get.is_valid()) { - _context_menu->add_separator("Variable Get"); - if (variable_get->is_validated()) - _context_menu->add_item("Make Pure", CM_MAKE_PURE_GETTER); - else - _context_menu->add_item("Make Validated", CM_MAKE_VALIDATED_GETTER); + bool can_be_validated = false; + if (variable_get.is_valid() && variable_get->can_be_validated()) + can_be_validated = true; + if (local_variable_get.is_valid() && local_variable_get->can_be_validated()) + can_be_validated = true; + + if (can_be_validated) + { + bool is_validated = false; + if (variable_get.is_valid() && variable_get->is_validated()) + is_validated = true; + if (local_variable_get.is_valid() && local_variable_get->is_validated()) + is_validated = true; + + const String item_text = is_validated ? "Make Pure" : "Make Validated"; + const int item_id = is_validated ? CM_MAKE_PURE_GETTER : CM_MAKE_VALIDATED_GETTER; + + _context_menu->add_separator(vformat("%sVariable Get", local_variable_get.is_valid() ? "Local " : "")); + _context_menu->add_item(item_text, item_id); + } } _context_menu->set_position(get_screen_position() + (p_position * (real_t) get_graph()->get_zoom())); @@ -779,13 +806,25 @@ void OrchestratorGraphNode::_handle_context_menu(int p_id) case CM_MAKE_PURE_GETTER: { Ref getter = _node; - getter->set_validated(false); + if (getter.is_valid()) + getter->set_validated(false); + + Ref local_getter = _node; + if (local_getter.is_valid()) + local_getter->set_validated(false); + break; } case CM_MAKE_VALIDATED_GETTER: { Ref getter = _node; - getter->set_validated(true); + if (getter.is_valid()) + getter->set_validated(true); + + Ref local_getter = _node; + if (local_getter.is_valid()) + local_getter->set_validated(true); + break; } #if GODOT_VERSION >= 0x040300 diff --git a/src/editor/graph/graph_node_spawner.cpp b/src/editor/graph/graph_node_spawner.cpp index 736ccd88..0b23ce86 100644 --- a/src/editor/graph/graph_node_spawner.cpp +++ b/src/editor/graph/graph_node_spawner.cpp @@ -399,9 +399,16 @@ void OrchestratorGraphNodeSpawnerVariableGet::execute(OrchestratorGraphEdit* p_g Dictionary data; data["validation"] = _validation; + + if (_local && p_graph->get_owning_graph()->is_function()) + data["function_guid"] = p_graph->get_owning_graph()->get_function_guid(); + context.user_data = data; - p_graph->spawn_node(context, p_position); + if (_local) + p_graph->spawn_node(context, p_position); + else + p_graph->spawn_node(context, p_position); } bool OrchestratorGraphNodeSpawnerVariableGet::is_filtered(const OrchestratorGraphActionFilter& p_filter, @@ -409,11 +416,23 @@ bool OrchestratorGraphNodeSpawnerVariableGet::is_filtered(const OrchestratorGrap { if (p_filter.context_sensitive && p_filter.target_type != Variant::NIL && !p_filter.context.pins.is_empty()) { - if (Orchestration* orchestration = p_filter.get_orchestration()) + if (_local) { - const Ref variable = orchestration->get_variable(_variable_name); - if (variable.is_valid() && variable->get_variable_type() != p_filter.target_type) - return true; + if (OrchestratorGraphEdit* graph = p_filter.context.graph) + { + const Ref variable = graph->get_owning_graph()->get_local_variable(_variable_name); + if (variable.is_valid() && variable->get_variable_type() != p_filter.target_type) + return true; + } + } + else + { + if (Orchestration* orchestration = p_filter.get_orchestration()) + { + const Ref variable = orchestration->get_variable(_variable_name); + if (variable.is_valid() && variable->get_variable_type() != p_filter.target_type) + return true; + } } } return OrchestratorGraphNodeSpawnerVariable::is_filtered(p_filter, p_spec); @@ -426,7 +445,20 @@ void OrchestratorGraphNodeSpawnerVariableSet::execute(OrchestratorGraphEdit* p_g OScriptNodeInitContext context; context.variable_name = _variable_name; - p_graph->spawn_node(context, p_position); + if (_local) + { + Dictionary data; + const Ref graph = p_graph->get_owning_graph(); + if (graph->is_function()) + { + data["function_guid"] = graph->get_function_guid(); + context.user_data = data; + } + + p_graph->spawn_node(context, p_position); + } + else + p_graph->spawn_node(context, p_position); } bool OrchestratorGraphNodeSpawnerVariableSet::is_filtered(const OrchestratorGraphActionFilter& p_filter, @@ -434,9 +466,21 @@ bool OrchestratorGraphNodeSpawnerVariableSet::is_filtered(const OrchestratorGrap { if (p_filter.context_sensitive && p_filter.target_type != Variant::NIL && !p_filter.context.pins.is_empty()) { - const Ref variable = p_filter.get_orchestration()->get_variable(_variable_name); - if (variable.is_valid() && variable->get_variable_type() != p_filter.target_type) - return true; + if (_local) + { + if (OrchestratorGraphEdit* graph_edit = p_filter.context.graph) + { + const Ref variable = graph_edit->get_owning_graph()->get_local_variable(_variable_name); + if (variable.is_valid() && variable->get_variable_type() != p_filter.target_type) + return true; + } + } + else + { + const Ref variable = p_filter.get_orchestration()->get_variable(_variable_name); + if (variable.is_valid() && variable->get_variable_type() != p_filter.target_type) + return true; + } } return OrchestratorGraphNodeSpawnerVariable::is_filtered(p_filter, p_spec); } diff --git a/src/editor/graph/graph_node_spawner.h b/src/editor/graph/graph_node_spawner.h index 931bbaf7..d736c156 100644 --- a/src/editor/graph/graph_node_spawner.h +++ b/src/editor/graph/graph_node_spawner.h @@ -248,10 +248,12 @@ class OrchestratorGraphNodeSpawnerVariable : public OrchestratorGraphNodeSpawner OrchestratorGraphNodeSpawnerVariable() = default; StringName _variable_name; + bool _local{ false }; public: - OrchestratorGraphNodeSpawnerVariable(const StringName& p_variable_name) + OrchestratorGraphNodeSpawnerVariable(const StringName& p_variable_name, bool p_local = false) : _variable_name(p_variable_name) + , _local(p_local) { } @@ -273,8 +275,8 @@ class OrchestratorGraphNodeSpawnerVariableGet : public OrchestratorGraphNodeSpaw OrchestratorGraphNodeSpawnerVariableGet() = default; public: - OrchestratorGraphNodeSpawnerVariableGet(const StringName& p_variable_name, bool p_validation = false) - : OrchestratorGraphNodeSpawnerVariable(p_variable_name) + OrchestratorGraphNodeSpawnerVariableGet(const StringName& p_variable_name, bool p_validation = false, bool p_local = false) + : OrchestratorGraphNodeSpawnerVariable(p_variable_name, p_local) , _validation(p_validation) { } @@ -296,8 +298,8 @@ class OrchestratorGraphNodeSpawnerVariableSet : public OrchestratorGraphNodeSpaw OrchestratorGraphNodeSpawnerVariableSet() = default; public: - OrchestratorGraphNodeSpawnerVariableSet(const StringName& p_variable_name) - : OrchestratorGraphNodeSpawnerVariable(p_variable_name) + OrchestratorGraphNodeSpawnerVariableSet(const StringName& p_variable_name, bool p_local = false) + : OrchestratorGraphNodeSpawnerVariable(p_variable_name, p_local) { } diff --git a/src/editor/graph/pins/graph_node_pin_object.cpp b/src/editor/graph/pins/graph_node_pin_object.cpp index 25cc770b..35c80d58 100644 --- a/src/editor/graph/pins/graph_node_pin_object.cpp +++ b/src/editor/graph/pins/graph_node_pin_object.cpp @@ -39,7 +39,7 @@ void OrchestratorGraphNodePinObject::_update_label() const String base_type = _pin->get_owning_node()->get_orchestration()->get_base_type(); if (!target_class.is_empty() && ClassDB::is_parent_class(base_type, target_class)) { - if (!_pin->has_any_connections() || !Object::cast_to(_pin->get_owning_node())) + if (!_pin->has_any_connections() || !Object::cast_to(_pin->get_owning_node())) { _label->set_text("[Self]"); return; diff --git a/src/editor/plugins/inspector_plugins.cpp b/src/editor/plugins/inspector_plugins.cpp index b64e8765..f3246680 100644 --- a/src/editor/plugins/inspector_plugins.cpp +++ b/src/editor/plugins/inspector_plugins.cpp @@ -142,12 +142,12 @@ bool OrchestratorEditorInspectorPluginSignal::_parse_property(Object* p_object, bool OrchestratorEditorInspectorPluginVariable::_can_handle(Object* p_object) const { - return p_object->get_class() == OScriptVariable::get_class_static(); + return ClassDB::is_parent_class(p_object->get_class(), OScriptVariableBase::get_class_static()); } bool OrchestratorEditorInspectorPluginVariable::_parse_property(Object* p_object, Variant::Type p_type, const String& p_name, PropertyHint p_hint, const String& p_hint_string, BitField p_usage, bool p_wide) { - Ref variable = Object::cast_to(p_object); + Ref variable = Object::cast_to(p_object); if (variable.is_null()) return false; @@ -168,13 +168,12 @@ bool OrchestratorEditorInspectorPluginVariable::_parse_property(Object* p_object void OrchestratorEditorInspectorPluginVariable::edit_classification(Object* p_object) { - Ref variable = Object::cast_to(p_object); - if (variable.is_null()) - return; - - // This is done to clear and reset the editor interface - EditorInterface::get_singleton()->edit_node(nullptr); - EditorInterface::get_singleton()->edit_resource(variable); - - _classification->edit(); + Ref variable = Object::cast_to(p_object); + if (variable.is_valid()) + { + // This is done to clear and reset the editor interface + EditorInterface::get_singleton()->edit_node(nullptr); + EditorInterface::get_singleton()->edit_resource(variable); + _classification->edit(); + } } \ No newline at end of file diff --git a/src/editor/register_editor_types.cpp b/src/editor/register_editor_types.cpp index 100762ee..eb5d8546 100644 --- a/src/editor/register_editor_types.cpp +++ b/src/editor/register_editor_types.cpp @@ -20,6 +20,7 @@ #include "editor/component_panels/component_panel.h" #include "editor/component_panels/functions_panel.h" #include "editor/component_panels/graphs_panel.h" +#include "editor/component_panels/local_variables_panel.h" #include "editor/component_panels/macros_panel.h" #include "editor/component_panels/signals_panel.h" #include "editor/component_panels/variables_panel.h" @@ -123,6 +124,7 @@ void register_editor_types() ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OrchestratorScriptMacrosComponentPanel) ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OrchestratorScriptSignalsComponentPanel) ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OrchestratorScriptVariablesComponentPanel) + ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OrchestratorScriptLocalVariablesComponentPanel) // Graph Classes ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OrchestratorGraphEdit) diff --git a/src/editor/script_editor_viewport.cpp b/src/editor/script_editor_viewport.cpp index 908004cb..68b3d4f6 100644 --- a/src/editor/script_editor_viewport.cpp +++ b/src/editor/script_editor_viewport.cpp @@ -20,6 +20,7 @@ #include "common/name_utils.h" #include "editor/component_panels/functions_panel.h" #include "editor/component_panels/graphs_panel.h" +#include "editor/component_panels/local_variables_panel.h" #include "editor/component_panels/macros_panel.h" #include "editor/component_panels/signals_panel.h" #include "editor/component_panels/variables_panel.h" @@ -39,6 +40,7 @@ void OrchestratorScriptEditorViewport::_update_components() _macros->update(); _variables->update(); _signals->update(); + _local_variables->update(); } bool OrchestratorScriptEditorViewport::_can_graph_be_closed(OrchestratorGraphEdit* p_graph) @@ -65,6 +67,26 @@ void OrchestratorScriptEditorViewport::_graph_opened(OrchestratorGraphEdit* p_gr p_graph->connect("expand_node", callable_mp(this, &OrchestratorScriptEditorViewport::_expand_node).bind(p_graph)); } +void OrchestratorScriptEditorViewport::_graph_selected(OrchestratorGraphEdit* p_graph) +{ + OrchestratorEditorViewport::_graph_selected(p_graph); + + Ref graph = p_graph->get_owning_graph(); + if (graph.is_valid()) + { + const Ref function = graph->get_function(); + if (function.is_valid()) + { + _local_variables->set_function(function); + _local_variables->set_visible(true); + return; + } + } + + _local_variables->set_function(nullptr); + _local_variables->set_visible(false); +} + void OrchestratorScriptEditorViewport::_save_state() { Ref cache = OrchestratorPlugin::get_singleton()->get_editor_cache(); @@ -97,6 +119,7 @@ void OrchestratorScriptEditorViewport::_save_state() panel_states["macros"] = _macros->is_collapsed(); panel_states["variables"] = _variables->is_collapsed(); panel_states["signals"] = _signals->is_collapsed(); + panel_states["local_variables"] = _local_variables->is_collapsed(); cache->set_script_state(_orchestration->get_self()->get_path(), state); cache->save(); @@ -141,6 +164,7 @@ void OrchestratorScriptEditorViewport::_restore_state() _macros->set_collapsed(panel_state.get("macros", false)); _variables->set_collapsed(panel_state.get("variables", false)); _signals->set_collapsed(panel_state.get("signals", false)); + _local_variables->set_collapsed(panel_state.get("local_variables", false)); } } } @@ -582,6 +606,10 @@ void OrchestratorScriptEditorViewport::_notification(int p_what) _signals->connect("scroll_to_item", callable_mp(this, &OrchestratorScriptEditorViewport::_scroll_to_item)); _component_container->add_child(_signals); + _local_variables = memnew(OrchestratorScriptLocalVariablesComponentPanel(_orchestration)); + _local_variables->connect("scroll_to_item", callable_mp(this, &OrchestratorScriptEditorViewport::_scroll_to_item)); + _component_container->add_child(_local_variables); + // Always open the event graph _event_graph = _get_or_create_tab(EVENT_GRAPH_NAME); diff --git a/src/editor/script_editor_viewport.h b/src/editor/script_editor_viewport.h index 78af4ee6..186c0655 100644 --- a/src/editor/script_editor_viewport.h +++ b/src/editor/script_editor_viewport.h @@ -21,6 +21,7 @@ /// Forward declarations class OrchestratorScriptComponentPanel; +class OrchestratorScriptLocalVariablesComponentPanel; class OScript; class OScriptFunction; @@ -37,6 +38,7 @@ class OrchestratorScriptEditorViewport : public OrchestratorEditorViewport OrchestratorScriptComponentPanel* _macros{ nullptr }; //! Macros section OrchestratorScriptComponentPanel* _variables{ nullptr }; //! Variables section OrchestratorScriptComponentPanel* _signals{ nullptr }; //! Signals section + OrchestratorScriptLocalVariablesComponentPanel* _local_variables{ nullptr }; //! Local variables section //~ Begin Godot Interface void _notification(int p_what); @@ -47,6 +49,7 @@ class OrchestratorScriptEditorViewport : public OrchestratorEditorViewport bool _can_graph_be_closed(OrchestratorGraphEdit* p_graph) override; void _focus_object(Object* p_object) override; void _graph_opened(OrchestratorGraphEdit* p_graph) override; + void _graph_selected(OrchestratorGraphEdit* p_graph) override; //~ End OrchestratorEditorViewport Interface /// Saves the editor state to the cache diff --git a/src/orchestration/orchestration.cpp b/src/orchestration/orchestration.cpp index e091167f..1270566f 100644 --- a/src/orchestration/orchestration.cpp +++ b/src/orchestration/orchestration.cpp @@ -778,10 +778,10 @@ void Orchestration::remove_variable(const StringName& p_name) { ERR_FAIL_COND_MSG(!has_variable(p_name), "Cannot remove a variable that does not exist: " + p_name); - const List node_ids = _get_node_type_node_ids(); + const List node_ids = _get_node_type_node_ids(); for (int node_id : node_ids) { - const Ref variable = get_node(node_id); + const Ref variable = get_node(node_id); if (variable.is_valid() && variable->get_variable()->get_variable_name().match(p_name)) remove_node(node_id); } @@ -849,7 +849,7 @@ bool Orchestration::can_remove_variable(const StringName& p_name) const { for (const KeyValue>& E : _nodes) { - Ref variable = E.value; + const Ref variable = E.value; if (variable.is_valid() && variable->get_variable()->get_variable_name().match(p_name)) return false; } diff --git a/src/script/function.cpp b/src/script/function.cpp index 183d297d..b3151bf6 100644 --- a/src/script/function.cpp +++ b/src/script/function.cpp @@ -19,8 +19,10 @@ #include "common/dictionary_utils.h" #include "common/method_utils.h" #include "common/property_utils.h" +#include "common/variant_utils.h" #include "nodes/functions/function_entry.h" #include "nodes/functions/function_result.h" +#include "nodes/variables/variable.h" #include "script/script.h" void OScriptFunction::_get_property_list(List *r_list) const @@ -29,6 +31,7 @@ void OScriptFunction::_get_property_list(List *r_list) const r_list->push_back(PropertyInfo(Variant::DICTIONARY, "method")); r_list->push_back(PropertyInfo(Variant::BOOL, "user_defined")); r_list->push_back(PropertyInfo(Variant::INT, "id")); + r_list->push_back(PropertyInfo(Variant::ARRAY, "locals", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); } bool OScriptFunction::_get(const StringName &p_name, Variant &r_value) @@ -53,6 +56,14 @@ bool OScriptFunction::_get(const StringName &p_name, Variant &r_value) r_value = _user_defined; return true; } + else if (p_name.match("locals")) + { + TypedArray variables; + for (const KeyValue>& E : _local_variables) + variables.push_back(E.value); + r_value = variables; + return true; + } return false; } @@ -96,6 +107,18 @@ bool OScriptFunction::_set(const StringName &p_name, const Variant &p_value) _user_defined = p_value; result = true; } + else if (p_name.match("locals")) + { + _local_variables.clear(); + + const TypedArray variables = p_value; + for (int i = 0; i < variables.size(); i++) + { + const Ref local = variables[i]; + _local_variables[local->get_variable_name()] = local; + } + result = true; + } if (result) emit_changed(); @@ -344,3 +367,86 @@ void OScriptFunction::set_has_return_value(bool p_has_return_value) emit_changed(); } } + +Vector> OScriptFunction::get_local_variables() const +{ + Vector> results; + for (const KeyValue>& E : _local_variables) + results.push_back(E.value); + return results; +} + +Ref OScriptFunction::create_local_variable(const StringName& p_name) +{ + ERR_FAIL_COND_V_MSG(!String(p_name).is_valid_identifier(), nullptr, "Cannot create local variable, invalid name: " + p_name); + ERR_FAIL_COND_V_MSG(has_local_variable(p_name), nullptr, "A local variable with that name already exists: " + p_name); + + PropertyInfo property; + property.name = p_name; + property.type = Variant::BOOL; + + Ref variable(memnew(OScriptLocalVariable)); + variable->_info = property; + variable->_default_value = VariantUtils::make_default(property.type); + variable->_category = "Default"; + variable->_classification = "type:" + Variant::get_type_name(property.type); + variable->_info.hint = PROPERTY_HINT_NONE; + variable->_info.hint_string = ""; + variable->_info.class_name = ""; + variable->_info.usage = PROPERTY_USAGE_STORAGE; + _local_variables[p_name] = variable; + + return variable; +} + +void OScriptFunction::remove_local_variable(const StringName& p_name) +{ + ERR_FAIL_COND_MSG(!has_local_variable(p_name), "No local variable defined with name: " + p_name); + + for (const Ref& node : _orchestration->get_nodes()) + { + const Ref variable = node; + if (variable.is_valid() && variable->get_variable_name().match(p_name)) + _orchestration->remove_node(node->get_id()); + } + + _local_variables.erase(p_name); + + emit_changed(); +} + +bool OScriptFunction::has_local_variable(const StringName& p_name) const +{ + return _local_variables.has(p_name); +} + +Ref OScriptFunction::get_local_variable(const StringName& p_name) const +{ + if (has_local_variable(p_name)) + return _local_variables[p_name]; + + return nullptr; +} + +bool OScriptFunction::rename_local_variable(const String& p_old_name, const String& p_new_name) +{ + if (p_old_name == p_new_name) + return false; + + ERR_FAIL_COND_V_MSG(!has_local_variable(p_old_name), false, "Cannot rename, no local variable exists with the old name: " + p_old_name); + ERR_FAIL_COND_V_MSG(has_local_variable(p_new_name), false, "Cannot rename, a local variable already exists with the new name: " + p_new_name); + ERR_FAIL_COND_V_MSG(!String(p_new_name).is_valid_identifier(), false, "Cannot rename, local variable name is not valid: " + p_new_name); + + const Ref variable = _local_variables[p_old_name]; + variable->set_variable_name(p_new_name); + + _local_variables[p_new_name] = variable; + _local_variables.erase(p_old_name); + + emit_changed(); + notify_property_list_changed(); + + return true; +} + + diff --git a/src/script/function.h b/src/script/function.h index fdd798c8..d84ad0eb 100644 --- a/src/script/function.h +++ b/src/script/function.h @@ -19,8 +19,10 @@ #include "common/guid.h" #include "orchestration/build_log.h" +#include "script/variable.h" #include +#include #include #include #include @@ -54,6 +56,7 @@ class OScriptFunction : public Resource bool _user_defined{ false }; //! Whether function is user-defined int _owning_node_id{ -1 }; //! Owning node id bool _returns_value{ false }; //! Whether the function returns a value + HashMap> _local_variables; protected: //~ Begin Wrapped Interface @@ -184,6 +187,31 @@ class OScriptFunction : public Resource /// Sets whether the function has a return value /// @param p_has_return_value value true if the function has a return value, false otherwise void set_has_return_value(bool p_has_return_value); + + /// Get all local variables defined in the function + /// @return list of local variables + Vector> get_local_variables() const; + + /// Creates a new local variable with the given name + /// @param p_name the local variable name + /// @return the created local variable, or an invalid reference if it failed + Ref create_local_variable(const StringName& p_name); + + /// Removes a local variable by name + /// @param p_name the variable name + void remove_local_variable(const StringName& p_name); + + /// Return whether a local variable with the given name exists + /// @param p_name the local variable name + /// @return true if a local variable exists with the name, false otherwise + bool has_local_variable(const StringName& p_name) const; + + /// Get a local variable by name, if found. + /// @param p_name the variable name + /// @return the local variable declaration if found, an invalid reference if it doesn't. + Ref get_local_variable(const StringName& p_name) const; + + bool rename_local_variable(const String& p_old_name, const String& p_new_name); }; #endif // ORCHESTRATOR_SCRIPT_FUNCTION_H \ No newline at end of file diff --git a/src/script/graph.cpp b/src/script/graph.cpp index d5e5077d..0f165874 100644 --- a/src/script/graph.cpp +++ b/src/script/graph.cpp @@ -294,6 +294,26 @@ Vector> OScriptGraph::get_nodes() const return nodes; } +Guid OScriptGraph::get_function_guid() const +{ + const Ref function = get_function(); + if (function.is_valid()) + return function->get_guid(); + + return {}; +} + +Ref OScriptGraph::get_function() const +{ + if (is_function() && _functions.size() == 1) + { + const Ref entry = _orchestration->get_node(_functions.front()->get()); + if (entry.is_valid()) + return entry->get_function(); + } + return nullptr; +} + RBSet OScriptGraph::get_connections() const { RBSet connections; @@ -450,6 +470,15 @@ void OScriptGraph::remove_connection_knot(uint64_t p_connection_id) emit_signal("connection_knots_removed", p_connection_id); } +Ref OScriptGraph::get_local_variable(const StringName& p_variable_name) const +{ + const Ref function = get_function(); + if (function.is_valid()) + return function->get_local_variable(p_variable_name); + + return nullptr; +} + Ref OScriptGraph::create_node(const StringName& p_type, const OScriptNodeInitContext& p_context, const Vector2& p_position) { const Ref spawned = OScriptLanguage::get_singleton()->create_node_from_name(p_type, _orchestration); diff --git a/src/script/graph.h b/src/script/graph.h index d85a7edf..81d15f24 100644 --- a/src/script/graph.h +++ b/src/script/graph.h @@ -17,6 +17,7 @@ #ifndef ORCHESTRATOR_SCRIPT_GRAPH_H #define ORCHESTRATOR_SCRIPT_GRAPH_H +#include "common/guid.h" #include "script/connection.h" #include @@ -28,6 +29,7 @@ using namespace godot; /// Forward declarations class Orchestration; class OScriptFunction; +class OScriptLocalVariable; class OScriptNode; struct OScriptNodeInitContext; @@ -131,6 +133,18 @@ class OScriptGraph : public Resource /// @param p_flags bit field of flags void set_flags(BitField p_flags); + /// Return whether the graph is a function graph + /// @return true if the graph has the function flag + bool is_function() const { return _flags.has_flag(GF_FUNCTION); } + + /// Get the function graph's function GUID + /// @return the function guid, if a function graph + Guid get_function_guid() const; + + /// Gets the singular function when a function graph + /// @return the function, or an invalid reference if this graph contains multiple functions + Ref get_function() const; + /// Get all connections within this graph /// @return the graph connections RBSet get_connections() const; @@ -216,6 +230,11 @@ class OScriptGraph : public Resource /// Remove connection knots for connection void remove_connection_knot(uint64_t p_connection_id); + /// Lookup a local variable in the graph + /// @param p_variable_name + /// @return a local variable reference if found, an invalid reference if not found + Ref get_local_variable(const StringName& p_variable_name) const; + /// Create a new node within this graph /// @tparam T the node type /// @param p_context node initialization context diff --git a/src/script/node.h b/src/script/node.h index e66c75bf..5aed21e2 100644 --- a/src/script/node.h +++ b/src/script/node.h @@ -77,7 +77,8 @@ class OScriptNode : public Resource NONE = 1 << 0, //! No specific flags CATALOGABLE = 1 << 1, //! Node should appear in the action catalog DEVELOPMENT_ONLY = 1 << 2, //! Node should be marked in the UI as development only - EXPERIMENTAL = 1 << 3 //! Node is experimental and may change + EXPERIMENTAL = 1 << 3, //! Node is experimental and may change + DEPRECATED = 1 << 4 //! Node is deprecated and scheduled for removal }; #if GODOT_VERSION >= 0x040300 diff --git a/src/script/nodes/variables/local_variable.cpp b/src/script/nodes/variables/local_variable.cpp index 22ad8b40..705f0f2c 100644 --- a/src/script/nodes/variables/local_variable.cpp +++ b/src/script/nodes/variables/local_variable.cpp @@ -182,6 +182,11 @@ void OScriptNodeLocalVariable::initialize(const OScriptNodeInitContext& p_contex super::initialize(p_context); } +OScriptNodeLocalVariable::OScriptNodeLocalVariable() +{ + _flags = ScriptNodeFlags::DEPRECATED; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void OScriptNodeAssignLocalVariable::_upgrade(uint32_t p_version, uint32_t p_current_version) @@ -328,4 +333,9 @@ String OScriptNodeAssignLocalVariable::get_variable_guid() const } } return {}; +} + +OScriptNodeAssignLocalVariable::OScriptNodeAssignLocalVariable() +{ + _flags = ScriptNodeFlags::DEPRECATED; } \ No newline at end of file diff --git a/src/script/nodes/variables/local_variable.h b/src/script/nodes/variables/local_variable.h index b36c55d7..1c66e82e 100644 --- a/src/script/nodes/variables/local_variable.h +++ b/src/script/nodes/variables/local_variable.h @@ -41,7 +41,7 @@ class OScriptNodeLocalVariable : public OScriptNode void allocate_default_pins() override; String get_node_title() const override; String get_icon() const override; - String get_node_title_color_name() const override { return "variable"; } + String get_node_title_color_name() const override { return "local_variable"; } String get_tooltip_text() const override; bool is_compatible_with_graph(const Ref& p_graph) const override; bool can_duplicate() const override { return false; } @@ -51,6 +51,8 @@ class OScriptNodeLocalVariable : public OScriptNode /// Get the associated variable GUID String get_variable_guid() const { return _guid.to_string(); } + + OScriptNodeLocalVariable(); }; class OScriptNodeAssignLocalVariable : public OScriptNode @@ -71,7 +73,7 @@ class OScriptNodeAssignLocalVariable : public OScriptNode void post_placed_new_node() override; void allocate_default_pins() override; String get_node_title() const override; - String get_node_title_color_name() const override { return "variable"; } + String get_node_title_color_name() const override { return "local_variable"; } String get_tooltip_text() const override; bool is_compatible_with_graph(const Ref& p_graph) const override; OScriptNodeInstance* instantiate() override; @@ -82,6 +84,8 @@ class OScriptNodeAssignLocalVariable : public OScriptNode /// Get the associated variable GUID String get_variable_guid() const; + + OScriptNodeAssignLocalVariable(); }; #endif // ORCHESTRATOR_SCRIPT_NODE_LOCAL_VARIABLE_H \ No newline at end of file diff --git a/src/script/nodes/variables/variable.cpp b/src/script/nodes/variables/variable.cpp index 9ede6a13..7ea2765a 100644 --- a/src/script/nodes/variables/variable.cpp +++ b/src/script/nodes/variables/variable.cpp @@ -16,18 +16,12 @@ // #include "variable.h" -OScriptNodeVariable::OScriptNodeVariable() -{ - // Catalog versions are added explicitly - _flags = ScriptNodeFlags::NONE; -} - -void OScriptNodeVariable::_get_property_list(List* r_list) const +void OScriptNodeVariableBase::_get_property_list(List* r_list) const { r_list->push_back(PropertyInfo(Variant::STRING, "variable_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); } -bool OScriptNodeVariable::_get(const StringName& p_name, Variant& r_value) const +bool OScriptNodeVariableBase::_get(const StringName& p_name, Variant& r_value) const { if (p_name.match("variable_name")) { @@ -37,7 +31,7 @@ bool OScriptNodeVariable::_get(const StringName& p_name, Variant& r_value) const return false; } -bool OScriptNodeVariable::_set(const StringName& p_name, const Variant& p_value) +bool OScriptNodeVariableBase::_set(const StringName& p_name, const Variant& p_value) { if (p_name.match("variable_name")) { @@ -47,7 +41,30 @@ bool OScriptNodeVariable::_set(const StringName& p_name, const Variant& p_value) return false; } -void OScriptNodeVariable::_on_variable_changed() +void OScriptNodeVariableBase::initialize(const OScriptNodeInitContext& p_context) +{ + ERR_FAIL_COND_MSG(!p_context.variable_name, "Failed to initialize Variable without a variable name"); + _variable_name = p_context.variable_name.value(); + + _lookup_and_set_variable(_variable_name); + + super::initialize(p_context); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void OScriptNodeScriptVariableBase::_lookup_and_set_variable(const StringName& p_variable_name) +{ + Ref variable = get_orchestration()->get_variable(p_variable_name); + if (variable.is_valid()) + { + _variable = variable; + if (_is_in_editor()) + _variable->connect("changed", callable_mp(this, &OScriptNodeScriptVariableBase::_on_variable_changed)); + } +} + +void OScriptNodeScriptVariableBase::_on_variable_changed() { if (_variable.is_valid()) { @@ -59,39 +76,121 @@ void OScriptNodeVariable::_on_variable_changed() } } -void OScriptNodeVariable::post_initialize() +void OScriptNodeScriptVariableBase::post_initialize() { if (!_variable_name.is_empty()) + _lookup_and_set_variable(_variable_name); + + super::post_initialize(); +} + +void OScriptNodeScriptVariableBase::validate_node_during_build(BuildLog& p_log) const +{ + const Ref variable = get_orchestration()->get_variable(_variable_name); + if (!variable.is_valid()) + p_log.error(this, "Variable is no longer defined."); + + super::validate_node_during_build(p_log); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void OScriptNodeLocalVariableBase::_get_property_list(List* r_list) const +{ + r_list->push_back(PropertyInfo(Variant::STRING, "guid", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); +} + +bool OScriptNodeLocalVariableBase::_get(const StringName& p_name, Variant& r_value) const +{ + if (p_name.match("guid")) { - _variable = get_orchestration()->get_variable(_variable_name); - if (_variable.is_valid() && _is_in_editor()) - _variable->connect("changed", callable_mp(this, &OScriptNodeVariable::_on_variable_changed)); + r_value = _function_guid.to_string(); + return true; } - super::post_initialize(); + return false; +} + +bool OScriptNodeLocalVariableBase::_set(const StringName& p_name, const Variant& p_value) +{ + if (p_name.match("guid")) + { + _function_guid = Guid(p_value); + return true; + } + return false; } -String OScriptNodeVariable::get_icon() const +void OScriptNodeLocalVariableBase::_lookup_and_set_variable(const StringName& p_variable_name) { - return "MemberProperty"; + const Ref function = get_function(); + if (!function.is_valid()) + return; + + const Ref variable = function->get_local_variable(p_variable_name); + if (!variable.is_valid()) + return; + + _variable = variable; + + if (_is_in_editor()) + _variable->connect("changed", callable_mp(this, &OScriptNodeLocalVariableBase::_on_variable_changed)); } -void OScriptNodeVariable::initialize(const OScriptNodeInitContext& p_context) +void OScriptNodeLocalVariableBase::_on_variable_changed() { - ERR_FAIL_COND_MSG(!p_context.variable_name, "Failed to initialize Variable without a variable name"); + if (_variable.is_valid()) + { + _variable_name = _variable->get_variable_name(); + reconstruct_node(); - _variable_name = p_context.variable_name.value(); - _variable = get_orchestration()->get_variable(_variable_name); + // This must be triggered after reconstruction + _variable_changed(); + } +} + +void OScriptNodeLocalVariableBase::post_initialize() +{ + _lookup_and_set_variable(_variable_name); - if (_variable.is_valid() && _is_in_editor()) - _variable->connect("changed", callable_mp(this, &OScriptNodeVariable::_on_variable_changed)); + super::post_initialize(); +} + +void OScriptNodeLocalVariableBase::initialize(const OScriptNodeInitContext& p_context) +{ + ERR_FAIL_COND_MSG(!p_context.user_data, "Must supply user data to create a local variable node."); + ERR_FAIL_COND_MSG(!p_context.user_data.value().has("function_guid"), "Must have a function guid."); + + _function_guid = Guid(p_context.user_data.value()["function_guid"]); super::initialize(p_context); } -void OScriptNodeVariable::validate_node_during_build(BuildLog& p_log) const +void OScriptNodeLocalVariableBase::validate_node_during_build(BuildLog& p_log) const { - if (!_variable.is_valid()) - p_log.error(this, "Variable is no longer defined."); + if (!_function_guid.is_valid()) + p_log.error(this, "Function reference is invalid."); + + if (_function_guid.is_valid()) + { + const Ref function = get_function(); + if (!function.is_valid()) + p_log.error(this, "Function is no longer defined."); + + if (function.is_valid()) + { + const Ref variable = function->get_local_variable(_variable_name); + if (!variable.is_valid()) + p_log.error(this, "Local variable is no longer defined."); + } + } super::validate_node_during_build(p_log); -} \ No newline at end of file +} + +Ref OScriptNodeLocalVariableBase::get_function() const +{ + if (!_function_guid.is_valid()) + return nullptr; + + return get_orchestration()->find_function(_function_guid); +} diff --git a/src/script/nodes/variables/variable.h b/src/script/nodes/variables/variable.h index 183e9486..f8a880ea 100644 --- a/src/script/nodes/variables/variable.h +++ b/src/script/nodes/variables/variable.h @@ -19,15 +19,14 @@ #include "script/script.h" -/// An abstract script node for all variable operations. -class OScriptNodeVariable : public OScriptNode +/// An abstract script node for all variable types (script and local) +class OScriptNodeVariableBase : public OScriptNode { - ORCHESTRATOR_NODE_CLASS(OScriptNodeVariable, OScriptNode); + ORCHESTRATOR_NODE_CLASS(OScriptNodeVariableBase, OScriptNode); static void _bind_methods() { } protected: - StringName _variable_name; //! Variable name reference - Ref _variable; //! Variable reference + StringName _variable_name; //! Variable name //~ Begin Wrapped Interface void _get_property_list(List* r_list) const; @@ -35,25 +34,87 @@ class OScriptNodeVariable : public OScriptNode bool _set(const StringName& p_name, const Variant& p_value); //~ End Wrapped Interface - /// Called when the script variable is modified - void _on_variable_changed(); + /// Lookup and set the variable + /// @param p_variable_name the variable name + virtual void _lookup_and_set_variable(const StringName& p_variable_name) { } - /// Allows subclasses to handle variable changed + /// Allow subclasses to update when variable changes virtual void _variable_changed() { } public: - OScriptNodeVariable(); + //~ Begin OScriptNode Interface + String get_icon() const override { return "MemberProperty"; } + void initialize(const OScriptNodeInitContext& p_context) override; + //~ End OScriptNode Interface + + /// Get the variable name this node represents + String get_variable_name() const { return _variable_name; } + + OScriptNodeVariableBase() = default; // todo: is this needed? +}; + +/// An abstract script node for script variables. +class OScriptNodeScriptVariableBase : public OScriptNodeVariableBase +{ + ORCHESTRATOR_NODE_CLASS(OScriptNodeScriptVariableBase, OScriptNodeVariableBase); + static void _bind_methods() { } +protected: + Ref _variable; //! Script variable + + //~ Begin OScriptNodeVariableBase Interface + void _lookup_and_set_variable(const StringName& p_variable_name) override; + //~ End OScriptNodeVariableBase Interface + + /// Called when the script variable is modified + void _on_variable_changed(); + +public: //~ Begin OScriptNode Interface void post_initialize() override; - String get_icon() const override; String get_node_title_color_name() const override { return "variable"; } - Ref get_inspect_object() override { return _variable; } + void validate_node_during_build(BuildLog& p_log) const override; + //~ End OScriptNode Interface + + /// Get the variable this node represents + Ref get_variable() const { return _variable; } +}; + +/// An abstract script node for local variables. +class OScriptNodeLocalVariableBase : public OScriptNodeVariableBase +{ + ORCHESTRATOR_NODE_CLASS(OScriptNodeLocalVariableBase, OScriptNodeVariableBase); + static void _bind_methods() { } + +protected: + Guid _function_guid; //! Function guid + Ref _variable; //! Local variable + + //~ Begin Wrapped Interface + void _get_property_list(List* r_list) const; + bool _get(const StringName& p_name, Variant& r_value) const; + bool _set(const StringName& p_name, const Variant& p_value); + //~ End Wrapped Interface + + //~ Begin OScriptNodeVariableBase Interface + void _lookup_and_set_variable(const StringName& p_variable_name) override; + //~ End OScriptNodeVariableBase Interface + + /// Called when the local variable is modified + void _on_variable_changed(); + +public: + //~ Begin OScriptNode Interface + void post_initialize() override; + String get_node_title_color_name() const override { return "local_variable"; } void initialize(const OScriptNodeInitContext& p_context) override; void validate_node_during_build(BuildLog& p_log) const override; //~ End OScriptNode Interface - Ref get_variable() { return _variable; } + Ref get_function() const; + + /// Get the local variable this node represents + Ref get_variable() const { return _variable; } }; #endif // ORCHESTRATOR_SCRIPT_NODE_VARIABLE_H diff --git a/src/script/nodes/variables/variable_get.cpp b/src/script/nodes/variables/variable_get.cpp index 0b9061c7..f20b9dda 100644 --- a/src/script/nodes/variables/variable_get.cpp +++ b/src/script/nodes/variables/variable_get.cpp @@ -48,6 +48,33 @@ class OScriptNodeVariableGetInstance : public OScriptNodeInstance //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class OScriptNodeLocalVariableGetInstance : public OScriptNodeInstance +{ + DECLARE_SCRIPT_NODE_INSTANCE(OScriptNodeLocalVariableGet); + StringName _variable_name; + bool _validated{ false }; + +public: + int step(OScriptExecutionContext& p_context) override + { + OScriptVirtualMachine::Function* function = reinterpret_cast(p_context.get_function()); + if (!function) + { + p_context.set_error("Failed to resolve current executing function."); + return -1 | STEP_FLAG_END; + } + + p_context.set_output(0, function->_variables[_variable_name]); + + if (_validated) + return Object::cast_to(p_context.get_working_memory()) ? 0 : 1; + + return 0; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void OScriptNodeVariableGet::_get_property_list(List* r_list) const { r_list->push_back(PropertyInfo(Variant::BOOL, "validated", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); @@ -63,7 +90,7 @@ bool OScriptNodeVariableGet::_get(const StringName& p_name, Variant& r_value) co // todo: GodotCPP expects this to be done by the developer, Wrapped::get_bind doesn't do this // see https://github.com/godotengine/godot-cpp/pull/1539 - return OScriptNodeVariable::_get(p_name, r_value); + return OScriptNodeVariableBase::_get(p_name, r_value); } bool OScriptNodeVariableGet::_set(const StringName& p_name, const Variant& p_value) @@ -76,7 +103,7 @@ bool OScriptNodeVariableGet::_set(const StringName& p_name, const Variant& p_val // todo: GodotCPP expects this to be done by the developer, Wrapped::set_bind doesn't do this // see https://github.com/godotengine/godot-cpp/pull/1539 - return OScriptNodeVariable::_set(p_name, p_value); + return OScriptNodeVariableBase::_set(p_name, p_value); } void OScriptNodeVariableGet::_upgrade(uint32_t p_version, uint32_t p_current_version) @@ -215,3 +242,165 @@ void OScriptNodeVariableGet::set_validated(bool p_validated) } } } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void OScriptNodeLocalVariableGet::_get_property_list(List* r_list) const +{ + r_list->push_back(PropertyInfo(Variant::BOOL, "validated", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE)); +} + +bool OScriptNodeLocalVariableGet::_get(const StringName& p_name, Variant& r_value) const +{ + if (p_name.match("validated")) + { + r_value = _validated; + return true; + } + + #if GODOT_VERSION < 0x04300 + // todo: GodotCPP expects this to be done by the developer, Wrapped::get_bind doesn't do this + // see https://github.com/godotengine/godot-cpp/pull/1539 + return OScriptNodeVariableBase::_get(p_name, r_value); + #else + return false; + #endif +} + +bool OScriptNodeLocalVariableGet::_set(const StringName& p_name, const Variant& p_value) +{ + if (p_name.match("validated")) + { + _validated = p_value; + return true; + } + + #if GODOT_VERSION < 0x040300 + // todo: GodotCPP expects this to be done by the developer, Wrapped::set_bind doesn't do this + // see https://github.com/godotengine/godot-cpp/pull/1539 + return OScriptNodeVariableBase::_set(p_name, p_value); + #else + return false; + #endif +} + +void OScriptNodeLocalVariableGet::_variable_changed() +{ + if (_is_in_editor()) + { + Ref output = find_pin("value", PD_Output); + if (output.is_valid() && output->has_any_connections()) + { + Ref target = output->get_connections()[0]; + if (target.is_valid() && !target->can_accept(output)) + output->unlink_all(); + } + } + + super::_variable_changed(); +} + +void OScriptNodeLocalVariableGet::allocate_default_pins() +{ + if (_validated) + { + create_pin(PD_Input, PT_Execution, PropertyUtils::make_exec("ExecIn")); + create_pin(PD_Output, PT_Execution, PropertyUtils::make_exec("is_valid"))->set_label("Is Valid"); + create_pin(PD_Output, PT_Execution, PropertyUtils::make_exec("is_invalid"))->set_label("Is Invalid"); + } + + create_pin(PD_Output, PT_Data, PropertyUtils::as("value", _variable->get_info()))->set_label(_variable_name, false); + + super::allocate_default_pins(); +} + +String OScriptNodeLocalVariableGet::get_tooltip_text() const +{ + if (_variable.is_valid()) + { + String tooltip_text = vformat("Read the value of local variable %s", _variable->get_variable_name()); + if (!_variable->get_description().is_empty()) + tooltip_text += "\n\nDescription:\n" + _variable->get_description(); + + return tooltip_text; + } + + return "Read the value of a local variable"; +} + +String OScriptNodeLocalVariableGet::get_node_title() const +{ + return vformat("Get %s", _variable_name); +} + +OScriptNodeInstance* OScriptNodeLocalVariableGet::instantiate() +{ + OScriptNodeLocalVariableGetInstance *i = memnew(OScriptNodeLocalVariableGetInstance); + i->_node = this; + i->_variable_name = _variable_name; + i->_validated = _validated; + return i; +} + +void OScriptNodeLocalVariableGet::initialize(const OScriptNodeInitContext& p_context) +{ + if (p_context.user_data) + { + const Dictionary& data = p_context.user_data.value(); + if (data.has("validation")) + _validated = data["validation"]; + } + + super::initialize(p_context); +} + +bool OScriptNodeLocalVariableGet::can_be_validated() +{ + if (_variable.is_valid()) + return _variable->supports_validated_getter(); + + return false; +} + +void OScriptNodeLocalVariableGet::set_validated(bool p_validated) +{ + if (_validated != p_validated) + { + _validated = p_validated; + + if (!_validated) + { + // Disconnect any control flow pins, if they exist + Ref exec_in = find_pin("ExecIn", PD_Input); + if (exec_in.is_valid()) + exec_in->unlink_all(); + + Ref is_valid = find_pin("is_valid", PD_Output); + if (is_valid.is_valid()) + is_valid->unlink_all(); + + Ref is_not_valid = find_pin("is_not_valid", PD_Output); + if (is_not_valid.is_valid()) + is_not_valid->unlink_all(); + } + + // Record the connection before the change + Ref connection; + Ref value = find_pin("value", PD_Output); + if (value.is_valid() && value->has_any_connections()) + { + connection = value->get_connections()[0]; + value->unlink_all(); + } + + _notify_pins_changed(); + + if (connection.is_valid()) + { + // Relink connection on change + value = find_pin("value", PD_Output); + if (value.is_valid()) + value->link(connection); + } + } +} diff --git a/src/script/nodes/variables/variable_get.h b/src/script/nodes/variables/variable_get.h index 6e09dbe5..4fb6f0a4 100644 --- a/src/script/nodes/variables/variable_get.h +++ b/src/script/nodes/variables/variable_get.h @@ -20,9 +20,9 @@ #include "variable.h" /// A variable implementation that gets the value of a variable. -class OScriptNodeVariableGet : public OScriptNodeVariable +class OScriptNodeVariableGet : public OScriptNodeScriptVariableBase { - ORCHESTRATOR_NODE_CLASS(OScriptNodeVariableGet, OScriptNodeVariable); + ORCHESTRATOR_NODE_CLASS(OScriptNodeVariableGet, OScriptNodeScriptVariableBase); static void _bind_methods() { } protected: @@ -65,4 +65,46 @@ class OScriptNodeVariableGet : public OScriptNodeVariable void set_validated(bool p_validated); }; +/// A variable implementation that gets the value of a function local variable. +class OScriptNodeLocalVariableGet : public OScriptNodeLocalVariableBase +{ + ORCHESTRATOR_NODE_CLASS(OScriptNodeLocalVariableGet, OScriptNodeLocalVariableBase); + static void _bind_methods() { } + +protected: + bool _validated{ false }; //! Whether to represent get as validated get + + // //~ Begin Wrapped Interface + void _get_property_list(List* r_list) const; + bool _get(const StringName& p_name, Variant& r_value) const; + bool _set(const StringName& p_name, const Variant& p_value); + // //~ End Wrapped Interface + + //~ Begin OScriptNodeVariable Interface + void _variable_changed() override; + //~ End OScriptNodeVariable Interface + +public: + //~ Begin OScriptNode Interface + void allocate_default_pins() override; + String get_tooltip_text() const override; + String get_node_title() const override; + bool should_draw_as_bead() const override { return true; } + OScriptNodeInstance* instantiate() override; + void initialize(const OScriptNodeInitContext& p_context) override; + //~ End OScriptNode Interface + + /// Return whether the node can be validated + /// @return true if the node can be validated, false otherwise + bool can_be_validated(); + + /// Return whether the variable is validated + /// @return true if the node is rendered as a validated node, false otherwise. + bool is_validated() const { return _validated; } + + /// Change whether the node is rendered as a validated get + /// @param p_validated when true, rendered as validated get + void set_validated(bool p_validated); +}; + #endif // ORCHESTRATOR_SCRIPT_NODE_VARIABLE_GET_H diff --git a/src/script/nodes/variables/variable_set.cpp b/src/script/nodes/variables/variable_set.cpp index c238eb87..ed55df98 100644 --- a/src/script/nodes/variables/variable_set.cpp +++ b/src/script/nodes/variables/variable_set.cpp @@ -66,6 +66,30 @@ class OScriptNodeVariableSetInstance : public OScriptNodeInstance //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class OScriptNodeLocalVariableSetInstance : public OScriptNodeInstance +{ + DECLARE_SCRIPT_NODE_INSTANCE(OScriptNodeLocalVariableSet); + StringName _variable_name; + +public: + int step(OScriptExecutionContext& p_context) override + { + OScriptVirtualMachine::Function* function = reinterpret_cast(p_context.get_function()); + if (!function) + { + p_context.set_error("Failed to resolve current executing function."); + return -1 | STEP_FLAG_END; + } + + function->_variables[_variable_name] = p_context.get_input(0); + p_context.set_output(0, function->_variables[_variable_name]); + + return 0; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void OScriptNodeVariableSet::_upgrade(uint32_t p_version, uint32_t p_current_version) { if (p_version == 1 and p_current_version >= 2) @@ -133,7 +157,7 @@ String OScriptNodeVariableSet::get_tooltip_text() const String OScriptNodeVariableSet::get_node_title() const { - return vformat("Set %s", _variable->get_variable_name()); + return vformat("Set %s", _variable_name); } void OScriptNodeVariableSet::reallocate_pins_during_reconstruction(const Vector>& p_old_pins) @@ -173,3 +197,89 @@ OScriptNodeInstance* OScriptNodeVariableSet::instantiate() i->_constant = _variable->is_constant(); return i; } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void OScriptNodeLocalVariableSet::_variable_changed() +{ + if (_is_in_editor()) + { + Ref input = find_pin(1, PD_Input); + if (input.is_valid() && input->has_any_connections()) + { + Ref source = input->get_connections()[0]; + if (!input->can_accept(source)) + input->unlink_all(); + } + + Ref output = find_pin("value", PD_Output); + if (output.is_valid() && output->has_any_connections()) + { + // todo: shouldn't this support multiple? + Ref target = output->get_connections()[0]; + if (!target->can_accept(output)) + output->unlink_all(); + } + } + + super::_variable_changed(); +} + +void OScriptNodeLocalVariableSet::allocate_default_pins() +{ + create_pin(PD_Input, PT_Execution, PropertyUtils::make_exec("ExecIn")); + create_pin(PD_Input, PT_Data, _variable->get_info())->no_pretty_format(); + + create_pin(PD_Output, PT_Execution, PropertyUtils::make_exec("ExecOut")); + create_pin(PD_Output, PT_Data, PropertyUtils::as("value", _variable->get_info()))->hide_label(); + + super::allocate_default_pins(); +} + +String OScriptNodeLocalVariableSet::get_tooltip_text() const +{ + if (_variable.is_valid()) + { + String tooltip_text = vformat("Set the value of local variable %s", _variable->get_variable_name()); + if (!_variable->get_description().is_empty()) + tooltip_text += "\n\nDescription:\n" + _variable->get_description(); + + return tooltip_text; + } + + return vformat("Set the value of a local variable"); +} + +String OScriptNodeLocalVariableSet::get_node_title() const +{ + return vformat("Set %s", _variable_name); +} + +void OScriptNodeLocalVariableSet::reallocate_pins_during_reconstruction(const Vector>& p_old_pins) +{ + super::reallocate_pins_during_reconstruction(p_old_pins); + + // Keep old default value if one was set that differs from the variable's default value + for (const Ref& old_pin : p_old_pins) + { + if (old_pin->is_input() && !old_pin->is_execution()) + { + if (old_pin->get_effective_default_value() != _variable->get_default_value()) + { + Ref value_pin = find_pin(_variable->get_variable_name(), PD_Input); + if (value_pin.is_valid() && !value_pin->has_any_connections()) + value_pin->set_default_value(VariantUtils::convert(old_pin->get_effective_default_value(), value_pin->get_type())); + + break; + } + } + } +} + +OScriptNodeInstance* OScriptNodeLocalVariableSet::instantiate() +{ + OScriptNodeLocalVariableSetInstance *i = memnew(OScriptNodeLocalVariableSetInstance); + i->_node = this; + i->_variable_name = _variable_name; + return i; +} \ No newline at end of file diff --git a/src/script/nodes/variables/variable_set.h b/src/script/nodes/variables/variable_set.h index 5b3be985..5fa47961 100644 --- a/src/script/nodes/variables/variable_set.h +++ b/src/script/nodes/variables/variable_set.h @@ -20,9 +20,9 @@ #include "variable.h" /// A variable implementation that sets the value of a variable. -class OScriptNodeVariableSet : public OScriptNodeVariable +class OScriptNodeVariableSet : public OScriptNodeScriptVariableBase { - ORCHESTRATOR_NODE_CLASS(OScriptNodeVariableSet, OScriptNodeVariable); + ORCHESTRATOR_NODE_CLASS(OScriptNodeVariableSet, OScriptNodeScriptVariableBase); static void _bind_methods() { } protected: @@ -45,4 +45,25 @@ class OScriptNodeVariableSet : public OScriptNodeVariable //~ End OScriptNode Interface }; +/// A variable implementation that sets the value of a local variable. +class OScriptNodeLocalVariableSet : public OScriptNodeLocalVariableBase +{ + ORCHESTRATOR_NODE_CLASS(OScriptNodeLocalVariableSet, OScriptNodeLocalVariableBase); + static void _bind_methods() { } + +protected: + //~ Begin OScriptNodeVariable Interface + void _variable_changed() override; + //~ End OScriptNodeVariable Interface + +public: + //~ Begin OScriptNode Interface + void allocate_default_pins() override; + String get_tooltip_text() const override; + String get_node_title() const override; + void reallocate_pins_during_reconstruction(const Vector>& p_old_pins) override; + OScriptNodeInstance* instantiate() override; + //~ End OScriptNode Interface +}; + #endif // ORCHESTRATOR_SCRIPT_NODE_VARIABLE_SET_H diff --git a/src/script/register_script_types.cpp b/src/script/register_script_types.cpp index 3d9eeb73..3b5a70fa 100644 --- a/src/script/register_script_types.cpp +++ b/src/script/register_script_types.cpp @@ -63,7 +63,9 @@ void register_script_types() ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptLanguage) ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptGraph) ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptFunction) + ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptVariableBase) ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptVariable) + ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptLocalVariable) ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptSignal) ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptState) ORCHESTRATOR_REGISTER_INTERNAL_CLASS(OScriptAction) @@ -122,7 +124,9 @@ void register_script_node_types() // Script Nodes (Abstracts first) ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptEditablePinNode) ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptNodeProperty) - ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptNodeVariable) + ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptNodeVariableBase) + ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptNodeLocalVariableBase) + ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptNodeScriptVariableBase) ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptNodeConstant) ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptNodeSwitchEditablePin) ORCHESTRATOR_REGISTER_ABSTRACT_NODE_CLASS(OScriptNodeClassConstantBase) @@ -218,6 +222,8 @@ void register_script_node_types() // Variables ORCHESTRATOR_REGISTER_NODE_CLASS(OScriptNodeSelf) + ORCHESTRATOR_REGISTER_NODE_CLASS(OScriptNodeLocalVariableGet) + ORCHESTRATOR_REGISTER_NODE_CLASS(OScriptNodeLocalVariableSet) ORCHESTRATOR_REGISTER_NODE_CLASS(OScriptNodeVariableGet) ORCHESTRATOR_REGISTER_NODE_CLASS(OScriptNodeVariableSet) ORCHESTRATOR_REGISTER_NODE_CLASS(OScriptNodeLocalVariable) diff --git a/src/script/variable.cpp b/src/script/variable.cpp index e416482a..f8ad9163 100644 --- a/src/script/variable.cpp +++ b/src/script/variable.cpp @@ -22,52 +22,141 @@ #include "common/variant_utils.h" #include "script/script.h" -void OScriptVariable::_bind_methods() +PropertyInfo _parse_classification(const String& p_classification) { - // This is read-only to avoid name changes in the inspector, which creates cache issues with the owning script - ClassDB::bind_method(D_METHOD("set_variable_name", "name"), &OScriptVariable::set_variable_name); - ClassDB::bind_method(D_METHOD("get_variable_name"), &OScriptVariable::get_variable_name); - ADD_PROPERTY( - PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), - "set_variable_name", "get_variable_name"); - - ClassDB::bind_method(D_METHOD("set_category", "category"), &OScriptVariable::set_category); - ClassDB::bind_method(D_METHOD("get_category"), &OScriptVariable::get_category); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "category"), "set_category", "get_category"); + if (p_classification.begins_with("type:")) + { + // Basic types + const String type_name = p_classification.substr(p_classification.find(":") + 1); + for (int i = 0; i < Variant::VARIANT_MAX; i++) + { + const Variant::Type type = VariantUtils::to_type(i); + if (Variant::get_type_name(type).match(type_name)) + { + PropertyInfo property; + property.type = type; + property.hint = PROPERTY_HINT_NONE; + property.hint_string = ""; + property.class_name = ""; + property.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; + + switch (type) + { + case Variant::CALLABLE: + case Variant::SIGNAL: + case Variant::RID: + property.usage |= PROPERTY_USAGE_STORAGE; + break; + case Variant::NIL: + property.usage |= (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_NIL_IS_VARIANT); + break; + default: + property.usage |= PROPERTY_USAGE_DEFAULT; + } - ClassDB::bind_method(D_METHOD("set_constant", "constant"), &OScriptVariable::set_constant); - ClassDB::bind_method(D_METHOD("is_constant"), &OScriptVariable::is_constant); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constant"), "set_constant", "is_constant"); + return property; + } + } + } + else if (p_classification.begins_with("enum:") || p_classification.begins_with("bitfield:")) + { + // Enum and Bitfields + const String name = p_classification.substr(p_classification.find(":") + 1); + const EnumInfo& info = ExtensionDB::get_global_enum(name); + if (!info.values.is_empty()) + { + PackedStringArray hints; + for (int index = 0; index < info.values.size(); ++index) + hints.push_back(info.values[index].name); - ClassDB::bind_method(D_METHOD("set_exported", "exported"), &OScriptVariable::set_exported); - ClassDB::bind_method(D_METHOD("is_exported"), &OScriptVariable::is_exported); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exported"), "set_exported", "is_exported"); + const bool is_bitfield = p_classification.begins_with("bitfield:"); - ClassDB::bind_method(D_METHOD("set_classification", "classification"), &OScriptVariable::set_classification); - ClassDB::bind_method(D_METHOD("get_classification"), &OScriptVariable::get_classification); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "classification"), "set_classification", "get_classification"); + PropertyInfo property; + property.type = Variant::INT; + property.hint_string = StringUtils::join(",", hints); + property.class_name = name; + property.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE; - const String types = VariantUtils::to_enum_list(); - ClassDB::bind_method(D_METHOD("set_variable_type", "type"), &OScriptVariable::set_variable_type); - ClassDB::bind_method(D_METHOD("get_variable_type"), &OScriptVariable::get_variable_type); - ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, types, PROPERTY_USAGE_STORAGE), "set_variable_type", "get_variable_type"); + if (is_bitfield) + { + property.hint = PROPERTY_HINT_FLAGS; + property.usage |= PROPERTY_USAGE_CLASS_IS_BITFIELD; + } + else + { + property.hint = PROPERTY_HINT_ENUM; + property.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + } - ClassDB::bind_method(D_METHOD("set_default_value", "value"), &OScriptVariable::set_default_value); - ClassDB::bind_method(D_METHOD("get_default_value"), &OScriptVariable::get_default_value); - ADD_PROPERTY(PropertyInfo(Variant::NIL, "default_value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), - "set_default_value", "get_default_value"); + return property; + } + } + else if (p_classification.begins_with("class:")) + { + // Classes + const String class_name = p_classification.substr(p_classification.find(":") + 1); - ClassDB::bind_method(D_METHOD("set_custom_value_list", "value_list"), &OScriptVariable::set_custom_value_list); - ClassDB::bind_method(D_METHOD("get_custom_value_list"), &OScriptVariable::get_custom_value_list); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_list", PROPERTY_HINT_MULTILINE_TEXT), "set_custom_value_list", "get_custom_value_list"); + PropertyInfo property; + property.type = Variant::OBJECT; + property.hint = PROPERTY_USAGE_NONE; + property.hint_string = ""; + property.class_name = class_name; + property.usage = PROPERTY_USAGE_NO_EDITOR; + + if (ClassDB::is_parent_class(class_name, "Resource")) + { + property.hint = PROPERTY_HINT_RESOURCE_TYPE; + property.hint_string = class_name; + property.class_name = ""; + property.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE; + } + else if (ClassDB::is_parent_class(class_name, "Node")) + { + property.hint = PROPERTY_HINT_NODE_TYPE; + property.hint_string = class_name; + property.class_name = class_name; + property.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE; + } + + return property; + } + else if (p_classification.begins_with("class_enum:") || p_classification.begins_with("class_bitfield:")) + { + const String class_enum_name = p_classification.substr(p_classification.find(":") + 1); + const String class_name = class_enum_name.substr(0, class_enum_name.find(".")); + const String enum_name = class_enum_name.substr(class_enum_name.find(".") + 1); + const String hint_string = StringUtils::join(",", ClassDB::class_get_enum_constants(class_name, enum_name, true)); + + PropertyInfo property; + property.type = Variant::INT; + property.hint_string = hint_string; + property.class_name = class_enum_name; - ClassDB::bind_method(D_METHOD("set_description", "description"), &OScriptVariable::set_description); - ClassDB::bind_method(D_METHOD("get_description"), &OScriptVariable::get_description); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT), "set_description", - "get_description"); + if (p_classification.begins_with("class_bitfield:")) + { + property.hint = PROPERTY_HINT_FLAGS; + property.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CLASS_IS_BITFIELD | PROPERTY_USAGE_SCRIPT_VARIABLE; + } + else + { + property.hint = PROPERTY_HINT_ENUM; + property.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_SCRIPT_VARIABLE; + } + + return property; + } + + PropertyInfo property; + property.type = Variant::NIL; + property.hint = PROPERTY_HINT_NONE; + property.hint_string = ""; + property.class_name = ""; + property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_SCRIPT_VARIABLE; + + return property; } -void OScriptVariable::_validate_property(PropertyInfo& p_property) const +void OScriptVariableBase::_validate_property(PropertyInfo& p_property) const { if (p_property.name.match("default_value")) { @@ -75,9 +164,41 @@ void OScriptVariable::_validate_property(PropertyInfo& p_property) const p_property.class_name = _info.class_name; p_property.hint = _info.hint; p_property.hint_string = _info.hint_string; - p_property.usage = _info.hint == PROPERTY_HINT_NODE_TYPE - ? PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_SCRIPT_VARIABLE - : _info.usage; + + switch (_info.hint) + { + case PROPERTY_HINT_NODE_TYPE: + p_property.usage = PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_SCRIPT_VARIABLE; + break; + default: + p_property.usage = _info.usage; + break; + }; + } + else if (p_property.name.match("constant")) + { + if (_supports_constants()) + p_property.usage = PROPERTY_USAGE_DEFAULT; + else + p_property.usage = PROPERTY_USAGE_NONE; + } + else if (p_property.name.match("exported")) + { + if (_supports_exported()) + { + p_property.usage = PROPERTY_USAGE_DEFAULT; + if (!_exportable) + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + else + p_property.usage = PROPERTY_USAGE_NONE; + } + else if (p_property.name.match("type")) + { + if (_supports_legacy_type()) + p_property.usage = PROPERTY_USAGE_STORAGE; + else + p_property.usage = PROPERTY_USAGE_NONE; } else if (p_property.name.match("value_list")) { @@ -90,22 +211,15 @@ void OScriptVariable::_validate_property(PropertyInfo& p_property) const else p_property.usage = PROPERTY_USAGE_NO_EDITOR; } - else if (p_property.name.match("exported")) - { - if (_exportable) - p_property.usage &= ~PROPERTY_USAGE_READ_ONLY; - else - p_property.usage |= PROPERTY_USAGE_READ_ONLY; - } } -bool OScriptVariable::_property_can_revert(const StringName& p_name) const +bool OScriptVariableBase::_property_can_revert(const StringName& p_name) const { static Array properties = Array::make("name", "category", "exported", "classification", "default_value", "description", "constant"); return properties.has(p_name); } -bool OScriptVariable::_property_get_revert(const StringName& p_name, Variant& r_property) +bool OScriptVariableBase::_property_get_revert(const StringName& p_name, Variant& r_property) { if (p_name.match("name")) { @@ -117,11 +231,6 @@ bool OScriptVariable::_property_get_revert(const StringName& p_name, Variant& r_ r_property = "Default"; return true; } - else if (p_name.match("exported")) - { - r_property = false; - return true; - } else if (p_name.match("classification")) { r_property = "type:bool"; @@ -137,7 +246,7 @@ bool OScriptVariable::_property_get_revert(const StringName& p_name, Variant& r_ r_property = ""; return true; } - else if (p_name.match("constant")) + else if (p_name.match("exported") || p_name.match("constant")) { r_property = false; return true; @@ -145,10 +254,24 @@ bool OScriptVariable::_property_get_revert(const StringName& p_name, Variant& r_ return false; } -bool OScriptVariable::_is_exportable_type(const PropertyInfo& p_property) const +void OScriptVariableBase::_set_variable_type(Variant::Type p_type) +{ + if (_info.type != p_type) + { + _info.type = p_type; + + if (_default_value.get_type() != _info.type) + set_default_value(VariantUtils::make_default(_info.type)); + + notify_property_list_changed(); + emit_changed(); + } +} + +bool OScriptVariableBase::_is_exportable_type(const PropertyInfo& p_property) const { - // Constants cannot be exported - if (_constant) + // If the variable doesn't support the export feature or is a constant, cannot be exported + if (!_supports_exported() || is_constant()) return false; switch (p_property.type) @@ -159,14 +282,11 @@ bool OScriptVariable::_is_exportable_type(const PropertyInfo& p_property) const case Variant::RID: return false; - // Object has specific circumstances depending on hint string + // Object has specific use cases case Variant::OBJECT: { - if (p_property.hint_string.is_empty()) - return false; - - if (!ClassDB::is_parent_class(p_property.hint_string, "Node") - && !ClassDB::is_parent_class(p_property.hint_string, "Resource")) + const String& hint = p_property.hint_string; + if (hint.is_empty() || (!ClassDB::is_parent_class(hint, "Node") && !ClassDB::is_parent_class(hint, "Resource"))) return false; break; @@ -175,36 +295,32 @@ bool OScriptVariable::_is_exportable_type(const PropertyInfo& p_property) const default: break; } - return true; -} -bool OScriptVariable::_convert_default_value(Variant::Type p_new_type) -{ - set_default_value(VariantUtils::convert(get_default_value(), p_new_type)); return true; } -OScriptVariable::OScriptVariable() -{ - _info.type = Variant::NIL; -} - -void OScriptVariable::post_initialize() +void OScriptVariableBase::post_initialize() { if (_classification.is_empty()) { - // Prepares the OScriptVariable for the variable system v2 using classifications - _exportable = _is_exportable_type(_info); + if (_supports_exported()) + _exportable = _is_exportable_type(_info); + _classification = vformat("type:%s", Variant::get_type_name(_info.type)); } } -Orchestration* OScriptVariable::get_orchestration() const +bool OScriptVariableBase::is_grouped_by_category() const { - return _orchestration; + return !_category.is_empty() && !_category.to_lower().match("default") && !_category.to_lower().match("none"); +} + +String OScriptVariableBase::get_variable_type_name() const +{ + return PropertyUtils::get_property_type_name(_info); } -void OScriptVariable::set_variable_name(const String& p_name) +void OScriptVariableBase::set_variable_name(const String& p_name) { if (!_info.name.match(p_name)) { @@ -213,12 +329,16 @@ void OScriptVariable::set_variable_name(const String& p_name) } } -bool OScriptVariable::is_grouped_by_category() const +void OScriptVariableBase::set_description(const String& p_description) { - return !_category.is_empty() && !_category.to_lower().match("default") && !_category.to_lower().match("none"); + if (!_description.match(p_description)) + { + _description = p_description; + emit_changed(); + } } -void OScriptVariable::set_category(const String& p_category) +void OScriptVariableBase::set_category(const String& p_category) { if (!_category.match(p_category)) { @@ -227,200 +347,122 @@ void OScriptVariable::set_category(const String& p_category) } } -void OScriptVariable::set_classification(const String& p_classification) +void OScriptVariableBase::set_default_value(const Variant& p_value) { - if (!_classification.match(p_classification)) + if (_default_value != p_value) { - _classification = p_classification; + _default_value = p_value; + emit_changed(); + } +} - if (_classification.contains(":")) - { - if (_classification.begins_with("type:")) - { - // basic types - const String type_name = _classification.substr(_classification.find(":") + 1); - for (int i = 0; i < Variant::VARIANT_MAX; i++) - { - const Variant::Type type = VariantUtils::to_type(i); - if (Variant::get_type_name(type).match(type_name)) - { - if (_info.type != type) - _convert_default_value(type); - - _info.type = type; - _info.hint = PROPERTY_HINT_NONE; - _info.hint_string = ""; - _info.class_name = ""; - - // These cannot have default values - if (type == Variant::CALLABLE || type == Variant::SIGNAL || type == Variant::RID || type == Variant::NIL) - _info.usage = PROPERTY_USAGE_STORAGE; - else - _info.usage = PROPERTY_USAGE_DEFAULT; - - if (type == Variant::NIL) - _info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; +void OScriptVariableBase::set_classification(const String& p_classification) +{ + if (!_classification.match(p_classification) && p_classification.contains(":")) + { + _classification = p_classification; - break; - } - } - } - else if (_classification.begins_with("enum:") || _classification.begins_with("bitfield:")) - { - // enum/bitfields - const String name = _classification.substr(_classification.find(":") + 1); - const EnumInfo& enum_info = ExtensionDB::get_global_enum(name); - if (!enum_info.values.is_empty()) - { - PackedStringArray hints; - for (int i = 0; i < enum_info.values.size(); i++) - hints.push_back(enum_info.values[i].name); - - _info.type = Variant::INT; - _info.hint = _classification.begins_with("bitfield:") ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; - _info.hint_string = StringUtils::join(",", hints); - _info.class_name = name; - if (_classification.begins_with("bitfield:")) - _info.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CLASS_IS_BITFIELD; - else - _info.usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CLASS_IS_ENUM; - } - } - else if (_classification.begins_with("class:")) - { - // class type - const String class_name = _classification.substr(_classification.find(":") + 1); - if (ClassDB::is_parent_class(class_name, "Resource")) - { - _info.type = Variant::OBJECT; - _info.hint = PROPERTY_HINT_RESOURCE_TYPE; - _info.hint_string = class_name; - _info.class_name = ""; - _info.usage = PROPERTY_USAGE_DEFAULT; - } - else if (ClassDB::is_parent_class(class_name, "Node")) - { - _info.type = Variant::OBJECT; - _info.hint = PROPERTY_HINT_NODE_TYPE; - _info.hint_string = class_name; - _info.class_name = class_name; - _info.usage = PROPERTY_USAGE_DEFAULT; - } - else - { - _info.type = Variant::OBJECT; - _info.hint = PROPERTY_HINT_NONE; - _info.hint_string = ""; - _info.class_name = class_name; - _info.usage = PROPERTY_USAGE_NO_EDITOR; - } - } - else if (_classification.begins_with("class_enum:") || _classification.begins_with("class_bitfield:")) - { - const String class_enum_name = _classification.substr(_classification.find(":") + 1); - const String class_name = class_enum_name.substr(0, class_enum_name.find(".")); - const String enum_name = class_enum_name.substr(class_enum_name.find(".") + 1); - const String hint_string = StringUtils::join(",", ClassDB::class_get_enum_constants(class_name, enum_name, true)); - const bool bitfield = _classification.begins_with("class_bitfield:"); - - _info.type = Variant::INT; - _info.hint = bitfield ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; - _info.hint_string = hint_string; - _info.class_name = class_enum_name; - _info.usage = PROPERTY_USAGE_DEFAULT | (bitfield ? PROPERTY_USAGE_CLASS_IS_BITFIELD : PROPERTY_USAGE_CLASS_IS_ENUM); - } - else if (_classification.begins_with("custom_enum:") || _classification.begins_with("custom_bitfield:")) - { - const bool bitfield = _classification.begins_with("custom_bitfield:"); - _info.type = Variant::INT; - _info.hint = bitfield ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; - _info.hint_string = ""; - _info.class_name = ""; - _info.usage = PROPERTY_USAGE_NO_EDITOR; - } - } - else + const PropertyInfo property = _parse_classification(_classification); + if (property.hint == PROPERTY_USAGE_NONE && property.hint_string.is_empty() && property.class_name.is_empty()) { - _info.type = Variant::NIL; - _info.hint = PROPERTY_HINT_NONE; - _info.hint_string = ""; - _info.class_name = ""; - _info.usage = PROPERTY_USAGE_NO_EDITOR; // no default value + // Basic type + if (_info.type != property.type) + set_default_value(VariantUtils::convert(get_default_value(), property.type)); } + _info.type = property.type; + _info.hint = property.hint; + _info.hint_string = property.hint_string; + _info.class_name = property.class_name; + _info.usage = property.usage; + _exportable = _is_exportable_type(_info); - _info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; notify_property_list_changed(); emit_changed(); } } -void OScriptVariable::set_custom_value_list(const String& p_value_list) +void OScriptVariableBase::set_custom_value_list(const String& p_custom_value_list) { - if (!_value_list.match(p_value_list)) + if (!_custom_value_list.match(p_custom_value_list)) { - _value_list = p_value_list; + _custom_value_list = p_custom_value_list; emit_changed(); } } -void OScriptVariable::set_variable_type(const Variant::Type p_type) +void OScriptVariableBase::set_constant(bool p_constant) { - if (_info.type != p_type) + if (_constant != p_constant) { - _info.type = p_type; - - if (_default_value.get_type() != _info.type) - set_default_value(VariantUtils::make_default(_info.type)); + ERR_FAIL_COND_MSG(!_supports_constants(), "Variable does not support the constant feature"); - emit_changed(); - notify_property_list_changed(); - } -} + _constant = p_constant; -String OScriptVariable::get_variable_type_name() const -{ - return PropertyUtils::get_property_type_name(_info); -} + _exportable = _is_exportable_type(_info); + if (!_exportable && _constant) + _exported = false; -void OScriptVariable::set_description(const String& p_description) -{ - if (_description != p_description) - { - _description = p_description; + notify_property_list_changed(); + emit_changed(); } } -void OScriptVariable::set_exported(bool p_exported) +void OScriptVariableBase::set_exported(bool p_exported) { if (_exported != p_exported) { + ERR_FAIL_COND_MSG(!_supports_exported(), "Variable does not support the exported feature."); + _exported = p_exported; emit_changed(); } } -void OScriptVariable::set_default_value(const Variant& p_default_value) +bool OScriptVariableBase::supports_validated_getter() const { - if (_default_value != p_default_value) - { - _default_value = p_default_value; - emit_changed(); - } + return _info.type == Variant::OBJECT; } -void OScriptVariable::set_constant(bool p_constant) +void OScriptVariableBase::_bind_methods() { - if (_constant != p_constant) - { - _constant = p_constant; + // Name is read-only in the inspector + // Renaming of variables should be done via the component panel only. + ClassDB::bind_method(D_METHOD("set_variable_name", "name"), &OScriptVariableBase::set_variable_name); + ClassDB::bind_method(D_METHOD("get_variable_name"), &OScriptVariableBase::get_variable_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_variable_name", "get_variable_name"); + + ClassDB::bind_method(D_METHOD("set_category", "category"), &OScriptVariableBase::set_category); + ClassDB::bind_method(D_METHOD("get_category"), &OScriptVariableBase::get_category); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "category"), "set_category", "get_category"); - _exportable = _is_exportable_type(_info); - if (!_exportable && _constant) - _exported = false; + ClassDB::bind_method(D_METHOD("set_constant", "constant"), &OScriptVariableBase::set_constant); + ClassDB::bind_method(D_METHOD("is_constant"), &OScriptVariableBase::is_constant); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constant"), "set_constant", "is_constant"); - notify_property_list_changed(); - emit_changed(); - } + ClassDB::bind_method(D_METHOD("set_exported", "exported"), &OScriptVariableBase::set_exported); + ClassDB::bind_method(D_METHOD("is_exported"), &OScriptVariableBase::is_exported); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exported"), "set_exported", "is_exported"); + + ClassDB::bind_method(D_METHOD("set_classification", "classification"), &OScriptVariableBase::set_classification); + ClassDB::bind_method(D_METHOD("get_classification"), &OScriptVariableBase::get_classification); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "classification"), "set_classification", "get_classification"); + + const String types = VariantUtils::to_enum_list(); + ClassDB::bind_method(D_METHOD("_set_variable_type", "type"), &OScriptVariableBase::_set_variable_type); + ClassDB::bind_method(D_METHOD("_get_variable_type"), &OScriptVariableBase::_get_variable_type); + ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, types, PROPERTY_USAGE_STORAGE), "_set_variable_type", "_get_variable_type"); + + ClassDB::bind_method(D_METHOD("set_default_value", "value"), &OScriptVariableBase::set_default_value); + ClassDB::bind_method(D_METHOD("get_default_value"), &OScriptVariableBase::get_default_value); + ADD_PROPERTY(PropertyInfo(Variant::NIL, "default_value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_default_value", "get_default_value"); + + ClassDB::bind_method(D_METHOD("set_custom_value_list", "value_list"), &OScriptVariableBase::set_custom_value_list); + ClassDB::bind_method(D_METHOD("get_custom_value_list"), &OScriptVariableBase::get_custom_value_list); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_list", PROPERTY_HINT_MULTILINE_TEXT), "set_custom_value_list", "get_custom_value_list"); + + ClassDB::bind_method(D_METHOD("set_description", "description"), &OScriptVariableBase::set_description); + ClassDB::bind_method(D_METHOD("get_description"), &OScriptVariableBase::get_description); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT), "set_description", "get_description"); } diff --git a/src/script/variable.h b/src/script/variable.h index 54e2649e..c2522f56 100644 --- a/src/script/variable.h +++ b/src/script/variable.h @@ -23,152 +23,185 @@ using namespace godot; /// Forward declarations class Orchestration; +class OScriptFunction; -/// Defines a script variable -/// -/// Variables are defined as resources which provides multiple benefits. First, it allows -/// us to use Godot's serialization technique for them as embedded elements in the script -/// while also exposing the directly in the Editor's InspectorDock. -/// -class OScriptVariable : public Resource +/// The base implementation for all variable types (orchestration and local function variables) +class OScriptVariableBase : public Resource { - friend class Orchestration; - - GDCLASS(OScriptVariable, Resource); - + GDCLASS(OScriptVariableBase, Resource); static void _bind_methods(); - Orchestration* _orchestration{ nullptr }; //! The owning orchestration - PropertyInfo _info; //! Basic property details - Variant _default_value; //! Optional defined default value - String _description; //! An optional description for the variable - String _category; //! Category for variables - bool _exported{ false }; //! Whether the variable is exposed on the node - bool _exportable{ false }; //! Tracks whether the variable can be exported - String _classification; //! Variable classification - int _type_category{ 0 }; //! Defaults to basic - Variant _type_subcategory; //! Subcategory type - String _value_list; //! Enum/Bitfield custom value list - bool _constant{ false }; //! Whether variable is a constant - protected: + PropertyInfo _info; //! Variable property details + String _description; //! Description + String _category; //! Category + Variant _default_value; //! Default value + String _classification; //! Variable type classification + bool _constant{ false }; //! Is variable a constant + bool _exportable{ false }; //! Whether the variable can be exported + bool _exported{ false }; //! Is variable exported + String _custom_value_list; //! Custom value list for enum/bitfields + //~ Begin Wrapped Interface void _validate_property(PropertyInfo& p_property) const; bool _property_can_revert(const StringName& p_name) const; bool _property_get_revert(const StringName& p_name, Variant& r_property); //~ End Wrapped Interface - /// Get whether the specified property is exportable. - /// @param p_property the property - /// @return true if the property can be exported, false otherwise - bool _is_exportable_type(const PropertyInfo& p_property) const; + /// Whether the variable type supports constants + virtual bool _supports_constants() const { return false; } + + /// Whether the variable supports exporting + virtual bool _supports_exported() const { return false; } - /// Attempt to convert the default value to the new type - /// @return true if the conversion was successful, false otherwise - bool _convert_default_value(Variant::Type p_new_type); + /// Whether the variable supports legacy "type" attributes + virtual bool _supports_legacy_type() const { return false; } - /// Constructor - /// Intentionally protected, variables created via an Orchestration - OScriptVariable(); + /// Gets the variable type, if supported + /// @return the variable type + Variant::Type _get_variable_type() const { return _info.type; } + + /// Sets the variable type, if supported + /// @param p_type the variable type + void _set_variable_type(Variant::Type p_type); + + /// Returns whether the property definition is an exportable type + /// @param p_property the property to check if it can be exported + /// @return true if the property can be marked as exported, false otherwise + virtual bool _is_exportable_type(const PropertyInfo& p_property) const; + + // Abstract class, cannot be constructed + OScriptVariableBase() = default; public: - /// Performs post resource initialization. - /// This is used to align and fix-up state across versions. - void post_initialize(); + /// Perform post initialization steps after orchestration is loaded + virtual void post_initialize(); - /// Get a reference to the orchestration that owns this variable. - /// @return the owning orchestration reference, should always be valid - Orchestration* get_orchestration() const; + /// Returns if the category name should be used in grouping. + /// @return true if the category should apply grouping, false otherwise + bool is_grouped_by_category() const; - /// Get the variable's PropertyInfo structure + /// Get the variable's property details /// @return the property info const PropertyInfo& get_info() const { return _info; } - /// Get the variable's name + /// Get the variable type + /// @return the variable type + [[deprecated("Use get_info().type instead")]] + Variant::Type get_variable_type() const + { + return _info.type; + } + + /// Get the variable type name + /// @return variable type name + String get_variable_type_name() const; + + /// Get the variable name /// @return the variable's name String get_variable_name() const { return _info.name; } - /// Set the variable's name - /// @param p_name the variable name + /// Set the variable name + /// @param p_name the new variable name void set_variable_name(const String& p_name); - /// Returns whether the category name should be used in grouping. - /// This centralizes the logic as using "Default", "None", or an empty string will validate - /// at not being grouped where-as any other value will. - /// @return true if the variable should be categorized, false to show it uncategorized. - bool is_grouped_by_category() const; + /// Get the variable description + /// @return the description + String get_description() const { return _description; } + + /// Set the variable's description + /// @param p_description the variable's description + void set_description(const String& p_description); /// Get the variable's category - /// @return the category for the variable + /// @return the category String get_category() const { return _category; } - /// Sets the category - /// @param p_category the variable's category + /// Set the variable's category + /// @param p_category the category void set_category(const String& p_category); - /// Get the variable's type - /// @return the variable type + /// Get the variable's default value + /// @return the default value + Variant get_default_value() const { return _default_value; } + + /// Sets the default value for the variable + /// @param p_value the default value to be set + void set_default_value(const Variant& p_value); + + /// Get the variable type classification + /// @return the classification String get_classification() const { return _classification; } - /// Set the variable's type - /// @param p_classification the variable type + /// Sets the variable type's classification + /// @param p_classification the type classification void set_classification(const String& p_classification); - /// Get the custom value list for enum/bitfields - /// @return the custom value list - String get_custom_value_list() const { return _value_list; } + /// Gets the custom value list + /// @return the custom value list for enum/bitfields + String get_custom_value_list() const { return _custom_value_list; } - /// Sets the custom value list for enum/bitfields - /// @param p_value_list the custom value list - void set_custom_value_list(const String& p_value_list); + /// Sets the custom value list, used for custom enums/bitfields + /// @param p_custom_value_list + void set_custom_value_list(const String& p_custom_value_list); - /// Get the variable type - /// @return the variable type - Variant::Type get_variable_type() const { return _info.type; } - - /// Set the variable type - /// @param p_type the variable type - void set_variable_type(Variant::Type p_type); - - /// Get the variable type name - /// @return the variable type name - String get_variable_type_name() const; + /// Get whether the variable is a constant + /// @return whether the variable is read-only, constant + bool is_constant() const { return _constant; } - /// Get the variable's description - /// @return description for the variable - String get_description() const { return _description; } + /// Sets whether the variable is a constant + /// @param p_constant whether the variable is a constant + void set_constant(bool p_constant); - /// Set the variable's description - /// @param p_description the description - void set_description(const String& p_description); + /// Get whether the variable can be exported + /// @return true if the variable can be exported, false otherwise + bool is_exportable() const { return _exportable; } - /// Is the variable to be exported - /// @return true if the variable is exported, false otherwise + /// Get whether the variable is exported + /// @return whether the variable is exported bool is_exported() const { return _exported; } - /// Set the variable as exported. - /// @param p_exported true exports the variable, false otherwise + /// Set whether the variable is exported + /// @param p_exported is the variable exported void set_exported(bool p_exported); - /// Is the variable exportable. - /// @return true if the variable can be exported, false otherwise - bool is_exportable() const { return _is_exportable_type(_info); } + /// Check whether the variable supports validated getters + /// @return true if validated getters are allowed, false otherwise + bool supports_validated_getter() const; +}; - /// Get the default value for the variable, if one is defined - /// @return variable's default value - Variant get_default_value() const { return _default_value; } +/// Variable implementation for function local variables +class OScriptLocalVariable : public OScriptVariableBase +{ + friend class OScriptFunction; - /// Set the variable's default value - /// @param p_default_value the default value - void set_default_value(const Variant& p_default_value); + GDCLASS(OScriptLocalVariable, OScriptVariableBase); + static void _bind_methods() { } - /// Return whether the variable is a constant value - /// @return true if the variable is a constant and cannot be set, false otherwise - bool is_constant() const { return _constant; } +protected: + /// Intentionally protected, created via OScriptFunction + OScriptLocalVariable() = default; +}; - /// Sets whether the variable is a constant - /// @param p_constant whether the variable is constant - void set_constant(bool p_constant); +/// Defines a top-level script variable that can be exported and available to the outside world. +class OScriptVariable : public OScriptVariableBase +{ + friend class Orchestration; + + GDCLASS(OScriptVariable, OScriptVariableBase); + static void _bind_methods() { } + +protected: + Orchestration* _orchestration{ nullptr }; //! Owning orchestration + + //~ Begin OScriptVariableBase Interface + bool _supports_constants() const override { return true; } + bool _supports_exported() const override { return true; } + bool _supports_legacy_type() const override { return true; } + //~ End OScriptVariableBase Interface + + /// Intentionally protected, created via OScriptFunction + OScriptVariable() = default; }; #endif // ORCHESTRATOR_SCRIPT_VARIABLE_H diff --git a/src/script/vm/execution_context.h b/src/script/vm/execution_context.h index 448956cf..a20eb7c0 100644 --- a/src/script/vm/execution_context.h +++ b/src/script/vm/execution_context.h @@ -91,6 +91,8 @@ class OScriptExecutionContext GDExtensionCallError* _error{ nullptr }; //! The call error reference String _error_reason; //! The error reason + void* _function{ nullptr }; //! Current executing function + /// Initialize the variant stack using memnew_placement void _initialize_variant_stack(); @@ -180,6 +182,10 @@ class OScriptExecutionContext /// @return the owning virtual machine _FORCE_INLINE_ OScriptVirtualMachine* get_runtime() { return _instance; } + /// Get the current executing function + /// @return the function pointer + _FORCE_INLINE_ void* get_function() const { return _function; } + /// Gets the owner object, typically the owner of the virtual machine. /// @return the owner object, should never be null. Object* get_owner(); diff --git a/src/script/vm/script_vm.cpp b/src/script/vm/script_vm.cpp index 58b53c39..18126885 100644 --- a/src/script/vm/script_vm.cpp +++ b/src/script/vm/script_vm.cpp @@ -20,6 +20,8 @@ #include "orchestration/orchestration.h" #include "script/instances/node_instance.h" #include "script/nodes/variables/local_variable.h" +#include "script/nodes/variables/variable_get.h" +#include "script/nodes/variables/variable_set.h" #include "script/vm/script_state.h" #include @@ -692,6 +694,7 @@ void OScriptVirtualMachine::_call_method_internal(const StringName& p_method, OS context._step_mode = OScriptNodeInstance::StepMode::STEP_MODE_BEGIN; context._error = &r_err; context._current_node_id = p_function->node; + context._function = p_function; OScriptNodeInstance* node = p_instance; int node_port = 0; // always assumes 0 for now @@ -1008,6 +1011,10 @@ bool OScriptVirtualMachine::register_function(const Ref& p_func // Calculate the maximum number of input arguments based on the function definition. _max_inputs = Math::max(_max_inputs, function.argument_count); + // Populate the function's local variables + for (const Ref& local : p_function->get_local_variables()) + function._variables[local->get_variable_name()] = local->get_default_value(); + // Initialize the function's node graph HashMap local_variable_indices; _build_function_node_graph(p_function, function, local_variable_indices); diff --git a/src/script/vm/script_vm.h b/src/script/vm/script_vm.h index 204343f4..da487cb0 100644 --- a/src/script/vm/script_vm.h +++ b/src/script/vm/script_vm.h @@ -56,6 +56,7 @@ class OScriptVirtualMachine int pass_stack_size{ 0 }; //! Pass stack size int node_count{ 0 }; //! Number of nodes in the function's graph int argument_count{ 0 }; //! Number of function arguments + HashMap _variables; //! Function local variables OScriptNodeInstance* instance{ nullptr }; //! Cached instance of the node that starts this function };