-
Notifications
You must be signed in to change notification settings - Fork 0
App and AppInstance
App is Bootstrap's static entry point. It holds the one currently running AppInstance and exposes the handful of static operations that don't belong on the instance itself: getting the current instance, locating a service without going through ServiceRef<T> first, resetting the app, and a couple of editor-only lifecycle hooks.
It's split across two files: App.cs, which is cross-platform, and App.Editor.cs, which only compiles in the editor and drives the transitions covered in Bootstrap Flow.
App.Locate<T>() and App.TryLocate<T>() are shortcuts for App.Instance.Locate<T>()/App.Instance.TryLocate<T>(). See Services for the full set of ways to access a service.
App.GetExtension<T>() returns a singleton instance of T, for any type registered as an extension via the assembly-level [AppExtensionType(typeof(YourExtension))] attribute. YourExtension just needs to implement the empty marker interface IAppExtension and have a public parameterless constructor. Registration happens once per domain, by scanning every loaded assembly for the attribute, so an extension doesn't need any manual wiring beyond declaring the attribute and implementing the interface.
App.Reset() tears down the current instance and starts a fresh one, without going through an actual editor Play mode transition: it reloads the first scene and re-triggers the runtime entry point if the app is playing, or reschedules the editor app if not. It's a no-op (with a warning) if there's no current instance.
The editor-only half of App, App.Editor.cs, is what actually drives entering and exiting play mode, described in full in Bootstrap Flow.
The Bootstrap library attempts to minimize static state as much as possible to avoid difficult-to-debug issues between App Sessions. One way it does so is by storing as much application state as possible in an instance of an object called an AppInstance. It stores its services, task scheduler, current lifecycle state, and a handful of identifiers for that particular session. AppInstance itself is abstract, so what you actually get is one of its subclasses, PlayModeAppInstance, BuildAppInstance, or EditModeAppInstance (the first two both derive from an intermediate RuntimeAppInstance). Only one AppInstance exists at a time, accessible through App.Instance.
Every AppInstance has a SessionGuid uniquely identifying that particular session. This can be used as an indirect mechanism to detect session changes.
-
App.Instance: returns the currentAppInstance, or throws if there isn't one. -
App.TryGetInstance(out AppInstance instance)and its typed overloadApp.TryGetInstance<T>(out T instance): the non-throwing equivalent, the typed version only succeeding if the current instance is (or derives from)T.
-
ServiceLocator: this session's service container, created fresh at the start ofBootstrapAsync(). See Services. -
TaskScheduler: this session's cooperative work queue, also created fresh at bootstrap. See Self Setup vs. Cooperative Work. -
CanLocateServices: true once theServiceLocatorexists and reports it's ready to be queried. -
Locate<T>()/TryLocate<T>(): instance-level versions of the staticApp.Locate<T>()/App.TryLocate<T>().
AppInstance.ScopeToApp<T>(obj), and its static shortcuts App.ScopeToApp(obj) / App.InstantiateAndScopeToApp(original), mark a UnityEngine.Object as belonging to the current app instance. DontDestroyOnLoad is applied to it, and it's added to this instance's list of app-scoped objects so it survives scene loads for the rest of the session.
A service that needs a scene-independent GameObject, like a UI service that needs an EventSystem to exist no matter what scene is loaded, is a typical use for App.InstantiateAndScopeToApp:
[Serializable]
public class UIService : IService
{
[field: SerializeField]
private GameObject EventSystemPrefab { get; set; }
private GameObject _eventSystemInstance;
void IService.InitService(BootstrapContext context)
{
// The instantiated `EventSystem` survives every scene load for the rest of the session,
// and gets destroyed along with the rest of the app-scoped objects on a reset.
_eventSystemInstance = App.InstantiateAndScopeToApp(EventSystemPrefab);
}
}Those objects aren't cleaned up just because the app quits, they're only destroyed as part of a reset. A quit disposes services and cancels the app's lifetime token. A reset does that too, and then additionally destroys every app-scoped object, since a reset expects to rebuild everything from scratch.
BootstrapAsync() is what App awaits to bring an instance up. Each subclass overrides it to layer its own sequence on top (calling the base implementation first), which is what Bootstrap Flow walks through in detail for the runtime and edit mode cases.
AppLifetimeCancellationToken is cancelled when the instance is disposed, whether from a quit or a reset. It's the token that Bootstrap's own internal async work (like the task scheduler's flush loop) is tied to, and it's available for your own long-running work that should stop when the session ends.
Bootstrap expects a session to be able to start and stop repeatedly, in the editor or via a manual App.Reset(), without leftover state from a previous session leaking into the next one. Writing code that holds up to that comes down to two habits.
A static field survives longer than any single AppInstance. If a service (or any other code) stores its state in a static field instead of on itself, that state has no natural point where it gets cleared between sessions, it'll still be there the next time the app boots, whether that's after a manual reset or just the next time you press Play. AppInstance exists specifically so state has somewhere to live that's guaranteed to start fresh every session. Prefer storing state on a service and reading it through Accessing Services instead of a static field.
Calling UnityEngine.Object.DontDestroyOnLoad directly makes an object survive scene loads, but Bootstrap has no way of knowing that object exists, so it won't be cleaned up when the app resets. The result is a duplicate the next time the same service runs and creates another one, since the old instance never went away.
App-Scoped Objects solve this the same way, App.ScopeToApp/App.InstantiateAndScopeToApp make the same DontDestroyOnLoad call, but also register the object with the current AppInstance, so it's destroyed automatically as part of a reset instead of quietly accumulating.