using System; using System.Threading.Tasks; using Core; using Core.Lifecycle; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; namespace Bootstrap { /// /// Entrypoint for the Custom Boot initialisation /// public static class CustomBoot { /// /// Current initialisation status /// public static bool Initialised { get; private set; } /// /// Event triggered when boot progress changes /// public static event Action OnBootProgressChanged; /// /// Event triggered when boot process completes /// public static event Action OnBootCompleted; /// /// Current progress of the boot process (0-1) /// public static float CurrentProgress { get; private set; } /// /// Called as soon as the game begins /// [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; PerformInitialisation(); } /// /// Initialise the bootstrapper /// public static void PerformInitialisation() { //Reset progress CurrentProgress = 0f; //In editor, perform initialisation synchronously if (Application.isEditor) { InitialiseBootSettingsSync(); } else { //In builds, just run things asynchronously, since we can add any checks we need early on _ = InitialiseBootSettings(); } } /// /// Called as the game is quitting, allowing for cleanup /// private static void ApplicationOnUnloading() { Application.quitting -= ApplicationOnUnloading; PerformDeInitialisation(); } /// /// De-Initialise the bootstrapper /// public static void PerformDeInitialisation() { Cleanup(runtimeBootSettingsHandle); Cleanup(editorBootSettingsHandle); Initialised = false; } /// /// Initialise the boot settings asynchronously /// private static async Task InitialiseBootSettings() { await LoadCustomBootSettings(); Initialised = true; CurrentProgress = 1f; OnBootProgressChanged?.Invoke(1f); OnBootCompleted?.Invoke(); // Notify the LifecycleManager that boot is complete if (Application.isPlaying) { Logging.Debug("Calling LifecycleManager.OnBootCompletionTriggered()"); if (LifecycleManager.Instance != null) { LifecycleManager.Instance.OnBootCompletionTriggered(); } } } /// /// Initialise the boot settings synchronously /// private static void InitialiseBootSettingsSync() { LoadCustomBootSettingsSync(); Initialised = true; CurrentProgress = 1f; OnBootProgressChanged?.Invoke(1f); OnBootCompleted?.Invoke(); // Notify the LifecycleManager that boot is complete if (Application.isPlaying) { Logging.Debug("Calling LifecycleManager.OnBootCompletionTriggered()"); if (LifecycleManager.Instance != null) { LifecycleManager.Instance.OnBootCompletionTriggered(); } } } /// /// Clean up the boot settings /// /// private static void Cleanup(AsyncOperationHandle handle) { if (handle.IsValid()) { handle.Result.Cleanup(); Addressables.Release(handle); } } /// /// Async handle for the runtime custom boot settings scriptable object /// private static AsyncOperationHandle runtimeBootSettingsHandle; /// /// Async handle for the editor custom boot settings object /// private static AsyncOperationHandle editorBootSettingsHandle; /// /// Runtime addressable key /// private static string RuntimeAsset = $"{nameof(CustomBootSettings)}_Runtime"; /// /// Editor addressable key /// private static string EditorAsset = $"{nameof(CustomBootSettings)}_Editor"; /// /// Load the custom boot settings asynchronously and run the initialisation method /// private static async Task LoadCustomBootSettings() { if (Application.isEditor) { editorBootSettingsHandle = await InitialiseBootSettingsAsset(EditorAsset); } runtimeBootSettingsHandle = await InitialiseBootSettingsAsset(RuntimeAsset); } /// /// Load the custom boot settings synchronously and run the initialisation method /// private static void LoadCustomBootSettingsSync() { if (Application.isEditor) { editorBootSettingsHandle = InitialiseBootSettingsAssetSync(EditorAsset); } runtimeBootSettingsHandle = InitialiseBootSettingsAssetSync(RuntimeAsset); } /// /// Initialise the boot settings asset with the given key /// /// /// private static async Task> InitialiseBootSettingsAsset(string key) { var handle = Addressables.LoadAssetAsync(key); await handle.Task; switch (handle.Status) { case AsyncOperationStatus.Failed: Debug.LogError(handle.OperationException); break; case AsyncOperationStatus.Succeeded: await handle.Result.Initialise(); break; } return handle; } /// /// Initialise the boot settings asset with the given key synchronously /// /// /// private static AsyncOperationHandle InitialiseBootSettingsAssetSync(string key) { var handle = Addressables.LoadAssetAsync(key); var result = handle.WaitForCompletion(); result.InitialiseSync(); return handle; } /// /// Updates the current progress value and triggers the progress event /// /// Progress value between 0-1 internal static void UpdateProgress(float progress) { CurrentProgress = Mathf.Clamp01(progress); OnBootProgressChanged?.Invoke(CurrentProgress); Logging.Debug($"Progress: {CurrentProgress:P0}"); } } }