Node: Re-add `find_node` as `find_child`, improve docs

The new name contrasts it better with `find_parent`, and makes it clear
that it only matches child/descendant nodes.

Also rename `find_nodes` to `find_children` accordingly.
This commit is contained in:
Rémi Verschelde 2022-04-25 15:16:44 +02:00
parent 7c7ce7dcd7
commit 9c2ea7e296
4 changed files with 81 additions and 40 deletions

View File

@ -193,28 +193,45 @@
[b]Note:[/b] It will not work properly if the node contains a script with constructor arguments (i.e. needs to supply arguments to [method Object._init] method). In that case, the node will be duplicated without a script. [b]Note:[/b] It will not work properly if the node contains a script with constructor arguments (i.e. needs to supply arguments to [method Object._init] method). In that case, the node will be duplicated without a script.
</description> </description>
</method> </method>
<method name="find_nodes" qualifiers="const"> <method name="find_child" qualifiers="const">
<return type="Node" />
<argument index="0" name="pattern" type="String" />
<argument index="1" name="recursive" type="bool" default="true" />
<argument index="2" name="owned" type="bool" default="true" />
<description>
Finds the first descendant of this node whose name matches [code]pattern[/code] as in [method String.match].
[code]pattern[/code] does not match against the full path, just against individual node names. It is case-sensitive, with [code]"*"[/code] matching zero or more characters and [code]"?"[/code] matching any single character except [code]"."[/code]).
If [code]recursive[/code] is [code]true[/code], all child nodes are included, even if deeply nested. Nodes are checked in tree order, so this node's first direct child is checked first, then its own direct children, etc., before moving to the second direct child, and so on. If [code]recursive[/code] is [code]false[/code], only this node's direct children are matched.
If [code]owned[/code] is [code]true[/code], this method only finds nodes who have an assigned [member Node.owner]. This is especially important for scenes instantiated through a script, because those scenes don't have an owner.
Returns [code]null[/code] if no matching [Node] is found.
[b]Note:[/b] As this method walks through all the descendants of the node, it is the slowest way to get a reference to another node. Whenever possible, consider using [method get_node] with unique names instead (see [member unique_name_in_owner]), or caching the node references into variable.
[b]Note:[/b] To find all descendant nodes matching a pattern or a class type, see [method find_children].
</description>
</method>
<method name="find_children" qualifiers="const">
<return type="Node[]" /> <return type="Node[]" />
<argument index="0" name="mask" type="String" /> <argument index="0" name="pattern" type="String" />
<argument index="1" name="type" type="String" default="&quot;&quot;" /> <argument index="1" name="type" type="String" default="&quot;&quot;" />
<argument index="2" name="recursive" type="bool" default="true" /> <argument index="2" name="recursive" type="bool" default="true" />
<argument index="3" name="owned" type="bool" default="true" /> <argument index="3" name="owned" type="bool" default="true" />
<description> <description>
Finds descendants of this node whose, name matches [code]mask[/code] as in [method String.match], and/or type matches [code]type[/code] as in [method Object.is_class]. Finds descendants of this node whose name matches [code]pattern[/code] as in [method String.match], and/or type matches [code]type[/code] as in [method Object.is_class].
[code]mask[/code] does not match against the full path, just against individual node names. It is case-sensitive, with [code]"*"[/code] matching zero or more characters and [code]"?"[/code] matching any single character except [code]"."[/code]). [code]pattern[/code] does not match against the full path, just against individual node names. It is case-sensitive, with [code]"*"[/code] matching zero or more characters and [code]"?"[/code] matching any single character except [code]"."[/code]).
[code]type[/code] will check equality or inheritance. It is case-sensitive, [code]"Object"[/code] will match a node whose type is [code]"Node"[/code] but not the other way around. [code]type[/code] will check equality or inheritance, and is case-sensitive. [code]"Object"[/code] will match a node whose type is [code]"Node"[/code] but not the other way around.
If [code]owned[/code] is [code]true[/code], this method only finds nodes whose owner is this node. This is especially important for scenes instantiated through a script, because those scenes don't have an owner. If [code]recursive[/code] is [code]true[/code], all child nodes are included, even if deeply nested. Nodes are checked in tree order, so this node's first direct child is checked first, then its own direct children, etc., before moving to the second direct child, and so on. If [code]recursive[/code] is [code]false[/code], only this node's direct children are matched.
Returns an empty array, if no matching nodes are found. If [code]owned[/code] is [code]true[/code], this method only finds nodes who have an assigned [member Node.owner]. This is especially important for scenes instantiated through a script, because those scenes don't have an owner.
[b]Note:[/b] As this method walks through all the descendants of the node, it is the slowest way to get references to other nodes. To avoid using [method find_nodes] too often, consider caching the node references into variables. Returns an empty array if no matching nodes are found.
[b]Note:[/b] As this method walks through all the descendants of the node, it is the slowest way to get references to other nodes. Whenever possible, consider caching the node references into variables.
[b]Note:[/b] If you only want to find the first descendant node that matches a pattern, see [method find_child].
</description> </description>
</method> </method>
<method name="find_parent" qualifiers="const"> <method name="find_parent" qualifiers="const">
<return type="Node" /> <return type="Node" />
<argument index="0" name="mask" type="String" /> <argument index="0" name="pattern" type="String" />
<description> <description>
Finds the first parent of the current node whose name matches [code]mask[/code] as in [method String.match] (i.e. case-sensitive, but [code]"*"[/code] matches zero or more characters and [code]"?"[/code] matches any single character except [code]"."[/code]). Finds the first parent of the current node whose name matches [code]pattern[/code] as in [method String.match].
[b]Note:[/b] It does not match against the full path, just against individual node names. [code]pattern[/code] does not match against the full path, just against individual node names. It is case-sensitive, with [code]"*"[/code] matching zero or more characters and [code]"?"[/code] matching any single character except [code]"."[/code]).
[b]Note:[/b] As this method walks upwards in the scene tree, it can be slow in large, deeply nested scene trees. Whenever possible, consider using [method get_node] instead. To avoid using [method find_parent] too often, consider caching the node reference into a variable. [b]Note:[/b] As this method walks upwards in the scene tree, it can be slow in large, deeply nested scene trees. Whenever possible, consider using [method get_node] with unique names instead (see [member unique_name_in_owner]), or caching the node references into variable.
</description> </description>
</method> </method>
<method name="get_child" qualifiers="const"> <method name="get_child" qualifiers="const">
@ -492,12 +509,6 @@
Returns [code]true[/code] if the node is processing unhandled key input (see [method set_process_unhandled_key_input]). Returns [code]true[/code] if the node is processing unhandled key input (see [method set_process_unhandled_key_input]).
</description> </description>
</method> </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"> <method name="move_child">
<return type="void" /> <return type="void" />
<argument index="0" name="child_node" type="Node" /> <argument index="0" name="child_node" type="Node" />
@ -725,13 +736,6 @@
Sets whether this is an instance load placeholder. See [InstancePlaceholder]. Sets whether this is an instance load placeholder. See [InstancePlaceholder].
</description> </description>
</method> </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"> <method name="update_configuration_warnings">
<return type="void" /> <return type="void" />
<description> <description>
@ -767,6 +771,10 @@
<member name="scene_file_path" type="String" setter="set_scene_file_path" getter="get_scene_file_path"> <member name="scene_file_path" type="String" setter="set_scene_file_path" getter="get_scene_file_path">
If a scene is instantiated from a file, its topmost node contains the absolute file path from which it was loaded in [member scene_file_path] (e.g. [code]res://levels/1.tscn[/code]). Otherwise, [member scene_file_path] is set to an empty string. If a scene is instantiated from a file, its topmost node contains the absolute file path from which it was loaded in [member scene_file_path] (e.g. [code]res://levels/1.tscn[/code]). Otherwise, [member scene_file_path] is set to an empty string.
</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

@ -423,7 +423,7 @@ void SceneImportSettings::_update_view_gizmos() {
continue; continue;
} }
TypedArray<Node> descendants = mesh_node->find_nodes("collider_view", "MeshInstance3D"); TypedArray<Node> descendants = mesh_node->find_children("collider_view", "MeshInstance3D");
CRASH_COND_MSG(descendants.is_empty(), "This is unreachable, since the collider view is always created even when the collision is not used! If this is triggered there is a bug on the function `_fill_scene`."); CRASH_COND_MSG(descendants.is_empty(), "This is unreachable, since the collider view is always created even when the collision is not used! If this is triggered there is a bug on the function `_fill_scene`.");

