Compare commits

..

No commits in common. "76cc128c4f9744150c2af60d5b2b06fc20249059" and "d2b3a36da64fdc7d969b7a5cab8dd69d8f99be36" have entirely different histories.

6 changed files with 367 additions and 458 deletions

View File

@ -8,20 +8,17 @@ namespace AlicizaX.Resource.Runtime
public partial class ResourceExtComponent public partial class ResourceExtComponent
{ {
private static IResourceModule _resourceModule; private static IResourceModule _resourceModule;
private LoadAssetCallbacks _loadAssetCallbacks;
public static IResourceModule ResourceModule => _resourceModule; public static IResourceModule ResourceModule => _resourceModule;
private class LoadingState : IMemory private class LoadingState : IMemory
{ {
public CancellationTokenSource Cts { get; set; } public CancellationTokenSource Cts { get; set; }
public CancellationTokenRegistration Registration { get; set; }
public string Location { get; set; } public string Location { get; set; }
public void Clear() public void Clear()
{ {
Registration.Dispose();
Registration = default;
if (Cts != null) if (Cts != null)
{ {
Cts.Cancel(); Cts.Cancel();
@ -29,44 +26,64 @@ namespace AlicizaX.Resource.Runtime
Cts = null; Cts = null;
} }
Location = string.Empty; Location = String.Empty;
} }
} }
private readonly Dictionary<UnityEngine.Object, LoadingState> _loadingStates = new Dictionary<UnityEngine.Object, LoadingState>(); private static readonly Dictionary<UnityEngine.Object, LoadingState> _loadingStates = new Dictionary<UnityEngine.Object, LoadingState>();
private void InitializedResources() private void InitializedResources()
{ {
_resourceModule = ModuleSystem.GetModule<IResourceModule>(); _resourceModule = ModuleSystem.GetModule<IResourceModule>();
_loadAssetCallbacks = new LoadAssetCallbacks(OnLoadAssetSuccess, OnLoadAssetFailure);
}
private void OnLoadAssetFailure(string assetName, LoadResourceStatus status, string errormessage, object userdata)
{
_assetLoadingList.Remove(assetName);
ISetAssetObject setAssetObject = (ISetAssetObject)userdata;
if (setAssetObject != null)
{
ClearLoadingState(setAssetObject.TargetObject);
}
Log.Error("Can not load asset from '{0}' with error message '{1}'.", assetName, errormessage);
} }
private void OnLoadAssetSuccess(string assetName, object asset, float duration, object userdata) private void OnLoadAssetSuccess(string assetName, object asset, float duration, object userdata)
{ {
_assetLoadingList.Remove(assetName);
ISetAssetObject setAssetObject = (ISetAssetObject)userdata; ISetAssetObject setAssetObject = (ISetAssetObject)userdata;
if (setAssetObject == null)
{
return;
}
UnityEngine.Object assetObject = asset as UnityEngine.Object; UnityEngine.Object assetObject = asset as UnityEngine.Object;
if (assetObject == null)
{
Log.Error($"Load failure asset type is {asset?.GetType()}.");
return;
}
if (IsCurrentLocation(setAssetObject.TargetObject, setAssetObject.Location)) if (assetObject != null)
{ {
ClearLoadingState(setAssetObject.TargetObject); // 检查资源是否仍然是当前需要的。
SetAsset(setAssetObject, TrackLoadedAsset(setAssetObject.Location, assetObject)); if (IsCurrentLocation(setAssetObject.TargetObject, setAssetObject.Location))
{
ClearLoadingState(setAssetObject.TargetObject);
_assetItemPool.Register(AssetItemObject.Create(setAssetObject.Location, assetObject), true);
SetAsset(setAssetObject, assetObject);
}
else
{
// 资源已经过期,卸载。
_resourceModule.UnloadAsset(assetObject);
}
} }
else else
{ {
_resourceModule.UnloadAsset(assetObject); Log.Error($"Load failure asset type is {asset.GetType()}.");
} }
} }
public async UniTaskVoid SetAssetByResources<T>(ISetAssetObject setAssetObject, CancellationToken cancellationToken) where T : UnityEngine.Object /// <summary>
/// 通过Unity对象加载资源。
/// </summary>
/// <param name="setAssetObject">ISetAssetObject。</param>
/// <typeparam name="T">Unity对象类型。</typeparam>
public async UniTaskVoid SetAssetByResources<T>(ISetAssetObject setAssetObject,CancellationToken cancellationToken) where T : UnityEngine.Object
{ {
var target = setAssetObject.TargetObject; var target = setAssetObject.TargetObject;
var location = setAssetObject.Location; var location = setAssetObject.Location;
@ -76,55 +93,62 @@ namespace AlicizaX.Resource.Runtime
return; return;
} }
// 取消并清理旧的加载请求。
CancelAndCleanupOldRequest(target); CancelAndCleanupOldRequest(target);
var linkedTokenSource = new CancellationTokenSource(); // 创建新的加载状态
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var loadingState = MemoryPool.Acquire<LoadingState>(); var loadingState = MemoryPool.Acquire<LoadingState>();
loadingState.Cts = linkedTokenSource; loadingState.Cts = linkedTokenSource;
if (cancellationToken.CanBeCanceled)
{
loadingState.Registration = cancellationToken.Register(static state =>
{
((CancellationTokenSource)state).Cancel();
}, linkedTokenSource);
}
loadingState.Location = location; loadingState.Location = location;
_loadingStates[target] = loadingState; _loadingStates[target] = loadingState;
try try
{ {
// 等待其他可能正在进行的加载。
await TryWaitingLoading(location).AttachExternalCancellation(linkedTokenSource.Token);
// 再次检查是否被新请求替换。
if (!IsCurrentLocation(target, location)) if (!IsCurrentLocation(target, location))
{ {
return; return;
} }
// 检查缓存。
if (_assetItemPool.CanSpawn(location)) if (_assetItemPool.CanSpawn(location))
{ {
ClearLoadingState(target); ClearLoadingState(target);
var assetObject = (T)_assetItemPool.Spawn(location).Target; var assetObject = (T)_assetItemPool.Spawn(location).Target;
SetAsset(setAssetObject, assetObject); SetAsset(setAssetObject, assetObject);
return;
} }
else
if (!IsCurrentLocation(target, location))
{ {
return; // 最后一次检查是否被替换。
} if (!IsCurrentLocation(target, location))
{
return;
}
T resource = await _resourceModule.LoadAssetAsync<T>(location, linkedTokenSource.Token); // 防止重复加载同一资源。
if (resource == null) if (!_assetLoadingList.Add(location))
{ {
ClearLoadingState(target); // 已经在加载中,等待回调处理。
return; return;
} }
OnLoadAssetSuccess(location, resource, 0f, setAssetObject); T resource = await _resourceModule.LoadAssetAsync<T>(location, linkedTokenSource.Token);
if (resource != null)
{
_loadAssetCallbacks?.LoadAssetSuccessCallback.Invoke(location,resource, 0f, setAssetObject);
}
_assetLoadingList.Remove(location);
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
ClearLoadingState(target); // 请求被取消,正常情况,无需处理。
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -133,6 +157,10 @@ namespace AlicizaX.Resource.Runtime
} }
} }
/// <summary>
/// 取消并清理旧的加载请求。
/// <param name="target">Unity对象。</param>
/// </summary>
private void CancelAndCleanupOldRequest(UnityEngine.Object target) private void CancelAndCleanupOldRequest(UnityEngine.Object target)
{ {
if (_loadingStates.TryGetValue(target, out var oldState)) if (_loadingStates.TryGetValue(target, out var oldState))
@ -142,6 +170,10 @@ namespace AlicizaX.Resource.Runtime
} }
} }
/// <summary>
/// 清理加载状态。
/// <param name="target">Unity对象。</param>
/// </summary>
private void ClearLoadingState(UnityEngine.Object target) private void ClearLoadingState(UnityEngine.Object target)
{ {
if (_loadingStates.TryGetValue(target, out var state)) if (_loadingStates.TryGetValue(target, out var state))
@ -151,33 +183,23 @@ namespace AlicizaX.Resource.Runtime
} }
} }
/// <summary>
/// 检查指定位置是否仍是该目标的当前加载位置。
/// </summary>
private bool IsCurrentLocation(UnityEngine.Object target, string location) private bool IsCurrentLocation(UnityEngine.Object target, string location)
{ {
if (target == null) if (target == null)
{ {
return false; return false;
} }
return _loadingStates.TryGetValue(target, out var state) && state.Location == location; return _loadingStates.TryGetValue(target, out var state) && state.Location == location;
} }
private UnityEngine.Object TrackLoadedAsset(string location, UnityEngine.Object assetObject) /// <summary>
{ /// 组件销毁时清理所有资源。
if (_assetItemPool.CanSpawn(location)) /// </summary>
{
var cachedAsset = _assetItemPool.Spawn(location).Target as UnityEngine.Object;
_resourceModule.UnloadAsset(assetObject);
return cachedAsset;
}
_assetItemPool.Register(AssetItemObject.Create(location, assetObject), true);
return assetObject;
}
private void OnDestroy() private void OnDestroy()
{ {
ReleaseTrackedAssets();
foreach (var state in _loadingStates.Values) foreach (var state in _loadingStates.Values)
{ {
MemoryPool.Release(state); MemoryPool.Release(state);

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
@ -15,7 +14,6 @@ namespace AlicizaX.Resource.Runtime
{ {
private readonly Dictionary<string, SubAssetsHandle> _subAssetsHandles = new Dictionary<string, SubAssetsHandle>(); private readonly Dictionary<string, SubAssetsHandle> _subAssetsHandles = new Dictionary<string, SubAssetsHandle>();
private readonly Dictionary<string, int> _subSpriteReferences = new Dictionary<string, int>(); private readonly Dictionary<string, int> _subSpriteReferences = new Dictionary<string, int>();
private readonly Dictionary<string, UniTaskCompletionSource<SubAssetsHandle>> _subAssetLoadingOperations = new Dictionary<string, UniTaskCompletionSource<SubAssetsHandle>>();
public async UniTask SetSubSprite(Image image, string location, string spriteName, bool setNativeSize = false, CancellationToken cancellationToken = default) public async UniTask SetSubSprite(Image image, string location, string spriteName, bool setNativeSize = false, CancellationToken cancellationToken = default)
{ {
@ -24,7 +22,6 @@ namespace AlicizaX.Resource.Runtime
if (image == null) if (image == null)
{ {
Log.Warning($"SetSubAssets Image is null"); Log.Warning($"SetSubAssets Image is null");
ReleaseSubAssetsIfUnused(location);
return; return;
} }
@ -44,7 +41,6 @@ namespace AlicizaX.Resource.Runtime
if (spriteRenderer == null) if (spriteRenderer == null)
{ {
Log.Warning($"SetSubAssets Image is null"); Log.Warning($"SetSubAssets Image is null");
ReleaseSubAssetsIfUnused(location);
return; return;
} }
@ -60,7 +56,14 @@ namespace AlicizaX.Resource.Runtime
throw new GameFrameworkException($"Invalid location: {location}"); throw new GameFrameworkException($"Invalid location: {location}");
} }
var subAssetsHandle = await GetOrLoadSubAssetsHandleAsync(location, cancellationToken); await TryWaitingLoading(location);
if (!_subAssetsHandles.TryGetValue(location, out var subAssetsHandle))
{
subAssetsHandle = YooAssets.LoadSubAssetsAsync<Sprite>(location);
await subAssetsHandle.ToUniTask(cancellationToken: cancellationToken);
_subAssetsHandles[location] = subAssetsHandle;
}
var subSprite = subAssetsHandle.GetSubAssetObject<Sprite>(spriteName); var subSprite = subAssetsHandle.GetSubAssetObject<Sprite>(spriteName);
if (subSprite == null) if (subSprite == null)
@ -79,10 +82,8 @@ namespace AlicizaX.Resource.Runtime
subSpriteReference = target.AddComponent<SubSpriteReference>(); subSpriteReference = target.AddComponent<SubSpriteReference>();
} }
if (subSpriteReference.Reference(location)) _subSpriteReferences[location] = _subSpriteReferences.TryGetValue(location, out var count) ? count + 1 : 1;
{ subSpriteReference.Reference(location);
_subSpriteReferences[location] = _subSpriteReferences.TryGetValue(location, out var count) ? count + 1 : 1;
}
} }
internal void DeleteReference(string location) internal void DeleteReference(string location)
@ -92,85 +93,13 @@ namespace AlicizaX.Resource.Runtime
return; return;
} }
int nextCount = _subSpriteReferences.TryGetValue(location, out var count) ? count - 1 : 0; _subSpriteReferences[location] = _subSpriteReferences.TryGetValue(location, out var count) ? count - 1 : 0;
if (nextCount > 0) if (_subSpriteReferences[location] <= 0)
{
_subSpriteReferences[location] = nextCount;
return;
}
_subSpriteReferences.Remove(location);
if (_subAssetsHandles.TryGetValue(location, out var subAssetsHandle))
{
subAssetsHandle.Dispose();
_subAssetsHandles.Remove(location);
}
}
private async UniTask<SubAssetsHandle> GetOrLoadSubAssetsHandleAsync(string location, CancellationToken cancellationToken)
{
while (true)
{
if (_subAssetsHandles.TryGetValue(location, out var cachedHandle))
{
return cachedHandle;
}
if (_subAssetLoadingOperations.TryGetValue(location, out var loadingOperation))
{
if (cancellationToken.CanBeCanceled)
{
await loadingOperation.Task.AttachExternalCancellation(cancellationToken);
}
else
{
await loadingOperation.Task;
}
continue;
}
var completionSource = new UniTaskCompletionSource<SubAssetsHandle>();
_subAssetLoadingOperations.Add(location, completionSource);
SubAssetsHandle subAssetsHandle = default;
try
{
subAssetsHandle = YooAssets.LoadSubAssetsAsync<Sprite>(location);
await subAssetsHandle.ToUniTask();
_subAssetsHandles[location] = subAssetsHandle;
completionSource.TrySetResult(subAssetsHandle);
return subAssetsHandle;
}
catch (Exception ex)
{
subAssetsHandle?.Dispose();
completionSource.TrySetException(ex);
throw;
}
finally
{
_subAssetLoadingOperations.Remove(location);
}
}
}
private void ReleaseSubAssetsIfUnused(string location)
{
if (string.IsNullOrEmpty(location))
{
return;
}
if (_subSpriteReferences.TryGetValue(location, out var count) && count > 0)
{
return;
}
if (_subAssetsHandles.TryGetValue(location, out var subAssetsHandle))
{ {
var subAssetsHandle = _subAssetsHandles[location];
subAssetsHandle.Dispose(); subAssetsHandle.Dispose();
_subAssetsHandles.Remove(location); _subAssetsHandles.Remove(location);
_subSpriteReferences.Remove(location);
} }
} }
} }
@ -180,20 +109,14 @@ namespace AlicizaX.Resource.Runtime
{ {
private string _location; private string _location;
public bool Reference(string location) public void Reference(string location)
{ {
if (_location == location)
{
return false;
}
if (_location != null && _location != location) if (_location != null && _location != location)
{ {
ResourceExtComponent.Instance?.DeleteReference(_location); ResourceExtComponent.Instance?.DeleteReference(_location);
} }
_location = location; _location = location;
return true;
} }
private void OnDestroy() private void OnDestroy()

View File

@ -17,6 +17,13 @@ namespace AlicizaX.Resource.Runtime
{ {
public static ResourceExtComponent Instance { private set; get; } public static ResourceExtComponent Instance { private set; get; }
private readonly TimeoutController _timeoutController = new TimeoutController();
/// <summary>
/// 正在加载的资源列表。
/// </summary>
private readonly HashSet<string> _assetLoadingList = new HashSet<string>();
/// <summary> /// <summary>
/// 检查是否可以释放间隔。 /// 检查是否可以释放间隔。
/// </summary> /// </summary>
@ -47,8 +54,6 @@ namespace AlicizaX.Resource.Runtime
/// </summary> /// </summary>
private LinkedList<LoadAssetObject> _loadAssetObjectsLinkedList; private LinkedList<LoadAssetObject> _loadAssetObjectsLinkedList;
private Dictionary<Object, LinkedListNode<LoadAssetObject>> _trackedAssetNodes;
/// <summary> /// <summary>
/// 散图集合对象池。 /// 散图集合对象池。
/// </summary> /// </summary>
@ -71,7 +76,6 @@ namespace AlicizaX.Resource.Runtime
"SetAssetPool", "SetAssetPool",
autoReleaseInterval, 16, 60, 0); autoReleaseInterval, 16, 60, 0);
_loadAssetObjectsLinkedList = new LinkedList<LoadAssetObject>(); _loadAssetObjectsLinkedList = new LinkedList<LoadAssetObject>();
_trackedAssetNodes = new Dictionary<Object, LinkedListNode<LoadAssetObject>>(16);
InitializedResources(); InitializedResources();
} }
@ -116,7 +120,9 @@ namespace AlicizaX.Resource.Runtime
if (current.Value.AssetObject.IsCanRelease()) if (current.Value.AssetObject.IsCanRelease())
{ {
RemoveTrackedNode(current, releaseAsset: true); _assetItemPool.Unspawn(current.Value.AssetTarget);
MemoryPool.Release(current.Value.AssetObject);
_loadAssetObjectsLinkedList.Remove(current);
} }
current = next; current = next;
@ -135,78 +141,33 @@ namespace AlicizaX.Resource.Runtime
private void SetAsset(ISetAssetObject setAssetObject, Object assetObject) private void SetAsset(ISetAssetObject setAssetObject, Object assetObject)
{ {
ReplaceTrackedAsset(setAssetObject.TargetObject); _loadAssetObjectsLinkedList.AddLast(new LoadAssetObject(setAssetObject, assetObject));
var node = _loadAssetObjectsLinkedList.AddLast(new LoadAssetObject(setAssetObject, assetObject));
if (setAssetObject.TargetObject != null)
{
_trackedAssetNodes[setAssetObject.TargetObject] = node;
}
setAssetObject.SetAsset(assetObject); setAssetObject.SetAsset(assetObject);
} }
private void ReplaceTrackedAsset(Object target) private async UniTask TryWaitingLoading(string assetObjectKey)
{ {
if (target == null || _trackedAssetNodes == null) if (_assetLoadingList.Contains(assetObjectKey))
{ {
return; try
}
if (_trackedAssetNodes.TryGetValue(target, out var existingNode))
{
if (_currentProcessNode == existingNode)
{ {
_currentProcessNode = existingNode.Next; await UniTask.WaitUntil(
() => !_assetLoadingList.Contains(assetObjectKey))
#if UNITY_EDITOR
.AttachExternalCancellation(_timeoutController.Timeout(TimeSpan.FromSeconds(60)));
_timeoutController.Reset();
#else
;
#endif
}
catch (OperationCanceledException ex)
{
if (_timeoutController.IsTimeout())
{
Log.Error($"LoadAssetAsync Waiting {assetObjectKey} timeout. reason:{ex.Message}");
}
} }
RemoveTrackedNode(existingNode, releaseAsset: true);
} }
} }
private void RemoveTrackedNode(LinkedListNode<LoadAssetObject> node, bool releaseAsset)
{
if (node == null)
{
return;
}
var trackedObject = node.Value.AssetObject?.TargetObject;
if (trackedObject != null && _trackedAssetNodes != null)
{
_trackedAssetNodes.Remove(trackedObject);
}
if (releaseAsset && node.Value.AssetTarget != null && _assetItemPool != null)
{
_assetItemPool.Unspawn(node.Value.AssetTarget);
}
if (node.Value.AssetObject != null)
{
MemoryPool.Release(node.Value.AssetObject);
}
_loadAssetObjectsLinkedList?.Remove(node);
}
private void ReleaseTrackedAssets()
{
if (_loadAssetObjectsLinkedList == null)
{
return;
}
var current = _loadAssetObjectsLinkedList.First;
while (current != null)
{
var next = current.Next;
RemoveTrackedNode(current, releaseAsset: true);
current = next;
}
_currentProcessNode = null;
_trackedAssetNodes?.Clear();
}
} }
} }

