Previously freed instance error on an awaited timer?

Godot Version

v4.3-stable

Question

The below is a snippet of code from a larger function turn() which simulates a player as an ai making moves in a turn-based strategy game by looping through all it’s pieces and making calculations. The player ai calculates it’s move, ability and attack and each of these has a 0.5 timer timeout, so that the moves don’t all happen at once.

func turn(...):
	
	... 	
	# ...'s are where I believe the code is not relevant.
	
	if not is_instance_valid(unit): continue
	print(unit.entity_name, " is trying to attack")
	var calculated_attack = unit.ai.calculate_best_attack_options()
	if calculated_attack != null:
		unit.attack(calculated_attack[0], calculated_attack[1])
		print(get_tree())
		await get_tree().create_timer(0.5).timeout #Error occurs here

This code was all working fine until I added code that awaits dialogue finishing in a different script.

func level_specifics():
	
	await handle_status_effects()

	if player_turn == 2 && not ai.game_over:  
		await handle_dialogue()

	show_actionables_for_player_turn() 
	#Simple function that shows some coloured circles for the current player's units.

	# I've gutted this function to it's core for clarity purposes:
	# The code that was here relates to the specific turns_played variable
	# The error that occurs for me is on turn 6.5 which has no specific code here

	await ai.turn(computer_buildings_entities.call(), player_units.call(), turns_played)

I have been doing my best to research this issue, googling etc. - I made sure to await other functions that come before ai.turn() which also use await. I also printed get_tree() and it returned a proper object as per my first code snippet.

I am not using onready variables as other people with similar issues have, additionally, I checked if using a longer wait period (5 seconds) would work to ensure that the timer was not timing out before I was accessing it.

Here is the some code in my handle_dialogue function which handles turn 6.5:

	if turns_played == 6.5 and is_alive("goblin_slave") and is_alive("necromancer"):
		dialogue.play_optional_dialogue("Montague_Kills_Grinkle")
		await dialogue.finished_dialogue_set
		grinkle.die()
			
		if is_player_unit_in_grinkle_zone():
			await player_is_spotted("necromancer")

Grinkle is a goblin unit on the npc’s side (as is Montague the necromancer). I have just thought of the theory that grinkle dying is the issue as my first snippet loops through all the units - however, this still confuses me as the error is occuring right after the necromancer is making an attack on an outpost and the error is on the timer and not at the start of the loop for the goblin unit (Grinkle).

Also apologies for the sphagetti code, have been a little depressed lately and have been struggling to code. Hopefully this question is clear and not annoying. Thanks in advance. Please let me know how I can help provide more information or if you want me to try something.

Get rid of await completely. Use regular signal handling functions instead.

1 Like

oh? Is there a specific reason for getting rid of await? Am I simply using it in the wrong context, or are there just better alternatives?

Additionally, do you mean get rid of all awaits aside from the timer ones? I’m not sure exactly how I would get rid of the timer awaits?

For the rest, just to double check - do you mean like emitting a signal when the dialogue has finished and connecting that to a function which alongside the turns_played variable decides what to do next? (Just wanting to make sure I’ve understood)

Edited to add: Have just thought of how to connect the timer signals (sorry - am a little exhausted today)

await always waits for signals. Whatever you can do with awaits you can do with signal connections as well, with significantly less confusion and bugs.

Most people don’t fully understand how await works and tend to grossly misuse it, which results in weird hard to understand bugs.

Just try to make await-less version. If you can’t make engine do what you want without awaits, doing it properly with awaits will be even harder.

1 Like

Bugs like yours typically happen when you have multiple instances of the same function awaiting but you’re not aware that this is the case, and the wakeup returns into some context/state that the function expects is there, but it isn’t any more, or wakes up multiple awaiting instances of the function at the same time.

1 Like

Thank you! This is helpful, I will try to write a version without awaits, but also am curious as to what specifically I’ve done wrong? Will deffo go for the simpler approach though as I am not enjoying working with awaits either.

Out of curiousity, if I were smarter, more experienced and dedicated - would you recommend learning more about awaits? Are they more helpful at a higher level? (Just out of curiousity). Am very curious as to the specific advantages of using them over simply using signals.

You likely don’t fully understand how awaits operate. Definitely learn more, but awaits tend to get problematic even for people who understand how they work because the way they work can result in code execution flow that’s mentally hard to follow.

2 Likes

ok thank you for your help. Will have a crack a t this tomorrow (since it’s almost 2am for me now) - thanks again! Super helpful ^^. I have quite a few awaits in other areas of my code (unrelated to this) that I may end up changing as well. Thank you! Also feel free to write more :slight_smile: will read it in the morning.

Doing await in a function turns it into a coroutine. Coroutines behave a bit differently from plain functions. They can get suspended and continued at a later time. Often it’s hard to keep track of your suspended coroutines, especially if you juggle many of them at the same time. Conversely, it’s easy to forget you have a sleeping coroutine somewhere out there, just waiting to wake up into a state that was there when it went asleep but it isn’t there any more, because some other code altered that state while the coroutine was asleep.

Advantages: shorter, more elegant code in some cases, if you know what you’re doing.
Disadvantages: Guaranteed chaos, if you don’t know what you’re doing, and sometimes even if you do know.

1 Like