Files
MinFt/Client/Assets/Scripts/Services/LoadResourceService.cs
2026-04-27 12:07:32 +08:00

515 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 需要显示下载进度的并发下载
/// </summary>
/// <param name="label"></param>
/// <returns></returns>
Task<bool> Load(string label);
/// <summary>
/// 需要显示下载进度的并发下载
/// </summary>
/// <param name="label"></param>
/// <returns></returns>
Task<bool> Loads(List<string> label);
/// <summary>
/// 不需要知道任何下载情况的后台静默排队下载,资源包队列下载
/// </summary>
/// <param name="label"></param>
void EnqueueBundleSilently(List<string> label);
/// <summary>
/// 登陆游戏预下载地图资源
/// </summary>
/// <param name="assetName"></param>
/// <param name="progress"></param>
/// <returns></returns>
Task<bool> Loading(List<string> assetName, System.Action<float> progress);
/// <summary>
/// 检查资源是否需要下载
/// </summary>
/// <param name="label"></param>
/// <returns></returns>
Task<bool> CheckResource(string label);
/// <summary>
/// 后台排队下载,如果需要下载则排队下载等待 LoadEventResourceEvent 事件否则直接返回true传入 List 为组队列下载
/// </summary>
/// <param name="key"></param>
/// <param name="label"></param>
/// <returns></returns>
Task<bool> CheckResourceLoadQueue(string key, List<string> label);
string curName { get; }
string oldName { get; }
}
public class LoadResourceData
{
public string name;
public AsyncOperationHandle downloadOp;
}
public struct LoadEventResourceEvent
{
public string key;
public List<string> 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<string, AsyncOperationHandle> loadResourceDatas = new Dictionary<string, AsyncOperationHandle>();
LinkedList<string> loadQueue = new LinkedList<string>();
LinkedList<LoadEventResourceEvent> loadEventQueue = new LinkedList<LoadEventResourceEvent>();
bool LoadEventResourceing = false;
Dictionary<string, bool> loadRecord = new Dictionary<string, bool>();
//检查资源是否需要更新
public string curName { get; private set; }
public string oldName { get; private set; }
/// <summary>
/// 登陆游戏预下载地图资源
/// </summary>
/// <param name="assetName"></param>
/// <param name="progress"> 进度 </param>
/// <returns></returns>
public async Task<bool> Loading(List<string> assetName, System.Action<float> 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";
}
/// <summary>
/// 多线程下载资源 下载界面调用
/// </summary>
public async Task<bool> 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;
}
/// <summary>
/// 多线程下载资源 下载界面调用
/// </summary>
/// <param name="name"></param>
/// <param name="totalDownloadSize"></param>
/// <param name="keys"></param>
public async Task<bool> Loads(List<string> 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;
}
/// <summary>
/// 多线程下载资源 下载界面调用
/// </summary>
/// <param name="name"></param>
/// <param name="totalDownloadSize"></param>
/// <param name="keys"></param>
async void Load(string name, float totalDownloadSize, IList<string> 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);
}
/// <summary>
/// 检查某一资源是否需要下载
/// </summary>
/// <param name="label"></param>
/// <returns></returns>
public async Task<bool> 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;
}
/// <summary>
/// 后台静默资源下载排队,一传一大坨,然后一个包一个包排队下载
/// </summary>
/// <param name="label"></param>
public async void EnqueueBundleSilently(List<string> 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; }
/// <summary>
/// 检查资源下载排队,一传一组,分组下载,如果需要下载则排队下载等待 LoadEventResourceEvent 事件否则直接返回true
/// </summary>
/// <param name="label"></param>
/// <returns></returns>
public async Task<bool> CheckResourceLoadQueue(string key, List<string> 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);
}
}
}
}
}