View File

@ -29,61 +29,69 @@ namespace AlicizaX.Resource.Runtime
private List<AssetsRefInfo> refAssetInfoList; private List<AssetsRefInfo> refAssetInfoList;
private static IResourceModule _resourceModule; private static IResourceModule _resourceModule;
private HashSet<int> _refAssetIds;
private bool TryEnsureResourceModule() private static Dictionary<GameObject, AssetsReference> _originalRefs = new();
private void CheckInit()
{ {
if (_resourceModule != null) if (_resourceModule != null)
{ {
return true; return;
}
else
{
_resourceModule = ModuleSystem.GetModule<IResourceModule>();
} }
_resourceModule = ModuleSystem.GetModule<IResourceModule>(); if (_resourceModule == null)
return _resourceModule != null; {
throw new GameFrameworkException($"resourceModule is null.");
}
} }
private void ReleaseSourceReference() private void CheckRelease()
{ {
if (sourceGameObject != null) if (sourceGameObject != null)
{ {
_resourceModule.UnloadAsset(sourceGameObject); _resourceModule.UnloadAsset(sourceGameObject);
sourceGameObject = null; }
else
{
Log.Warning($"sourceGameObject is not invalid.");
} }
} }
private void EnsureReferenceCache()
private void Awake()
{ {
if (_refAssetIds != null) // If it is a clone, clear the reference records before cloning
if (!IsOriginalInstance())
{ {
return; ClearCloneReferences();
} }
}
_refAssetIds = new HashSet<int>(); private bool IsOriginalInstance()
if (refAssetInfoList == null) {
{ return _originalRefs.TryGetValue(gameObject, out var originalComponent) &&
return; originalComponent == this;
} }
foreach (var refInfo in refAssetInfoList) private void ClearCloneReferences()
{ {
if (refInfo.refAsset != null) sourceGameObject = null;
{ refAssetInfoList?.Clear();
_refAssetIds.Add(refInfo.instanceId != 0 ? refInfo.instanceId : refInfo.refAsset.GetInstanceID());
}
}
} }
private void OnDestroy() private void OnDestroy()
{ {
if (!TryEnsureResourceModule()) CheckInit();
if (sourceGameObject != null)
{ {
sourceGameObject = null; CheckRelease();
refAssetInfoList?.Clear();
_refAssetIds?.Clear();
return;
} }
ReleaseSourceReference();
ReleaseRefAssetInfoList(); ReleaseRefAssetInfoList();
} }
@ -98,8 +106,6 @@ namespace AlicizaX.Resource.Runtime
refAssetInfoList.Clear(); refAssetInfoList.Clear();
} }
_refAssetIds?.Clear();
} }
public AssetsReference Ref(GameObject source, IResourceModule resourceModule = null) public AssetsReference Ref(GameObject source, IResourceModule resourceModule = null)
@ -114,17 +120,14 @@ namespace AlicizaX.Resource.Runtime
throw new GameFrameworkException($"Source gameObject is in scene."); throw new GameFrameworkException($"Source gameObject is in scene.");
} }
if (resourceModule != null) _resourceModule = resourceModule;
{
_resourceModule = resourceModule;
}
if (sourceGameObject != null && sourceGameObject != source && TryEnsureResourceModule())
{
ReleaseSourceReference();
}
sourceGameObject = source; sourceGameObject = source;
if (!_originalRefs.ContainsKey(gameObject))
{
_originalRefs.Add(gameObject, this);
}
return this; return this;
} }
@ -135,18 +138,7 @@ namespace AlicizaX.Resource.Runtime
throw new GameFrameworkException($"Source gameObject is null."); throw new GameFrameworkException($"Source gameObject is null.");
} }
if (resourceModule != null) _resourceModule = resourceModule;
{
_resourceModule = resourceModule;
}
EnsureReferenceCache();
int instanceId = source.GetInstanceID();
if (!_refAssetIds.Add(instanceId))
{
return this;
}
if (refAssetInfoList == null) if (refAssetInfoList == null)
{ {
refAssetInfoList = new List<AssetsRefInfo>(); refAssetInfoList = new List<AssetsRefInfo>();

View File

@ -28,16 +28,12 @@ namespace AlicizaX.Resource.Runtime
private float _lastUnloadUnusedAssetsOperationElapseSeconds = 0f; private float _lastUnloadUnusedAssetsOperationElapseSeconds = 0f;
private float _lastGCCollectElapseSeconds = float.MaxValue;
[SerializeField] private float minUnloadUnusedAssetsInterval = 60f; [SerializeField] private float minUnloadUnusedAssetsInterval = 60f;
[SerializeField] private float maxUnloadUnusedAssetsInterval = 300f; [SerializeField] private float maxUnloadUnusedAssetsInterval = 300f;
[SerializeField] private bool useSystemUnloadUnusedAssets = true; [SerializeField] private bool useSystemUnloadUnusedAssets = true;
[SerializeField] private float minGCCollectInterval = 30f;
[SerializeField] private string decryptionServices = ""; [SerializeField] private string decryptionServices = "";
/// <summary> /// <summary>
@ -243,7 +239,6 @@ namespace AlicizaX.Resource.Runtime
private void Update() private void Update()
{ {
_lastUnloadUnusedAssetsOperationElapseSeconds += Time.unscaledDeltaTime; _lastUnloadUnusedAssetsOperationElapseSeconds += Time.unscaledDeltaTime;
_lastGCCollectElapseSeconds += Time.unscaledDeltaTime;
if (_asyncOperation == null && (_forceUnloadUnusedAssets || _lastUnloadUnusedAssetsOperationElapseSeconds >= maxUnloadUnusedAssetsInterval || if (_asyncOperation == null && (_forceUnloadUnusedAssets || _lastUnloadUnusedAssetsOperationElapseSeconds >= maxUnloadUnusedAssetsInterval ||
_preorderUnloadUnusedAssets && _lastUnloadUnusedAssetsOperationElapseSeconds >= minUnloadUnusedAssetsInterval)) _preorderUnloadUnusedAssets && _lastUnloadUnusedAssetsOperationElapseSeconds >= minUnloadUnusedAssetsInterval))
{ {
@ -251,13 +246,11 @@ namespace AlicizaX.Resource.Runtime
_forceUnloadUnusedAssets = false; _forceUnloadUnusedAssets = false;
_preorderUnloadUnusedAssets = false; _preorderUnloadUnusedAssets = false;
_lastUnloadUnusedAssetsOperationElapseSeconds = 0f; _lastUnloadUnusedAssetsOperationElapseSeconds = 0f;
_resourceModule.UnloadUnusedAssets(); _asyncOperation = Resources.UnloadUnusedAssets();
_asyncOperation = useSystemUnloadUnusedAssets ? Resources.UnloadUnusedAssets() : null; if (useSystemUnloadUnusedAssets)
} {
_resourceModule.UnloadUnusedAssets();
if (_asyncOperation == null && _performGCCollect) }
{
TryCollectGarbage();
} }
if (_asyncOperation is { isDone: true }) if (_asyncOperation is { isDone: true })
@ -265,24 +258,13 @@ namespace AlicizaX.Resource.Runtime
_asyncOperation = null; _asyncOperation = null;
if (_performGCCollect) if (_performGCCollect)
{ {
TryCollectGarbage(); Log.Info("GC.Collect...");
_performGCCollect = false;
GC.Collect();
} }
} }
} }
private void TryCollectGarbage()
{
if (_lastGCCollectElapseSeconds < minGCCollectInterval)
{
return;
}
Log.Info("GC.Collect...");
_performGCCollect = false;
_lastGCCollectElapseSeconds = 0f;
GC.Collect();
}
private void OnLowMemory() private void OnLowMemory()
{ {
Log.Warning("Low memory reported..."); Log.Warning("Low memory reported...");

View File

@ -85,11 +85,9 @@ namespace AlicizaX.Resource.Runtime
private readonly Dictionary<string, AssetInfo> _assetInfoMap = new Dictionary<string, AssetInfo>(); private readonly Dictionary<string, AssetInfo> _assetInfoMap = new Dictionary<string, AssetInfo>();
/// <summary> /// <summary>
/// 正在加载的资源任务 /// 正在加载的资源列表
/// </summary> /// </summary>
private readonly Dictionary<string, UniTaskCompletionSource<bool>> _assetLoadingOperations = new Dictionary<string, UniTaskCompletionSource<bool>>(); private readonly HashSet<string> _assetLoadingList = new HashSet<string>();
private const float ProgressCallbackThreshold = 0.01f;
#endregion #endregion
@ -109,7 +107,6 @@ namespace AlicizaX.Resource.Runtime
} }
DefaultPackage = defaultPackage; DefaultPackage = defaultPackage;
PackageMap[packageName] = defaultPackage;
IObjectPoolModule objectPoolModule = ModuleSystem.GetModule<IObjectPoolModule>(); IObjectPoolModule objectPoolModule = ModuleSystem.GetModule<IObjectPoolModule>();
SetObjectPoolModule(objectPoolModule); SetObjectPoolModule(objectPoolModule);
@ -118,14 +115,9 @@ namespace AlicizaX.Resource.Runtime
void IModule.Dispose() void IModule.Dispose()
{ {
foreach (var loadingOperation in _assetLoadingOperations.Values)
{
loadingOperation.TrySetResult(false);
}
PackageMap.Clear(); PackageMap.Clear();
_assetPool = null; _assetPool = null;
_assetLoadingOperations.Clear(); _assetLoadingList.Clear();
_assetInfoMap.Clear(); _assetInfoMap.Clear();
} }
@ -471,7 +463,7 @@ namespace AlicizaX.Resource.Runtime
AssetInfo assetInfo = GetAssetInfo(location, packageName); AssetInfo assetInfo = GetAssetInfo(location, packageName);
if (!CheckLocationValid(location, packageName)) if (!CheckLocationValid(location))
{ {
return HasAssetResult.Valid; return HasAssetResult.Valid;
} }
@ -543,25 +535,20 @@ namespace AlicizaX.Resource.Runtime
/// <param name="packageName">指定资源包的名称。不传使用默认资源包</param> /// <param name="packageName">指定资源包的名称。不传使用默认资源包</param>
/// <typeparam name="T">资源类型。</typeparam> /// <typeparam name="T">资源类型。</typeparam>
/// <returns>资源句柄。</returns> /// <returns>资源句柄。</returns>
private AssetHandle GetHandleAsync<T>(string location, string packageName = "", uint priority = 0) where T : UnityEngine.Object private AssetHandle GetHandleAsync<T>(string location, string packageName = "") where T : UnityEngine.Object
{ {
return GetHandleAsync(location, typeof(T), packageName, priority); return GetHandleAsync(location, typeof(T), packageName);
} }
private AssetHandle GetHandleAsync(string location, Type assetType, string packageName = "", uint priority = 0) private AssetHandle GetHandleAsync(string location, Type assetType, string packageName = "")
{ {
if (string.IsNullOrEmpty(packageName)) if (string.IsNullOrEmpty(packageName))
{ {
return YooAssets.LoadAssetAsync(location, assetType, priority); return YooAssets.LoadAssetAsync(location, assetType);
} }
var package = YooAssets.GetPackage(packageName); var package = YooAssets.GetPackage(packageName);
return package.LoadAssetAsync(location, assetType, priority); return package.LoadAssetAsync(location, assetType);
}
private static uint NormalizePriority(int priority)
{
return priority > 0 ? (uint)priority : 0u;
} }
#endregion #endregion
@ -665,16 +652,37 @@ namespace AlicizaX.Resource.Runtime
} }
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
try
await TryWaitingLoading(assetObjectKey);
AssetObject assetObject = _assetPool.Spawn(assetObjectKey);
if (assetObject != null)
{ {
var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey); await UniTask.Yield();
callback?.Invoke(asset as T); callback?.Invoke(assetObject.Target as T);
return;
} }
catch (Exception ex)
_assetLoadingList.Add(assetObjectKey);
AssetHandle handle = GetHandleAsync<T>(location, packageName: packageName);
handle.Completed += assetHandle =>
{ {
Log.Error($"Can not load asset '{location}'. {ex}"); _assetLoadingList.Remove(assetObjectKey);
callback?.Invoke(null);
} if (assetHandle.AssetObject != null)
{
assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle, this);
_assetPool.Register(assetObject, true);
callback?.Invoke(assetObject.Target as T);
}
else
{
callback?.Invoke(null);
}
};
} }
public async UniTask<T> LoadAssetAsync<T>(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object public async UniTask<T> LoadAssetAsync<T>(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object
@ -693,8 +701,33 @@ namespace AlicizaX.Resource.Runtime
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey, cancellationToken: cancellationToken); await TryWaitingLoading(assetObjectKey);
return asset as T;
AssetObject assetObject = _assetPool.Spawn(assetObjectKey);
if (assetObject != null)
{
await UniTask.Yield();
return assetObject.Target as T;
}
_assetLoadingList.Add(assetObjectKey);
AssetHandle handle = GetHandleAsync<T>(location, packageName: packageName);
bool cancelOrFailed = await handle.ToUniTask().AttachExternalCancellation(cancellationToken).SuppressCancellationThrow();
if (cancelOrFailed)
{
_assetLoadingList.Remove(assetObjectKey);
return null;
}
assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle, this);
_assetPool.Register(assetObject, true);
_assetLoadingList.Remove(assetObjectKey);
return handle.AssetObject as T;
} }
public async UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default, string packageName = "") public async UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default, string packageName = "")
@ -712,8 +745,35 @@ namespace AlicizaX.Resource.Runtime
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
var asset = await GetOrLoadAssetAsync(location, typeof(GameObject), packageName, assetObjectKey, cancellationToken: cancellationToken); await TryWaitingLoading(assetObjectKey);
return asset != null ? AssetsReference.Instantiate(asset as GameObject, parent, this).gameObject : null;
AssetObject assetObject = _assetPool.Spawn(assetObjectKey);
if (assetObject != null)
{
await UniTask.Yield();
return AssetsReference.Instantiate(assetObject.Target as GameObject, parent, this).gameObject;
}
_assetLoadingList.Add(assetObjectKey);
AssetHandle handle = GetHandleAsync<GameObject>(location, packageName: packageName);
bool cancelOrFailed = await handle.ToUniTask().AttachExternalCancellation(cancellationToken).SuppressCancellationThrow();
if (cancelOrFailed)
{
_assetLoadingList.Remove(assetObjectKey);
return null;
}
GameObject gameObject = AssetsReference.Instantiate(handle.AssetObject as GameObject, parent, this).gameObject;
assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle, this);
_assetPool.Register(assetObject, true);
_assetLoadingList.Remove(assetObjectKey);
return gameObject;
} }
#endregion #endregion
@ -753,12 +813,26 @@ namespace AlicizaX.Resource.Runtime
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
await TryWaitingLoading(assetObjectKey);
float duration = Time.time; float duration = Time.time;
AssetObject assetObject = _assetPool.Spawn(assetObjectKey);
if (assetObject != null)
{
await UniTask.Yield();
loadAssetCallbacks.LoadAssetSuccessCallback(location, assetObject.Target, Time.time - duration, userData);
return;
}
_assetLoadingList.Add(assetObjectKey);
AssetInfo assetInfo = GetAssetInfo(location, packageName); AssetInfo assetInfo = GetAssetInfo(location, packageName);
if (!string.IsNullOrEmpty(assetInfo.Error)) if (!string.IsNullOrEmpty(assetInfo.Error))
{ {
_assetLoadingList.Remove(assetObjectKey);
string errorMessage = Utility.Text.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error); string errorMessage = Utility.Text.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error);
if (loadAssetCallbacks.LoadAssetFailureCallback != null) if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{ {
@ -769,23 +843,41 @@ namespace AlicizaX.Resource.Runtime
throw new GameFrameworkException(errorMessage); throw new GameFrameworkException(errorMessage);
} }
try AssetHandle handle = GetHandleAsync(location, assetType, packageName: packageName);
if (loadAssetCallbacks.LoadAssetUpdateCallback != null)
{ {
var asset = await GetOrLoadAssetAsync(location, assetType, packageName, assetObjectKey, NormalizePriority(priority), default, InvokeProgress(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData).Forget();
handle => StartProgressTask(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData));
loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData);
} }
catch (Exception ex)
await handle.ToUniTask();
if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed)
{ {
_assetLoadingList.Remove(assetObjectKey);
string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", location); string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", location);
Log.Error($"{errorMessage} {ex}");
if (loadAssetCallbacks.LoadAssetFailureCallback != null) if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{ {
loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotReady, errorMessage, userData); loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotReady, errorMessage, userData);
return; return;
} }
throw; throw new GameFrameworkException(errorMessage);
}
else
{
assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle, this);
_assetPool.Register(assetObject, true);
_assetLoadingList.Remove(assetObjectKey);
if (loadAssetCallbacks.LoadAssetSuccessCallback != null)
{
duration = Time.time - duration;
loadAssetCallbacks.LoadAssetSuccessCallback(location, handle.AssetObject, duration, userData);
}
} }
} }
@ -823,12 +915,26 @@ namespace AlicizaX.Resource.Runtime
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
await TryWaitingLoading(assetObjectKey);
float duration = Time.time; float duration = Time.time;
AssetObject assetObject = _assetPool.Spawn(assetObjectKey);
if (assetObject != null)
{
await UniTask.Yield();
loadAssetCallbacks.LoadAssetSuccessCallback(location, assetObject.Target, Time.time - duration, userData);
return;
}
_assetLoadingList.Add(assetObjectKey);
AssetInfo assetInfo = GetAssetInfo(location, packageName); AssetInfo assetInfo = GetAssetInfo(location, packageName);
if (!string.IsNullOrEmpty(assetInfo.Error)) if (!string.IsNullOrEmpty(assetInfo.Error))
{ {
_assetLoadingList.Remove(assetObjectKey);
string errorMessage = Utility.Text.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error); string errorMessage = Utility.Text.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error);
if (loadAssetCallbacks.LoadAssetFailureCallback != null) if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{ {
@ -839,23 +945,41 @@ namespace AlicizaX.Resource.Runtime
throw new GameFrameworkException(errorMessage); throw new GameFrameworkException(errorMessage);
} }
try AssetHandle handle = GetHandleAsync(location, assetInfo.AssetType, packageName: packageName);
if (loadAssetCallbacks.LoadAssetUpdateCallback != null)
{ {
var asset = await GetOrLoadAssetAsync(location, assetInfo.AssetType, packageName, assetObjectKey, NormalizePriority(priority), default, InvokeProgress(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData).Forget();
handle => StartProgressTask(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData));
loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData);
} }
catch (Exception ex)
await handle.ToUniTask();
if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed)
{ {
_assetLoadingList.Remove(assetObjectKey);
string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", location); string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", location);
Log.Error($"{errorMessage} {ex}");
if (loadAssetCallbacks.LoadAssetFailureCallback != null) if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{ {
loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotReady, errorMessage, userData); loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotReady, errorMessage, userData);
return; return;
} }
throw; throw new GameFrameworkException(errorMessage);
}
else
{
assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle, this);
_assetPool.Register(assetObject, true);
_assetLoadingList.Remove(assetObjectKey);
if (loadAssetCallbacks.LoadAssetSuccessCallback != null)
{
duration = Time.time - duration;
loadAssetCallbacks.LoadAssetSuccessCallback(location, handle.AssetObject, duration, userData);
}
} }
} }
@ -868,21 +992,11 @@ namespace AlicizaX.Resource.Runtime
if (loadAssetUpdateCallback != null) if (loadAssetUpdateCallback != null)
{ {
float lastReportedProgress = -1f;
while (assetHandle is { IsValid: true, IsDone: false }) while (assetHandle is { IsValid: true, IsDone: false })
{ {
await UniTask.Yield(); await UniTask.Yield();
float progress = assetHandle.Progress;
if (lastReportedProgress < 0f || progress - lastReportedProgress >= ProgressCallbackThreshold)
{
lastReportedProgress = progress;
loadAssetUpdateCallback.Invoke(location, progress, userData);
}
}
if (assetHandle is { IsValid: true } && lastReportedProgress < 1f) loadAssetUpdateCallback.Invoke(location, assetHandle.Progress, userData);
{
loadAssetUpdateCallback.Invoke(location, 1f, userData);
} }
} }
} }
@ -925,117 +1039,32 @@ namespace AlicizaX.Resource.Runtime
#endregion #endregion
private async UniTask<UnityEngine.Object> GetOrLoadAssetAsync(string location, Type assetType, string packageName, private readonly TimeoutController _timeoutController = new TimeoutController();
string assetObjectKey, uint priority = 0, CancellationToken cancellationToken = default, Action<AssetHandle> onHandleCreated = null)
private async UniTask TryWaitingLoading(string assetObjectKey)
{ {
while (true) if (_assetLoadingList.Contains(assetObjectKey))
{ {
cancellationToken.ThrowIfCancellationRequested();
AssetObject cachedAssetObject = _assetPool.Spawn(assetObjectKey);
if (cachedAssetObject != null)
{
return cachedAssetObject.Target as UnityEngine.Object;
}
if (!TryBeginLoading(assetObjectKey))
{
await WaitForLoadingAsync(assetObjectKey, cancellationToken);
continue;
}
AssetHandle handle = null;
try try
{ {
handle = GetHandleAsync(location, assetType, packageName: packageName, priority: priority); await UniTask.WaitUntil(() => !_assetLoadingList.Contains(assetObjectKey))
onHandleCreated?.Invoke(handle); #if UNITY_EDITOR
await handle.ToUniTask(); .AttachExternalCancellation(_timeoutController.Timeout(TimeSpan.FromSeconds(60)));
_timeoutController.Reset();
if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed) #else
{ ;
throw new GameFrameworkException(Utility.Text.Format("Can not load asset '{0}'.", location)); #endif
}
var assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle, this);
_assetPool.Register(assetObject, true);
CompleteLoading(assetObjectKey);
return handle.AssetObject as UnityEngine.Object;
} }
catch (Exception ex) catch (OperationCanceledException ex)
{ {
DisposeHandle(handle); if (_timeoutController.IsTimeout())
FailLoading(assetObjectKey, ex); {
throw; Log.Error($"LoadAssetAsync Waiting {assetObjectKey} timeout. reason:{ex.Message}");
}
} }
} }
} }
private bool TryBeginLoading(string assetObjectKey)
{
if (_assetLoadingOperations.ContainsKey(assetObjectKey))
{
return false;
}
_assetLoadingOperations.Add(assetObjectKey, new UniTaskCompletionSource<bool>());
return true;
}
private async UniTask WaitForLoadingAsync(string assetObjectKey, CancellationToken cancellationToken = default)
{
if (!_assetLoadingOperations.TryGetValue(assetObjectKey, out var loadingOperation))
{
return;
}
if (cancellationToken.CanBeCanceled)
{
await loadingOperation.Task.AttachExternalCancellation(cancellationToken);
}
else
{
await loadingOperation.Task;
}
}
private void CompleteLoading(string assetObjectKey)
{
if (!_assetLoadingOperations.TryGetValue(assetObjectKey, out var loadingOperation))
{
return;
}
_assetLoadingOperations.Remove(assetObjectKey);
loadingOperation.TrySetResult(true);
}
private void FailLoading(string assetObjectKey, Exception exception)
{
if (!_assetLoadingOperations.TryGetValue(assetObjectKey, out var loadingOperation))
{
return;
}
_assetLoadingOperations.Remove(assetObjectKey);
loadingOperation.TrySetException(exception);
}
private void DisposeHandle(AssetHandle handle)
{
if (handle is { IsValid: true })
{
handle.Dispose();
}
}
private void StartProgressTask(string location, AssetHandle handle, LoadAssetUpdateCallback loadAssetUpdateCallback, object userData)
{
if (loadAssetUpdateCallback != null && handle is { IsValid: true, IsDone: false })
{
InvokeProgress(location, handle, loadAssetUpdateCallback, userData).Forget();
}
}
#endregion #endregion
#region #region