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

10 KiB

Scene Management Event Orchestration - Trimming Analysis

Date: November 4, 2025
Context: Post-lifecycle migration - all components now use lifecycle hooks


Executive Summary

After migrating components to use ManagedBehaviour lifecycle hooks, SceneManagerService events can be significantly trimmed. Most components no longer need these events since they use lifecycle hooks instead.


Current Event Usage Analysis

SceneManagerService Events (6 total)

Event Current Subscribers Can Be Removed? Notes
SceneLoadStarted 1 (LoadingScreen) ⚠️ KEEP Loading screen needs this
SceneLoadProgress 0 REMOVE No subscribers
SceneLoadCompleted 2 (LoadingScreen, PauseMenu) ⚠️ KEEP Still needed
SceneUnloadStarted 0 REMOVE No subscribers
SceneUnloadProgress 0 REMOVE No subscribers
SceneUnloadCompleted 0 REMOVE No subscribers

Detailed Event Analysis

1. SceneLoadStarted ⚠️ KEEP

Current Usage:

// SceneManagerService.cs line 102
SceneLoadStarted += _ => _loadingScreen.ShowLoadingScreen(() => GetAggregateLoadProgress());

Why Keep:

  • LoadingScreenController needs to know when loading begins
  • Shows loading screen with progress callback
  • No lifecycle equivalent (this is orchestration, not component lifecycle)

Action: KEEP


2. SceneLoadProgress REMOVE

Current Usage: None - no subscribers found

Why Remove:

  • Not used anywhere in codebase
  • Progress is reported via IProgress parameter in async methods
  • Loading screen gets progress via callback, not event

Action: REMOVE - Dead code


3. SceneLoadCompleted ⚠️ KEEP (but maybe simplify)

Current Usage:

// SceneManagerService.cs line 103
SceneLoadCompleted += _ => _loadingScreen.HideLoadingScreen();

// PauseMenu.cs line 45
SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel;

Analysis:

  • LoadingScreen subscriber: Internal orchestration - keeps loading screen hidden until scene ready
  • PauseMenu subscriber: Legitimate use case - needs to react to EVERY scene load to set visibility

Why PauseMenu Can't Use Lifecycle:

  • OnSceneReady() fires only for the scene PauseMenu is IN
  • PauseMenu is in BootstrapScene (persistent)
  • Needs to know about OTHER scenes loading to update visibility
  • Event subscription is correct pattern here

Action: ⚠️ KEEP - Still has legitimate uses


4. SceneUnloadStarted REMOVE

Current Usage: None - no subscribers found

Why Remove:

  • Components now use OnSceneUnloading() lifecycle hook
  • LifecycleManager.BroadcastSceneUnloading() replaces this
  • Event is fired but nobody listens

Action: REMOVE - Dead code


5. SceneUnloadProgress REMOVE

Current Usage: None - no subscribers found

Why Remove:

  • Not used anywhere
  • Progress reported via IProgress parameter

Action: REMOVE - Dead code


6. SceneUnloadCompleted REMOVE

Current Usage: None - no subscribers found

Why Remove:

  • Not used anywhere
  • Components use OnSceneUnloading() before unload
  • No need to know AFTER unload completes

Action: REMOVE - Dead code


Trimming Recommendations

Remove these events entirely:

  • SceneLoadProgress
  • SceneUnloadStarted
  • SceneUnloadProgress
  • SceneUnloadCompleted

Keep these events:

  • SceneLoadStarted (loading screen orchestration)
  • SceneLoadCompleted (loading screen + PauseMenu cross-scene awareness)

Result:

  • 4 events removed (67% reduction)
  • 2 events kept (essential for orchestration)
  • Clean separation: Lifecycle hooks for components, events for orchestration

Option B: Conservative Approach

Remove only provably unused:

  • SceneLoadProgress
  • SceneUnloadProgress

Mark as deprecated but keep:

  • ⚠️ SceneUnloadStarted (deprecated - use OnSceneUnloading)
  • ⚠️ SceneUnloadCompleted (deprecated - use lifecycle)

Result:

  • 2 events removed (33% reduction)
  • 4 events kept (backward compatibility)
  • Gradual migration path

Remove ALL events, replace with callbacks:

// Instead of events, use callbacks in SwitchSceneAsync
public async Task SwitchSceneAsync(
    string newSceneName, 
    IProgress<float> progress = null,
    Action onLoadStarted = null,
    Action onLoadCompleted = null
)

Why NOT Recommended:

  • Breaks existing loading screen integration
  • Removes flexibility
  • Makes simple orchestration harder

Step 1: Remove Dead Events

File: SceneManagerService.cs

Remove these declarations:

// REMOVE:
public event Action<string, float> SceneLoadProgress;
public event Action<string> SceneUnloadStarted;
public event Action<string, float> SceneUnloadProgress;
public event Action<string> SceneUnloadCompleted;

Remove these invocations:

// REMOVE from LoadSceneAsync:
SceneLoadProgress?.Invoke(sceneName, op.progress);

