godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.1k stars 69 forks source link

Add scene-unique nodes to make accessing nodes from code more convenient #4096

Closed reduz closed 2 years ago

reduz commented 2 years ago

Describe the project you are working on

Godot

Describe the problem or limitation you are having in your project

Currently in Godot, finding specific nodes that have different accesses across the project is done the following way:

This suffices for a lot of cases, but it often happens that developers want to access a specific node in the scene tree, but this node is situated behind layers of containers or other parent nodes, so typing the full path such as:

$VBoxContainer/HBoxContainer/VBoxContainer/Option1/Label1.text = " hello"

Can be very cumbersome and annoying, even with code completion.

What many users do to work around this is to put a unique name to it and use find_node with an autoload to find it:

@onready var label1 : Label = find_node("Label1")

But this is messy. If there are many nodes around, having to create local variables for each makes code difficult to read. Also the solution is just not obvious.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

The way to solve this is by implementing Scene Unique Nodes.

These nodes would only exist once per scene and you will be able to access them by name by ensuring their name is unique.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

The idea is like this:

RMB any node and set it as unique:

image

This node will be, thus, unique in this local scene. It does not mean that any other node can't be named the same, but it ensures that no unique nodes can't be named the same:

image

Then, accessing it from script would be simply, just type the name:


$@Label1.text = "Hello"
# Same as:
$VBoxContainer/HBoxContainer/Label1.text = "Hello"

And that's it.

So. some questions may arise such as:

Q: What is the difference with find? A: More strict and largely more performant, as access will be instant because this node is unique (no actual searching takes place). Simpler to type and to understand when reading the code.

Q: What if I want to make the parent unique but access a child node? A: $@Label1/Child.say_hello()

Q: What if I want to access it in a sub-scene? A: The idea is that here we want to satisfy the most common case which is searching within a scene, but you can work around this rather easily. If the scene in the example above was a subecene you could do:

$Path/To/SubScene1/@Label1.text = "Hello" # Still works 
# Instead of
$Path/To/SubScene1/VBoxContainer/HBoxContainer/Label1.text = "Hello"

But additionally, you can make the subscene also a scene unique node, so you can do:

$@SubScene1/@Label1.text = "Hello" # Still works 
# Instead of
$Path/To/SubScene1/VBoxContainer/HBoxContainer/Label1.text = "Hello"

