Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Godot 4.0 yield changed to await #382

Closed
bitwes opened this issue Aug 3, 2022 · 10 comments
Closed

Godot 4.0 yield changed to await #382

bitwes opened this issue Aug 3, 2022 · 10 comments
Labels
Godot 4.0 Issues related to Godot 4.0

Comments

@bitwes
Copy link
Owner

bitwes commented Aug 3, 2022

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

@bitwes bitwes added the Godot 4.0 Issues related to Godot 4.0 label Aug 3, 2022
@bitwes
Copy link
Owner Author

bitwes commented Aug 3, 2022

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
Copy link
Owner Author

bitwes commented Aug 3, 2022

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
Copy link
Owner Author

bitwes commented Aug 4, 2022

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
Copy link
Owner Author

bitwes commented Aug 5, 2022

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
Copy link
Owner Author

bitwes commented Aug 10, 2022

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 bitwes changed the title Godot 4.0 yield doesn't exist anymore Godot 4.0 yield changed to await Aug 10, 2022
@bitwes
Copy link
Owner Author

bitwes commented Aug 10, 2022

Made an issue about metadata for 4.0 godotengine/godot#64236

@bitwes
Copy link
Owner Author

bitwes commented Oct 9, 2022

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

@SlashScreen
Copy link

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.

@bitwes
Copy link
Owner Author

bitwes commented May 2, 2023

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
Copy link
Owner Author

bitwes commented Jan 16, 2024

These are allllll done now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Godot 4.0 Issues related to Godot 4.0
Projects
None yet
Development

No branches or pull requests

2 participants