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

243 lines
7.3 KiB
Markdown

# 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()`:
```csharp
// 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
```csharp
[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:
```csharp
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
---
## Related Files
- **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.