Files
AppleHillsProduction/docs/levelswitch_onsceneready_fix.md
2025-11-07 13:53:11 +01:00

4.1 KiB

OnSceneReady Not Called for LevelSwitch - Root Cause & Fix

Problem Identified from Logs

[LevelSwitch] Awake called for CementFactory in scene AppleHillsOverworld
[LevelSwitch] OnManagedAwake called for CementFactory

Awake() called
OnManagedAwake() called
OnSceneReady() NEVER called

Root Cause Analysis

The Timing Issue

Looking at the stack trace, OnManagedAwake() is being called during registration at LifecycleManager line 125, which is the "late registration" code path (boot already complete).

The sequence that breaks OnSceneReady:

  1. Phase 8 in SceneManagerService.SwitchSceneAsync():

    await LoadSceneAsync(newSceneName, progress);
    
    • Unity loads the scene
    • LevelSwitch.Awake() runs
    • LevelSwitch calls base.Awake()
    • ManagedBehaviour.Awake() calls LifecycleManager.Register()
    • LifecycleManager checks: if (currentSceneReady == sceneName)FALSE (not set yet!)
    • OnSceneReady() NOT called
  2. Phase 9 in SceneManagerService.SwitchSceneAsync():

    LifecycleManager.Instance?.BroadcastSceneReady(newSceneName);
    
    • Sets currentSceneReady = sceneName
    • Broadcasts to all components in sceneReadyList
    • But LevelSwitch was already checked in step 1, so it's skipped!

The Gap

Between when LoadSceneAsync() completes (scene loaded, Awake() called) and when BroadcastSceneReady() is called, there's a timing gap where:

  • Components register with LifecycleManager
  • currentSceneReady is NOT yet set to the new scene name
  • Late registration check fails: currentSceneReady == sceneName → false
  • Components miss their OnSceneReady() call

The Fix

Modified LifecycleManager.Register() to check if a scene is actually loaded via Unity's SceneManager, not just relying on currentSceneReady:

// If this scene is already ready, call OnSceneReady immediately
// Check both currentSceneReady AND if the Unity scene is actually loaded
// (during scene loading, components Awake before BroadcastSceneReady is called)
bool sceneIsReady = currentSceneReady == sceneName;

// Also check if this is happening during boot and the scene is the active scene
// This handles components that register during initial scene load
if (!sceneIsReady && isBootComplete && sceneName != "DontDestroyOnLoad")
{
    var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
    sceneIsReady = scene.isLoaded;
}

if (sceneIsReady)
{
    LogDebug($"Late registration: Calling OnSceneReady immediately for {component.gameObject.name}");
    component.InvokeSceneReady();
}

Why This Works

  1. Components register during scene load (after Awake())
  2. currentSceneReady might not be set yet
  3. BUT scene.isLoaded returns true because Unity has already loaded the scene
  4. OnSceneReady() gets called immediately during registration
  5. Components get their lifecycle hook even though they register between load and broadcast

Expected Behavior After Fix

When playing the game, you should now see:

[LevelSwitch] Awake called for CementFactory in scene AppleHillsOverworld
[LifecycleManager] Late registration: Calling OnManagedAwake immediately for CementFactory
[LevelSwitch] OnManagedAwake called for CementFactory
[LifecycleManager] Late registration: Calling OnSceneReady immediately for CementFactory
[LevelSwitch] OnSceneReady called for CementFactory  ← THIS IS NEW!

Files Modified

  1. LifecycleManager.cs - Enhanced late registration check to verify Unity scene load status

Design Insight

This reveals an important timing consideration:

During scene loading:

  • Unity loads the scene
  • All Awake() methods run (including base.Awake() for ManagedBehaviours)
  • Components register with LifecycleManager
  • SceneManager.LoadSceneAsync completes
  • THEN SceneManagerService calls BroadcastSceneReady()

There's an inherent gap between Unity's scene load completion and our lifecycle broadcast. The fix handles this by checking Unity's actual scene state, not just our tracking variable.


Status: FIXED - OnSceneReady will now be called for all in-scene ManagedBehaviours, even during late registration!