Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert removal of find_node #58412

Closed
BeayemX opened this issue Feb 21, 2022 · 29 comments · Fixed by #60511
Closed

Revert removal of find_node #58412

BeayemX opened this issue Feb 21, 2022 · 29 comments · Fixed by #60511

Comments

@BeayemX
Copy link
Contributor

BeayemX commented Feb 21, 2022

Godot version

v4.0.alpha.custom_build [256069e]

System information

Linux Mint - Vulkan API 1.2.131 - Using Vulkan Device #0: NVIDIA - NVIDIA GeForce GTX 1080

Issue description

The function find_node() (returning a node) was replaced with find_nodes() (returning an array of nodes).
The commit 78dc608 made the changes.

While I think that the new functionality can be useful I think we should also keep the old implementation.

Previous usage

I used the old implementation mostly to reference nodes that were subject to be moved in the hierarchy later on.
It would look something like this:

@onready var circle : Polygon2D = find_node("FullCircle")
@onready var outer_deadzone : Polygon2D = find_node("OuterDeadzone")
@onready var inner_deadzone : Polygon2D = find_node("InnerDeadzone")
@onready var pointer = find_node("Pointer")
@onready var pointer_raw = find_node("PointerRaw")

Drawbacks

Ugly code
With the new implementation I would have to access the first element of the returned array every time.
This

@onready var circle : Polygon2D = find_node("FullCircle")
@onready var outer_deadzone : Polygon2D = find_node("OuterDeadzone")
...

would become this:

@onready var circle : Polygon2D = find_nodes("FullCircle")[0]
@onready var outer_deadzone : Polygon2D = find_nodes("OuterDeadzone")[0]
...

Possible performance gains?
I am not sure if this is correct, but could it be possible for find_node to have performance benefits because the traversal could break early when finding the first match?

Steps to reproduce

Not applicable

Minimal reproduction project

No response

@KoBeWi
Copy link
Member

KoBeWi commented Feb 22, 2022

This makes sense considering that get_first_node_in_group() exists.

@Calinou Calinou added this to the 4.0 milestone Feb 22, 2022
@Xrayez
Copy link
Contributor

Xrayez commented Feb 22, 2022

For a moment I thought that find_node() functionality is removed altogether: godotengine/godot-proposals#1303. 😅

onready var circle : Polygon2D = find_nodes("FullCircle")[0]
onready var outer_deadzone : Polygon2D = find_nodes("OuterDeadzone")[0]
...

This will actually lead to an error if array is empty, and the purpose of find_node() is to find nodes without leading to any kind of errors (unlike get_node()). See my explanation at godotengine/godot-proposals#3671 (comment).

So yeah, I support returning find_node() method.

The current find_nodes() should likely be renamed to something else, like filter_nodes(). Having find_node() and find_nodes() isn't nice to be distinguished by plural only.

@h0lley
Copy link

h0lley commented Feb 22, 2022

yes please, as it is not just this:

Ugly code With the new implementation I would have to access the first element of the returned array every time. This

@onready var circle : Polygon2D = find_node("FullCircle")
@onready var outer_deadzone : Polygon2D = find_node("OuterDeadzone")
...

would become this:

@onready var circle : Polygon2D = find_nodes("FullCircle")[0]
@onready var outer_deadzone : Polygon2D = find_nodes("OuterDeadzone")[0]
...

but considering that generally you'd need to check first if there's at least one match, this

var my_node = find_node("SomeNode*")
if my_node:
    my_node.do_something()

would become this:

var my_node_matches = find_nodes("SomeNode*")
if not my_node_matches.is_empty():
    my_node_matches[0].do_something()

@smix8
Copy link
Contributor

smix8 commented Feb 22, 2022

Please bring it back. Godot is already a Nodepath fixer-up hell on larger scenes and projects.

Iterating on complex, nested GUI is basically unbearable in Godot without a simple "find node recursive by name" function like find_node() did. Fixing all those onready String paths by hand when a large container node is moved is such a pain.

