diff --git a/.gitignore b/.gitignore index 6229a730..b6aa0b2e 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,6 @@ InitTestScene*.unity* .vscode/launch.json .vscode/settings.json .idea/.idea.AppleHillsProduction/.idea/indexLayout.xml + +# WIP docs +/docs/wip/ diff --git a/Assets/Scripts/Bootstrap/BootSceneController.cs b/Assets/Scripts/Bootstrap/BootSceneController.cs index 6878413e..95f0ae86 100644 --- a/Assets/Scripts/Bootstrap/BootSceneController.cs +++ b/Assets/Scripts/Bootstrap/BootSceneController.cs @@ -30,8 +30,6 @@ namespace Bootstrap private float _sceneLoadingProgress = 0f; private LogVerbosity _logVerbosity = LogVerbosity.Warning; - // Run very early - need to set up loading screen before other systems initialize - public override int ManagedAwakePriority => 5; internal override void OnManagedAwake() { @@ -83,10 +81,8 @@ namespace Bootstrap Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Manual cleanup for events if (initialLoadingScreen != null) { diff --git a/Assets/Scripts/Cinematics/CinematicsManager.cs b/Assets/Scripts/Cinematics/CinematicsManager.cs index 1f39000f..553a8324 100644 --- a/Assets/Scripts/Cinematics/CinematicsManager.cs +++ b/Assets/Scripts/Cinematics/CinematicsManager.cs @@ -37,8 +37,6 @@ namespace Cinematics public PlayableDirector playableDirector; - public override int ManagedAwakePriority => 170; // Cinematic systems - internal override void OnManagedAwake() { // Set instance immediately (early initialization) diff --git a/Assets/Scripts/Cinematics/SkipCinematic.cs b/Assets/Scripts/Cinematics/SkipCinematic.cs index 0135f773..23ed0613 100644 --- a/Assets/Scripts/Cinematics/SkipCinematic.cs +++ b/Assets/Scripts/Cinematics/SkipCinematic.cs @@ -15,8 +15,6 @@ namespace Cinematics private float _holdStartTime; private bool _isHolding; private bool _skipPerformed; - - public override int ManagedAwakePriority => 180; // Cinematic UI internal override void OnManagedStart() { @@ -32,10 +30,8 @@ namespace Cinematics Logging.Debug("[SkipCinematic] Initialized"); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Clean up subscriptions UnsubscribeFromCinematicsEvents(); } diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index 686c46e1..6ccd6750 100644 --- a/Assets/Scripts/Core/GameManager.cs +++ b/Assets/Scripts/Core/GameManager.cs @@ -34,8 +34,6 @@ namespace Core public event Action OnGamePaused; public event Action OnGameResumed; - // ManagedBehaviour configuration - public override int ManagedAwakePriority => 10; // Core infrastructure - runs early internal override void OnManagedAwake() { diff --git a/Assets/Scripts/Core/ItemManager.cs b/Assets/Scripts/Core/ItemManager.cs index 8c6e269b..0979df76 100644 --- a/Assets/Scripts/Core/ItemManager.cs +++ b/Assets/Scripts/Core/ItemManager.cs @@ -47,8 +47,6 @@ namespace Core // Broadcasts when any two items are successfully combined // Args: first item data, second item data, result item data public event Action OnItemsCombined; - - public override int ManagedAwakePriority => 75; // Item registry internal override void OnManagedAwake() { @@ -67,10 +65,8 @@ namespace Core ClearAllRegistrations(); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Ensure we clean up any subscriptions from registered items when the manager is destroyed ClearAllRegistrations(); } diff --git a/Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs b/Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs index 54c3b391..09517170 100644 --- a/Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs +++ b/Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs @@ -6,12 +6,19 @@ /// public enum LifecyclePhase { + /// + /// Called immediately during registration (during Awake). + /// Use for early initialization such as setting singleton instances. + /// NOT ordered - fires whenever Unity calls this component's Awake(). + /// + ManagedAwake, + /// /// 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. /// - ManagedAwake, + ManagedStart, /// /// Called before a scene is unloaded. diff --git a/Assets/Scripts/Core/Lifecycle/LifecycleManager.cs b/Assets/Scripts/Core/Lifecycle/LifecycleManager.cs index eb2ba3c5..87788e1c 100644 --- a/Assets/Scripts/Core/Lifecycle/LifecycleManager.cs +++ b/Assets/Scripts/Core/Lifecycle/LifecycleManager.cs @@ -59,11 +59,11 @@ namespace Core.Lifecycle #region State Flags - private bool isBootComplete = false; + private bool isBootComplete; private string currentSceneReady = ""; // Scene loading state tracking - private bool isLoadingScene = false; + private bool isLoadingScene; private string sceneBeingLoaded = ""; private List pendingSceneComponents = new List(); @@ -120,17 +120,13 @@ namespace Core.Lifecycle // Track which scene this component belongs to componentScenes[component] = sceneName; - // ALWAYS add to managedAwakeList - this is the master list used for save/load - 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); - - // Call OnManagedAwake immediately after registration (early initialization hook) + // Add to all lifecycle lists (order of registration determines execution order) + managedAwakeList.Add(component); + sceneUnloadingList.Add(component); + sceneReadyList.Add(component); + saveRequestedList.Add(component); + restoreRequestedList.Add(component); + destroyList.Add(component); try { component.OnManagedAwake(); @@ -146,7 +142,7 @@ namespace Core.Lifecycle // Check if we're currently loading a scene if (isLoadingScene && sceneName == sceneBeingLoaded) { - // Batch this component - will be processed in priority order when scene load completes + // Batch this component - will be processed when scene load completes pendingSceneComponents.Add(component); LogDebug($"Batched component for scene load: {component.gameObject.name} (Scene: {sceneName})"); } @@ -282,10 +278,7 @@ namespace Core.Lifecycle LogDebug($"Processing {pendingSceneComponents.Count} batched components for scene: {sceneBeingLoaded}"); - // Sort by ManagedAwake priority (lower values first) - pendingSceneComponents.Sort((a, b) => a.ManagedAwakePriority.CompareTo(b.ManagedAwakePriority)); - - // Call OnManagedStart in priority order + // Call OnManagedStart in registration order foreach (var component in pendingSceneComponents) { if (component == null) continue; @@ -294,7 +287,7 @@ namespace Core.Lifecycle { component.OnManagedStart(); HandleAutoRegistrations(component); - LogDebug($"Processed batched component: {component.gameObject.name} (Priority: {component.ManagedAwakePriority})"); + LogDebug($"Processed batched component: {component.gameObject.name}"); } catch (Exception ex) { @@ -309,7 +302,7 @@ namespace Core.Lifecycle } /// - /// Broadcast OnSceneUnloading to components in the specified scene (reverse priority order). + /// Broadcast OnSceneUnloading to components in the specified scene. /// public void BroadcastSceneUnloading(string sceneName) { @@ -336,8 +329,8 @@ namespace Core.Lifecycle } /// - /// Broadcast OnSceneReady to components in the specified scene (priority order). - /// If scene loading mode is active, processes batched components first. + /// Broadcast OnSceneReady to components in the specified scene. + /// Processes batched components first, then calls OnSceneReady on all components in that scene. /// public void BroadcastSceneReady(string sceneName) { @@ -621,42 +614,6 @@ namespace Core.Lifecycle #endregion #region Helper Methods - - /// - /// Insert component into list maintaining sorted order by priority. - /// Uses binary search for efficient insertion. - /// - private void InsertSorted(List 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); - } - - /// - /// Get the priority value for a component based on which list it's in. - /// - private int GetPriorityForList(ManagedBehaviour component, List 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; - } /// /// Log debug message if debug logging is enabled. diff --git a/Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs b/Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs index 0fe6d79e..e857a359 100644 --- a/Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs +++ b/Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs @@ -8,46 +8,6 @@ namespace Core.Lifecycle /// public abstract class ManagedBehaviour : MonoBehaviour { - #region Priority Properties - - /// - /// Priority for OnManagedStart (lower values execute first). - /// Default: 100 - /// - public virtual int ManagedAwakePriority => 100; - - /// - /// Priority for OnSceneUnloading (executed in reverse: higher values execute first). - /// Default: 100 - /// - public virtual int SceneUnloadingPriority => 100; - - /// - /// Priority for OnSceneReady (lower values execute first). - /// Default: 100 - /// - public virtual int SceneReadyPriority => 100; - - /// - /// Priority for OnSaveRequested (executed in reverse: higher values execute first). - /// Default: 100 - /// - public virtual int SavePriority => 100; - - /// - /// Priority for OnRestoreRequested (lower values execute first). - /// Default: 100 - /// - public virtual int RestorePriority => 100; - - /// - /// Priority for OnManagedDestroy (executed in reverse: higher values execute first). - /// Default: 100 - /// - public virtual int DestroyPriority => 100; - - #endregion - #region Configuration Properties /// @@ -67,14 +27,19 @@ namespace Core.Lifecycle /// Unique identifier for this component in the save system. /// Default: "SceneName/GameObjectName/ComponentType" /// Override ONLY for special cases (e.g., singletons like "PlayerController", or custom IDs). + /// Cached on first access to avoid runtime allocation. /// public virtual string SaveId { get { - string sceneName = gameObject.scene.IsValid() ? gameObject.scene.name : "UnknownScene"; - string componentType = GetType().Name; - return $"{sceneName}/{gameObject.name}/{componentType}"; + if (_cachedSaveId == null) + { + string sceneName = gameObject.scene.IsValid() ? gameObject.scene.name : "UnknownScene"; + string componentType = GetType().Name; + _cachedSaveId = $"{sceneName}/{gameObject.name}/{componentType}"; + } + return _cachedSaveId; } } @@ -83,6 +48,7 @@ namespace Core.Lifecycle #region Private Fields private bool _isRegistered; + private string _cachedSaveId; #endregion @@ -107,13 +73,16 @@ namespace Core.Lifecycle /// /// Unity OnDestroy - automatically unregisters and cleans up. - /// IMPORTANT: Derived classes that override OnDestroy MUST call base.OnDestroy() + /// SEALED: Cannot be overridden. Use OnManagedDestroy() for custom cleanup logic. /// - protected virtual void OnDestroy() + private void OnDestroy() { if (!_isRegistered) return; + // Call managed destroy hook + OnManagedDestroy(); + // Unregister from LifecycleManager if (LifecycleManager.Instance != null) { @@ -149,7 +118,7 @@ namespace Core.Lifecycle /// /// Called once per component after bootstrap completes. /// GUARANTEE: Bootstrap resources are available, all managers are initialized. - /// For boot-time components: Called during LifecycleManager.BroadcastManagedStart (priority ordered). + /// For boot-time components: Called during LifecycleManager.BroadcastManagedStart (registration order). /// For late-registered components: Called immediately upon registration (bootstrap already complete). /// Use for initialization that depends on other systems. /// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes. @@ -161,7 +130,6 @@ 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. /// @@ -172,7 +140,6 @@ 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. /// @@ -312,7 +279,6 @@ namespace Core.Lifecycle /// /// 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. /// Internal visibility allows LifecycleManager to call directly. Override in derived classes. diff --git a/Assets/Scripts/Core/QuickAccess.cs b/Assets/Scripts/Core/QuickAccess.cs index f086e3b6..f86d4b05 100644 --- a/Assets/Scripts/Core/QuickAccess.cs +++ b/Assets/Scripts/Core/QuickAccess.cs @@ -24,9 +24,6 @@ namespace AppleHills.Core #endregion Singleton Setup - // Very early initialization - QuickAccess should be available immediately - public override int ManagedAwakePriority => 5; - #region Manager Instances // Core Managers diff --git a/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs b/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs index d43c736e..a5563ba0 100644 --- a/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs +++ b/Assets/Scripts/Core/SaveLoad/SaveLoadManager.cs @@ -43,8 +43,6 @@ namespace Core.SaveLoad public event Action OnLoadCompleted; public event Action OnParticipantStatesRestored; - // ManagedBehaviour configuration - public override int ManagedAwakePriority => 20; // After GameManager and SceneManagerService internal override void OnManagedAwake() { @@ -95,10 +93,8 @@ namespace Core.SaveLoad // ...existing code... - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); // Important: call base to unregister from LifecycleManager - if (_instance == this) { _instance = null; diff --git a/Assets/Scripts/Core/SaveablePlayableDirector.cs b/Assets/Scripts/Core/SaveablePlayableDirector.cs index 13bf74c3..8d87b748 100644 --- a/Assets/Scripts/Core/SaveablePlayableDirector.cs +++ b/Assets/Scripts/Core/SaveablePlayableDirector.cs @@ -42,10 +42,8 @@ namespace Core } } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - if (_director != null) { _director.stopped -= OnDirectorStopped; diff --git a/Assets/Scripts/Core/SceneManagerService.cs b/Assets/Scripts/Core/SceneManagerService.cs index 028f67bb..ecdd9377 100644 --- a/Assets/Scripts/Core/SceneManagerService.cs +++ b/Assets/Scripts/Core/SceneManagerService.cs @@ -44,8 +44,6 @@ namespace Core private LogVerbosity _logVerbosity = LogVerbosity.Debug; private const string BootstrapSceneName = "BootstrapScene"; - // ManagedBehaviour configuration - public override int ManagedAwakePriority => 15; // Core infrastructure, after GameManager internal override void OnManagedAwake() { @@ -369,7 +367,7 @@ namespace Core await LoadSceneAsync(newSceneName, progress); CurrentGameplayScene = newSceneName; - // PHASE 10: Broadcast scene ready - processes batched components in priority order, then calls OnSceneReady + // PHASE 10: Broadcast scene ready - processes batched components, then calls OnSceneReady Logging.Debug($"Broadcasting OnSceneReady for: {newSceneName}"); LifecycleManager.Instance?.BroadcastSceneReady(newSceneName); diff --git a/Assets/Scripts/Core/SceneOrientationEnforcer.cs b/Assets/Scripts/Core/SceneOrientationEnforcer.cs index cebea931..f3c32079 100644 --- a/Assets/Scripts/Core/SceneOrientationEnforcer.cs +++ b/Assets/Scripts/Core/SceneOrientationEnforcer.cs @@ -18,9 +18,6 @@ namespace Core public GameObject orientationPromptPrefab; private LogVerbosity _logVerbosity = LogVerbosity.Warning; - // ManagedBehaviour configuration - public override int ManagedAwakePriority => 70; // Platform-specific utility - internal override void OnManagedAwake() { // Set instance immediately (early initialization) @@ -103,15 +100,13 @@ namespace Core } } - protected override void OnDestroy() + internal override void OnManagedDestroy() { // Unsubscribe from events to prevent memory leaks if (SceneManagerService.Instance != null) { SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted; } - - base.OnDestroy(); // Important: call base } /// diff --git a/Assets/Scripts/Data/CardSystem/CardSystemManager.cs b/Assets/Scripts/Data/CardSystem/CardSystemManager.cs index 01ecec84..df7993d7 100644 --- a/Assets/Scripts/Data/CardSystem/CardSystemManager.cs +++ b/Assets/Scripts/Data/CardSystem/CardSystemManager.cs @@ -43,8 +43,6 @@ namespace Data.CardSystem public event Action OnBoosterCountChanged; public event Action OnPendingCardAdded; public event Action OnCardPlacedInAlbum; - - public override int ManagedAwakePriority => 60; // Data systems internal override void OnManagedAwake() { diff --git a/Assets/Scripts/Dialogue/DialogueComponent.cs b/Assets/Scripts/Dialogue/DialogueComponent.cs index c84fe6fb..b4102b79 100644 --- a/Assets/Scripts/Dialogue/DialogueComponent.cs +++ b/Assets/Scripts/Dialogue/DialogueComponent.cs @@ -32,9 +32,6 @@ namespace Dialogue public bool IsCompleted { get; private set; } public string CurrentSpeakerName => dialogueGraph?.speakerName; - - public override int ManagedAwakePriority => 150; // Dialogue systems - internal override void OnManagedStart() { // Get required components @@ -184,10 +181,8 @@ namespace Dialogue return null; } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Unregister from events if (PuzzleManager.Instance != null) PuzzleManager.Instance.OnStepCompleted -= OnAnyPuzzleStepCompleted; diff --git a/Assets/Scripts/Input/InputManager.cs b/Assets/Scripts/Input/InputManager.cs index dd830e99..ec1f7903 100644 --- a/Assets/Scripts/Input/InputManager.cs +++ b/Assets/Scripts/Input/InputManager.cs @@ -49,8 +49,6 @@ namespace Input private ITouchInputConsumer defaultConsumer; private bool isHoldActive; private LogVerbosity _logVerbosity = LogVerbosity.Warning; - - public override int ManagedAwakePriority => 25; // Input infrastructure internal override void OnManagedAwake() { @@ -106,7 +104,7 @@ namespace Input SwitchInputOnSceneLoaded(sceneName); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { // Unsubscribe from SceneManagerService events if (SceneManagerService.Instance != null) @@ -114,7 +112,6 @@ namespace Input SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted; } - base.OnDestroy(); // Input action cleanup happens automatically } diff --git a/Assets/Scripts/Input/PlayerTouchController.cs b/Assets/Scripts/Input/PlayerTouchController.cs index 1f00618d..39950db1 100644 --- a/Assets/Scripts/Input/PlayerTouchController.cs +++ b/Assets/Scripts/Input/PlayerTouchController.cs @@ -70,7 +70,6 @@ namespace Input public override bool AutoRegisterForSave => true; // Scene-specific SaveId - each level has its own player state public override string SaveId => $"{gameObject.scene.name}/PlayerController"; - public override int ManagedAwakePriority => 100; // Player controller internal override void OnManagedStart() { diff --git a/Assets/Scripts/Interactions/Interactable.cs b/Assets/Scripts/Interactions/Interactable.cs index 48eab9f7..d97f7c3a 100644 --- a/Assets/Scripts/Interactions/Interactable.cs +++ b/Assets/Scripts/Interactions/Interactable.cs @@ -41,9 +41,6 @@ namespace Interactions // Action component system private List _registeredActions = new List(); - // ManagedBehaviour configuration - public override int ManagedAwakePriority => 100; // Gameplay base classes - /// /// Register an action component with this interactable diff --git a/Assets/Scripts/Interactions/ItemSlot.cs b/Assets/Scripts/Interactions/ItemSlot.cs index 2a12a251..659a601a 100644 --- a/Assets/Scripts/Interactions/ItemSlot.cs +++ b/Assets/Scripts/Interactions/ItemSlot.cs @@ -287,10 +287,8 @@ namespace Interactions ItemManager.Instance?.RegisterItemSlot(this); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Unregister from slot manager ItemManager.Instance?.UnregisterItemSlot(this); } diff --git a/Assets/Scripts/Interactions/Pickup.cs b/Assets/Scripts/Interactions/Pickup.cs index 06abf497..4d0d7316 100644 --- a/Assets/Scripts/Interactions/Pickup.cs +++ b/Assets/Scripts/Interactions/Pickup.cs @@ -50,10 +50,8 @@ namespace Interactions ItemManager.Instance?.RegisterPickup(this); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Unregister from ItemManager ItemManager.Instance?.UnregisterPickup(this); } diff --git a/Assets/Scripts/Levels/MinigameSwitch.cs b/Assets/Scripts/Levels/MinigameSwitch.cs index ed5b1754..5f1af48c 100644 --- a/Assets/Scripts/Levels/MinigameSwitch.cs +++ b/Assets/Scripts/Levels/MinigameSwitch.cs @@ -80,10 +80,8 @@ namespace Levels } } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - if (PuzzleManager.Instance != null) { PuzzleManager.Instance.OnAllPuzzlesComplete -= HandleAllPuzzlesComplete; diff --git a/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs b/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs index f86ca9d9..7357f4fb 100644 --- a/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs +++ b/Assets/Scripts/Minigames/DivingForPictures/DivingGameManager.cs @@ -103,7 +103,6 @@ namespace Minigames.DivingForPictures public static DivingGameManager Instance => _instance; - public override int ManagedAwakePriority => 190; public override bool AutoRegisterPausable => true; // Automatic GameManager registration internal override void OnManagedAwake() @@ -161,10 +160,8 @@ namespace Minigames.DivingForPictures } } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); // Handles auto-unregister from GameManager - // Unsubscribe from events when the manager is destroyed PlayerCollisionBehavior.OnDamageTaken -= OnPlayerDamageTaken; OnMonsterSpawned -= DoMonsterSpawned; diff --git a/Assets/Scripts/Movement/FollowerController.cs b/Assets/Scripts/Movement/FollowerController.cs index 6a40abfe..9378d835 100644 --- a/Assets/Scripts/Movement/FollowerController.cs +++ b/Assets/Scripts/Movement/FollowerController.cs @@ -106,8 +106,6 @@ public class FollowerController : ManagedBehaviour private bool _hasRestoredHeldItem; // Track if held item restoration completed private string _expectedHeldItemSaveId; // Expected saveId during restoration - public override int ManagedAwakePriority => 110; // Follower after player - internal override void OnManagedStart() { _aiPath = GetComponent(); diff --git a/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs b/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs index db50ac6e..60828fde 100644 --- a/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs +++ b/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs @@ -83,10 +83,8 @@ namespace PuzzleS } } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - if (PuzzleManager.Instance != null && stepData != null) { PuzzleManager.Instance.UnregisterStepBehaviour(this); diff --git a/Assets/Scripts/PuzzleS/PuzzleManager.cs b/Assets/Scripts/PuzzleS/PuzzleManager.cs index 44f6dc4f..981abe71 100644 --- a/Assets/Scripts/PuzzleS/PuzzleManager.cs +++ b/Assets/Scripts/PuzzleS/PuzzleManager.cs @@ -93,8 +93,6 @@ namespace PuzzleS // Track pending unlocks for steps that were unlocked before their behavior registered private HashSet _pendingUnlocks = new HashSet(); - - public override int ManagedAwakePriority => 80; // Puzzle systems internal override void OnManagedAwake() { @@ -138,10 +136,8 @@ namespace PuzzleS LoadPuzzlesForScene(sceneName); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Unsubscribe from SceneManagerService events if (SceneManagerService.Instance != null) { diff --git a/Assets/Scripts/Sound/AudioManager.cs b/Assets/Scripts/Sound/AudioManager.cs index 5bc74244..890a49f4 100644 --- a/Assets/Scripts/Sound/AudioManager.cs +++ b/Assets/Scripts/Sound/AudioManager.cs @@ -40,8 +40,6 @@ public class AudioManager : ManagedBehaviour, IPausable /// public static AudioManager Instance => _instance; - // ManagedBehaviour configuration - public override int ManagedAwakePriority => 30; // Audio infrastructure public override bool AutoRegisterPausable => true; // Auto-register as IPausable internal override void OnManagedAwake() diff --git a/Assets/Scripts/UI/AppSwitcher.cs b/Assets/Scripts/UI/AppSwitcher.cs index 4ad238b3..7497a97f 100644 --- a/Assets/Scripts/UI/AppSwitcher.cs +++ b/Assets/Scripts/UI/AppSwitcher.cs @@ -93,10 +93,8 @@ public class AppSwitcher : UIPage ); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Clean up tweens slideInTween?.Stop(); slideOutTween?.Stop(); diff --git a/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs b/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs index e68e727b..628d7615 100644 --- a/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs +++ b/Assets/Scripts/UI/CardSystem/AlbumViewPage.cs @@ -149,7 +149,7 @@ namespace UI.CardSystem } } - protected override void OnDestroy() + internal override void OnManagedDestroy() { // Unsubscribe from CardSystemManager if (CardSystemManager.Instance != null) @@ -181,9 +181,6 @@ namespace UI.CardSystem // Clean up active cards CleanupActiveCards(); - - // Call base implementation - base.OnDestroy(); } private void OnExitButtonClicked() diff --git a/Assets/Scripts/UI/CardSystem/BoosterNotificationDot.cs b/Assets/Scripts/UI/CardSystem/BoosterNotificationDot.cs index 564363c8..9eb3fe11 100644 --- a/Assets/Scripts/UI/CardSystem/BoosterNotificationDot.cs +++ b/Assets/Scripts/UI/CardSystem/BoosterNotificationDot.cs @@ -70,16 +70,13 @@ namespace UI.CardSystem } } - protected override void OnDestroy() + internal override void OnManagedDestroy() { // Unsubscribe from CardSystemManager events to prevent memory leaks if (CardSystemManager.Instance != null) { CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged; } - - // Call base implementation - base.OnDestroy(); } /// diff --git a/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs b/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs index 5998f090..6cc1af0f 100644 --- a/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs +++ b/Assets/Scripts/UI/CardSystem/BoosterOpeningPage.cs @@ -76,10 +76,8 @@ namespace UI.CardSystem gameObject.SetActive(false); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Unsubscribe from dismiss button if (_dismissButton != null) { diff --git a/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackVisual.cs b/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackVisual.cs index f97cbe1f..4783140d 100644 --- a/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackVisual.cs +++ b/Assets/Scripts/UI/CardSystem/DragDrop/BoosterPackVisual.cs @@ -307,7 +307,7 @@ namespace UI.CardSystem.DragDrop } #endregion - + protected override void OnDestroy() { base.OnDestroy(); diff --git a/Assets/Scripts/UI/CardSystem/DragDrop/CardDraggableVisual.cs b/Assets/Scripts/UI/CardSystem/DragDrop/CardDraggableVisual.cs index b9938668..50b3e3fb 100644 --- a/Assets/Scripts/UI/CardSystem/DragDrop/CardDraggableVisual.cs +++ b/Assets/Scripts/UI/CardSystem/DragDrop/CardDraggableVisual.cs @@ -106,7 +106,7 @@ namespace UI.CardSystem.DragDrop base.OnDragEndedVisual(); // Card-specific visual effects when dragging ends } - + protected override void OnDestroy() { base.OnDestroy(); diff --git a/Assets/Scripts/UI/Core/UIPage.cs b/Assets/Scripts/UI/Core/UIPage.cs index 853eac06..cde1a54e 100644 --- a/Assets/Scripts/UI/Core/UIPage.cs +++ b/Assets/Scripts/UI/Core/UIPage.cs @@ -15,9 +15,6 @@ namespace UI.Core [Header("Page Settings")] public string PageName; - // UI pages load after UI infrastructure (UIPageController is priority 50) - public override int ManagedAwakePriority => 200; - // Events using System.Action instead of UnityEvents public event Action OnTransitionInStarted; public event Action OnTransitionInCompleted; diff --git a/Assets/Scripts/UI/Core/UIPageController.cs b/Assets/Scripts/UI/Core/UIPageController.cs index 43b8e40c..a2b41ea3 100644 --- a/Assets/Scripts/UI/Core/UIPageController.cs +++ b/Assets/Scripts/UI/Core/UIPageController.cs @@ -37,8 +37,6 @@ namespace UI.Core private PlayerInput _playerInput; private InputAction _cancelAction; - public override int ManagedAwakePriority => 50; // UI infrastructure - internal override void OnManagedAwake() { // Set instance immediately (early initialization) @@ -50,10 +48,8 @@ namespace UI.Core Logging.Debug("[UIPageController] Initialized"); } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Clean up cached instances foreach (var cachedPage in _prefabInstanceCache.Values) { diff --git a/Assets/Scripts/UI/LoadingScreenController.cs b/Assets/Scripts/UI/LoadingScreenController.cs index 950a5fc9..f5e13fab 100644 --- a/Assets/Scripts/UI/LoadingScreenController.cs +++ b/Assets/Scripts/UI/LoadingScreenController.cs @@ -52,9 +52,6 @@ namespace UI /// Singleton instance of the LoadingScreenController. No longer creates an instance if one doesn't exist. /// public static LoadingScreenController Instance => _instance; - - // ManagedBehaviour configuration - public override int ManagedAwakePriority => 45; // UI infrastructure, before UIPageController internal override void OnManagedAwake() { diff --git a/Assets/Scripts/UI/PauseMenu.cs b/Assets/Scripts/UI/PauseMenu.cs index c69910c4..9e2e30b5 100644 --- a/Assets/Scripts/UI/PauseMenu.cs +++ b/Assets/Scripts/UI/PauseMenu.cs @@ -27,9 +27,6 @@ namespace UI [SerializeField] private UnityEngine.UI.Button devOptionsButton; [SerializeField] private GameObject mainOptionsContainer; [SerializeField] private GameObject devOptionsContainer; - - // After UIPageController (50) - public override int ManagedAwakePriority => 55; internal override void OnManagedAwake() { @@ -76,10 +73,8 @@ namespace UI // This only fires once for DontDestroyOnLoad objects, so we handle scene loads in OnManagedAwake } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Unsubscribe when destroyed if (SceneManagerService.Instance != null) { diff --git a/Assets/Scripts/UI/PlayerHudManager.cs b/Assets/Scripts/UI/PlayerHudManager.cs index 06bde44f..b363d4a0 100644 --- a/Assets/Scripts/UI/PlayerHudManager.cs +++ b/Assets/Scripts/UI/PlayerHudManager.cs @@ -172,10 +172,8 @@ namespace UI } } - protected override void OnDestroy() + internal override void OnManagedDestroy() { - base.OnDestroy(); - // Unsubscribe from events if (_uiPageController != null) { diff --git a/Assets/Scripts/UI/Tutorial/DivingTutorial.cs b/Assets/Scripts/UI/Tutorial/DivingTutorial.cs index eb9fb8b8..42a3481a 100644 --- a/Assets/Scripts/UI/Tutorial/DivingTutorial.cs +++ b/Assets/Scripts/UI/Tutorial/DivingTutorial.cs @@ -30,7 +30,6 @@ namespace UI.Tutorial private bool _canAcceptInput; private Coroutine _waitLoopCoroutine; - public override int ManagedAwakePriority => 200; // Tutorial runs late, after other systems internal override void OnManagedStart() { diff --git a/docs/managed_behavior/architecture_overview.md b/docs/managed_behavior/architecture_overview.md new file mode 100644 index 00000000..9030e86c --- /dev/null +++ b/docs/managed_behavior/architecture_overview.md @@ -0,0 +1,93 @@ +# ManagedBehaviour System - Architecture Overview + +**Version:** 2.0
+**Updated:** 11.11.2025 + + +--- + +## What is the ManagedBehaviour System? + +Lifecycle orchestration framework that provides guaranteed execution order and deterministic lifecycle management for Unity components. + +### Problems Solved + +We've had quite a few things shoe-stringed together in various ways and dependant on references to each other. +Due to undefined initialization order - null references during access to yet uninitialized resources was becoming a problem. + +### What You Get + +- **Guaranteed Initialization Order** - Managers ready before components that depend on them +- **Deterministic Lifecycle Hooks** - Predictable callbacks at key moments +- **Automatic Registration** - No boilerplate for wiring up systems +- **Scene Lifecycle Events** - Built-in hooks for scene load/unload +- **Save/Load Coordination** - Centralized collection of save data +- **Bootstrap Integration** - Components know when bootstrap completes + +--- + +## Architecture Principles + +### 1. Centralized Orchestration + +Single `LifecycleManager` singleton coordinates all lifecycle events. Components auto-register and receive callbacks at appropriate times. + +### 2. Sealed Framework Methods + +`Awake()` and `OnDestroy()` are sealed. Use `OnManagedAwake()` and `OnManagedDestroy()` instead. Prevents forgetting to call `base.Awake()`. + +### 3. Two-Phase Initialization + +- **Early (`OnManagedAwake()`)**: During Unity's Awake, before bootstrap. Use for singleton setup. +- **Late (`OnManagedStart()`)**: After bootstrap completes. All managers guaranteed ready. + +### 4. Registration Order Execution + +Execution follows Unity's natural Awake order. No priority numbers to manage. + +### 5. Automatic Cleanup + +Framework handles unregistration automatically. Override `OnManagedDestroy()` only if you need custom cleanup. + +--- + +## Lifecycle Flow Diagrams + +### Boot Sequence + +![Boot Sequence](../media/boot_sequence.png) + +### Scene Transition Flow + +![Scene Transition Flow](../media/Scene_transition_flow.png) + +### Component Lifecycle (Individual Component) + +![Component Lifecycle](../media/component_lifecycle.png) + +--- + +## Class Diagram + +![Class Diagram](../media/class_diagram.png) + +--- +## Key Guarantees + +### Guaranteed + +1. **Bootstrap Completion** - `OnManagedStart()` always fires after bootstrap completes +2. **Manager Availability** - All manager singletons exist when `OnManagedStart()` is called +3. **Scene Lifecycle** - `OnSceneReady()` fires after scene load, `OnSceneUnloading()` before unload +4. **Automatic Registration** - Can't forget to register (Awake is sealed) +5. **Automatic Cleanup** - Can't forget to unregister (OnDestroy is sealed) +6. **Save/Load Coordination** - All save participants called in one pass + +### Not Guaranteed + +1. **Initialization Order Between Components** - `OnManagedAwake()` follows Unity's unpredictable Awake order +2. **Thread Safety** - All methods must run on main thread +3. **Performance** - Broadcasting to 1000+ components may have overhead +4. **SaveId Uniqueness** - Developer responsible for unique SaveIds + + diff --git a/docs/managed_behavior/technical_reference.md b/docs/managed_behavior/technical_reference.md new file mode 100644 index 00000000..aa56aec7 --- /dev/null +++ b/docs/managed_behavior/technical_reference.md @@ -0,0 +1,377 @@ +# ManagedBehaviour System - Technical Reference + +**Version:** 2.0
+**Updated:** 11.11.2025 + +--- + +## Overview + +The ManagedBehaviour system provides a deterministic, ordered lifecycle management framework for Unity MonoBehaviours. This document provides complete technical documentation of all classes, methods, and APIs. + +--- + +## Core Classes + +### ManagedBehaviour (Abstract Base Class) + +**Namespace:** `Core.Lifecycle` +**Inherits:** `MonoBehaviour` +**Location:** `Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs` → [View Source](../../Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs) + +Abstract base class that all managed components must inherit from. Provides automatic registration with LifecycleManager and ordered lifecycle callbacks. + +#### Lifecycle Hook Methods + +Override these `internal virtual` methods to customize component behavior at different lifecycle stages. Called automatically by `LifecycleManager`. + +
+Click to see more details + +##### `OnManagedAwake()` +```csharp +internal virtual void OnManagedAwake() +``` +**Called:** During registration (within Unity's Awake phase) +**Execution Order:** Natural Unity script execution order (not guaranteed between components) +**Use For:** +- Setting singleton instances (`_instance = this`) +- Early GetComponent calls +- One-time initialization that doesn't depend on other systems + +**Timing Guarantees:** +- ✅ GameObject and component exist +- ✅ Scene is loaded +- ❌ Other components may not be initialized yet +- ❌ Bootstrap may not be complete + +##### `OnManagedStart()` +```csharp +internal virtual void OnManagedStart() +``` +**Called:** After bootstrap completes (for boot components) or immediately after registration (for late-registered components) +**Execution Order:** Registration order +**Use For:** +- Initialization that depends on other managers +- Accessing singleton instances safely +- Setting up cross-system dependencies + +**Timing Guarantees:** +- ✅ All managers are initialized +- ✅ Bootstrap resources are available +- ✅ Safe to access `GameManager.Instance`, `AudioManager.Instance`, etc. + +##### `OnSceneUnloading()` +```csharp +internal virtual void OnSceneUnloading() +``` +**Called:** Before scene unload +**Execution Order:** Registration order +**Use For:** +- Scene-specific cleanup +- Saving temporary scene state + +**Timing Guarantees:** +- ✅ Scene is still loaded +- ✅ Other components in scene still exist + +##### `OnSceneReady()` +```csharp +internal virtual void OnSceneReady() +``` +**Called:** After scene load completes +**Execution Order:** Registration order +**Use For:** +- Scene-specific initialization +- Finding scene objects +- Setting up scene-specific state + +**Timing Guarantees:** +- ✅ All scene GameObjects are loaded +- ✅ Batched components have received `OnManagedStart()` + +##### `OnSceneSaveRequested()` +```csharp +internal virtual string OnSceneSaveRequested() +``` +**Called:** Before scene unload during scene transitions +**Returns:** Serialized scene-specific data (JSON string), or `null` if nothing to save +**Use For:** +- Level progress +- Object positions +- Puzzle states +- Temporary scene data + +**⚠️ Important:** Must return synchronously. + +##### `OnSceneRestoreRequested(string serializedData)` +```csharp +internal virtual void OnSceneRestoreRequested(string serializedData) +``` +**Called:** After scene load, during `OnSceneReady` phase +**Parameters:** +- `serializedData` - Previously saved data from `OnSceneSaveRequested()` + +**Use For:** +- Restoring level progress +- Setting object positions +- Restoring puzzle states + +**⚠️ Important:** Must execute synchronously. Do not use coroutines or async/await. + +##### `OnSceneRestoreCompleted()` +```csharp +internal virtual void OnSceneRestoreCompleted() +``` +**Called:** After all `OnSceneRestoreRequested()` calls complete +**Timing:** Always called after scene load, whether save data exists or not +**Use For:** +- Post-restore initialization +- First-time initialization (when no save data exists) +- Triggering events after state is restored + +**Common Pattern:** +```csharp +internal override void OnSceneRestoreCompleted() +{ + if (!_hasPlayed) // Check if this is first time + { + PlayIntroAudio(); + _hasPlayed = true; + } +} +``` + +##### `OnGlobalSaveRequested()` +```csharp +internal virtual string OnGlobalSaveRequested() +``` +**Called:** Once before save file is written to disk +**Returns:** Serialized global persistent data (JSON string), or `null` +**Use For:** +- Player inventory +- Unlocked features +- Card collections +- Persistent progression + +**Timing:** Called once per game save (not per scene transition) + +##### `OnGlobalRestoreRequested(string serializedData)` +```csharp +internal virtual void OnGlobalRestoreRequested(string serializedData) +``` +**Called:** Once on game boot after save file is read +**Parameters:** +- `serializedData` - Previously saved data from `OnGlobalSaveRequested()` + +**Use For:** +- Restoring player inventory +- Restoring unlocked features +- Restoring persistent progression + +**Timing:** Called once on boot, not during scene transitions + +##### `OnGlobalLoadCompleted()` +```csharp +internal virtual void OnGlobalLoadCompleted() +``` +**Called:** Once on game boot after all global restore operations complete +**Use For:** +- Triggering UI updates after load +- Broadcasting load events +- Post-load initialization + +##### `OnGlobalSaveStarted()` +```csharp +internal virtual void OnGlobalSaveStarted() +``` +**Called:** Once before save file is written +**Use For:** +- Final validation before save +- Cleanup operations before save + +##### `OnManagedDestroy()` +```csharp +internal virtual void OnManagedDestroy() +``` +**Called:** During `OnDestroy`, before unregistration +**Execution Order:** Registration order +**Use For:** +- Unsubscribing from events +- Releasing resources +- Custom cleanup logic + +**Note:** Most cleanup is automatic (auto-registrations are handled by framework). +
+ +#### Configuration Properties + +Virtual properties that control automatic behaviors like pause registration and save system participation. + +
+Click to see more details + +##### `AutoRegisterPausable` +```csharp +public virtual bool AutoRegisterPausable => false; +``` +**Type:** `bool` +**Default:** `false` +**Description:** If true and component implements `IPausable`, automatically registers with `GameManager.Instance` during initialization. Automatic unregistration occurs on destruction. + +##### `AutoRegisterForSave` +```csharp +public virtual bool AutoRegisterForSave => false; +``` +**Type:** `bool` +**Default:** `false` +**Description:** If true, component participates in the save/load system. Should override `OnSceneSaveRequested()` and `OnSceneRestoreRequested()` or global equivalents. + +##### `SaveId` +```csharp +public virtual string SaveId { get; } +``` +**Type:** `string` +**Default:** `"{SceneName}/{GameObjectName}/{ComponentType}"` +**Description:** Unique identifier for this component in the save system. Cached on first access for performance. Override for singletons (e.g., `"PlayerController"`) or custom IDs. + +**⚠️ Warning:** GameObject name changes at runtime will NOT update the cached SaveId. +
+ +### Private Lifecycle Methods +
+Click to see more details + +##### `Awake()` +```csharp +private void Awake() +``` +**Visibility:** `private` (sealed, cannot be overridden) +**Called By:** Unity +**Description:** Automatically registers component with `LifecycleManager`. Calls `OnManagedAwake()` during registration. + +**⚠️ Important:** This method is sealed. Use `OnManagedAwake()` for early initialization. + +##### `OnDestroy()` +```csharp +private void OnDestroy() +``` +**Visibility:** `private` (sealed, cannot be overridden) +**Called By:** Unity +**Description:** Calls `OnManagedDestroy()`, unregisters from `LifecycleManager`, and handles auto-unregistrations. + +**⚠️ Important:** This method is sealed. Use `OnManagedDestroy()` for custom cleanup. +
+ +--- + +## LifecycleManager (Singleton Orchestrator) + +**Namespace:** `Core.Lifecycle` +**Inherits:** `MonoBehaviour` +**Location:** `Assets/Scripts/Core/Lifecycle/LifecycleManager.cs` → [View Source](../../Assets/Scripts/Core/Lifecycle/LifecycleManager.cs) + +Central orchestrator that manages all `ManagedBehaviour` components and broadcasts lifecycle events. + +### Static Properties + +##### `Instance` +Singleton instance. Created automatically by `CustomBoot` before bootstrap begins. + +### Public Methods + +Core methods for registration, lifecycle broadcasting, and save/restore operations. Most are called automatically by the framework. + +
+Click to see more details + +##### `Register(ManagedBehaviour component)` +```csharp +public void Register(ManagedBehaviour component) +``` +Registers a component with the lifecycle system. **Called automatically from `ManagedBehaviour.Awake()`.** + +##### `Unregister(ManagedBehaviour component)` +```csharp +public void Unregister(ManagedBehaviour component) +``` +Unregisters a component. **Called automatically from `ManagedBehaviour.OnDestroy()`.** + +##### `OnBootCompletionTriggered()` +```csharp +public void OnBootCompletionTriggered() +``` +Called by `CustomBoot` after bootstrap completes. Broadcasts `OnManagedStart()` to all registered components. + +##### `BeginSceneLoad(string sceneName)` +```csharp +public void BeginSceneLoad(string sceneName) +``` +Activates scene loading batching mode. Called by `SceneManagerService` when loading a scene. + +##### `BroadcastSceneReady(string sceneName)` +```csharp +public void BroadcastSceneReady(string sceneName) +``` +Processes batched components and broadcasts `OnSceneReady()` to all components in the scene. Called by `SceneManagerService` after scene load. + +##### `BroadcastSceneUnloading(string sceneName)` +```csharp +public void BroadcastSceneUnloading(string sceneName) +``` +Broadcasts `OnSceneUnloading()` to all components in the specified scene. Called by `SceneManagerService` before scene unload. + +##### `BroadcastSceneSaveRequested()` +```csharp +public Dictionary BroadcastSceneSaveRequested() +``` +Broadcasts `OnSceneSaveRequested()` to all components with `AutoRegisterForSave == true`. Returns dictionary of SaveId → serialized data. + +##### `BroadcastSceneRestoreRequested(Dictionary saveData)` +```csharp +public void BroadcastSceneRestoreRequested(Dictionary saveData) +``` +Distributes save data to components by matching `SaveId`, then broadcasts `OnSceneRestoreCompleted()`. + +##### `BroadcastGlobalSaveRequested()` +```csharp +public Dictionary BroadcastGlobalSaveRequested() +``` +Broadcasts `OnGlobalSaveRequested()` to all components with `AutoRegisterForSave == true`. Returns dictionary of SaveId → serialized data. + +##### `BroadcastGlobalRestoreRequested(Dictionary saveData)` +```csharp +public void BroadcastGlobalRestoreRequested(Dictionary saveData) +``` +Distributes save data to components by matching `SaveId`, then broadcasts `OnGlobalLoadCompleted()`. + +##### `BroadcastGlobalSaveStarted()` +```csharp +public void BroadcastGlobalSaveStarted() +``` +Broadcasts `OnGlobalSaveStarted()` to all components with `AutoRegisterForSave == true`. + +## LifecyclePhase (Enum) + +**Namespace:** `Core.Lifecycle` +**Location:** `Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs` → [View Source](../../Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs) + +Defines the different lifecycle phases for documentation and tooling purposes. + +```csharp +public enum LifecyclePhase +{ + ManagedAwake, // During registration (Awake) + ManagedStart, // After bootstrap or late registration + SceneUnloading, // Before scene unload + SceneReady, // After scene load + SaveRequested, // Before scene unload (save) + RestoreRequested, // After scene load (restore) + ManagedDestroy // During OnDestroy +} +``` + +--- + + + diff --git a/docs/managed_behavior/use_cases_quick_start.md b/docs/managed_behavior/use_cases_quick_start.md new file mode 100644 index 00000000..564681ff --- /dev/null +++ b/docs/managed_behavior/use_cases_quick_start.md @@ -0,0 +1,522 @@ +# ManagedBehaviour System - Quick Start & Use Cases + +**TL;DR:** Inherit from `ManagedBehaviour` instead of `MonoBehaviour`. Override lifecycle hooks instead of Awake/OnDestroy. Get guaranteed initialization order and automatic registration. + +--- + +## Table of Contents + +1. [Lifecycle Methods Summary](#lifecycle-methods-summary) +2. [Quick Reference - Common Use Cases](#quick-reference---common-use-cases) +3. [Getting Started Examples](#getting-started-examples) +4. [Detailed Use Cases](#detailed-use-cases) +5. [Common Patterns](#common-patterns) +6. [Migration Checklist](#migration-checklist) +7. [Troubleshooting](#troubleshooting) +8. [Best Practices](#best-practices) +9. [FAQ](#faq) + +--- + +## Summary + +**ManagedBehaviour in 3 Sentences:** + +Inherit from `ManagedBehaviour` to get automatic lifecycle management with guaranteed initialization order. Use `OnManagedStart()` to safely access manager singletons, and use built-in save/load hooks for persistence. The framework handles registration and cleanup automatically - you just override the hooks you need. + +**When to Use:** +- ✅ Singleton managers +- ✅ Components that access managers +- ✅ Components that need save/load +- ✅ Components that need scene lifecycle events + +**When to Skip:** +- ❌ Simple self-contained components +- ❌ Third-party code (can't change base class) +- ❌ Performance-critical code with no dependencies + +**But can I still use regular MonoBehaviour?!**
+_Yes!_ Only use ManagedBehaviour when you need its features (manager access, save/load, etc.) + + +--- + +## Lifecycle Methods Summary + +1. **OnManagedAwake()** - Called during Unity's Awake phase; use for **internal setup** and GetComponent calls. +2. **OnManagedStart()** - Called after bootstrap completes; **safe to access** manager singletons. +3. **OnSceneReady()** - Called after scene finishes loading; use for scene-specific initialization. +4. **OnSceneUnloading()** - Called before scene unloads; use for scene cleanup. +5. **OnSceneSaveRequested()** - Returns serialized scene-specific data before scene transitions. +6. **OnSceneRestoreRequested(data)** - Receives saved scene data after scene loads. +7. **OnSceneRestoreCompleted()** - Called after all scene restore operations complete. +8. **OnGlobalSaveRequested()** - Returns serialized persistent data when game saves. +9. **OnGlobalRestoreRequested(data)** - Receives persistent data on game boot. +10. **OnGlobalLoadCompleted()** - Called after all global restore operations complete on boot. +11. **OnGlobalSaveStarted()** - Called before save file is written; use for pre-save validation. +12. **OnManagedDestroy()** - Called during OnDestroy; use for cleanup and event unsubscription. + +--- + +## Quick Reference - Common Use Cases + +### Access Manager Singleton Safely +```csharp +internal override void OnManagedStart() +{ + GameManager.Instance.DoSomething(); // Safe - managers guaranteed ready +} +``` + +### Create Singleton Manager +```csharp +private static MyManager _instance; +public static MyManager Instance => _instance; + +internal override void OnManagedAwake() +{ + _instance = this; +} +``` + +### Save Scene Progress +```csharp +public override bool AutoRegisterForSave => true; + +internal override string OnSceneSaveRequested() +{ + return JsonUtility.ToJson(myData); +} + +internal override void OnSceneRestoreRequested(string data) +{ + myData = JsonUtility.FromJson(data); +} +``` + +### Auto-Register as Pausable +```csharp +public class MyComponent : ManagedBehaviour, IPausable +{ + public override bool AutoRegisterPausable => true; + + public void Pause() { /* pause logic */ } + public void Resume() { /* resume logic */ } +} +``` + +### Scene-Specific Initialization +```csharp +internal override void OnSceneReady() +{ + FindObjectsInScene(); + InitializeLevel(); +} +``` + +### Cleanup on Destroy +```csharp +internal override void OnManagedDestroy() +{ + EventBus.OnSomething -= HandleEvent; +} +``` + +--- + +## Getting Started Examples + +### 1. Basic Component + +```csharp +using Core.Lifecycle; + +public class MyComponent : ManagedBehaviour +{ + internal override void OnManagedAwake() + { + // Early initialization (singleton setup, GetComponent) + Debug.Log("MyComponent awakened"); + } + + internal override void OnManagedStart() + { + // Late initialization (safe to access managers) + Debug.Log("MyComponent started - managers are ready"); + } +} +``` + +### 2. Singleton Manager + +```csharp +using Core.Lifecycle; + +public class MyManager : ManagedBehaviour +{ + private static MyManager _instance; + public static MyManager Instance => _instance; + + internal override void OnManagedAwake() + { + _instance = this; // Set singleton early + } + + internal override void OnManagedStart() + { + // All other managers are ready here + AudioManager.Instance.PlaySound("ManagerReady"); + } +} +``` + +### 3. Component with Save/Load + +```csharp +using Core.Lifecycle; + +public class PuzzleComponent : ManagedBehaviour +{ + public override bool AutoRegisterForSave => true; + public override string SaveId => "MyPuzzle"; // Custom ID + + private bool _isSolved; + + internal override string OnSceneSaveRequested() + { + return JsonUtility.ToJson(new { isSolved = _isSolved }); + } + + internal override void OnSceneRestoreRequested(string data) + { + var saveData = JsonUtility.FromJson(data); + _isSolved = saveData.isSolved; + } + + [System.Serializable] + private class SaveData + { + public bool isSolved; + } +} +``` + +### 4. Component with Cleanup + +```csharp +using Core.Lifecycle; + +public class EventSubscriber : ManagedBehaviour +{ + internal override void OnManagedStart() + { + GameManager.Instance.OnGamePaused += HandlePause; + } + + internal override void OnManagedDestroy() + { + // Automatic cleanup + if (GameManager.Instance != null) + GameManager.Instance.OnGamePaused -= HandlePause; + } + + private void HandlePause() { } +} +``` + +--- + +## Detailed Use Cases + +### Use Case 1: Accessing Singleton Managers Safely + +**Problem:** Accessing `GameManager.Instance` in `Awake()` might fail if GameManager hasn't initialized yet. + +**Solution:** +```csharp +public class Player : ManagedBehaviour +{ + // ❌ DON'T: Risky in OnManagedAwake (managers may not be ready) + internal override void OnManagedAwake() + { + // var settings = GameManager.Instance.GetSettings(); // RISKY! + } + + // ✅ DO: Safe in OnManagedStart (managers guaranteed ready) + internal override void OnManagedStart() + { + var settings = GameManager.Instance.GetSettings(); // SAFE! + ApplySettings(settings); + } +} +``` + +--- + +### Use Case 2: Scene-Specific Initialization + +**Problem:** Need to initialize something after a scene finishes loading. + +**Solution:** +```csharp +public class LevelManager : ManagedBehaviour +{ + internal override void OnSceneReady() + { + // Scene is fully loaded, all objects exist + FindObjectiveMarkers(); + SpawnEnemies(); + PlayLevelMusic(); + } +} +``` + +--- + +### Use Case 3: Saving Player Progress + +**Problem:** Need to save level progress when transitioning between scenes. + +**Solution:** +```csharp +public class LevelProgress : ManagedBehaviour +{ + public override bool AutoRegisterForSave => true; + public override string SaveId => "LevelProgress"; + + private int _checkpointIndex; + private float _timeElapsed; + + internal override string OnSceneSaveRequested() + { + return JsonUtility.ToJson(new + { + checkpoint = _checkpointIndex, + time = _timeElapsed + }); + } + + internal override void OnSceneRestoreRequested(string data) + { + var save = JsonUtility.FromJson(data); + _checkpointIndex = save.checkpoint; + _timeElapsed = save.time; + } + + internal override void OnSceneRestoreCompleted() + { + // Restore complete, trigger UI update + UpdateProgressUI(); + } +} +``` + +--- + +### Use Case 4: Global Persistent Data (Inventory) + +**Problem:** Need to save player inventory across all scenes. + +**Solution:** +```csharp +public class InventoryManager : ManagedBehaviour +{ + public override bool AutoRegisterForSave => true; + public override string SaveId => "Inventory"; + + private List _items = new List(); + + internal override string OnGlobalSaveRequested() + { + // Save to global save file (not scene-specific) + return JsonUtility.ToJson(new { items = _items }); + } + + internal override void OnGlobalRestoreRequested(string data) + { + // Restore from global save file on boot + var save = JsonUtility.FromJson(data); + _items = new List(save.items); + } + + internal override void OnGlobalLoadCompleted() + { + // All global data loaded, safe to initialize UI + RefreshInventoryUI(); + } +} +``` + +--- + +### Use Case 5: Auto-Registering as Pausable + +**Problem:** Component implements `IPausable` and needs to register with `GameManager`. + +**Solution:** +```csharp +public class AnimatedCharacter : ManagedBehaviour, IPausable +{ + public override bool AutoRegisterPausable => true; + + // IPausable implementation + public void Pause() + { + // Pause animations + } + + public void Resume() + { + // Resume animations + } +} +// No manual registration needed - automatic! +``` + +--- + +### Use Case 6: Scene Cleanup + +**Problem:** Need to clean up scene-specific state before transitioning. + +**Solution:** +```csharp +public class ParticleSpawner : ManagedBehaviour +{ + private List _activeParticles = new List(); + + internal override void OnSceneUnloading() + { + // Clean up before scene unloads + foreach (var particle in _activeParticles) + { + if (particle != null) + Destroy(particle); + } + _activeParticles.Clear(); + } +} +``` + +--- + +### Use Case 7: First-Time vs Restored State + +**Problem:** Need to play intro animation only on first visit, not when restoring from save. + +**Solution:** +```csharp +public class LevelIntro : ManagedBehaviour +{ + public override bool AutoRegisterForSave => true; + + private bool _hasPlayedIntro; + + internal override string OnSceneSaveRequested() + { + return JsonUtility.ToJson(new { hasPlayed = _hasPlayedIntro }); + } + + internal override void OnSceneRestoreRequested(string data) + { + var save = JsonUtility.FromJson(data); + _hasPlayedIntro = save.hasPlayed; + } + + internal override void OnSceneRestoreCompleted() + { + // This fires whether we restored or not + if (!_hasPlayedIntro) + { + PlayIntroAnimation(); + _hasPlayedIntro = true; + } + } +} +``` + +--- + + +## Common Patterns + +### Pattern: Manager Singleton +```csharp +public class MyManager : ManagedBehaviour +{ + private static MyManager _instance; + public static MyManager Instance => _instance; + + internal override void OnManagedAwake() + { + _instance = this; + } +} +``` + +### Pattern: Event Subscription +```csharp +internal override void OnManagedStart() +{ + EventBus.OnSomething += HandleEvent; +} + +internal override void OnManagedDestroy() +{ + EventBus.OnSomething -= HandleEvent; +} +``` + +### Pattern: Save/Restore +```csharp +public override bool AutoRegisterForSave => true; + +internal override string OnSceneSaveRequested() +{ + return JsonUtility.ToJson(myData); +} + +internal override void OnSceneRestoreRequested(string data) +{ + myData = JsonUtility.FromJson(data); +} +``` + +### Pattern: Custom SaveId +```csharp +// For singletons or special cases +public override string SaveId => "PlayerInventory"; + +// For scene-specific instances +public override string SaveId => $"{gameObject.scene.name}/MySpecialObject"; +``` + +## Troubleshooting + +### "NullReferenceException when accessing Manager.Instance" +**Problem:** Accessing singleton in `OnManagedAwake()` +**Solution:** Move to `OnManagedStart()` where managers are guaranteed ready + +### "My SaveId is colliding with another component" +**Problem:** Two components have same GameObject name and type +**Solution:** Override `SaveId` property with unique value + +### "My component isn't receiving lifecycle callbacks" +**Problem:** Not inheriting from `ManagedBehaviour` +**Solution:** Ensure class inherits `ManagedBehaviour` (not plain `MonoBehaviour`) + +### "Save data isn't persisting" +**Problem:** `AutoRegisterForSave` is false +**Solution:** Set `public override bool AutoRegisterForSave => true;` + +### "OnSceneRestoreCompleted isn't firing" +**Problem:** Missing implementation +**Solution:** Override the method even if just logging for now + +--- + +**Quick Links:** +- [Technical Reference](01_technical_reference.md) - Complete API documentation +- [Architecture Overview](02_architecture_overview.md) - System design and principles + + diff --git a/docs/media/Scene_transition_flow.png b/docs/media/Scene_transition_flow.png new file mode 100644 index 00000000..b9e77c39 Binary files /dev/null and b/docs/media/Scene_transition_flow.png differ diff --git a/docs/media/boot_sequence.png b/docs/media/boot_sequence.png new file mode 100644 index 00000000..c92d7405 Binary files /dev/null and b/docs/media/boot_sequence.png differ diff --git a/docs/media/class_diagram.png b/docs/media/class_diagram.png new file mode 100644 index 00000000..0ca7fd51 Binary files /dev/null and b/docs/media/class_diagram.png differ diff --git a/docs/media/component_lifecycle.png b/docs/media/component_lifecycle.png new file mode 100644 index 00000000..671bd71b Binary files /dev/null and b/docs/media/component_lifecycle.png differ