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

17 KiB

SceneManagerService Independent Review & Migration Opportunities

Date: November 4, 2025
Reviewer: AI Assistant
Context: Post-BootCompletionService migration, lifecycle system fully operational


Executive Summary

SceneManagerService is currently partially integrated with the lifecycle system but is missing critical lifecycle orchestration as outlined in the roadmap. The service manages scene loading/unloading effectively but doesn't broadcast lifecycle events to ManagedBehaviour components during scene transitions.

Key Finding: ⚠️ Phase 3 of the roadmap (Scene Orchestration) was never implemented


Part 1: SceneManagerService Review

Current State Analysis

Strengths

  1. ManagedBehaviour Migration Complete

    • Inherits from ManagedBehaviour
    • Priority: 15 (core infrastructure)
    • Uses OnManagedAwake() properly
    • Removed BootCompletionService dependency
  2. Solid Event-Driven Architecture

    • 6 public events for scene lifecycle
    • Clean async/await pattern
    • Progress reporting support
    • Loading screen integration
  3. Good Scene Management Features

    • Additive scene loading
    • Multi-scene support
    • Bootstrap scene tracking
    • Current scene tracking

⚠️ Critical Gaps

  1. Missing Lifecycle Orchestration

    • Does NOT call LifecycleManager.BroadcastSceneUnloading()
    • Does NOT call LifecycleManager.BroadcastSaveRequested()
    • Does NOT call LifecycleManager.BroadcastSceneReady()
    • Does NOT call LifecycleManager.BroadcastRestoreRequested()
  2. No Save/Load Integration

    • Scene transitions don't trigger automatic saves
    • No restoration hooks after scene load
    • SaveLoadManager not invoked during transitions
  3. Incomplete Scene Transition Flow

    // CURRENT (Incomplete):
    SwitchSceneAsync() {
        1. Show loading screen
        2. Destroy AstarPath
        3. Unload old scene
        4. Load new scene
        5. Hide loading screen
    }
    
    // MISSING (Per Roadmap Phase 3):
    SwitchSceneAsync() {
        1. Show loading screen
        2. BroadcastSceneUnloading(oldScene)      MISSING
        3. BroadcastSaveRequested(oldScene)       MISSING
        4. SaveLoadManager.Save()                 MISSING
        5. Destroy AstarPath
        6. Unload old scene (Unity OnDestroy)
        7. Load new scene
        8. BroadcastSceneReady(newScene)          MISSING
        9. BroadcastRestoreRequested(newScene)    MISSING
        10. Hide loading screen
    }
    

📊 Code Quality Assessment

Good:

  • Clean separation of concerns
  • Well-documented public API
  • Null safety checks
  • Proper async handling
  • DontDestroyOnLoad handled correctly

Could Improve:

  • Add lifecycle orchestration (critical)
  • Integrate SaveLoadManager
  • Add scene validation before transitions
  • Consider cancellation token support
  • Add scene transition state tracking

Priority 1: Add Lifecycle Orchestration (Critical)

Location: SwitchSceneAsync() method

Changes Needed:

