using asap.core; using GameCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; public interface ILoadResourceService { /// /// 需要显示下载进度的并发下载 /// /// /// Task Load(string label); /// /// 需要显示下载进度的并发下载 /// /// /// Task Loads(List label); /// /// 不需要知道任何下载情况的后台静默排队下载,资源包队列下载 /// /// void EnqueueBundleSilently(List label); /// /// 登陆游戏预下载地图资源 /// /// /// /// Task Loading(List assetName, System.Action progress); /// /// 检查资源是否需要下载 /// /// /// Task CheckResource(string label); /// /// 后台排队下载,如果需要下载则排队下载等待 LoadEventResourceEvent 事件,否则直接返回true,传入 List 为组队列下载 /// /// /// /// Task CheckResourceLoadQueue(string key, List label); string curName { get; } string oldName { get; } } public class LoadResourceData { public string name; public AsyncOperationHandle downloadOp; } public struct LoadEventResourceEvent { public string key; public List label; public bool isSucceeded; } public class LoadProgressEvent { public string name; public float progress; public float totalDownloadSize; public bool isSucceeded; public bool isFailed; } public class LoadResourceService : ILoadResourceService { [Inject] public IConfig config { get; set; } Dictionary loadResourceDatas = new Dictionary(); LinkedList loadQueue = new LinkedList(); LinkedList loadEventQueue = new LinkedList(); bool LoadEventResourceing = false; Dictionary loadRecord = new Dictionary(); //检查资源是否需要更新 public string curName { get; private set; } public string oldName { get; private set; } /// /// 登陆游戏预下载地图资源 /// /// /// 进度 /// public async Task Loading(List assetName, System.Action progress) { if (!loadResourceDatas.ContainsKey(assetName[0])) { var locationsOp = Addressables.LoadResourceLocationsAsync(assetName, Addressables.MergeMode.Union); var locations = await locationsOp.Task; var totalDownloadSize = await Addressables.GetDownloadSizeAsync(locations).Task; if (totalDownloadSize > 1024) { AsyncOperationHandle download = Addressables.DownloadDependenciesAsync(locations.Select(_ => _.PrimaryKey), Addressables.MergeMode.Union); loadResourceDatas[assetName[0]] = download; } Addressables.Release(locationsOp); } bool result = true; if (loadResourceDatas.ContainsKey(assetName[0])) { #if AGG using (var e = GEvent.GameEvent("Preload_start")) { e.AddContent("expected_assets", string.Join(',', assetName)); } #endif float percent = 0; LoadingScreen.SetLoadingInfo(LocalizationMgr.GetText("UI_ToastPanel_26")); AsyncOperationHandle downloadOp = loadResourceDatas[assetName[0]]; DateTime startTime = DateTime.Now; LoadingScreen.SetSubProgress(0, ""); LoadingScreen.ShowSubProgressBar(); while (!downloadOp.IsDone && downloadOp.Status != AsyncOperationStatus.Failed) { var downloadStatus = downloadOp.GetDownloadStatus(); if (downloadStatus.Percent > percent) percent = downloadStatus.Percent; progress?.Invoke(percent); LoadingScreen.SetSubProgress(percent, GetSizeByBytes(downloadStatus.TotalBytes, percent)); await Awaiters.NextFrame; } LoadingScreen.HideSubProgressBar(); loadResourceDatas.Remove(assetName[0]); if (downloadOp.Status == AsyncOperationStatus.Failed || downloadOp.OperationException != null) { result = false; } DateTime endTime = DateTime.Now; Debug.Log($"Preload finished, result:{result}, time cost:{(endTime - startTime).TotalSeconds}s"); #if AGG using (var e = GEvent.GameEvent("Preload_finish")) { e.AddContent("status", result ? "Succeeded" : "Failed") .AddContent("duration_ms", (endTime - startTime).TotalMilliseconds); if (downloadOp.OperationException != null) { e.AddContent("error", downloadOp.OperationException.Message); } } #endif Addressables.Release(downloadOp); } progress?.Invoke(1); return result; } private const float byte2mb = 1024 * 1024; private string GetSizeByBytes(long total, float percent) { return $"{(percent * total / byte2mb).ToString("0.0")}MB/{(total / byte2mb).ToString("0.0")}MB"; } private string GetSizeByBytes(long total) { return $"{(total / byte2mb).ToString("0.00")}MB"; } /// /// 多线程下载资源 下载界面调用 /// public async Task Load(string label) { try { oldName = label; if (loadResourceDatas.ContainsKey(label)) { curName = label; return false; } var locationsOp = Addressables.LoadResourceLocationsAsync(label); GameCore.UIManager.ShowWaitingBlock("LoadResourceService::Load"); var locations = await locationsOp.Task; var totalDownloadSize = await Addressables.GetDownloadSizeAsync(locations).Task; GameCore.UIManager.HideWaitingBlock("LoadResourceService::Load"); if (totalDownloadSize > 1) { Load(label, totalDownloadSize, locations.Select(_ => _.PrimaryKey).ToList()); return false; } Addressables.Release(locationsOp); return true; } catch (System.Exception e) { GameCore.UIManager.HideWaitingBlock("LoadResourceService::Load"); Debug.LogError(e); } return false; } /// /// 多线程下载资源 下载界面调用 /// /// /// /// public async Task Loads(List labels) { try { oldName = labels[0]; if (loadResourceDatas.ContainsKey(labels[0])) { curName = labels[0]; return false; } var locationsOp = Addressables.LoadResourceLocationsAsync(labels, Addressables.MergeMode.Union); GameCore.UIManager.ShowWaitingBlock("LoadResourceService::Loads"); var locations = await locationsOp.Task; var totalDownloadSize = await Addressables.GetDownloadSizeAsync(locations).Task; GameCore.UIManager.HideWaitingBlock("LoadResourceService::Loads"); if (totalDownloadSize > 1) { Load(labels[0], totalDownloadSize, locations.Select(_ => _.PrimaryKey).ToList()); return false; } Addressables.Release(locationsOp); return true; } catch (System.Exception e) { GameCore.UIManager.HideWaitingBlock("LoadResourceService::Loads"); Debug.LogError(e); } return false; } /// /// 多线程下载资源 下载界面调用 /// /// /// /// async void Load(string name, float totalDownloadSize, IList keys) { try { curName = name; AsyncOperationHandle downloadOp = Addressables.DownloadDependenciesAsync(keys, Addressables.MergeMode.Union); loadResourceDatas[name] = downloadOp; LoadProgressEvent loadProgressEvent = new LoadProgressEvent() { name = name, progress = downloadOp.GetDownloadStatus().Percent, totalDownloadSize = totalDownloadSize }; while (!downloadOp.IsDone && downloadOp.Status != AsyncOperationStatus.Failed) { if (name == curName) { loadProgressEvent.progress = downloadOp.GetDownloadStatus().Percent; loadProgressEvent.totalDownloadSize = totalDownloadSize; GContext.Publish(loadProgressEvent); } await Awaiters.Seconds(0.2f); } if (name == curName) { if (downloadOp.Status == AsyncOperationStatus.Succeeded) { loadProgressEvent.progress = 1.0001f; loadProgressEvent.isSucceeded = true; } else { loadProgressEvent.isFailed = true; } loadProgressEvent.totalDownloadSize = totalDownloadSize; GContext.Publish(loadProgressEvent); } try { OnLoadGameEvent(downloadOp, string.Join(',', keys)); } catch (Exception e) { Debug.LogError(e); } Addressables.Release(downloadOp); } catch (System.Exception e) { Debug.LogError(e); } loadResourceDatas.Remove(name); } /// /// 检查某一资源是否需要下载 /// /// /// public async Task CheckResource(string label) { try { var locationsOp = Addressables.LoadResourceLocationsAsync(label); GameCore.UIManager.ShowWaitingBlock("LoadResourceService::Load"); var locations = await locationsOp.Task; var totalDownloadSize = await Addressables.GetDownloadSizeAsync(locations).Task; GameCore.UIManager.HideWaitingBlock("LoadResourceService::Load"); Addressables.Release(locationsOp); if (totalDownloadSize > 1) { return false; } return true; } catch (System.Exception e) { GameCore.UIManager.HideWaitingBlock("LoadResourceService::Load"); Debug.LogError(e); } return false; } /// /// 后台静默资源下载排队,一传一大坨,然后一个包一个包排队下载 /// /// public async void EnqueueBundleSilently(List label) { var locationsOp = Addressables.LoadResourceLocationsAsync(label, Addressables.MergeMode.Union); var locations = await locationsOp.Task; var totalDownloadSize = await Addressables.GetDownloadSizeAsync(locations).Task; if (totalDownloadSize > 0) { for (int i = 0; locations.Count > i; i++) { //判断 loadQueue 是否包含当前资源 if (loadQueue.Contains(locations[i].PrimaryKey)) { continue; } loadQueue.AddLast(locations[i].PrimaryKey); if (loadQueue.Count == 1) { LoadKey(); } } } Addressables.Release(locationsOp); } // 排队下载资源,后台静默下载 async void LoadKey() { while (loadQueue.Count > 0) { string key = loadQueue.First.Value; AsyncOperationHandle downloadOp = Addressables.DownloadDependenciesAsync(key); await downloadOp.Task; OnLoadGameEvent(downloadOp, key); Addressables.Release(downloadOp); await Awaiters.NextFrame; loadQueue.RemoveFirst(); } } System.Threading.SemaphoreSlim _asyncLock { set; get; } /// /// 检查资源下载排队,一传一组,分组下载,如果需要下载则排队下载等待 LoadEventResourceEvent 事件,否则直接返回true /// /// /// public async Task CheckResourceLoadQueue(string key, List label) { bool NoCheckResource = config.Get("NoCheckResource", "0") == "1"; if (NoCheckResource) return true; _asyncLock ??= new System.Threading.SemaphoreSlim(1, 1); bool _isLockAcquired = false; try { if (loadRecord.ContainsKey(key)) { return loadRecord[key]; } await _asyncLock.WaitAsync(); _isLockAcquired = true; var locationsOp = Addressables.LoadResourceLocationsAsync(label, Addressables.MergeMode.Union); var locations = await locationsOp.Task; var totalDownloadSize = await Addressables.GetDownloadSizeAsync(locations).Task; Addressables.Release(locationsOp); if (totalDownloadSize > 1) { loadRecord[key] = false; LoadEventResourceEvent loadEventData = new LoadEventResourceEvent(); loadEventData.key = key; loadEventData.label = label; if (loadEventData.label == null || loadEventData.label.Count == 0) return false; loadEventQueue.AddLast(loadEventData); if (loadEventQueue.Count == 1) { LoadEventKey(); } return false; } loadRecord[key] = true; return true; } catch (System.Exception e) { loadRecord.Remove(key); Debug.LogError(e); } finally { if (_isLockAcquired) { _asyncLock.Release(); _isLockAcquired = false; } } return false; } async void LoadEventKey() { LoadEventResourceing = true; while (loadEventQueue.Count > 0) { LoadEventResourceEvent loadEventData = loadEventQueue.First.Value; if (loadEventData.label == null || loadEventData.label.Count == 0) { loadEventQueue.RemoveFirst(); continue; } try { var locationsOp = Addressables.LoadResourceLocationsAsync(loadEventData.label, Addressables.MergeMode.Union); var locations = await locationsOp.Task; Addressables.Release(locationsOp); // 检查locations是否有效 if (locations == null || locations.Count == 0) { loadEventData.isSucceeded = false; GContext.Publish(loadEventData); loadEventQueue.RemoveFirst(); continue; } var totalDownloadSize = await Addressables.GetDownloadSizeAsync(locations).Task; if (totalDownloadSize > 0) { var keys = locations.Select(_ => _.PrimaryKey).ToList(); string keyStr = string.Join(",", keys); Debug.Log($"资源名称:{keyStr}"); AsyncOperationHandle downloadOp = Addressables.DownloadDependenciesAsync(keys, Addressables.MergeMode.Union); await downloadOp.Task; await Awaiters.NextFrame; OnLoadGameEvent(downloadOp, keyStr); bool isSucceeded = downloadOp.Status == AsyncOperationStatus.Succeeded; loadEventData.isSucceeded = isSucceeded; GContext.Publish(loadEventData); if (isSucceeded) { loadRecord[loadEventData.key] = isSucceeded; } else { loadRecord.Remove(loadEventData.key); } Addressables.Release(downloadOp); } else { Debug.Log($"资源大小:{0}"); await Awaiters.NextFrame; loadEventData.isSucceeded = true; GContext.Publish(loadEventData); loadRecord[loadEventData.key] = true; } loadEventQueue.RemoveFirst(); } catch (Exception e) { Debug.LogError($"资源下载: {e.Message} {e.StackTrace}"); loadEventData.isSucceeded = false; GContext.Publish(loadEventData); loadRecord.Remove(loadEventData.key); loadEventQueue.RemoveFirst(); Debug.Log($"错误下载活动资源队列 count == {loadEventQueue.Count}"); } } } void OnLoadGameEvent(AsyncOperationHandle downloadOp, string key) { if (downloadOp.Status == AsyncOperationStatus.Failed || downloadOp.OperationException != null) { Exception exception = downloadOp.OperationException; using (var e = GEvent.GameEvent("resource_load_finish")) { e.AddContent("status", "Failed") .AddContent("keys", key); if (exception != null) { e.AddContent("error", exception.Message); } } } } }