"await" causes Loading and Saving process to overlap and a visual glitch

Godot Version

Godot 4.3

Question

Hey everybody, I have an annoying bug.

The problem:

When I use “await” to call the Fade to Black transition screen when my character dies, it seems like the loading process overlaps the saving process (or the loading has issues).

It shows for a split second the correct amount of health (eg: I have 5 hearts: 2 filled, 3 empty), but then it turns them all empty.

Player’s alive and it seems just a visual glitch: when taking damage or collecting health, it gets fixed.

To clarify, this is the steps:

  1. Player’s health reaches 0 and enters Death state (I have a State Machine)

  2. Death animation finishes and calls the load_process() inside SaveManager autoload:

    func _on_animated_sprite_2d_animation_finished():
    	if animated_sprite_2d.animation == "death":
    		print("Player Death anim finished")
    		player.invulnerable = false
    		player.is_player_hit = false
    		
    		player.invulnerable_timer = 0
    		animation_finished = true
    		Main.death_animation_finished = true
    		
    		SaveManager.load_process()
    
    
  3. Inside load_process() I use await to first call the fade to black transition before the loading process, otherwise you’d see the Player teleporting to the SavePoint.
    Once that is done, the game_loaded signal fires:

    func load_process():
    	if game_is_loading:
    		print("Save Manager: Game is already loading, ignoring duplicate call.")
    		return
    	
    	print("Save Manager: Loading saved resources")
    	
    	game_is_loading = true
    	
    	await FadeTransition.hold_timer(1)
    	await FadeTransition.fade_to_black_and_hold(2,1)
    	
    	# Load the saved resources from disk.
    	_save_game = SaveGame.load_game()
    	
    	# Reset states from HUD
    	_hud.reset_runtime_state()
    	
    	# Assign the saved values to the relative values.
    	_player._stats = _save_game.player_resource
    	
    	_hud.hearts = _save_game.hud_resource.hearts
    	_hud.hearts_health = _save_game.hud_resource.hearts_health
    	
    	_player.global_position = _save_game.global_position   # Relocate Player to the last saved position (on a SavePoint).
    	
    	await get_tree().physics_frame   # Wait for the game to complete a frame so hopefully signal firing won't overlap with saving.
    	
    	game_is_loading = false   
    	
    	GameEvents.game_loaded.emit() 
    
  4. game_loaded connects to the Player’s State Machine that transitions to Resting state where it enables the flag Main.Resting = true which is needed to save:

    func enter(previous_state_path: String, data := {}) -> void:
    	print("Player enters Resting state")
    	
    	FadeTransition.hold_and_fade_from_black(1, 0.5) # no await here
    	
    	player.animated_sprite_2d.play("resting")
    	player.set_collision_layer_value(1, true)
    	
    	Main.resting = true
    
  5. if Main.resting is true and Player is inside the SavePoint area, save_process gets called from SavePoint’s _process function:

    func _process(delta):
    	if Main.resting and not SaveManager.game_is_loading:
    		if is_player_in_area:
    			print("Save Point: Resting received in SavePoint")
    			
    			SaveManager.save_process()
    			
    			Main.resting = false
    

In the Output, after Fading transtion finishes, everything gets done in a frame (a split second), and it shows first 5 hearts (2 filled hearts / 3 empty,) and then 5 empty.

Without the “await”, everything works smoothly and it only shows 2 filled / 3 empty hearts in the Output and in-game HUD.

I tried adding a wait timer after loading is done, before firing the game_loaded signal, it reduces the glitch frequency to rarely, but still happens.

To have a complete view, this is the FadeTransition’s function that gets called:

func fade_to_black_and_hold(fade_duration_in: float = 0.5, hold_duration: float = 0.5):
	print("FadeTransition: Fade to black and hold")
	var tween: Tween = create_tween()
	tween.tween_property(color_rect, "modulate:a", 1.0, fade_duration_in)
	await tween.finished
	await get_tree().create_timer(hold_duration).timeout

I tried moving the await FadeTransition inside Death animation_finished, before SaveManager.load_process() is called (or elsewhere together with load_process) but nothing changes.

Any ideas? Should I separate better loading and saving processes? Maybe a Game State Machine?

Thanks for any help!

Well, need I say it :smiley: ? - Don’t use awaits.

4 Likes

I suspect your problem is that you are awaiting something that does not return anything. (Your fade_to_black_and_hold function() doesn’t declare a return type, so its return type is void.) It sounds like an odd bug, but also you’re using await incorrectly. You can try having your function return a bool and see if that fixes it.

You did not show us the code for the hold_timer() function. Are you sure that’s not causing the problem?

There are a couple of possible approaches:

  1. Put signals in those functions to tell you when they’re done, and await the signals.
  2. Just await a timer get_tree().create_timer(1.0).finished and feed it the time you want to wait.
  3. Do what @normalized is alluding to and create a signal that when it is fired, executes the rest of the code in another function. (Which is really the best way to go.)

Whenever you run into awaits as the solution, it’s good practice to ask yourself how you can solve the problem without them.

1 Like

Thanks a lot for the help!

I solved this by not using awaits, but a chain of signals.
Now a signal is fired for each step and I’m assuring that by the time it’s fired certain conditions are met.

Wish more people would reply with explanations to understand the issue as you did, instead of being a unhelpful “no sh*t Sherlock” as the person above :slight_smile:

Have a great day!

1 Like

I literally laughed out loud at this. @normalized comes across that way sometimes.

Glad I could help.

1 Like

You’re supposed to ask follow-up questions if you don’t fully understand the answer. Especially if you expect people to remote-debug your spaghetti code.

So you did precisely what I suggested but marked other person’s post as the solution. And that other person said to do exactly what I initially wrote, just wrapped in some pseudo-polite fluff. (My lawyer with talk to your lawyer regarding this @dragonforge-dev)

Are you spending too much time with LLMs and starting to expect the same tone and volume from humans?

1 Like