In an ideal scenario we can keep both functions.

@Calinou
Copy link
Member

Calinou commented Feb 22, 2022

Fixing all those onready String paths by hand when a large container node is moved is such a pain.

You can use exported NodePath properties, which will be fixed automatically when you move nodes around. You'll have to use get_node() on those NodePath properties, but it's a price to pay for easier refactoring.

(This also applies to preload()ed resources when using the ResourcePreloader node instead of preloading via code.)

@CsloudX
Copy link

CsloudX commented Feb 23, 2022

Yes, please bring it back.
I often use find_node on @onready:

@onready var btn = find_node("ResetButton")

It's so good.

by find_nodes, I must in _ready function:

var btn
func _ready():
    var btns = find_nodes("ResetButton")
    if !btns.is_empty():
        btn=btns[0]

by NodePath, I need more code:

var btn_path : NodePath
@onready var btn = get_node_or_null(btn_path)

@reduz
Copy link
Member

reduz commented Feb 23, 2022

My take on this is that we should probably do this in a different way and take the chance to do it properly in 4.0

I was thinking that we could have an option for a node to make it unique named within the scene you are editing. This is, for this scene, it is warranted that the node name is unique. If you try to make it non-unique the editor will error.

Then, from any node within the scene, if you do $Name you obtain this node. This should be relatively easy to add and I think its a lot more usefriendly than having to use find_node, which a lot of users end up relying to and which also requires an unique name. It can also be autocompleted.

@Zireael07
Copy link
Contributor

@reduz: Very good idea because I often end up with a single node groups (e.g. "Root" or "player") for exactly this purpose...

@aaronfranke
Copy link
Member

@BeayemX @smix8 Would this proposal work for your use case? Instead of finding by name, you could just export a reference to that node, which should be fast and easy godotengine/godot-proposals#1048

@BeayemX
Copy link
Contributor Author

BeayemX commented Feb 23, 2022

@aaronfranke That would be great and fit my mentioned usecase pretty well.

But I still think that find_node() should be made available again.

@EIREXE
Copy link
Contributor

EIREXE commented Feb 25, 2022

I can setup a PR to restore the old find_node, in fact I will probably do it in a bit, I don't see why we can't have both.

@PoisonousGame
Copy link

How is find_nodes() better than find_node()? Better performance? How much better? If there is no better advantage, then please revert and put an end to ugly code.

@Zireael07
Copy link
Contributor

@GiantArtisan: From what I understand, performance is one of the advantages.

@PoisonousGame
Copy link

@GiantArtisan: From what I understand, performance is one of the advantages.

If there is test data to illustrate, it might convince a bunch of people

@h0lley
Copy link

h0lley commented Feb 25, 2022

more so than performance, I think it is about the greater utility, now being able to find multiple nodes and mask not just for the node name, but also being able to specify the desired class name. furthermore, I think the intention is for find_nodes() to be treated as a tool for a specific job, and not being misused as the main way of obtaining node references.

but "forcefully" getting users to adopt different patterns for their project (when there is no real cost to letting them stick to the patterns they are used to) is probably not the way to go.

@Zireael07
Copy link
Contributor

@GiantArtisan: Change was made so recently that I expect not many people have used it before and after (very few people seem to be using Godot 4 alphas, mostly due to veeery incomplete documentation for many things changed/introduced)

@dalexeev
Copy link
Member

I don't see anything wrong with having both find_nodes and find_node. The first is a universal method, the second is faster (because it only searches for the first occurrence). HTML/JavaScript has querySelectorAll and querySelector methods, the situation is similar.

@andyp123
Copy link

andyp123 commented Feb 25, 2022

Coming to Godot from other engines, I was using find_node as an alternative to finding a child node by type, which is not a built-in function of Node. The reason I wasn't just using $nodepath was that I didn't want to enforce a fixed path, so the script would be useable in different scenes, although by using find_node I was enforcing a name on the node. I couldn't figure out how to expose the node as a parameter either, but maybe I didn't try hard enough on that front.

