godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 88 forks source link

Add support for providers (dependency injection) in GDScript #8322

Open TheColorRed opened 11 months ago

TheColorRed commented 11 months ago

Describe the project you are working on

A top-down shooter.

Describe the problem or limitation you are having in your project

An easier way to pass a value/reference (or anything) from a parent to a child (1 or more nodes/scenes deep).

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

You have something that you want your child to have access to, you could put the node in a group, but then that is global and the same for anything that wants to use it which doesn't always work. Using dependency injection (what I will be calling it) you create a reference to the item at that node's level, it is then accessible in all of its children and grandchildren. This allows for two objects to be at the same level and have different values that get provided to the children. For example each could have a different color "red" and "blue". The child would then have this value injected into it and it now knows the color of it's parent. If it changes parent the color would change to the parents color.

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

Note: Assume each node is a packed scene (in which you normally wouldn't be able to see it's children in the current scene). image

I am not sure if this is the best syntax, but how this works is; you provide a variable with the @provides decorator, this then provides its value to it's children (recursive). A child can then override it's provider with it's own @provides (with the same name), and that will then be provided to its children (as seen in the image above).

So, take these two properties:

# provides_5.gd
@export @provides var amount: int
# provides_10.gd
@export @provides var amount: int

They will set the provided value that the children can use.

When the following script is a child of a node that @provides a value it uses that value. You could then move the node anywhere in the scene and its provided value will then change.

# provided.gd
@provided('amount') var amount: int
#         ^^^^^^^^ This is the name of the provided variable from `@provides`.

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

You could use a group, but groups are global and not unique to each node.

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

This enhances GDScript.

TamerSoup625 commented 11 months ago

If I understood correctly, you want a property of a node to sync with a specific property for all its chidren and grandchildren.

If setting the property is done more often than getting it, it can be worked around by having the "provides" node search a property in its ancestors until it finds it:

var provided_value
var ancestor = get_parent()
while ancestor != null:
    if "amount" in ancestor:
        provided_value = ancestor.amount
        break
    ancestor = ancestor.get_parent()

If getting the property is done more often, you can rather use a recursive function for the provider that sets a property for all its children and grandchildren:

Autoload:

func provide(provider, property, value):
    for i in provider.get_children():
        if property in i:
            i[property] = value
        # Propagate to children
        provide(i, property, value)

Provider:

var amount:
    set(value):
        amount = value
        Autoload.provide(self, "_amount", value)

Provides:

var _amount # Set by ancestors when the value changes

This implementation no longer works if you have to reparent nodes, but it can be worked around by using the other implementation when doing so.

In general, I feel like this feature wouldn't be used very much, and if you need it you can use some lines of script or make a plugin

TheColorRed commented 11 months ago

@TamerSoup625 I was actually able to make something similar this morning to what you just posted. It works really well in my opinion, instead of searching properties on the class I store the reference information within the metadata.

RedMser commented 11 months ago

Since the UI theme system works like how you propose (parent specifies a theme which contains certain overrides, which propagate down through the tree), this proposal seems to be related: #2832

MarioLiebisch commented 11 months ago

I think something similar can already be done by putting your information into your own resource, then instantiate it once and share it between every node that needs it (passing references).