Files
AppleHillsProduction/docs/editor_lifecycle_bootstrap.md
2025-11-07 13:54:19 +01:00

7.3 KiB

Editor Lifecycle Bootstrap - Quality of Life Improvement

Date: November 5, 2025
Feature: Editor-Only Lifecycle Orchestration
Status: Implemented


Problem Statement

When playing a scene directly from the Unity Editor (pressing Play without going through the bootstrap scene), the managed lifecycle system wasn't being fully triggered:

What Was Happening

  1. CustomBoot.Initialise() runs ([RuntimeInitializeOnLoadMethod])
  2. LifecycleManager gets created
  3. Components' Awake() runs, they register with LifecycleManager
  4. OnBootCompletionTriggered() broadcasts OnManagedAwake() to all components
  5. BroadcastSceneReady() NEVER CALLED for the initial scene
  6. Components never receive their OnSceneReady() callback

Why This Happened

The OnSceneReady lifecycle event is normally triggered by SceneManagerService.SwitchSceneAsync():

// PHASE 8: Begin scene loading mode
LifecycleManager.Instance?.BeginSceneLoad(newSceneName);

// PHASE 9: Load new gameplay scene
await LoadSceneAsync(newSceneName, progress);

// PHASE 10: Broadcast scene ready
LifecycleManager.Instance?.BroadcastSceneReady(newSceneName);

But when you press Play directly in a scene:

  • The scene is already loaded by Unity
  • SceneManagerService doesn't orchestrate this initial load
  • BroadcastSceneReady is never called

This meant components couldn't properly initialize scene-specific logic in OnSceneReady().


Solution: EditorLifecycleBootstrap

Created an editor-only script that detects when playing directly from a scene and ensures the lifecycle is properly orchestrated.

Implementation Details

File: Assets/Editor/Lifecycle/EditorLifecycleBootstrap.cs

Key Features:

  1. Automatic Detection: Uses [InitializeOnLoad] to run in editor
  2. Play Mode Hook: Subscribes to EditorApplication.playModeStateChanged
  3. Boot Completion Wait: Polls until CustomBoot.Initialised is true
  4. Scene Ready Trigger: Broadcasts OnSceneReady for the active scene
  5. One-Time Execution: Only triggers once per play session
  6. Bootstrap Scene Skip: Ignores the Bootstrap scene (doesn't need OnSceneReady)

How It Works

User Presses Play in Scene "AppleHillsOverworld"
    ↓
PlayModeStateChange.EnteredPlayMode
    ↓
EditorLifecycleBootstrap starts polling
    ↓
Wait for CustomBoot.Initialised == true
    ↓
Get active scene (AppleHillsOverworld)
    ↓
Call LifecycleManager.Instance.BroadcastSceneReady("AppleHillsOverworld")
    ↓
All components in scene receive OnSceneReady() callback ✅

Code Flow

[InitializeOnLoad]
public static class EditorLifecycleBootstrap
{
    static EditorLifecycleBootstrap()
    {
        EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
    }

    private static void OnPlayModeStateChanged(PlayModeStateChange state)
    {
        if (state == PlayModeStateChange.EnteredPlayMode)
        {
            // Start polling for boot completion
            EditorApplication.update += WaitForBootAndTriggerSceneReady;
        }
    }

    private static void WaitForBootAndTriggerSceneReady()
    {
        // Wait for boot to complete
        if (!Bootstrap.CustomBoot.Initialised)
            return;

        // Get active scene
        Scene activeScene = SceneManager.GetActiveScene();
        
        // Trigger OnSceneReady for the initial scene
        LifecycleManager.Instance.BroadcastSceneReady(activeScene.name);
    }
}

Benefits

For Developers

  1. Consistent Lifecycle: Same lifecycle behavior whether you play from Bootstrap or directly from a scene
  2. No Manual Setup: Automatic - no need to remember to call anything
  3. Editor-Only: Zero overhead in builds
  4. Debugging Made Easy: Can test any scene directly without worrying about lifecycle issues

For Components

Components can now reliably use OnSceneReady() for scene-specific initialization:

public class LevelSwitch : ManagedBehaviour
{
    protected override void OnSceneReady()
    {
        Debug.Log($"Scene ready: {gameObject.scene.name}");
        // This now works when playing directly from editor! ✅
    }
}

Usage

No action required! The system works automatically:

  1. Open any gameplay scene in Unity
  2. Press Play
  3. Components receive their full lifecycle:
    • OnManagedAwake()
    • OnSceneReady() (now works!)

Expected Logs

When playing "AppleHillsOverworld" scene directly:

[CustomBoot] Boot initialized
[LifecycleManager] Instance created
[LifecycleManager] Broadcasting ManagedAwake to 15 components
[LifecycleManager] === Boot Completion Triggered ===
[EditorLifecycleBootstrap] Triggering OnSceneReady for initial scene: AppleHillsOverworld
[LifecycleManager] Broadcasting SceneReady for scene: AppleHillsOverworld
[LevelSwitch] OnSceneReady called for CementFactory

Technical Considerations

Why Not Use RuntimeInitializeOnLoadMethod?

[RuntimeInitializeOnLoadMethod] runs too early - before CustomBoot completes. We need to wait for the full bootstrap to finish before triggering OnSceneReady.

Why EditorApplication.update?

Unity's EditorApplication.update provides a simple polling mechanism to wait for boot completion. It's a lightweight solution for editor-only code.

Why Check for Bootstrap Scene?

The Bootstrap scene is a special infrastructure scene that doesn't contain gameplay components. It doesn't need OnSceneReady, and components there shouldn't expect it.

Thread Safety

All Unity API calls and lifecycle broadcasts happen on the main thread via EditorApplication.update, ensuring thread safety.


Compatibility

  • Works with existing lifecycle system
  • No changes to runtime code
  • No impact on builds (editor-only)
  • Compatible with scene manager service
  • Safe for DontDestroyOnLoad objects

Testing

Test Case 1: Direct Play from Gameplay Scene

  1. Open AppleHillsOverworld scene
  2. Press Play
  3. Verify components log both OnManagedAwake and OnSceneReady

Test Case 2: Play from Bootstrap

  1. Open Bootstrap scene
  2. Press Play
  3. Verify normal boot flow works (should NOT trigger editor bootstrap)

Test Case 3: Scene Transitions

  1. Play from any scene
  2. Use LevelSwitch to change scenes
  3. Verify SceneManagerService still orchestrates transitions normally

Future Enhancements

Potential improvements if needed:

  1. Configuration: Add developer settings to enable/disable editor lifecycle
  2. Multi-Scene Support: Handle multiple loaded scenes in editor
  3. Delayed Trigger: Option to delay OnSceneReady by frames for complex setups
  4. Debug Visualization: Editor window showing lifecycle state

  • Implementation: Assets/Editor/Lifecycle/EditorLifecycleBootstrap.cs
  • Core System: Assets/Scripts/Core/Lifecycle/LifecycleManager.cs
  • Bootstrap: Assets/Scripts/Bootstrap/CustomBoot.cs
  • Scene Management: Assets/Scripts/Core/SceneManagerService.cs

Summary

The EditorLifecycleBootstrap provides a seamless quality-of-life improvement for developers working with the managed lifecycle system. It ensures that playing scenes directly from the Unity Editor provides the same consistent lifecycle orchestration as the production scene flow, making development and debugging significantly easier.