From 4cf5eb57d2245bbfd86f98c72cc8d50bb5ad89af 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, 28 Apr 2026 11:31:58 +0800 Subject: [PATCH] [Opt] MemoryPool & ResourceService --- .../Benchmark/MemoryPoolBenchmark.cs | 23 ++ Runtime/MemoryPool/MemoryPool.cs | 17 + Runtime/MemoryPool/MemoryPoolHandle.cs | 32 ++ Runtime/MemoryPool/MemoryPoolHandle.cs.meta | 11 + Runtime/MemoryPool/MemoryPoolRegistry.cs | 17 + .../Resource/Callback/LoadAssetCallbacks.cs | 2 +- .../Resource/Extension/AssetItemObject.cs | 2 +- .../Resource/Extension/ISetAssetObject.cs | 2 +- .../Resource/Extension/LoadAssetObject.cs | 26 +- .../Extension/LoadAssetObject.cs.meta | 10 +- .../ResourceExtComponent.Resource.cs | 74 ++-- .../ResourceExtComponent.SubSprite.cs | 148 ++++++-- .../Extension/ResourceExtComponent.cs | 185 +++++----- .../Resource/Reference/AssetsReference.cs | 105 +++--- .../Resource/Reference/AssetsSetHelper.cs | 238 +++++++++---- .../Reference/AssetsSetHelper.cs.meta | 10 +- .../Reference/MaterialInstanceReference.cs | 29 ++ .../MaterialInstanceReference.cs.meta | 11 + .../Resource/Resource/ResourceComponent.cs | 17 +- .../Resource/ResourceService.AssetObject.cs | 11 +- .../ResourceService.Initialization.cs | 3 +- .../Resource/Resource/ResourceService.Pool.cs | 2 +- .../Resource/ResourceService.Services.cs | 5 +- Runtime/Resource/Resource/ResourceService.cs | 336 ++++++++++-------- 24 files changed, 853 insertions(+), 463 deletions(-) create mode 100644 Runtime/MemoryPool/MemoryPoolHandle.cs create mode 100644 Runtime/MemoryPool/MemoryPoolHandle.cs.meta create mode 100644 Runtime/Resource/Resource/Reference/MaterialInstanceReference.cs create mode 100644 Runtime/Resource/Resource/Reference/MaterialInstanceReference.cs.meta diff --git a/Runtime/MemoryPool/Benchmark/MemoryPoolBenchmark.cs b/Runtime/MemoryPool/Benchmark/MemoryPoolBenchmark.cs index d5346f6..86ab6f9 100644 --- a/Runtime/MemoryPool/Benchmark/MemoryPoolBenchmark.cs +++ b/Runtime/MemoryPool/Benchmark/MemoryPoolBenchmark.cs @@ -96,6 +96,7 @@ namespace AlicizaX RunCase("ClearAll Unschedule", RunClearAllUnschedule); RunCase("ClearAll Active Queue Reset", RunClearAllActiveQueueReset); RunCase("Type API Cold Path", RunTypeApiColdPath); + RunCase("Cached Handle Hot Path", RunCachedHandleHotPath); RunCase("Info Buffer No Alloc", RunInfoBufferNoAlloc); RunCase("Explicit Compact", RunExplicitCompact); } @@ -628,6 +629,28 @@ namespace AlicizaX } } + + private void RunCachedHandleHotPath() + { + using (s_ExtremeMarker.Auto()) + { + MemoryPool.ClearAll(); + MemoryPool.Prewarm(objectCount); + MemoryPoolHandle handle = MemoryPool.GetHandle(typeof(BenchmarkMemory)); + AssertTrue(handle.IsValid, "cached handle is invalid"); + + RestartCaseMeasure(); + for (int i = 0; i < loopCount; i++) + { + IMemory item = handle.Acquire(); + handle.Release(item); + } + StopCaseMeasure(); + + MemoryPool.ClearAll(); + } + } + private void RunInfoBufferNoAlloc() { using (s_InfoMarker.Auto()) diff --git a/Runtime/MemoryPool/MemoryPool.cs b/Runtime/MemoryPool/MemoryPool.cs index 8ae971e..aac4b6a 100644 --- a/Runtime/MemoryPool/MemoryPool.cs +++ b/Runtime/MemoryPool/MemoryPool.cs @@ -53,6 +53,19 @@ namespace AlicizaX return MemoryPool.Acquire(); } + /// + /// 获取动态内存类型的缓存句柄。运行时热路径应提前缓存该句柄,避免反复使用 Type 查找。 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryPoolHandle GetHandle(Type memoryType) + { + return MemoryPoolRegistry.GetHandle(memoryType); + } + + /// + /// 慢速动态路径。禁止在运行时热路径调用;请提前通过 GetHandle(Type) 缓存 MemoryPoolHandle 后再获取对象。 + /// + [Obsolete("慢速动态路径,禁止在运行时热路径使用。请缓存 MemoryPoolHandle,或改用 MemoryPool.Acquire / MemoryPool.Acquire。", false)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IMemory Acquire(Type memoryType) { @@ -66,6 +79,10 @@ namespace AlicizaX MemoryPool.Release(memory); } + /// + /// 慢速动态路径。禁止在运行时热路径调用;请通过缓存的 MemoryPoolHandle 或 Release<T> 回收对象。 + /// + [Obsolete("慢速动态路径,禁止在运行时热路径使用。请通过缓存的 MemoryPoolHandle,或改用 MemoryPool.Release / MemoryPool.Release。", false)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Release(IMemory memory) { diff --git a/Runtime/MemoryPool/MemoryPoolHandle.cs b/Runtime/MemoryPool/MemoryPoolHandle.cs new file mode 100644 index 0000000..a4f1acb --- /dev/null +++ b/Runtime/MemoryPool/MemoryPoolHandle.cs @@ -0,0 +1,32 @@ +using System.Runtime.CompilerServices; + +namespace AlicizaX +{ + public readonly struct MemoryPoolHandle + { + private readonly MemoryPoolRegistry.MemoryPoolHandle _handle; + + internal MemoryPoolHandle(MemoryPoolRegistry.MemoryPoolHandle handle) + { + _handle = handle; + } + + public bool IsValid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _handle != null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IMemory Acquire() + { + return _handle.Acquire(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Release(IMemory memory) + { + _handle.Release(memory); + } + } +} diff --git a/Runtime/MemoryPool/MemoryPoolHandle.cs.meta b/Runtime/MemoryPool/MemoryPoolHandle.cs.meta new file mode 100644 index 0000000..ed02852 --- /dev/null +++ b/Runtime/MemoryPool/MemoryPoolHandle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b7195e879c74d7c9d013fa76df5e37c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/MemoryPool/MemoryPoolRegistry.cs b/Runtime/MemoryPool/MemoryPoolRegistry.cs index f3c092a..56fd131 100644 --- a/Runtime/MemoryPool/MemoryPoolRegistry.cs +++ b/Runtime/MemoryPool/MemoryPoolRegistry.cs @@ -101,6 +101,23 @@ namespace AlicizaX } } + + public static AlicizaX.MemoryPoolHandle GetHandle(Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + if (s_Handles.TryGetValue(type, out var handle)) + return new AlicizaX.MemoryPoolHandle(handle); + + EnsureRegistered(type); + + if (s_Handles.TryGetValue(type, out handle)) + return new AlicizaX.MemoryPoolHandle(handle); + + throw new Exception($"MemoryPool: Type '{type.FullName}' is not a valid IMemory type."); + } + public static IMemory Acquire(Type type) { if (type == null) diff --git a/Runtime/Resource/Resource/Callback/LoadAssetCallbacks.cs b/Runtime/Resource/Resource/Callback/LoadAssetCallbacks.cs index c5c1907..41df7b0 100644 --- a/Runtime/Resource/Resource/Callback/LoadAssetCallbacks.cs +++ b/Runtime/Resource/Resource/Callback/LoadAssetCallbacks.cs @@ -1,4 +1,4 @@ -using AlicizaX; +using AlicizaX; namespace AlicizaX.Resource.Runtime { diff --git a/Runtime/Resource/Resource/Extension/AssetItemObject.cs b/Runtime/Resource/Resource/Extension/AssetItemObject.cs index 2a8e2e0..9d6272d 100644 --- a/Runtime/Resource/Resource/Extension/AssetItemObject.cs +++ b/Runtime/Resource/Resource/Extension/AssetItemObject.cs @@ -1,4 +1,4 @@ -using AlicizaX.ObjectPool; +using AlicizaX.ObjectPool; using AlicizaX; namespace AlicizaX.Resource.Runtime diff --git a/Runtime/Resource/Resource/Extension/ISetAssetObject.cs b/Runtime/Resource/Resource/Extension/ISetAssetObject.cs index ff5f271..c9c0d41 100644 --- a/Runtime/Resource/Resource/Extension/ISetAssetObject.cs +++ b/Runtime/Resource/Resource/Extension/ISetAssetObject.cs @@ -1,4 +1,4 @@ - + using AlicizaX; namespace AlicizaX.Resource.Runtime diff --git a/Runtime/Resource/Resource/Extension/LoadAssetObject.cs b/Runtime/Resource/Resource/Extension/LoadAssetObject.cs index 7c0f7e8..ecebe00 100644 --- a/Runtime/Resource/Resource/Extension/LoadAssetObject.cs +++ b/Runtime/Resource/Resource/Extension/LoadAssetObject.cs @@ -1,21 +1,31 @@ using System; -using UnityEngine; - +using AlicizaX; namespace AlicizaX.Resource.Runtime { [Serializable] - public class LoadAssetObject + public class LoadAssetObject : IMemory { - public ISetAssetObject AssetObject { get; } - public UnityEngine.Object AssetTarget { get; } + public ISetAssetObject AssetObject { get; private set; } + public UnityEngine.Object AssetTarget { get; private set; } #if UNITY_EDITOR public bool IsSelect { get; set; } #endif - public LoadAssetObject(ISetAssetObject obj, UnityEngine.Object assetTarget) + public static LoadAssetObject Create(ISetAssetObject obj, UnityEngine.Object assetTarget) { - AssetObject = obj; - AssetTarget = assetTarget; + LoadAssetObject item = MemoryPool.Acquire(); + item.AssetObject = obj; + item.AssetTarget = assetTarget; + return item; + } + + public void Clear() + { + AssetObject = null; + AssetTarget = null; +#if UNITY_EDITOR + IsSelect = false; +#endif } } } diff --git a/Runtime/Resource/Resource/Extension/LoadAssetObject.cs.meta b/Runtime/Resource/Resource/Extension/LoadAssetObject.cs.meta index c834831..b37161e 100644 --- a/Runtime/Resource/Resource/Extension/LoadAssetObject.cs.meta +++ b/Runtime/Resource/Resource/Extension/LoadAssetObject.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: b25c6b1f257bf3445b8e2651e169e314 -timeCreated: 1710733596 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs index 89150a3..c18d9e0 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs @@ -1,6 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; +using Cysharp.Text; using Cysharp.Threading.Tasks; namespace AlicizaX.Resource.Runtime @@ -49,9 +49,9 @@ namespace AlicizaX.Resource.Runtime } UnityEngine.Object assetObject = asset as UnityEngine.Object; - if (assetObject == null) - { - Log.Error($"Load failure asset type is {asset?.GetType()}."); + if (assetObject == null) + { + Log.Error(ZString.Format("Load failure asset type is {0}.", asset?.GetType())); return; } @@ -78,61 +78,50 @@ namespace AlicizaX.Resource.Runtime CancelAndCleanupOldRequest(target); - var linkedTokenSource = new CancellationTokenSource(); var loadingState = MemoryPool.Acquire(); - loadingState.Cts = linkedTokenSource; + CancellationToken loadToken = cancellationToken; if (cancellationToken.CanBeCanceled) { + var linkedTokenSource = new CancellationTokenSource(); + loadingState.Cts = linkedTokenSource; loadingState.Registration = cancellationToken.Register(static state => { ((CancellationTokenSource)state).Cancel(); }, linkedTokenSource); + loadToken = linkedTokenSource.Token; } loadingState.Location = location; _loadingStates[target] = loadingState; - try + if (!IsCurrentLocation(target, location)) { - if (!IsCurrentLocation(target, location)) - { - return; - } - - if (_assetItemPool.CanSpawn(location)) - { - ClearLoadingState(target); - - var assetObject = (T)_assetItemPool.Spawn(location).Target; - SetAsset(setAssetObject, assetObject); - return; - } - - if (!IsCurrentLocation(target, location)) - { - return; - } - - T resource = await _resourceService.LoadAssetAsync(location, linkedTokenSource.Token); - if (resource == null) - { - ClearLoadingState(target); - return; - } - - OnLoadAssetSuccess(location, resource, 0f, setAssetObject); + return; } - catch (OperationCanceledException) + + if (_assetItemPool.CanSpawn(location)) { ClearLoadingState(target); + + var assetObject = (T)_assetItemPool.Spawn(location).Target; + SetAsset(setAssetObject, assetObject); + return; } - catch (Exception ex) + + if (!IsCurrentLocation(target, location)) { - Log.Error($"Failed to load asset '{location}': {ex}"); - ClearLoadingState(target); + return; } + + var loadResult = await _resourceService.LoadAssetAsync(location, loadToken).SuppressCancellationThrow(); + if (loadResult.IsCanceled || loadResult.Result == null) + { + ClearLoadingState(target); + return; + } + + OnLoadAssetSuccess(location, loadResult.Result, 0f, setAssetObject); } - private void CancelAndCleanupOldRequest(UnityEngine.Object target) { if (_loadingStates.TryGetValue(target, out var oldState)) @@ -179,9 +168,10 @@ namespace AlicizaX.Resource.Runtime UnityEngine.Application.lowMemory -= OnLowMemory; ReleaseTrackedAssets(); - foreach (var state in _loadingStates.Values) + var enumerator = _loadingStates.GetEnumerator(); + while (enumerator.MoveNext()) { - MemoryPool.Release(state); + MemoryPool.Release(enumerator.Current.Value); } _loadingStates.Clear(); diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.SubSprite.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.SubSprite.cs index 4285e50..87107ab 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.SubSprite.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.SubSprite.cs @@ -1,6 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; +using Cysharp.Text; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UI; @@ -15,15 +15,60 @@ namespace AlicizaX.Resource.Runtime { private readonly Dictionary _subAssetsHandles = new Dictionary(); private readonly Dictionary _subSpriteReferences = new Dictionary(); - private readonly Dictionary> _subAssetLoadingOperations = new Dictionary>(); + private readonly Dictionary> _subAssetLoadingOperations = new Dictionary>(); + private readonly Dictionary _subSpriteCache = new Dictionary(SubSpriteKeyComparer.Instance); + + private readonly struct SubSpriteKey + { + public readonly string Location; + public readonly string SpriteName; + + public SubSpriteKey(string location, string spriteName) + { + Location = location ?? string.Empty; + SpriteName = spriteName ?? string.Empty; + } + } + + private sealed class SubSpriteKeyComparer : IEqualityComparer + { + public static readonly SubSpriteKeyComparer Instance = new SubSpriteKeyComparer(); + + private SubSpriteKeyComparer() + { + } + + public bool Equals(SubSpriteKey x, SubSpriteKey y) + { + return string.Equals(x.Location, y.Location, System.StringComparison.Ordinal) && + string.Equals(x.SpriteName, y.SpriteName, System.StringComparison.Ordinal); + } + + public int GetHashCode(SubSpriteKey obj) + { + unchecked + { + int hash = 17; + hash = hash * 31 + System.StringComparer.Ordinal.GetHashCode(obj.Location ?? string.Empty); + hash = hash * 31 + System.StringComparer.Ordinal.GetHashCode(obj.SpriteName ?? string.Empty); + return hash; + } + } + } public async UniTask SetSubSprite(Image image, string location, string spriteName, bool setNativeSize = false, CancellationToken cancellationToken = default) { - var subSprite = await GetSubSpriteImp(location, spriteName, cancellationToken); - if (image == null) { - Log.Warning($"SetSubAssets Image is null"); + Log.Warning("SetSubAssets Image is null"); + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + var subSprite = await GetSubSpriteImp(location, spriteName, cancellationToken); + + if (image == null || cancellationToken.IsCancellationRequested || subSprite == null) + { ReleaseSubAssetsIfUnused(location); return; } @@ -39,11 +84,17 @@ namespace AlicizaX.Resource.Runtime public async UniTask SetSubSprite(SpriteRenderer spriteRenderer, string location, string spriteName, CancellationToken cancellationToken = default) { - var subSprite = await GetSubSpriteImp(location, spriteName, cancellationToken); - if (spriteRenderer == null) { - Log.Warning($"SetSubAssets Image is null"); + Log.Warning("SetSubAssets Image is null"); + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + var subSprite = await GetSubSpriteImp(location, spriteName, cancellationToken); + + if (spriteRenderer == null || cancellationToken.IsCancellationRequested || subSprite == null) + { ReleaseSubAssetsIfUnused(location); return; } @@ -57,17 +108,28 @@ namespace AlicizaX.Resource.Runtime var assetInfo = YooAssets.GetAssetInfo(location); if (assetInfo.IsInvalid) { - throw new GameFrameworkException($"Invalid location: {location}"); + throw new GameFrameworkException(ZString.Format("Invalid location: {0}", location)); + } + + SubSpriteKey key = new SubSpriteKey(location, spriteName); + if (_subSpriteCache.TryGetValue(key, out Sprite cachedSprite)) + { + return cachedSprite; } var subAssetsHandle = await GetOrLoadSubAssetsHandleAsync(location, cancellationToken); + if (!subAssetsHandle.IsValid) + { + return null; + } var subSprite = subAssetsHandle.GetSubAssetObject(spriteName); if (subSprite == null) { - throw new GameFrameworkException($"Invalid sprite name: {spriteName}"); + throw new GameFrameworkException(ZString.Format("Invalid sprite name: {0}", spriteName)); } + _subSpriteCache[key] = subSprite; return subSprite; } @@ -104,6 +166,7 @@ namespace AlicizaX.Resource.Runtime { subAssetsHandle.Dispose(); _subAssetsHandles.Remove(location); + ClearSubSpriteCache(location); } } @@ -130,31 +193,59 @@ namespace AlicizaX.Resource.Runtime continue; } - var completionSource = new UniTaskCompletionSource(); + var completionSource = AutoResetUniTaskCompletionSource.Create(); _subAssetLoadingOperations.Add(location, completionSource); - SubAssetsHandle subAssetsHandle = default; - try + SubAssetsHandle subAssetsHandle = YooAssets.LoadSubAssetsAsync(location); + while (subAssetsHandle is { IsValid: true, IsDone: false }) { - subAssetsHandle = YooAssets.LoadSubAssetsAsync(location); - await subAssetsHandle.ToUniTask(); - _subAssetsHandles[location] = subAssetsHandle; - completionSource.TrySetResult(subAssetsHandle); - return subAssetsHandle; + if (cancellationToken.IsCancellationRequested) + { + DisposeSubAssetsHandle(subAssetsHandle); + CompleteSubAssetLoading(location, completionSource, default); + return default; + } + + await UniTask.Yield(); } - catch (Exception ex) + + if (!subAssetsHandle.IsValid || subAssetsHandle.Status == EOperationStatus.Failed) { - subAssetsHandle?.Dispose(); - completionSource.TrySetException(ex); - throw; - } - finally - { - _subAssetLoadingOperations.Remove(location); + DisposeSubAssetsHandle(subAssetsHandle); + CompleteSubAssetLoading(location, completionSource, default); + return default; } + + _subAssetsHandles[location] = subAssetsHandle; + CompleteSubAssetLoading(location, completionSource, subAssetsHandle); + return subAssetsHandle; } } + private void CompleteSubAssetLoading(string location, AutoResetUniTaskCompletionSource completionSource, SubAssetsHandle subAssetsHandle) + { + _subAssetLoadingOperations.Remove(location); + completionSource.TrySetResult(subAssetsHandle); + } + + private static void DisposeSubAssetsHandle(SubAssetsHandle subAssetsHandle) + { + if (subAssetsHandle.IsValid) + { + subAssetsHandle.Dispose(); + } + } + + private void ClearSubSpriteCache(string location) + { + if (_subSpriteCache.Count == 0) + { + return; + } + + _subSpriteCache.Clear(); + } + private void ReleaseSubAssetsIfUnused(string location) { if (string.IsNullOrEmpty(location)) @@ -171,6 +262,7 @@ namespace AlicizaX.Resource.Runtime { subAssetsHandle.Dispose(); _subAssetsHandles.Remove(location); + ClearSubSpriteCache(location); } } } @@ -204,4 +296,4 @@ namespace AlicizaX.Resource.Runtime } } } -} +} \ No newline at end of file diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs index b68fbe7..fbe11ce 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs @@ -1,10 +1,8 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using AlicizaX.ObjectPool; -using Cysharp.Threading.Tasks; using UnityEngine; -using UnityEngine.Serialization; using Object = UnityEngine.Object; namespace AlicizaX.Resource.Runtime @@ -17,54 +15,27 @@ namespace AlicizaX.Resource.Runtime { public static ResourceExtComponent Instance { private set; get; } - /// - /// 检查是否可以释放间隔。 - /// [SerializeField] private float checkCanReleaseInterval = 30f; - /// - /// 对象池自动释放时间间隔。 - /// [SerializeField] private float autoReleaseInterval = 60f; - /// - /// 每帧最大处理资源数量,用于分帧处理避免卡顿。 - /// [SerializeField] private int maxProcessPerFrame = 50; [SerializeField] private int releaseCheckThreshold = 16; - /// - /// 当前正在处理的节点,用于分帧处理。 - /// - private LinkedListNode _currentProcessNode; - - /// - /// 保存加载的图片对象。 - /// - private LinkedList _loadAssetObjectsLinkedList; - - private Dictionary> _trackedAssetNodes; + private LoadAssetObject[] _loadAssetObjects; + private int _loadAssetObjectCount; + private int _currentProcessIndex; + private Dictionary _trackedAssetIndices; private bool _releaseRequested; private float _nextReleaseCheckTime = float.MaxValue; - /// - /// 散图集合对象池。 - /// private IObjectPool _assetItemPool; - -#if UNITY_EDITOR - public LinkedList LoadAssetObjectsLinkedList - { - get => _loadAssetObjectsLinkedList; - set => _loadAssetObjectsLinkedList = value; - } -#endif private IEnumerator Start() { Instance = this; @@ -80,8 +51,8 @@ namespace AlicizaX.Resource.Runtime capacity: 16, expireTime: 60, priority: 0)); - _loadAssetObjectsLinkedList = new LinkedList(); - _trackedAssetNodes = new Dictionary>(16); + _loadAssetObjects = new LoadAssetObject[16]; + _trackedAssetIndices = new Dictionary(16); InitializedResources(); } @@ -102,50 +73,36 @@ namespace AlicizaX.Resource.Runtime ReleaseUnused(); } - /// - /// 回收无引用的缓存资产。 - /// 使用分帧处理优化性能,避免一次性处理大量资源导致卡顿。 - /// public void ReleaseUnused() { - if (_loadAssetObjectsLinkedList == null || _loadAssetObjectsLinkedList.Count == 0) + if (_loadAssetObjectCount == 0) { - _currentProcessNode = null; + _currentProcessIndex = 0; _releaseRequested = false; _nextReleaseCheckTime = float.MaxValue; enabled = false; return; } - // 如果当前没有正在处理的节点,从头开始 - if (_currentProcessNode == null) - { - _currentProcessNode = _loadAssetObjectsLinkedList.First; - } - int processedCount = 0; - LinkedListNode current = _currentProcessNode; - - // 分帧处理:每帧最多处理 maxProcessPerFrame 个资源 - while (current != null && processedCount < maxProcessPerFrame) + while (_currentProcessIndex < _loadAssetObjectCount && processedCount < maxProcessPerFrame) { - var next = current.Next; - - if (current.Value.AssetObject.IsCanRelease()) + LoadAssetObject item = _loadAssetObjects[_currentProcessIndex]; + if (item.AssetObject.IsCanRelease()) { - RemoveTrackedNode(current, releaseAsset: true); + RemoveTrackedAt(_currentProcessIndex, releaseAsset: true); + } + else + { + _currentProcessIndex++; } - current = next; processedCount++; } - // 更新当前处理节点 - _currentProcessNode = current; - - // 如果已经处理完所有节点,重置状态 - if (_currentProcessNode == null) + if (_currentProcessIndex >= _loadAssetObjectCount) { + _currentProcessIndex = 0; _releaseRequested = false; _nextReleaseCheckTime = float.MaxValue; enabled = false; @@ -159,15 +116,17 @@ namespace AlicizaX.Resource.Runtime private void SetAsset(ISetAssetObject setAssetObject, Object assetObject) { ReplaceTrackedAsset(setAssetObject.TargetObject); + EnsureTrackedCapacity(_loadAssetObjectCount + 1); - var node = _loadAssetObjectsLinkedList.AddLast(new LoadAssetObject(setAssetObject, assetObject)); + int index = _loadAssetObjectCount++; + _loadAssetObjects[index] = LoadAssetObject.Create(setAssetObject, assetObject); if (setAssetObject.TargetObject != null) { - _trackedAssetNodes[setAssetObject.TargetObject] = node; + _trackedAssetIndices[setAssetObject.TargetObject] = index; } setAssetObject.SetAsset(assetObject); - if (_loadAssetObjectsLinkedList.Count >= releaseCheckThreshold) + if (_loadAssetObjectCount >= releaseCheckThreshold) { ScheduleReleaseSweep(); } @@ -175,73 +134,105 @@ namespace AlicizaX.Resource.Runtime private void ReplaceTrackedAsset(Object target) { - if (target == null || _trackedAssetNodes == null) + if (target == null || _trackedAssetIndices == null) { return; } - if (_trackedAssetNodes.TryGetValue(target, out var existingNode)) + if (_trackedAssetIndices.TryGetValue(target, out int existingIndex)) { - if (_currentProcessNode == existingNode) - { - _currentProcessNode = existingNode.Next; - } - - RemoveTrackedNode(existingNode, releaseAsset: true); + RemoveTrackedAt(existingIndex, releaseAsset: true); } } - private void RemoveTrackedNode(LinkedListNode node, bool releaseAsset) + private void RemoveTrackedAt(int index, bool releaseAsset) { - if (node == null) + if (index < 0 || index >= _loadAssetObjectCount) { return; } - var trackedObject = node.Value.AssetObject?.TargetObject; - if (trackedObject != null && _trackedAssetNodes != null) + LoadAssetObject item = _loadAssetObjects[index]; + var trackedObject = item.AssetObject?.TargetObject; + if (trackedObject != null && _trackedAssetIndices != null) { - _trackedAssetNodes.Remove(trackedObject); + _trackedAssetIndices.Remove(trackedObject); } - if (releaseAsset && node.Value.AssetTarget != null) + if (releaseAsset && item.AssetTarget != null) { - _resourceService?.UnloadAsset(node.Value.AssetTarget); - _assetItemPool?.Unspawn(node.Value.AssetTarget); + _resourceService?.UnloadAsset(item.AssetTarget); + _assetItemPool?.Unspawn(item.AssetTarget); } - if (node.Value.AssetObject != null) + if (item.AssetObject != null) { - MemoryPool.Release(node.Value.AssetObject); + ReleaseSetAssetObject(item.AssetObject); } - _loadAssetObjectsLinkedList?.Remove(node); - if (_loadAssetObjectsLinkedList != null && _loadAssetObjectsLinkedList.Count > 0) + MemoryPool.Release(item); + + int lastIndex = _loadAssetObjectCount - 1; + if (index != lastIndex) + { + LoadAssetObject lastItem = _loadAssetObjects[lastIndex]; + _loadAssetObjects[index] = lastItem; + Object movedTarget = lastItem.AssetObject?.TargetObject; + if (movedTarget != null && _trackedAssetIndices != null) + { + _trackedAssetIndices[movedTarget] = index; + } + } + + _loadAssetObjects[lastIndex] = null; + _loadAssetObjectCount = lastIndex; + if (_currentProcessIndex > index) + { + _currentProcessIndex--; + } + + if (_loadAssetObjectCount > 0) { ScheduleReleaseSweep(checkCanReleaseInterval); } } + + private static void ReleaseSetAssetObject(ISetAssetObject assetObject) + { + if (assetObject is SetSpriteObject setSpriteObject) + { + MemoryPool.Release(setSpriteObject); + } + } private void ReleaseTrackedAssets() { - if (_loadAssetObjectsLinkedList == null) + for (int i = _loadAssetObjectCount - 1; i >= 0; i--) + { + RemoveTrackedAt(i, releaseAsset: true); + } + + _currentProcessIndex = 0; + _trackedAssetIndices?.Clear(); + _releaseRequested = false; + _nextReleaseCheckTime = float.MaxValue; + enabled = false; + } + + private void EnsureTrackedCapacity(int capacity) + { + if (_loadAssetObjects.Length >= capacity) { return; } - var current = _loadAssetObjectsLinkedList.First; - while (current != null) + int newCapacity = _loadAssetObjects.Length << 1; + while (newCapacity < capacity) { - var next = current.Next; - RemoveTrackedNode(current, releaseAsset: true); - current = next; + newCapacity <<= 1; } - _currentProcessNode = null; - _trackedAssetNodes?.Clear(); - _releaseRequested = false; - _nextReleaseCheckTime = float.MaxValue; - enabled = false; + Array.Resize(ref _loadAssetObjects, newCapacity); } private void ScheduleReleaseSweep(float delay = 0f) @@ -257,4 +248,4 @@ namespace AlicizaX.Resource.Runtime ScheduleReleaseSweep(); } } -} +} \ No newline at end of file diff --git a/Runtime/Resource/Resource/Reference/AssetsReference.cs b/Runtime/Resource/Resource/Reference/AssetsReference.cs index a89f963..1b06c53 100644 --- a/Runtime/Resource/Resource/Reference/AssetsReference.cs +++ b/Runtime/Resource/Resource/Reference/AssetsReference.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System; using UnityEngine; using Object = UnityEngine.Object; @@ -26,10 +25,12 @@ namespace AlicizaX.Resource.Runtime private GameObject sourceGameObject; [SerializeField] - private List refAssetInfoList; + private AssetsRefInfo[] refAssetInfoArray; + + [SerializeField] + private int refAssetInfoCount; private static IResourceService _resourceService; - private HashSet _refAssetIds; private bool TryEnsureResourceModule() { @@ -51,26 +52,19 @@ namespace AlicizaX.Resource.Runtime } } - private void EnsureReferenceCache() + private bool ContainsRefAsset(int instanceId) { - if (_refAssetIds != null) + for (int i = 0; i < refAssetInfoCount; i++) { - return; - } - - _refAssetIds = new HashSet(); - if (refAssetInfoList == null) - { - return; - } - - foreach (var refInfo in refAssetInfoList) - { - if (refInfo.refAsset != null) + AssetsRefInfo refInfo = refAssetInfoArray[i]; + int refInstanceId = refInfo.instanceId != 0 ? refInfo.instanceId : refInfo.refAsset != null ? refInfo.refAsset.GetInstanceID() : 0; + if (refInstanceId == instanceId) { - _refAssetIds.Add(refInfo.instanceId != 0 ? refInfo.instanceId : refInfo.refAsset.GetInstanceID()); + return true; } } + + return false; } private void OnDestroy() @@ -78,40 +72,60 @@ namespace AlicizaX.Resource.Runtime if (!TryEnsureResourceModule()) { sourceGameObject = null; - refAssetInfoList?.Clear(); - _refAssetIds?.Clear(); + ClearRefAssetInfoArray(); return; } ReleaseSourceReference(); - ReleaseRefAssetInfoList(); + ReleaseRefAssetInfoArray(); } - private void ReleaseRefAssetInfoList() + private void ReleaseRefAssetInfoArray() { - if (refAssetInfoList != null) + for (int i = 0; i < refAssetInfoCount; i++) { - foreach (var refInfo in refAssetInfoList) - { - _resourceService.UnloadAsset(refInfo.refAsset); - } - - refAssetInfoList.Clear(); + _resourceService.UnloadAsset(refAssetInfoArray[i].refAsset); } - _refAssetIds?.Clear(); + ClearRefAssetInfoArray(); + } + + private void ClearRefAssetInfoArray() + { + for (int i = 0; i < refAssetInfoCount; i++) + { + refAssetInfoArray[i] = default; + } + + refAssetInfoCount = 0; + } + + private void EnsureRefAssetCapacity(int capacity) + { + if (refAssetInfoArray != null && refAssetInfoArray.Length >= capacity) + { + return; + } + + int newCapacity = refAssetInfoArray == null || refAssetInfoArray.Length == 0 ? 4 : refAssetInfoArray.Length << 1; + while (newCapacity < capacity) + { + newCapacity <<= 1; + } + + Array.Resize(ref refAssetInfoArray, newCapacity); } public AssetsReference Ref(GameObject source, IResourceService resourceService = null) { if (source == null) { - throw new GameFrameworkException($"Source gameObject is null."); + throw new GameFrameworkException("Source gameObject is null."); } if (source.scene.name != null) { - throw new GameFrameworkException($"Source gameObject is in scene."); + throw new GameFrameworkException("Source gameObject is in scene."); } if (resourceService != null) @@ -132,7 +146,7 @@ namespace AlicizaX.Resource.Runtime { if (source == null) { - throw new GameFrameworkException($"Source gameObject is null."); + throw new GameFrameworkException("Source gameObject is null."); } if (resourceService != null) @@ -140,19 +154,14 @@ namespace AlicizaX.Resource.Runtime _resourceService = resourceService; } - EnsureReferenceCache(); int instanceId = source.GetInstanceID(); - if (!_refAssetIds.Add(instanceId)) + if (ContainsRefAsset(instanceId)) { return this; } - if (refAssetInfoList == null) - { - refAssetInfoList = new List(); - } - - refAssetInfoList.Add(new AssetsRefInfo(source)); + EnsureRefAssetCapacity(refAssetInfoCount + 1); + refAssetInfoArray[refAssetInfoCount++] = new AssetsRefInfo(source); return this; } @@ -160,12 +169,12 @@ namespace AlicizaX.Resource.Runtime { if (source == null) { - throw new GameFrameworkException($"Source gameObject is null."); + throw new GameFrameworkException("Source gameObject is null."); } if (source.scene.name != null) { - throw new GameFrameworkException($"Source gameObject is in scene."); + throw new GameFrameworkException("Source gameObject is in scene."); } GameObject instance = Object.Instantiate(source, parent); @@ -176,12 +185,12 @@ namespace AlicizaX.Resource.Runtime { if (source == null) { - throw new GameFrameworkException($"Source gameObject is null."); + throw new GameFrameworkException("Source gameObject is null."); } if (source.scene.name != null) { - throw new GameFrameworkException($"Source gameObject is in scene."); + throw new GameFrameworkException("Source gameObject is in scene."); } var comp = instance.GetComponent(); @@ -192,11 +201,11 @@ namespace AlicizaX.Resource.Runtime { if (source == null) { - throw new GameFrameworkException($"Source gameObject is null."); + throw new GameFrameworkException("Source gameObject is null."); } var comp = instance.GetComponent(); return comp ? comp.Ref(source, resourceService) : instance.AddComponent().Ref(source, resourceService); } } -} +} \ No newline at end of file diff --git a/Runtime/Resource/Resource/Reference/AssetsSetHelper.cs b/Runtime/Resource/Resource/Reference/AssetsSetHelper.cs index b89f81b..4d7d3d9 100644 --- a/Runtime/Resource/Resource/Reference/AssetsSetHelper.cs +++ b/Runtime/Resource/Resource/Reference/AssetsSetHelper.cs @@ -1,4 +1,5 @@ -using AlicizaX; +using AlicizaX; +using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UI; @@ -6,6 +7,140 @@ namespace AlicizaX.Resource.Runtime { public static class AssetsSetHelper { + private enum MaterialTargetType + { + Image, + SpriteRenderer, + MeshRenderer, + MeshRendererShared, + } + + private sealed class MaterialSetRequest : IMemory + { + public MaterialTargetType TargetType; + public Image Image; + public SpriteRenderer SpriteRenderer; + public MeshRenderer MeshRenderer; + public bool NeedInstance; + + public static MaterialSetRequest Create(Image image) + { + MaterialSetRequest request = MemoryPool.Acquire(); + request.TargetType = MaterialTargetType.Image; + request.Image = image; + return request; + } + + public static MaterialSetRequest Create(SpriteRenderer spriteRenderer) + { + MaterialSetRequest request = MemoryPool.Acquire(); + request.TargetType = MaterialTargetType.SpriteRenderer; + request.SpriteRenderer = spriteRenderer; + return request; + } + + public static MaterialSetRequest Create(MeshRenderer meshRenderer, bool needInstance, bool sharedMaterial) + { + MaterialSetRequest request = MemoryPool.Acquire(); + request.TargetType = sharedMaterial ? MaterialTargetType.MeshRendererShared : MaterialTargetType.MeshRenderer; + request.MeshRenderer = meshRenderer; + request.NeedInstance = needInstance; + return request; + } + + public void Apply(Material material) + { + if (material == null) + { + MemoryPool.Release(this); + return; + } + + switch (TargetType) + { + case MaterialTargetType.Image: + { + if (Image == null || Image.gameObject == null) + { + _resourceService.UnloadAsset(material); + MemoryPool.Release(this); + return; + } + + Image.material = material; + AssetsReference.Ref(material, Image.gameObject); + break; + } + case MaterialTargetType.SpriteRenderer: + { + if (SpriteRenderer == null || SpriteRenderer.gameObject == null) + { + _resourceService.UnloadAsset(material); + MemoryPool.Release(this); + return; + } + + SpriteRenderer.material = material; + AssetsReference.Ref(material, SpriteRenderer.gameObject); + break; + } + case MaterialTargetType.MeshRenderer: + { + if (MeshRenderer == null || MeshRenderer.gameObject == null) + { + _resourceService.UnloadAsset(material); + MemoryPool.Release(this); + return; + } + + SetMeshMaterial(MeshRenderer, material, NeedInstance); + break; + } + case MaterialTargetType.MeshRendererShared: + { + if (MeshRenderer == null || MeshRenderer.gameObject == null) + { + _resourceService.UnloadAsset(material); + MemoryPool.Release(this); + return; + } + + MeshRenderer.sharedMaterial = material; + AssetsReference.Ref(material, MeshRenderer.gameObject); + break; + } + } + + MemoryPool.Release(this); + } + + public void Clear() + { + TargetType = MaterialTargetType.Image; + Image = null; + SpriteRenderer = null; + MeshRenderer = null; + NeedInstance = false; + } + } + + private sealed class MaterialLoadCallbacks + { + public static readonly LoadAssetCallbacks Instance = new LoadAssetCallbacks(OnSuccess, OnFailure); + + private static void OnSuccess(string assetName, object asset, float duration, object userData) + { + MaterialSetRequest request = (MaterialSetRequest)userData; + request.Apply(asset as Material); + } + + private static void OnFailure(string assetName, LoadResourceStatus status, string errorMessage, object userData) + { + MaterialSetRequest request = (MaterialSetRequest)userData; + MemoryPool.Release(request); + } + } + private static IResourceService _resourceService; private static void CheckResourceManager() @@ -16,13 +151,39 @@ namespace AlicizaX.Resource.Runtime } } + private static void LoadMaterialAsync(string location, string packageName, MaterialSetRequest request) + { + _resourceService.LoadAssetAsync(location, typeof(Material), 0, MaterialLoadCallbacks.Instance, request, packageName).Forget(); + } + + private static void SetMeshMaterial(MeshRenderer meshRenderer, Material material, bool needInstance) + { + if (!needInstance) + { + meshRenderer.material = material; + AssetsReference.Ref(material, meshRenderer.gameObject); + return; + } + + Material instance = Object.Instantiate(material); + meshRenderer.material = instance; + AssetsReference.Ref(material, meshRenderer.gameObject); + var reference = meshRenderer.GetComponent(); + if (reference == null) + { + reference = meshRenderer.gameObject.AddComponent(); + } + + reference.Set(instance); + } + #region SetMaterial public static void SetMaterial(this Image image, string location, bool isAsync = false, string packageName = "") { if (image == null) { - throw new GameFrameworkException($"SetSprite failed. Because image is null."); + throw new GameFrameworkException("SetSprite failed. Because image is null."); } CheckResourceManager(); @@ -32,29 +193,17 @@ namespace AlicizaX.Resource.Runtime Material material = _resourceService.LoadAsset(location, packageName); image.material = material; AssetsReference.Ref(material, image.gameObject); + return; } - else - { - _resourceService.LoadAsset(location, material => - { - if (image == null || image.gameObject == null) - { - _resourceService.UnloadAsset(material); - material = null; - return; - } - image.material = material; - AssetsReference.Ref(material, image.gameObject); - }, packageName); - } + LoadMaterialAsync(location, packageName, MaterialSetRequest.Create(image)); } public static void SetMaterial(this SpriteRenderer spriteRenderer, string location, bool isAsync = false, string packageName = "") { if (spriteRenderer == null) { - throw new GameFrameworkException($"SetSprite failed. Because image is null."); + throw new GameFrameworkException("SetSprite failed. Because image is null."); } CheckResourceManager(); @@ -64,29 +213,17 @@ namespace AlicizaX.Resource.Runtime Material material = _resourceService.LoadAsset(location, packageName); spriteRenderer.material = material; AssetsReference.Ref(material, spriteRenderer.gameObject); + return; } - else - { - _resourceService.LoadAsset(location, material => - { - if (spriteRenderer == null || spriteRenderer.gameObject == null) - { - _resourceService.UnloadAsset(material); - material = null; - return; - } - spriteRenderer.material = material; - AssetsReference.Ref(material, spriteRenderer.gameObject); - }, packageName); - } + LoadMaterialAsync(location, packageName, MaterialSetRequest.Create(spriteRenderer)); } public static void SetMaterial(this MeshRenderer meshRenderer, string location, bool needInstance = true, bool isAsync = false, string packageName = "") { if (meshRenderer == null) { - throw new GameFrameworkException($"SetSprite failed. Because image is null."); + throw new GameFrameworkException("SetSprite failed. Because image is null."); } CheckResourceManager(); @@ -94,31 +231,18 @@ namespace AlicizaX.Resource.Runtime if (!isAsync) { Material material = _resourceService.LoadAsset(location, packageName); - meshRenderer.material = needInstance ? Object.Instantiate(material) : material; - AssetsReference.Ref(material, meshRenderer.gameObject); + SetMeshMaterial(meshRenderer, material, needInstance); + return; } - else - { - _resourceService.LoadAsset(location, material => - { - if (meshRenderer == null || meshRenderer.gameObject == null) - { - _resourceService.UnloadAsset(material); - material = null; - return; - } - meshRenderer.material = needInstance ? Object.Instantiate(material) : material; - AssetsReference.Ref(material, meshRenderer.gameObject); - }, packageName); - } + LoadMaterialAsync(location, packageName, MaterialSetRequest.Create(meshRenderer, needInstance, false)); } public static void SetSharedMaterial(this MeshRenderer meshRenderer, string location, bool isAsync = false, string packageName = "") { if (meshRenderer == null) { - throw new GameFrameworkException($"SetSprite failed. Because image is null."); + throw new GameFrameworkException("SetSprite failed. Because image is null."); } CheckResourceManager(); @@ -128,22 +252,10 @@ namespace AlicizaX.Resource.Runtime Material material = _resourceService.LoadAsset(location, packageName); meshRenderer.sharedMaterial = material; AssetsReference.Ref(material, meshRenderer.gameObject); + return; } - else - { - _resourceService.LoadAsset(location, material => - { - if (meshRenderer == null || meshRenderer.gameObject == null) - { - _resourceService.UnloadAsset(material); - material = null; - return; - } - meshRenderer.sharedMaterial = material; - AssetsReference.Ref(material, meshRenderer.gameObject); - }, packageName); - } + LoadMaterialAsync(location, packageName, MaterialSetRequest.Create(meshRenderer, false, true)); } #endregion diff --git a/Runtime/Resource/Resource/Reference/AssetsSetHelper.cs.meta b/Runtime/Resource/Resource/Reference/AssetsSetHelper.cs.meta index 8ba320a..7a444f5 100644 --- a/Runtime/Resource/Resource/Reference/AssetsSetHelper.cs.meta +++ b/Runtime/Resource/Resource/Reference/AssetsSetHelper.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: 370ab69f738b11b429fbcc1d9e7a2fb1 -timeCreated: 1708490345 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Resource/Resource/Reference/MaterialInstanceReference.cs b/Runtime/Resource/Resource/Reference/MaterialInstanceReference.cs new file mode 100644 index 0000000..dd05a46 --- /dev/null +++ b/Runtime/Resource/Resource/Reference/MaterialInstanceReference.cs @@ -0,0 +1,29 @@ +using UnityEngine; + +namespace AlicizaX.Resource.Runtime +{ + [DisallowMultipleComponent] + public sealed class MaterialInstanceReference : MonoBehaviour + { + private Material _material; + + public void Set(Material material) + { + if (_material != null && _material != material) + { + Object.Destroy(_material); + } + + _material = material; + } + + private void OnDestroy() + { + if (_material != null) + { + Object.Destroy(_material); + _material = null; + } + } + } +} diff --git a/Runtime/Resource/Resource/Reference/MaterialInstanceReference.cs.meta b/Runtime/Resource/Resource/Reference/MaterialInstanceReference.cs.meta new file mode 100644 index 0000000..4560b10 --- /dev/null +++ b/Runtime/Resource/Resource/Reference/MaterialInstanceReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eec0821e60a7c3946b3b434cfdbd999d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Resource/Resource/ResourceComponent.cs b/Runtime/Resource/Resource/ResourceComponent.cs index ed81ea1..2755711 100644 --- a/Runtime/Resource/Resource/ResourceComponent.cs +++ b/Runtime/Resource/Resource/ResourceComponent.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Cysharp.Text; using AlicizaX; using UnityEngine; using UnityEngine.Serialization; @@ -17,6 +18,8 @@ namespace AlicizaX.Resource.Runtime private bool _forceUnloadUnusedAssets = false; + private bool _forceSystemUnloadUnusedAssets = false; + private bool _preorderUnloadUnusedAssets = false; private bool _performGCCollect = false; @@ -153,7 +156,9 @@ namespace AlicizaX.Resource.Runtime Application.lowMemory += OnLowMemory; } - public static string PrefsKey = Application.dataPath.GetHashCode() + "GamePlayMode"; + #if UNITY_EDITOR + public static string PrefsKey = ZString.Concat(Application.dataPath.GetHashCode(), "GamePlayMode"); + #endif private void Start() { @@ -173,7 +178,7 @@ namespace AlicizaX.Resource.Runtime _resourceService.AssetExpireTime = assetExpireTime; _resourceService.AssetPriority = assetPriority; _resourceService.SetForceUnloadUnusedAssetsAction(ForceUnloadUnusedAssets); - Log.Info($"ResourceModule Run Mode {_playMode}"); + Log.Info(ZString.Format("ResourceModule Run Mode {0}", _playMode)); } private void OnApplicationQuit() @@ -189,6 +194,7 @@ namespace AlicizaX.Resource.Runtime if (performGCCollect) { _performGCCollect = true; + _forceSystemUnloadUnusedAssets = true; } } @@ -200,12 +206,13 @@ namespace AlicizaX.Resource.Runtime if (_asyncOperation == null && (_forceUnloadUnusedAssets || _lastUnloadUnusedAssetsOperationElapseSeconds >= maxUnloadUnusedAssetsInterval || _preorderUnloadUnusedAssets && _lastUnloadUnusedAssetsOperationElapseSeconds >= minUnloadUnusedAssetsInterval)) { - Log.Info("Unload unused assets..."); + bool useSystemUnload = _forceSystemUnloadUnusedAssets && useSystemUnloadUnusedAssets; _forceUnloadUnusedAssets = false; + _forceSystemUnloadUnusedAssets = false; _preorderUnloadUnusedAssets = false; _lastUnloadUnusedAssetsOperationElapseSeconds = 0f; _resourceService.UnloadUnusedAssets(); - _asyncOperation = useSystemUnloadUnusedAssets ? Resources.UnloadUnusedAssets() : null; + _asyncOperation = useSystemUnload ? Resources.UnloadUnusedAssets() : null; } if (_asyncOperation == null && _performGCCollect) diff --git a/Runtime/Resource/Resource/ResourceService.AssetObject.cs b/Runtime/Resource/Resource/ResourceService.AssetObject.cs index 9eeeda0..0d6ef8c 100644 --- a/Runtime/Resource/Resource/ResourceService.AssetObject.cs +++ b/Runtime/Resource/Resource/ResourceService.AssetObject.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Collections.Generic; using AlicizaX.ObjectPool; using AlicizaX; @@ -47,13 +47,10 @@ namespace AlicizaX.Resource.Runtime protected internal override void Release(bool isShutdown) { - if (!isShutdown) + AssetHandle handle = m_AssetHandle; + if (handle is { IsValid: true }) { - AssetHandle handle = m_AssetHandle; - if (handle is { IsValid: true }) - { - handle.Dispose(); - } + handle.Dispose(); } } } diff --git a/Runtime/Resource/Resource/ResourceService.Initialization.cs b/Runtime/Resource/Resource/ResourceService.Initialization.cs index 93d72bc..0746673 100644 --- a/Runtime/Resource/Resource/ResourceService.Initialization.cs +++ b/Runtime/Resource/Resource/ResourceService.Initialization.cs @@ -1,4 +1,5 @@ using System; +using Cysharp.Text; using UnityEngine; using YooAsset; @@ -90,7 +91,7 @@ namespace AlicizaX.Resource.Runtime // 小游戏缓存根目录 // 注意:此处代码根据微信插件配置来填写! WeChatWASM.WXBase.PreloadConcurrent(10); - string packageRoot = $"{WeChatWASM.WX.env.USER_DATA_PATH}/__GAME_FILE_CACHE/yoo"; + string packageRoot = ZString.Concat(WeChatWASM.WX.env.USER_DATA_PATH, "/__GAME_FILE_CACHE/yoo"); webRemoteFileSystemParams = WechatFileSystemCreater.CreateFileSystemParameters(packageRoot, remoteServices, null); #endif diff --git a/Runtime/Resource/Resource/ResourceService.Pool.cs b/Runtime/Resource/Resource/ResourceService.Pool.cs index 9073fa8..5d59a09 100644 --- a/Runtime/Resource/Resource/ResourceService.Pool.cs +++ b/Runtime/Resource/Resource/ResourceService.Pool.cs @@ -1,4 +1,4 @@ -using AlicizaX.ObjectPool; +using AlicizaX.ObjectPool; using AlicizaX; namespace AlicizaX.Resource.Runtime diff --git a/Runtime/Resource/Resource/ResourceService.Services.cs b/Runtime/Resource/Resource/ResourceService.Services.cs index 245eb13..e3d6887 100644 --- a/Runtime/Resource/Resource/ResourceService.Services.cs +++ b/Runtime/Resource/Resource/ResourceService.Services.cs @@ -1,4 +1,5 @@ using System.IO; +using Cysharp.Text; using UnityEngine; using YooAsset; @@ -20,12 +21,12 @@ namespace AlicizaX.Resource.Runtime string IRemoteServices.GetRemoteMainURL(string fileName) { - return $"{_defaultHostServer}/{fileName}"; + return ZString.Concat(_defaultHostServer, "/", fileName); } string IRemoteServices.GetRemoteFallbackURL(string fileName) { - return $"{_fallbackHostServer}/{fileName}"; + return ZString.Concat(_fallbackHostServer, "/", fileName); } } diff --git a/Runtime/Resource/Resource/ResourceService.cs b/Runtime/Resource/Resource/ResourceService.cs index 1efaa98..c4ba259 100644 --- a/Runtime/Resource/Resource/ResourceService.cs +++ b/Runtime/Resource/Resource/ResourceService.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Threading; using AlicizaX.ObjectPool; using AlicizaX; +using Cysharp.Text; using Cysharp.Threading.Tasks; using UnityEngine; using YooAsset; @@ -28,7 +29,7 @@ namespace AlicizaX.Resource.Runtime public string DecryptionServices { get; set; } /// - /// 自动释放资源引用计数为0的资源包 + /// 自动释放资源引用计数。。的资源包 /// public bool AutoUnloadBundleWhenUnused { get; set; } = false; @@ -60,7 +61,6 @@ namespace AlicizaX.Resource.Runtime public int FailedTryAgain { get; set; } - #region internal /// @@ -76,39 +76,89 @@ namespace AlicizaX.Resource.Runtime /// /// 资源信息列表。 /// - private readonly Dictionary _assetInfoMap = new Dictionary(); + private readonly Dictionary _assetInfoMap = new Dictionary(AssetCacheKeyComparer.Instance); /// /// 正在加载的资源任务。 /// - private readonly Dictionary> _assetLoadingOperations = new Dictionary>(); + private readonly Dictionary> _assetLoadingOperations = new Dictionary>(); + + private readonly Dictionary _assetObjectKeyMap = new Dictionary(AssetCacheKeyComparer.Instance); private const float ProgressCallbackThreshold = 0.01f; + private UnloadUnusedAssetsOperation _unloadUnusedAssetsOperation; + + private UnloadAllAssetsOperation _unloadAllAssetsOperation; + + private readonly struct AssetCacheKey + { + public readonly string PackageName; + + public readonly string Location; + + public AssetCacheKey(string packageName, string location) + { + PackageName = packageName ?? string.Empty; + Location = location ?? string.Empty; + } + } + + private sealed class AssetCacheKeyComparer : IEqualityComparer + { + public static readonly AssetCacheKeyComparer Instance = new AssetCacheKeyComparer(); + + private AssetCacheKeyComparer() + { + } + + public bool Equals(AssetCacheKey x, AssetCacheKey y) + { + return string.Equals(x.PackageName, y.PackageName, StringComparison.Ordinal) && + string.Equals(x.Location, y.Location, StringComparison.Ordinal); + } + + public int GetHashCode(AssetCacheKey obj) + { + unchecked + { + int hash = 17; + hash = hash * 31 + StringComparer.Ordinal.GetHashCode(obj.PackageName ?? string.Empty); + hash = hash * 31 + StringComparer.Ordinal.GetHashCode(obj.Location ?? string.Empty); + return hash; + } + } + } + #endregion public void Initialize() { - // 初始化资源系统 + // 初始化资源系。。 YooAssets.Initialize(new ResourceLogger()); + YooAssets.SetOperationSystemMaxTimeSlice(Milliseconds); // 创建默认的资源包 string packageName = DefaultPackageName; + var defaultPackage = YooAssets.TryGetPackage(packageName); + if (defaultPackage == null) + { defaultPackage = YooAssets.CreatePackage(packageName); + YooAssets.SetDefaultPackage(defaultPackage); } DefaultPackage = defaultPackage; + PackageMap[packageName] = defaultPackage; CreateAssetPool(); } - protected override void OnInitialize() { } @@ -124,6 +174,7 @@ namespace AlicizaX.Resource.Runtime _assetPool = null; _assetLoadingOperations.Clear(); _assetInfoMap.Clear(); + _assetObjectKeyMap.Clear(); } public UniTask InitPackageAsync(string packageName = "", string hostServerURL = "", string fallbackHostServerURL = "") @@ -137,7 +188,7 @@ namespace AlicizaX.Resource.Runtime { if (resPackage.InitializeStatus is EOperationStatus.Processing or EOperationStatus.Succeed) { - Log.Error($"ResourceSystem has already init package : {packageName}"); + Log.Error(ZString.Format("ResourceSystem has already init package : {0}", packageName)); return new UniTask(false); } else @@ -150,7 +201,6 @@ namespace AlicizaX.Resource.Runtime GameFrameworkGuard.NotNull(packageName, nameof(packageName)); GameFrameworkGuard.NotNull(hostServerURL, nameof(hostServerURL)); GameFrameworkGuard.NotNull(fallbackHostServerURL, nameof(fallbackHostServerURL)); - // 创建默认的资源包 var resourcePackage = YooAssets.TryGetPackage(packageName); if (resourcePackage == null) @@ -159,7 +209,6 @@ namespace AlicizaX.Resource.Runtime } PackageMap[packageName] = resourcePackage; - var initializationOperationHandler = CreateInitializationOperationHandler(resourcePackage, hostServerURL, fallbackHostServerURL, DecryptionServices); initializationOperationHandler.Completed += asyncOperationBase => { @@ -172,10 +221,10 @@ namespace AlicizaX.Resource.Runtime taskCompletionSource.TrySetException(new Exception(asyncOperationBase.Error)); } }; + return taskCompletionSource.Task; } - /// /// 获取当前资源包版本。 /// @@ -186,6 +235,7 @@ namespace AlicizaX.Resource.Runtime var package = string.IsNullOrEmpty(customPackageName) ? YooAssets.GetPackage(DefaultPackageName) : YooAssets.GetPackage(customPackageName); + if (package == null) { return string.Empty; @@ -204,44 +254,29 @@ namespace AlicizaX.Resource.Runtime public RequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks = false, int timeout = 60, string customPackageName = "") { - var package = string.IsNullOrEmpty(customPackageName) - ? YooAssets.GetPackage(DefaultPackageName) - : YooAssets.GetPackage(customPackageName); + var package = GetPackageOrThrow(customPackageName); return package.RequestPackageVersionAsync(appendTimeTicks, timeout); } - /// - /// 向网络端请求并更新清单 + /// 向网络端请求并更新清。。 /// - /// 更新的包裹版本 + /// 更新的包裹版。。 /// 超时时间(默认值:60秒) /// 指定资源包的名称。不传使用默认资源包 public UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, int timeout = 60, string customPackageName = "") { - var package = string.IsNullOrEmpty(customPackageName) - ? YooAssets.GetPackage(this.DefaultPackageName) - : YooAssets.GetPackage(customPackageName); + var package = GetPackageOrThrow(customPackageName); return package.UpdatePackageManifestAsync(packageVersion, timeout); } - /// /// 创建资源下载器,用于下载当前资源版本所有的资源包文件。 /// /// 指定资源包的名称。不传使用默认资源包 public ResourceDownloaderOperation CreateResourceDownloader(string customPackageName = "") { - ResourcePackage package = null; - if (string.IsNullOrEmpty(customPackageName)) - { - package = YooAssets.GetPackage(this.DefaultPackageName); - } - else - { - package = YooAssets.GetPackage(customPackageName); - } - + ResourcePackage package = GetPackageOrThrow(customPackageName); return package.CreateResourceDownloader(DownloadingMaxNum, FailedTryAgain); } @@ -254,10 +289,8 @@ namespace AlicizaX.Resource.Runtime EFileClearMode clearMode = EFileClearMode.ClearUnusedBundleFiles, string customPackageName = "") { - var package = string.IsNullOrEmpty(customPackageName) - ? YooAssets.GetPackage(DefaultPackageName) - : YooAssets.GetPackage(customPackageName); - return package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles); + var package = GetPackageOrThrow(customPackageName); + return package.ClearCacheFilesAsync(clearMode); } /// @@ -266,9 +299,7 @@ namespace AlicizaX.Resource.Runtime /// 指定资源包的名称。不传使用默认资源包 public void ClearAllBundleFiles(string customPackageName = "") { - var package = string.IsNullOrEmpty(customPackageName) - ? YooAssets.GetPackage(DefaultPackageName) - : YooAssets.GetPackage(customPackageName); + var package = GetPackageOrThrow(customPackageName); package.ClearCacheFilesAsync(EFileClearMode.ClearAllBundleFiles); } @@ -297,11 +328,16 @@ namespace AlicizaX.Resource.Runtime public void UnloadUnusedAssets() { _assetPool.ReleaseAllUnused(); + if (_unloadUnusedAssetsOperation is { IsDone: false }) + { + return; + } + foreach (var package in PackageMap.Values) { if (package is { InitializeStatus: EOperationStatus.Succeed }) { - package.UnloadUnusedAssetsAsync(); + _unloadUnusedAssetsOperation = package.UnloadUnusedAssetsAsync(); } } } @@ -312,15 +348,19 @@ namespace AlicizaX.Resource.Runtime public void ForceUnloadAllAssets() { #if UNITY_WEBGL - Log.Warning($"WebGL not support invoke {nameof(ForceUnloadAllAssets)}"); + Log.Warning(ZString.Format("WebGL not support invoke {0}", nameof(ForceUnloadAllAssets))); return; #else + if (_unloadAllAssetsOperation is { IsDone: false }) + { + return; + } foreach (var package in PackageMap.Values) { if (package is { InitializeStatus: EOperationStatus.Succeed }) { - package.UnloadAllAssetsAsync(); + _unloadAllAssetsOperation = package.UnloadAllAssetsAsync(); } } #endif @@ -331,6 +371,19 @@ namespace AlicizaX.Resource.Runtime _forceUnloadUnusedAssetsAction?.Invoke(performGCCollect); } + private ResourcePackage GetPackageOrThrow(string packageName) + { + ResourcePackage package = string.IsNullOrEmpty(packageName) + ? YooAssets.GetPackage(DefaultPackageName) + : YooAssets.GetPackage(packageName); + if (package == null) + { + throw new GameFrameworkException(ZString.Format("The package does not exist. Package Name :{0}", string.IsNullOrEmpty(packageName) ? DefaultPackageName : packageName)); + } + + return package; + } + #region Public Methods #region 获取资源信息 @@ -346,11 +399,9 @@ namespace AlicizaX.Resource.Runtime { return YooAssets.IsNeedDownloadFromRemote(location); } - else - { - var package = YooAssets.GetPackage(packageName); - return package.IsNeedDownloadFromRemote(location); - } + + var package = YooAssets.GetPackage(packageName); + return package.IsNeedDownloadFromRemote(location); } /// @@ -364,11 +415,9 @@ namespace AlicizaX.Resource.Runtime { return YooAssets.IsNeedDownloadFromRemote(assetInfo); } - else - { - var package = YooAssets.GetPackage(packageName); - return package.IsNeedDownloadFromRemote(assetInfo); - } + + var package = YooAssets.GetPackage(packageName); + return package.IsNeedDownloadFromRemote(assetInfo); } /// @@ -383,11 +432,9 @@ namespace AlicizaX.Resource.Runtime { return YooAssets.GetAssetInfos(tag); } - else - { - var package = YooAssets.GetPackage(packageName); - return package.GetAssetInfos(tag); - } + + var package = YooAssets.GetPackage(packageName); + return package.GetAssetInfos(tag); } /// @@ -402,11 +449,9 @@ namespace AlicizaX.Resource.Runtime { return YooAssets.GetAssetInfos(tags); } - else - { - var package = YooAssets.GetPackage(packageName); - return package.GetAssetInfos(tags); - } + + var package = YooAssets.GetPackage(packageName); + return package.GetAssetInfos(tags); } /// @@ -424,18 +469,19 @@ namespace AlicizaX.Resource.Runtime if (string.IsNullOrEmpty(packageName)) { - if (_assetInfoMap.TryGetValue(location, out AssetInfo assetInfo)) + AssetCacheKey key = new AssetCacheKey(DefaultPackageName, location); + if (_assetInfoMap.TryGetValue(key, out AssetInfo assetInfo)) { return assetInfo; } assetInfo = YooAssets.GetAssetInfo(location); - _assetInfoMap[location] = assetInfo; + _assetInfoMap[key] = assetInfo; return assetInfo; } else { - string key = $"{packageName}/{location}"; + AssetCacheKey key = new AssetCacheKey(packageName, location); if (_assetInfoMap.TryGetValue(key, out AssetInfo assetInfo)) { return assetInfo; @@ -444,7 +490,7 @@ namespace AlicizaX.Resource.Runtime var package = YooAssets.GetPackage(packageName); if (package == null) { - throw new GameFrameworkException($"The package does not exist. Package Name :{packageName}"); + throw new GameFrameworkException(ZString.Format("The package does not exist. Package Name :{0}", packageName)); } assetInfo = package.GetAssetInfo(location); @@ -467,10 +513,9 @@ namespace AlicizaX.Resource.Runtime } AssetInfo assetInfo = GetAssetInfo(location, packageName); - if (!CheckLocationValid(location, packageName)) { - return HasAssetResult.Valid; + return HasAssetResult.NotExist; } if (assetInfo == null) @@ -499,7 +544,7 @@ namespace AlicizaX.Resource.Runtime } var package = YooAssets.GetPackage(packageName); - return package.CheckLocationValid(location); + return package != null && package.CheckLocationValid(location); } #endregion @@ -546,6 +591,7 @@ namespace AlicizaX.Resource.Runtime private AssetHandle GetHandleAsync(string location, Type assetType, string packageName = "", uint priority = 0) { if (string.IsNullOrEmpty(packageName)) + { return YooAssets.LoadAssetAsync(location, assetType, priority); } @@ -574,7 +620,16 @@ namespace AlicizaX.Resource.Runtime return location; } - return $"{packageName}/{location}"; + AssetCacheKey key = new AssetCacheKey(packageName, location); + + if (_assetObjectKeyMap.TryGetValue(key, out string cacheKey)) + { + return cacheKey; + } + + cacheKey = ZString.Concat(packageName, "/", location); + _assetObjectKeyMap[key] = cacheKey; + return cacheKey; } public T LoadAsset(string location, string packageName = "") where T : UnityEngine.Object @@ -586,21 +641,20 @@ namespace AlicizaX.Resource.Runtime if (!CheckLocationValid(location, packageName)) { - Log.Error($"Could not found location [{location}]."); + Log.Error(ZString.Format("Could not found location [{0}].", location)); return null; } string assetObjectKey = GetCacheKey(location, packageName); AssetObject assetObject = _assetPool.Spawn(assetObjectKey); + if (assetObject != null) { return assetObject.Target as T; } AssetHandle handle = GetHandleSync(location, packageName: packageName); - T ret = handle.AssetObject as T; - assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle); _assetPool.Register(assetObject, true); @@ -616,21 +670,20 @@ namespace AlicizaX.Resource.Runtime if (!CheckLocationValid(location, packageName)) { - Log.Error($"Could not found location [{location}]."); + Log.Error(ZString.Format("Could not found location [{0}].", location)); return null; } string assetObjectKey = GetCacheKey(location, packageName); AssetObject assetObject = _assetPool.Spawn(assetObjectKey); + if (assetObject != null) { return AssetsReference.Instantiate(assetObject.Target as GameObject, parent, this).gameObject; } AssetHandle handle = GetHandleSync(location, packageName: packageName); - GameObject gameObject = AssetsReference.Instantiate(handle.AssetObject as GameObject, parent, this).gameObject; - assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle); _assetPool.Register(assetObject, true); @@ -654,22 +707,14 @@ namespace AlicizaX.Resource.Runtime if (!CheckLocationValid(location, packageName)) { - Log.Error($"Could not found location [{location}]."); + Log.Error(ZString.Format("Could not found location [{0}].", location)); callback?.Invoke(null); return; } string assetObjectKey = GetCacheKey(location, packageName); - try - { - var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey); - callback?.Invoke(asset as T); - } - catch (Exception ex) - { - Log.Error($"Can not load asset '{location}'. {ex}"); - callback?.Invoke(null); - } + var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey); + callback?.Invoke(asset as T); } public async UniTask LoadAssetAsync(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object @@ -681,13 +726,11 @@ namespace AlicizaX.Resource.Runtime if (!CheckLocationValid(location, packageName)) { - Log.Error($"Could not found location [{location}]."); + Log.Error(ZString.Format("Could not found location [{0}].", location)); return null; } - string assetObjectKey = GetCacheKey(location, packageName); - var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey, cancellationToken: cancellationToken); return asset as T; } @@ -701,12 +744,11 @@ namespace AlicizaX.Resource.Runtime if (!CheckLocationValid(location, packageName)) { - Log.Error($"Could not found location [{location}]."); + Log.Error(ZString.Format("Could not found location [{0}].", location)); return null; } string assetObjectKey = GetCacheKey(location, packageName); - var asset = await GetOrLoadAssetAsync(location, typeof(GameObject), packageName, assetObjectKey, cancellationToken: cancellationToken); return asset != null ? AssetsReference.Instantiate(asset as GameObject, parent, this).gameObject : null; } @@ -736,7 +778,7 @@ namespace AlicizaX.Resource.Runtime if (!CheckLocationValid(location, packageName)) { - string errorMessage = Utility.Text.Format("Could not found location [{0}].", location); + string errorMessage = ZString.Format("Could not found location [{0}].", location); Log.Error(errorMessage); if (loadAssetCallbacks.LoadAssetFailureCallback != null) { @@ -747,14 +789,11 @@ namespace AlicizaX.Resource.Runtime } string assetObjectKey = GetCacheKey(location, packageName); - float duration = Time.time; - AssetInfo assetInfo = GetAssetInfo(location, packageName); - if (!string.IsNullOrEmpty(assetInfo.Error)) { - string errorMessage = Utility.Text.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error); + string errorMessage = ZString.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error); if (loadAssetCallbacks.LoadAssetFailureCallback != null) { loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotExist, errorMessage, userData); @@ -764,24 +803,17 @@ namespace AlicizaX.Resource.Runtime throw new GameFrameworkException(errorMessage); } - try - { - 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); - } - catch (Exception ex) - { - 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; - } + var asset = await GetOrLoadAssetAsync(location, assetType, packageName, assetObjectKey, NormalizePriority(priority), default, + loadAssetCallbacks.LoadAssetUpdateCallback, userData); - throw; + if (asset == null) + { + string errorMessage = ZString.Format("Can not load asset '{0}'.", location); + loadAssetCallbacks.LoadAssetFailureCallback?.Invoke(location, LoadResourceStatus.NotReady, errorMessage, userData); + return; } + + loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData); } /// @@ -806,7 +838,7 @@ namespace AlicizaX.Resource.Runtime if (!CheckLocationValid(location, packageName)) { - string errorMessage = Utility.Text.Format("Could not found location [{0}].", location); + string errorMessage = ZString.Format("Could not found location [{0}].", location); Log.Error(errorMessage); if (loadAssetCallbacks.LoadAssetFailureCallback != null) { @@ -817,14 +849,11 @@ namespace AlicizaX.Resource.Runtime } string assetObjectKey = GetCacheKey(location, packageName); - float duration = Time.time; - AssetInfo assetInfo = GetAssetInfo(location, packageName); - if (!string.IsNullOrEmpty(assetInfo.Error)) { - string errorMessage = Utility.Text.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error); + string errorMessage = ZString.Format("Can not load asset '{0}' because :'{1}'.", location, assetInfo.Error); if (loadAssetCallbacks.LoadAssetFailureCallback != null) { loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotExist, errorMessage, userData); @@ -834,24 +863,17 @@ namespace AlicizaX.Resource.Runtime throw new GameFrameworkException(errorMessage); } - try - { - 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); - } - catch (Exception ex) - { - 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; - } + var asset = await GetOrLoadAssetAsync(location, assetInfo.AssetType, packageName, assetObjectKey, NormalizePriority(priority), default, + loadAssetCallbacks.LoadAssetUpdateCallback, userData); - throw; + if (asset == null) + { + string errorMessage = ZString.Format("Can not load asset '{0}'.", location); + loadAssetCallbacks.LoadAssetFailureCallback?.Invoke(location, LoadResourceStatus.NotReady, errorMessage, userData); + return; } + + loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData); } private async UniTaskVoid InvokeProgress(string location, AssetHandle assetHandle, LoadAssetUpdateCallback loadAssetUpdateCallback, object userData) @@ -921,12 +943,11 @@ namespace AlicizaX.Resource.Runtime #endregion private async UniTask GetOrLoadAssetAsync(string location, Type assetType, string packageName, - string assetObjectKey, uint priority = 0, CancellationToken cancellationToken = default, Action onHandleCreated = null) + string assetObjectKey, uint priority = 0, CancellationToken cancellationToken = default, LoadAssetUpdateCallback loadAssetUpdateCallback = null, object userData = null) { while (true) { cancellationToken.ThrowIfCancellationRequested(); - AssetObject cachedAssetObject = _assetPool.Spawn(assetObjectKey); if (cachedAssetObject != null) { @@ -939,40 +960,42 @@ namespace AlicizaX.Resource.Runtime continue; } - AssetHandle handle = null; - try + AssetHandle handle = GetHandleAsync(location, assetType, packageName: packageName, priority: priority); + StartProgressTask(location, handle, loadAssetUpdateCallback, userData); + while (handle is { IsValid: true, IsDone: false }) { - handle = GetHandleAsync(location, assetType, packageName: packageName, priority: priority); - onHandleCreated?.Invoke(handle); - await handle.ToUniTask(); - - if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed) + if (cancellationToken.IsCancellationRequested) { - throw new GameFrameworkException(Utility.Text.Format("Can not load asset '{0}'.", location)); + DisposeHandle(handle); + FailLoading(assetObjectKey, null); + return null; } - var assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle); - _assetPool.Register(assetObject, true); - CompleteLoading(assetObjectKey); - return handle.AssetObject as UnityEngine.Object; + await UniTask.Yield(); } - catch (Exception ex) + + if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed) { DisposeHandle(handle); - FailLoading(assetObjectKey, ex); - throw; + FailLoading(assetObjectKey, null); + return null; } + + var assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle); + _assetPool.Register(assetObject, true); + CompleteLoading(assetObjectKey); + return handle.AssetObject as UnityEngine.Object; } } private bool TryBeginLoading(string assetObjectKey) { - if (_assetLoadingOperations.ContainsKey(assetObjectKey)) + if (_assetLoadingOperations.TryGetValue(assetObjectKey, out _)) { return false; } - _assetLoadingOperations.Add(assetObjectKey, new UniTaskCompletionSource()); + _assetLoadingOperations.Add(assetObjectKey, AutoResetUniTaskCompletionSource.Create()); return true; } @@ -1007,12 +1030,13 @@ namespace AlicizaX.Resource.Runtime private void FailLoading(string assetObjectKey, Exception exception) { if (!_assetLoadingOperations.TryGetValue(assetObjectKey, out var loadingOperation)) + { return; } _assetLoadingOperations.Remove(assetObjectKey); - loadingOperation.TrySetException(exception); + loadingOperation.TrySetResult(false); } private void DisposeHandle(AssetHandle handle) @@ -1038,7 +1062,7 @@ namespace AlicizaX.Resource.Runtime /// /// 设置下载系统参数,自定义下载请求。 /// - /// 自定义下载器的请求委托。 + /// 自定义下载器的请求委托, public void SetDownloadSystemUnityWebRequest(UnityWebRequestDelegate downloadSystemUnityWebRequest) { YooAssets.SetDownloadSystemUnityWebRequest(downloadSystemUnityWebRequest); @@ -1054,9 +1078,9 @@ namespace AlicizaX.Resource.Runtime private string GetAuthorization(string userName, string password) { - string auth = $"{userName}:{password}"; + string auth = ZString.Concat(userName, ":", password); var bytes = System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(auth); - return $"Basic {Convert.ToBase64String(bytes)}"; + return ZString.Concat("Basic ", Convert.ToBase64String(bytes)); } #endregion