Files
AppleHillsProduction/docs/bootstrap_readme.md
2025-10-21 14:20:09 +02:00

11 KiB
Raw Blame History

Apple Hills Bootstrap System

A concise, code-first overview of the projects 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

  • 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

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 oneoffs)
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()
);
  1. Full function callback (method group)
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

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.

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:

// 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:

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:

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.