ready method

  1. @override
Future<void> ready()
override

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;
    }
  }
}