// ============================================================================
|
// YooAssetService.cs — YooAsset 封装服务
|
// 实现 IYooAssetService 和 IYooAssetBridge,替代 AssetBundleUtility
|
// ============================================================================
|
|
using System;
|
using System.Collections.Generic;
|
using System.Threading;
|
using Cysharp.Threading.Tasks;
|
using UnityEngine;
|
using UnityEngine.SceneManagement;
|
using YooAsset;
|
|
namespace ProjSG.Resource
|
{
|
/// <summary>
|
/// YooAsset 资源加载服务单例。
|
/// 封装 YooAsset ResourcePackage 的核心加载能力,提供 UniTask 异步 API。
|
/// 同时实现 IYooAssetBridge 供 Launch 程序集跨程序集调用。
|
/// </summary>
|
public class YooAssetService : Singleton<YooAssetService>, IYooAssetService, IYooAssetBridge
|
{
|
private readonly Dictionary<string, ResourcePackage> _packages = new Dictionary<string, ResourcePackage>();
|
private ResourcePackage _defaultPackage;
|
private IRemoteServices _remoteServices;
|
private bool _isInitialized;
|
private EPlayMode _playMode;
|
|
// ====================================================================
|
// IYooAssetService Properties
|
// ====================================================================
|
|
/// <inheritdoc />
|
public bool IsInitialized => _isInitialized;
|
|
/// <inheritdoc />
|
public EPlayMode PlayMode => _playMode;
|
|
// ====================================================================
|
// IYooAssetBridge Properties
|
// ====================================================================
|
|
bool IYooAssetBridge.IsRegistered => _isInitialized;
|
|
// ====================================================================
|
// Initialization
|
// ====================================================================
|
|
/// <inheritdoc />
|
public async UniTask InitializeAsync(EPlayMode playMode, IRemoteServices remoteServices = null)
|
{
|
if (_isInitialized)
|
{
|
Debug.LogWarning("[YooAssetService] Already initialized.");
|
return;
|
}
|
|
_playMode = playMode;
|
_remoteServices = remoteServices;
|
|
// YooAsset 全局初始化(幂等操作)
|
YooAssets.Initialize();
|
|
// 初始化所有配置中的包裹
|
foreach (var pkgName in YooAssetPackageConfig.AllPackages)
|
{
|
try
|
{
|
// 优先复用 Launch 阶段已创建的包裹
|
var package = YooAssets.TryGetPackage(pkgName);
|
if (package != null)
|
{
|
// 验证包裹是否已成功初始化
|
if (package.InitializeStatus == EOperationStatus.Succeed)
|
{
|
Debug.Log($"[YooAssetService] Reusing existing package '{pkgName}' (InitializeStatus=Succeed)");
|
}
|
else
|
{
|
// 包裹存在但初始化未完成或失败(僵尸包裹)
|
// 销毁后重新创建并初始化
|
Debug.LogWarning($"[YooAssetService] Package '{pkgName}' exists but InitializeStatus={package.InitializeStatus}, destroying and re-creating...");
|
|
// 根据状态清理僵尸包裹
|
if (package.InitializeStatus == EOperationStatus.None)
|
{
|
// 未初始化状态可以直接移除
|
YooAssets.RemovePackage(pkgName);
|
}
|
else
|
{
|
// Failed/Processing 状态需先销毁再移除
|
var destroyOp = package.DestroyAsync();
|
await destroyOp.ToUniTask();
|
YooAssets.RemovePackage(pkgName);
|
}
|
|
package = YooAssets.CreatePackage(pkgName);
|
var initParams = CreateInitParameters(playMode, remoteServices, pkgName);
|
var initOp = package.InitializeAsync(initParams);
|
await initOp.ToUniTask();
|
|
if (initOp.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogWarning($"[YooAssetService] Package '{pkgName}' re-init failed: {initOp.Error}");
|
continue;
|
}
|
|
Debug.Log($"[YooAssetService] Package '{pkgName}' re-initialized successfully.");
|
|
// 重新初始化后必须请求版本并更新 Manifest
|
await RequestVersionAndUpdateForPackageAsync(pkgName, package);
|
}
|
}
|
else
|
{
|
// 自行创建并初始化(首次启动或该包未在 Launch 阶段创建)
|
package = YooAssets.CreatePackage(pkgName);
|
var initParams = CreateInitParameters(playMode, remoteServices, pkgName);
|
var initOp = package.InitializeAsync(initParams);
|
await initOp.ToUniTask();
|
|
if (initOp.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogWarning($"[YooAssetService] Package '{pkgName}' init failed: {initOp.Error}");
|
continue;
|
}
|
|
// 初始化后必须请求版本并更新 Manifest,否则 ActiveManifest 为 null
|
await RequestVersionAndUpdateForPackageAsync(pkgName, package);
|
|
Debug.Log($"[YooAssetService] Package '{pkgName}' newly initialized.");
|
}
|
|
_packages[pkgName] = package;
|
|
// 设置默认包
|
if (_defaultPackage == null || pkgName == YooAssetPackageConfig.DefaultPackage)
|
{
|
_defaultPackage = package;
|
YooAssets.SetDefaultPackage(package);
|
}
|
}
|
catch (Exception ex)
|
{
|
// EditorSimulateMode 下包不在 Collector 中会抛异常,跳过
|
Debug.LogWarning($"[YooAssetService] Package '{pkgName}' init exception (skipped): {ex.Message}");
|
}
|
}
|
|
if (_defaultPackage == null)
|
{
|
Debug.LogError("[YooAssetService] No packages initialized successfully!");
|
throw new InvalidOperationException("YooAsset initialization failed: no packages available.");
|
}
|
|
_isInitialized = true;
|
|
// 输出初始化摘要 — 帮助诊断缺失的包裹
|
var allPkgs = YooAssetPackageConfig.AllPackages;
|
var missingPkgs = new System.Collections.Generic.List<string>();
|
foreach (var p in allPkgs)
|
{
|
if (!_packages.ContainsKey(p))
|
missingPkgs.Add(p);
|
}
|
if (missingPkgs.Count > 0)
|
{
|
Debug.LogError($"[YooAssetService] WARNING: {missingPkgs.Count} package(s) FAILED to initialize: [{string.Join(", ", missingPkgs)}]. " +
|
$"Assets routed to these packages will fail to load! Check earlier console errors for SimulateBuild failures.");
|
}
|
Debug.Log($"[YooAssetService] Initialized {_packages.Count}/{allPkgs.Length} packages with PlayMode={playMode}. " +
|
$"Active: [{string.Join(", ", _packages.Keys)}]");
|
|
#if UNITY_WEBGL
|
// WebGL 诊断:输出每个包的 Manifest 和 Bundle 详情
|
DiagDumpPackageStatus();
|
#endif
|
}
|
|
/// <summary>
|
/// 初始化指定名称的额外资源包裹。
|
/// </summary>
|
public async UniTask InitializePackageAsync(string packageName, EPlayMode playMode,
|
IRemoteServices remoteServices = null)
|
{
|
if (_packages.ContainsKey(packageName))
|
{
|
Debug.LogWarning($"[YooAssetService] Package '{packageName}' already initialized.");
|
return;
|
}
|
|
// 优先复用已存在的包裹(可能由 Launch 阶段创建)
|
var package = YooAssets.TryGetPackage(packageName);
|
bool needManifest = false;
|
if (package == null)
|
{
|
package = YooAssets.CreatePackage(packageName);
|
var initParams = CreateInitParameters(playMode, remoteServices ?? _remoteServices, packageName);
|
var initOp = package.InitializeAsync(initParams);
|
await initOp.ToUniTask();
|
|
if (initOp.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogError($"[YooAssetService] Initialize package '{packageName}' failed: {initOp.Error}");
|
throw new InvalidOperationException($"YooAsset package '{packageName}' initialization failed: {initOp.Error}");
|
}
|
needManifest = true;
|
}
|
|
_packages[packageName] = package;
|
|
// 确保 ActiveManifest 已加载
|
if (needManifest)
|
{
|
await RequestVersionAndUpdateForPackageAsync(packageName, package);
|
}
|
|
Debug.Log($"[YooAssetService] Package '{packageName}' initialized.");
|
}
|
|
private InitializeParameters CreateInitParameters(EPlayMode playMode, IRemoteServices remoteServices, string packageName)
|
{
|
switch (playMode)
|
{
|
case EPlayMode.EditorSimulateMode:
|
{
|
#if UNITY_EDITOR
|
var simulateResult = EditorSimulateModeHelper.SimulateBuild(packageName);
|
return new EditorSimulateModeParameters
|
{
|
EditorFileSystemParameters = FileSystemParameters
|
.CreateDefaultEditorFileSystemParameters(simulateResult.PackageRootDirectory)
|
};
|
#else
|
throw new InvalidOperationException("EditorSimulateMode is only available in Unity Editor.");
|
#endif
|
}
|
case EPlayMode.HostPlayMode:
|
{
|
return new HostPlayModeParameters
|
{
|
BuildinFileSystemParameters = FileSystemParameters
|
.CreateDefaultBuildinFileSystemParameters(),
|
CacheFileSystemParameters = FileSystemParameters
|
.CreateDefaultCacheFileSystemParameters(remoteServices)
|
};
|
}
|
case EPlayMode.OfflinePlayMode:
|
{
|
return new OfflinePlayModeParameters
|
{
|
BuildinFileSystemParameters = FileSystemParameters
|
.CreateDefaultBuildinFileSystemParameters()
|
};
|
}
|
case EPlayMode.WebPlayMode:
|
{
|
var webParams = new WebPlayModeParameters();
|
#if UNITY_WEBGL && WEIXINMINIGAME && !UNITY_EDITOR
|
string packageRoot = $"{WeChatWASM.WX.env.USER_DATA_PATH}/__GAME_FILE_CACHE";
|
webParams.WebServerFileSystemParameters = WechatFileSystemCreater
|
.CreateFileSystemParameters(packageRoot, remoteServices);
|
#elif UNITY_WEBGL && DOUYINMINIGAME && !UNITY_EDITOR
|
string packageRoot = TTSDK.TTFileSystem.USER_DATA_PATH + "/__GAME_FILE_CACHE";
|
webParams.WebServerFileSystemParameters = TiktokFileSystemCreater
|
.CreateFileSystemParameters(packageRoot, remoteServices);
|
#else
|
webParams.WebServerFileSystemParameters = FileSystemParameters
|
.CreateDefaultWebServerFileSystemParameters();
|
if (remoteServices != null)
|
{
|
webParams.WebRemoteFileSystemParameters = FileSystemParameters
|
.CreateDefaultWebRemoteFileSystemParameters(remoteServices);
|
}
|
#endif
|
return webParams;
|
}
|
default:
|
throw new ArgumentOutOfRangeException(nameof(playMode), playMode, "Unsupported PlayMode.");
|
}
|
}
|
|
/// <summary>
|
/// 对单个包裹执行版本请求和 Manifest 更新。
|
/// 所有运行模式(包括 EditorSimulateMode)都需要此步骤来填充 ActiveManifest。
|
/// </summary>
|
private async UniTask RequestVersionAndUpdateForPackageAsync(string pkgName, ResourcePackage package)
|
{
|
try
|
{
|
var versionOp = package.RequestPackageVersionAsync();
|
await versionOp.ToUniTask();
|
|
if (versionOp.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogWarning($"[YooAssetService] RequestPackageVersion failed for '{pkgName}': {versionOp.Error}");
|
return;
|
}
|
|
var manifestOp = package.UpdatePackageManifestAsync(versionOp.PackageVersion);
|
await manifestOp.ToUniTask();
|
|
if (manifestOp.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogWarning($"[YooAssetService] UpdatePackageManifest failed for '{pkgName}': {manifestOp.Error}");
|
return;
|
}
|
|
Debug.Log($"[YooAssetService] Package '{pkgName}' manifest loaded (version={versionOp.PackageVersion}).");
|
}
|
catch (Exception ex)
|
{
|
Debug.LogWarning($"[YooAssetService] RequestVersionAndUpdate for '{pkgName}' exception: {ex.Message}");
|
}
|
}
|
|
// ====================================================================
|
// Asset Loading
|
// ====================================================================
|
|
/// <summary>
|
/// 资源加载重试配置
|
/// </summary>
|
private const int MAX_RETRY_COUNT = 3;
|
private const int BASE_RETRY_DELAY_MS = 500; // 500ms, 1000ms, 2000ms (exponential)
|
|
/// <summary>
|
/// 带重试的异步操作执行器。
|
/// 使用指数退避策略(500ms → 1000ms → 2000ms)。
|
/// </summary>
|
/// <param name="operation">要执行的异步操作</param>
|
/// <param name="operationName">操作名称(用于日志)</param>
|
/// <param name="ct">取消令牌</param>
|
/// <returns>操作结果</returns>
|
private async UniTask<T> ExecuteWithRetryAsync<T>(
|
Func<UniTask<T>> operation,
|
string operationName,
|
CancellationToken ct = default)
|
{
|
Exception lastException = null;
|
|
for (int attempt = 0; attempt <= MAX_RETRY_COUNT; attempt++)
|
{
|
try
|
{
|
ct.ThrowIfCancellationRequested();
|
return await operation();
|
}
|
catch (OperationCanceledException)
|
{
|
throw; // Don't retry cancellations
|
}
|
catch (Exception ex)
|
{
|
lastException = ex;
|
if (attempt < MAX_RETRY_COUNT)
|
{
|
int delayMs = BASE_RETRY_DELAY_MS * (1 << attempt); // Exponential backoff
|
Debug.LogWarning($"[YooAssetService] {operationName} failed (attempt {attempt + 1}/{MAX_RETRY_COUNT + 1}), retrying in {delayMs}ms: {ex.Message}");
|
await UniTask.Delay(delayMs, cancellationToken: ct);
|
}
|
}
|
}
|
|
Debug.LogError($"[YooAssetService] {operationName} failed after {MAX_RETRY_COUNT + 1} attempts: {lastException?.Message}");
|
return default;
|
}
|
|
private void ThrowIfNotInitialized()
|
{
|
if (!_isInitialized)
|
throw new InvalidOperationException("[YooAssetService] Service not initialized. Call InitializeAsync first.");
|
}
|
|
/// <summary>
|
/// 根据资源路径查找应使用的 ResourcePackage。
|
/// 使用 YooAssetPackageConfig 路由表确定目标包,找不到则回退到默认包。
|
/// </summary>
|
private ResourcePackage FindPackageForAsset(string location)
|
{
|
var packageName = YooAssetPackageConfig.GetPackageForLocation(location);
|
if (_packages.TryGetValue(packageName, out var package))
|
return package;
|
|
// 路由到的包尚未初始化,回退到默认包 — 发出明确警告
|
Debug.LogWarning($"[YooAssetService] Package '{packageName}' not available for location '{location}'. " +
|
$"Available packages: [{string.Join(", ", _packages.Keys)}]. " +
|
$"Falling back to default package '{_defaultPackage?.PackageName ?? "NULL"}'." +
|
$"\n → This usually means SimulateBuild failed for '{packageName}'. Check earlier console errors.");
|
return _defaultPackage;
|
}
|
|
/// <inheritdoc />
|
public async UniTask<T> LoadAssetAsync<T>(string location, uint priority = 0,
|
CancellationToken ct = default) where T : UnityEngine.Object
|
{
|
ThrowIfNotInitialized();
|
|
if (string.IsNullOrEmpty(location))
|
{
|
Debug.LogError("[YooAssetService] LoadAssetAsync: location is null or empty.");
|
return null;
|
}
|
|
var package = FindPackageForAsset(location);
|
return await ExecuteWithRetryAsync(async () =>
|
{
|
var handle = package.LoadAssetAsync<T>(location, priority);
|
await handle.ToUniTask(cancellationToken: ct);
|
|
if (handle.Status != EOperationStatus.Succeed)
|
{
|
throw new InvalidOperationException($"LoadAssetAsync failed for '{location}': {handle.LastError}");
|
}
|
|
return handle.GetAssetObject<T>();
|
}, $"LoadAssetAsync<{typeof(T).Name}>('{location}')", ct);
|
}
|
|
public void UnloadAsset(string location)
|
{
|
ThrowIfNotInitialized();
|
|
if (string.IsNullOrEmpty(location))
|
{
|
Debug.LogError("[YooAssetService] UnloadAsset: location is null or empty.");
|
return;
|
}
|
|
var package = FindPackageForAsset(location);
|
|
if (null != package)
|
{
|
YooAsset.AssetInfo assetInfo = package.GetAssetInfo(location);
|
|
if (null != assetInfo)
|
{
|
package.TryUnloadUnusedAsset(assetInfo);
|
}
|
}
|
}
|
|
/// <inheritdoc />
|
public async UniTask<UnityEngine.Object> LoadAssetAsync(string location, Type type, uint priority = 0,
|
CancellationToken ct = default)
|
{
|
ThrowIfNotInitialized();
|
|
if (string.IsNullOrEmpty(location))
|
{
|
Debug.LogError("[YooAssetService] LoadAssetAsync: location is null or empty.");
|
return null;
|
}
|
|
var package = FindPackageForAsset(location);
|
|
// Debug.LogError($"[YooAssetService] LoadAssetAsync: Loading asset '{location}' of package '{package.PackageName}'.");
|
|
return await ExecuteWithRetryAsync(async () =>
|
{
|
var handle = package.LoadAssetAsync(location, type, priority);
|
await handle.ToUniTask(cancellationToken: ct);
|
|
if (handle.Status != EOperationStatus.Succeed)
|
{
|
throw new InvalidOperationException($"LoadAssetAsync failed for '{location}': {handle.LastError}");
|
}
|
|
return handle.AssetObject;
|
}, $"LoadAssetAsync('{location}', {type.Name})", ct);
|
}
|
|
/// <summary>
|
/// 同步加载资产(仅在非 WebGL 平台过渡期使用)。
|
/// </summary>
|
[System.Obsolete("Use LoadAssetAsync instead. Sync loading will be removed in US2.")]
|
public T LoadAssetSync<T>(string location) where T : UnityEngine.Object
|
{
|
throw new NotSupportedException("同步资源加载接口已禁用,请使用异步API");
|
}
|
|
/// <inheritdoc />
|
public async UniTask<SubAssetsHandle> LoadSubAssetsAsync<T>(string location, uint priority = 0,
|
CancellationToken ct = default) where T : UnityEngine.Object
|
{
|
ThrowIfNotInitialized();
|
|
var package = FindPackageForAsset(location);
|
var handle = package.LoadSubAssetsAsync<T>(location, priority);
|
await handle.ToUniTask();
|
ct.ThrowIfCancellationRequested();
|
|
if (handle.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogError($"[YooAssetService] LoadSubAssetsAsync failed for '{location}': {handle.LastError}");
|
}
|
|
return handle;
|
}
|
|
/// <inheritdoc />
|
public async UniTask<AllAssetsHandle> LoadAllAssetsAsync<T>(string location, uint priority = 0,
|
CancellationToken ct = default) where T : UnityEngine.Object
|
{
|
ThrowIfNotInitialized();
|
|
var package = FindPackageForAsset(location);
|
var handle = package.LoadAllAssetsAsync<T>(location, priority);
|
await handle.ToUniTask();
|
ct.ThrowIfCancellationRequested();
|
|
if (handle.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogError($"[YooAssetService] LoadAllAssetsAsync failed for '{location}': {handle.LastError}");
|
}
|
|
return handle;
|
}
|
|
// ====================================================================
|
// RawFile Loading
|
// ====================================================================
|
|
/// <inheritdoc />
|
public async UniTask<string> LoadRawFileTextAsync(string location, CancellationToken ct = default)
|
{
|
ThrowIfNotInitialized();
|
|
var rawPackage = FindPackageForAsset(location);
|
return await ExecuteWithRetryAsync(async () =>
|
{
|
var handle = rawPackage.LoadRawFileAsync(location);
|
await handle.ToUniTask(cancellationToken: ct);
|
|
if (handle.Status != EOperationStatus.Succeed)
|
{
|
throw new InvalidOperationException($"LoadRawFileTextAsync failed for '{location}': {handle.LastError}");
|
}
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
// 真机:StreamingAssets在apk内,需用UnityWebRequest读取
|
string filePath = handle.GetRawFilePath();
|
using var uwr = UnityEngine.Networking.UnityWebRequest.Get(filePath);
|
await uwr.SendWebRequest().ToUniTask(cancellationToken: ct);
|
if (uwr.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
|
throw new InvalidOperationException($"LoadRawFileTextAsync UWR failed for '{location}': {uwr.error}");
|
return uwr.downloadHandler.text;
|
#else
|
return handle.GetRawFileText();
|
#endif
|
}, $"LoadRawFileTextAsync('{location}')", ct);
|
}
|
|
/// <inheritdoc />
|
public async UniTask<byte[]> LoadRawFileBytesAsync(string location, CancellationToken ct = default)
|
{
|
ThrowIfNotInitialized();
|
|
var rawPackage = FindPackageForAsset(location);
|
return await ExecuteWithRetryAsync(async () =>
|
{
|
var handle = rawPackage.LoadRawFileAsync(location);
|
await handle.ToUniTask(cancellationToken: ct);
|
|
if (handle.Status != EOperationStatus.Succeed)
|
{
|
throw new InvalidOperationException($"LoadRawFileBytesAsync failed for '{location}': {handle.LastError}");
|
}
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
// 真机:StreamingAssets在apk内,需用UnityWebRequest读取
|
string filePath = handle.GetRawFilePath();
|
using var uwr = UnityEngine.Networking.UnityWebRequest.Get(filePath);
|
await uwr.SendWebRequest().ToUniTask(cancellationToken: ct);
|
if (uwr.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
|
throw new InvalidOperationException($"LoadRawFileBytesAsync UWR failed for '{location}': {uwr.error}");
|
return uwr.downloadHandler.data;
|
#else
|
return handle.GetRawFileData();
|
#endif
|
}, $"LoadRawFileBytesAsync('{location}')", ct);
|
}
|
|
// ====================================================================
|
// Scene Loading
|
// ====================================================================
|
|
/// <inheritdoc />
|
public async UniTask<SceneHandle> LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single,
|
LocalPhysicsMode physicsMode = LocalPhysicsMode.None, bool suspendLoad = false, uint priority = 0, CancellationToken ct = default)
|
{
|
ThrowIfNotInitialized();
|
|
var package = FindPackageForAsset(location);
|
var handle = package.LoadSceneAsync(location, sceneMode, physicsMode, suspendLoad, priority);
|
await handle.ToUniTask();
|
ct.ThrowIfCancellationRequested();
|
|
if (handle.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogError($"[YooAssetService] LoadSceneAsync failed for '{location}': {handle.LastError}");
|
}
|
|
return handle;
|
}
|
|
// ====================================================================
|
// Query
|
// ====================================================================
|
|
/// <inheritdoc />
|
public bool CheckLocationValid(string location)
|
{
|
ThrowIfNotInitialized();
|
// 先用路由包检查,找不到则遍历所有包
|
var package = FindPackageForAsset(location);
|
if (package.CheckLocationValid(location))
|
return true;
|
foreach (var kvp in _packages)
|
{
|
if (kvp.Value != package && kvp.Value.CheckLocationValid(location))
|
return true;
|
}
|
return false;
|
}
|
|
/// <inheritdoc />
|
public YooAsset.AssetInfo[] GetAssetInfosByTag(string tag)
|
{
|
ThrowIfNotInitialized();
|
// 从所有包收集指定标签的资源信息
|
var allInfos = new List<YooAsset.AssetInfo>();
|
foreach (var kvp in _packages)
|
{
|
var infos = kvp.Value.GetAssetInfos(tag);
|
if (infos != null && infos.Length > 0)
|
allInfos.AddRange(infos);
|
}
|
return allInfos.ToArray();
|
}
|
|
/// <inheritdoc />
|
public bool IsNeedDownloadFromRemote(string location)
|
{
|
ThrowIfNotInitialized();
|
var package = FindPackageForAsset(location);
|
return package.IsNeedDownloadFromRemote(location);
|
}
|
|
// ====================================================================
|
// Download
|
// ====================================================================
|
|
/// <inheritdoc />
|
public async UniTask DownloadByTagsAsync(string[] tags, int downloadingMaxNumber = 10,
|
int failedTryAgain = 3, IProgress<float> progress = null, CancellationToken ct = default)
|
{
|
ThrowIfNotInitialized();
|
|
foreach (var tag in tags)
|
{
|
// 对所有包按标签创建下载器
|
foreach (var kvp in _packages)
|
{
|
var downloader = kvp.Value.CreateResourceDownloader(tag, downloadingMaxNumber, failedTryAgain);
|
if (downloader.TotalDownloadCount == 0)
|
continue;
|
|
downloader.BeginDownload();
|
while (!downloader.IsDone)
|
{
|
ct.ThrowIfCancellationRequested();
|
progress?.Report(downloader.Progress);
|
await UniTask.Yield();
|
}
|
|
if (downloader.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogError($"[YooAssetService] Download tag '{tag}' from package '{kvp.Key}' failed: {downloader.Error}");
|
throw new InvalidOperationException($"Resource download failed for tag '{tag}': {downloader.Error}");
|
}
|
}
|
}
|
|
progress?.Report(1f);
|
}
|
|
// ====================================================================
|
// Version Management
|
// ====================================================================
|
|
/// <inheritdoc />
|
public async UniTask<string> RequestPackageVersionAsync(CancellationToken ct = default)
|
{
|
ThrowIfNotInitialized();
|
|
var op = _defaultPackage.RequestPackageVersionAsync();
|
await op.ToUniTask();
|
ct.ThrowIfCancellationRequested();
|
|
if (op.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogError($"[YooAssetService] RequestPackageVersion failed: {op.Error}");
|
throw new InvalidOperationException($"Request package version failed: {op.Error}");
|
}
|
|
return op.PackageVersion;
|
}
|
|
/// <inheritdoc />
|
public async UniTask UpdatePackageManifestAsync(string packageVersion, CancellationToken ct = default)
|
{
|
ThrowIfNotInitialized();
|
|
var op = _defaultPackage.UpdatePackageManifestAsync(packageVersion);
|
await op.ToUniTask();
|
ct.ThrowIfCancellationRequested();
|
|
if (op.Status != EOperationStatus.Succeed)
|
{
|
Debug.LogError($"[YooAssetService] UpdatePackageManifest failed: {op.Error}");
|
throw new InvalidOperationException($"Update package manifest failed: {op.Error}");
|
}
|
}
|
|
// ====================================================================
|
// Release
|
// ====================================================================
|
|
/// <inheritdoc />
|
public void ReleaseHandle(HandleBase handle)
|
{
|
if (handle == null) return;
|
handle.Release();
|
}
|
|
/// <inheritdoc />
|
public async UniTask UnloadUnusedAssetsAsync()
|
{
|
ThrowIfNotInitialized();
|
// 对所有包执行卸载
|
foreach (var kvp in _packages)
|
{
|
var op = kvp.Value.UnloadUnusedAssetsAsync();
|
await op.ToUniTask();
|
}
|
}
|
|
/// <inheritdoc />
|
public async UniTask UnloadAllAssetsAsync()
|
{
|
ThrowIfNotInitialized();
|
// 对所有包执行卸载
|
foreach (var kvp in _packages)
|
{
|
var op = kvp.Value.UnloadAllAssetsAsync();
|
await op.ToUniTask();
|
}
|
}
|
|
// ====================================================================
|
// IYooAssetBridge Implementation
|
// ====================================================================
|
|
async UniTask<T> IYooAssetBridge.LoadAssetAsync<T>(string location)
|
{
|
return await LoadAssetAsync<T>(location);
|
}
|
|
async UniTask<string> IYooAssetBridge.LoadRawFileTextAsync(string location)
|
{
|
return await LoadRawFileTextAsync(location);
|
}
|
|
async UniTask<byte[]> IYooAssetBridge.LoadRawFileBytesAsync(string location)
|
{
|
return await LoadRawFileBytesAsync(location);
|
}
|
|
async UniTask IYooAssetBridge.PreloadAsync(string[] locations)
|
{
|
// 批量预加载,使用 UniTask.WhenAll 并行
|
var tasks = new List<UniTask>(locations.Length);
|
foreach (var loc in locations)
|
{
|
tasks.Add(LoadAssetAsync<UnityEngine.Object>(loc).AsUniTask());
|
}
|
await UniTask.WhenAll(tasks);
|
}
|
|
T IYooAssetBridge.GetCached<T>(string location)
|
{
|
// 委托给 ResourceCacheManager(US4 已集成)
|
if (ProjSG.Resource.ResourceCacheManager.IsValid())
|
{
|
return ProjSG.Resource.ResourceCacheManager.Instance.GetCached<T>(location);
|
}
|
return null;
|
}
|
|
// ====================================================================
|
// Sync Wrappers (Transitional — removed in US2)
|
// ====================================================================
|
|
/// <summary>
|
/// 同步加载所有同类型资源(过渡期使用)。
|
/// </summary>
|
[System.Obsolete("Use LoadAllAssetsAsync instead. Sync loading will be removed in US2.")]
|
public AllAssetsHandle LoadAllAssetsSync<T>(string location) where T : UnityEngine.Object
|
{
|
ThrowIfNotInitialized();
|
var package = FindPackageForAsset(location);
|
return package.LoadAllAssetsSync<T>(location);
|
}
|
|
#if UNITY_WEBGL
|
/// <summary>
|
/// WebGL 诊断:输出每个 YooAsset 包的初始化状态和资源数量。
|
/// </summary>
|
private void DiagDumpPackageStatus()
|
{
|
var sb = new System.Text.StringBuilder();
|
sb.AppendLine("[YooAssetService][WebGL-Diag] Package status dump:");
|
foreach (var kv in _packages)
|
{
|
var pkg = kv.Value;
|
var status = pkg.InitializeStatus;
|
// 尝试获取清单中的资源信息数量
|
int assetCount = 0;
|
try
|
{
|
// GetAssetInfos 返回该包所有资源信息
|
var infos = pkg.GetAssetInfos(string.Empty);
|
assetCount = infos != null ? infos.Length : -1;
|
}
|
catch
|
{
|
assetCount = -1;
|
}
|
sb.AppendLine($" [{kv.Key}] status={status}, assetInfoCount={assetCount}");
|
}
|
Debug.Log(sb.ToString());
|
}
|
#endif
|
}
|
}
|