Files
AppleHillsProduction/docs/scene_event_trimming_analysis.md

400 lines
10 KiB
Markdown
Raw Normal View History

# 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:**
```csharp
// 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<float> parameter in async methods
- Loading screen gets progress via callback, not event
**Action:** ✅ **REMOVE** - Dead code
---
### 3. SceneLoadCompleted ⚠️ **KEEP (but maybe simplify)**
**Current Usage:**
```csharp
// 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<float> 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
### Option A: Aggressive Trimming (Recommended)
**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
---
### Option C: Nuclear Option (Not Recommended)
**Remove ALL events, replace with callbacks:**
```csharp
// 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
---
## Recommended Implementation
### Step 1: Remove Dead Events
**File:** `SceneManagerService.cs`
**Remove these declarations:**
```csharp
// 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:**
```csharp
// 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):**
```csharp
/// <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:**
```csharp
/// <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
```csharp
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
```csharp
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
```csharp
public class MyComponent : ManagedBehaviour
{
private void Start()
{
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoaded;
}
}
```
### ✅ DO: Use lifecycle hooks
```csharp
public class MyComponent : ManagedBehaviour
{
protected override void OnSceneReady()
{
// Scene is ready, do initialization
}
}
```
### ✅ EXCEPTION: Cross-scene awareness
```csharp
// 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)