godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.14k stars 93 forks source link

Please allow yielding on signal results #3964

Open Shadowblitz16 opened 2 years ago

Shadowblitz16 commented 2 years ago

Describe the project you are working on

Spaceship game

Describe the problem or limitation you are having in your project

I am creating a menu and I need to wait till all the functions connected to a emitted signal are finished

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

Allow us to yield on a signal and wait for it to finish

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

tool extends Button

signal post_pressed

var locked := true

func _ready():
    Signals.bind(self, "pressed"     , self, "_pressed")
    Signals.bind(self, "post_pressed", self, "test")
    reset()

func reset():
    $Animator.seek(0, true)
    $Animator.play("default")
    locked = false

func _pressed():
    if !locked:
        # lock and flash for a while
        locked = true
        $Animator.play("flash")
        $Timer.start  (wait_time)
        yield($Timer, "timeout")

        # hide
        $Animator.play("hide")

        # Emit and Wait for all connected methods to finish
        emit_signal("post_pressed")
        yield(self, "post_pressed")

        # If nothing happens reset
        reset()

func test():
    yield(get_tree().create_timer(1), "timeout")

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

It would probably be used in networking if supported, also I think it can be worked around but it requires something like this...

extends Node

signal finished

class SignalCount:
    var count := 0
    signal complete

    func increment(_source:Object, _signal:String)->void:
        var err =  _source.connect(_signal, self, "decrement", [], CONNECT_ONESHOT)
        if  err == OK:
            count += 1
        else:
            print(err)

    func decrement():
        count -= 1
        if count == 0:
            emit_signal("complete")

var count := { }

func emit_counted(_source:Object, _signal:String, _args:=[]):
    var countedSignals = SignalCount.new()

    for connection in _source.get_signal_connection_list(_signal):
        countedSignals.increment(connection["source"], connection["signal"])

    call_deferred("emit_signal", _signal)
    yield(countedSignals, "complete")
    emit_signal("finished")

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

It helps avoid node coupling It could be used in networking too.

KoBeWi commented 2 years ago

But this is how it already works... When you emit a signal, all connected methods are called immediately, unless you connect them with DEFERRED flag.

Zireael07 commented 2 years ago

They are called immediately, but you need to manually do some black magic if you want stuff to happen on/after the last one happens.

KoBeWi commented 2 years ago

No, the code after emit is executed after all calls are finished.

Zireael07 commented 2 years ago

Aah, nice trick that isn't documented well enough

dalexeev commented 2 years ago

No, the code after emit is executed after all calls are finished.

Only synchronous part (before the first yield/await).

KoBeWi commented 2 years ago

Well, if something is called asynchronously in a signal callback (e.g. using deferred call), there is no way to tell that it was called from a signal. You should use a second signal in such cases.

Shadowblitz16 commented 2 years ago

But this is how it already works... When you emit a signal, all connected methods are called immediately, unless you connect them with DEFERRED flag.

If this is the case then its a bug. Right now it executes immediately even if I yield on connected signals

also even if they are called immediately it doesn't mean they will finished immediately, the connected signals could have yield code

Well, if something is called asynchronously in a signal callback (e.g. using deferred call), there is no way to tell that it was called from a signal. You should use a second signal in such cases.

This isn't really a good idea either it introduces coupling of nodes, the point was that it may not have a signal connected to it and in that case it resets.