public async Task SwitchSceneAsync(string newSceneName, IProgress<float> progress = null, bool autoHideLoadingScreen = true)
{
    // PHASE 1: Show loading screen
    if (_loadingScreen != null && !_loadingScreen.IsActive)
    {
        _loadingScreen.ShowLoadingScreen();
    }

    string oldSceneName = CurrentGameplayScene;
    
    // PHASE 2: Broadcast scene unloading (NEW)
    LogDebugMessage($"Broadcasting scene unloading for: {oldSceneName}");
    LifecycleManager.Instance?.BroadcastSceneUnloading(oldSceneName);
    
    // PHASE 3: Broadcast save request (NEW)
    LogDebugMessage($"Broadcasting save request for: {oldSceneName}");
    LifecycleManager.Instance?.BroadcastSaveRequested(oldSceneName);
    
    // PHASE 4: Trigger global save (NEW)
    if (SaveLoadManager.Instance != null && 
        DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().useSaveLoadSystem)
    {
        LogDebugMessage("Saving global game state");
        SaveLoadManager.Instance.Save();
    }
    
    // PHASE 5: Cleanup (existing)
    var astarPaths = FindObjectsByType<AstarPath>(FindObjectsSortMode.None);
    foreach (var astar in astarPaths)
    {
        if (Application.isPlaying)
            Destroy(astar.gameObject);
        else
            DestroyImmediate(astar.gameObject);
    }
    
    // PHASE 6: Unload old scene (existing)
    if (!string.IsNullOrEmpty(oldSceneName) && oldSceneName != BootstrapSceneName)
    {
        var prevScene = SceneManager.GetSceneByName(oldSceneName);
        if (prevScene.isLoaded)
        {
            await UnloadSceneAsync(oldSceneName);
            // Unity automatically calls OnDestroy → OnManagedDestroy()
        }
    }
    
    // PHASE 7: Ensure bootstrap loaded (existing)
    var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
    if (!bootstrap.isLoaded)
    {
        SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive);
    }
    
    // PHASE 8: Load new scene (existing)
    await LoadSceneAsync(newSceneName, progress);
    CurrentGameplayScene = newSceneName;
    
    // PHASE 9: Broadcast scene ready (NEW)
    LogDebugMessage($"Broadcasting scene ready for: {newSceneName}");
    LifecycleManager.Instance?.BroadcastSceneReady(newSceneName);
    
    // PHASE 10: Broadcast restore request (NEW)
    LogDebugMessage($"Broadcasting restore request for: {newSceneName}");
    LifecycleManager.Instance?.BroadcastRestoreRequested(newSceneName);
    
    // PHASE 11: Hide loading screen (existing)
    if (autoHideLoadingScreen && _loadingScreen != null)
    {
        _loadingScreen.HideLoadingScreen();
    }
}

Impact: 🔴 HIGH - Critical for proper lifecycle integration


Part 2: Components Depending on Scene Loading Events

Analysis Results

I found 6 components that currently subscribe to SceneManagerService events. Here's the breakdown:


Component 1: PauseMenu Already Using Lifecycle

Current Implementation:

protected override void OnSceneReady()
{
    SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel;
    // ...
}

Status: ALREADY OPTIMAL

  • Uses OnSceneReady() hook
  • Subscribes to SceneLoadCompleted for per-scene visibility logic
  • No migration needed - dual approach is appropriate here

Rationale: PauseMenu needs to know about EVERY scene load (not just transitions), so event subscription is correct.


Component 2: QuickAccess ⚠️ Should Migrate to Lifecycle

File: Assets/Scripts/Core/QuickAccess.cs

Current Implementation:

public class QuickAccess : MonoBehaviour  // ← Not ManagedBehaviour!
{
    private void Awake()
    {
        _instance = this;
        
        if (!_initialized)
        {
            if (SceneManager != null)
            {
                SceneManager.SceneLoadCompleted += OnSceneLoadCompleted;
            }
            _initialized = true;
        }
    }

    private void OnSceneLoadCompleted(string sceneName)
    {
        ClearReferences();  // Invalidates cached GameObjects
    }
}

Issues:

  • Not using ManagedBehaviour
  • Manual event subscription in Awake
  • No automatic cleanup
  • Uses event for scene transitions

Recommended Migration:

public class QuickAccess : ManagedBehaviour
{
    public override int ManagedAwakePriority => 5; // Very early
    
    protected override void OnManagedAwake()
    {
        _instance = this;
        _initialized = true;
    }
    
    protected override void OnSceneUnloading(string sceneName)
    {
        // Clear references BEFORE scene unloads
        ClearReferences();
    }
    
    // REMOVE: Awake() method
    // REMOVE: OnSceneLoadCompleted() method
    // REMOVE: SceneLoadCompleted subscription
}

Benefits:

  • Automatic cleanup (no manual unsubscribe)
  • Clear references at proper time (before unload, not after load)
  • Consistent with lifecycle pattern
  • One less event subscription

Impact: 🟡 MEDIUM - Improves cleanup timing and consistency


Component 3: CardSystemSceneVisibility Already Migrated

File: Assets/Scripts/UI/CardSystem/CardSystemSceneVisibility.cs

Current Implementation:

protected override void OnSceneReady()
{
    // Replaces SceneLoadCompleted subscription
    SetVisibilityForScene(sceneName);
}