Maybe my thinking was too component-based, but I do think it would be useful to have find_child_by_name(name) and find_child_by_type(class) functions that simply return the first match if possible/agreeable. I understand not having such a function if you think that users of other engines would just bring their programming style to Godot and not learn the Godot way :)

EDIT: I didn't realise that get_children() had no recursive option. find_nodes is quite useful, but I agree that it's a little clunkier due to the need to check the returned array is not empty before the node can be safely accessed.

@OscarCookeAbbott
Copy link

I agree. find_node() can be useful even if it isn't necessarily best practice to use it, and find_nodes() also would have its uses but does not perfectly replace the former for all the reasons previously mentioned by others.

@pchasco
Copy link

pchasco commented Feb 25, 2022

I propose restoring it. The idea that one would remove oft-used language features to discourage "bad practices" is somewhat problematic for me. What is a "bad practice" can be considered subjective, and if that rationale were evenly applied along the entire engine, how many other features could be pruned? In some sense, the usage of node literal paths ($/path/to/node) is bad practice because they tend to make changing scene structure difficult.

@smix8
Copy link
Contributor

smix8 commented Feb 26, 2022

@Calinou @aaronfranke

This is not directly related to the find_node() function but part of the problem why NodePath's don't work as a replacement for it currently.

The problem with the exported NodePath's (or Nodetypes like in the linked proposal) is that they share the same issue that the filesystem rename function has. The pathupdate functionality breaks reliably on large projects with heavy, nested scene assets when files are moved or renamed corrupting entire scene files. The "fix broken filepaths" dialog in Godot editor stops working on them. If the nested subfile of the subfile breaks you not even get a realiable error msg. Before this is not fixed the current NodePath solution only works for small sized projects. On a different note, having so many NodePaths as export variables in the Editor Inspector for simple scenes would be incredibly clumsy to work with even when grouping them.

@Rodeo-McCabe
Copy link

Just want to give my +1 for renaming find_nodes to filter_nodes or perhaps match_nodes, something a little more clear and different.

@h0lley
Copy link

h0lley commented Mar 6, 2022

hm I'm of the opposite opinion.
finding a node and finding nodes is the most straight forward description of what those two very similar functions do, and when they'd use different terminology - like find and filter - then that would

  1. suggest that they do different things beyond just the number of matches they return
  2. make it harder to look up documentation (where methods are sorted alphabetically, so they may no longer appear directly adjacent to each other)
  3. even when one already knows about the existence of both versions, when one wants to switch from one to the other, one may have to think about "what was the term used for the other one again?"

I think the distinction by plural only is just fine.

@Bloodyaugust
Copy link

Another vote to bring back find_node here. I very frequently use it at the root script of a scene I know is going to be instantiated at runtime, generally more than once. It's a wonderful way to find children quickly, while using the @onready decorator. It's a one-liner with no index necessary, which keeps it very simple and straightforward. I could definitely be convinced that a scene-local unique identifier as proposed above can do this better, but until that's in... can we restore it?

@Salvakiya
Copy link

I use find_node quite a lot in 3.0 for complex scenes (mostly gui) which I refactor the node hierarchy alot. While I would support the return of find_node I actually really like Reduz's suggestion from earlier.

@voylin
Copy link
Contributor

voylin commented Mar 25, 2022

I would also like to request this feature to come back, find_node was very helpful in my opinion.

@reduz
Copy link
Member

reduz commented Apr 20, 2022

So, #60298 implements this same functionality in a cleaner (and more efficient) way, but some users prefer to keep to find_node because it goes better with their mental model or have cases where they really need it.

My proposal here would be to not revert find_node, but to implement it as find_child in 4.0 instead, so it can contrast with the existing find_parent. Would that be ok?

@cybereality
Copy link
Contributor

I like that idea.

@akien-mga
Copy link
Member

My proposal here would be to not revert find_node, but to implement it as find_child in 4.0 instead, so it can contrast with the existing find_parent. Would that be ok?

Done with #60511.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.