Q: What about non GDScript languages? A: get_node (GetNode in C#) will work the same:

GetNode<Label>("@Label1").Text = "Hello";

Thanks @pycbouh and @groud for giving feedback on how to implement this to make this proposal more solid.

If this enhancement will not be used often, can it be worked around with a few lines of script?

N/A

Is there a reason why this should be core and not an add-on in the asset library?

N/A

groud commented 2 years ago

I think the proposal is mostly good. I would most likely rename the "Make unique" thing, as it's already used to make resources unique and is thus a little bit confusing.

Maybe we could name it somehow like "Mark as path shortcut"? or somehow like that?

aaronfranke commented 2 years ago

I'm not sure that giving a per-scene unique name to a node is the best solution here. It seems to me like the problem is that we want a node in the scene to have an easy reference to this particular node, without relying on find_node or node paths. So we just need a way to get a reference to these nodes.

I think a much better idea would be to allow exporting Node references. So you could just have a field on the scene's root node and set the node that way, instead of depending on a unique name. This would be IMO much nicer and a lot less magic. This can kinda already be done with exporting NodePaths but it's not as nice. https://github.com/godotengine/godot-proposals/issues/1048

(EDIT: For anyone who says this isn't possible... Unity does it, so it is possible.)

I also don't like how this proposal makes scenes "exist" at runtime, in Godot scenes are groups of nodes and when instanced everything just becomes one scene with nodes in in the SceneTree.

groud commented 2 years ago

I think a much better idea would be to allow exporting Node references. So you could just have a field on the scene's root node and set the node that way, instead of depending on a unique name. This would be IMO much nicer and a lot less magic. This can kinda already be done with exporting NodePaths but it's not as nice. https://github.com/godotengine/godot-proposals/issues/1048

I don't think this is possible or make sense, as node are accessed at runtime. A node cannot be "stored" in a scene, only its path. However I suppose what you mean is that doing something like RootNode.nodepath.call_something_on_the_node_itself() would work.

https://github.com/godotengine/godot-proposals/issues/1048I also don't like how this proposal makes scenes "exist" at runtime, in Godot scenes are groups of nodes and when instanced everything just becomes one scene with nodes in in the SceneTree.

I don't think this is an issue at all. The "shortcuts" can be stored in the scene root node at runtime and would act like any other node.

SilencedPerson commented 2 years ago

Does this work with get_node() and get_node_or_null()? as in can i do get_node("@Label1")?

WolfgangSenff commented 2 years ago

I really like groud's suggestion for handling this, and also that "Make unique" is already a bit loaded. "Make shortcut" also works in my mind, or "Set as shortcut". Actually, a nice feature would be if you called it "Set as shortcut", that could bring up a small dialog that you can type in what you want it to be aliased as in the script, and then it creates a reference to it for you in the script. That said, if there was no script to reference yet, it would be slightly annoying, so probably just not doing anything automatic like that is better.

BigZaphod commented 2 years ago

This seems like a clever solution that avoids the boilerplate of adding and assigning to a NodePath export. My only feedback is about naming and syntax, really.

Using "Make Unique" here feels potentially confusing in light of what that means for resources and such. It almost feels as if it is implying that nodes are somehow shared across scenes by default and that this option undoes that sharing. "Shortcut" or "Alias" seem more along the right track to me.

Adding an @ into the nodepath syntax to reference one of these feels weird to me. Probably it would be too magical and error-prone, but what if the nodepath resolution just checked for these shortcuts/aliases first so that you could simply type $Label1 and it'd first check the list of unique/alias/shortcut nodes for an exact match and, if it can't find one, it falls back to resolving like normal? (On the other hand, a benefit of using a prefix like @ here is that once you mark the node as an alias in the editor, not only could the node's color change but the name's label could change, too, to include the prefix @ so it becomes more self-documenting.)

pchasco commented 2 years ago

I don't see the need to create a new engine feature for unique node names. To my mind, the developer can just give the node a precise name and that should solve the problem for them.

I do see the benefit of offering a "find" operator within the node path. This would solve the issue of deep node paths, as well as the extreme pain of having to fixup node path references after moving something within the tree.

$DialogNode/@NameControl.text = "Hi"

The "@" symbol basically just means "first child whose name is NameControl, breadth first recursively"

Personally, I do not allow references to nodes using node path from outside the a scene. Refactoring node paths within the scene is already difficult enough. Any external node must use a method on the root node of the scene to act upon it, or the scene may respond to a signal from outside.

muno777 commented 2 years ago

Cool suggestion imo. Maybe label it "Make Global to Scene"? Since you're giving "global" access to it within that scene. Sounds a bit more clear than "unique nodes" to me.

ficoos commented 2 years ago

I just do

onready var foo = $path/to/foo

The advantage of this is that foo is just a field so everything just works. No special syntax nothing new to learn. It also works for parent nodes or sub-scenes.

I think that solving this in the UI by is better than adding it to the language.

reduz commented 2 years ago

@SilencedPerson

Does this work with get_node() and get_node_or_null()? as in can i do get_node("@Label1")?

Yes, Added the clarification. It should work with this fine.

reduz commented 2 years ago

@pchasco

I do see the benefit of offering a "find" operator within the node path.

Its not really a find operator, its simply a reference to a unique node within the scene. You get instant access to it, so its quite performant.

boyhax commented 2 years ago

Maybe just every node exists should have unique id visible in inspector and i can copy it and use it from any where if it exists in the tree

pchasco commented 2 years ago

@reduz

Its not really a find operator, its simply a reference to a unique node within the scene. You get instant access to it, so its quite performant.

For some reason I just have a little bit of a cringe reaction to the "make unique" feature because, in my mind, if the node is important enough to be referenced uniquely, then the developer should already be giving it a distinctive name.

I do see the benefits of having a shortcut to the node if that shortcut is preserved through the entire scene and is not lost when moving a node within the scene. Personally I just use onready variables for this and it's always been fine. I use find_node, but I don't worry about performance because the search is only done once and then always referenced via the variable afterwards.

jasonwinterpixel commented 2 years ago

I think a much better idea would be to allow exporting Node references

I agree with @aaronfranke

Using $Some/Long/Path syntax is error prone and should be avoided. It prevents developers from being able to freely adjust our scene trees. It means someone has to read script code before they can adjust node names or paths. If you want to change a VBox to an HBox, well now you have to read the code to see if the name is important. Want to move a button from one container to another? Read all the code first. Got a typo in a node name? search all in the project before changing it. It's more flimsy than using export(NodePath). For that reason, I think Godot should prefer to improve the usability of export(NodePath) over the proposed solution.

Here are the two syntaxes in question:

onready var add_friend_button := $Some/Fragile/Long/Path/To/A/Button as BaseButton

vs

export(NodePath) var _add_friend_button
onready var add_friend_button := get_node(_add_friend_button) as BaseButton

Only in the second syntax is the add_friend_button reference valid after renaming or moving the node. It also means that the script exposes its dependencies, so if a developer is creating a new friends UI, and they add this script to a node, they will see that it has a dependency on a node referred to by the handle add_friend_button. That handle reveals the purpose of the node, and it also contains the Type. Godot can better support the typing of NodePaths, as well.

In my opinion, $Long/Node/Path is error prone, and this proposal is merely a syntax band aid to support poor workflow choices. The names of nodes should not be critical to the functionality of the scripts.

A better solution is to offer some syntax sugar to the 2 line export(NodePath) & onready syntax that is listed above. Here's my suggestion:

export(Label) var some_label

This could work by creating a shadow NodePath variable that is exported. The equivalent of this:

export(NodePath) var __some_label__

Then godot can add a implicit get_node to fetch some_label in the same system as onready variables.

Godot should also allow type hints on NodePath exports like this:

export(NodePath, Label) var path_to_a_label

Just to add some clear contrast to the missing syntax sugar:

This godot code:

export(NodePath) var _label
onready var label := get_node(_label) as Label

Here's the equivalent of the above GDScript in Unity: (also note that the Unity code will only allow you to set Labels to the label field in the editor, godot doesnt support that)

public Label label;

Godot should solve the outlined workflow problem by adding syntax sugar to setting NodePath in the editor because it's a more powerful and useful paradigm. Every day I write dozens of export(NodePath)/onready pairs. This proposal wont change anything about my workflow and I won't use it. I'll still write dozens of export(NodePath)/onready pairs, and I'll still tell everyone who listens to me to do it that way.

pchasco commented 2 years ago

Godot should solve the outlined workflow problem by adding syntax sugar to setting NodePath in the editor because it's a more powerful and useful paradigm.

Exporting a node from within a scene is already a code structure problem, as it breaks encapsulation of your scene. Better to have methods on the root of the scene that manipulate the children, and emit any signals from the scene root for external observers.

jasonwinterpixel commented 2 years ago

Godot should solve the outlined workflow problem by adding syntax sugar to setting NodePath in the editor because it's a more powerful and useful paradigm.

Exporting a node from within a scene is already a code structure problem, as it breaks encapsulation of your scene. Better to have methods on the root of the scene that manipulate the children, and emit any signals from the scene root for external observers.

I agree, my post is in alignment with that design. The syntax I recommend supports encapsulation of scenes, since by default the editor for setting NodePaths doesnt expose nodes not owned by the current scene, but using $Long/Path/Syntax does allow developers to touch things they shouldnt.

jordo commented 2 years ago

Does this work for 'editable children' sub-scenes, and if so what happens when a sub-scene tree content is changed and/or contains a naming/alias conflict ?

theraot commented 2 years ago

@aaronfranke

I also don't like how this proposal makes scenes "exist" at runtime, in Godot scenes are groups of nodes and when instanced everything just becomes one scene with nodes in in the SceneTree.

This makes me think that perhaps every Node could be able to store such shortcuts, but just happens that the editor UI allows you to place them on the root of the currently edited scene. That way scenes don't have to be considered an special thing at runtime.

I notice none of the example are reaching outward. I think the node path syntax would look for shortcuts on the Node it currently is at.

If I'm correct, the shortcuts places form the editor UI would only work on a script attached to root node of the scene. This might not be what developers expect. Edit: I suppose nodes could look for shortcuts in their owner as fallback.


I like @jasonwinterpixel idea above.

By the way, I have seen people do this (Godot 3.x):

export(NodePath) onready var add_friend_button = get_node(add_friend_button) as BaseButton

And Godot understand that. The variable is exported as a NodePath which you can set from the editor UI, and then on ready, Godot will get the Node it points to and store it on the same variable. And yes, the variable is not typed, and will have values of different types (it will be null in _init, a NodePath in _enter_tree and a BaseButton on _ready). And, by the way, Godot offers and autocompletes the members of the Node type.

YuriSizov commented 2 years ago

For that reason, I think Godot should prefer to improve the usability of export(NodePath) over the proposed solution.

@jasonwinterpixel Those are not mutually exclusive features. Yes, we should allow exporting nodes directly, somehow. However, this proposal is an alternative to find_node and other use cases where people rely on node paths in code. I prefer to have paths defined in code, because that is where all the work is done. I don't find it useful to me to export them and use UI to select or update the nodes. It's much faster to just write the correct path, it is autocompleted and all. And it allows me to be in one context when I work.

And yes, changing it is a maintenance, but I don't imagine a flow where someone is changing node composition of the scene, — nodes types and all, — and doesn't look at the code and doesn't change anything in code. This can only happen if you simply move the nodes, but such a change can be quickly reflected in code. And a change from HBox to VBox is not going to affect anything if you give your nodes names based on their semantic quality, which I always do when I organize scenes.

I'm not a fan of using find_node, but some people do use it, and sometimes I have to work with such code. To me, this proposed solution would make code in these cases clearer (and faster!) without the need to move part of the logic to the editor's UI. And, again, I don't think it is competing with exposing nodes, a totally different case that we should also support.

jordo commented 2 years ago

If i understand this proposal correctly, this is basically adding a map of stringnames (unique key) to node references, into the scene definition/schema?

jasonwinterpixel commented 2 years ago

Yes, @pycbouh they are not mutually exclusive. But time is the great excluder and I am here to advocate for what I would prefer is prioritized.

I don't imagine a flow where someone is changing node composition of the scene, nodes types and all, and doesn't look at the code and doesn't change anything in code.

Large game development studios often employ more designers than developers. The way to enable designers to make changes to a scene is with export(NodePath).

YuriSizov commented 2 years ago

Large game development studios often employ more designers than developers. The way to enable designers to make changes to a scene is with export(NodePath).

I'm not talking about the context. I really can't imagine what kind of changes can you do to a scene where you won't need to adjust your code somehow. It almost never comes to simply moving nodes around, when you run into a problem or a feature that requires changing a scene. It's almost always that you need to change the behavior as well.

pchasco commented 2 years ago

And yes, changing it is a maintenance, but I don't imagine a flow where someone is changing node composition of the scene, — nodes types and all, — and doesn't look at the code and doesn't change anything in code. This can only happen if you simply move the nodes, but such a change can be quickly reflected in code. And a change from HBox to VBox is not going to affect anything if you give your nodes names based on their semantic quality, which I always do when I organize scenes.

This is commonly done when creating a pluggable scene.

I'm not a fan of using find_node, but some people do use it, and sometimes I have to work with such code. To me, this proposed solution would make code in these cases clearer without the need to move part of the logic to the editor's UI. And, again, I don't think it is competing with exposing nodes, a totally different case that we should also support.

The engine feature proposed can be easily solved by the developer by simply adding this line to their script, and using that to reference the node. Nodes of "importance" in a scene should probably be more formalized within the scene by creating variables anyway.

@onready var name_textbox = find_node("name_textbox")

I understand that creating a feature such as that proposed may make some people's lives a bit easier because they would not need to be as careful about node naming, but we should also consider that new features come with a cost. The cost must be paid both initially, and ongoing as long as the feature remains in the application. I wonder if the little bit of convenience for the developer is worth that cost.

Plus, as others have said, the "make unique" feature proposed is more of a developer's concern rather than a designer. Developer concerns should be kept in the code as much as possible.

jordo commented 2 years ago

I really can't imagine what kind of changes can you do to a scene where you won't need to adjust your code somehow. It almost never comes to simply moving nodes around, when you run into a problem or a feature that requires changing a scene.

I think this is really not representative of artists or designers who work within the context of a team in godot. This is exactly what designers do all the time, as they want to adjust layouts, alignments, an additional padding nodes or transform containers, etc, etc. They want to do this without having to understand gdscript or change code.

YuriSizov commented 2 years ago

I think this is really not representative of artists or designers who work within the context of a team in godot. This is exactly what designers do all the time, as they want to adjust layouts, alignments, an additional padding nodes or transform containers, etc, etc. They want to do this without having to understand gdscript or change code.

Half of that is not done by moving any nodes, or breaking node paths. Again, I'm not against exporting nodes. I'm in favor of it, it's very useful. I just think that this proposal is also useful, because if we don't have this proposal, we are adding find_node back, and that's really not great.

jordo commented 2 years ago

Half of that is not done by moving any nodes, or breaking node paths. Again, I'm not against exporting nodes. I'm in favor of it, it's very useful. I just think that this proposal is also useful, because if we don't have this proposal, we are adding find_node back, and that's really not great.

You are interpreting my one-off example list too literally, This is simply not true, moving any nodes, and breaking node paths is exactly what it involves, very often.

jasonwinterpixel commented 2 years ago

The proposal has value, I'm just here to represent that I would prefer syntax sugar on export(NodePath) be prioritized higher than this feature, as I think export(NodePath) should be preferred over the alternatives.

jordo commented 2 years ago

I think I would like to understand a little more how the proposal is better than find_node, other than the advantage or ability to remove a local var declaration. But perhaps that is the main one.

Presumably an @ lookup syntax still has runtime costs correct? It will need to figure out what subtree of a running scene tree (map) to lookup in and addition to a map lookup, rather than a DFS for find_node?

The proposal should elaborate more around how the proposal is better than find_node. find_node seems to be a more robust method to resolving a dynamic path at runtime.

My concern with the map lookup is how it is going to integrate with composable objects and scenes. Scripts that use the @ syntax, need to be fully aware of the local scene they are working in, but often I find it very useful to compose scenes which include subscenes with 'editable children'. This allows for more modularity when building reusable components.

image

So a question: Will the @ syntax work for referencing the SDF node from the script attached to say TankGame (SDF is a node in a sub scene that is exposed with 'editable children'). From my understanding of the proposal, it would not work, but find_node would in this case. And if it does work in this case, how will it resolve alias conflicts or collisions?

reduz commented 2 years ago

@jasonwinterpixel

I'm just here to represent that I would prefer syntax sugar on export(NodePath) be prioritized higher than this feature, as I think export(NodePath) should be preferred over the alternatives.

I agree that also needs a better syntax sugar, but I think its a different problem and requires a different proposal. We did talk about this with @vnen.

nonunknown commented 2 years ago

This proposal will for sure solve the "get node hell" that most users has, here are my finger on this.

before I dig into other stuff, first I think the Make Unique option is cool, and also a gdscript way should be implemented, like we have @tool we can have something like @unique_node or @node_alias, also when you do via menu RMB if the node has a script the above tag will be added to the top of the script!

In terms of accessing the node the examples you provided, still produces a "semi get node hell ". A way to solve that is by having the unique nodes like a Singleton, example:

script A: @unique_node uniqueNameHere or @unique_node(uniqueNameHere)

script B:

var test := uniqueNameHere or uniqueNameHere.something() also uniqueNameHere.get_child("node") or $uniqueNameHere/Label

via RMB after clicking Make Unique, a popup is shown by showing the unique name which is the node name, but the user can change!

jasonwinterpixel commented 2 years ago

I'd recommend not using "Make Unique" on the node since it conceptual collides with "Make Unique" on a resource. Perhaps this feature should be conceptually known as "Node Aliasing", and it should be "Make Alias" or something like that. Just something other than Make Unique

jordo commented 2 years ago

Does this make sense to expose at runtime though? It seems very editor oriented, and mappings end up being baked into the .tscn definition correct? Then LUT's are loaded at runtime into nodes that are the sub-tree 'owners' yes?

I guess I'm just curious if the syntax works with editable children, and if so how. As a concrete example, here's another screenshot from how we are currently doing, and customizing, a settings dialog. The dialog is composed of multiple sub-scenes, menu's, other dialogs, buttons, etc, and any special code logic happens in the scene root's script.

image

Right now in the above screenshot you can change the tree any which you we want because all the important node references in the script root are done as NodePaths which update. A good user-story of this occurring is when say we were modifying this scene, and some contents out-grew some container... The solution for this was to add a new node, ScrollContainer, somewhere mid scene tree. It was easily added mid-scene, the tree paths end up being totally different, and nothing breaks. And this also works with sub scenes with editable children.

So would just like to know if in the above screenshot I can do @NoButton and @DebugSettings in the UISettingsDislog script, and it will work as expected. If @NoButton works, is this something I make unique in this scene, or in it's own scene instance?

theraot commented 2 years ago

@jordo For editable children it should work, like this:

$@SubScene1/@Label1.text = "Hello" # Still works 

I copied the example from OP.

That is, you would have the path to the scene instance (which could very well have a unique name), and then you can use "/@" to refer to a unique name inside the sub-scene.

nonunknown commented 2 years ago

@jasonwinterpixel good point, I just edited from make_unique to unique_node which I think that makes sense!

jordo commented 2 years ago

The advantages to doing things with NodePath references, especially in the editor, is anyone including designers can see what node ref dependencies any scene root class has. If I see a NodePath dependency called 'Log Out Button', it's intuitive at a glance if the dependency is hooked up or not. If it's done with aliasing or @syntax, it's purely in code, there's no visual cue there, and it requires inspection of code to see if there is anything hooked up or if some ref is broken or disconnected.

There are some advantages doing this more with NodePaths, and leaning into a more NodePath oriented design or solution, because NodePath references already work with say renaming, they are kind of scene-oriented already, and there's a single hookup/failure spot. I can rename a node, and it's path reference automatically updates. I can also see, from the editor, with a quick glance if things are connected properly. With this proposal, can you rename an aliased/unique node? If so then any code syntax with @ will presumably break. The correlation and/or connection is 'hidden', and then someone will probably find that out at runtime.

Does the @ syntax from the node or target of invocation move up and down a scene tree? I.e., if I have a script attached to a node mid-way through a scene tree, does that @syntax map to a node alias from a grandparent? And does this also work with editable children and sub scenes? Anyways, just lots of things to consider, potential edge cases, and I want to understand the rules on how the syntax works in different scenarios. Does it work prior to scene-tree enter, etc?

This is what currently works with NodePaths right now, it's pretty good! Moving, renaming, scene tree path modifications, dependency injection, etc.

https://user-images.githubusercontent.com/323868/155408872-dd0c0ca7-3d9e-4038-99fa-1b07bd1fe93e.mp4

jordo commented 2 years ago

Basically just saying that, if you have a script that does something like the example in the OP proposal:

$@SubScene1/@Label1.text = "Hello" # Still works 
# Instead of
$Path/To/SubScene1/VBoxContainer/HBoxContainer/Label1.text = "Hello"

What that means is that your script has a dependency on

$@SubScene1/@Label1

existing, and being resolved, as it's going to be writing a value of "Hello" to the .text property. If that dependency isn't resolved, (maybe you forgot to make a node 'unqiue', or maybe you have a typo, etc), things will break.

The proposal is OK, but sometimes not adding more ways to do things is OK too. NodePaths are nice right now because they auto-complete, there's a nice dialog picker for the editor etc. So I also kind of agree that a proposal surrounding improving the NodePath dependency/requirement can potentially improve what this proposal is also trying to improve. It seems somewhat related and definitely not mutually exclusive to me. It basically is a way to reliably reference a dependent node from a base script, with the minimal amount of effort and chance to misconfigure. But this proposal still requires multiple steps. 1) Write code that contains $@TargetNode syntax. 2) Make sure there is a node named TargetNode in the current scene (is there editor autocomplete with this?), and 3) make sure the TargetNode is configured to be a unique node. If any of those things aren't typed right, it won't work and you won't find out until runtime.

I think syntactic sugar around around node/nodepath dependencies can potentially solve the same problem. Say

@Required RequiredLabel1 : Label

Then you can click/choose it from the editor (editor can open up a scene tree node chooser, much like how NodePaths work right now), and you can just select the label you want wherever it is. There's no chance for typo error, as the connection is explicit and visual, and in your code you can still type:

RequiredLabel1.text = "Hello"

And the hookup is still in the editor, although just with the tree's node chooser dialog, instead of an explicit Node name + RMB + make unique click.

jordo commented 2 years ago

Or even better yet, after typing @Required in the gdscript editor window, the editor could prompt you to select a target node from the current scene (much how the current autocomplete for typing NodePath's work) to set this reference to. After selection it could even auto fill the type as well, which would result in code that looks like:

@Required RequiredLabel1 : Label

Could work much how the current autocomplete for typing NodePath's work, except you are declaring a node reference (which could be backed by a NodePath as an implementation detail).

Would be cool to see something along these lines, maybe with @Required syntax, or @Optional syntax, which could convey correct operation if a dependency is unset / unconnected.

pchasco commented 2 years ago

I like the idea of @required, but would extend this to the idea of exports in general and not only limited to nodepath. If a value is not supplied then the project should not compile. If compile-time validation is not feasible, then a check at runtime to ensure all @required exports are satisfied at the end of the ready event might be OK.

The original proposal adds additional complexity to the editor and engine to solve a problem that already has at least two solutions: Use an exported nodepath variable, or assign an @onready variable with find_node with appropriately named nodes. The cost to find_node is trivial if it is only being called at ready.

theraot commented 2 years ago

@jordo @pchasco How about @required generating a node configuration warning instead (the warning that appears as a yellow triangle next to the node in the scene editor), which is a feature that already exists.

Perhaps it can be expanded so that it does not only shows warnings but also errors. And if there are errors, have a prompt asking the user to confirm if they want to build the project anyway.

However, I think these should be two separate proposals: one for @required, and one for node configuration errors.

pchasco commented 2 years ago

@theraot That seems decent to me. We should obey conventions whenever possible.

YuriSizov commented 2 years ago

The original proposal adds additional complexity to the editor and engine to solve a problem that already has at least two solutions: Use an exported nodepath variable, or assign an @onready variable with find_node with appropriately named nodes. The cost to find_node is trivial if it is only being called at ready.

find_node has been removed in master. So there is currently no solution for non-designers. What reduz is proposing is a solution that would fill find_node's shoes better instead of restoring that potentially misused method.


Let's please stay on topic. Export validation is an interesting idea, but it deserves its own proposal instead of taking time from discussing the feature proposed here.

ReneHabermann commented 2 years ago

It seems to me this is really about performance, as the node would still be addressed by its name and i'd consider $@Label1 not more readable or writable than find_node("Label1"). If that's correct, couldn't this just be a compile time optimization? As in "every find_node(x) is replaced with a direct reference to x, if x is already present below the calling node"?

pchasco commented 2 years ago

@pycbouh I wasn't aware find_node was removed. That's a bummer. Find_node was the only thing that made working with nodes tolerable. I wonder if removing the find_node tool was not the correct decision since we're now looking for ways to get the functionality back without calling it find_node?

YuriSizov commented 2 years ago

If that's correct, couldn't this just be a compile time optimization? As in "every find_node(x) is replaced with a direct reference to x, if x is already present below the calling node"?

find_node accepts any string input. As such, the nodes it not guaranteed to be there at "compile" time. It can also be a variable. This cannot be optimized, it's purpose is to do a full tree lookup. Which is why it was a long standing desire to remove the method, and so it was done recently.

YuriSizov commented 2 years ago

@pycbouh I wasn't aware find_node was removed. That's a bummer. Find_node was the only thing that made working with nodes tolerable. I wonder if removing the find_node tool was not the correct decision since we're now looking for ways to get the functionality back without calling it find_node?

It wasn't completely removed, it was replaced with a more general purpose and versatile find_nodes() (see https://github.com/godotengine/godot/pull/56032). Which in turn made it ugly to use for onready node references (see https://github.com/godotengine/godot/issues/58412).

As we were discussing the restoration, reduz proposed this alternative. It would be desirable to not introduce a potentially dangerous and inefficient method, that is so easily misused. So yeah, we do want to have this functionality without actually doing the same thing internally and implement it smarter instead.

This is the full context for this proposal to happen, I'm not sure why reduz didn't mention it in the OP.

MarioLiebisch commented 2 years ago

I think the proposal is mostly good. I would most likely rename the "Make unique" thing, as it's already used to make resources unique and is thus a little bit confusing.

Maybe we could name it somehow like "Mark as path shortcut"? or somehow like that?

Was about to mention the very same thing. This feels potentially very misleading. How about calling it Manage Alias instead?

However, the more I think about it, the more likely this might be better suited as a new pair of annotations? That's clearly not necessarily as fancy as some menu item and a dialog box, but then again these names would mostly (only?) be used in scripts anyway, so might make more sense to put this there.

Also they wouldn't have to be unique and instead find the first matching element (which might be useful in some situations, too).

# Any child node may access this node under this name (move up until a matching alias is found)
@local var something_here = ...

# Any child or parent node may access this node under this name (move up, if nothing is found, move into children, their siblings, then grandchildren?)
@global var something_there = ...

# Make the current node accessible
@global @onready var PLAYER = self
TokisanGames commented 2 years ago

Having quite a large project in Godot, I don't see this proposal as something we would need or use.

What might be useful is a globally unique name so I can access get_node("@node") in any loaded scene anywhere (attached to the tree or not). But locally unique doesn't provide any clear benefit above what is already provided.

We already specify nodes in our scripts. Changing this when we update the path is simple.

onready var button = $Control/HBox/Button
... 
button.text = "Submit" 

And we occasionally use find_node when the structure is dynamic. Globally unique names might help this, as well as other uses I can think of in our in game scene loader and scene management. But a locally unique name doesn't do anything helpful for us.

Also it shouldn't be called "make unique". That phrase is taken. I would call it "make global" given my described functionality. For the current proposed functionality, someone's idea of "make alias" is fine.


Also what would be useful are unique resource identifiers in scene files (instead of incremented id numbers) so they can be mergeable with git and allow multiple people to edit scene files. @reduz you mentioned something about this months ago. Was this proposed or implemented?

aaronfranke commented 2 years ago

@tinmanjuggernaut Globally unique nodes already exist in the form of AutoLoads and can be accessed with $"/root/MyNode".

MarioLiebisch commented 2 years ago

I do see the benefit of offering a "find" operator within the node path. This would solve the issue of deep node paths, as well as the extreme pain of having to fixup node path references after moving something within the tree.

$DialogNode/@NameControl.text = "Hi"

How about using other path syntax forms users might be familiar with?

# find first node anywhere
var something = $/**/something

# find first direct child
var something = $*
# same
var something = $**

# find first direct child with a name starting with `a`
var something = $a*
# find any child with a name starting with `a`
var something =$**/a*

# find first direct child with a child `label`
var something = $*/label

# find any child with a child `label`
var something = $**/label
starry-abyss commented 2 years ago

Not a bad idea as a whole, but please don't convert GDScript to Perl with these $@ things.

How about one gives a node an alias, and under this alias it auto-registers itself equivalent to @onready var Alias = ...., but without actually adding this code in the text file? We have visual connection of node signals to functions already (and they are not out of place because one side, the scene tree, is visual), why not also have visual connection of nodes to variables?

EDIT: To @aaronfranke below, I'm not referring to anything global to the game, please take a look at the signal connection UI, if you haven't used it before, that's what I'm referring to. In the script the glue code is absent, it's only shown in the form of a mark near the line number. This allows to not add new operators like @, as it doesn't matter if the equivalent full code to make the connection is long ("connect(....)") or not.