Implement Scene Unique Nodes

This commit is contained in:
kobewi 2022-04-26 13:37:20 +02:00
parent d063bc4277
commit e1c74ae83f
11 changed files with 171 additions and 1 deletions

View file

@ -35,6 +35,8 @@
#include "core/safe_refcount.h" #include "core/safe_refcount.h"
#include "core/ustring.h" #include "core/ustring.h"
#define UNIQUE_NODE_PREFIX "%"
struct StaticCString { struct StaticCString {
const char *ptr; const char *ptr;
static StaticCString create(const char *p_ptr); static StaticCString create(const char *p_ptr);
@ -92,6 +94,17 @@ public:
bool operator==(const String &p_name) const; bool operator==(const String &p_name) const;
bool operator==(const char *p_name) const; bool operator==(const char *p_name) const;
bool operator!=(const String &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 { _FORCE_INLINE_ bool operator<(const StringName &p_name) const {
return _data < p_name._data; return _data < p_name._data;
} }

View file

@ -39,6 +39,7 @@
#include "core/math/math_funcs.h" #include "core/math/math_funcs.h"
#include "core/os/memory.h" #include "core/os/memory.h"
#include "core/print_string.h" #include "core/print_string.h"
#include "core/string_name.h"
#include "core/translation.h" #include "core/translation.h"
#include "core/ucaps.h" #include "core/ucaps.h"
#include "core/variant.h" #include "core/variant.h"
@ -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. // 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 { String String::validate_node_name() const {
Vector<String> chars = String::invalid_node_name_characters.split(" "); Vector<String> chars = String::invalid_node_name_characters.split(" ");

View file

@ -749,6 +749,10 @@
<member name="process_priority" type="int" setter="set_process_priority" getter="get_process_priority" default="0"> <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. 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>
<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> </members>
<signals> <signals>
<signal name="child_entered_tree"> <signal name="child_entered_tree">

View file

@ -0,0 +1 @@
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4.378 2.224q1.235 0 2.084.866.865.85.865 2.083 0 1.17-.881 2.036-.866.85-2.068.85-1.218 0-2.083-.85-.866-.866-.866-2.068t.866-2.051q.865-.866 2.083-.866zm.962 1.988q-.4-.4-.962-.4-.56 0-.961.4-.401.384-.401.93 0 .56.4.961.401.385.962.385.561 0 .962-.385.4-.4.4-.946 0-.56-.4-.945zm5.45-2.116h1.218L5.677 13.78H4.442Zm1.17 5.722q1.234 0 2.083.866.866.849.866 2.1 0 1.17-.882 2.035-.865.85-2.068.85-1.218 0-2.083-.85-.866-.866-.866-2.084 0-1.202.866-2.051.865-.866 2.083-.866zm.961 1.987q-.4-.4-.962-.4-.56 0-.961.4-.4.385-.4.946 0 .561.4.962.4.384.961.384.561 0 .962-.384.4-.4.4-.946 0-.56-.4-.962z" aria-label="%" style="font-weight:600;font-size:16.0277px;font-family:FreeSans;-inkscape-font-specification:'FreeSans Semi-Bold';letter-spacing:0;word-spacing:0;fill:#e0e0e0;fill-opacity:.996078;stroke-width:.400692"/></svg>

After

Width:  |  Height:  |  Size: 897 B

View file

@ -1122,6 +1122,28 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
} }
} }
} break; } 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_2D_SCENE:
case TOOL_CREATE_3D_SCENE: case TOOL_CREATE_3D_SCENE:
case TOOL_CREATE_USER_INTERFACE: case TOOL_CREATE_USER_INTERFACE:
@ -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(); UndoRedo *undo_redo = &editor_data->get_undo_redo();
switch (p_mode) { switch (p_mode) {
case MODE_BIDI: { 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_do_method(p_node, "set_owner", p_root);
undo_redo->add_undo_method(p_node, "set_owner", p_base); 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; } break;
case MODE_DO: { case MODE_DO: {
@ -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); 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) { if (existing_script.is_valid() && exisiting_script_removable) {
add_separator = true; add_separator = true;
menu->add_icon_shortcut(get_icon("ScriptRemove", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/detach_script"), TOOL_DETACH_SCRIPT); menu->add_icon_shortcut(get_icon("ScriptRemove", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/detach_script"), TOOL_DETACH_SCRIPT);

View file

@ -92,6 +92,7 @@ class SceneTreeDock : public VBoxContainer {
TOOL_SCENE_CLEAR_INHERITANCE, TOOL_SCENE_CLEAR_INHERITANCE,
TOOL_SCENE_CLEAR_INHERITANCE_CONFIRM, TOOL_SCENE_CLEAR_INHERITANCE_CONFIRM,
TOOL_SCENE_OPEN_INHERITED, TOOL_SCENE_OPEN_INHERITED,
TOOL_TOGGLE_SCENE_UNIQUE_NAME,
TOOL_CREATE_2D_SCENE, TOOL_CREATE_2D_SCENE,
TOOL_CREATE_3D_SCENE, TOOL_CREATE_3D_SCENE,

View file

@ -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->get_parent()->call("set_current_tab", NodeDock::singleton->get_index());
NodeDock::singleton->show_groups(); 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) { void SceneTreeEditor::_toggle_visible(Node *p_node) {
@ -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()); 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_connections = p_node->get_persistent_signal_connection_count();
int num_groups = p_node->get_persistent_group_count(); int num_groups = p_node->get_persistent_group_count();
@ -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()`. // 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(); 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) { if (!undo_redo) {
n->set_name(new_name); n->set_name(new_name);
which->set_metadata(0, n->get_path()); which->set_metadata(0, n->get_path());

View file

@ -53,6 +53,7 @@ class SceneTreeEditor : public Control {
BUTTON_SIGNALS = 6, BUTTON_SIGNALS = 6,
BUTTON_GROUPS = 7, BUTTON_GROUPS = 7,
BUTTON_PIN = 8, BUTTON_PIN = 8,
BUTTON_UNIQUE = 9,
}; };
Tree *tree; Tree *tree;

View file

@ -268,6 +268,9 @@ void Node::_propagate_after_exit_branch(bool p_exiting_tree) {
} }
if (!found) { if (!found) {
if (data.unique_name_in_owner) {
_release_unique_name_in_owner();
}
data.owner->data.owned.erase(data.OW); data.owner->data.owned.erase(data.OW);
data.owner = nullptr; data.owner = nullptr;
} }
@ -978,12 +981,20 @@ void Node::set_name(const String &p_name) {
String name = p_name.validate_node_name(); String name = p_name.validate_node_name();
ERR_FAIL_COND(name == ""); ERR_FAIL_COND(name == "");
if (data.unique_name_in_owner && data.owner) {
_release_unique_name_in_owner();
}
data.name = name; data.name = name;
if (data.parent) { if (data.parent) {
data.parent->_validate_child_name(this); data.parent->_validate_child_name(this);
} }
if (data.unique_name_in_owner && data.owner) {
_acquire_unique_name_in_owner();
}
propagate_notification(NOTIFICATION_PATH_CHANGED); propagate_notification(NOTIFICATION_PATH_CHANGED);
if (is_inside_tree()) { if (is_inside_tree()) {
@ -1331,6 +1342,24 @@ Node *Node::get_node_or_null(const NodePath &p_path) const {
next = root; 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 { } else {
next = nullptr; next = nullptr;
@ -1507,8 +1536,54 @@ void Node::_set_owner_nocheck(Node *p_owner) {
data.OW = data.owner->data.owned.back(); 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) { void Node::set_owner(Node *p_owner) {
if (data.owner) { if (data.owner) {
if (data.unique_name_in_owner) {
_release_unique_name_in_owner();
}
data.owner->data.owned.erase(data.OW); data.owner->data.owned.erase(data.OW);
data.OW = nullptr; data.OW = nullptr;
data.owner = nullptr; data.owner = nullptr;
@ -1535,6 +1610,10 @@ void Node::set_owner(Node *p_owner) {
ERR_FAIL_COND(!owner_valid); ERR_FAIL_COND(!owner_valid);
_set_owner_nocheck(p_owner); _set_owner_nocheck(p_owner);
if (data.unique_name_in_owner) {
_acquire_unique_name_in_owner();
}
} }
Node *Node::get_owner() const { Node *Node::get_owner() const {
return data.owner; return data.owner;
@ -2908,6 +2987,8 @@ void Node::_bind_methods() {
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned); ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned);
#endif #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; MethodInfo mi;
@ -2993,6 +3074,7 @@ void Node::_bind_methods() {
#endif #endif
ADD_PROPERTY(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, "", 0), "set_name", "get_name"); 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::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, "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"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "", "get_multiplayer");

View file

@ -95,6 +95,9 @@ private:
Node *parent; Node *parent;
Node *owner; Node *owner;
Vector<Node *> children; // list of children Vector<Node *> children; // list of children
HashMap<StringName, Node *> owned_unique_nodes;
bool unique_name_in_owner = false;
int pos; int pos;
int depth; int depth;
int blocked; // safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed. int blocked; // safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
@ -201,6 +204,8 @@ private:
friend class SceneTree; friend class SceneTree;
void _set_tree(SceneTree *p_tree); void _set_tree(SceneTree *p_tree);
void _release_unique_name_in_owner();
void _acquire_unique_name_in_owner();
protected: protected:
void _block() { data.blocked++; } void _block() { data.blocked++; }
@ -324,6 +329,9 @@ public:
Node *get_owner() const; Node *get_owner() const;
void get_owned_by(Node *p_by, List<Node *> *p_owned); 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(); void remove_and_skip();
int get_index() const; int get_index() const;

View file

@ -309,6 +309,9 @@ Node *SceneState::instance(GenEditState p_edit_state) const {
NODE_FROM_ID(owner, n.owner); NODE_FROM_ID(owner, n.owner);
if (owner) { if (owner) {
node->_set_owner_nocheck(owner); node->_set_owner_nocheck(owner);
if (node->data.unique_name_in_owner) {
node->_acquire_unique_name_in_owner();
}
} }
} }