Status: ALREADY MIGRATED

  • Uses OnSceneReady() lifecycle hook
  • No event subscription
  • No action needed

Component 4: PuzzleManager Already Migrated

File: Assets/Scripts/PuzzleS/PuzzleManager.cs

Current Implementation:

protected override void OnSceneReady()
{
    // Replaces SceneLoadCompleted subscription
    string sceneName = SceneManagerService.Instance.CurrentGameplayScene;
    OnSceneLoadCompleted(sceneName);
}

public void OnSceneLoadCompleted(string sceneName)
{
    // Load puzzle data for scene
    // ...
}

Status: ALREADY MIGRATED

  • Uses OnSceneReady() lifecycle hook
  • Method renamed but could be cleaned up
  • Minor cleanup recommended (rename OnSceneLoadCompleted → LoadPuzzlesForScene)

Component 5: InputManager Already Migrated

File: Assets/Scripts/Input/InputManager.cs

Current Implementation:

protected override void OnSceneReady()
{
    // Replaces SceneLoadCompleted subscription
    UpdateInputModeForScene();
}

Status: ALREADY MIGRATED

  • Uses OnSceneReady() lifecycle hook
  • No action needed

Component 6: ItemManager Already Migrated

File: Assets/Scripts/Core/ItemManager.cs

Current Implementation:

protected override void OnSceneUnloading(string sceneName)
{
    // Replaces SceneLoadStarted subscription for clearing registrations
    ClearItemRegistrations();
}

Status: ALREADY MIGRATED

  • Uses OnSceneUnloading() lifecycle hook
  • No action needed

Component 7: SaveLoadManager ⚠️ Has Orphaned Methods

File: Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs

Current Implementation:

protected override void OnSceneReady()
{
    // Replaces SceneLoadCompleted event subscription
    string sceneName = SceneManagerService.Instance.CurrentGameplayScene;
    OnSceneLoadCompleted(sceneName);
}

private void OnSceneLoadCompleted(string sceneName)
{
    // Restore global save data
    // ...
}

private void OnSceneUnloadStarted(string sceneName)
{
    // Save global data before unload
    // ...
}

Issues:

  • ⚠️ Has OnSceneUnloadStarted() method but doesn't override OnSceneUnloading()
  • ⚠️ Should use OnSaveRequested() instead of custom method
  • ⚠️ Method naming inconsistent with lifecycle pattern

Recommended Changes:

protected override void OnSceneReady()
{
    string sceneName = SceneManagerService.Instance.CurrentGameplayScene;
    RestoreGlobalData(sceneName); // Renamed for clarity
}

protected override void OnSaveRequested(string sceneName)
{
    // Replaces OnSceneUnloadStarted
    SaveGlobalData(sceneName);
}

// REMOVE: OnSceneLoadCompleted() - rename to RestoreGlobalData()
// REMOVE: OnSceneUnloadStarted() - replace with OnSaveRequested()

Impact: 🟡 MEDIUM - Cleanup and consistency


Part 3: Migration Summary & Recommendations

Components Needing Migration

Component Current State Action Required Priority Estimated Time
SceneManagerService Missing lifecycle broadcasts Add lifecycle orchestration 🔴 CRITICAL 30 min
QuickAccess MonoBehaviour, uses events Migrate to ManagedBehaviour 🟡 MEDIUM 20 min
SaveLoadManager Has orphaned methods Cleanup and use lifecycle hooks 🟡 MEDIUM 15 min
PuzzleManager Minor naming issue Rename method 🟢 LOW 5 min

Total Estimated Time: ~70 minutes


Migration Benefits

After Full Migration:

  1. Proper Scene Lifecycle Flow

    Scene Transition Triggers:
    1. OnSceneUnloading() - Components clean up
    2. OnSaveRequested() - Save level-specific data
    3. SaveLoadManager.Save() - Save global data
    4. [Scene Unload] - Unity OnDestroy
    5. [Scene Load] - Unity Awake
    6. OnSceneReady() - Components initialize
    7. OnRestoreRequested() - Restore level-specific data
    
  2. Fewer Event Subscriptions

    • Current: 7 event subscriptions across 6 components
    • After: 1 event subscription (PauseMenu only)
    • Reduction: ~86%
  3. Consistent Pattern

    • All components use lifecycle hooks
    • Clear separation: boot vs scene lifecycle
    • Predictable execution order
  4. Automatic Cleanup

    • No manual event unsubscription
    • ManagedBehaviour handles cleanup
    • Fewer memory leak opportunities

