build method

Future<Dependencies> build()

Builds and returns an instance of type T from the registered factories, initializing all types that implements IInitializable.

Factories are ordered in a topological order, ensuring that dependencies are resolved before the instance is created.

  • T: The type of the instance to build.

Throws an StateError if no factory is registered for the type T or if a cyclic dependency is detected.

Implementation

Future<Dependencies> build() async {
  final graph = <String, List<String>>{};
  final inDegrees = <String, int>{};

  for (final dep in _dependencies) {
    final typeName = dep.getTypeName();

    graph[typeName] = [];
    inDegrees[typeName] = 0;
  }

  for (final dep in _dependencies) {
    final typeName = dep.getTypeName();

    for (final dependency in dep.dependsOn) {
      final dependencyName = dependency.toString();

      if (graph.containsKey(dependencyName) == false) {
        throw StateError(
          "Dependency '$dependency' not found for '${typeName}'.",
        );
      }

      graph[dependencyName]!.add(typeName);
      inDegrees[typeName] = (inDegrees[typeName] ?? 0) + 1;
    }
  }

  final queue = Queue<String>();
  final sorted = <Dependency<dynamic>>[];

  for (final entry in inDegrees.entries) {
    if (entry.value == 0) {
      queue.add(entry.key);
    }
  }

  while (queue.isNotEmpty) {
    final current = queue.removeFirst();

    final currentDep = _dependencies.firstWhere(
      (dep) => dep.getTypeName() == current,
    );

    sorted.add(currentDep);

    for (final neighbor in graph[current]!) {
      inDegrees[neighbor] = inDegrees[neighbor]! - 1;

      if (inDegrees[neighbor] == 0) {
        queue.add(neighbor);
      }
    }
  }

  if (sorted.length != _dependencies.length) {
    throw StateError("Cyclic dependency detected!");
  }

  for (final dependency in sorted) {
    final typeName = dependency.getTypeName();

    _logger.info("Instantiating ${typeName}");

    final instance = dependency.factory(this);

    _instances[typeName] = MapEntry(this, instance);
  }

  final completers = {
    for (final dep in sorted) dep.getTypeName(): Completer<void>(),
  };

  final noDeps = <Future<void>>[];
  final noDepsNames = <String>[];

  for (final dep in sorted) {
    if (dep.dependsOn.isEmpty) {
      final typeName = dep.getTypeName();
      final instance = _instances[typeName]!.value;

      if (instance is IInitializable) {
        noDepsNames.add(typeName);
        noDeps.add(instance.initialize());
        completers[typeName]!.complete();
      }
    }
  }

  if (noDeps.isNotEmpty) {
    _logger.fine("Initializing ${noDepsNames.join(", ")}");
    await Future.wait(noDeps);
  }

  Future<void> initializeDependency(String typeName) async {
    if (completers[typeName]!.isCompleted) {
      return;
    }

    final instance = _instances[typeName]!.value;

    if (instance is IInitializable) {
      final dependency = sorted.firstWhere(
        (dep) => dep.getTypeName() == typeName,
      );

      final dependenciesCompleters = dependency.dependsOn
          .map((d) => completers[d.toString()]!)
          .where((c) => c.isCompleted == false)
          .map((c) => c.future);

      final dependenciesNames = dependency.dependsOn
          .map(
            (d) => MapEntry<Type, Completer<void>>(
              d,
              completers[d.toString()]!,
            ),
          )
          .where((c) => c.value.isCompleted == false)
          .map((c) => c.key)
          .toList();

      if (dependenciesCompleters.isNotEmpty) {
        _logger.fine("Initializing ${dependenciesNames.join(", ")}");
        await Future.wait(dependenciesCompleters);
      }

      await instance.initialize();
      _logger.info("${typeName} initialized");
    }

    completers[typeName]!.complete();
  }

  final initializers = sorted
      .map(
        (dep) => initializeDependency(dep.getTypeName()),
      )
      .toList();

  await Future.wait(initializers);

  return this;
}