View File

@ -1373,9 +1373,39 @@ bool Node::has_node(const NodePath &p_path) const {
return get_node_or_null(p_path) != nullptr; return get_node_or_null(p_path) != nullptr;
} }
TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bool p_recursive, bool p_owned) const { // Finds the first child node (in tree order) whose name matches the given pattern.
// Can be recursive or not, and limited to owned nodes.
Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned) const {
ERR_FAIL_COND_V(p_pattern.is_empty(), nullptr);
Node *const *cptr = data.children.ptr();
int ccount = data.children.size();
for (int i = 0; i < ccount; i++) {
if (p_owned && !cptr[i]->data.owner) {
continue;
}
if (cptr[i]->data.name.operator String().match(p_pattern)) {
return cptr[i];
}
if (!p_recursive) {
continue;
}
Node *ret = cptr[i]->find_child(p_pattern, true, p_owned);
if (ret) {
return ret;
}
}
return nullptr;
}
// Finds child nodes based on their name using pattern matching, or class name,
// or both (either pattern or type can be left empty).
// Can be recursive or not, and limited to owned nodes.
TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_type, bool p_recursive, bool p_owned) const {
TypedArray<Node> ret; TypedArray<Node> ret;
ERR_FAIL_COND_V(p_mask.is_empty() && p_type.is_empty(), ret); ERR_FAIL_COND_V(p_pattern.is_empty() && p_type.is_empty(), ret);
Node *const *cptr = data.children.ptr(); Node *const *cptr = data.children.ptr();
int ccount = data.children.size(); int ccount = data.children.size();
@ -1384,8 +1414,8 @@ TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bo
continue; continue;
} }
if (!p_mask.is_empty()) { if (!p_pattern.is_empty()) {
if (!cptr[i]->data.name.operator String().match(p_mask)) { if (!cptr[i]->data.name.operator String().match(p_pattern)) {
continue; continue;
} else if (p_type.is_empty()) { } else if (p_type.is_empty()) {
ret.append(cptr[i]); ret.append(cptr[i]);
@ -1407,7 +1437,7 @@ TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bo
} }
if (p_recursive) { if (p_recursive) {
ret.append_array(cptr[i]->find_nodes(p_mask, p_type, true, p_owned)); ret.append_array(cptr[i]->find_children(p_pattern, p_type, true, p_owned));
} }
} }
@ -1418,10 +1448,10 @@ Node *Node::get_parent() const {
return data.parent; return data.parent;
} }
Node *Node::find_parent(const String &p_mask) const { Node *Node::find_parent(const String &p_pattern) const {
Node *p = data.parent; Node *p = data.parent;
while (p) { while (p) {
if (p->data.name.operator String().match(p_mask)) { if (p->data.name.operator String().match(p_pattern)) {
return p; return p;
} }
p = p->data.parent; p = p->data.parent;
@ -1542,7 +1572,9 @@ void Node::_acquire_unique_name_in_owner() {
StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String()); StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String());
Node **which = data.owner->data.owned_unique_nodes.getptr(key); Node **which = data.owner->data.owned_unique_nodes.getptr(key);
if (which != nullptr && *which != this) { 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))); String which_path = is_inside_tree() ? (*which)->get_path() : data.owner->get_path_to(*which);
WARN_PRINT(vformat(RTR("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'.\n'%s' is no longer set as having a unique name."),
get_name(), is_inside_tree() ? get_path() : data.owner->get_path_to(this), which_path, which_path));
data.unique_name_in_owner = false; data.unique_name_in_owner = false;
return; return;
} }
@ -2780,8 +2812,9 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node); ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node);
ClassDB::bind_method(D_METHOD("get_node_or_null", "path"), &Node::get_node_or_null); ClassDB::bind_method(D_METHOD("get_node_or_null", "path"), &Node::get_node_or_null);
ClassDB::bind_method(D_METHOD("get_parent"), &Node::get_parent); ClassDB::bind_method(D_METHOD("get_parent"), &Node::get_parent);
ClassDB::bind_method(D_METHOD("find_nodes", "mask", "type", "recursive", "owned"), &Node::find_nodes, DEFVAL(""), DEFVAL(true), DEFVAL(true)); ClassDB::bind_method(D_METHOD("find_child", "pattern", "recursive", "owned"), &Node::find_child, DEFVAL(true), DEFVAL(true));
ClassDB::bind_method(D_METHOD("find_parent", "mask"), &Node::find_parent); ClassDB::bind_method(D_METHOD("find_children", "pattern", "type", "recursive", "owned"), &Node::find_children, DEFVAL(""), DEFVAL(true), DEFVAL(true));
ClassDB::bind_method(D_METHOD("find_parent", "pattern"), &Node::find_parent);
ClassDB::bind_method(D_METHOD("has_node_and_resource", "path"), &Node::has_node_and_resource); ClassDB::bind_method(D_METHOD("has_node_and_resource", "path"), &Node::has_node_and_resource);
ClassDB::bind_method(D_METHOD("get_node_and_resource", "path"), &Node::_get_node_and_resource); ClassDB::bind_method(D_METHOD("get_node_and_resource", "path"), &Node::_get_node_and_resource);
@ -2872,8 +2905,6 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_unique_name_in_owner", "enable"), &Node::set_unique_name_in_owner); 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_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 #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
@ -2964,6 +2995,7 @@ void Node::_bind_methods() {
ADD_SIGNAL(MethodInfo("child_exited_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); ADD_SIGNAL(MethodInfo("child_exited_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_unique_name_in_owner", "is_unique_name_in_owner");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_file_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_file_path", "get_scene_file_path"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_file_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_file_path", "get_scene_file_path");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_owner", "get_owner"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_owner", "get_owner");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "", "get_multiplayer"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "", "get_multiplayer");

View File

@ -310,12 +310,13 @@ public:
bool has_node(const NodePath &p_path) const; bool has_node(const NodePath &p_path) const;
Node *get_node(const NodePath &p_path) const; Node *get_node(const NodePath &p_path) const;
Node *get_node_or_null(const NodePath &p_path) const; Node *get_node_or_null(const NodePath &p_path) const;
TypedArray<Node> find_nodes(const String &p_mask, const String &p_type = "", bool p_recursive = true, bool p_owned = true) const; Node *find_child(const String &p_pattern, bool p_recursive = true, bool p_owned = true) const;
TypedArray<Node> find_children(const String &p_pattern, const String &p_type = "", bool p_recursive = true, bool p_owned = true) const;
bool has_node_and_resource(const NodePath &p_path) const; bool has_node_and_resource(const NodePath &p_path) const;
Node *get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property = true) const; Node *get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property = true) const;
Node *get_parent() const; Node *get_parent() const;
Node *find_parent(const String &p_mask) const; Node *find_parent(const String &p_pattern) const;
_FORCE_INLINE_ SceneTree *get_tree() const { _FORCE_INLINE_ SceneTree *get_tree() const {
ERR_FAIL_COND_V(!data.tree, nullptr); ERR_FAIL_COND_V(!data.tree, nullptr);