Skip to content

Bootstrap Flow

Mika Notarnicola edited this page Jul 20, 2026 · 8 revisions

The Runtime Bootstrap Flow

This section will describe exactly what happens from beginning to end during runtime. In the editor this is from the moment you press Play in the editor through to a fully initialized game, and back again when you stop. In a build it's from the moment the executable starts to when it quits (or after the AppInstance has been reset). There's more common behavior than context-specific behavior between play mode and a player build, so they will both be described together. The differences are more easily understood by referring to the diagram.

Note that parts of this page describe what happens using the default, built-in implementations of IPreBootstrapHandler and IPostBootstrapHandler.

1. Exiting Edit Mode (Editor Only)

When you hit the Play button, the Unity editor begins the process of exiting edit mode. This is where the editor's Bootstrap flow begins.

If the Editor Flow Enabled setting is off, none of the following happens and play mode just starts on whatever scenes are loaded, with no bootstrap redirect at all.

Assuming it's on:

  1. Any unsaved untitled scene forces a save prompt. If you decline, play mode is cancelled outright.
  2. Bootstrap resolves which environment to boot: the currently Selected Environment wins if one is set, otherwise it checks whether the active scene has a specific environment mapped to it, and falls back to the project's Default Play Mode Environment if not. If none of these resolves to anything, setup stops here and play mode starts on the active scene unchanged. See Choosing an Environment for the full precedence.
  3. EditorSceneManager.playModeStartScene is set to the bootstrap scene (the first valid entry in the project's Scenes in Build list). Unity will load this scene when entering play mode, regardless of the scenes you had open in edit mode.
  4. Every other currently open scene, along with your current object selection, is recorded and saved to SessionState alongside the resolved environment. SessionState survives the transition into play mode, which is how the flow finds its way back to your original scenes later.

2. RuntimeEntryPoint

The runtime bootstrap sequence starts from a method called RuntimeEntryPoint. This utilizes RuntimeInitializeOnLoadMethod, and executes at the earliest point Unity offers to run code the application has loaded.

From there, an AppInstance is created and stepped through a fixed sequence of states:

State What happens
BootstrapHandlerDiscovery Determines which pre/post bootstrap handlers will run this session (the environment can supply its own; otherwise Bootstrap's defaults are used).
PreBootstrap Runs the pre-bootstrap handler, before any service exists.
ServiceDiscovery Clones the environment's Service List asset and reads the it's entries, sorting them by init priority.
ServiceBinding Registers each service under its type (and any additional types it declares) so it can be located later.
ServiceInit Calls InitService on every service, in priority order.
AsyncTaskFlush Waits for any work services scheduled during init to finish.
PostBootstrap Runs the post-bootstrap handler, now that every service is live.
Ready Bootstrap is complete.

See Services for what happens during service discovery, binding, and init in more detail.

3. Post Bootstrapping

After every service has finished initializing the environment's selected IPostBootstrapHandler implementation executes. If no implementation has been selected then the default implementation is used, which different depending on if in the editor or a player build.

In the Editor

Restoring your original scenes and selection happens as part of the default editor IPostBootstrapHandler implementation, PlayModeBootstrapHandler, which runs after every service has finished initializing.

  • If you had other scenes open (recorded in step 1), they're loaded back now: the first one (the "active scene" from edit mode) replaces the bootstrap scene, and the rest are loaded additively alongside it. If you didn't have any other scenes open, the scene at index 1 is loaded instead.
  • Your original object selection from edit mode is restored a couple of frames later, once the reloaded scenes have had a chance to run their own Awake calls. Due to editor limitations any scene foldouts will remain collapsed, but the inspector should still reflect your restored selection.

This step is skipped entirely during play mode test runs, so tests aren't disturbed by scene reloads.

If an environment supplies its own custom post-bootstrap handler, this restoration step doesn't happen automatically. A custom handler that wants scene/selection restoration needs to do this manually, though you can easily invoke default behavior. See Bootstrap Handlers for details.

In a Build

The default implementation of IPostBootstrapHandler for a player build, BuildBootstrapHandler, simply loads the scene at index 1. There is an optional Addressables implementation, AddressablesBootstrapHandler, which loads the next scene via an Addressables key.

4. App Exit

This occurs when stopping play mode or when quitting a build.

In the Editor

Stopping play mode happens in two steps:

  1. On ExitingPlayMode, the running AppInstance is told to quit. Every service that implements IDisposable is disposed, and the cloned service list used for this session is discarded.
  2. On EnteredEditMode, the play-mode AppInstance reference is finally dropped, playModeStartScene is reset back to null, and a fresh edit-mode AppInstance is initialized from the project's Edit Mode Services list. This runs its own, much shorter bootstrap: discovery, binding, and init only, with no pre/post-bootstrap handlers involved.

Unity itself is responsible for restoring your original scene setup when play mode ends. That part isn't something Bootstrap needs to manage; it's native editor behavior independent of playModeStartScene, which only affects what loads when entering play mode.

In a Build

During Application Quit the running AppInstance is told to quit. Every service that implements IDisposable is disposed, and the cloned service list used for this session is discarded.

Edge Cases

  • Editor Flow disabled, or no environment resolves for the active scene: play mode starts on the active scene directly, with no redirect and no service bootstrap.
  • No valid scene at the top of the Scenes in Build list: same fallback as above; a warning is logged.
  • Recompiling scripts while an AppInstance is running forces play mode to stop and tears down whatever instance was live, since the domain reload would otherwise leave it in an invalid state.
  • Bootstrap also exposes a manual reset function, App.Reset(), which tears down and re-runs the same sequence on demand, without an actual play mode transition.

Diagram

graph TD
    PlayInEditor(["Press Play in the Editor"])
    StartBuild(["Launch a Build"])

    PlayInEditor --> ExitEditMode["1. Exiting Edit Mode:<br/>resolve environment, redirect to<br/>bootstrap scene, snapshot scenes/selection"]

    ExitEditMode --> RuntimeEntryPoint
    StartBuild  --> RuntimeEntryPoint

    RuntimeEntryPoint["2. RuntimeEntryPoint"]
    RuntimeEntryPoint --> HandlerDiscovery["BootstrapHandlerDiscovery"]
    HandlerDiscovery --> PreBootstrap["PreBootstrap"]
    PreBootstrap --> ServiceDiscovery["ServiceDiscovery"]
    ServiceDiscovery --> ServiceBinding["ServiceBinding"]
    ServiceBinding --> ServiceInit["ServiceInit"]
    ServiceInit --> AsyncTaskFlush["AsyncTaskFlush"]
    AsyncTaskFlush --> PostBootstrap["PostBootstrap<br/>3. Post Bootstrapping<br/>(invoke IPostBootstrapHandler)"]

    PostBootstrap --> IsEditor{"In the editor?"}
    IsEditor -->|Yes.| RestoreScenes["Reload edit mode scenes and restore selection (PlayModeBootstrapHandler)."]
    IsEditor -->|No.| LoadNext["Load the scene at index 1. (BuildBootstrapHandler)."]

    RestoreScenes --> Ready["Ready"]
    LoadNext --> Ready

    IsEditor2{"In the editor?"}

    Ready --> IsEditor2
    
    StopPlayMode["Stopping Play Mode"]
    QuittingBuild["Quitting Build"]
    
    IsEditor2 --> |Yes.| StopPlayMode
    IsEditor2 --> |No.| QuittingBuild

    StopPlayMode --> AppQuit
    QuittingBuild --> AppQuit

    AppQuit["4. App Quit:<br/>Dispose services, deinitialize AppInstance."]
    IsEditor3{"In the editor?"}

    AppQuit --> IsEditor3
    IsEditor3 --> |Yes.| EnteredEditMode
    EnteredEditMode["EnteredEditMode: EditModeAppInstance initializes."]
Loading

Edit Mode Bootstrapping

Separately from the runtime flow above, an EditModeAppInstance bootstraps any time the editor is open and not in Play mode. It's what keeps the Edit Mode Services list running in the background for editor tooling, and it's considerably simpler: there's no environment, no IPreBootstrapHandler/IPostBootstrapHandler, and no BootstrapHandlerDiscovery/PreBootstrap/AsyncTaskFlush/PostBootstrap steps.

One major reason for its simplicity is the lack of control over the edit mode application lifecycle we have as Unity users. The runtime flow effectively sandboxes gameplay code via utilization of a bootstrap scene at index 0 and RuntimeInitializeOnLoad. There are very few similar and reliable mechanisms available to us in edit mode. This means no async initialization is possible and user code will execute immediately when edit mode begins, so initialization must be synchronous to minimize exceptions, errors, and invalid state from out-of-order operations.

Triggers

Edit mode (re)initialization occurs whenever:

  • The editor finishes loading or a domain reload completes.
  • Play mode is exited and the editor returns to edit mode.
  • The Edit Mode Services setting itself changes, in either Project or User scope.

Guards

Before doing anything, two guards are checked: if the editor is currently compiling, or is currently playing or about to change play mode, initialization is skipped entirely.

Bootstrapping

If neither guard applies, it looks up the configured Edit Mode Services list. If none is set, bootstrapping stops there, no services are created. If one is set, it's cloned, then stepped through ServiceDiscovery, ServiceBinding, and ServiceInit the same way the runtime flow's Bootstrap Sequence does, any scheduled task work is flushed, and the instance reaches Ready.

Teardown

The EditModeAppInstance is torn down the same way any other AppInstance is: services are disposed, and the cloned list is discarded. This happens whenever the editor is about to exit edit mode, a script recompile starts, or the Edit Mode Services setting changes out from under it.

Diagram

graph TD
    Trigger(["Editor loads, returns to edit mode,<br/>or Edit Mode Services changes"])
    Trigger --> Guard{"Compiling, or<br/>playing/changing play mode?"}

    Guard -->|Yes, skip| Skipped(["No initialization"])
    Guard -->|No| HasList{"Edit Mode Services<br/>configured?"}

    HasList -->|No| NoOp(["Bootstrapping stops here,<br/>no services created"])
    HasList -->|Yes| Discovery["ServiceDiscovery"]

    Discovery --> Binding["ServiceBinding"]
    Binding --> Init["ServiceInit"]
    Init --> Flush["Flush any scheduled task work"]
    Flush --> Ready["Ready"]

    Ready --> Teardown{"Exiting edit mode, recompiling,<br/>or Edit Mode Services changed?"}
    Teardown -->|Yes| Dispose["Dispose services,<br/>discard cloned list"]
Loading

Clone this wiki locally