In GDScript, the "await" keyword cannot replace "yield"

Godot Version

4.21

Question

I’m looking at GDScript…

From my perspective of dealing with Lua, coroutines were absolutely necessary.

I found the ‘yield’ keyword…
'A function with that keyword becomes a coroutine…
When calling yield, a coroutine object is returned, and by calling the ‘resume’ function on the coroutine object, it is possible to proceed after ‘yield’.

It was similar to the coroutine seen in Lua and was very easy.


func my_func():
print(“Start”)
yield()
print(“Resume”)

func_ready():
var y = my_func()
# Function state saved in ‘y’.
print(“Waiting to resume”)
y.resume()
# ‘y’ resumed and is now an invalid state.


[Users can control coroutines very simply by looking at the code]

So, when using Lua, I gave each game object a coroutine and ran it like a micro thread…

The problem is that this ‘yield’ keyword is only supported up to version 3.5 of GDScript.

In GDScript, ‘await’ was added instead of ‘yield’ keyword…
It was very difficult to understand.

It has to be used with signal…

I’m not sure how to use it like ‘yield’ and ‘resume’.

Please tell me how to write it like ‘yield’

In Gdscript, await works similarly to how yield worked. Await is not only used with signals.

You can use await until a function returns a value

await sample_function()

func sample_function() -> bool:
   return true

You can also await a signal

Signal resume

await resume

This will await until you call resume.emit() somewhere. This can be used with all node signals or anyone you setup yourself.

You can await a timer.

await get_tree.create_timer(2.0)

This is will wait for 2 seconds

You can use the second option to mimic the behaviour you described in Lua. Just create a signal call resume and await that signal.

3 Likes

What I wanted was to be able to remove this coroutine as well when removing the game object from external code.

So that the coroutine function does not proceed anymore.

Since the ‘await’ method does not return a coroutine object, the coroutine function must continue even when the game object is removed…

So, the logic within the coroutine function must be completed by checking the exception flag every time and returning from the coroutine function if it becomes true.

Is there a way to remove this coroutine when removing the game object?

In the GDScript manual, I saw that a GD coroutine object is created when ‘await’ is used. However, since the GD coroutine object was not returned when calling ‘await’, I was curious where the GD coroutine object was.

I tested it to see if a GD coroutine object was created inside the node.

I created a coroutine class for my task that inherited Node and called ‘await’ in the run function.

I tested whether the GD coroutine object was actually created inside the node.

class_name CoroutineManager
static var _co : Coroutine
class Coroutine extends Node:			
	func run():		
		print("test 1")		
		await get_tree().create_timer(2).timeout
		print("test 2")
		pass

static func test_run(node : Node):
	_co = Coroutine.new()	
	node.add_child(_co)
	_co.run()
	
static func myFree():
	_co.free()

When calling test_run, a My Coroutine object is created and the run function is called.
print(“test 1”)
Call and rest for 2 seconds (await get_tree().create_timer(2).timeout)
print(“test 2”) was called

What I wanted was to remove the node that called ‘await’ while resting for 2 seconds (await get_tree().create_timer(2).timeout).
If print(“test 2”) is not called,
I can see that a GD Coroutine has been created inside the node that called ‘await’.

-------# This is another external code

    print("Main Start")
	var mgr = CoroutineManager.new()
	mgr.test_run(self)
	await get_tree().create_timer(1).timeout
	mgr.myFree()
	print("Main End")

The key here is the code above
After calling the test_run function
via “await get_tree().create_timer(1).timeout”

As soon as I rest for 1 second, the node (class Coroutine) object I created is removed.

When I run this code, as expected, print(“test 2”) is not called and

If the wait time is greater than 2, print(“test 2”) is called.

The conclusion is that when ‘await’, a GD Coroutine object is created inside the node that called ‘await’, and when the node is removed, the GD Coroutine is also removed.

If I misunderstood, please reply.

You are right. When you free a node, the scripts attached to it can no longer run.

1 Like

This allows me to control the creation and deletion of GD coroutine objects.

Thanks for your help.

Coming from python (and the asyncio library), this is really confusing. It seems like a misunderstanding of generators and generator functions (yield) and coroutines (asyncronous programming). It makes no sense to replace yield with await. Yield returns a generator object and halts the execution of the function at the point of execution where yield was encountered. “yield” is just like “return”, except the local variables and point of execution are remembered, allowing you to call the generator function again to continue at the point that yield was last encountered. This allows you to loop over a generator function and gives you more control over the execution.

Generators are awesome. If you ever have a large list or array of items you want to loop over, storing the whole list in memory sometimes is not feasible and will crash your program. By using yield and a generator function, you only store one item of the list in memory for each iteration, which speeds up your program and saves a lot of memory. It seems like game development would benefit from this concept a great deal. (Looping over an array of enemy nodes to check for collisions etc.) With a generator, you could have an array of infinite objects and it would consume the same amount of memory as an array with 1 element.

Coroutines are what are returned when you call a coroutine function without awaiting it. You define a coroutine function by putting the keyword “async” in front of it. A coroutine is an awaitable, so to call it and get the return value, you use “await” or asyncio.run()

Coroutines and generators are totally separate concepts in python. There is such thing as an async generator, but thats just a generator with “async” keyword.

I know gdscript is not python, but it borrows so much from python that I get sad when it lacks certain elements that make python so great. (Such as list/dict/tuple expressions)

The choice to use yield and await like they are in gdscript just feels wrong. But it could just be me.

2 Likes

In my opinion, the uses of ‘signal’ and ‘await’ and ‘yield’ and ‘resume’ are different.

When processing the logic of a game object, ‘yield’ and ‘resume’ are intuitive, but processing the logic of a game object by making it ‘signal’ and ‘await’ is not intuitive.