diff --git a/core/string/node_path.h b/core/string/node_path.h index 53976bd52472..3750c36143d7 100644 --- a/core/string/node_path.h +++ b/core/string/node_path.h @@ -34,6 +34,8 @@ #include "core/string/string_name.h" #include "core/string/ustring.h" +#define UNIQUE_NODE_PREFIX "%" + class NodePath { struct Data { SafeRefCount refcount; diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 2e941b80379f..a809bb889b00 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -31,6 +31,7 @@ #include "string_name.h" #include "core/os/os.h" +#include "core/string/node_path.h" #include "core/string/print_string.h" StaticCString StaticCString::create(const char *p_ptr) { @@ -240,6 +241,9 @@ StringName::StringName(const char *p_name, bool p_static) { _data = memnew(_Data); _data->name = p_name; + if (_data->name != String() && (char32_t)_data->name[0] == (char32_t)UNIQUE_NODE_PREFIX[0]) { + _data->node_unique_name = true; + } _data->refcount.init(); _data->static_count.set(p_static ? 1 : 0); _data->hash = hash; @@ -247,6 +251,7 @@ StringName::StringName(const char *p_name, bool p_static) { _data->cname = nullptr; _data->next = _table[idx]; _data->prev = nullptr; + #ifdef DEBUG_ENABLED if (unlikely(debug_stringname)) { // Keep in memory, force static. @@ -305,6 +310,9 @@ StringName::StringName(const StaticCString &p_static_string, bool p_static) { _data->hash = hash; _data->idx = idx; _data->cname = p_static_string.ptr; + if (_data->cname && (char32_t)_data->cname[0] == (char32_t)UNIQUE_NODE_PREFIX[0]) { + _data->node_unique_name = true; + } _data->next = _table[idx]; _data->prev = nullptr; #ifdef DEBUG_ENABLED @@ -360,6 +368,9 @@ StringName::StringName(const String &p_name, bool p_static) { _data = memnew(_Data); _data->name = p_name; + if (_data->name != String() && (char32_t)_data->name[0] == (char32_t)UNIQUE_NODE_PREFIX[0]) { + _data->node_unique_name = true; + } _data->refcount.init(); _data->static_count.set(p_static ? 1 : 0); _data->hash = hash; diff --git a/core/string/string_name.h b/core/string/string_name.h index f4233854ac85..60759192b98e 100644 --- a/core/string/string_name.h +++ b/core/string/string_name.h @@ -54,6 +54,7 @@ class StringName { SafeNumeric static_count; const char *cname = nullptr; String name; + bool node_unique_name = false; #ifdef DEBUG_ENABLED uint32_t debug_references = 0; #endif @@ -100,6 +101,10 @@ class StringName { bool operator==(const String &p_name) const; bool operator==(const char *p_name) const; bool operator!=(const String &p_name) const; + + _FORCE_INLINE_ bool is_node_unique_name() const { + return _data && _data->node_unique_name; + } _FORCE_INLINE_ bool operator<(const StringName &p_name) const { return _data < p_name._data; } diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 7cfd34b53ed7..fc2c822c2337 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -34,12 +34,12 @@ #include "core/math/color.h" #include "core/math/math_funcs.h" #include "core/os/memory.h" +#include "core/string/node_path.h" #include "core/string/print_string.h" #include "core/string/translation.h" #include "core/string/ucaps.h" #include "core/variant/variant.h" #include "core/version_generated.gen.h" - #include #include #include @@ -4357,7 +4357,7 @@ String String::property_name_encode() const { } // Changes made to the set of invalid characters must also be reflected in the String documentation. -const String String::invalid_node_name_characters = ". : @ / \""; +const String String::invalid_node_name_characters = ". : @ / \" " UNIQUE_NODE_PREFIX; String String::validate_node_name() const { Vector chars = String::invalid_node_name_characters.split(" "); diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 7079036879b2..b1950a8d397f 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -492,6 +492,18 @@ Returns [code]true[/code] if the node is processing unhandled key input (see [method set_process_unhandled_key_input]). + + + + Sets whether the node is an unique name for all the other nodes owned by this owner (owner most of the time means that scene this node belongs to). + + + + + + Return whether this node is not only an unique name, but also whether it's valid. If more than one node is set to this unique name, this function will return false. + + @@ -719,6 +731,13 @@ Sets whether this is an instance load placeholder. See [InstancePlaceholder]. + + + + + Set this name as the unique name in the owner (most of the time, the scene it belongs to). This allows the node being accessed as "%Name" instead of the full path from any node within that scene. + + diff --git a/editor/icons/SceneUniqueName.svg b/editor/icons/SceneUniqueName.svg new file mode 100644 index 000000000000..34279a14a696 --- /dev/null +++ b/editor/icons/SceneUniqueName.svg @@ -0,0 +1 @@ + diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 03f65cdf527b..8bcfac3ea185 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1061,6 +1061,23 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } } } break; + case TOOL_TOGGLE_SCENE_UNIQUE: { + List selection = editor_selection->get_selected_node_list(); + List::Element *e = selection.front(); + if (e) { + UndoRedo *undo_redo = &editor_data->get_undo_redo(); + Node *node = e->get(); + bool enabled = node->is_unique_name_in_owner(); + if (!enabled) { + undo_redo->create_action(TTR("Enable Scene Unique Name")); + } else { + undo_redo->create_action(TTR("Disable Scene Unique Name")); + } + undo_redo->add_do_method(node, "set_unique_name_in_owner", !enabled); + undo_redo->add_undo_method(node, "set_unique_name_in_owner", enabled); + undo_redo->commit_action(); + } + } break; case TOOL_CREATE_2D_SCENE: case TOOL_CREATE_3D_SCENE: case TOOL_CREATE_USER_INTERFACE: @@ -2774,11 +2791,19 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_separator(); menu->add_icon_shortcut(get_theme_icon(SNAME("CreateNewSceneFrom"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/save_branch_as_scene"), TOOL_NEW_SCENE_FROM); } + if (full_selection.size() == 1) { menu->add_separator(); menu->add_icon_shortcut(get_theme_icon(SNAME("CopyNodePath"), SNAME("EditorIcons")), ED_GET_SHORTCUT("scene_tree/copy_node_path"), TOOL_COPY_NODE_PATH); } + if (selection[0]->get_owner() == EditorNode::get_singleton()->get_edited_scene()) { + // Only for nodes owned by the edited scene root. + menu->add_separator(); + menu->add_icon_check_item(get_theme_icon(SNAME("SceneUniqueName"), SNAME("EditorIcons")), TTR("Access as Scene Unique Name"), TOOL_TOGGLE_SCENE_UNIQUE); + menu->set_item_checked(menu->get_item_index(TOOL_TOGGLE_SCENE_UNIQUE), selection[0]->is_unique_name_in_owner()); + } + bool is_external = (!selection[0]->get_scene_file_path().is_empty()); if (is_external) { bool is_inherited = selection[0]->get_scene_inherited_state() != nullptr; diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 599fb01203d0..45af7a24afff 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -91,7 +91,7 @@ class SceneTreeDock : public VBoxContainer { TOOL_SCENE_CLEAR_INHERITANCE, TOOL_SCENE_CLEAR_INHERITANCE_CONFIRM, TOOL_SCENE_OPEN_INHERITED, - + TOOL_TOGGLE_SCENE_UNIQUE, TOOL_CREATE_2D_SCENE, TOOL_CREATE_3D_SCENE, TOOL_CREATE_USER_INTERFACE, diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 44eb5c670d3f..82aae57e2e99 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -148,6 +148,14 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i TabContainer *tab_container = Object::cast_to(NodeDock::get_singleton()->get_parent()); NodeDock::get_singleton()->get_parent()->call("set_current_tab", tab_container->get_tab_idx_from_control(NodeDock::get_singleton())); NodeDock::get_singleton()->show_groups(); + } else if (p_id == BUTTON_UNIQUE) { + print_line("disable unique?"); + undo_redo->create_action(TTR("Disable unique node")); + undo_redo->add_do_method(n, "set_unique_name_in_owner", false); + undo_redo->add_undo_method(n, "set_unique_name_in_owner", true); + undo_redo->add_do_method(this, "_update_tree"); + undo_redo->add_undo_method(this, "_update_tree"); + undo_redo->commit_action(); } } @@ -260,6 +268,10 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll item->add_button(0, get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons")), BUTTON_WARNING, false, TTR("Node configuration warning:") + "\n" + warning); } + if (p_node->is_unique_name_in_owner()) { + item->add_button(0, get_theme_icon(SNAME("SceneUniqueName"), SNAME("EditorIcons")), BUTTON_UNIQUE, false, vformat(TTR("This node can be accessed from within anywhere in the scene by preceding it by the '%s' prefix in a node path.\nClick to disable this."), UNIQUE_NODE_PREFIX)); + } + int num_connections = p_node->get_persistent_signal_connection_count(); int num_groups = p_node->get_persistent_group_count(); diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index 547a5b57cacb..3d88081ab1c4 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -53,6 +53,7 @@ class SceneTreeEditor : public Control { BUTTON_SIGNALS = 6, BUTTON_GROUPS = 7, BUTTON_PIN = 8, + BUTTON_UNIQUE = 9, }; Tree *tree = nullptr; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 34bb1cde05ae..728fbf17c761 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -257,6 +257,9 @@ void Node::_propagate_after_exit_tree() { } if (!found) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.owner = nullptr; } @@ -1303,6 +1306,24 @@ Node *Node::get_node_or_null(const NodePath &p_path) const { next = root; } + } else if (name.is_node_unique_name()) { + if (current->data.owned_unique_nodes.size()) { + // Has unique nodes in ownership + Node **unique = current->data.owned_unique_nodes.getptr(name); + if (!unique) { + return nullptr; + } + next = *unique; + } else if (current->data.owner) { + Node **unique = current->data.owner->data.owned_unique_nodes.getptr(name); + if (!unique) { + return nullptr; + } + next = *unique; + } else { + return nullptr; + } + } else { next = nullptr; @@ -1498,8 +1519,62 @@ void Node::_set_owner_nocheck(Node *p_owner) { data.OW = data.owner->data.owned.back(); } +void Node::_release_unique_name_in_owner() { + StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + if (which == nullptr || *which != this) { + return; // Ignore. + } + data.owner->data.owned_unique_nodes.erase(key); +} + +void Node::_acquire_unique_name_in_owner() { + StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + if (which != nullptr && *which != this) { + if (!Engine::get_singleton()->is_editor_hint()) { + WARN_PRINT(vformat(RTR("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'."), get_name(), get_path(), (*which)->get_path())); + } + return; // Ignore. + } + data.owner->data.owned_unique_nodes[key] = this; +} + +void Node::set_unique_name_in_owner(bool p_enabled) { + if (data.unique_name_in_owner == p_enabled) { + return; + } + + if (data.unique_name_in_owner && data.owner != nullptr) { + _release_unique_name_in_owner(); + } + data.unique_name_in_owner = p_enabled; + + if (data.unique_name_in_owner && data.owner != nullptr) { + _acquire_unique_name_in_owner(); + } + + update_configuration_warnings(); +} + +bool Node::is_unique_name_in_owner() const { + return data.unique_name_in_owner; +} + +bool Node::is_valid_unique_name_in_owner() const { + if (!data.unique_name_in_owner) { + return false; + } + StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + return (which != nullptr && *which == this); +} + void Node::set_owner(Node *p_owner) { if (data.owner) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.OW = nullptr; data.owner = nullptr; @@ -1526,6 +1601,10 @@ void Node::set_owner(Node *p_owner) { ERR_FAIL_COND(!owner_valid); _set_owner_nocheck(p_owner); + + if (data.unique_name_in_owner) { + _acquire_unique_name_in_owner(); + } } Node *Node::get_owner() const { @@ -2585,16 +2664,19 @@ void Node::clear_internal_tree_resource_paths() { } TypedArray Node::get_configuration_warnings() const { + TypedArray ret; + if (is_unique_name_in_owner() && !is_valid_unique_name_in_owner()) { + ret.push_back(RTR("Duplicate unique name (will be ignored in paths)")); + } + Vector warnings; if (GDVIRTUAL_CALL(_get_configuration_warnings, warnings)) { - TypedArray ret; - ret.resize(warnings.size()); for (int i = 0; i < warnings.size(); i++) { - ret[i] = warnings[i]; + ret.push_back(warnings[i]); } - return ret; } - return Array(); + + return ret; } String Node::get_configuration_warnings_as_string() const { @@ -2790,6 +2872,12 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_import_path", "import_path"), &Node::set_import_path); ClassDB::bind_method(D_METHOD("_get_import_path"), &Node::get_import_path); + ClassDB::bind_method(D_METHOD("set_unique_name_in_owner", "enable"), &Node::set_unique_name_in_owner); + ClassDB::bind_method(D_METHOD("is_unique_name_in_owner"), &Node::is_unique_name_in_owner); + ClassDB::bind_method(D_METHOD("is_valid_unique_name_in_owner"), &Node::is_valid_unique_name_in_owner); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_unique_name_in_owner", "is_unique_name_in_owner"); + #ifdef TOOLS_ENABLED ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned); #endif diff --git a/scene/main/node.h b/scene/main/node.h index 57b150e29a9e..718b868ffbcb 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -33,6 +33,7 @@ #include "core/string/node_path.h" #include "core/templates/map.h" +#include "core/templates/oa_hash_map.h" #include "core/variant/typed_array.h" #include "scene/main/scene_tree.h" @@ -99,6 +100,9 @@ class Node : public Object { Node *parent = nullptr; Node *owner = nullptr; Vector children; + HashMap owned_unique_nodes; + bool unique_name_in_owner = false; + int internal_children_front = 0; int internal_children_back = 0; int pos = -1; @@ -193,6 +197,9 @@ class Node : public Object { _FORCE_INLINE_ bool _can_process(bool p_paused) const; _FORCE_INLINE_ bool _is_enabled() const; + void _release_unique_name_in_owner(); + void _acquire_unique_name_in_owner(); + protected: void _block() { data.blocked++; } void _unblock() { data.blocked--; } @@ -345,6 +352,10 @@ class Node : public Object { Node *get_owner() const; void get_owned_by(Node *p_by, List *p_owned); + void set_unique_name_in_owner(bool p_enabled); + bool is_unique_name_in_owner() const; + bool is_valid_unique_name_in_owner() const; + void remove_and_skip(); int get_index(bool p_include_internal = true) const; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index b1c2702a1e75..b991cb1abed7 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -310,6 +310,9 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { NODE_FROM_ID(owner, n.owner); if (owner) { node->_set_owner_nocheck(owner); + if (node->data.unique_name_in_owner) { + node->_acquire_unique_name_in_owner(); + } } }