Gut
Gut copied to clipboard
Godot 4.0 yield changed to await
yield has been replaced with await. await looks completely different. Should be fun.
It appears the compiler knows when a function has an await in it and requires that any calls that that function use await.
It is not clear what happens if the function that contains awaits never actually executes and await. When GUT runs a test (and in other places) it checks the return value to see if it is a GDScriptFunctionState. If it is, then it would yield to the completed signal. If not then things just continue as normal. This means that some functions may or may not await. In 3.x if you yield to a function that does not itself yield then the program will hang.
Many times I've gotten around this by yielding to an idle frame if nothing yielded so that the caller does not have to check. The new way may need a bit of finesse to work.
When awaiting a coroutine you just put await in front of the call. Unlike with yield you are not waiting on the completed signal.
Ex:
var ret_val = await foo(bar)
Since the new await uses <object>.<signal_name> all old yields using the YIELD constant in test.gd will not work. We will have to add the timeout signal to test.gd and all awaits using yield_for, and yield_to will have to use .timeout instead.
Also, yield_for, yield_to, yield_frames should all be renamed to something else. Maybe wait_*? And maybe timeout should be renamed? Maybe done_waiting? At the very least it should get a new alias and the yield_* methods should be deprecated.
Actually...with the new approach we might not need any signal. Maybe it can just be await wait_for(.5).
Last thought wait_for should take two parameters, 2nd one optional. The first could be a signal (foo.bar) or an amount of time as a int/float, or the newer string that represents frames ('2f'). If the first is a signal the 2nd will be the maximum time to wait for the signal if specified. We'll still probably need wait_frames as well.
await must be used on a method that contains an await only if you are using the return value. If you are not using the return value then you don't have to worry about the await if you don't care when the method finishes.
There does appear to be a flag for coroutines, but it is on the return class. This is not set for every method that contains an await, only the methods that have an await and return a value.
See the different values for usage:
func this_just_does_an_await():
await get_tree().create_timer(1).timeout
{
"args": [
],
"default_args": [
],
"flags": 1,
"id": 0,
"name": "this_just_does_an_await",
"return": {
"class_name": "",
"hint": 0,
"hint_string": "",
"name": "",
"type": 0,
"usage": 6
}
}
func this_is_a_coroutine():
return await get_tree().create_timer(1).timeout
this_is_a_coroutine
{
"args": [
],
"default_args": [
],
"flags": 1,
"id": 0,
"name": "this_is_a_coroutine",
"return": {
"class_name": "",
"hint": 0,
"hint_string": "",
"name": "",
"type": 0,
"usage": 262150
}
}
func might_await(should):
if(should):
print('awaiting')
await this_is_a_coroutine()
else:
print('not awaiting')
return should
{
"args": [
{
"class_name": "",
"hint": 0,
"hint_string": "",
"name": "should",
"type": 0,
"usage": 262150
}
],
"default_args": [
],
"flags": 1,
"id": 0,
"name": "might_await",
"return": {
"class_name": "",
"hint": 0,
"hint_string": "",
"name": "",
"type": 0,
"usage": 262150
}
}
Made an issue about metadata for 4.0 https://github.com/godotengine/godot/issues/64236
As of now, await is used anywhere that we may have to await. Any await call in a test that does not call a yield_* or one of the new wait_* methods cannot be detected.
The yield_ methods have been deprecated and their usage changed to:
await yield_to(signaler, 'the_signal_name', 5, 'optional message')
await yield_for(1.5, 'optional message')
await yield_frames(30, 'optional message')
The replacements are:
await wait_for_signal(signaler.the_signal, 5, 'optional message')
await wait_seconds(1.5, 'optional message')
await wait_frames(30, 'optional message')
Sorry, I'm confused... how do I await a signal if no signals are sent, since the main game loop isn't running? I have a tree of nodes I've built in-script that send signals to eachother in complex ways, and I want to test the interaction, but no signals can be sent, and so the await never ends.
If you want to take a look at what I mean, the test I'm working on is here.
I believe your issue is because your test world has not been added to the tree. The game loop is running, I think you just need to call add_child(root) in test_damage.
Then, prior to asserting you would use await and wait_for_signal to pause the test until a signal has fired or the maximum amount of time you want to wait has expired:
await wait_for_signal(signaler.the_signal, 5, 'optional message') # wait for signal or 5 seconds, whichever comes first
These are allllll done now.