// REMOVE from UnloadSceneAsync:
SceneUnloadStarted?.Invoke(sceneName);
SceneUnloadProgress?.Invoke(sceneName, op.progress);
SceneUnloadCompleted?.Invoke(sceneName);

Step 2: Simplify Remaining Events

Keep these (minimal set):

/// <summary>
/// Fired when a scene starts loading.
/// Used by loading screen orchestration.
/// </summary>
public event Action<string> SceneLoadStarted;

/// <summary>
/// Fired when a scene finishes loading.
/// Used by loading screen orchestration and cross-scene components (e.g., PauseMenu).
/// For component initialization, use OnSceneReady() lifecycle hook instead.
/// </summary>
public event Action<string> SceneLoadCompleted;

Step 3: Update Documentation

Add to SceneManagerService class documentation:

/// <summary>
/// Singleton service for loading and unloading Unity scenes asynchronously.
/// 
/// EVENTS vs LIFECYCLE HOOKS:
/// - Events (SceneLoadStarted/Completed): For orchestration and cross-scene awareness
/// - Lifecycle hooks (OnSceneReady, OnSceneUnloading): For component-level scene initialization
/// 
/// Most components should use lifecycle hooks, not event subscriptions.
/// </summary>

Impact Analysis

Before Trimming

public class SceneManagerService : ManagedBehaviour
{
    // 6 events
    public event Action<string> SceneLoadStarted;
    public event Action<string, float> SceneLoadProgress;      // ← UNUSED
    public event Action<string> SceneLoadCompleted;
    public event Action<string> SceneUnloadStarted;            // ← UNUSED
    public event Action<string, float> SceneUnloadProgress;    // ← UNUSED
    public event Action<string> SceneUnloadCompleted;          // ← UNUSED
}

After Trimming

public class SceneManagerService : ManagedBehaviour
{
    // 2 events (essential orchestration only)
    public event Action<string> SceneLoadStarted;
    public event Action<string> SceneLoadCompleted;
}

Metrics

  • Events removed: 4 out of 6 (67%)
  • Event invocations removed: ~8 call sites
  • Lines of code removed: ~15-20 lines
  • Cognitive complexity: Reduced significantly
  • Maintenance burden: Lower

Migration Path for Future Components

DON'T: Subscribe to scene events

public class MyComponent : ManagedBehaviour
{
    private void Start()
    {
        SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoaded;
    }
}

DO: Use lifecycle hooks

public class MyComponent : ManagedBehaviour
{
    protected override void OnSceneReady()
    {
        // Scene is ready, do initialization
    }
}

EXCEPTION: Cross-scene awareness

// Only if component needs to know about OTHER scenes loading
// (e.g., PauseMenu in BootstrapScene reacting to gameplay scenes)
public class CrossSceneComponent : ManagedBehaviour
{
    protected override void OnSceneReady()
    {
        SceneManagerService.Instance.SceneLoadCompleted += OnOtherSceneLoaded;
    }
    
    private void OnOtherSceneLoaded(string sceneName)
    {
        // React to OTHER scenes loading
    }
}

Code Quality Improvements

Before (Complex Event Mesh)

Component A ──┐
Component B ──┼──→ SceneLoadProgress (unused)
Component C ──┘

Component D ──┐
Component E ──┼──→ SceneUnloadStarted (unused)
Component F ──┘

Component G ──┐
Component H ──┼──→ SceneUnloadProgress (unused)
Component I ──┘

Component J ──┐
Component K ──┼──→ SceneUnloadCompleted (unused)
Component L ──┘

After (Clean Separation)

LoadingScreen ──→ SceneLoadStarted    ✓ (orchestration)
LoadingScreen ──→ SceneLoadCompleted  ✓ (orchestration)
PauseMenu ─────→ SceneLoadCompleted   ✓ (cross-scene)

All other components → Use lifecycle hooks (OnSceneReady, OnSceneUnloading)

Testing Checklist

After implementing trimming:

  • Verify loading screen shows/hides correctly
  • Verify PauseMenu visibility updates per scene
  • Test scene transitions work smoothly
  • Check no compilation errors
  • Grep for removed event names (ensure no orphaned subscribers)
  • Run full playthrough

Recommendation: Option A (Aggressive Trimming)

Rationale:

  1. 67% reduction in events (4 removed)
  2. No breaking changes (removed events had no subscribers)
  3. Clearer architecture (events for orchestration, lifecycle for components)
  4. Easier to maintain going forward
  5. Matches lifecycle system philosophy

Estimated Time: 15-20 minutes

Risk Level: 🟢 LOW - Removed events have zero subscribers


Next Steps

  1. Remove dead events from SceneManagerService (10 min)
  2. Update documentation in SceneManagerService (5 min)
  3. Grep search to verify no orphaned subscribers (2 min)
  4. Test scene transitions (5 min)

Total: ~22 minutes


Analysis Complete
Recommendation: Proceed with aggressive trimming (Option A)