Skip to content

Commit

Permalink
Implement Scene Unique Nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
KoBeWi committed Apr 26, 2022
1 parent d063bc4 commit e1c74ae
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 1 deletion.
13 changes: 13 additions & 0 deletions core/string_name.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "core/safe_refcount.h"
#include "core/ustring.h"

#define UNIQUE_NODE_PREFIX "%"

struct StaticCString {
const char *ptr;
static StaticCString create(const char *p_ptr);
Expand Down Expand Up @@ -92,6 +94,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
4 changes: 3 additions & 1 deletion core/ustring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "core/math/math_funcs.h"
#include "core/os/memory.h"
#include "core/print_string.h"
#include "core/string_name.h"
#include "core/translation.h"
#include "core/ucaps.h"
#include "core/variant.h"
Expand Down Expand Up @@ -4140,7 +4141,8 @@ 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
4 changes: 4 additions & 0 deletions doc/classes/Node.xml
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,10 @@
<member name="process_priority" type="int" setter="set_process_priority" getter="get_process_priority" default="0">
The node's priority in the execution order of the enabled processing callbacks (i.e. [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS] and their internal counterparts). Nodes whose process priority value is [i]lower[/i] will have their processing callbacks executed first.
</member>
<member name="unique_name_in_owner" type="bool" setter="set_unique_name_in_owner" getter="is_unique_name_in_owner" default="false">
Sets this node's name as a 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.
If another node with the same owner already had that name declared as unique, that other node's name will no longer be set as having a unique name.
</member>
</members>
<signals>
<signal name="child_entered_tree">
Expand Down
1 change: 1 addition & 0 deletions editor/icons/icon_scene_unique_name.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions editor/scene_tree_dock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,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 @@ -1353,8 +1375,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 @@ -2753,6 +2784,12 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
menu->add_icon_shortcut(get_icon("ScriptExtend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_EXTEND_SCRIPT);
}
}
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_icon("SceneUniqueName", "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());
}
if (existing_script.is_valid() && exisiting_script_removable) {
add_separator = true;
menu->add_icon_shortcut(get_icon("ScriptRemove", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/detach_script"), TOOL_DETACH_SCRIPT);
Expand Down
1 change: 1 addition & 0 deletions editor/scene_tree_dock.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,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,
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 @@ -145,6 +145,13 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i

NodeDock::singleton->get_parent()->call("set_current_tab", NodeDock::singleton->get_index());
NodeDock::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();
}
}
void SceneTreeEditor::_toggle_visible(Node *p_node) {
Expand Down Expand Up @@ -263,6 +270,10 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
item->add_button(0, get_icon("NodeWarning", "EditorIcons"), BUTTON_WARNING, false, TTR("Node configuration warning:") + "\n" + p_node->get_configuration_warning());
}

if (p_node->is_unique_name_in_owner()) {
item->add_button(0, get_icon("SceneUniqueName", "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 @@ -823,6 +834,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;
Expand Down
82 changes: 82 additions & 0 deletions scene/main/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ void Node::_propagate_after_exit_branch(bool p_exiting_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 @@ -978,12 +981,20 @@ void Node::set_name(const String &p_name) {
String name = p_name.validate_node_name();

ERR_FAIL_COND(name == "");

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);
}

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

propagate_notification(NOTIFICATION_PATH_CHANGED);

if (is_inside_tree()) {
Expand Down Expand Up @@ -1331,6 +1342,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 @@ -1507,8 +1536,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_warning();
}

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 @@ -1535,6 +1610,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 {
return data.owner;
Expand Down Expand Up @@ -2908,6 +2987,8 @@ void Node::_bind_methods() {
#ifdef TOOLS_ENABLED
ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned);
#endif
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);

{
MethodInfo mi;
Expand Down Expand Up @@ -2993,6 +3074,7 @@ void Node::_bind_methods() {
#endif

ADD_PROPERTY(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, "", 0), "set_name", "get_name");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_unique_name_in_owner", "is_unique_name_in_owner");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename", PROPERTY_HINT_NONE, "", 0), "set_filename", "get_filename");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_owner", "get_owner");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "", "get_multiplayer");
Expand Down
8 changes: 8 additions & 0 deletions scene/main/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ class Node : public Object {
Node *parent;
Node *owner;
Vector<Node *> children; // list of children
HashMap<StringName, Node *> owned_unique_nodes;
bool unique_name_in_owner = false;

int pos;
int depth;
int blocked; // safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
Expand Down Expand Up @@ -201,6 +204,8 @@ class Node : public Object {
friend class SceneTree;

void _set_tree(SceneTree *p_tree);
void _release_unique_name_in_owner();
void _acquire_unique_name_in_owner();

protected:
void _block() { data.blocked++; }
Expand Down Expand Up @@ -324,6 +329,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() 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 @@ -309,6 +309,9 @@ Node *SceneState::instance(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 e1c74ae

Please sign in to comment.