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 16, 2022
1 parent 50bb184 commit c16ca88
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 8 deletions.
2 changes: 2 additions & 0 deletions core/string/node_path.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions core/string/string_name.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -240,13 +241,17 @@ 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;
_data->idx = idx;
_data->cname = nullptr;
_data->next = _table[idx];
_data->prev = nullptr;

#ifdef DEBUG_ENABLED
if (unlikely(debug_stringname)) {
// Keep in memory, force static.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions core/string/string_name.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class StringName {
SafeNumeric<uint32_t> static_count;
const char *cname = nullptr;
String name;
bool node_unique_name = false;
#ifdef DEBUG_ENABLED
uint32_t debug_references = 0;
#endif
Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions core/string/ustring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdio.h>
#include <stdlib.h>
#include <cstdint>
Expand Down Expand Up @@ -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<String> chars = String::invalid_node_name_characters.split(" ");
Expand Down
19 changes: 19 additions & 0 deletions doc/classes/Node.xml
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,18 @@
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>
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).
</description>
</method>
<method name="is_valid_unique_name_in_owner" qualifiers="const">
<return type="bool" />
<description>
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.
</description>
</method>
<method name="move_child">
<return type="void" />
<argument index="0" name="child_node" type="Node" />
Expand Down Expand Up @@ -719,6 +731,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>
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.
</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.
25 changes: 25 additions & 0 deletions editor/scene_tree_dock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,23 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
}
}
} break;
case TOOL_TOGGLE_SCENE_UNIQUE: {
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) {
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 @@ -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;
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,
TOOL_CREATE_2D_SCENE,
TOOL_CREATE_3D_SCENE,
TOOL_CREATE_USER_INTERFACE,
Expand Down
12 changes: 12 additions & 0 deletions editor/scene_tree_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ 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) {
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();
}
}

Expand Down Expand Up @@ -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();

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
98 changes: 93 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 @@ -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;

Expand Down Expand Up @@ -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;
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,19 @@ void Node::clear_internal_tree_resource_paths() {
}

TypedArray<String> Node::get_configuration_warnings() const {
TypedArray<String> 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<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 +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
Expand Down
Loading

0 comments on commit c16ca88

Please sign in to comment.