solve method

Stream<ResolveEvent> solve(
  1. List<ServiceDescriptor> descriptors
)

Starts all given descriptors respecting dependency constraints. The cyclic service resolution is carried out by performing following checks on each unsolved service until either no unsolved services are left or no new changes can be made:

  1. Is this service optional and is it still unsolvable?
    true => Skip the service and remove it from the list

  2. Is this still missing required dependencies or are missing optional dependencies still possibly obtainable from other unsolved services?
    true => Skip this resolution cycle

  3. (indirect) Are all required dependencies provided?
    true => Start the service
    false => Throw an exception

In this algorithm, step 3 is indirectly performed as the resolution cycle will be skipped indefinitely in the case of an missing required dependency, which will result in the cancellation of the resolution loop since no further changes have been made in the last cycle.

Implementation

Stream<ResolveEvent> solve(List<ServiceDescriptor> descriptors) async* {
  yield ResolveEvent(ResolveEventType.started, null);
  var unsolved = descriptors.toList();
  while (true) {
    var lenBefore = unsolved.length; // Remember length before cycle
    // Collect injector keys which could possibly become available
    var futurePromises =
        unsolved.expand((element) => element.publications).toList();
    for (var descriptor in unsolved.toList()) {
      // If the service is optional and not solvable in this context, skip it
      if (descriptor.optional &&
          !descriptor.isSolvable(injector, futurePromises)) {
        unsolved.remove(descriptor);
        yield ResolveEvent(
            ResolveEventType.serviceSkipped,
            descriptor,
            descriptor.dependencies
                .where((element) => !injector.checkKey(element))
                .toList());
        continue;
      }
      // If the service has unfulfilled dependencies or optional dependencies
      // could still become available, wait another dependency cycle
      if (descriptor.skipDependencyCycle(injector, futurePromises)) {
        continue;
      }
      // All dependencies are met and no more optional dependencies can
      // be fulfilled anymore -> start the service now.
      unsolved.remove(descriptor);
      await activate(descriptor);
      yield ResolveEvent(ResolveEventType.serviceStarted, descriptor);
    }
    var lenAfter = unsolved.length;
    if (lenAfter == 0) {
      yield ResolveEvent(ResolveEventType.finished, null);
      break;
    }
    if (lenBefore != lenAfter) continue; // At least one declined
    for (var descriptor in unsolved) {
      yield ResolveEvent(
          ResolveEventType.serviceError,
          descriptor,
          descriptor.dependencies
              .where((element) => !injector.checkKey(element))
              .toList());
    }
    yield ResolveEvent(ResolveEventType.failed, null);
    break;
  }
}