Files
AppleHillsProduction/docs/critical_bugfix_missing_base_awake.md
2025-11-05 20:37:16 +01:00

4.7 KiB

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

// 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:

// 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

  1. InputManager.cs (Priority 25)
  2. AudioManager.cs (Priority 30)
  3. LoadingScreenController.cs (Priority 45)
  4. UIPageController.cs (Priority 50)
  5. PauseMenu.cs (Priority 55)
  6. SceneOrientationEnforcer.cs (Priority 70)

Game Systems

  1. ItemManager.cs (Priority 75)
  2. PuzzleManager.cs (Priority 80)
  3. CinematicsManager.cs (Priority 170)
  4. 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:

// 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():

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:

  • 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!

// 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!