Part 4: Detailed Implementation Plan

Step 1: Update SceneManagerService (30 min) 🔴 CRITICAL

Priority: Must do this first - enables all other migrations

Changes:

  1. Add lifecycle broadcasts to SwitchSceneAsync()
  2. Integrate SaveLoadManager.Save() call
  3. Add debug logging for lifecycle events
  4. Test scene transitions thoroughly

Testing:

  • Verify lifecycle order in console logs
  • Check save/load triggers correctly
  • Ensure OnSceneReady fires after scene fully loaded

Step 2: Migrate QuickAccess (20 min) 🟡 MEDIUM

Changes:

  1. Change base class to ManagedBehaviour
  2. Set priority to 5 (very early)
  3. Override OnManagedAwake()
  4. Override OnSceneUnloading() for cleanup
  5. Remove Awake() and event subscription
  6. Test reference caching works

Testing:

  • Verify references cached correctly
  • Check clearing happens before scene unload
  • Ensure singleton works across scenes

Step 3: Cleanup SaveLoadManager (15 min) 🟡 MEDIUM

Changes:

  1. Rename OnSceneLoadCompleted()RestoreGlobalData()
  2. Replace OnSceneUnloadStarted() with OnSaveRequested() override
  3. Update method documentation
  4. Verify save/load flow

Testing:

  • Test global data saves before scene unload
  • Verify restoration after scene load
  • Check ISaveParticipant integration

Step 4: Minor Cleanups (5 min) 🟢 LOW

PuzzleManager:

  • Rename OnSceneLoadCompleted()LoadPuzzlesForScene()
  • Update documentation

Part 5: Risk Assessment

Low Risk

  • PauseMenu, CardSystemSceneVisibility, InputManager, ItemManager already migrated
  • Lifecycle system battle-tested from previous migrations

Medium Risk ⚠️

  • SceneManagerService changes - Critical path, test thoroughly
  • QuickAccess migration - Used throughout codebase, verify references work
  • SaveLoadManager cleanup - Save/load is critical, extensive testing needed

Mitigation Strategies

  1. Test after each migration - Don't batch changes
  2. Use git checkpoints - Commit after each component
  3. Playtest thoroughly - Full game loop after SceneManagerService update
  4. Keep backups - Copy methods before deleting

Part 6: Next Steps Recommendation

Immediate Actions (This Session)

Option A: Full Migration (Recommended)

  1. Update SceneManagerService with lifecycle orchestration (30 min)
  2. Migrate QuickAccess to ManagedBehaviour (20 min)
  3. Cleanup SaveLoadManager (15 min)
  4. Test thoroughly (30 min) Total: ~95 minutes

Option B: Critical Path Only

  1. Update SceneManagerService only (30 min)
  2. Test scene transitions (20 min)
  3. ⏸️ Defer other migrations Total: ~50 minutes

Option C: Review Only

  • This document completed
  • ⏸️ Wait for user approval before proceeding

Part 7: Expected Outcomes

After Complete Migration:

Architecture:

  • Scene transitions fully orchestrated by LifecycleManager
  • Automatic save/load during scene transitions
  • All components use lifecycle hooks consistently
  • Event subscriptions minimized (7 → 1)

Developer Experience:

  • Clear lifecycle flow for debugging
  • Predictable initialization order
  • Less manual cleanup code
  • Consistent patterns across codebase

Performance:

  • Fewer active event subscriptions
  • Better memory management
  • No change in frame time (lifecycle is event-driven)

Conclusion

Key Finding: SceneManagerService is missing the critical lifecycle orchestration outlined in Phase 3 of the roadmap. This is the final piece needed to complete the lifecycle system implementation.

Recommendation: Implement SceneManagerService lifecycle orchestration (Step 1) immediately. This unlocks the full potential of the lifecycle system and completes the architectural vision from the roadmap.

Next Action: Await user approval to proceed with implementation.


Review Completed: November 4, 2025
Status: Ready for implementation
Estimated Total Time: 70-95 minutes