From 3d6caf40289d9521f11d38eba0354ce30e7b89db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Tue, 24 Mar 2026 18:48:47 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=A8=A1=E5=9D=97=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ResourceExtComponent.Resource.cs | 54 +-- .../ResourceExtComponent.SubSprite.cs | 109 +++++- .../Extension/ResourceExtComponent.cs | 32 -- .../Resource/Reference/AssetsReference.cs | 94 ++--- .../Resource/Resource/ResourceComponent.cs | 14 +- Runtime/Resource/Resource/ResourceModule.cs | 347 ++++++++---------- 6 files changed, 341 insertions(+), 309 deletions(-) diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs index c48311c..e05dcf8 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs @@ -8,7 +8,6 @@ namespace AlicizaX.Resource.Runtime public partial class ResourceExtComponent { private static IResourceModule _resourceModule; - private LoadAssetCallbacks _loadAssetCallbacks; public static IResourceModule ResourceModule => _resourceModule; @@ -30,30 +29,20 @@ namespace AlicizaX.Resource.Runtime } } - private static readonly Dictionary _loadingStates = new Dictionary(); + private readonly Dictionary _loadingStates = new Dictionary(); private void InitializedResources() { _resourceModule = ModuleSystem.GetModule(); - _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) { - _assetLoadingList.Remove(assetName); ISetAssetObject setAssetObject = (ISetAssetObject)userdata; + if (setAssetObject == null) + { + return; + } UnityEngine.Object assetObject = asset as UnityEngine.Object; if (assetObject != null) @@ -63,8 +52,7 @@ namespace AlicizaX.Resource.Runtime { ClearLoadingState(setAssetObject.TargetObject); - _assetItemPool.Register(AssetItemObject.Create(setAssetObject.Location, assetObject), true); - SetAsset(setAssetObject, assetObject); + SetAsset(setAssetObject, TrackLoadedAsset(setAssetObject.Location, assetObject)); } else { @@ -74,7 +62,7 @@ namespace AlicizaX.Resource.Runtime } else { - Log.Error($"Load failure asset type is {asset.GetType()}."); + Log.Error($"Load failure asset type is {asset?.GetType()}."); } } @@ -106,7 +94,6 @@ namespace AlicizaX.Resource.Runtime try { // 等待其他可能正在进行的加载。 - await TryWaitingLoading(location).AttachExternalCancellation(linkedTokenSource.Token); // 再次检查是否被新请求替换。 if (!IsCurrentLocation(target, location)) @@ -121,6 +108,7 @@ namespace AlicizaX.Resource.Runtime var assetObject = (T)_assetItemPool.Spawn(location).Target; SetAsset(setAssetObject, assetObject); + return; } else { @@ -131,23 +119,26 @@ namespace AlicizaX.Resource.Runtime } // 防止重复加载同一资源。 - if (!_assetLoadingList.Add(location)) + if (!IsCurrentLocation(target, location)) { // 已经在加载中,等待回调处理。 return; } T resource = await _resourceModule.LoadAssetAsync(location, linkedTokenSource.Token); - if (resource != null) + if (resource == null) { - _loadAssetCallbacks?.LoadAssetSuccessCallback.Invoke(location,resource, 0f, setAssetObject); - + ClearLoadingState(target); + return; } - _assetLoadingList.Remove(location); + + OnLoadAssetSuccess(location, resource, 0f, setAssetObject); + return; } } catch (OperationCanceledException) { + ClearLoadingState(target); // 请求被取消,正常情况,无需处理。 } catch (Exception ex) @@ -195,6 +186,19 @@ namespace AlicizaX.Resource.Runtime return _loadingStates.TryGetValue(target, out var state) && state.Location == location; } + private UnityEngine.Object TrackLoadedAsset(string location, UnityEngine.Object assetObject) + { + if (_assetItemPool.CanSpawn(location)) + { + var cachedAsset = _assetItemPool.Spawn(location).Target as UnityEngine.Object; + _resourceModule.UnloadAsset(assetObject); + return cachedAsset; + } + + _assetItemPool.Register(AssetItemObject.Create(location, assetObject), true); + return assetObject; + } + /// /// 组件销毁时清理所有资源。 /// diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.SubSprite.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.SubSprite.cs index eb19e39..4285e50 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.SubSprite.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.SubSprite.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine; @@ -14,6 +15,7 @@ namespace AlicizaX.Resource.Runtime { private readonly Dictionary _subAssetsHandles = new Dictionary(); private readonly Dictionary _subSpriteReferences = new Dictionary(); + private readonly Dictionary> _subAssetLoadingOperations = new Dictionary>(); public async UniTask SetSubSprite(Image image, string location, string spriteName, bool setNativeSize = false, CancellationToken cancellationToken = default) { @@ -22,6 +24,7 @@ namespace AlicizaX.Resource.Runtime if (image == null) { Log.Warning($"SetSubAssets Image is null"); + ReleaseSubAssetsIfUnused(location); return; } @@ -41,6 +44,7 @@ namespace AlicizaX.Resource.Runtime if (spriteRenderer == null) { Log.Warning($"SetSubAssets Image is null"); + ReleaseSubAssetsIfUnused(location); return; } @@ -56,14 +60,7 @@ namespace AlicizaX.Resource.Runtime throw new GameFrameworkException($"Invalid location: {location}"); } - await TryWaitingLoading(location); - - if (!_subAssetsHandles.TryGetValue(location, out var subAssetsHandle)) - { - subAssetsHandle = YooAssets.LoadSubAssetsAsync(location); - await subAssetsHandle.ToUniTask(cancellationToken: cancellationToken); - _subAssetsHandles[location] = subAssetsHandle; - } + var subAssetsHandle = await GetOrLoadSubAssetsHandleAsync(location, cancellationToken); var subSprite = subAssetsHandle.GetSubAssetObject(spriteName); if (subSprite == null) @@ -82,8 +79,10 @@ namespace AlicizaX.Resource.Runtime subSpriteReference = target.AddComponent(); } - _subSpriteReferences[location] = _subSpriteReferences.TryGetValue(location, out var count) ? count + 1 : 1; - subSpriteReference.Reference(location); + if (subSpriteReference.Reference(location)) + { + _subSpriteReferences[location] = _subSpriteReferences.TryGetValue(location, out var count) ? count + 1 : 1; + } } internal void DeleteReference(string location) @@ -93,13 +92,85 @@ namespace AlicizaX.Resource.Runtime return; } - _subSpriteReferences[location] = _subSpriteReferences.TryGetValue(location, out var count) ? count - 1 : 0; - if (_subSpriteReferences[location] <= 0) + int nextCount = _subSpriteReferences.TryGetValue(location, out var count) ? count - 1 : 0; + if (nextCount > 0) + { + _subSpriteReferences[location] = nextCount; + return; + } + + _subSpriteReferences.Remove(location); + if (_subAssetsHandles.TryGetValue(location, out var subAssetsHandle)) + { + subAssetsHandle.Dispose(); + _subAssetsHandles.Remove(location); + } + } + + private async UniTask 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(); + _subAssetLoadingOperations.Add(location, completionSource); + + SubAssetsHandle subAssetsHandle = default; + try + { + subAssetsHandle = YooAssets.LoadSubAssetsAsync(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(); _subAssetsHandles.Remove(location); - _subSpriteReferences.Remove(location); } } } @@ -109,14 +180,20 @@ namespace AlicizaX.Resource.Runtime { private string _location; - public void Reference(string location) + public bool Reference(string location) { + if (_location == location) + { + return false; + } + if (_location != null && _location != location) { ResourceExtComponent.Instance?.DeleteReference(_location); } _location = location; + return true; } private void OnDestroy() diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs index 668ead2..21518f7 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs @@ -17,13 +17,6 @@ namespace AlicizaX.Resource.Runtime { public static ResourceExtComponent Instance { private set; get; } - private readonly TimeoutController _timeoutController = new TimeoutController(); - - /// - /// 正在加载的资源列表。 - /// - private readonly HashSet _assetLoadingList = new HashSet(); - /// /// 检查是否可以释放间隔。 /// @@ -144,30 +137,5 @@ namespace AlicizaX.Resource.Runtime _loadAssetObjectsLinkedList.AddLast(new LoadAssetObject(setAssetObject, assetObject)); setAssetObject.SetAsset(assetObject); } - - private async UniTask TryWaitingLoading(string assetObjectKey) - { - if (_assetLoadingList.Contains(assetObjectKey)) - { - try - { - 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}"); - } - } - } - } } } diff --git a/Runtime/Resource/Resource/Reference/AssetsReference.cs b/Runtime/Resource/Resource/Reference/AssetsReference.cs index 310765b..29ed325 100644 --- a/Runtime/Resource/Resource/Reference/AssetsReference.cs +++ b/Runtime/Resource/Resource/Reference/AssetsReference.cs @@ -29,69 +29,61 @@ namespace AlicizaX.Resource.Runtime private List refAssetInfoList; private static IResourceModule _resourceModule; + private HashSet _refAssetIds; - private static Dictionary _originalRefs = new(); - - - private void CheckInit() + private bool TryEnsureResourceModule() { if (_resourceModule != null) { - return; - } - else - { - _resourceModule = ModuleSystem.GetModule(); + return true; } - if (_resourceModule == null) - { - throw new GameFrameworkException($"resourceModule is null."); - } + _resourceModule = ModuleSystem.GetModule(); + return _resourceModule != null; } - private void CheckRelease() + private void ReleaseSourceReference() { if (sourceGameObject != null) { _resourceModule.UnloadAsset(sourceGameObject); + sourceGameObject = null; } - else + } + + private void EnsureReferenceCache() + { + if (_refAssetIds != null) { - Log.Warning($"sourceGameObject is not invalid."); + return; } - } - - private void Awake() - { - // If it is a clone, clear the reference records before cloning - if (!IsOriginalInstance()) + _refAssetIds = new HashSet(); + if (refAssetInfoList == null) { - ClearCloneReferences(); + return; } - } - private bool IsOriginalInstance() - { - return _originalRefs.TryGetValue(gameObject, out var originalComponent) && - originalComponent == this; - } - - private void ClearCloneReferences() - { - sourceGameObject = null; - refAssetInfoList?.Clear(); + foreach (var refInfo in refAssetInfoList) + { + if (refInfo.refAsset != null) + { + _refAssetIds.Add(refInfo.instanceId != 0 ? refInfo.instanceId : refInfo.refAsset.GetInstanceID()); + } + } } private void OnDestroy() { - CheckInit(); - if (sourceGameObject != null) + if (!TryEnsureResourceModule()) { - CheckRelease(); + sourceGameObject = null; + refAssetInfoList?.Clear(); + _refAssetIds?.Clear(); + return; } + ReleaseSourceReference(); ReleaseRefAssetInfoList(); } @@ -106,6 +98,8 @@ namespace AlicizaX.Resource.Runtime refAssetInfoList.Clear(); } + + _refAssetIds?.Clear(); } public AssetsReference Ref(GameObject source, IResourceModule resourceModule = null) @@ -120,14 +114,17 @@ namespace AlicizaX.Resource.Runtime throw new GameFrameworkException($"Source gameObject is in scene."); } - _resourceModule = resourceModule; - sourceGameObject = source; - - if (!_originalRefs.ContainsKey(gameObject)) + if (resourceModule != null) { - _originalRefs.Add(gameObject, this); + _resourceModule = resourceModule; } + if (sourceGameObject != null && sourceGameObject != source && TryEnsureResourceModule()) + { + ReleaseSourceReference(); + } + + sourceGameObject = source; return this; } @@ -138,7 +135,18 @@ namespace AlicizaX.Resource.Runtime throw new GameFrameworkException($"Source gameObject is null."); } - _resourceModule = resourceModule; + if (resourceModule != null) + { + _resourceModule = resourceModule; + } + + EnsureReferenceCache(); + int instanceId = source.GetInstanceID(); + if (!_refAssetIds.Add(instanceId)) + { + return this; + } + if (refAssetInfoList == null) { refAssetInfoList = new List(); diff --git a/Runtime/Resource/Resource/ResourceComponent.cs b/Runtime/Resource/Resource/ResourceComponent.cs index 1ef10b0..51ab132 100644 --- a/Runtime/Resource/Resource/ResourceComponent.cs +++ b/Runtime/Resource/Resource/ResourceComponent.cs @@ -246,11 +246,15 @@ namespace AlicizaX.Resource.Runtime _forceUnloadUnusedAssets = false; _preorderUnloadUnusedAssets = false; _lastUnloadUnusedAssetsOperationElapseSeconds = 0f; - _asyncOperation = Resources.UnloadUnusedAssets(); - if (useSystemUnloadUnusedAssets) - { - _resourceModule.UnloadUnusedAssets(); - } + _resourceModule.UnloadUnusedAssets(); + _asyncOperation = useSystemUnloadUnusedAssets ? Resources.UnloadUnusedAssets() : null; + } + + if (_asyncOperation == null && _performGCCollect) + { + Log.Info("GC.Collect..."); + _performGCCollect = false; + GC.Collect(); } if (_asyncOperation is { isDone: true }) diff --git a/Runtime/Resource/Resource/ResourceModule.cs b/Runtime/Resource/Resource/ResourceModule.cs index 624148f..560fcdd 100644 --- a/Runtime/Resource/Resource/ResourceModule.cs +++ b/Runtime/Resource/Resource/ResourceModule.cs @@ -85,9 +85,11 @@ namespace AlicizaX.Resource.Runtime private readonly Dictionary _assetInfoMap = new Dictionary(); /// - /// 正在加载的资源列表。 + /// 正在加载的资源任务。 /// - private readonly HashSet _assetLoadingList = new HashSet(); + private readonly Dictionary> _assetLoadingOperations = new Dictionary>(); + + private const float ProgressCallbackThreshold = 0.01f; #endregion @@ -107,6 +109,7 @@ namespace AlicizaX.Resource.Runtime } DefaultPackage = defaultPackage; + PackageMap[packageName] = defaultPackage; IObjectPoolModule objectPoolModule = ModuleSystem.GetModule(); SetObjectPoolModule(objectPoolModule); @@ -115,9 +118,14 @@ namespace AlicizaX.Resource.Runtime void IModule.Dispose() { + foreach (var loadingOperation in _assetLoadingOperations.Values) + { + loadingOperation.TrySetResult(false); + } + PackageMap.Clear(); _assetPool = null; - _assetLoadingList.Clear(); + _assetLoadingOperations.Clear(); _assetInfoMap.Clear(); } @@ -463,7 +471,7 @@ namespace AlicizaX.Resource.Runtime AssetInfo assetInfo = GetAssetInfo(location, packageName); - if (!CheckLocationValid(location)) + if (!CheckLocationValid(location, packageName)) { return HasAssetResult.Valid; } @@ -535,20 +543,25 @@ namespace AlicizaX.Resource.Runtime /// 指定资源包的名称。不传使用默认资源包 /// 资源类型。 /// 资源句柄。 - private AssetHandle GetHandleAsync(string location, string packageName = "") where T : UnityEngine.Object + private AssetHandle GetHandleAsync(string location, string packageName = "", uint priority = 0) where T : UnityEngine.Object { - return GetHandleAsync(location, typeof(T), packageName); + return GetHandleAsync(location, typeof(T), packageName, priority); } - private AssetHandle GetHandleAsync(string location, Type assetType, string packageName = "") + private AssetHandle GetHandleAsync(string location, Type assetType, string packageName = "", uint priority = 0) { if (string.IsNullOrEmpty(packageName)) { - return YooAssets.LoadAssetAsync(location, assetType); + return YooAssets.LoadAssetAsync(location, assetType, priority); } var package = YooAssets.GetPackage(packageName); - return package.LoadAssetAsync(location, assetType); + return package.LoadAssetAsync(location, assetType, priority); + } + + private static uint NormalizePriority(int priority) + { + return priority > 0 ? (uint)priority : 0u; } #endregion @@ -652,37 +665,16 @@ namespace AlicizaX.Resource.Runtime } string assetObjectKey = GetCacheKey(location, packageName); - - await TryWaitingLoading(assetObjectKey); - - AssetObject assetObject = _assetPool.Spawn(assetObjectKey); - if (assetObject != null) + try { - await UniTask.Yield(); - callback?.Invoke(assetObject.Target as T); - return; + var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey); + callback?.Invoke(asset as T); } - - _assetLoadingList.Add(assetObjectKey); - - AssetHandle handle = GetHandleAsync(location, packageName: packageName); - - handle.Completed += assetHandle => + catch (Exception ex) { - _assetLoadingList.Remove(assetObjectKey); - - 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); - } - }; + Log.Error($"Can not load asset '{location}'. {ex}"); + callback?.Invoke(null); + } } public async UniTask LoadAssetAsync(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object @@ -701,33 +693,8 @@ namespace AlicizaX.Resource.Runtime string assetObjectKey = GetCacheKey(location, packageName); - await TryWaitingLoading(assetObjectKey); - - AssetObject assetObject = _assetPool.Spawn(assetObjectKey); - if (assetObject != null) - { - await UniTask.Yield(); - return assetObject.Target as T; - } - - _assetLoadingList.Add(assetObjectKey); - - AssetHandle handle = GetHandleAsync(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; + var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey, cancellationToken); + return asset as T; } public async UniTask LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default, string packageName = "") @@ -745,35 +712,8 @@ namespace AlicizaX.Resource.Runtime string assetObjectKey = GetCacheKey(location, packageName); - await TryWaitingLoading(assetObjectKey); - - 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(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; + var asset = await GetOrLoadAssetAsync(location, typeof(GameObject), packageName, assetObjectKey, cancellationToken); + return asset != null ? AssetsReference.Instantiate(asset as GameObject, parent, this).gameObject : null; } #endregion @@ -813,26 +753,12 @@ namespace AlicizaX.Resource.Runtime string assetObjectKey = GetCacheKey(location, packageName); - await TryWaitingLoading(assetObjectKey); - 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); if (!string.IsNullOrEmpty(assetInfo.Error)) { - _assetLoadingList.Remove(assetObjectKey); - string errorMessage = Utility.Text.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error); if (loadAssetCallbacks.LoadAssetFailureCallback != null) { @@ -843,41 +769,23 @@ namespace AlicizaX.Resource.Runtime throw new GameFrameworkException(errorMessage); } - AssetHandle handle = GetHandleAsync(location, assetType, packageName: packageName); - - if (loadAssetCallbacks.LoadAssetUpdateCallback != null) + try { - InvokeProgress(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData).Forget(); + var asset = await GetOrLoadAssetAsync(location, assetType, packageName, assetObjectKey, NormalizePriority(priority), default, + handle => StartProgressTask(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData)); + loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData); } - - await handle.ToUniTask(); - - if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed) + catch (Exception ex) { - _assetLoadingList.Remove(assetObjectKey); - string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", location); + Log.Error($"{errorMessage} {ex}"); if (loadAssetCallbacks.LoadAssetFailureCallback != null) { loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotReady, errorMessage, userData); return; } - 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); - } + throw; } } @@ -915,26 +823,12 @@ namespace AlicizaX.Resource.Runtime string assetObjectKey = GetCacheKey(location, packageName); - await TryWaitingLoading(assetObjectKey); - 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); if (!string.IsNullOrEmpty(assetInfo.Error)) { - _assetLoadingList.Remove(assetObjectKey); - string errorMessage = Utility.Text.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error); if (loadAssetCallbacks.LoadAssetFailureCallback != null) { @@ -945,41 +839,23 @@ namespace AlicizaX.Resource.Runtime throw new GameFrameworkException(errorMessage); } - AssetHandle handle = GetHandleAsync(location, assetInfo.AssetType, packageName: packageName); - - if (loadAssetCallbacks.LoadAssetUpdateCallback != null) + try { - InvokeProgress(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData).Forget(); + var asset = await GetOrLoadAssetAsync(location, assetInfo.AssetType, packageName, assetObjectKey, NormalizePriority(priority), default, + handle => StartProgressTask(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData)); + loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData); } - - await handle.ToUniTask(); - - if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed) + catch (Exception ex) { - _assetLoadingList.Remove(assetObjectKey); - string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", location); + Log.Error($"{errorMessage} {ex}"); if (loadAssetCallbacks.LoadAssetFailureCallback != null) { loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotReady, errorMessage, userData); return; } - 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); - } + throw; } } @@ -992,11 +868,21 @@ namespace AlicizaX.Resource.Runtime if (loadAssetUpdateCallback != null) { + float lastReportedProgress = -1f; while (assetHandle is { IsValid: true, IsDone: false }) { await UniTask.Yield(); + float progress = assetHandle.Progress; + if (lastReportedProgress < 0f || progress - lastReportedProgress >= ProgressCallbackThreshold) + { + lastReportedProgress = progress; + loadAssetUpdateCallback.Invoke(location, progress, userData); + } + } - loadAssetUpdateCallback.Invoke(location, assetHandle.Progress, userData); + if (assetHandle is { IsValid: true } && lastReportedProgress < 1f) + { + loadAssetUpdateCallback.Invoke(location, 1f, userData); } } } @@ -1039,29 +925,114 @@ namespace AlicizaX.Resource.Runtime #endregion - private readonly TimeoutController _timeoutController = new TimeoutController(); - - private async UniTask TryWaitingLoading(string assetObjectKey) + private async UniTask GetOrLoadAssetAsync(string location, Type assetType, string packageName, + string assetObjectKey, uint priority = 0, CancellationToken cancellationToken = default, Action onHandleCreated = null) { - if (_assetLoadingList.Contains(assetObjectKey)) + while (true) { + 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 { - 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()) + handle = GetHandleAsync(location, assetType, packageName: packageName, priority: priority); + onHandleCreated?.Invoke(handle); + await handle.ToUniTask(); + + if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed) { - Log.Error($"LoadAssetAsync Waiting {assetObjectKey} timeout. reason:{ex.Message}"); + throw new GameFrameworkException(Utility.Text.Format("Can not load asset '{0}'.", location)); } + + var assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle, this); + _assetPool.Register(assetObject, true); + CompleteLoading(assetObjectKey); + return handle.AssetObject as UnityEngine.Object; } + catch (Exception ex) + { + DisposeHandle(handle); + FailLoading(assetObjectKey, ex); + throw; + } + } + } + + private bool TryBeginLoading(string assetObjectKey) + { + if (_assetLoadingOperations.ContainsKey(assetObjectKey)) + { + return false; + } + + _assetLoadingOperations.Add(assetObjectKey, new UniTaskCompletionSource()); + 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(); } }