Rework of base interactables and managed behaviors
This commit is contained in:
114
docs/levelswitch_onsceneready_fix.md
Normal file
114
docs/levelswitch_onsceneready_fix.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 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()`:
|
||||
```csharp
|
||||
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()`:
|
||||
```csharp
|
||||
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`:
|
||||
|
||||
```csharp
|
||||
// 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!
|
||||
|
||||
Reference in New Issue
Block a user