167 lines
4.7 KiB
Markdown
167 lines
4.7 KiB
Markdown
|
|
# Critical Bug Fix: Missing base.Awake() Calls
|
||
|
|
|
||
|
|
## The Bug
|
||
|
|
|
||
|
|
When we added custom `Awake()` methods to singleton managers using the `new` keyword to hide the base class method, **we forgot to call `base.Awake()`**, which prevented ManagedBehaviour from registering components with LifecycleManager.
|
||
|
|
|
||
|
|
### Root Cause
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// BROKEN - Missing base.Awake() call
|
||
|
|
private new void Awake()
|
||
|
|
{
|
||
|
|
_instance = this;
|
||
|
|
// ... initialization
|
||
|
|
}
|
||
|
|
// ❌ ManagedBehaviour.Awake() NEVER CALLED!
|
||
|
|
// ❌ LifecycleManager.Register() NEVER CALLED!
|
||
|
|
// ❌ OnManagedAwake() NEVER INVOKED!
|
||
|
|
```
|
||
|
|
|
||
|
|
**What should have happened:**
|
||
|
|
1. `ManagedBehaviour.Awake()` registers component with LifecycleManager
|
||
|
|
2. LifecycleManager broadcasts `OnManagedAwake()` after boot completion
|
||
|
|
3. Component receives lifecycle callbacks
|
||
|
|
|
||
|
|
**What actually happened:**
|
||
|
|
1. Custom `Awake()` hides base implementation
|
||
|
|
2. Component never registers with LifecycleManager
|
||
|
|
3. `OnManagedAwake()` never called ❌
|
||
|
|
|
||
|
|
## The Fix
|
||
|
|
|
||
|
|
Added `base.Awake()` as the **FIRST line** in every custom Awake() method:
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// FIXED - Calls base.Awake() to register
|
||
|
|
private new void Awake()
|
||
|
|
{
|
||
|
|
base.Awake(); // CRITICAL: Register with LifecycleManager!
|
||
|
|
|
||
|
|
_instance = this;
|
||
|
|
// ... initialization
|
||
|
|
}
|
||
|
|
// ✅ ManagedBehaviour.Awake() called!
|
||
|
|
// ✅ LifecycleManager.Register() called!
|
||
|
|
// ✅ OnManagedAwake() will be invoked!
|
||
|
|
```
|
||
|
|
|
||
|
|
## Files Fixed (14 Total)
|
||
|
|
|
||
|
|
### Core Systems
|
||
|
|
1. ✅ **GameManager.cs** (Priority 10)
|
||
|
|
2. ✅ **SceneManagerService.cs** (Priority 15)
|
||
|
|
3. ✅ **SaveLoadManager.cs** (Priority 20)
|
||
|
|
4. ✅ **QuickAccess.cs** (Priority 5)
|
||
|
|
|
||
|
|
### Infrastructure
|
||
|
|
5. ✅ **InputManager.cs** (Priority 25)
|
||
|
|
6. ✅ **AudioManager.cs** (Priority 30)
|
||
|
|
7. ✅ **LoadingScreenController.cs** (Priority 45)
|
||
|
|
8. ✅ **UIPageController.cs** (Priority 50)
|
||
|
|
9. ✅ **PauseMenu.cs** (Priority 55)
|
||
|
|
10. ✅ **SceneOrientationEnforcer.cs** (Priority 70)
|
||
|
|
|
||
|
|
### Game Systems
|
||
|
|
11. ✅ **ItemManager.cs** (Priority 75)
|
||
|
|
12. ✅ **PuzzleManager.cs** (Priority 80)
|
||
|
|
13. ✅ **CinematicsManager.cs** (Priority 170)
|
||
|
|
14. ✅ **CardSystemManager.cs** (Priority 60)
|
||
|
|
|
||
|
|
## Impact
|
||
|
|
|
||
|
|
### Before Fix ❌
|
||
|
|
- Singleton instances were set (`_instance = this`) ✅
|
||
|
|
- Settings were initialized ✅
|
||
|
|
- **BUT**: Components never registered with LifecycleManager ❌
|
||
|
|
- **Result**: `OnManagedAwake()` never called ❌
|
||
|
|
- **Result**: No lifecycle hooks (OnSceneReady, OnSceneUnloading, etc.) ❌
|
||
|
|
- **Result**: Auto-registration features (IPausable, etc.) broken ❌
|
||
|
|
|
||
|
|
### After Fix ✅
|
||
|
|
- Singleton instances set ✅
|
||
|
|
- Settings initialized ✅
|
||
|
|
- Components registered with LifecycleManager ✅
|
||
|
|
- `OnManagedAwake()` called in priority order ✅
|
||
|
|
- All lifecycle hooks working ✅
|
||
|
|
- Auto-registration features working ✅
|
||
|
|
|
||
|
|
## Why This Happened
|
||
|
|
|
||
|
|
When we moved singleton instance assignment from `OnManagedAwake()` to `Awake()`, we used the `new` keyword to hide the base class Awake method. However, **hiding is not the same as overriding**:
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// Hiding (new) - base method NOT called automatically
|
||
|
|
private new void Awake() { }
|
||
|
|
|
||
|
|
// Overriding (override) - base method NOT called automatically
|
||
|
|
protected override void Awake() { }
|
||
|
|
|
||
|
|
// Both require EXPLICIT base.Awake() call!
|
||
|
|
```
|
||
|
|
|
||
|
|
We correctly used `new` (since ManagedBehaviour.Awake() is not virtual), but forgot to explicitly call `base.Awake()`.
|
||
|
|
|
||
|
|
## The Correct Pattern
|
||
|
|
|
||
|
|
For any ManagedBehaviour with a custom Awake():
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
public class MyManager : ManagedBehaviour
|
||
|
|
{
|
||
|
|
private static MyManager _instance;
|
||
|
|
public static MyManager Instance => _instance;
|
||
|
|
|
||
|
|
public override int ManagedAwakePriority => 50;
|
||
|
|
|
||
|
|
private new void Awake()
|
||
|
|
{
|
||
|
|
base.Awake(); // ✅ ALWAYS CALL THIS FIRST!
|
||
|
|
|
||
|
|
_instance = this;
|
||
|
|
// ... other early initialization
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void OnManagedAwake()
|
||
|
|
{
|
||
|
|
// Lifecycle hooks work now!
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing Checklist
|
||
|
|
|
||
|
|
To verify the fix works:
|
||
|
|
|
||
|
|
- [x] All files compile without errors
|
||
|
|
- [ ] Run from StartingScene - verify boot sequence works
|
||
|
|
- [ ] Check console for `[LifecycleManager] Registered [ComponentName]` messages
|
||
|
|
- [ ] Verify OnManagedAwake() logs appear (e.g., "XAXA" from LevelSwitch)
|
||
|
|
- [ ] Test scene switching - verify lifecycle hooks fire
|
||
|
|
- [ ] Test pause system - verify IPausable auto-registration works
|
||
|
|
- [ ] Test save/load - verify ISaveParticipant integration works
|
||
|
|
|
||
|
|
## Key Lesson
|
||
|
|
|
||
|
|
**When hiding a base class method with `new`, you MUST explicitly call the base implementation if you need its functionality!**
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// WRONG ❌
|
||
|
|
private new void Awake()
|
||
|
|
{
|
||
|
|
// Missing base.Awake()
|
||
|
|
}
|
||
|
|
|
||
|
|
// CORRECT ✅
|
||
|
|
private new void Awake()
|
||
|
|
{
|
||
|
|
base.Awake(); // Explicitly call base!
|
||
|
|
// ... custom logic
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Status**: ✅ FIXED - All 14 managers now properly call base.Awake() to ensure LifecycleManager registration!
|
||
|
|
|