How to break out of a function that has an in-progress yield?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Diet Estus

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?

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?

Ertain | 2018-11-11 16:59

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.

Diet Estus | 2018-11-11 19:07

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.

Ertain | 2018-11-12 02:03

:bust_in_silhouette: Reply From: avencherus

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. GDFunctionState — Godot Engine latest documentation

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

NOTE: An issue discussion on this type of yield and signal behavior was created here: Get a error when resume() was called to close the coroutine · Issue #43187 · godotengine/godot · GitHub

avencherus | 2020-10-30 07:12