fix bug class run before addressable remote

This commit is contained in:
CuongNV
2026-05-21 17:18:28 +07:00
parent 5affac9cbd
commit 3dc0255eae
5 changed files with 140 additions and 24 deletions
@@ -99,7 +99,19 @@ namespace BrewMonster.Scripts
}
Debug.Log("[Cuong] AddressableManager: Bootstrap gate xong — đang InitializeAsync Addressables...");
Addressables.InitializeAsync().Completed += OnInitializeComplete;
try
{
await AddressablesInitService.EnsureInitializedAsync();
_isInitialized = true;
_initializationTcs.TrySetResult();
BMLogger.Log("AddressableManager: Initialized");
Debug.Log("[Cuong] AddressableManager: InitializeAsync xong — sẵn sàng load asset.");
}
catch (Exception e)
{
BMLogger.LogError($"AddressableManager: Failed to initialize: {e.Message}");
Debug.LogError($"[Cuong] AddressableManager: InitializeAsync thất bại — {e.Message}");
}
}
async UniTask<bool> WaitForBootstrapGateWithTimeoutAsync()
@@ -142,23 +154,6 @@ namespace BrewMonster.Scripts
#endregion
#region private functions
private void OnInitializeComplete(AsyncOperationHandle<IResourceLocator> handle)
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
_isInitialized = true;
_initializationTcs.TrySetResult();
BMLogger.Log($"AddressableManager: Initialized");
Debug.Log("[Cuong] AddressableManager: InitializeAsync xong — sẵn sàng load asset.");
}
else
{
// print out the error
BMLogger.LogError($"AddressableManager: Failed to initialize: {handle.OperationException?.Message} {handle.OperationException?.StackTrace}");
Debug.LogError($"[Cuong] AddressableManager: InitializeAsync thất bại — {handle.OperationException?.Message}");
}
}
private void RemoveFromReleaseAssetDictionary(string assetPath)
{
if (_releaseAssetTimestamps.ContainsKey(assetPath))
@@ -55,7 +55,7 @@ namespace BrewMonster.Scripts
/// </summary>
public static async UniTask EnsureInitializedAsync()
{
await Addressables.InitializeAsync().ToUniTask();
await AddressablesInitService.EnsureInitializedAsync();
}
/// <summary>
@@ -0,0 +1,92 @@
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace BrewMonster.Scripts
{
/// <summary>
/// Single entry for <see cref="Addressables.InitializeAsync"/> after <see cref="GameContentBootstrap"/> gate
/// (version fetch + optional <see cref="AddressablesRuntimeUrlRewriter"/>). Prevents early init from UI code
/// (e.g. <c>AUIManager</c>) hitting remote URLs before CDN rewrite.
/// </summary>
public static class AddressablesInitService
{
static readonly object s_lock = new();
static UniTaskCompletionSource<bool> s_initTcs;
static bool s_initialized;
public static bool IsInitialized => s_initialized;
/// <summary>
/// Waits bootstrap gate, then initializes Addressables once.
/// </summary>
public static async UniTask EnsureInitializedAsync()
{
if (s_initialized)
return;
UniTaskCompletionSource<bool> waiter;
lock (s_lock)
{
if (s_initialized)
return;
if (s_initTcs != null)
{
waiter = s_initTcs;
}
else
{
waiter = new UniTaskCompletionSource<bool>();
s_initTcs = waiter;
}
}
if (waiter != s_initTcs)
{
await waiter.Task;
return;
}
try
{
await GameContentBootstrap.WaitForPreAddressablesSetupIfAnyAsync();
Debug.Log("[Cuong] AddressablesInitService: Bootstrap gate OK — InitializeAsync...");
var handle = Addressables.InitializeAsync();
await handle.ToUniTask();
if (handle.Status != AsyncOperationStatus.Succeeded)
{
var msg = handle.OperationException?.Message ?? "InitializeAsync failed";
waiter.TrySetException(new InvalidOperationException(msg));
lock (s_lock)
s_initTcs = null;
throw handle.OperationException ?? new InvalidOperationException(msg);
}
s_initialized = true;
waiter.TrySetResult(true);
Debug.Log("[Cuong] AddressablesInitService: InitializeAsync xong.");
}
catch (Exception ex)
{
if (!waiter.Task.Status.IsCompleted())
waiter.TrySetException(ex);
lock (s_lock)
s_initTcs = null;
throw;
}
}
/// <summary>
/// Sync wait for legacy <c>Init()</c> paths. Prefer async when possible.
/// </summary>
public static void EnsureInitializedBlocking()
{
EnsureInitializedAsync().GetAwaiter().GetResult();
}
}
}
@@ -236,7 +236,15 @@ namespace BrewMonster.Scripts
AddressablesRuntimeUrlRewriter.InstallPrefixRewrite(
_bakedRemoteUrlPrefixForRewrite,
fetch.AssetsBaseUrl);
Debug.Log("[Cuong] GameContentBootstrap: Đã gắn URL rewrite cho remote CDN.");
Debug.Log(
$"[Cuong] GameContentBootstrap: URL rewrite | from={_bakedRemoteUrlPrefixForRewrite.Trim()} → to={fetch.AssetsBaseUrl.Trim()}");
}
else if (!string.IsNullOrWhiteSpace(fetch.AssetsBaseUrl) &&
string.IsNullOrWhiteSpace(_bakedRemoteUrlPrefixForRewrite))
{
Debug.LogWarning(
"[Cuong] GameContentBootstrap: assetsBaseUrl có nhưng _bakedRemoteUrlPrefixForRewrite trống — " +
"catalog/bundle vẫn dùng URL bake trong build (vd CDN cũ). Điền prefix URL lúc build.");
}
AllowAddressablesInit("version-and-url-ready");
@@ -264,8 +272,18 @@ namespace BrewMonster.Scripts
var catalog = await AddressablesCatalogUpdater.CheckAndUpdateCatalogsIfNeededAsync(false);
if (!catalog.Success)
{
Debug.LogError($"[Cuong] GameContentBootstrap: Catalog update thất bại — {catalog.Error?.Message}");
return new BootstrapResult(false, true, catalog.Error?.Message ?? "Catalog update failed", fetch.ContentVersion);
var errMsg = catalog.Error?.Message ?? "Catalog update failed";
Debug.LogError($"[Cuong] GameContentBootstrap: Catalog update thất bại — {errMsg}");
if (IsLikelyRemoteTlsOrNetworkError(errMsg))
{
Debug.LogError(
"[Cuong] GameContentBootstrap: Gợi ý — SSL CA / mạng trên mobile. " +
"Kiểm tra chuỗi chứng chỉ CDN; đảm bảo _bakedRemoteUrlPrefixForRewrite khớp host trong catalog build " +
"(vd https://prefect-world-asset....wcsapi.com/) và assetsBaseUrl trỏ CDN HTTPS hợp lệ. " +
"Không gọi Addressables.InitializeAsync trước bootstrap (AUIManager đã dùng AddressablesInitService).");
}
return new BootstrapResult(false, true, errMsg, fetch.ContentVersion);
}
Debug.Log("[Cuong] GameContentBootstrap: Catalog OK.");
@@ -402,6 +420,16 @@ namespace BrewMonster.Scripts
return s.Trim();
}
static bool IsLikelyRemoteTlsOrNetworkError(string message)
{
if (string.IsNullOrEmpty(message))
return false;
return message.IndexOf("SSL", StringComparison.OrdinalIgnoreCase) >= 0 ||
message.IndexOf("certificate", StringComparison.OrdinalIgnoreCase) >= 0 ||
message.IndexOf("ConnectionError", StringComparison.OrdinalIgnoreCase) >= 0 ||
message.IndexOf("unable to load from url", StringComparison.OrdinalIgnoreCase) >= 0;
}
public readonly struct BootstrapResult
{
public BootstrapResult(bool success, bool didContentWork, string errorMessage, string serverContentVersion)
+3 -2
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BrewMonster.Scripts;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
@@ -282,8 +283,8 @@ namespace BrewMonster.UI
{
try
{
// Initialize Addressables if not already initialized (Unity-safe)
Addressables.InitializeAsync().WaitForCompletion();
// Wait for GameContentBootstrap gate + URL rewrite, then single shared init.
AddressablesInitService.EnsureInitializedBlocking();
// Load using Addressables directly with WaitForCompletion (Unity-safe, won't deadlock)
// This matches the pattern used in EC_Game.cs