Skip to content

Commit

Permalink
Implement Scene Unique Nodes
Browse files Browse the repository at this point in the history
Implements godotengine/godot-proposals#4096

* Nodes can be marked unique to the scene in the editor (or via code).
* Unique nodes can be accessed via the **%** prefix at any point in the path. From that point in the path (depending on whether the scene of the path is), the unique node will be fetched.
* Implementation is very optimal, as these nodes are cached.
  • Loading branch information
reduz committed Apr 25, 2022
1 parent f4b0c7a commit 8580f37
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 7 deletions.
1 change: 1 addition & 0 deletions core/string/string_name.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,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.
Expand Down
13 changes: 13 additions & 0 deletions core/string/string_name.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "core/string/ustring.h"
#include "core/templates/safe_refcount.h"

#define UNIQUE_NODE_PREFIX "%"

class Main;

struct StaticCString {
Expand Down Expand Up @@ -100,6 +102,17 @@ 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 {
if (!_data) {
return false;
}
if (_data->cname != nullptr) {
return (char32_t)_data->cname[0] == (char32_t)UNIQUE_NODE_PREFIX[0];
} else {
return (char32_t)_data->name[0] == (char32_t)UNIQUE_NODE_PREFIX[0];
}
}
_FORCE_INLINE_ bool operator<(const StringName &p_name) const {
return _data < p_name._data;
}
Expand Down
3 changes: 2 additions & 1 deletion core/string/ustring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "core/math/math_funcs.h"
#include "core/os/memory.h"
#include "core/string/print_string.h"
#include "core/string/string_name.h"
#include "core/string/translation.h"
#include "core/string/ucaps.h"
#include "core/variant/variant.h"
Expand Down Expand Up @@ -4357,7 +4358,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<String> chars = String::invalid_node_name_characters.split(" ");
Expand Down
13 changes: 13 additions & 0 deletions doc/classes/Node.xml
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,12 @@
Returns [code]true[/code] if the node is processing unhandled key input (see [method set_process_unhandled_key_input]).
</description>
</method>
<method name="is_unique_name_in_owner" qualifiers="const">
<return type="bool" />
<description>
Returns whether the node is an unique name for all the other nodes owned by its [member owner].
</description>
</method>
<method name="move_child">
<return type="void" />
<argument index="0" name="child_node" type="Node" />
Expand Down Expand Up @@ -719,6 +725,13 @@
Sets whether this is an instance load placeholder. See [InstancePlaceholder].
</description>
</method>
<method name="set_unique_name_in_owner">
<return type="void" />
<argument index="0" name="enable" type="bool" />
<description>
Sets this node's name as the unique name in its [member owner]. This allows the node to be accessed as [code]%Name[/code] instead of the full path, from any node within that scene.
</description>
</method>
<method name="update_configuration_warnings">
<return type="void" />
<description>
Expand Down
1 change: 1 addition & 0 deletions editor/icons/SceneUniqueName.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions editor/scene_tree_dock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,28 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
}
}
} break;
case TOOL_TOGGLE_SCENE_UNIQUE_NAME: {
List<Node *> selection = editor_selection->get_selected_node_list();
List<Node *>::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 && get_tree()->get_edited_scene_root()->get_node_or_null(UNIQUE_NODE_PREFIX + String(node->get_name())) != nullptr) {
accept->set_text(TTR("Another node already uses this unique name in the scene."));
accept->popup_centered();
return;
}
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:
Expand Down Expand Up @@ -1309,8 +1331,17 @@ void SceneTreeDock::_node_replace_owner(Node *p_base, Node *p_node, Node *p_root
UndoRedo *undo_redo = &editor_data->get_undo_redo();
switch (p_mode) {
case MODE_BIDI: {
bool is_unique = p_node->is_unique_name_in_owner() && p_base->get_node_or_null(UNIQUE_NODE_PREFIX + String(p_node->get_name())) != nullptr;
if (is_unique) {
// Will create a unique name conflict. Disable before setting owner.
undo_redo->add_do_method(p_node, "set_unique_name_in_owner", false);
}
undo_redo->add_do_method(p_node, "set_owner", p_root);
undo_redo->add_undo_method(p_node, "set_owner", p_base);
if (is_unique) {
// Will create a unique name conflict. Enable after setting owner.
undo_redo->add_undo_method(p_node, "set_unique_name_in_owner", true);
}

} break;
case MODE_DO: {
Expand Down Expand Up @@ -2774,11 +2805,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_NAME);
menu->set_item_checked(menu->get_item_index(TOOL_TOGGLE_SCENE_UNIQUE_NAME), 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;
Expand Down
2 changes: 1 addition & 1 deletion editor/scene_tree_dock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_NAME,
TOOL_CREATE_2D_SCENE,
TOOL_CREATE_3D_SCENE,
TOOL_CREATE_USER_INTERFACE,
Expand Down
18 changes: 18 additions & 0 deletions editor/scene_tree_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i
TabContainer *tab_container = Object::cast_to<TabContainer>(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) {
undo_redo->create_action(TTR("Disable Scene Unique Name"));
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();
}
}

Expand Down Expand Up @@ -260,6 +267,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 with 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();

Expand Down Expand Up @@ -832,6 +843,13 @@ void SceneTreeEditor::_renamed() {
// Trim leading/trailing whitespace to prevent node names from containing accidental whitespace, which would make it more difficult to get the node via `get_node()`.
new_name = new_name.strip_edges();

if (n->is_unique_name_in_owner() && get_tree()->get_edited_scene_root()->get_node_or_null("%" + new_name) != nullptr) {
error->set_text(TTR("Another node already uses this unique name in the scene."));
error->popup_centered();
which->set_text(0, n->get_name());
return;
}

if (!undo_redo) {
n->set_name(new_name);
which->set_metadata(0, n->get_path());
Expand Down
1 change: 1 addition & 0 deletions editor/scene_tree_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class SceneTreeEditor : public Control {
BUTTON_SIGNALS = 6,
BUTTON_GROUPS = 7,
BUTTON_PIN = 8,
BUTTON_UNIQUE = 9,
};

Tree *tree = nullptr;
Expand Down
94 changes: 89 additions & 5 deletions scene/main/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -917,12 +920,20 @@ void Node::set_name(const String &p_name) {
String name = p_name.validate_node_name();

ERR_FAIL_COND(name.is_empty());

if (data.unique_name_in_owner && data.owner) {
_release_unique_name_in_owner();
}
data.name = name;

if (data.parent) {
data.parent->_validate_child_name(this, true);
}

if (data.unique_name_in_owner && data.owner) {
_acquire_unique_name_in_owner();
}

propagate_notification(NOTIFICATION_PATH_RENAMED);

if (is_inside_tree()) {
Expand Down Expand Up @@ -1303,6 +1314,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;

Expand Down Expand Up @@ -1498,8 +1527,54 @@ void Node::_set_owner_nocheck(Node *p_owner) {
data.OW = data.owner->data.owned.back();
}

void Node::_release_unique_name_in_owner() {
ERR_FAIL_NULL(data.owner); // Sanity check.
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() {
ERR_FAIL_NULL(data.owner); // Sanity check.
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) {
WARN_PRINT(vformat(RTR("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'. This node is no longer set unique."), get_name(), is_inside_tree() ? get_path() : data.owner->get_path_to(this), is_inside_tree() ? (*which)->get_path() : data.owner->get_path_to(*which)));
data.unique_name_in_owner = false;
return;
}
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;
}

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;
Expand All @@ -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 {
Expand Down Expand Up @@ -2585,16 +2664,16 @@ void Node::clear_internal_tree_resource_paths() {
}

TypedArray<String> Node::get_configuration_warnings() const {
TypedArray<String> ret;

Vector<String> warnings;
if (GDVIRTUAL_CALL(_get_configuration_warnings, warnings)) {
TypedArray<String> 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 {
Expand Down Expand Up @@ -2790,6 +2869,11 @@ 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);

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
Expand Down
9 changes: 9 additions & 0 deletions scene/main/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ class Node : public Object {
Node *parent = nullptr;
Node *owner = nullptr;
Vector<Node *> children;
HashMap<StringName, Node *> owned_unique_nodes;
bool unique_name_in_owner = false;

int internal_children_front = 0;
int internal_children_back = 0;
int pos = -1;
Expand Down Expand Up @@ -193,6 +196,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--; }
Expand Down Expand Up @@ -345,6 +351,9 @@ class Node : public Object {
Node *get_owner() const;
void get_owned_by(Node *p_by, List<Node *> *p_owned);

void set_unique_name_in_owner(bool p_enabled);
bool is_unique_name_in_owner() const;

void remove_and_skip();
int get_index(bool p_include_internal = true) const;

Expand Down
3 changes: 3 additions & 0 deletions scene/resources/packed_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}

Expand Down

0 comments on commit 8580f37

Please sign in to comment.