Rework of base interactables and managed behaviors
This commit is contained in:
committed by
Michal Pikulski
parent
00e1746ac4
commit
f88bd0e2c9
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using AppleHills.Core.Interfaces;
|
||||
using AppleHills.Core.Settings;
|
||||
using Bootstrap;
|
||||
using Core.Lifecycle;
|
||||
using Core.Settings;
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
@@ -12,7 +12,7 @@ namespace Core
|
||||
/// <summary>
|
||||
/// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters.
|
||||
/// </summary>
|
||||
public class GameManager : MonoBehaviour
|
||||
public class GameManager : ManagedBehaviour
|
||||
{
|
||||
// Singleton implementation
|
||||
private static GameManager _instance;
|
||||
@@ -34,33 +34,33 @@ namespace Core
|
||||
public event Action OnGamePaused;
|
||||
public event Action OnGameResumed;
|
||||
|
||||
void Awake()
|
||||
// ManagedBehaviour configuration
|
||||
public override int ManagedAwakePriority => 10; // Core infrastructure - runs early
|
||||
|
||||
private new void Awake()
|
||||
{
|
||||
base.Awake(); // CRITICAL: Register with LifecycleManager!
|
||||
|
||||
// Set instance immediately so it's available before OnManagedAwake() is called
|
||||
_instance = this;
|
||||
|
||||
// Create settings providers if it doesn't exist
|
||||
|
||||
// Create settings providers - must happen in Awake so other managers can access settings in their ManagedAwake
|
||||
SettingsProvider.Instance.gameObject.name = "Settings Provider";
|
||||
DeveloperSettingsProvider.Instance.gameObject.name = "Developer Settings Provider";
|
||||
|
||||
// Load all settings synchronously during Awake
|
||||
// Load all settings synchronously - critical infrastructure for other managers
|
||||
InitializeSettings();
|
||||
InitializeDeveloperSettings();
|
||||
|
||||
// Register for post-boot initialization
|
||||
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
||||
|
||||
// DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
|
||||
// Load verbosity settings early
|
||||
_settingsLogVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().settingsLogVerbosity;
|
||||
_managerLogVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().gameManagerLogVerbosity;
|
||||
}
|
||||
|
||||
private void InitializePostBoot()
|
||||
protected override void OnManagedAwake()
|
||||
{
|
||||
// For post-boot correct initialization order
|
||||
// Settings are already initialized in Awake()
|
||||
// This is available for future initialization that depends on other managers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Interactions;
|
||||
using Bootstrap;
|
||||
using Core.Lifecycle;
|
||||
using Core.SaveLoad;
|
||||
|
||||
namespace Core
|
||||
@@ -11,7 +11,7 @@ namespace Core
|
||||
/// Central registry for pickups and item slots.
|
||||
/// Mirrors the singleton pattern used by PuzzleManager.
|
||||
/// </summary>
|
||||
public class ItemManager : MonoBehaviour
|
||||
public class ItemManager : ManagedBehaviour
|
||||
{
|
||||
private static ItemManager _instance;
|
||||
|
||||
@@ -48,35 +48,32 @@ namespace Core
|
||||
// Args: first item data, second item data, result item data
|
||||
public event Action<PickupItemData, PickupItemData, PickupItemData> OnItemsCombined;
|
||||
|
||||
void Awake()
|
||||
public override int ManagedAwakePriority => 75; // Item registry
|
||||
|
||||
private new void Awake()
|
||||
{
|
||||
_instance = this;
|
||||
base.Awake(); // CRITICAL: Register with LifecycleManager!
|
||||
|
||||
// Register for post-boot initialization
|
||||
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
||||
// Set instance immediately so it's available before OnManagedAwake() is called
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
private void InitializePostBoot()
|
||||
protected override void OnManagedAwake()
|
||||
{
|
||||
// Subscribe to scene load completed so we can clear registrations when scenes change
|
||||
SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted;
|
||||
|
||||
Logging.Debug("[ItemManager] Subscribed to SceneManagerService events");
|
||||
Logging.Debug("[ItemManager] Initialized");
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
|
||||
protected override void OnSceneReady()
|
||||
{
|
||||
// Unsubscribe from SceneManagerService
|
||||
if (SceneManagerService.Instance != null)
|
||||
SceneManagerService.Instance.SceneLoadStarted -= OnSceneLoadStarted;
|
||||
|
||||
// Ensure we clean up any subscriptions from registered items when the manager is destroyed
|
||||
// Replaces SceneLoadStarted subscription for clearing registrations
|
||||
ClearAllRegistrations();
|
||||
}
|
||||
|
||||
private void OnSceneLoadStarted(string sceneName)
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
// Clear all registrations when a new scene is loaded, so no stale references persist
|
||||
base.OnDestroy();
|
||||
|
||||
// Ensure we clean up any subscriptions from registered items when the manager is destroyed
|
||||
ClearAllRegistrations();
|
||||
}
|
||||
|
||||
|
||||
3
Assets/Scripts/Core/Lifecycle.meta
Normal file
3
Assets/Scripts/Core/Lifecycle.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06a2c07342e5422eae1eb613f614ed61
|
||||
timeCreated: 1762206473
|
||||
48
Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs
Normal file
48
Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace Core.Lifecycle
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the different lifecycle phases that can be broadcast by the LifecycleManager.
|
||||
/// All ManagedBehaviours participate in all lifecycle phases by default.
|
||||
/// </summary>
|
||||
public enum LifecyclePhase
|
||||
{
|
||||
/// <summary>
|
||||
/// Called once per component after bootstrap completes.
|
||||
/// Guaranteed to be called after all bootstrap resources are loaded.
|
||||
/// For late-registered components, called immediately upon registration.
|
||||
/// </summary>
|
||||
ManagedAwake,
|
||||
|
||||
/// <summary>
|
||||
/// Called before a scene is unloaded.
|
||||
/// Only called for components in the scene being unloaded.
|
||||
/// </summary>
|
||||
SceneUnloading,
|
||||
|
||||
/// <summary>
|
||||
/// Called after a scene has finished loading.
|
||||
/// Only called for components in the scene being loaded.
|
||||
/// </summary>
|
||||
SceneReady,
|
||||
|
||||
/// <summary>
|
||||
/// Called before scene unloads to save data via SaveLoadManager.
|
||||
/// Integrates with existing SaveLoadManager save system.
|
||||
/// </summary>
|
||||
SaveRequested,
|
||||
|
||||
/// <summary>
|
||||
/// Called after scene loads to restore data via SaveLoadManager.
|
||||
/// Integrates with existing SaveLoadManager restore system.
|
||||
/// </summary>
|
||||
RestoreRequested,
|
||||
|
||||
/// <summary>
|
||||
/// Called during OnDestroy before component is destroyed.
|
||||
/// Use for custom cleanup logic.
|
||||
/// Most cleanup is automatic (managed events, auto-registrations).
|
||||
/// </summary>
|
||||
ManagedDestroy
|
||||
}
|
||||
}
|
||||
|
||||
2
Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs.meta
Normal file
2
Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f5f0f19f08240d4d9863b6be6a3cf03
|
||||
420
Assets/Scripts/Core/Lifecycle/LifecycleManager.cs
Normal file
420
Assets/Scripts/Core/Lifecycle/LifecycleManager.cs
Normal file
@@ -0,0 +1,420 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Core.Lifecycle
|
||||
{
|
||||
/// <summary>
|
||||
/// Central orchestrator for ManagedBehaviour lifecycle events.
|
||||
/// Singleton that broadcasts lifecycle events in priority-ordered manner.
|
||||
/// </summary>
|
||||
public class LifecycleManager : MonoBehaviour
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private static LifecycleManager _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton instance of the LifecycleManager.
|
||||
/// Created by CustomBoot.Initialise() before bootstrap begins.
|
||||
/// </summary>
|
||||
public static LifecycleManager Instance => _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Create LifecycleManager instance. Called by CustomBoot.Initialise() before bootstrap begins.
|
||||
/// </summary>
|
||||
public static void CreateInstance()
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
Debug.LogWarning("[LifecycleManager] Instance already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
var go = new GameObject("LifecycleManager");
|
||||
_instance = go.AddComponent<LifecycleManager>();
|
||||
DontDestroyOnLoad(go);
|
||||
|
||||
Debug.Log("[LifecycleManager] Instance created");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle Lists
|
||||
|
||||
private List<ManagedBehaviour> managedAwakeList = new List<ManagedBehaviour>();
|
||||
private List<ManagedBehaviour> sceneUnloadingList = new List<ManagedBehaviour>();
|
||||
private List<ManagedBehaviour> sceneReadyList = new List<ManagedBehaviour>();
|
||||
private List<ManagedBehaviour> saveRequestedList = new List<ManagedBehaviour>();
|
||||
private List<ManagedBehaviour> restoreRequestedList = new List<ManagedBehaviour>();
|
||||
private List<ManagedBehaviour> destroyList = new List<ManagedBehaviour>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tracking Dictionaries
|
||||
|
||||
private Dictionary<ManagedBehaviour, string> componentScenes = new Dictionary<ManagedBehaviour, string>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Flags
|
||||
|
||||
private bool isBootComplete = false;
|
||||
private string currentSceneReady = "";
|
||||
|
||||
[SerializeField] private bool enableDebugLogging = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Instance should already be set by CreateInstance() called from CustomBoot
|
||||
// This Awake is backup in case LifecycleManager was manually added to a scene
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
LogDebug("LifecycleManager initialized via Awake (fallback)");
|
||||
}
|
||||
else if (_instance != this)
|
||||
{
|
||||
Debug.LogWarning("[LifecycleManager] Duplicate instance detected. Destroying.");
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (_instance == this)
|
||||
{
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Registration
|
||||
|
||||
/// <summary>
|
||||
/// Register a ManagedBehaviour with the lifecycle system.
|
||||
/// Called automatically from ManagedBehaviour.Awake().
|
||||
/// All components participate in all lifecycle hooks.
|
||||
/// </summary>
|
||||
public void Register(ManagedBehaviour component)
|
||||
{
|
||||
if (component == null)
|
||||
{
|
||||
Debug.LogWarning("[LifecycleManager] Attempted to register null component");
|
||||
return;
|
||||
}
|
||||
|
||||
var sceneName = component.gameObject.scene.name;
|
||||
|
||||
// Track which scene this component belongs to
|
||||
componentScenes[component] = sceneName;
|
||||
|
||||
// Register for ManagedAwake
|
||||
if (isBootComplete)
|
||||
{
|
||||
// Boot already complete - call OnManagedAwake immediately
|
||||
LogDebug($"Late registration: Calling OnManagedAwake immediately for {component.gameObject.name}");
|
||||
try
|
||||
{
|
||||
component.InvokeManagedAwake();
|
||||
HandleAutoRegistrations(component);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Boot not complete yet - add to list for broadcast
|
||||
InsertSorted(managedAwakeList, component, component.ManagedAwakePriority);
|
||||
}
|
||||
|
||||
// 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 this scene is already ready, call OnSceneReady immediately
|
||||
// Check both currentSceneReady AND if the Unity scene is actually loaded
|
||||
// (during scene loading, components Awake before BroadcastSceneReady is called)
|
||||
bool sceneIsReady = currentSceneReady == sceneName;
|
||||
|
||||
// Also check if this is happening during boot and the scene is the active scene
|
||||
// This handles components that register during initial scene load
|
||||
if (!sceneIsReady && isBootComplete && sceneName != "DontDestroyOnLoad")
|
||||
{
|
||||
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
|
||||
sceneIsReady = scene.isLoaded;
|
||||
}
|
||||
|
||||
if (sceneIsReady)
|
||||
{
|
||||
LogDebug($"Late registration: Calling OnSceneReady immediately for {component.gameObject.name}");
|
||||
try
|
||||
{
|
||||
component.InvokeSceneReady();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnSceneReady for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
LogDebug($"Registered {component.gameObject.name} (Scene: {sceneName})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a ManagedBehaviour from the lifecycle system.
|
||||
/// Called automatically from ManagedBehaviour.OnDestroy().
|
||||
/// </summary>
|
||||
public void Unregister(ManagedBehaviour component)
|
||||
{
|
||||
if (component == null)
|
||||
return;
|
||||
|
||||
managedAwakeList.Remove(component);
|
||||
sceneUnloadingList.Remove(component);
|
||||
sceneReadyList.Remove(component);
|
||||
saveRequestedList.Remove(component);
|
||||
restoreRequestedList.Remove(component);
|
||||
destroyList.Remove(component);
|
||||
|
||||
componentScenes.Remove(component);
|
||||
|
||||
LogDebug($"Unregistered {component.gameObject.name}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Broadcast Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called by CustomBoot when boot completes.
|
||||
/// Broadcasts ManagedAwake to all registered components.
|
||||
/// </summary>
|
||||
public void OnBootCompletionTriggered()
|
||||
{
|
||||
if (isBootComplete)
|
||||
return;
|
||||
|
||||
LogDebug("=== Boot Completion Triggered ===");
|
||||
BroadcastManagedAwake();
|
||||
isBootComplete = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast OnManagedAwake to all registered components (priority ordered).
|
||||
/// </summary>
|
||||
private void BroadcastManagedAwake()
|
||||
{
|
||||
LogDebug($"Broadcasting ManagedAwake to {managedAwakeList.Count} components");
|
||||
|
||||
foreach (var component in managedAwakeList)
|
||||
{
|
||||
if (component == null) continue;
|
||||
|
||||
try
|
||||
{
|
||||
component.InvokeManagedAwake();
|
||||
HandleAutoRegistrations(component);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the list - components already initialized
|
||||
managedAwakeList.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast OnSceneUnloading to components in the specified scene (reverse priority order).
|
||||
/// </summary>
|
||||
public void BroadcastSceneUnloading(string sceneName)
|
||||
{
|
||||
LogDebug($"Broadcasting SceneUnloading for scene: {sceneName}");
|
||||
|
||||
// Iterate backwards (high priority → low priority)
|
||||
for (int i = sceneUnloadingList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var component = sceneUnloadingList[i];
|
||||
if (component == null) continue;
|
||||
|
||||
if (componentScenes.TryGetValue(component, out string compScene) && compScene == sceneName)
|
||||
{
|
||||
try
|
||||
{
|
||||
component.InvokeSceneUnloading();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnSceneUnloading for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast OnSaveRequested to components in the specified scene (reverse priority order).
|
||||
/// </summary>
|
||||
public void BroadcastSaveRequested(string sceneName)
|
||||
{
|
||||
LogDebug($"Broadcasting SaveRequested for scene: {sceneName}");
|
||||
|
||||
// Iterate backwards (high priority → low priority)
|
||||
for (int i = saveRequestedList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var component = saveRequestedList[i];
|
||||
if (component == null) continue;
|
||||
|
||||
if (componentScenes.TryGetValue(component, out string compScene) && compScene == sceneName)
|
||||
{
|
||||
try
|
||||
{
|
||||
component.InvokeSaveRequested();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnSaveRequested for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast OnSceneReady to components in the specified scene (priority order).
|
||||
/// </summary>
|
||||
public void BroadcastSceneReady(string sceneName)
|
||||
{
|
||||
LogDebug($"Broadcasting SceneReady for scene: {sceneName}");
|
||||
currentSceneReady = sceneName;
|
||||
|
||||
foreach (var component in sceneReadyList)
|
||||
{
|
||||
if (component == null) continue;
|
||||
|
||||
if (componentScenes.TryGetValue(component, out string compScene) && compScene == sceneName)
|
||||
{
|
||||
try
|
||||
{
|
||||
component.InvokeSceneReady();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnSceneReady for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast OnRestoreRequested to components in the specified scene (priority order).
|
||||
/// </summary>
|
||||
public void BroadcastRestoreRequested(string sceneName)
|
||||
{
|
||||
LogDebug($"Broadcasting RestoreRequested for scene: {sceneName}");
|
||||
|
||||
foreach (var component in restoreRequestedList)
|
||||
{
|
||||
if (component == null) continue;
|
||||
|
||||
if (componentScenes.TryGetValue(component, out string compScene) && compScene == sceneName)
|
||||
{
|
||||
try
|
||||
{
|
||||
component.InvokeRestoreRequested();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LifecycleManager] Error in OnRestoreRequested for {component.gameObject.name}: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Auto-Registration
|
||||
|
||||
/// <summary>
|
||||
/// Handle automatic registration with GameManager.
|
||||
/// </summary>
|
||||
private void HandleAutoRegistrations(ManagedBehaviour component)
|
||||
{
|
||||
|
||||
// Auto-register IPausable
|
||||
if (component.AutoRegisterPausable && component is AppleHills.Core.Interfaces.IPausable pausable)
|
||||
{
|
||||
if (GameManager.Instance != null)
|
||||
{
|
||||
GameManager.Instance.RegisterPausableComponent(pausable);
|
||||
LogDebug($"Auto-registered IPausable: {component.gameObject.name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Insert component into list maintaining sorted order by priority.
|
||||
/// Uses binary search for efficient insertion.
|
||||
/// </summary>
|
||||
private void InsertSorted(List<ManagedBehaviour> list, ManagedBehaviour component, int priority)
|
||||
{
|
||||
// Simple linear insertion for now (can optimize with binary search later if needed)
|
||||
int index = 0;
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
int existingPriority = GetPriorityForList(list[i], list);
|
||||
if (priority < existingPriority)
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
index = i + 1;
|
||||
}
|
||||
|
||||
list.Insert(index, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the priority value for a component based on which list it's in.
|
||||
/// </summary>
|
||||
private int GetPriorityForList(ManagedBehaviour component, List<ManagedBehaviour> list)
|
||||
{
|
||||
if (list == managedAwakeList) return component.ManagedAwakePriority;
|
||||
if (list == sceneUnloadingList) return component.SceneUnloadingPriority;
|
||||
if (list == sceneReadyList) return component.SceneReadyPriority;
|
||||
if (list == saveRequestedList) return component.SavePriority;
|
||||
if (list == restoreRequestedList) return component.RestorePriority;
|
||||
if (list == destroyList) return component.DestroyPriority;
|
||||
return 100;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log debug message if debug logging is enabled.
|
||||
/// </summary>
|
||||
private void LogDebug(string message)
|
||||
{
|
||||
if (enableDebugLogging)
|
||||
{
|
||||
Debug.Log($"[LifecycleManager] {message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
2
Assets/Scripts/Core/Lifecycle/LifecycleManager.cs.meta
Normal file
2
Assets/Scripts/Core/Lifecycle/LifecycleManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db6d4743867a3a44381d511cea39218d
|
||||
224
Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs
Normal file
224
Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Core.Lifecycle
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all managed behaviours with deterministic lifecycle hooks.
|
||||
/// Automatically registers with LifecycleManager and provides ordered lifecycle callbacks.
|
||||
/// </summary>
|
||||
public abstract class ManagedBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Priority Properties
|
||||
|
||||
/// <summary>
|
||||
/// Priority for OnManagedAwake (lower values execute first).
|
||||
/// Default: 100
|
||||
/// </summary>
|
||||
public virtual int ManagedAwakePriority => 100;
|
||||
|
||||
/// <summary>
|
||||
/// Priority for OnSceneUnloading (executed in reverse: higher values execute first).
|
||||
/// Default: 100
|
||||
/// </summary>
|
||||
public virtual int SceneUnloadingPriority => 100;
|
||||
|
||||
/// <summary>
|
||||
/// Priority for OnSceneReady (lower values execute first).
|
||||
/// Default: 100
|
||||
/// </summary>
|
||||
public virtual int SceneReadyPriority => 100;
|
||||
|
||||
/// <summary>
|
||||
/// Priority for OnSaveRequested (executed in reverse: higher values execute first).
|
||||
/// Default: 100
|
||||
/// </summary>
|
||||
public virtual int SavePriority => 100;
|
||||
|
||||
/// <summary>
|
||||
/// Priority for OnRestoreRequested (lower values execute first).
|
||||
/// Default: 100
|
||||
/// </summary>
|
||||
public virtual int RestorePriority => 100;
|
||||
|
||||
/// <summary>
|
||||
/// Priority for OnManagedDestroy (executed in reverse: higher values execute first).
|
||||
/// Default: 100
|
||||
/// </summary>
|
||||
public virtual int DestroyPriority => 100;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Configuration Properties
|
||||
|
||||
/// <summary>
|
||||
/// If true and component implements IPausable, automatically registers with GameManager.
|
||||
/// Default: false
|
||||
/// </summary>
|
||||
public virtual bool AutoRegisterPausable => false;
|
||||
|
||||
#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 void InvokeSaveRequested() => OnSaveRequested();
|
||||
public void InvokeRestoreRequested() => OnRestoreRequested();
|
||||
public void InvokeManagedDestroy() => OnManagedDestroy();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private ManagedEventManager _eventManager;
|
||||
private bool _isRegistered;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Unity Awake - automatically registers with LifecycleManager.
|
||||
/// IMPORTANT: Derived classes that override Awake MUST call base.Awake()
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_eventManager = new ManagedEventManager();
|
||||
|
||||
if (LifecycleManager.Instance != null)
|
||||
{
|
||||
LifecycleManager.Instance.Register(this);
|
||||
_isRegistered = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[ManagedBehaviour] LifecycleManager not found for {gameObject.name}. Component will not receive lifecycle callbacks.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unity OnDestroy - automatically unregisters and cleans up.
|
||||
/// IMPORTANT: Derived classes that override OnDestroy MUST call base.OnDestroy()
|
||||
/// </summary>
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
if (!_isRegistered)
|
||||
return;
|
||||
|
||||
// Unregister from LifecycleManager
|
||||
if (LifecycleManager.Instance != null)
|
||||
{
|
||||
LifecycleManager.Instance.Unregister(this);
|
||||
}
|
||||
|
||||
// Auto-cleanup managed events
|
||||
_eventManager?.UnregisterAllEvents();
|
||||
|
||||
|
||||
// Auto-unregister from GameManager if auto-registered
|
||||
if (AutoRegisterPausable && this is AppleHills.Core.Interfaces.IPausable pausable)
|
||||
{
|
||||
GameManager.Instance?.UnregisterPausableComponent(pausable);
|
||||
}
|
||||
|
||||
_isRegistered = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Managed Lifecycle Hooks
|
||||
|
||||
/// <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 late-registered components: Called immediately upon registration (bootstrap already complete).
|
||||
/// Replaces the old Awake + InitializePostBoot pattern.
|
||||
/// </summary>
|
||||
protected virtual void OnManagedAwake()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before the scene this component belongs to is unloaded.
|
||||
/// Called in REVERSE priority order (higher values execute first).
|
||||
/// Use for scene-specific cleanup.
|
||||
/// </summary>
|
||||
protected virtual void OnSceneUnloading()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the scene this component belongs to has finished loading.
|
||||
/// Called in priority order (lower values execute first).
|
||||
/// Use for scene-specific initialization.
|
||||
/// </summary>
|
||||
protected virtual void OnSceneReady()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before scene unloads to save data via SaveLoadManager.
|
||||
/// Called in REVERSE priority order (higher values execute first).
|
||||
/// Integrates with existing SaveLoadManager save system.
|
||||
/// Return serialized state string (e.g., JsonUtility.ToJson(myData)).
|
||||
/// </summary>
|
||||
protected virtual void OnSaveRequested()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after scene loads to restore data via SaveLoadManager.
|
||||
/// Called in priority order (lower values execute first).
|
||||
/// Integrates with existing SaveLoadManager restore system.
|
||||
/// Receives serialized state string to restore from.
|
||||
/// </summary>
|
||||
protected virtual void OnRestoreRequested()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during OnDestroy before component is destroyed.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
protected virtual void OnManagedDestroy()
|
||||
{
|
||||
// Override in derived classes
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Register an event subscription for automatic cleanup on destroy.
|
||||
/// Prevents memory leaks by ensuring the event is unsubscribed when this component is destroyed.
|
||||
/// </summary>
|
||||
/// <param name="target">The object that owns the event</param>
|
||||
/// <param name="eventName">Name of the event (e.g., "SceneLoadCompleted")</param>
|
||||
/// <param name="handler">The delegate/handler for the event</param>
|
||||
protected void RegisterManagedEvent(object target, string eventName, Delegate handler)
|
||||
{
|
||||
if (_eventManager == null)
|
||||
{
|
||||
Debug.LogWarning($"[ManagedBehaviour] Event manager not initialized for {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
_eventManager.RegisterEvent(target, eventName, handler);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
2
Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs.meta
Normal file
2
Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af776ef1493d6e543aa3cbe2601f4ef2
|
||||
102
Assets/Scripts/Core/Lifecycle/ManagedEventSubscription.cs
Normal file
102
Assets/Scripts/Core/Lifecycle/ManagedEventSubscription.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Core.Lifecycle
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores information about a single event subscription for automatic cleanup.
|
||||
/// </summary>
|
||||
internal class EventSubscriptionInfo
|
||||
{
|
||||
public object Target { get; set; }
|
||||
public Delegate Handler { get; set; }
|
||||
public string EventName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages event subscriptions for a ManagedBehaviour with automatic cleanup on destroy.
|
||||
/// Prevents memory leaks by ensuring all event subscriptions are properly unsubscribed.
|
||||
/// </summary>
|
||||
public class ManagedEventManager
|
||||
{
|
||||
private readonly List<EventSubscriptionInfo> _subscriptions = new List<EventSubscriptionInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Register an event subscription for automatic cleanup.
|
||||
/// </summary>
|
||||
/// <param name="target">The object that owns the event</param>
|
||||
/// <param name="eventName">Name of the event (e.g., "OnSomethingHappened")</param>
|
||||
/// <param name="handler">The delegate/handler for the event</param>
|
||||
public void RegisterEvent(object target, string eventName, Delegate handler)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
Debug.LogWarning("[ManagedEventManager] Cannot register event on null target");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(eventName))
|
||||
{
|
||||
Debug.LogWarning("[ManagedEventManager] Event name cannot be null or empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (handler == null)
|
||||
{
|
||||
Debug.LogWarning("[ManagedEventManager] Handler cannot be null");
|
||||
return;
|
||||
}
|
||||
|
||||
_subscriptions.Add(new EventSubscriptionInfo
|
||||
{
|
||||
Target = target,
|
||||
EventName = eventName,
|
||||
Handler = handler
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister all event subscriptions. Called automatically on ManagedBehaviour destruction.
|
||||
/// </summary>
|
||||
public void UnregisterAllEvents()
|
||||
{
|
||||
foreach (var subscription in _subscriptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (subscription.Target == null)
|
||||
continue;
|
||||
|
||||
// Use reflection to get the event and unsubscribe
|
||||
var eventInfo = subscription.Target.GetType().GetEvent(
|
||||
subscription.EventName,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static
|
||||
);
|
||||
|
||||
if (eventInfo != null)
|
||||
{
|
||||
eventInfo.RemoveEventHandler(subscription.Target, subscription.Handler);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[ManagedEventManager] Could not find event '{subscription.EventName}' on type '{subscription.Target.GetType().Name}'");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[ManagedEventManager] Error unsubscribing from event '{subscription.EventName}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
_subscriptions.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the count of registered event subscriptions (for debugging).
|
||||
/// </summary>
|
||||
public int SubscriptionCount => _subscriptions.Count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63e107279fdbf1542a9d93d57e60285c
|
||||
@@ -1,7 +1,7 @@
|
||||
using UnityEngine;
|
||||
using AppleHills.Data.CardSystem;
|
||||
using Cinematics;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Data.CardSystem;
|
||||
using Input;
|
||||
using PuzzleS;
|
||||
@@ -12,7 +12,7 @@ namespace AppleHills.Core
|
||||
/// Provides quick access to frequently used game objects, components, and manager instances.
|
||||
/// References are cached for performance and automatically invalidated on scene changes.
|
||||
/// </summary>
|
||||
public class QuickAccess : MonoBehaviour
|
||||
public class QuickAccess : ManagedBehaviour
|
||||
{
|
||||
#region Singleton Setup
|
||||
private static QuickAccess _instance;
|
||||
@@ -24,6 +24,9 @@ namespace AppleHills.Core
|
||||
|
||||
#endregion Singleton Setup
|
||||
|
||||
// Very early initialization - QuickAccess should be available immediately
|
||||
public override int ManagedAwakePriority => 5;
|
||||
|
||||
#region Manager Instances
|
||||
|
||||
// Core Managers
|
||||
@@ -46,7 +49,6 @@ namespace AppleHills.Core
|
||||
private PlayerTouchController _playerController;
|
||||
private FollowerController _followerController;
|
||||
private Camera _mainCamera;
|
||||
private bool _initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the player GameObject. Finds it if not already cached.
|
||||
@@ -125,31 +127,31 @@ namespace AppleHills.Core
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization and Scene Management
|
||||
#region Lifecycle Methods
|
||||
|
||||
private void Awake()
|
||||
private new void Awake()
|
||||
{
|
||||
_instance = this;
|
||||
base.Awake(); // CRITICAL: Register with LifecycleManager!
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
// Subscribe to scene changes
|
||||
if (SceneManager != null)
|
||||
{
|
||||
SceneManager.SceneLoadCompleted += OnSceneLoadCompleted;
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
// Set instance immediately so it's available before OnManagedAwake() is called
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle scene changes by clearing cached references.
|
||||
/// </summary>
|
||||
private void OnSceneLoadCompleted(string sceneName)
|
||||
protected override void OnManagedAwake()
|
||||
{
|
||||
// QuickAccess has minimal initialization
|
||||
}
|
||||
|
||||
protected override void OnSceneUnloading()
|
||||
{
|
||||
// Clear references BEFORE scene unloads for better cleanup timing
|
||||
ClearReferences();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reference Management
|
||||
|
||||
/// <summary>
|
||||
/// Clear all cached references.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using UnityEngine;
|
||||
using Pixelplacement;
|
||||
using Bootstrap;
|
||||
|
||||
namespace Core.SaveLoad
|
||||
{
|
||||
@@ -82,18 +81,15 @@ namespace Core.SaveLoad
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Register with save system (no validation needed - we auto-generate ID)
|
||||
BootCompletionService.RegisterInitAction(() =>
|
||||
// Direct registration - SaveLoadManager guaranteed available (priority 25)
|
||||
if (SaveLoadManager.Instance != null)
|
||||
{
|
||||
if (SaveLoadManager.Instance != null)
|
||||
{
|
||||
SaveLoadManager.Instance.RegisterParticipant(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[SaveableStateMachine] SaveLoadManager.Instance is null, cannot register '{name}'", this);
|
||||
}
|
||||
});
|
||||
SaveLoadManager.Instance.RegisterParticipant(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[AppleMachine] SaveLoadManager not available for '{name}'", this);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
@@ -4,20 +4,20 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AppleHills.Core.Settings;
|
||||
using Bootstrap;
|
||||
using Core.Lifecycle;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Core.SaveLoad
|
||||
{
|
||||
/// <summary>
|
||||
/// Save/Load manager that follows the project's bootstrap pattern.
|
||||
/// Save/Load manager that follows the project's lifecycle pattern.
|
||||
/// - Singleton instance
|
||||
/// - Registers a post-boot init action with BootCompletionService
|
||||
/// - Inherits from ManagedBehaviour for lifecycle integration
|
||||
/// - Manages participant registration for save/load operations
|
||||
/// - Exposes simple async Save/Load methods
|
||||
/// - Fires events on completion
|
||||
/// </summary>
|
||||
public class SaveLoadManager : MonoBehaviour
|
||||
public class SaveLoadManager : ManagedBehaviour
|
||||
{
|
||||
private static SaveLoadManager _instance;
|
||||
public static SaveLoadManager Instance => _instance;
|
||||
@@ -43,24 +43,49 @@ namespace Core.SaveLoad
|
||||
public event Action<string> OnLoadCompleted;
|
||||
public event Action OnParticipantStatesRestored;
|
||||
|
||||
void Awake()
|
||||
// ManagedBehaviour configuration
|
||||
public override int ManagedAwakePriority => 20; // After GameManager and SceneManagerService
|
||||
|
||||
private new void Awake()
|
||||
{
|
||||
base.Awake(); // CRITICAL: Register with LifecycleManager!
|
||||
|
||||
// Set instance immediately so it's available before OnManagedAwake() is called
|
||||
_instance = this;
|
||||
|
||||
// Initialize critical state immediately
|
||||
IsSaveDataLoaded = false;
|
||||
IsRestoringState = false;
|
||||
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
protected override void OnManagedAwake()
|
||||
{
|
||||
Logging.Debug("[SaveLoadManager] Initialized");
|
||||
|
||||
#if UNITY_EDITOR
|
||||
OnSceneLoadCompleted("RestoreInEditor");
|
||||
DiscoverInactiveSaveables("RestoreInEditor");
|
||||
#endif
|
||||
|
||||
// Load save data if save system is enabled (depends on settings from GameManager)
|
||||
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().useSaveLoadSystem)
|
||||
{
|
||||
Load();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSceneReady()
|
||||
{
|
||||
// Discover and register inactive SaveableInteractables in the newly loaded scene
|
||||
string sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
|
||||
DiscoverInactiveSaveables(sceneName);
|
||||
}
|
||||
|
||||
protected override void OnSaveRequested()
|
||||
{
|
||||
// Scene is about to unload - this is now handled by SceneManagerService
|
||||
// which calls Save() globally before scene transitions
|
||||
Logging.Debug($"[SaveLoadManager] OnSaveRequested called");
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
@@ -70,30 +95,14 @@ namespace Core.SaveLoad
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializePostBoot()
|
||||
{
|
||||
Logging.Debug("[SaveLoadManager] Post-boot initialization complete");
|
||||
|
||||
// Subscribe to scene lifecycle events if SceneManagerService is available
|
||||
if (SceneManagerService.Instance != null)
|
||||
{
|
||||
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
|
||||
SceneManagerService.Instance.SceneUnloadStarted += OnSceneUnloadStarted;
|
||||
Logging.Debug("[SaveLoadManager] Subscribed to SceneManagerService events");
|
||||
}
|
||||
}
|
||||
// ...existing code...
|
||||
|
||||
void OnDestroy()
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy(); // Important: call base to unregister from LifecycleManager
|
||||
|
||||
if (_instance == this)
|
||||
{
|
||||
// Unsubscribe from scene events
|
||||
if (SceneManagerService.Instance != null)
|
||||
{
|
||||
SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted;
|
||||
SceneManagerService.Instance.SceneUnloadStarted -= OnSceneUnloadStarted;
|
||||
}
|
||||
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
@@ -175,7 +184,11 @@ namespace Core.SaveLoad
|
||||
|
||||
#region Scene Lifecycle
|
||||
|
||||
private void OnSceneLoadCompleted(string sceneName)
|
||||
/// <summary>
|
||||
/// Discovers and registers inactive SaveableInteractables in the scene.
|
||||
/// Active SaveableInteractables register themselves via their Start() method.
|
||||
/// </summary>
|
||||
private void DiscoverInactiveSaveables(string sceneName)
|
||||
{
|
||||
Logging.Debug($"[SaveLoadManager] Scene '{sceneName}' loaded. Discovering inactive SaveableInteractables...");
|
||||
|
||||
@@ -201,13 +214,6 @@ namespace Core.SaveLoad
|
||||
Logging.Debug($"[SaveLoadManager] Discovered and registered {registeredCount} inactive SaveableInteractables");
|
||||
}
|
||||
|
||||
private void OnSceneUnloadStarted(string sceneName)
|
||||
{
|
||||
Logging.Debug($"[SaveLoadManager] Scene '{sceneName}' unloading. Note: Participants should unregister themselves.");
|
||||
|
||||
// We don't force-clear here because participants should manage their own lifecycle
|
||||
// This allows for proper cleanup in OnDestroy
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AppleHills.Core.Settings;
|
||||
using Core.Lifecycle;
|
||||
using Core.SaveLoad;
|
||||
using UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Bootstrap;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton service for loading and unloading Unity scenes asynchronously, with events for progress and completion.
|
||||
/// </summary>
|
||||
public class SceneManagerService : MonoBehaviour
|
||||
public class SceneManagerService : ManagedBehaviour
|
||||
{
|
||||
private LoadingScreenController _loadingScreen;
|
||||
private static SceneManagerService _instance;
|
||||
@@ -23,29 +24,39 @@ namespace Core
|
||||
public static SceneManagerService Instance => _instance;
|
||||
|
||||
// Events for scene lifecycle
|
||||
// NOTE: Most components should use lifecycle hooks (OnSceneReady, OnSceneUnloading)
|
||||
// instead of subscribing to these events. Events are primarily for orchestration.
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a scene starts loading. Used by loading screen orchestration.
|
||||
/// </summary>
|
||||
public event Action<string> SceneLoadStarted;
|
||||
public event Action<string, float> SceneLoadProgress;
|
||||
|
||||
/// <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;
|
||||
public event Action<string> SceneUnloadStarted;
|
||||
public event Action<string, float> SceneUnloadProgress;
|
||||
public event Action<string> SceneUnloadCompleted;
|
||||
|
||||
private readonly Dictionary<string, AsyncOperation> _activeLoads = new();
|
||||
private readonly Dictionary<string, AsyncOperation> _activeUnloads = new();
|
||||
private LogVerbosity _logVerbosity = LogVerbosity.Debug;
|
||||
private const string BootstrapSceneName = "BootstrapScene";
|
||||
|
||||
void Awake()
|
||||
// ManagedBehaviour configuration
|
||||
public override int ManagedAwakePriority => 15; // Core infrastructure, after GameManager
|
||||
|
||||
private new void Awake()
|
||||
{
|
||||
base.Awake(); // CRITICAL: Register with LifecycleManager!
|
||||
|
||||
// Set instance immediately so it's available before OnManagedAwake() is called
|
||||
_instance = this;
|
||||
// DontDestroyOnLoad(gameObject);
|
||||
|
||||
// Initialize current scene tracking immediately in Awake
|
||||
// Initialize current scene tracking - critical for scene management
|
||||
InitializeCurrentSceneTracking();
|
||||
|
||||
// Register for post-boot initialization
|
||||
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
||||
|
||||
// Ensure BootstrapScene is loaded at startup
|
||||
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
|
||||
if (!bootstrap.isLoaded)
|
||||
@@ -54,9 +65,17 @@ namespace Core
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
protected override void OnManagedAwake()
|
||||
{
|
||||
// Set up loading screen reference and events
|
||||
// This must happen in ManagedAwake because LoadingScreenController instance needs to be set first
|
||||
_loadingScreen = LoadingScreenController.Instance;
|
||||
SetupLoadingScreenEvents();
|
||||
|
||||
// Load verbosity settings
|
||||
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
|
||||
|
||||
LogDebugMessage($"SceneManagerService initialized, current scene is: {CurrentGameplayScene}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,17 +108,6 @@ namespace Core
|
||||
LogDebugMessage($"No valid active scene, defaulting to: {CurrentGameplayScene}");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializePostBoot()
|
||||
{
|
||||
// Set up loading screen reference and events after boot is complete
|
||||
_loadingScreen = LoadingScreenController.Instance;
|
||||
|
||||
// Set up loading screen event handlers if available
|
||||
SetupLoadingScreenEvents();
|
||||
|
||||
LogDebugMessage($"Post-boot initialization complete, current scene is: {CurrentGameplayScene}");
|
||||
}
|
||||
|
||||
private void SetupLoadingScreenEvents()
|
||||
{
|
||||
@@ -122,7 +130,6 @@ namespace Core
|
||||
while (!op.isDone)
|
||||
{
|
||||
progress?.Report(op.progress);
|
||||
SceneLoadProgress?.Invoke(sceneName, op.progress);
|
||||
await Task.Yield();
|
||||
}
|
||||
_activeLoads.Remove(sceneName);
|
||||
@@ -142,17 +149,15 @@ namespace Core
|
||||
Logging.Warning($"[SceneManagerService] Attempted to unload scene '{sceneName}', but it is not loaded.");
|
||||
return;
|
||||
}
|
||||
SceneUnloadStarted?.Invoke(sceneName);
|
||||
|
||||
var op = SceneManager.UnloadSceneAsync(sceneName);
|
||||
_activeUnloads[sceneName] = op;
|
||||
while (!op.isDone)
|
||||
{
|
||||
progress?.Report(op.progress);
|
||||
SceneUnloadProgress?.Invoke(sceneName, op.progress);
|
||||
await Task.Yield();
|
||||
}
|
||||
_activeUnloads.Remove(sceneName);
|
||||
SceneUnloadCompleted?.Invoke(sceneName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -230,7 +235,6 @@ namespace Core
|
||||
var op = SceneManager.UnloadSceneAsync(name);
|
||||
_activeUnloads[name] = op;
|
||||
ops.Add(op);
|
||||
SceneUnloadStarted?.Invoke(name);
|
||||
}
|
||||
|
||||
while (done < total)
|
||||
@@ -251,7 +255,6 @@ namespace Core
|
||||
foreach (var name in sceneNames)
|
||||
{
|
||||
_activeUnloads.Remove(name);
|
||||
SceneUnloadCompleted?.Invoke(name);
|
||||
}
|
||||
|
||||
// Hide loading screen after all scenes are unloaded
|
||||
@@ -293,13 +296,35 @@ namespace Core
|
||||
/// <param name="autoHideLoadingScreen">Whether to automatically hide the loading screen when complete. If false, caller must hide it manually.</param>
|
||||
public async Task SwitchSceneAsync(string newSceneName, IProgress<float> progress = null, bool autoHideLoadingScreen = true)
|
||||
{
|
||||
// Show loading screen at the start (whether using auto-hide or not)
|
||||
if (_loadingScreen != null && !_loadingScreen.IsActive)
|
||||
string oldSceneName = CurrentGameplayScene;
|
||||
|
||||
// PHASE 1: Show loading screen at the start
|
||||
// Use explicit progress provider to combine unload + load progress
|
||||
if (_loadingScreen != null)
|
||||
{
|
||||
_loadingScreen.ShowLoadingScreen();
|
||||
_loadingScreen.ShowLoadingScreen(() => GetAggregateLoadProgress());
|
||||
}
|
||||
|
||||
// Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
|
||||
// PHASE 2: Broadcast scene unloading - notify components to cleanup
|
||||
LogDebugMessage($"Broadcasting OnSceneUnloading for: {oldSceneName}");
|
||||
LifecycleManager.Instance?.BroadcastSceneUnloading(oldSceneName);
|
||||
|
||||
// PHASE 3: Broadcast save request - components save their level-specific data
|
||||
LogDebugMessage($"Broadcasting OnSaveRequested for: {oldSceneName}");
|
||||
LifecycleManager.Instance?.BroadcastSaveRequested(oldSceneName);
|
||||
|
||||
// PHASE 4: Trigger global save if save system is enabled
|
||||
if (SaveLoadManager.Instance != null)
|
||||
{
|
||||
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
|
||||
if (debugSettings.useSaveLoadSystem)
|
||||
{
|
||||
LogDebugMessage("Saving global game state");
|
||||
SaveLoadManager.Instance.Save();
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 5: Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
|
||||
var astarPaths = FindObjectsByType<AstarPath>(FindObjectsSortMode.None);
|
||||
foreach (var astar in astarPaths)
|
||||
{
|
||||
@@ -308,31 +333,41 @@ namespace Core
|
||||
else
|
||||
DestroyImmediate(astar.gameObject);
|
||||
}
|
||||
// Unload previous gameplay scene (if not BootstrapScene and not same as new)
|
||||
if (!string.IsNullOrEmpty(CurrentGameplayScene)&& CurrentGameplayScene != BootstrapSceneName)
|
||||
|
||||
// PHASE 6: Unload previous gameplay scene (Unity will call OnDestroy → OnManagedDestroy)
|
||||
if (!string.IsNullOrEmpty(oldSceneName) && oldSceneName != BootstrapSceneName)
|
||||
{
|
||||
var prevScene = SceneManager.GetSceneByName(CurrentGameplayScene);
|
||||
var prevScene = SceneManager.GetSceneByName(oldSceneName);
|
||||
if (prevScene.isLoaded)
|
||||
{
|
||||
await UnloadSceneAsync(CurrentGameplayScene);
|
||||
await UnloadSceneAsync(oldSceneName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning($"[SceneManagerService] Previous scene '{CurrentGameplayScene}' is not loaded, skipping unload.");
|
||||
Logging.Warning($"[SceneManagerService] Previous scene '{oldSceneName}' is not loaded, skipping unload.");
|
||||
}
|
||||
}
|
||||
// Ensure BootstrapScene is loaded before loading new scene
|
||||
|
||||
// PHASE 7: Ensure BootstrapScene is loaded before loading new scene
|
||||
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
|
||||
if (!bootstrap.isLoaded)
|
||||
{
|
||||
SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive);
|
||||
}
|
||||
// Load new gameplay scene
|
||||
|
||||
// PHASE 8: Load new gameplay scene
|
||||
await LoadSceneAsync(newSceneName, progress);
|
||||
// Update tracker
|
||||
CurrentGameplayScene = newSceneName;
|
||||
|
||||
// Only hide the loading screen if autoHideLoadingScreen is true
|
||||
// PHASE 9: Broadcast scene ready - components can now initialize scene-specific state
|
||||
LogDebugMessage($"Broadcasting OnSceneReady for: {newSceneName}");
|
||||
LifecycleManager.Instance?.BroadcastSceneReady(newSceneName);
|
||||
|
||||
// PHASE 10: Broadcast restore request - components restore their level-specific data
|
||||
LogDebugMessage($"Broadcasting OnRestoreRequested for: {newSceneName}");
|
||||
LifecycleManager.Instance?.BroadcastRestoreRequested(newSceneName);
|
||||
|
||||
// PHASE 11: Only hide the loading screen if autoHideLoadingScreen is true
|
||||
if (autoHideLoadingScreen && _loadingScreen != null)
|
||||
{
|
||||
_loadingScreen.HideLoadingScreen();
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using AppleHills.Core.Settings;
|
||||
using Bootstrap;
|
||||
using Input;
|
||||
using Core.Lifecycle;
|
||||
using Settings;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
public class SceneOrientationEnforcer : MonoBehaviour
|
||||
public class SceneOrientationEnforcer : ManagedBehaviour
|
||||
{
|
||||
// Singleton instance
|
||||
private static SceneOrientationEnforcer _instance;
|
||||
@@ -20,12 +18,23 @@ namespace Core
|
||||
public GameObject orientationPromptPrefab;
|
||||
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
|
||||
|
||||
void Awake()
|
||||
// ManagedBehaviour configuration
|
||||
public override int ManagedAwakePriority => 70; // Platform-specific utility
|
||||
|
||||
private new void Awake()
|
||||
{
|
||||
base.Awake(); // CRITICAL: Register with LifecycleManager!
|
||||
|
||||
// Set instance immediately so it's available before OnManagedAwake() is called
|
||||
_instance = this;
|
||||
|
||||
// Register for post-boot initialization
|
||||
BootCompletionService.RegisterInitAction(InitializePostBoot);
|
||||
// Load verbosity settings early (GameManager sets up settings in its Awake)
|
||||
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
|
||||
|
||||
LogDebugMessage("Initialized");
|
||||
|
||||
// Subscribe to sceneLoaded event immediately
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// When playing in the editor, manually invoke OnSceneLoaded for the currently active scene
|
||||
@@ -36,20 +45,11 @@ namespace Core
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Start()
|
||||
protected override void OnManagedAwake()
|
||||
{
|
||||
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
|
||||
// Initialization already done in Awake
|
||||
}
|
||||
|
||||
private void InitializePostBoot()
|
||||
{
|
||||
// Initialize any dependencies that require other services to be ready
|
||||
LogDebugMessage("Post-boot initialization complete");
|
||||
|
||||
// Subscribe to sceneLoaded event
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
// Determine desired orientation for this scene
|
||||
@@ -91,8 +91,9 @@ namespace Core
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy(); // Important: call base
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user