Is there a “Best” way to cancel an await

Godot Version : Godot 4.3

Question: Is there a “Best” way to cancel an await.

Thanks for looking at my topic :smiley:

I’m working on a horror game with a hiding mechanic. I have a method the player uses to handle the hiding interactions. Currently its working by using a few awaits.

SUDO CODE
SUDO CODE
	in_hiding_transition = true
	await reset()
	await hidee.mount_camera(where the player camera mounts, transition time)
	await hiding_spot.hiding_interactor.enter()
	in_hiding_transition = false

However, there is a scenario I’d like the game to have where the player can be interrupted during this hiding transition where a similar process using awaits will happen when the AI attacks the player.

This approach looks fine on paper but once this thing gets started it’s hard to interrupt. I did try adding a cancel method that would just set a cancelled bool. Then in the method with lots of awaits after each await, I’d query if cancelled was true and then return out of the method.

If CANCELLED SUDO CODE
If CANCELLED SUDO CODE   
	in_hiding_transition = true
	await reset()
	if cancelled:
		do_the_cancelling()
	await hidee.mount_camera(where the player camera mounts, transition time)
	if cancelled:
		do_the_cancelling()
	await hiding_spot.hiding_interactor.enter()
	if cancelled:
		do_the_cancelling()
	in_hiding_transition = false

A very ugly approach and not maintainable long term

I’ve searched the docs and even consulted the bots. However, I think my approach was flawed from the start.
Has anyone got any better ideas/approaches for this system.

Full Current code here

Please note I’ve kept this as bare bones as possible if you need more info please let me know.

func enter() -> Signal:
	hidee.item_controller.hide_equipment()
	in_hiding_tranisition = true

	await hiding_spot.hiding_interactor.reset()
	await hidee.mount_camera(hiding_spot.hiding_interactor.camera_anchor, 0.1)
	await hiding_spot.hiding_interactor.enter()

	hidee.detectable_target.detectable = false
	hidee.teleport_to_node(hiding_spot.hide_hint, true)
	in_hiding_tranisition = false
	is_hidden = true

	return finished

Further;

  • This question is coming from a ex-unity game developer who has some experience using coroutines in C#
  • It’s also my first time really interacting with the forum. If I’m missing something let me know how I can improve these interactions.

Thanks for reading

Could you use a tween for this?

Then you could save a reference to the tween and cancel it later as you wish.

I’d think you’d want to use a timer instead of await.

Those have callbacks they can fire, and they’re nodes in the tree so if you need to cancel them you can queue_free() them.

@thisisnotdalton
Thanks for the response.
I did want this approach. Maybe you can correct me. When you start a tween unless cancelled and restarted. It won’t recalculate the tween to the new intended target value.

In the example of moving the players camera to the hiding animations camera mount. if I want to tween to a position. and that position moves during the tween. Then the current tween will only move to the target point it was initially told to move to.

Please say I’m wrong because I haven’t had much luck with tweens yet.

@hexgrid
Thanks for the comment.
With the timer approach. initially I wasn’t too sure how best this could be handled. Other than daisy chaining mutliple timers that would call the next behaviour. Eitherway I went ahead and implemented the system with timers and it does work.

I broke the hiding into 3 stages/methods.
enter → enter_animation → entered

Implementation
class_name HidingHandler extends Node
#This class is doing way too much!

var hidee:Player = null
var hiding_spot:HidingSpot = null

var in_hiding_tranisition:bool = false
var is_hidden:bool = false
var timer:Timer = Timer.new()
signal finished

func _init(_hidee:Player, _hiding_spot:HidingSpot) -> void:
	hidee = _hidee
	hiding_spot = _hiding_spot
	add_child(timer)
	timer.one_shot = true
	timer.autostart = false
	timer.timeout.connect(flush_timeout_connections) # Automatic tear down between stages

#Set all intitial state!
func enter():
	hidee.item_controller.hide_equipment()
	hidee.freeze()
	in_hiding_tranisition = true
	var mount_duration:float = 1
	hidee.mount_camera(hiding_spot.hiding_interactor.camera_anchor, mount_duration)
	timer.start(mount_duration)
	timer.timeout.connect(enter_animation)
	print("enter")

#Call animation and wait for it to finish
func enter_animation() -> void:
	timer.start(hiding_spot.hiding_interactor.get_anim_length("enter"))
	timer.timeout.connect(entered)
	hiding_spot.hiding_interactor.play("enter")
	print("animation")

#Set player values when the animation has finished
func entered() -> void:
	hidee.detectable_target.detectable = false
	hidee.teleport_to_node(hiding_spot.hide_hint, true)
	in_hiding_tranisition = false
	is_hidden = true
	print("entered")

#This method is on the player just putting it here for the example
func cancel() -> void:
	queue_free()

func flush_timeout_connections() -> void:
	for connection in timer.timeout.get_connections():
		if connection["callable"] != flush_timeout_connections:
			print("removed " + str(connection["callable"]))
			timer.timeout.disconnect(connection["callable"])

Video of it working → https://youtu.be/gbRA2mjNbxI

Massive thank you for this idea :smiley:

When I have had to make tweens that interpolate things between moving targets, I would usually opt to store the destination node instead of its position, and then use tween_method to call a function to update the position based on the node’s most up-to-date position.

1 Like