268 lines
11 KiB
Markdown
268 lines
11 KiB
Markdown
|
|
# Apple Hills Bootstrap System
|
|||
|
|
|
|||
|
|
A concise, code-first overview of the project’s bootstrap system. It covers how boot runs (runtime and editor), how to wait for boot to complete, how to register post-boot initialization, how to retrieve bootstrapped systems safely, and practical case studies.
|
|||
|
|
|
|||
|
|
## Table of Contents
|
|||
|
|
- [What This Solves](#what-this-solves)
|
|||
|
|
- [Architecture at a Glance](#architecture-at-a-glance)
|
|||
|
|
- [Quick Start (Code-First)](#quick-start-code-first)
|
|||
|
|
- [Check/Wire Boot Completion](#checkwire-boot-completion)
|
|||
|
|
- [Register Post-Boot Initialization](#register-post-boot-initialization)
|
|||
|
|
- [Await Boot Asynchronously](#await-boot-asynchronously)
|
|||
|
|
- [Retrieve Bootstrapped Systems Safely](#retrieve-bootstrapped-systems-safely)
|
|||
|
|
- [Editor Workflow (Project Settings + Edit-Mode Boot)](#editor-workflow-project-settings--edit-mode-boot)
|
|||
|
|
- [Enable/Disable Edit-Mode Boot](#enabledisable-edit-mode-boot)
|
|||
|
|
- [What Happens in Edit Mode (Caveats)](#what-happens-in-edit-mode-caveats)
|
|||
|
|
- [Case Studies](#case-studies)
|
|||
|
|
- [UI Page Controller post-boot wiring](#ui-page-controller-post-boot-wiring)
|
|||
|
|
- [Card System post-boot registration](#card-system-post-boot-registration)
|
|||
|
|
- [Settings access after boot](#settings-access-after-boot)
|
|||
|
|
- [Events, APIs, and Keys](#events-apis-and-keys)
|
|||
|
|
- [Troubleshooting / FAQ](#troubleshooting--faq)
|
|||
|
|
- [Paths & Namespaces](#paths--namespaces)
|
|||
|
|
- [Change Log](#change-log)
|
|||
|
|
|
|||
|
|
## What This Solves
|
|||
|
|
- Centralizes startup of global systems via a data-driven list of prefabs.
|
|||
|
|
- Guarantees an explicit moment when all bootstrapped systems are ready.
|
|||
|
|
- Provides a robust way to run your own initialization code after boot, including priority ordering.
|
|||
|
|
- Supports editor preview of the booted world without having to enter Play Mode.
|
|||
|
|
|
|||
|
|
## Architecture at a Glance
|
|||
|
|
- Runtime orchestrator: `CustomBoot` (static)
|
|||
|
|
- Starts automatically via `RuntimeInitializeOnLoadMethod(BeforeSplashScreen)`.
|
|||
|
|
- Loads `CustomBootSettings` with Addressables and instantiates `BootPrefabs` under a runtime container (`DontDestroyOnLoad`).
|
|||
|
|
- Emits progress via `CustomBoot.OnBootProgressChanged` and completion via `CustomBoot.OnBootCompleted`.
|
|||
|
|
- Calls `BootCompletionService.HandleBootCompleted()` when done to unlock post-boot actions.
|
|||
|
|
- Boot data: `CustomBootSettings` (`ScriptableObject`)
|
|||
|
|
- Field: `GameObject[] BootPrefabs` — prefabs to instantiate at boot (usually contain your singletons/services).
|
|||
|
|
- Methods: `Initialise()`/`InitialiseSync()` instantiate prefabs and report progress; `Cleanup()` destroys them.
|
|||
|
|
- Post-boot coordination: `BootCompletionService` (static)
|
|||
|
|
- `bool IsBootComplete` — runtime flag.
|
|||
|
|
- `event Action OnBootComplete` — notify late subscribers.
|
|||
|
|
- `void RegisterInitAction(Action action, int priority = 100, string name = null)` — queue work to run after boot (priority: lower runs earlier). If boot has already completed, runs immediately.
|
|||
|
|
- `Task WaitForBootCompletionAsync()` — await boot in async flows.
|
|||
|
|
- Editor integration: `CustomBootEditorUtils`, `CustomBootSettingsProvider`, `CustomBootProjectSettings`
|
|||
|
|
- Project settings UI to assign Addressable references for runtime/editor boot settings.
|
|||
|
|
- Edit-mode boot toggle to preview booted state in the Scene view.
|
|||
|
|
|
|||
|
|
## Quick Start (Code-First)
|
|||
|
|
|
|||
|
|
### Check/Wire Boot Completion
|
|||
|
|
```csharp
|
|||
|
|
using Bootstrap;
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
public class BootGateExample : MonoBehaviour
|
|||
|
|
{
|
|||
|
|
private void Awake()
|
|||
|
|
{
|
|||
|
|
if (BootCompletionService.IsBootComplete)
|
|||
|
|
{
|
|||
|
|
SafeStart();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
BootCompletionService.OnBootComplete += SafeStart;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnDestroy()
|
|||
|
|
{
|
|||
|
|
BootCompletionService.OnBootComplete -= SafeStart;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void SafeStart()
|
|||
|
|
{
|
|||
|
|
// Your code here will only run once all BootPrefabs have been instantiated.
|
|||
|
|
Debug.Log("All systems ready – starting feature!");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Register Post-Boot Initialization
|
|||
|
|
Register work to run once all bootstrapped systems are ready. Two equivalent approaches are supported:
|
|||
|
|
|
|||
|
|
1) Inline lambda (great for small one‑offs)
|
|||
|
|
```csharp
|
|||
|
|
using Bootstrap;
|
|||
|
|
|
|||
|
|
// Lower priority runs earlier; name is used in logs.
|
|||
|
|
BootCompletionService.RegisterInitAction(
|
|||
|
|
action: () => AnalyticsService.Instance.StartSession(),
|
|||
|
|
priority: 50,
|
|||
|
|
name: "Start Analytics Session"
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Can also skip priority and name:
|
|||
|
|
BootCompletionService.RegisterInitAction(
|
|||
|
|
action: () => AnalyticsService.Instance.StartSession()
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2) Full function callback (method group)
|
|||
|
|
```csharp
|
|||
|
|
using Bootstrap;
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
public class AnalyticsBootstrap : MonoBehaviour
|
|||
|
|
{
|
|||
|
|
private void Awake()
|
|||
|
|
{
|
|||
|
|
// Pass a method group (no lambda needed). Helpful for reuse and testing.
|
|||
|
|
BootCompletionService.RegisterInitAction(
|
|||
|
|
action: InitAnalytics,
|
|||
|
|
priority: 50,
|
|||
|
|
name: "Init Analytics"
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Can also skip priority and name:
|
|||
|
|
BootCompletionService.RegisterInitAction(
|
|||
|
|
action: InitAnalytics
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void InitAnalytics()
|
|||
|
|
{
|
|||
|
|
AnalyticsService.Instance.StartSession();
|
|||
|
|
// You can call other setup methods here as needed
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
Notes:
|
|||
|
|
- Only the callback parameter is required. You can omit the priority and name parameters. If you don't have use for them, you probably should.
|
|||
|
|
- If boot has already completed when you call `RegisterInitAction`, your callback runs immediately.
|
|||
|
|
- Use `priority` to control ordering (lower numbers run earlier); `name` helps with log readability.
|
|||
|
|
|
|||
|
|
### Await Boot Asynchronously
|
|||
|
|
```csharp
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using Bootstrap;
|
|||
|
|
|
|||
|
|
public class AsyncBootConsumer
|
|||
|
|
{
|
|||
|
|
public async Task StartAfterBootAsync()
|
|||
|
|
{
|
|||
|
|
await BootCompletionService.WaitForBootCompletionAsync();
|
|||
|
|
// Now safe to query singletons installed by boot prefabs
|
|||
|
|
var inv = CardSystemManager.Instance; // example
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Retrieve Bootstrapped Systems Safely
|
|||
|
|
Boot usually instantiates service prefabs that expose global accessors (singletons or service locators). Always guard access behind boot completion.
|
|||
|
|
```csharp
|
|||
|
|
using Bootstrap;
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
public class UseServicesAfterBoot : MonoBehaviour
|
|||
|
|
{
|
|||
|
|
private void Start()
|
|||
|
|
{
|
|||
|
|
BootCompletionService.RegisterInitAction(() =>
|
|||
|
|
{
|
|||
|
|
// Example: get your services here
|
|||
|
|
var pages = UI.Core.UIPageController.Instance; // created in a boot prefab or scene
|
|||
|
|
var settings = AppleHills.Core.Settings.SettingsProvider.Instance;
|
|||
|
|
Debug.Log("Services acquired and ready.");
|
|||
|
|
}, priority: 100, name: "Acquire Services");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Editor Workflow (Project Settings + Edit-Mode Boot)
|
|||
|
|
|
|||
|
|
- Project Settings UI: `Project/Custom Boot` (`CustomBootSettingsProvider`)
|
|||
|
|
- Assign Addressables references for `RuntimeSettings` and `EditorSettings` in `CustomBootProjectSettings`.
|
|||
|
|
- Backed by assets `CustomBootSettings_Runtime` and `CustomBootSettings_Editor` that list `BootPrefabs` to spawn.
|
|||
|
|
- Asset management helper: `CustomBootSettingsUtil`
|
|||
|
|
- Creates missing assets and Addressables groups/entries automatically:
|
|||
|
|
- Runtime asset path: `Assets/Data/Bootstrap/Runtime/CustomBootSettings_Runtime.asset`
|
|||
|
|
- Editor asset path: `Assets/Data/Bootstrap/Editor/CustomBootSettings_Editor.asset`
|
|||
|
|
- Project settings asset path: `ProjectSettings/CustomBoot.asset`
|
|||
|
|
- Addressables keys: `CustomBootSettings_Runtime`, `CustomBootSettings_Editor`.
|
|||
|
|
|
|||
|
|
### Enable/Disable Edit-Mode Boot
|
|||
|
|
- Menu toggle: `Bootstrap/Editor Initialise` (`CustomBootEditorUtils`).
|
|||
|
|
- When enabled in Edit Mode (not Play), the system instantiates the `BootPrefabs` into the currently open scene so you can preview booted systems.
|
|||
|
|
|
|||
|
|
### What Happens in Edit Mode (Caveats)
|
|||
|
|
- `DontDestroyOnLoad` does not apply in edit mode; booted objects are added to the current scene.
|
|||
|
|
- Entering Play Mode de-initializes any edit-mode boot and hands off to the runtime boot.
|
|||
|
|
- Scene save is guarded: edit-mode boot de-initializes just before save to avoid polluting the scene, then re-initializes after.
|
|||
|
|
- Changing the active scene in Edit Mode triggers de-init/re-init of edit-mode boot.
|
|||
|
|
|
|||
|
|
## Case Studies
|
|||
|
|
|
|||
|
|
### UI Page Controller post-boot wiring
|
|||
|
|
`UIPageController` registers a post-boot hook:
|
|||
|
|
```csharp
|
|||
|
|
// Inside UIPageController.Awake()
|
|||
|
|
Bootstrap.BootCompletionService.RegisterInitAction(InitializePostBoot);
|
|||
|
|
```
|
|||
|
|
This guarantees input/transition wiring happens once systems from `BootPrefabs` are in place.
|
|||
|
|
|
|||
|
|
### Card System post-boot registration
|
|||
|
|
To ensure the card data/UI only touches `CardSystemManager` after boot:
|
|||
|
|
```csharp
|
|||
|
|
using Bootstrap;
|
|||
|
|
using AppleHills.Data.CardSystem;
|
|||
|
|
|
|||
|
|
BootCompletionService.RegisterInitAction(() =>
|
|||
|
|
{
|
|||
|
|
var mgr = CardSystemManager.Instance;
|
|||
|
|
// subscribe to events, warm up caches, etc.
|
|||
|
|
}, priority: 90, name: "CardSystem Init");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Settings access after boot
|
|||
|
|
If your settings provider lives in a boot prefab, wait for boot before first access:
|
|||
|
|
```csharp
|
|||
|
|
using Bootstrap;
|
|||
|
|
using AppleHills.Core.Settings;
|
|||
|
|
|
|||
|
|
BootCompletionService.RegisterInitAction(() =>
|
|||
|
|
{
|
|||
|
|
var gameplay = SettingsProvider.Instance.GetSettings<InteractionSettings>();
|
|||
|
|
Debug.Log($"DefaultPromptRange: {gameplay.DefaultPuzzlePromptRange}");
|
|||
|
|
}, name: "Load Interaction Settings");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Events, APIs, and Keys
|
|||
|
|
|
|||
|
|
- `CustomBoot` (static)
|
|||
|
|
- `bool Initialised`
|
|||
|
|
- `float CurrentProgress`
|
|||
|
|
- `event Action<float> OnBootProgressChanged`
|
|||
|
|
- `event Action OnBootCompleted`
|
|||
|
|
- `BootCompletionService` (static)
|
|||
|
|
- `bool IsBootComplete`
|
|||
|
|
- `event Action OnBootComplete`
|
|||
|
|
- `void RegisterInitAction(Action action, int priority = 100, string name = null)`
|
|||
|
|
- `Task WaitForBootCompletionAsync()`
|
|||
|
|
- Addressables keys
|
|||
|
|
- `CustomBootSettings_Runtime`
|
|||
|
|
- `CustomBootSettings_Editor`
|
|||
|
|
|
|||
|
|
## Troubleshooting / FAQ
|
|||
|
|
- My code runs before services exist:
|
|||
|
|
- Wrap it in `BootCompletionService.RegisterInitAction(...)` or check `IsBootComplete` and subscribe to `OnBootComplete`.
|
|||
|
|
- Boot never seems to complete in Edit Mode:
|
|||
|
|
- Edit-mode boot is a preview; verify the menu toggle `Bootstrap/Editor Initialise` is enabled and that your `CustomBootSettings_Editor.asset` lists the necessary `BootPrefabs`.
|
|||
|
|
- I get duplicate singletons after entering Play Mode:
|
|||
|
|
- Edit-mode boot de-inits on entering Play. If you manually created objects, ensure you destroy them; prefer boot prefabs.
|
|||
|
|
- Addressables key not found:
|
|||
|
|
- Open `Project/Custom Boot` and ensure both runtime and editor settings assets exist and are assigned; let the utility create missing assets.
|
|||
|
|
|
|||
|
|
## Paths & Namespaces
|
|||
|
|
- Runtime scripts: `Assets/Scripts/Bootstrap/`
|
|||
|
|
- `CustomBoot.cs`, `CustomBootSettings.cs`, `BootCompletionService.cs`
|
|||
|
|
- Editor scripts: `Assets/Editor/Bootstrap/`
|
|||
|
|
- `CustomBootSettingsProvider.cs`, `CustomBootEditorUtils.cs`, `CustomBootProjectSettings.cs`, `CustomBootSettingsUtil.cs`
|
|||
|
|
- Addressables keys: `CustomBootSettings_Runtime`, `CustomBootSettings_Editor`
|
|||
|
|
- Namespace: `Bootstrap` (runtime), `Editor.Bootstrap` (editor)
|
|||
|
|
|
|||
|
|
## Change Log
|
|||
|
|
- v1.0: Initial overview document with TOC, code-first playbooks, editor workflow, case studies, and API summary.
|