Rework of base interactables and managed behaviors

This commit is contained in:
Michal Pikulski
2025-11-04 11:11:27 +01:00
parent 0dc3f3e803
commit c57e3aa7e0
62 changed files with 11193 additions and 1376 deletions

View File

@@ -1,161 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AppleHills.Core.Settings;
using Core;
using UnityEngine;
namespace Bootstrap
{
/// <summary>
/// Service that provides notification and management of boot completion status.
/// Allows systems to subscribe to boot completion events, register initialization actions with priorities,
/// or await boot completion asynchronously.
/// </summary>
public static class BootCompletionService
{
/// <summary>
/// Indicates if the boot process has completed
/// </summary>
public static bool IsBootComplete { get; private set; } = false;
/// <summary>
/// Event triggered when boot completes
/// </summary>
public static event Action OnBootComplete;
/// <summary>
/// Represents an initialization action with priority
/// </summary>
private class InitializationAction
{
public Action Action { get; }
public int Priority { get; }
public string Name { get; }
public InitializationAction(Action action, int priority, string name)
{
Action = action;
Priority = priority;
Name = name;
}
}
// List of initialization actions to be executed once boot completes
private static List<InitializationAction> _initializationActions = new List<InitializationAction>();
// TaskCompletionSource for async await pattern
private static TaskCompletionSource<bool> _bootCompletionTask = new TaskCompletionSource<bool>();
/// <summary>
/// Called by CustomBoot when the boot process is complete
/// </summary>
internal static void HandleBootCompleted()
{
if (IsBootComplete)
return;
IsBootComplete = true;
LogDebugMessage("Boot process completed, executing initialization actions");
// Execute initialization actions in priority order (lower number = higher priority)
ExecuteInitializationActions();
// Trigger the event
OnBootComplete?.Invoke();
// Complete the task for async waiters
_bootCompletionTask.TrySetResult(true);
LogDebugMessage("All boot completion handlers executed");
}
/// <summary>
/// Register an action to be executed when boot completes.
/// Lower priority numbers run first.
/// </summary>
/// <param name="action">The action to execute</param>
/// <param name="priority">Priority (lower numbers run first)</param>
/// <param name="name">Name for debugging</param>
public static void RegisterInitAction(Action action, int priority = 100, string name = null)
{
if (action == null)
return;
if (string.IsNullOrEmpty(name))
name = $"Action_{_initializationActions.Count}";
var initAction = new InitializationAction(action, priority, name);
if (IsBootComplete)
{
// If boot is already complete, execute immediately
LogDebugMessage($"Executing late registration: {name} (Priority: {priority})");
try
{
action();
}
catch (Exception ex)
{
LogDebugMessage($"Error executing init action '{name}': {ex}");
}
}
else
{
// Otherwise add to the queue
_initializationActions.Add(initAction);
LogDebugMessage($"Registered init action: {name} (Priority: {priority})");
}
}
/// <summary>
/// Wait asynchronously for boot completion
/// </summary>
/// <returns>Task that completes when boot is complete</returns>
public static Task WaitForBootCompletionAsync()
{
if (IsBootComplete)
return Task.CompletedTask;
return _bootCompletionTask.Task;
}
/// <summary>
/// Execute all registered initialization actions in priority order
/// </summary>
private static void ExecuteInitializationActions()
{
// Sort by priority (lowest first)
var sortedActions = _initializationActions
.OrderBy(a => a.Priority)
.ToList();
foreach (var action in sortedActions)
{
try
{
LogDebugMessage($"Executing: {action.Name} (Priority: {action.Priority})");
action.Action();
}
catch (Exception ex)
{
LogDebugMessage($"Error executing init action '{action.Name}': {ex}");
}
}
// Clear the list after execution
_initializationActions.Clear();
}
private static void LogDebugMessage(string message)
{
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity <=
LogVerbosity.Debug)
{
Logging.Debug($"[BootCompletionService] {message}");
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: aa0228cf33a64515bc166b7a9bc8c0b9
timeCreated: 1760606319

View File

@@ -1,18 +1,17 @@
using System;
using System;
using AppleHills.Core.Settings;
using UnityEngine;
using UI;
using Core;
using Core.Lifecycle;
using UnityEngine.SceneManagement;
using Cinematics;
using UnityEngine.Serialization;
namespace Bootstrap
{
/// <summary>
/// Controller for the boot scene that coordinates bootstrap initialization with loading screen
/// </summary>
public class BootSceneController : MonoBehaviour
public class BootSceneController : ManagedBehaviour
{
[SerializeField] private string mainSceneName = "AppleHillsOverworld";
[SerializeField] private float minDelayAfterBoot = 0.5f; // Small delay after boot to ensure smooth transition
@@ -30,35 +29,35 @@ namespace Bootstrap
private float _sceneLoadingProgress = 0f;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
private void Start()
// Run very early - need to set up loading screen before other systems initialize
public override int ManagedAwakePriority => 5;
protected override void Awake()
{
LogDebugMessage("Boot scene started");
base.Awake(); // Register with LifecycleManager
// Ensure the initial loading screen exists
LogDebugMessage("BootSceneController.Awake() - Initializing loading screen DURING bootstrap");
// Validate loading screen exists
if (initialLoadingScreen == null)
{
Debug.LogError("[BootSceneController] No InitialLoadingScreen assigned! Please assign it in the inspector.");
return;
}
// Subscribe to the loading screen completion event
initialLoadingScreen.OnLoadingScreenFullyHidden += OnInitialLoadingComplete;
// Show the loading screen immediately with our combined progress provider
// This needs to happen DURING bootstrap to show progress
initialLoadingScreen.ShowLoadingScreen(GetCombinedProgress);
// Subscribe to boot progress events
CustomBoot.OnBootProgressChanged += OnBootProgressChanged;
// Subscribe to loading screen completion event
initialLoadingScreen.OnLoadingScreenFullyHidden += OnInitialLoadingComplete;
RegisterManagedEvent(initialLoadingScreen, nameof(initialLoadingScreen.OnLoadingScreenFullyHidden),
(Action)OnInitialLoadingComplete);
// Subscribe to boot progress for real-time updates during bootstrap
CustomBoot.OnBootProgressChanged += OnBootProgressChanged;
// Note: Static events need manual cleanup in OnDestroy
// Register our boot completion handler with the BootCompletionService
// This will execute either immediately if boot is already complete,
// or when the boot process completes
BootCompletionService.RegisterInitAction(
OnBootCompleted,
50, // Higher priority (lower number)
"BootSceneController.OnBootCompleted"
);
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity;
// In debug mode, log additional information
@@ -67,6 +66,28 @@ namespace Bootstrap
InvokeRepeating(nameof(LogDebugInfo), 0.1f, 0.5f);
}
}
protected override void OnManagedAwake()
{
LogDebugMessage("BootSceneController.OnManagedAwake() - Boot is GUARANTEED complete, starting scene loading");
// Boot is GUARANTEED complete at this point - that's the whole point of OnManagedAwake!
// No need to subscribe to OnBootCompleted or check CustomBoot.Initialised
_bootComplete = true;
_currentPhase = LoadingPhase.SceneLoading;
// Start loading the main scene after a small delay
// This prevents jerky transitions if boot happens very quickly
Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot);
}
protected override void OnDestroy()
{
// Manual cleanup for static event (RegisterManagedEvent doesn't handle static events properly)
CustomBoot.OnBootProgressChanged -= OnBootProgressChanged;
base.OnDestroy(); // Handles other managed event cleanup
}
/// <summary>
/// Called when the initial loading screen is fully hidden
@@ -94,23 +115,6 @@ namespace Bootstrap
}
}
private void OnDestroy()
{
// Clean up event subscriptions
CustomBoot.OnBootCompleted -= OnBootCompleted;
CustomBoot.OnBootProgressChanged -= OnBootProgressChanged;
if (initialLoadingScreen != null)
{
initialLoadingScreen.OnLoadingScreenFullyHidden -= OnInitialLoadingComplete;
}
if (debugMode)
{
CancelInvoke(nameof(LogDebugInfo));
}
}
/// <summary>
/// Progress provider that combines bootstrap and scene loading progress
/// </summary>
@@ -145,19 +149,7 @@ namespace Bootstrap
$"Scene: {_sceneLoadingProgress:P0}, Combined: {GetCombinedProgress():P0}, Boot Complete: {_bootComplete}");
}
private void OnBootCompleted()
{
// Unsubscribe to prevent duplicate calls
CustomBoot.OnBootCompleted -= OnBootCompleted;
LogDebugMessage("Boot process completed");
_bootComplete = true;
// After a small delay, start loading the main menu
// This prevents jerky transitions if boot happens very quickly
Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot);
}
private void StartLoadingMainMenu()
{
if (_hasStartedLoading)
@@ -207,6 +199,13 @@ namespace Bootstrap
// Ensure progress is complete
_sceneLoadingProgress = 1f;
// CRITICAL: Broadcast lifecycle events so components get their OnSceneReady callbacks
LogDebugMessage($"Broadcasting OnSceneReady for: {mainSceneName}");
LifecycleManager.Instance?.BroadcastSceneReady(mainSceneName);
LogDebugMessage($"Broadcasting OnRestoreRequested for: {mainSceneName}");
LifecycleManager.Instance?.BroadcastRestoreRequested(mainSceneName);
// Step 2: Scene is fully loaded, now hide the loading screen
// This will trigger OnInitialLoadingComplete via the event when animation completes
initialLoadingScreen.HideLoadingScreen();

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using AppleHills.Core.Settings;
using Core;
using Core.Lifecycle;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
@@ -39,6 +40,10 @@ namespace Bootstrap
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
private static void Initialise()
{
// Create LifecycleManager FIRST - before any bootstrap logic
// This ensures it exists when boot completes
LifecycleManager.CreateInstance();
//We should always clean up after Addressables, so let's take care of that immediately
Application.quitting += ApplicationOnUnloading;
@@ -97,12 +102,14 @@ namespace Bootstrap
OnBootProgressChanged?.Invoke(1f);
OnBootCompleted?.Invoke();
// Notify the BootCompletionService that boot is complete
// Notify the LifecycleManager that boot is complete
if (Application.isPlaying)
{
// Direct call to boot completion service
LogDebugMessage("Calling BootCompletionService.HandleBootCompleted()");
BootCompletionService.HandleBootCompleted();
LogDebugMessage("Calling LifecycleManager.OnBootCompletionTriggered()");
if (LifecycleManager.Instance != null)
{
LifecycleManager.Instance.OnBootCompletionTriggered();
}
}
}
@@ -117,12 +124,14 @@ namespace Bootstrap
OnBootProgressChanged?.Invoke(1f);
OnBootCompleted?.Invoke();
// Notify the BootCompletionService that boot is complete
// Notify the LifecycleManager that boot is complete
if (Application.isPlaying)
{
// Direct call to boot completion service
LogDebugMessage("Calling BootCompletionService.HandleBootCompleted()");
BootCompletionService.HandleBootCompleted();
LogDebugMessage("Calling LifecycleManager.OnBootCompletionTriggered()");
if (LifecycleManager.Instance != null)
{
LifecycleManager.Instance.OnBootCompletionTriggered();
}
}
}