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

Value of @export variables uses setter function after init #91882

Closed
KoB-Kirito opened this issue May 12, 2024 · 2 comments
Closed

Value of @export variables uses setter function after init #91882

KoB-Kirito opened this issue May 12, 2024 · 2 comments

Comments

@KoB-Kirito
Copy link

KoB-Kirito commented May 12, 2024

Tested versions

4.3.dev6

System information

Windows 10 - Godot 4.3.dev6 - Forward+

Issue description

Custom setters and getters are not called when a variable is initialized. That includes the value that gets assigned via @onready. docs
That is actually a good thing in my opinion, though I noticed that this is not the case for the value that is assigned via @export.

This causes problems when you want to change values of child nodes in the setter of an exported var, because at the time the value is applied the child is not loaded yet.
If you use a @tool script, you also get an error every time you open that too.
If you set a value for an exported variable, it gets assigned in-game after init, but still before enter_tree, and it calls the custom setter.

This behavior feels weird to me. I tested for hours to get why this is happening.
Semantically the value I set in the editor replaces the initial value, it's just a shortcut to do that not in code. And in my opinion it should be handled the same. At least at the moment it is reapplied on init.
When I use a @tool script, it should call the setter, because then I might want to do something with it in that moment. It might change the value of other nodes, but then it is fine. Those are loaded and the changed values will get saved too. But those values should not call the setter afterwards.

In short, if you change a value yourself actively, the setter should be called.
If the engine applies that value internally if that scene is loaded it should not call the setter.

TLDR:

  • The moment you change an exported var in the editor, the custom setter should be called.
  • The moment that stored exported value gets reapplied at init (at game-start or when that scene is loaded again), the setter should not be called

Steps to reproduce

  • @export a variable
  • add a custom setter that accesses a child node
  • change the exported value
  • breaks when the scene is loaded and the export value is applied while the child node is null

Minimal reproduction project (MRP)

getters-and-setters.zip
Did some testing to get a grip of how it actually works.
The main problem can be seen in test_access_children.tscn.

@dalexeev
Copy link
Member

Related:

Calling a setter when assigning a variable is a GDScript language feature. It has nothing to do with the core scene/resource (de)serialization system. If a property is stored in a scene/resource file, then during deserialization this property will be assigned, and therefore the setter will be called.

From GDScript point of view, the setter is not called only in a few cases: for an initializing expression and when assigning to a variable within its setter. See When setter/getter is not called for details.

From the core point of view, scenes/resources do not serialize default values (and some other cases), in this case the property is not assigned and therefore the GDScript setter is not called. But in other cases this happens. Core doesn't know anything about GDScript setters, it just calls Object.set(). It makes no difference whether the property is a native one, a GDScript variable, or a user property handled in _set().

Regarding the main problem you mentioned:

This causes problems when you want to change values of child nodes in the setter of an exported var, because at the time the value is applied the child is not loaded yet.
If you use a @tool script, you also get an error every time you open that too.
If you set a value for an exported variable, it gets assigned in-game after init, but still before enter_tree, and it calls the custom setter.

I agree with you that this causes certain inconveniences, but I do not agree with the solution you propose. I think the problem is not that the setter is called, but when the variable is assigned (and hence the setter is called). See also:

Unfortunately, @onready and potential @oninstantiated annotations do not solve this problem because they do not affect when core assigns the property (see the warning in @onready annotation). We probably need to add a new property usage flag that tells core that the exported value should be assigned after all children have been added.

In the meantime, there are two workarounds:

  1. Add child nodes in _init() instead (not very convenient).
  2. Use guards like:
var my_property:
    set(value):
        if not is_node_ready():
            await ready
        # ...

@vnen
Copy link
Member

vnen commented May 18, 2024

Yes, this is working as intended. If there are suggestions to change it should be done as a proposal (or discussion) in the proposals repository.

@vnen vnen closed this as not planned Won't fix, can't repro, duplicate, stale May 18, 2024
@vnen vnen added the archived label May 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants