docs
Systems
Execution
InitializationSystem
Architecture and Design

Design patterns in use

  • Template Method. InitializationManager is an abstract MonoBehaviour. Its Awake and Start methods define the bootstrap flow, and the abstract RegisterSystems method is the hook that derived project classes override to supply their own registrations.
  • Service Locator. InitializationManager builds the BusRegistry and passes it to ServiceLocator.Initialize, so later code can reach the buses globally. The locator itself is a dependency and is not documented here.
  • Static registry. InitializationRegistry is a static class that holds the lists of components between registration and initialization.

Connections to other systems

  • BusRegistry and ServiceLocator (dependencies). The manager creates a BusRegistry containing a QueryBus, CommandBus, EventBus, SignalBus, and RequestDispatcher, and registers it with ServiceLocator in Awake. This happens before RegisterSystems, so registration code can rely on the buses.
  • BusUtility (dependency). Start calls BusUtility.Raise with a Signal built from SaffronLibrarySignals.SystemsReady once initialization completes.
  • SaffronLibrarySignals.SystemsReady (dependency). This is the signal other systems listen for to know that bootstrap has finished. The signal name is defined in the signals contracts, outside this system.
  • IEventReceiver (dependency). The registry is meant to check for this interface to decide which sync components are initialized first.

Lifecycle and data flow

  1. Awake (manager). InitializeServiceLocator builds the BusRegistry and calls ServiceLocator.Initialize. Then RegisterSystems runs, and each derived-class call to SafeRegister constructs a component and passes it to InitializationRegistry.RegisterInitializable.
  2. Awake (scene components). Components that are MonoBehaviours in the scene can call InitializationRegistry.Register on themselves in their own Awake. The registry documentation describes this path as the way the application coordinator registers itself.
  3. Start (manager). Unity calls Start after the Awake calls for objects in the scene. Start creates an InitializationOrchestrator, calls InitializeSyncSystems, and awaits InitializeAsyncSystems.
  4. Sync phase. The orchestrator runs Init on every item in SyncInitializableEventReceivers, then on every item in SyncInitializables, in registration order.
  5. Async phase. The orchestrator takes a snapshot of AsyncInitializables and starts each InitAsync call. It returns a Task from Task.WhenAll that completes when all have finished.
  6. Completion. The manager calls InitializationRegistry.Clear and raises SystemsReady.

Failure behavior

  • Construction failures in SafeRegister are caught and logged. The component is not registered, and bootstrap continues.
  • Async failures in InitAsync are caught and logged per component, so one failing component does not fault the others. Because Task.WhenAll receives only wrapped tasks, it does not fault, and SystemsReady is still raised.
  • Sync failures in Init are not caught by the orchestrator. An exception thrown from Init propagates out of InitializeSyncSystems, which stops Start before the async phase, before Clear, and before SystemsReady. Because Start is async void, the exception is raised on the Unity side rather than returned to a caller.

Key terms

  • Sync initializable. A component implementing IInitializable. It is initialized by calling Init during the sync phase.
  • Async initializable. A component implementing IAsyncInitializable. It is initialized by awaiting InitAsync during the async phase.
  • Event receiver. A sync component the registry is meant to classify separately so it can subscribe before other sync components run.
  • Registration. Adding a component to one or more lists in InitializationRegistry. A component that implements both interfaces is added to both lists.

Thread safety

The code shows no locking. InitializationRegistry uses plain List instances, and nothing in the code synchronizes access to them. Registration is expected to happen during Awake, and the orchestrator reads the lists during Start. The async phase snapshots AsyncInitializables with ToList before starting any work, so the async calls do not iterate the live list. The sync and async phases use the same Start method, which Unity runs on the main thread, so continuations after await return to the Unity main thread.