Strange yield behaviour when instancing a scene

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

I think I might not be understanding yield, or this is potentially a bug, I’m not sure. I have the following segment of code:

func new_game():
	var loading_scene = loading.instance()
	add_child(loading_scene)
	print("reached here, before game setup")
	#yield(get_tree().create_timer(5), "timeout")
	game_setup()
	loading_scene.queue_free()
	var game_scene = game.instance()
	add_child(game_scene)

For context, “gamesetup()” is a function that tends to take 3-6 seconds or so, and I’m putting a loading screen while it waits. The loading screen scene is literally just a blank space, the word “loading”, and a little icon. If I run the code as it is now, with “yield” commented out, the loading scene never appears (or it possibly briefly appears, and is then immediately cleared at “loadingscene.queue_free()”) . Essentially, what I think is happening is for some reason, it’s waiting until the end of this script to try and add it to the scene. When I run the game, the loading happens and it proceeds to the next stage without a problem, but the loading screen never appears. So I know for certain that "gamesetup() is happening.

The strange behaviour is when I uncomment the “yield” line, then, all of a sudden, the loading screen appears, hangs for 5 seconds (as I’ve made it do) and then everything proceeds as normal.

What is happening here? Why does the loading scene only appear on-screen if there is a “yield” in the middle of the script?

:bust_in_silhouette: Reply From: MaaaxiKing

Everything you wrote in the code should be like you explained your problem. So your thoughts about not understanding yield is correct ;D The yield function pauses the function it’s written in until the given signal is emitted. If you want the loading screen to be visible until the game has setup completely, you have to do loading_scene.queue_free()at the end of game_setup(). What does game_setup() do exactly or should do? Do you want to wait before a button appears and an audio is played or something like that? If so, you’d have to do:

func game_setup():
	yield(get_tree().create_timer(1), "timeout")
	$Button.show()
	yield(get_tree().create_timer(1), "timeout")
	$AudioStreamPlayer.play()

The thing I don’t understand is the loading screen is just never visible if yield doesn’t get called. game_setup() calls an algorithm that can take a few seconds to load, it’s not loading any resources, it’s running a script. What I don’t understand is that during game_setup(), the loading screen isn’t visible, even though it was added to the scene first, and why does yielding make it visible?

psear | 2020-06-16 18:47

It is visible, you just don’t see it because it is too fast. With yield, the function pauses, so the loading screen will be hidden later. Without the yield, the game_setup() function will be executed very fast and because of this, the loading screen will be visible for just a few milliseconds or less.

MaaaxiKing | 2020-06-16 18:52

No, that’s the thing, i know for a fact that game_setup() takes multuple seconds to occur. it is a lengthy algorithm with lots of iteration in it. Without yield, it loads for multiple seconds (because it’s a slow algorithm) but the loading scene isn’t visible, even though it’s been added to the scene.

psear | 2020-06-16 18:54

Maybe you can give us a little insight in game_setup()?

MaaaxiKing | 2020-06-16 18:56

Game setup is a mathematical algorithm (sudoku) that takes a few moments to finish. It’s purely a script thing, it’s not loading any sprites or anything like that, it’s just a really heavy algorithm that takes multiple seconds to occur.

psear | 2020-06-16 19:07

:bust_in_silhouette: Reply From: deaton64

Hi,
Yes, what MaaaxiKing said and if you really want a proper loading screen, you should take a look at the Resource Loader that loads scenes a bit at a time.

Game setup doesn’t load any resources, it runs a lengthy algorithm. What I don’t understand is the loading screen is added to the scene before the game setup is run, why is it not visible in this time?

psear | 2020-06-16 18:51

When you run the game, in the Remote Scene viewer, does the loading_scene scene appear and stay there?

deaton64 | 2020-06-16 20:17

Yes, it does appear to sit there while the loading happens and then disappears when the game advances. The thing that I find so confusing is just yielding with a timer will make it visible. I’ve also noticed that if I set the timer to something like 0.5 seconds, the loading screen appears, plays the animation, but the animation freezes (after about 0.5 seconds)…
I’m really lost with this, I must be missing something fundamental about how a script is executed.

psear | 2020-06-17 13:29

Yes, well it will appear when the timer is there, as you are saying, “wait here for 5 seconds”.
The Yield command does the following:

print("hello")
yield(get_tree().create_timer(5), "timeout")	# wait for 5 seconds
print("hello again, 5 seconds after the first hello")

I don’t think that’s what you want to happen.
Are you sure game_setup() does that a while to run? because as soon as it returns, loading_scene.queue_free() will run, destroying the loading scene.

deaton64 | 2020-06-17 14:47

Yes, the whole reason I’m putting a loading screen there is because the game is just blank for a few seconds. But for some reason, it will only appear if I yield, and I noticed that if I set the yield timer to 0.5 or something, it appears, but stops the animation after 0.5 seconds. I understand that yield stops the function, and waits either for the signal or for it to be resumed, but what I don’t understand is that during the loading the screen is blank, even though I’ve added the scene, and when I use yield, at least I can see the loading screen, but it is interrupted by the yield timer.

psear | 2020-06-17 16:22

try this:
Keep the yield line commented out.
Either side of your game_setup() call, add a print time, like this:

print(OS.get_time())
game_setup() 
print(OS.get_time())

Paste the output here :slight_smile:

deaton64 | 2020-06-17 17:35

The output is:

{hour:19, minute:7, second:53}
{hour:19, minute:8, second:11}

it varies quite a bit some time, but as you can see it can sometimes take upwards of 15 seconds.

psear | 2020-06-17 18:09

OK, so I’ve recreated what you’re doing and this is what I think is happening.
Godot calls your new_game() function, gets to the add_child for the loading scene and as it’s adding the scene, you put the scene in a loop that’s stalling whatever scene new_game() is in.

It may be that there’s a better way to do what you’re doing, but I don’t know what that is. I tried with a thread, but that didn’t seem to work either. It may need multiple calls in a process function.

What did work for me, was changing the timer to .5 of a second, which gives Godot enough time to load the loading scene, before it goes into the game_setup loop.
What I also did was put a print statement in the process function and while the game_setup is running, it pauses. So the loop is definitely pausing the code.

I’ll past the code below. It’s a simple scene. The loading_scene has a text label and prints “loading scene”.

extends Node2D

var loading = preload("res://loading_scene.tscn")

func _ready():
	new_game()

func new_game():
	var loading_scene = loading.instance()
	add_child(loading_scene)
	print("reached here, before game setup")
	yield(get_tree().create_timer(.5), "timeout")
	game_setup()
	loading_scene.queue_free()
	var game_scene = game.instance()
	add_child(game_scene)

func game_setup():
	print("in game setup")
	for _i in range(1,100000000):
		var a = _i * 10 * 10 *10
	print("game setup done")

deaton64 | 2020-06-17 21:00

Thanks! I had a feeling what was happening is no onscreen changes were happening until the script either finished, or was paused by yield. But now I have a subsequent problem, whilst the loading screen appears, along with it’s animation, it freezes when the game hits “game_setup”.

psear | 2020-06-18 08:29

maybe have loading as a running scene and send a signal to it to start the loading animation and a signal to stop it.

deaton64 | 2020-06-18 23:10

:bust_in_silhouette: Reply From: avnih

Not sure, but try adding the line:

yield(get_tree(), "idle_frame")

before your existing yield.
It should allow the screen to refresh, before pausing the function execution.