+3 votes

Suppose I have a function whose structure uses yield() in the following way:

func my_func():
    tween.start()
    yield(tween, "tween_completed")
    some_other_func()

Suppose that I call this function and before the tweening completes I decide that I no longer want the remainder of the function to be called.

Is there a way to manually break out of the function when it's yielding?

in Engine by (1,533 points)

I don't know about when the function is yielding, but is there a way to check when the tween stops, and if it completes as normal, you could still yield it?

Yeah, I could definitely build a workaround by adding some safety conditions into the function itself, for example:

func my_func():
    tween.start()
    yield(tween, "tween_completed")
    if some_bool:
        some_other_func()
    else:
        pass

But I'd prefer if there was simply an easy way to break out of the function externally, otherwise I'll have to muddy up things with all kinds of single-use bools.

If you stop the tween before it gives off "tween_completed", the yield function will still go through? Can't really think of another way around this. I did find this answer, but that involves using a signal, and I don't entirely know how that will fit into this problem.

1 Answer

+2 votes
Best answer

EDIT / WARNING: I updated the example to clean up the SceneTreeTimer, as it will still be counting and call resume() twice. This fixes an error, but adds some distracting complexity to the code. The timer code can be ignored, its only to serve the example. If timers with yields are needed, I recommend using your own timers to have better control over them.


In addition to what you're doing, if you need to cancel a yield, you will want to keep track of the GDFunctionState that the yield() returns. https://docs.huihoo.com/godotengine/godot-docs/godot/classes/class_gdfunctionstate.html

A yield will return the function immediately with that object, and you can hold on to it and use it as you like.

First check if it is still is_valid(), and then call resume() to cancel the yield.

Additionally, you want to exit the function that is doing the yielding. To do this you can pass optional arguments into the resume(args), and capture them in var and then use them in a following if statement like you have above.

In your case, it is just return.

Here I have an example of 5 second timer, where I'm using a button to do the cancel.

The flow can be confusing, since things are jumping around.

extends Node2D

const CANCEL = true

var timer
var pending_yield

func _ready():

    pending_yield = do_stuff()

    # This what comes back out of the return from a yielded function.
    print(pending_yield)



func _on_Button_pressed():

    print(pending_yield.is_valid())

    if(pending_yield.is_valid()):

        # Resume can pass a value back.
        pending_yield.resume(CANCEL)

        # Cleans up SceneTreeTimer
        # Otherwise it will call resume() too.
        timer.unreference()
        timer = null


func do_stuff():

    timer = get_tree().create_timer(5.0) # Keep reference to timer.
    var result = yield(timer, "timeout")


    print(result)

    if(result and result == CANCEL):
        print("The yield was cancelled. Stopping the function.")
        return

    print("Waited 5 seconds... carrying on.")
    timer = null
by (5,192 points)
edited by

NOTE: An issue discussion on this type of yield and signal behavior was created here: https://github.com/godotengine/godot/issues/43187

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.