-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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:
- Any unsaved untitled scene forces a save prompt. If you decline, play mode is cancelled outright.
- 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.
-
EditorSceneManager.playModeStartSceneis 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. - Every other currently open scene, along with your current object selection, is recorded and saved to
SessionStatealongside the resolved environment.SessionStatesurvives the transition into play mode, which is how the flow finds its way back to your original scenes later.
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.
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.
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
Awakecalls. 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.
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.
This occurs when stopping play mode or when quitting a build.
Stopping play mode happens in two steps:
- On
ExitingPlayMode, the runningAppInstanceis told to quit. Every service that implementsIDisposableis disposed, and the cloned service list used for this session is discarded. - On
EnteredEditMode, the play-modeAppInstancereference is finally dropped,playModeStartSceneis reset back to null, and a fresh edit-modeAppInstanceis 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.
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.
- 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
AppInstanceis 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.
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."]
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.
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.
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.
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.
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.
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"]