How to run method without blocking?

Godot Version

v4.3.stable.official [77dcf97d8]

Question

I’m trying to figure out how to play a bunch of methods in parallel, hopefully without having to resort to threading.

The idea is I have a custom object that displays an effect that takes several seconds to complete. Once the effect is complete, my object emits a finished signal. I have nine of these objects and would like to start them offset by a small amount of time. I then want to wait for every effect to run to completion before doing the next thing.

I’m trying to figure out how to have these methods play in parallel. I don’t want to wait for one effect to finish before playing the next. I tried using tweens for this, but tweens don’t have a method that blocks until a signal happens.

Is there way to write this so that all the play_reveal happen in parallel and then I block until the last play_reveal is finished?

	for grid_idx:int in %game_grid.get_child_count():
		var child:EnemyGrid = %game_grid.get_child(grid_idx)

		var play_reveal = func():
			await get_tree().create_timer(grid_idx * .1).timeout
			child.play_reveal_tokens_effect()
			await child.reveal_finished

		await play_reveal.call()

in loop “for” at start add await get_tree().process_frame it will update only one tile per frame so it will reveal roughly 60 tiles per second by default.
just start effect on tile this way

adding multiple one after another
await get_tree().process_frame
await get_tree().process_frame
could limit its speed

for child_node in %game_grid.get_children():
	await get_tree().process_frame
	child_node.play_reveal_tokens_effect()

I want all the effects to play at the same time (with a specified delay in seconds). That’s why I’m calling them all at once.

My question is about control flow. I can’t find any way to start several methods in parallel and then wait for them all to finish. Neither Tween nor await seems to provide a way to do this.

Also, since my other process is using a signal to indicate that it has finished, what I want to do is call each method after a given delay and then wait until each object I have called has emitted the finished signal.

did you tried
Tween.set_parallel() ?

That doesn’t tell me when things have finished. That just starts them.

.tween_callback(callable) for it

Yes, I’m using tween_callback. How do I know when the thing that was called has finished?

why not to add it to
.play_reveal_tokens_effect()

var instance:Node
instance.play_reveal_tokens_effect()

in play_reveal_tokens_effect()
just make SceneRootNode.reveal_finished.emit(instance)
and then connect func that reacts on emitted instance

I need some way to detect that. Tween has no method to detect when a signal has been emitted. I’ve tried adding a local variable and counting, but apparently lambdas don’t write to local variables in GDScript. So I can’t write an await loop that just waits for all the signals to be emitted.

In other words, this doesn’t work because foo is still 0 every time the lambda is called. So how am I supposed to wait for all the signals to be emitted?

	var foo:int = 0
	
	var tween = create_tween()
	for i in 9:
		tween.tween_callback(func(): 
			foo += 1
			pass
			).set_delay(randf())
	
	while foo < 9:
		await get_tree().process_frame
		
	print(str(foo))
func play_effect(instance:Node2D)->void:
	var tween:=instance.create_tween()
	tween.tween_property(instance, "position:x", instance.position.x+150, 2.0).tween_callback(func(): scene_root_node.reveal_finished.emit(instance))

this will emit exact instance on tween end
because to make foo updated during lambda func it has to be passed as argument

The problem is not emitting the signal. The problem is finding a way to wait for all the signals that have been emitted to be done. I need some way to wait until everything has finished.

How would I pass foo as an argument? It’s supposed to be local to the outer method. It’s not meant to be passed in as a parameter. It’s meant to increment the counter outside of the lambda to keep track of how many signals have been received. If I passed it into the lambda, that would make it local to the lambda, which is not what I want.

Don’t use await here

for grid_idx:int in %game_grid.get_child_count():
	var child:EnemyGrid = %game_grid.get_child(grid_idx)

	var play_reveal = func():
		await get_tree().create_timer(grid_idx * .1).timeout
		child.play_reveal_tokens_effect()
		#await child.reveal_finished

	#await play_reveal.call()
	play_reveal.call()

I need to wait until all the effects have finished playing until a move on to the next stage. I know the effect has finished when it emits a reveal_finished signal. How I make sure each one of these effects has finished playing before continuing to the next part of my method?

You could await the last child specifically

for grid_idx:int in %game_grid.get_child_count():
	var child:EnemyGrid = %game_grid.get_child(grid_idx)

	get_tree().create_timer(grid_idx * 0.1).timeout.connect(child.play_reveal_tokens_effect)

var last_child: EnemyGrid = %game_grid.get_child(-1)
await last_child.reveal_finished

That is not necessarily going to be the last to finish. The effects don’t all play at a fixed rate.

Ah you could count how many have finished and call another function or emit a new signal

var child_count: int = %game_grid.get_child_count()
var tally_child = func() -> void:
	child_count -= 1
	if child_count == 0:
		end_of_tally_function() # or emit

for grid_idx:int in child_count:
	var child:EnemyGrid = %game_grid.get_child(grid_idx)

	get_tree().create_timer(grid_idx * 0.1).timeout.connect(child.play_reveal_tokens_effect)
	child.reveal_finished.connect(tally_child, CONNECT_ONE_SHOT)

I think I’m going to have to do something like that. Too bad GDScript makes this so difficult. Waiting for parallel ‘threads’ to finish is an important control flow feature.

Having to mix Tweens and await calls also makes for ugly code. Should be possible to do everything with tweens.

Do you know of any language that supports this better? I would love to see a snippet as I’ve only seen languages move towards this colorless await system, and I’d write the same code for actual multithreads, yearn to know what’s out there. (genuine seeking knowledge share, I am not a blind zelot to Godot’s coroutines of all things)

I think for tweens it could be done, callbacks might not work with parallel? try this out

var tween := %game_grid.create_tween().set_parallel()

for grid_idx:int in child_count:
	var child:EnemyGrid = %game_grid.get_child(grid_idx)
	tween.tween_callback(child.play_reveal_tokens_effect).set_delay(grid_idx * 0.1)

await tween.finished

Edit: ah no I think it will play them and not wait for the buried reveal_finished