Design patterns in use
- Singleton (enforced in Awake):
ApplicationCoordinatorexposes a staticInstancefield.Awakeassigns it on the first instance, callsDontDestroyOnLoad, and destroys any duplicate. The code carries a TODO to remove this singleton in favor of runtime initialization. - Service locator:
ServiceLocatorholds oneBusRegistryreference set during bootstrap withInitialize, and other classes retrieve it withGetBusRegistry.FlowControllerandApplicationFlowControlleruse it to reach the command and query buses. - Factory:
ApplicationEventFactorybuilds and raisesEventmessages onDomain.Application.ApplicationQueryFactorybuilds and sendsQuerymessages on the same domain. - Adapter:
IApplicationLifecycleAdapteris the contract for bridging an engine's lifecycle callbacks.UnityLifecycleAdapterimplements it for Unity. - Template method:
ApplicationLifecycleHandler.OnFocusChangedis not overridable. It raises the standardApplicationEventFactory.OnFocusChangedevent, then calls the abstractProcessFocusChangedhook that each project implements. - Abstract base with hooks:
ApplicationCoordinatordefines abstract hooks (RegisterPlatformModules,RunPlatformModules,InitializeStartupPresentation,InitializeVolumeState,CreateLifecycleHandler,CreateLifecycleAdapter) that each project fills in. - Partial static class:
ApplicationFlowServiceis declaredpartial, so project code can add content-specific methods in separate files.
How the system connects to dependencies
Initialization system. ApplicationCoordinator implements IInitializable and IAsyncInitializable. Its Awake registers it with InitializationRegistry, which places it in the sync list (it does not implement the event receiver interface) and the async list. InitializationManager.Start runs Init on the sync list, then awaits InitAsync on the async list, clears the registry, and raises SystemsReady.
Signal system. InitAsync calls WhenRaised on SaffronLibrarySignals.SystemsReady with InitializeApplication as the callback. If SystemsReady has already been raised, the callback runs immediately. After that, InitializeApplication runs the platform module, presentation, and lifecycle steps.
Bus system. Events are published through BusUtility.Raise(Event), which forwards to the EventBus. Queries are sent through BusUtility.AskAndGet, which forwards to the QueryBus. ApplicationFlowController registers OnQuery with queryBus.Register(Domain.Application, ...) during Init, so the query bus routes Application-domain queries to the coordinator.
Command system. ApplicationFlowController extends FlowController, which registers HandleCommand on the CommandBus for Domain.Application during Init. This system's HandleCommand is empty, so no commands are handled for this domain.
Param and query contracts. Messages carry data in Params. ApplicationParamKeys defines the typed keys (ParamKey<bool> for focus, ParamKey<ApplicationSubjects> for the subject). The focus-loss answer is written to CoreParamKeys.Duration, a ParamKey<float> defined in the Contracts system.
Contracts. Domain.Application (value 7) and QueryVerb.Get route the messages. CoreParamKeys.Duration is the result key for focus-loss queries.
Key concepts and terminology
- Subject:
ApplicationSubjects(Focus,Application) identifies which application topic a query or event is about. It is stored underApplicationParamKeys.ApplicationSubject. - Verb:
ApplicationEventVerb(Changed,Quit) identifies what happened in an event. TheEventverb is set to the enum's integer value. - Focus-loss time:
ApplicationLogicManagerstores a single float timestamp, in engine seconds, fromSetFocusLossTime.GetFocusLossDurationreturns the current time minus that timestamp. The stored value starts at 0, so before any call toSetFocusLossTimethe duration equals the time since startup. - Realtime clock:
UnityLifecycleAdapter.GetRealtimeSinceStartupreturnsTime.realtimeSinceStartup. The same clock must be used for bothSetFocusLossTimeandGetFocusLossDuration. - Lifecycle handler:
IApplicationLifecycleHandlerreceives forwarded focus changes. Its implementation,ApplicationLifecycleHandler, is engine-agnostic.
Event and data flow
Focus flow:
- Unity calls the private
OnApplicationFocusmethod onUnityLifecycleAdapter. - The adapter calls
handler.OnFocusChanged(hasFocus)on the injected handler, if one has been set. ApplicationLifecycleHandler.OnFocusChangedcallsApplicationEventFactory.OnFocusChanged(hasFocus), which raises anEvent(verbChanged, subjectFocus,HasFocusparam) onDomain.Applicationthrough theEventBus.- The handler calls
ProcessFocusChanged(hasFocus), the project-specific hook.
Quit flow:
- Unity calls the private
OnApplicationQuitmethod onUnityLifecycleAdapter. - The adapter calls
ApplicationEventFactory.OnApplicationQuit, which raises anEvent(verbQuit, subjectApplication) onDomain.Application.
Focus-loss query flow:
- A caller calls
ApplicationQueryFactory.GetFocusLossDuration, which builds aQueryforDomain.Applicationwith verbGetand subjectFocus, then callsBusUtility.AskAndGetwithCoreParamKeys.Duration. - The
QueryBusroutes the query toApplicationFlowController.OnQuery, which callsApplicationCoordinator.ProcessQuery. ProcessQueryreads the subject, callslogicManager.GetFocusLossDuration(lifecycleAdapter.GetRealtimeSinceStartup()), and writes the result toCoreParamKeys.Duration.- The answer returns to the caller through the same query object.
Startup flow:
Awakeregisters the coordinator withInitializationRegistry.InitializationManager.StartcallsInit, which createsApplicationLogicManager.InitializationManager.StartawaitsInitAsync, which subscribesInitializeApplicationtoSystemsReadyand then awaitsInitializeVolumeState.- When
SystemsReadyis raised,InitializeApplicationcallsRegisterPlatformModules,RunPlatformModules,InitializeStartupPresentation, andInitializeLifecycle. InitializeLifecyclecreates the handler and adapter through the abstract factory hooks, stores them, and callslifecycleAdapter.Initialize(lifecycleHandler).