Lifecycle System Refactor & Logging Centralization (#56)
## ManagedBehaviour System Refactor - **Sealed `Awake()`** to prevent override mistakes that break singleton registration - **Added `OnManagedAwake()`** for early initialization (fires during registration) - **Renamed lifecycle hook:** `OnManagedAwake()` → `OnManagedStart()` (fires after boot, mirrors Unity's Awake→Start) - **40 files migrated** to new pattern (2 core, 38 components) - Eliminated all fragile `private new void Awake()` patterns - Zero breaking changes - backward compatible ## Centralized Logging System - **Automatic tagging** via `CallerMemberName` and `CallerFilePath` - logs auto-tagged as `[ClassName][MethodName] message` - **Unified API:** Single `Logging.Debug/Info/Warning/Error()` replaces custom `LogDebugMessage()` implementations - **~90 logging call sites** migrated across 10 files - **10 redundant helper methods** removed - All logs broadcast via `Logging.OnLogEntryAdded` event for real-time monitoring ## Custom Log Console (Editor Window) - **Persistent filter popups** for multi-selection (classes, methods, log levels) - windows stay open during selection - **Search** across class names, methods, and message content - **Time range filter** with MinMaxSlider - **Export** filtered logs to timestamped `.txt` files - **Right-click context menu** for quick filtering and copy actions - **Visual improvements:** White text, alternating row backgrounds, color-coded log levels - **Multiple instances** supported for simultaneous system monitoring - Open via `AppleHills > Custom Log Console` Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: #56
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -123,7 +123,24 @@ namespace Core.Lifecycle
|
||||
// ALWAYS add to managedAwakeList - this is the master list used for save/load
|
||||
InsertSorted(managedAwakeList, component, component.ManagedAwakePriority);
|
||||
|
||||
// Handle ManagedAwake timing based on boot state
|
||||
// Register for all scene lifecycle hooks
|
||||
InsertSorted(sceneUnloadingList, component, component.SceneUnloadingPriority);
|
||||
InsertSorted(sceneReadyList, component, component.SceneReadyPriority);
|
||||
InsertSorted(saveRequestedList, component, component.SavePriority);
|
||||
InsertSorted(restoreRequestedList, component, component.RestorePriority);
|
||||
InsertSorted(destroyList, component, component.DestroyPriority);
|
||||
|
||||
// Call OnManagedAwake immediately after registration (early initialization hook)
|
||||
try
|
||||
{
|
||||
component.OnManagedAwake();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
|
||||
// Handle OnManagedStart timing based on boot state
|
||||
if (isBootComplete)
|
||||
{
|
||||
// Check if we're currently loading a scene
|
||||
@@ -136,27 +153,20 @@ namespace Core.Lifecycle
|
||||
else
|
||||
{
|
||||
// Truly late registration (component enabled after scene is ready)
|
||||
// Call OnManagedAwake immediately since boot already completed
|
||||
LogDebug($"Late registration: Calling OnManagedAwake immediately for {component.gameObject.name}");
|
||||
// Call OnManagedStart immediately since boot already completed
|
||||
LogDebug($"Late registration: Calling OnManagedStart immediately for {component.gameObject.name}");
|
||||
try
|
||||
{
|
||||
component.InvokeManagedAwake();
|
||||
component.OnManagedStart();
|
||||
HandleAutoRegistrations(component);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedStart for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
// If boot not complete, component stays in list and will be processed by BroadcastManagedAwake()
|
||||
|
||||
// Register for all scene lifecycle hooks
|
||||
InsertSorted(sceneUnloadingList, component, component.SceneUnloadingPriority);
|
||||
InsertSorted(sceneReadyList, component, component.SceneReadyPriority);
|
||||
InsertSorted(saveRequestedList, component, component.SavePriority);
|
||||
InsertSorted(restoreRequestedList, component, component.RestorePriority);
|
||||
InsertSorted(destroyList, component, component.DestroyPriority);
|
||||
// If boot not complete, component stays in list and will be processed by BroadcastManagedStart()
|
||||
|
||||
// If this scene is already ready (and we're not in loading mode), call OnSceneReady immediately
|
||||
if (!isLoadingScene && currentSceneReady == sceneName)
|
||||
@@ -164,7 +174,7 @@ namespace Core.Lifecycle
|
||||
LogDebug($"Late registration: Calling OnSceneReady immediately for {component.gameObject.name}");
|
||||
try
|
||||
{
|
||||
component.InvokeSceneReady();
|
||||
component.OnSceneReady();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -202,7 +212,7 @@ namespace Core.Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Called by CustomBoot when boot completes.
|
||||
/// Broadcasts ManagedAwake to all registered components.
|
||||
/// Broadcasts ManagedStart to all registered components.
|
||||
/// </summary>
|
||||
public void OnBootCompletionTriggered()
|
||||
{
|
||||
@@ -210,16 +220,16 @@ namespace Core.Lifecycle
|
||||
return;
|
||||
|
||||
LogDebug("=== Boot Completion Triggered ===");
|
||||
BroadcastManagedAwake();
|
||||
BroadcastManagedStart();
|
||||
isBootComplete = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast OnManagedAwake to all registered components (priority ordered).
|
||||
/// Broadcast OnManagedStart to all registered components (priority ordered).
|
||||
/// </summary>
|
||||
private void BroadcastManagedAwake()
|
||||
private void BroadcastManagedStart()
|
||||
{
|
||||
LogDebug($"Broadcasting ManagedAwake to {managedAwakeList.Count} components");
|
||||
LogDebug($"Broadcasting ManagedStart to {managedAwakeList.Count} components");
|
||||
|
||||
// Create a copy to avoid collection modification during iteration
|
||||
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
|
||||
@@ -230,12 +240,12 @@ namespace Core.Lifecycle
|
||||
|
||||
try
|
||||
{
|
||||
component.InvokeManagedAwake();
|
||||
component.OnManagedStart();
|
||||
HandleAutoRegistrations(component);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedStart for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,20 +285,20 @@ namespace Core.Lifecycle
|
||||
// Sort by ManagedAwake priority (lower values first)
|
||||
pendingSceneComponents.Sort((a, b) => a.ManagedAwakePriority.CompareTo(b.ManagedAwakePriority));
|
||||
|
||||
// Call OnManagedAwake in priority order
|
||||
// Call OnManagedStart in priority order
|
||||
foreach (var component in pendingSceneComponents)
|
||||
{
|
||||
if (component == null) continue;
|
||||
|
||||
try
|
||||
{
|
||||
component.InvokeManagedAwake();
|
||||
component.OnManagedStart();
|
||||
HandleAutoRegistrations(component);
|
||||
LogDebug($"Processed batched component: {component.gameObject.name} (Priority: {component.ManagedAwakePriority})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for batched component {component.gameObject.name}: {ex}");
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedStart for batched component {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +325,7 @@ namespace Core.Lifecycle
|
||||
{
|
||||
try
|
||||
{
|
||||
component.InvokeSceneUnloading();
|
||||
component.OnSceneUnloading();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -351,7 +361,7 @@ namespace Core.Lifecycle
|
||||
{
|
||||
try
|
||||
{
|
||||
component.InvokeSceneReady();
|
||||
component.OnSceneReady();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -379,7 +389,7 @@ namespace Core.Lifecycle
|
||||
|
||||
try
|
||||
{
|
||||
string serializedData = component.InvokeSceneSaveRequested();
|
||||
string serializedData = component.OnSceneSaveRequested();
|
||||
if (!string.IsNullOrEmpty(serializedData))
|
||||
{
|
||||
string saveId = component.SaveId;
|
||||
@@ -415,7 +425,7 @@ namespace Core.Lifecycle
|
||||
|
||||
try
|
||||
{
|
||||
string serializedData = component.InvokeGlobalSaveRequested();
|
||||
string serializedData = component.OnGlobalSaveRequested();
|
||||
if (!string.IsNullOrEmpty(serializedData))
|
||||
{
|
||||
saveData[component.SaveId] = serializedData;
|
||||
@@ -455,7 +465,7 @@ namespace Core.Lifecycle
|
||||
{
|
||||
try
|
||||
{
|
||||
component.InvokeSceneRestoreRequested(serializedData);
|
||||
component.OnSceneRestoreRequested(serializedData);
|
||||
restoredCount++;
|
||||
LogDebug($"Restored scene data to: {component.SaveId}");
|
||||
}
|
||||
@@ -486,7 +496,7 @@ namespace Core.Lifecycle
|
||||
|
||||
try
|
||||
{
|
||||
component.InvokeSceneRestoreCompleted();
|
||||
component.OnSceneRestoreCompleted();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -519,7 +529,7 @@ namespace Core.Lifecycle
|
||||
{
|
||||
try
|
||||
{
|
||||
component.InvokeGlobalRestoreRequested(serializedData);
|
||||
component.OnGlobalRestoreRequested(serializedData);
|
||||
restoredCount++;
|
||||
LogDebug($"Restored global data to: {component.SaveId}");
|
||||
}
|
||||
@@ -551,7 +561,7 @@ namespace Core.Lifecycle
|
||||
|
||||
try
|
||||
{
|
||||
component.InvokeGlobalLoadCompleted();
|
||||
component.OnGlobalLoadCompleted();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -578,7 +588,7 @@ namespace Core.Lifecycle
|
||||
|
||||
try
|
||||
{
|
||||
component.InvokeGlobalSaveStarted();
|
||||
component.OnGlobalSaveStarted();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Core.Lifecycle
|
||||
{
|
||||
@@ -12,7 +11,7 @@ namespace Core.Lifecycle
|
||||
#region Priority Properties
|
||||
|
||||
/// <summary>
|
||||
/// Priority for OnManagedAwake (lower values execute first).
|
||||
/// Priority for OnManagedStart (lower values execute first).
|
||||
/// Default: 100
|
||||
/// </summary>
|
||||
public virtual int ManagedAwakePriority => 100;
|
||||
@@ -81,23 +80,6 @@ namespace Core.Lifecycle
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Accessors (for LifecycleManager)
|
||||
|
||||
// Public wrappers to invoke protected lifecycle methods
|
||||
public void InvokeManagedAwake() => OnManagedAwake();
|
||||
public void InvokeSceneUnloading() => OnSceneUnloading();
|
||||
public void InvokeSceneReady() => OnSceneReady();
|
||||
public string InvokeSceneSaveRequested() => OnSceneSaveRequested();
|
||||
public void InvokeSceneRestoreRequested(string data) => OnSceneRestoreRequested(data);
|
||||
public void InvokeSceneRestoreCompleted() => OnSceneRestoreCompleted();
|
||||
public string InvokeGlobalSaveRequested() => OnGlobalSaveRequested();
|
||||
public void InvokeGlobalRestoreRequested(string data) => OnGlobalRestoreRequested(data);
|
||||
public void InvokeManagedDestroy() => OnManagedDestroy();
|
||||
public void InvokeGlobalLoadCompleted() => OnGlobalLoadCompleted();
|
||||
public void InvokeGlobalSaveStarted() => OnGlobalSaveStarted();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private bool _isRegistered;
|
||||
@@ -108,9 +90,9 @@ namespace Core.Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Unity Awake - automatically registers with LifecycleManager.
|
||||
/// IMPORTANT: Derived classes that override Awake MUST call base.Awake()
|
||||
/// SEALED: Cannot be overridden. Use OnManagedAwake() for early initialization or OnManagedStart() for late initialization.
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
private void Awake()
|
||||
{
|
||||
if (LifecycleManager.Instance != null)
|
||||
{
|
||||
@@ -152,14 +134,27 @@ namespace Core.Lifecycle
|
||||
|
||||
#region Managed Lifecycle Hooks
|
||||
|
||||
/// <summary>
|
||||
/// Called immediately during registration (during Awake).
|
||||
/// Use for early initialization such as setting singleton instances.
|
||||
/// TIMING: Fires during component's Awake(), no execution order guarantees between components.
|
||||
/// NOT priority-ordered - fires whenever Unity calls this component's Awake().
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
internal virtual void OnManagedAwake()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called once per component after bootstrap completes.
|
||||
/// GUARANTEE: Bootstrap resources are available, all managers are initialized.
|
||||
/// For boot-time components: Called during LifecycleManager.BroadcastManagedAwake (priority ordered).
|
||||
/// For boot-time components: Called during LifecycleManager.BroadcastManagedStart (priority ordered).
|
||||
/// For late-registered components: Called immediately upon registration (bootstrap already complete).
|
||||
/// Replaces the old Awake + InitializePostBoot pattern.
|
||||
/// Use for initialization that depends on other systems.
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnManagedAwake()
|
||||
internal virtual void OnManagedStart()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
@@ -168,8 +163,9 @@ namespace Core.Lifecycle
|
||||
/// Called before the scene this component belongs to is unloaded.
|
||||
/// Called in REVERSE priority order (higher values execute first).
|
||||
/// Use for scene-specific cleanup.
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnSceneUnloading()
|
||||
internal virtual void OnSceneUnloading()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
@@ -178,8 +174,9 @@ namespace Core.Lifecycle
|
||||
/// Called after the scene this component belongs to has finished loading.
|
||||
/// Called in priority order (lower values execute first).
|
||||
/// Use for scene-specific initialization.
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnSceneReady()
|
||||
internal virtual void OnSceneReady()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
@@ -193,8 +190,10 @@ namespace Core.Lifecycle
|
||||
/// - Called BEFORE scene unload during scene transitions
|
||||
/// - Frequency: Every scene transition
|
||||
/// - Use for: Level progress, object positions, puzzle states
|
||||
///
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual string OnSceneSaveRequested()
|
||||
internal virtual string OnSceneSaveRequested()
|
||||
{
|
||||
return null; // Default: no data to save
|
||||
}
|
||||
@@ -211,8 +210,10 @@ namespace Core.Lifecycle
|
||||
/// - Called AFTER scene load, during OnSceneReady phase
|
||||
/// - Frequency: Every scene transition
|
||||
/// - Use for: Restoring level progress, object positions, puzzle states
|
||||
///
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnSceneRestoreRequested(string serializedData)
|
||||
internal virtual void OnSceneRestoreRequested(string serializedData)
|
||||
{
|
||||
// Default: no-op
|
||||
}
|
||||
@@ -234,8 +235,10 @@ namespace Core.Lifecycle
|
||||
/// COMMON PATTERN:
|
||||
/// Use this to perform actions that depend on whether data was restored or not.
|
||||
/// Example: Play one-time audio only if it hasn't been played before (_hasPlayed == false).
|
||||
///
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnSceneRestoreCompleted()
|
||||
internal virtual void OnSceneRestoreCompleted()
|
||||
{
|
||||
// Default: no-op
|
||||
}
|
||||
@@ -248,8 +251,10 @@ namespace Core.Lifecycle
|
||||
/// - Called ONCE on game boot after save file is read
|
||||
/// - NOT called during scene transitions
|
||||
/// - Use for: Player inventory, unlocked features, card collections
|
||||
///
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnGlobalRestoreRequested(string serializedData)
|
||||
internal virtual void OnGlobalRestoreRequested(string serializedData)
|
||||
{
|
||||
// Default: no-op
|
||||
}
|
||||
@@ -263,8 +268,10 @@ namespace Core.Lifecycle
|
||||
/// - Called ONCE before save file is written (on quit, manual save, etc.)
|
||||
/// - NOT called during scene transitions
|
||||
/// - Use for: Player inventory, unlocked features, card collections
|
||||
///
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual string OnGlobalSaveRequested()
|
||||
internal virtual string OnGlobalSaveRequested()
|
||||
{
|
||||
return null; // Default: no data to save
|
||||
}
|
||||
@@ -278,8 +285,10 @@ namespace Core.Lifecycle
|
||||
/// - Called ONCE on game boot after all restore operations complete
|
||||
/// - NOT called during scene transitions
|
||||
/// - Use for: Triggering UI updates, broadcasting load events
|
||||
///
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnGlobalLoadCompleted()
|
||||
internal virtual void OnGlobalLoadCompleted()
|
||||
{
|
||||
// Default: no-op
|
||||
}
|
||||
@@ -293,8 +302,10 @@ namespace Core.Lifecycle
|
||||
/// - Called ONCE before save file is written
|
||||
/// - NOT called during scene transitions
|
||||
/// - Use for: Final validation, cleanup operations
|
||||
///
|
||||
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnGlobalSaveStarted()
|
||||
internal virtual void OnGlobalSaveStarted()
|
||||
{
|
||||
// Default: no-op
|
||||
}
|
||||
@@ -304,8 +315,9 @@ namespace Core.Lifecycle
|
||||
/// Called in REVERSE priority order (higher values execute first).
|
||||
/// NOTE: Most cleanup is automatic (managed events, auto-registrations).
|
||||
/// Only override if you need custom cleanup logic.
|
||||
/// Internal visibility allows LifecycleManager to call directly. Override in derived classes.
|
||||
/// </summary>
|
||||
protected virtual void OnManagedDestroy()
|
||||
internal virtual void OnManagedDestroy()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user