diff --git a/Runtime/Resource/Resource/Extension/ISetAssetObject.cs b/Runtime/Resource/Resource/Extension/ISetAssetObject.cs index 6ab7267..ff5f271 100644 --- a/Runtime/Resource/Resource/Extension/ISetAssetObject.cs +++ b/Runtime/Resource/Resource/Extension/ISetAssetObject.cs @@ -19,5 +19,11 @@ namespace AlicizaX.Resource.Runtime /// 是否可以回收。 /// bool IsCanRelease(); + + + /// + /// Unity资源对象。 + /// + public UnityEngine.Object TargetObject { get; set; } } } diff --git a/Runtime/Resource/Resource/Extension/Implement/SetSpriteObject.cs b/Runtime/Resource/Resource/Extension/Implement/SetSpriteObject.cs index f9aea6b..9d5810a 100644 --- a/Runtime/Resource/Resource/Extension/Implement/SetSpriteObject.cs +++ b/Runtime/Resource/Resource/Extension/Implement/SetSpriteObject.cs @@ -39,6 +39,8 @@ namespace AlicizaX.Resource.Runtime #endif private Sprite _sprite; + public Object TargetObject { get; set; } + public string Location { get; private set; } private bool _setNativeSize = false; @@ -90,6 +92,7 @@ namespace AlicizaX.Resource.Runtime _sprite = null; _setType = SetType.None; _setNativeSize = false; + TargetObject = null; } public static SetSpriteObject Create(Image image, string location, bool setNativeSize = false, CancellationToken cancellationToken = default) @@ -100,6 +103,7 @@ namespace AlicizaX.Resource.Runtime item.Location = location; item._cancellationToken = cancellationToken; item._setType = SetType.Image; + item.TargetObject = image; return item; } @@ -110,6 +114,7 @@ namespace AlicizaX.Resource.Runtime item.Location = location; item._cancellationToken = cancellationToken; item._setType = SetType.SpriteRender; + item.TargetObject = spriteRenderer; return item; } } diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs index 3fb4d4e..1aa4cc1 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs @@ -1,26 +1,52 @@ -using AlicizaX; +using System; +using System.Collections.Generic; +using System.Threading; using Cysharp.Threading.Tasks; namespace AlicizaX.Resource.Runtime { public partial class ResourceExtComponent { - /// - /// 资源组件。 - /// - private IResourceModule m_ResourceModule; + private static IResourceModule _resourceModule; + private LoadAssetCallbacks _loadAssetCallbacks; - private LoadAssetCallbacks m_LoadAssetCallbacks; + public static IResourceModule ResourceModule => _resourceModule; + + private class LoadingState : IMemory + { + public CancellationTokenSource Cts { get; set; } + public string Location { get; set; } + + public void Clear() + { + if (Cts != null) + { + Cts.Cancel(); + Cts.Dispose(); + Cts = null; + } + + Location = String.Empty; + } + } + + private static readonly Dictionary _loadingStates = new Dictionary(); private void InitializedResources() { - m_ResourceModule = ModuleSystem.GetModule(); - m_LoadAssetCallbacks = new LoadAssetCallbacks(OnLoadAssetSuccess, OnLoadAssetFailure); + _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); } @@ -29,10 +55,22 @@ namespace AlicizaX.Resource.Runtime _assetLoadingList.Remove(assetName); ISetAssetObject setAssetObject = (ISetAssetObject)userdata; UnityEngine.Object assetObject = asset as UnityEngine.Object; + if (assetObject != null) { - m_AssetItemPool.Register(AssetItemObject.Create(setAssetObject.Location, assetObject), true); - SetAsset(setAssetObject, 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 { @@ -41,23 +79,127 @@ namespace AlicizaX.Resource.Runtime } /// - /// 通过资源系统设置资源。 + /// 通过Unity对象加载资源。 /// - /// 需要设置的对象。 + /// ISetAssetObject。 + /// Unity对象类型。 public async UniTaskVoid SetAssetByResources(ISetAssetObject setAssetObject) where T : UnityEngine.Object { - await TryWaitingLoading(setAssetObject.Location); + var target = setAssetObject.TargetObject; + var location = setAssetObject.Location; - if (m_AssetItemPool.CanSpawn(setAssetObject.Location)) + if (target == null) { - var assetObject = (T)m_AssetItemPool.Spawn(setAssetObject.Location).Target; - SetAsset(setAssetObject, assetObject); + return; } - else + + // 取消并清理旧的加载请求。 + CancelAndCleanupOldRequest(target); + + // 创建新的加载状态 + var cts = new CancellationTokenSource(); + var loadingState = MemoryPool.Acquire(); + loadingState.Cts = cts; + loadingState.Location = location; + _loadingStates[target] = loadingState; + + try { - _assetLoadingList.Add(setAssetObject.Location); - m_ResourceModule.LoadAssetAsync(setAssetObject.Location, typeof(T), 0, m_LoadAssetCallbacks, setAssetObject); + // 等待其他可能正在进行的加载。 + await TryWaitingLoading(location).AttachExternalCancellation(cts.Token); + + // 再次检查是否被新请求替换。 + if (!IsCurrentLocation(target, location)) + { + return; + } + + // 检查缓存。 + if (_assetItemPool.CanSpawn(location)) + { + ClearLoadingState(target); + + var assetObject = (T)_assetItemPool.Spawn(location).Target; + SetAsset(setAssetObject, assetObject); + } + else + { + // 最后一次检查是否被替换。 + if (!IsCurrentLocation(target, location)) + { + return; + } + + // 防止重复加载同一资源。 + if (!_assetLoadingList.Add(location)) + { + // 已经在加载中,等待回调处理。 + return; + } + + _resourceModule.LoadAssetAsync(location, typeof(T), 0, _loadAssetCallbacks, setAssetObject); + } } + catch (OperationCanceledException) + { + // 请求被取消,正常情况,无需处理。 + } + catch (Exception ex) + { + Log.Error($"Failed to load asset '{location}': {ex}"); + ClearLoadingState(target); + } + } + + /// + /// 取消并清理旧的加载请求。 + /// Unity对象。 + /// + private void CancelAndCleanupOldRequest(UnityEngine.Object target) + { + if (_loadingStates.TryGetValue(target, out var oldState)) + { + MemoryPool.Release(oldState); + _loadingStates.Remove(target); + } + } + + /// + /// 清理加载状态。 + /// Unity对象。 + /// + private void ClearLoadingState(UnityEngine.Object target) + { + if (_loadingStates.TryGetValue(target, out var state)) + { + MemoryPool.Release(state); + _loadingStates.Remove(target); + } + } + + /// + /// 检查指定位置是否仍是该目标的当前加载位置。 + /// + private bool IsCurrentLocation(UnityEngine.Object target, string location) + { + if (target == null) + { + return false; + } + return _loadingStates.TryGetValue(target, out var state) && state.Location == location; + } + + /// + /// 组件销毁时清理所有资源。 + /// + private void OnDestroy() + { + foreach (var state in _loadingStates.Values) + { + MemoryPool.Release(state); + } + + _loadingStates.Clear(); } } } diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs index dcab63a..8f8dc72 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs @@ -47,7 +47,7 @@ namespace AlicizaX.Resource.Runtime /// /// 散图集合对象池 /// - private IObjectPool m_AssetItemPool; + private IObjectPool _assetItemPool; #if UNITY_EDITOR @@ -66,7 +66,7 @@ namespace AlicizaX.Resource.Runtime { yield return new WaitForEndOfFrame(); IObjectPoolModule objectPoolComponent = ModuleSystem.GetModule(); - m_AssetItemPool = objectPoolComponent.CreateMultiSpawnObjectPool( + _assetItemPool = objectPoolComponent.CreateMultiSpawnObjectPool( "SetAssetPool", m_AutoReleaseInterval, 16, 60, 0); m_LoadAssetObjectsLinkedList = new LinkedList(); @@ -105,7 +105,7 @@ namespace AlicizaX.Resource.Runtime var next = current.Next; if (current.Value.AssetObject.IsCanRelease()) { - m_AssetItemPool.Unspawn(current.Value.AssetTarget); + _assetItemPool.Unspawn(current.Value.AssetTarget); MemoryPool.Release(current.Value.AssetObject); m_LoadAssetObjectsLinkedList.Remove(current); }