Initialization Pipeline

An ordered async pipeline that runs your Unity startup tasks with criticality levels, weighted progress and telemetry streaming to any loading screen.

What the system is for

Every Unity project has a startup sequence. Assets need to be loaded, services need to be registered, remote config needs to be fetched, and the game session needs to be prepared before the player sees the first frame of gameplay. Without a structure for that sequence, the boot code ends up scattered across MonoBehaviours, Awake chains and coroutines that are impossible to test or extend.

The Initialization Pipeline in Serenity replaces that scattered code with an ordered set of async tasks. Each task declares its criticality and weight, the pipeline runs them in sequence, and progress and failures stream to a telemetry port that any loading screen can consume.

The Unity problem

Unity's default initialization model is flat. Awake and Start fire in an order that depends on script execution order settings, which are fragile and invisible. Teams work around this with singleton managers, static flags or coroutine chains, all of which break under refactoring. There is no standard way to express that one startup step is critical and another is optional, or to show a progress bar that accurately reflects how much boot work remains.

As a project grows, the boot sequence accumulates silent dependencies. A service assumes another is already initialized. A remote fetch silently fails and leaves the session in a partial state. A loading screen shows a stuck spinner because nobody hooked up progress events. These problems only manifest at runtime and only in specific device or network conditions.

How Serenity approaches it

Serenity models each startup step as an ITask with a unique ID, a TaskCriticality level and a float Weight. TaskCriticality has three values: Critical, Required and Degradable. Critical tasks abort the pipeline on failure. Degradable tasks log their failure and let the pipeline continue. The pipeline is defined through IInitializationPipelineProfileDefinition, which exposes a read-only list of ITaskDefinition instances that each build a concrete ITask at runtime via a TaskBuildContext.

The InitializationPipelineService receives an ordered set of ITask instances, an ISkipPolicy, an IInitializationPipelineTelemetry port and an ILogService. It builds a plan of InitializationPipelineStep records and runs them sequentially via RunAsync, calling the telemetry port at each step boundary so loading screens receive OnStarted, OnProgress, OnTaskCompleted, OnTaskFailed and OnCompleted events without any coupling to the pipeline internals.

How it fits into Serenity

The Initialization Pipeline lives in the Serenity.InitializationPipeline namespace and follows the foundation's layered Domain, Application and Installation structure. The Domain layer defines InitializationPipelineStep. The Application layer exposes IInitializationPipelineProfileDefinition, IInitializationPipelineTelemetry, ISkipPolicy and InitializationPipelineService. The Installation layer provides InitializationPipelineInstaller and its input DTO, which carries the task providers, skip policy, telemetry implementation and log service.

Tasks are authored as ScriptableObject-based definitions using the Task aggregate from the Serenity.Tasking namespace. ITaskDefinition, ITaskFactory and TaskBuildContext handle the creation boundary. The ReflectionCallTask implementation lets you point a task at any method in your codebase by type and method name without writing a dedicated ITask class. The pipeline cooperates with the AssetPrefetcher and Addressables workflow for warming assets at boot, with the Game Session for session preparation, and with the Event Dispatcher for publishing startup signals.

Practical workflow

  1. Create a UnityInitializationPipelineProfile ScriptableObject and add task definitions in the desired execution order.
  2. Assign each task definition a TaskCriticality level and a float Weight that reflects its share of boot time.
  3. Implement IInitializationPipelineTelemetry in your loading screen to receive progress and failure events.
  4. Optionally implement ISkipPolicy to allow non-critical tasks to be bypassed based on runtime conditions.
  5. Register the pipeline through UnityInitializationPipelineInstaller, passing providers, the skip policy and the telemetry implementation.
  6. Call RunAsync on InitializationPipelineService at startup and await the boolean result to know whether all critical tasks succeeded.

What you get

  • ITask primitive with ID, TaskCriticality, Weight, RunAsync and LastError
  • Three criticality levels: Critical, Required and Degradable
  • Weighted progress tracking through InitializationPipelineStep records
  • IInitializationPipelineTelemetry port with OnStarted, OnProgress, OnTaskCompleted, OnTaskFailed and OnCompleted
  • Pluggable ISkipPolicy to bypass tasks at runtime without modifying the profile
  • IInitializationPipelineProfileDefinition and ScriptableObject authoring via UnityInitializationPipelineProfile
  • ReflectionCallTask for zero-boilerplate method dispatch without a dedicated ITask class
  • UnityInitializationPipelineInstaller wired into the foundation's installer pattern

When to use this

  • Projects that need a reliable, ordered boot sequence before the first gameplay scene is loaded.
  • Games with a loading screen that must reflect accurate progress across heterogeneous startup work.
  • Projects where some startup steps are optional or skippable depending on feature flags or device capability.
  • Codebases that want boot steps to be authored in the Unity inspector without writing new C# classes for each one.

Related systems

Use Serenity when you want a startup sequence that is inspectable, testable and capable of driving any loading screen, without scattering initialization logic across Awake chains and singleton managers.

Back to the home page