I don't understand how await is a replacement for yield

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

In Godot 4, yield has been removed and await is marked as a replacement, but they don’t appear to function comparably.
I’m reading the docs on the subject.

My understanding was that yield allowed you to pause a function part way through, storing its state, and then continue it with another call. It’s useful if you have a function with a loop in it that returns a number of values or does a certain thing for a period of time(for example, once per process loop).

await, on the other hand… I’m actually having trouble understanding what it’s actually useful for. It needs signals to operate at all? And it doesn’t allow for looping. It’s so different that I’m having trouble understanding why it’s considered a replacement. Like it can do one of the things yield could do?

:bust_in_silhouette: Reply From: Juxxec

The await keyword functions the exact same way the yield keyword does. The only thing that changes here is the syntax.

Before you would use:

yield(get_tree().create_timer(1.0), "timeout")

var value = yield(some_function(), "completed")

Now you simply use:

await get_tree().create_timer(1.0).timeout

var value = await some_function()

I haven’t tested the last part, but I am pretty sure it should work as before. The difference here is that you do not use the completed signal as before when waiting for values, as opposed to signals.

Personally, I prefer the old yield syntax as it is more Python like. The new await keyword is more reminiscent of javascript.

:bust_in_silhouette: Reply From: FancyMongoose

I had similar problems to you I think. The problem seems to be that GDScriptFunctionState was removed from the engine. This means that out of the box some scenarios like looping, etc are a bit tricky to handle. I solved this by writing a wrapper class the gives me similar behavior to GDScriptFunctionState. Maybe it will be helpful.

#simple wrapper that helps you in situations where you still need
#behavior similar to GDScriptFunctionState
class CoroutineWrapper:
	signal resumed
	signal stopped

	var completed = false

	func _init():
		stopped.connect(_on_stopped)
		run()

	func resume():
		resumed.emit()

	func run():
		pass

	func _on_stopped():
		for item in [resumed, stopped]:
			var sig: Signal = item
			for connection in sig.get_connections():
				var callable: Callable = connection.callable
				sig.disconnect(callable)
		completed = true

An example of usage.

extends Node
var count_to_ten: CountToTen

class Abc extends CoroutineWrapper:
	func run():
		print("\tA")
		await resumed
		print("\tB")
		await resumed
		print("\tC")
		stopped.emit()

class CountToTen extends CoroutineWrapper:
	func run():
		var idx = 0
		while idx <= 10:
			print(idx)
			if idx == 5 or idx == 2:
				var abc = Abc.new()
				while abc.completed == false:
					abc.resume()
					await resumed
			await resumed
			idx += 1
		stopped.emit()

func _ready():
	count_to_ten = CountToTen.new()

func _process(delta):
	if count_to_ten == null:
		return

	if count_to_ten.completed:
		print("Stopped")
		count_to_ten = null
		return
		
	count_to_ten.resume()

And the output

0
1
2
	A
	B
	C
3
4
5
	A
	B
	C
6
7
8
9
10
Stopped

It’s a little bit more work, but it helped me figure out how to port my 3.x code bases to 4.0. I use co-routines a lot. Hopefully it can help you too :slight_smile:

Thanks for putting this example up! Its great to see a fully working example with await and resume() nicely packaged into a single class.

mellowed | 2023-04-25 06:34