Gut icon indicating copy to clipboard operation
Gut copied to clipboard

Godot 4.0 yield changed to await

Open bitwes opened this issue 3 years ago • 9 comments

yield has been replaced with await. await looks completely different. Should be fun.

bitwes avatar Aug 03 '22 02:08 bitwes

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.

bitwes avatar Aug 03 '22 02:08 bitwes

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)

bitwes avatar Aug 03 '22 03:08 bitwes

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.

bitwes avatar Aug 04 '22 01:08 bitwes

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.

bitwes avatar Aug 05 '22 00:08 bitwes

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
 }
}

bitwes avatar Aug 10 '22 16:08 bitwes

Made an issue about metadata for 4.0 https://github.com/godotengine/godot/issues/64236

bitwes avatar Aug 10 '22 19:08 bitwes

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')

bitwes avatar Oct 09 '22 20:10 bitwes

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.

SlashScreen avatar May 02 '23 07:05 SlashScreen

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

bitwes avatar May 02 '23 15:05 bitwes

These are allllll done now.

bitwes avatar Jan 16 '24 19:01 bitwes