234 lines
10 KiB
C#
234 lines
10 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using BrewMonster;
|
||
using Cysharp.Threading.Tasks;
|
||
using UnityEngine;
|
||
using UnityEngine.AddressableAssets;
|
||
using UnityEngine.AddressableAssets.ResourceLocators;
|
||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||
|
||
namespace BrewMonster.Scripts
|
||
{
|
||
/// <summary>
|
||
/// Remote catalog / bundle flows: CheckForCatalogUpdates, UpdateCatalogs, optional download size and dependencies.
|
||
/// Matches the “catalog new → bundle new / cache” behaviour described in project docs (Addressables overview).
|
||
/// </summary>
|
||
public static class AddressablesCatalogUpdater
|
||
{
|
||
/// <summary>
|
||
/// Result of checking the remote catalog hash list (may be empty when nothing changed).
|
||
/// </summary>
|
||
public readonly struct CatalogCheckResult
|
||
{
|
||
public CatalogCheckResult(bool success, IReadOnlyList<string> catalogsWithUpdates, Exception error)
|
||
{
|
||
Success = success;
|
||
CatalogsWithUpdates = catalogsWithUpdates ?? Array.Empty<string>();
|
||
Error = error;
|
||
}
|
||
|
||
public bool Success { get; }
|
||
public IReadOnlyList<string> CatalogsWithUpdates { get; }
|
||
public bool HasUpdates => CatalogsWithUpdates.Count > 0;
|
||
public Exception Error { get; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Result after applying <see cref="Addressables.UpdateCatalogs"/>.
|
||
/// </summary>
|
||
public readonly struct CatalogApplyResult
|
||
{
|
||
public CatalogApplyResult(bool success, IReadOnlyList<IResourceLocator> locators, Exception error)
|
||
{
|
||
Success = success;
|
||
Locators = locators ?? Array.Empty<IResourceLocator>();
|
||
Error = error;
|
||
}
|
||
|
||
public bool Success { get; }
|
||
public IReadOnlyList<IResourceLocator> Locators { get; }
|
||
public Exception Error { get; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ensures Addressables finished initial catalog load before checking remote updates.
|
||
/// </summary>
|
||
public static async UniTask EnsureInitializedAsync()
|
||
{
|
||
await Addressables.InitializeAsync().ToUniTask();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Calls <see cref="Addressables.CheckForCatalogUpdates"/> — lightweight compared to full bundle downloads.
|
||
/// </summary>
|
||
/// <param name="autoReleaseHandle">Pass through to Addressables (default true).</param>
|
||
public static async UniTask<CatalogCheckResult> CheckForCatalogUpdatesAsync(bool autoReleaseHandle = true)
|
||
{
|
||
try
|
||
{
|
||
await EnsureInitializedAsync();
|
||
var handle = Addressables.CheckForCatalogUpdates(autoReleaseHandle);
|
||
await handle.ToUniTask();
|
||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||
{
|
||
var err = handle.OperationException;
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: CheckForCatalogUpdates failed: {err?.Message}");
|
||
return new CatalogCheckResult(false, null, err);
|
||
}
|
||
|
||
var list = handle.Result;
|
||
var ids = list != null ? (IReadOnlyList<string>)list : Array.Empty<string>();
|
||
return new CatalogCheckResult(true, ids, null);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: CheckForCatalogUpdates exception: {e.Message}");
|
||
return new CatalogCheckResult(false, null, e);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Downloads new catalog JSON and refreshes locators. Pass <paramref name="catalogIds"/> from
|
||
/// <see cref="CatalogCheckResult.CatalogsWithUpdates"/> or null to use Addressables’ internal list.
|
||
/// </summary>
|
||
/// <param name="autoCleanBundleCache">When true, removes unreferenced bundles after update (see Addressables docs).</param>
|
||
public static async UniTask<CatalogApplyResult> UpdateCatalogsAsync(
|
||
IEnumerable<string> catalogIds = null,
|
||
bool autoCleanBundleCache = false,
|
||
bool autoReleaseHandle = true)
|
||
{
|
||
try
|
||
{
|
||
await EnsureInitializedAsync();
|
||
AsyncOperationHandle<List<IResourceLocator>> handle;
|
||
if (autoCleanBundleCache)
|
||
handle = Addressables.UpdateCatalogs(true, catalogIds, autoReleaseHandle);
|
||
else
|
||
handle = Addressables.UpdateCatalogs(catalogIds, autoReleaseHandle);
|
||
|
||
await handle.ToUniTask();
|
||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||
{
|
||
var err = handle.OperationException;
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: UpdateCatalogs failed: {err?.Message}");
|
||
return new CatalogApplyResult(false, null, err);
|
||
}
|
||
|
||
var locators = handle.Result;
|
||
var read = locators != null ? (IReadOnlyList<IResourceLocator>)locators : Array.Empty<IResourceLocator>();
|
||
return new CatalogApplyResult(true, read, null);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: UpdateCatalogs exception: {e.Message}");
|
||
return new CatalogApplyResult(false, null, e);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Check remote catalog, then apply updates if any catalog ids were returned.
|
||
/// </summary>
|
||
public static async UniTask<CatalogApplyResult> CheckAndUpdateCatalogsIfNeededAsync(bool autoCleanBundleCache = false)
|
||
{
|
||
var check = await CheckForCatalogUpdatesAsync(true);
|
||
if (!check.Success)
|
||
return new CatalogApplyResult(false, null, check.Error);
|
||
if (!check.HasUpdates)
|
||
return new CatalogApplyResult(true, Array.Empty<IResourceLocator>(), null);
|
||
|
||
return await UpdateCatalogsAsync(check.CatalogsWithUpdates, autoCleanBundleCache, true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Bytes that would be downloaded for <paramref name="key"/> given the current catalog and local cache (0 if fully cached).
|
||
/// </summary>
|
||
public static async UniTask<long> GetDownloadSizeBytesAsync(object key)
|
||
{
|
||
try
|
||
{
|
||
await EnsureInitializedAsync();
|
||
var handle = Addressables.GetDownloadSizeAsync(key);
|
||
await handle.ToUniTask();
|
||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||
{
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: GetDownloadSizeAsync failed: {handle.OperationException?.Message}");
|
||
return -1;
|
||
}
|
||
return handle.Result;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: GetDownloadSizeBytesAsync exception: {e.Message}");
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ensures dependencies for <paramref name="key"/> exist on disk; uses cache when CRC/catalog match (no redundant full download).
|
||
/// </summary>
|
||
public static async UniTask<bool> DownloadDependenciesAsync(object key, bool autoReleaseHandle = true)
|
||
{
|
||
try
|
||
{
|
||
await EnsureInitializedAsync();
|
||
var keyLabel = key?.ToString() ?? "(null)";
|
||
Debug.Log($"[Cuong] AddressablesCatalogUpdater: Đang tải dependencies (key={keyLabel})...");
|
||
|
||
var handle = Addressables.DownloadDependenciesAsync(key, autoReleaseHandle);
|
||
var lastLoggedMilestone = -1;
|
||
while (!handle.IsDone)
|
||
{
|
||
var milestone = (int)(handle.PercentComplete * 10f);
|
||
if (milestone > lastLoggedMilestone)
|
||
{
|
||
lastLoggedMilestone = milestone;
|
||
Debug.Log($"[Cuong] AddressablesCatalogUpdater: Đang tải '{keyLabel}'... {handle.PercentComplete * 100f:F0}%");
|
||
}
|
||
await UniTask.Yield();
|
||
}
|
||
|
||
await handle.ToUniTask();
|
||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||
{
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: DownloadDependenciesAsync failed: {handle.OperationException?.Message}");
|
||
Debug.LogError($"[Cuong] AddressablesCatalogUpdater: Tải thất bại '{keyLabel}' — {handle.OperationException?.Message}");
|
||
return false;
|
||
}
|
||
|
||
Debug.Log($"[Cuong] AddressablesCatalogUpdater: Tải xong dependencies (key={keyLabel}).");
|
||
return true;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: DownloadDependenciesAsync exception: {e.Message}");
|
||
Debug.LogError($"[Cuong] AddressablesCatalogUpdater: Exception khi tải — {e.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removes bundles not referenced by the given catalogs (optional maintenance / “clear old cache”).
|
||
/// </summary>
|
||
public static async UniTask<bool> CleanBundleCacheAsync(IEnumerable<string> catalogIds = null)
|
||
{
|
||
try
|
||
{
|
||
await EnsureInitializedAsync();
|
||
var handle = Addressables.CleanBundleCache(catalogIds);
|
||
await handle.ToUniTask();
|
||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||
{
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: CleanBundleCache failed: {handle.OperationException?.Message}");
|
||
return false;
|
||
}
|
||
return handle.Result;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
BMLogger.LogError($"AddressablesCatalogUpdater: CleanBundleCacheAsync exception: {e.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
}
|