I came across this post when I was about to ask how to run multiple await
s in parallel. It answered my question quite nicely, and I think kienhoang and jovialthunder wrote some good code, but I saw a way that ParallelCoroutines could be made more easily usable. So I’m posting the code for my own take on it here now. I also have it as a gist along with its license (MIT).
class_name ParallelCoroutines
extends RefCounted
## Emitted when all coroutines have completed from run_all()
signal completed
class QueuedCoroutine:
var coroutine:Callable
var callback:Callable
func _init(p_coroutine, p_callback):
coroutine = p_coroutine
callback = p_callback
var _queued_coroutines:Array[QueuedCoroutine] = []
var _total_count:int = 0
var _completed_count:int = 0
## Each coroutine that completes will have its result added to results.
## await completed to ensure all results are present.
## results will contain null values for coroutines with no return value.
var results:Array = []
## Adds a coroutine to be started when run_all() is called.
## coroutine must be an asynchronous method i.e. it must call await at least once.
## callback can be provided if you wish to immediately act upon coroutine's completion.
## callback will be called with the result returned by coroutine.
## If coroutine does not return a value, the result will be null.
func append(coroutine:Callable, callback:Callable = func(_result):{}) -> ParallelCoroutines:
#@warning_ignore does nothing for the warning thrown by the default lambda :(
_queued_coroutines.append(QueuedCoroutine.new(coroutine, callback))
return self
## Runs all coroutines added by append in parallel.
## returns completed Signal as a convenience.
## You may await completed or this method.
func run_all() -> Signal:
_total_count = _queued_coroutines.size()
var i = 0
while i < _total_count:
_run(_queued_coroutines[i])
i += 1
return completed
func _run(routine:QueuedCoroutine) -> void:
var result = await routine.coroutine.call()
results.append(result)
routine.callback.call(result)
_on_completed()
func _on_completed() -> void:
_completed_count += 1
if _completed_count >= _total_count:
completed.emit()
Usage example:
extends Node2D
func _ready():
call_routines()
func call_routines():
var routines = ParallelCoroutines.new()
routines.append(routine_0).append(routine_1, do_something_with_result).append(routine_2, do_something_with_result)
await routines.run_all()
print(routines.results)
print(str(Time.get_unix_time_from_system()))
func routine_0():
print("routine 0 started " + str(Time.get_unix_time_from_system()))
await get_tree().process_frame
print("routine 0 complete " + str(Time.get_unix_time_from_system()))
func routine_1():
print("routine 1 started " + str(Time.get_unix_time_from_system()))
await get_tree().create_timer(2.).timeout
print("routine 1 complete " + str(Time.get_unix_time_from_system()))
func routine_2():
print("routine 2 started " + str(Time.get_unix_time_from_system()))
await get_tree().create_timer(4.).timeout
print("routine 2 complete " + str(Time.get_unix_time_from_system()))
return "Not null!"
func do_something_with_result(result):
if result != null:
print("completed ", result)
If you try using it, please let me know if you have any questions or if you encounter any bug! There shouldn’t be any other than one annoying warning I can’t get rid of
If anyone knows how to provide a default Callable parameter without pissing off the compiler, I would be delighted if you’d share that with me.