ready method
Ensure that all pending tree operations finish.
Awaiting on this future ensures that all pending operations of adding components into the tree are fully materialized, waiting for any components that are still loading.
The GameWidget awaits this future when the game is first shown, so
that the game only starts, and the loading widget is only removed, once
the whole initial component tree has been loaded and mounted.
A component that fails to load does not block this future; its error is
reported through its Component.loaded future, or the current Zone if
nothing is awaiting that future.
Warning: since every pending component has to finish loading and
mounting first, this future never completes if the tree can never
settle. That happens when a component in the tree has an onLoad that
never completes, for example one that awaits something which only
happens once the game is running. Such a component keeps the
GameWidget on the loading widget until it is removed from the tree.
The same happens when a component is added to a parent that is not
itself part of the game tree: it never starts loading, since loading
only begins once its parent is mounted, so it blocks this future in
the same way until either the parent is added to the game or the
orphaned component is removed.
Implementation
@override
Future<void> ready() async {
while (isProcessingLifecycleEvents) {
// This call came from inside a lifecycle callback, which runs while
// [processLifecycleEvents] is iterating over the event queue. Since
// the queue only supports one iteration at a time, wait until the
// current processing pass has finished.
await null;
}
var wake = Completer<void>();
void wakeUp() {
if (!wake.isCompleted) {
wake.complete();
}
}
final watchedChildren = <Component>{};
while (hasLifecycleEvents) {
processLifecycleEvents();
if (!hasLifecycleEvents) {
break;
}
if (wake.isCompleted) {
wake = Completer<void>();
}
var hasLoadingChildren = false;
// Safe to iterate plainly: this always runs after
// [processLifecycleEvents] has returned, so it is never nested inside
// its own iteration over the same queue.
for (final event in queue) {
final child = event.child;
if (child == null || !child.isLoading) {
continue;
}
hasLoadingChildren = true;
if (watchedChildren.add(child)) {
child.loadSettled.then((_) => wakeUp());
}
}
if (hasLoadingChildren) {
// Sleep until a load settles, or until the event queue is changed
// from the outside, for example by a component being removed while
// it is still loading.
await Future.any([wake.future, nextLifecycleEventMutation]);
} else {
// The queue is stuck on a component added to a parent that is not
// part of the game tree, so it will never start loading on its own.
// Wait for the queue to change, for example because that parent, or
// the stuck component itself, is added to or removed from the tree.
await nextLifecycleEventMutation;
}
}
}