[Opt] MemoryPool & ResourceService

This commit is contained in:
陈思海 2026-04-28 11:31:58 +08:00
parent d6f36cc9df
commit 4cf5eb57d2
24 changed files with 853 additions and 463 deletions

View File

@ -96,6 +96,7 @@ namespace AlicizaX
RunCase("ClearAll Unschedule", RunClearAllUnschedule); RunCase("ClearAll Unschedule", RunClearAllUnschedule);
RunCase("ClearAll Active Queue Reset", RunClearAllActiveQueueReset); RunCase("ClearAll Active Queue Reset", RunClearAllActiveQueueReset);
RunCase("Type API Cold Path", RunTypeApiColdPath); RunCase("Type API Cold Path", RunTypeApiColdPath);
RunCase("Cached Handle Hot Path", RunCachedHandleHotPath);
RunCase("Info Buffer No Alloc", RunInfoBufferNoAlloc); RunCase("Info Buffer No Alloc", RunInfoBufferNoAlloc);
RunCase("Explicit Compact", RunExplicitCompact); RunCase("Explicit Compact", RunExplicitCompact);
} }
@ -628,6 +629,28 @@ namespace AlicizaX
} }
} }
private void RunCachedHandleHotPath()
{
using (s_ExtremeMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.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<BenchmarkMemory>.ClearAll();
}
}
private void RunInfoBufferNoAlloc() private void RunInfoBufferNoAlloc()
{ {
using (s_InfoMarker.Auto()) using (s_InfoMarker.Auto())

View File

@ -53,6 +53,19 @@ namespace AlicizaX
return MemoryPool<T>.Acquire(); return MemoryPool<T>.Acquire();
} }
/// <summary>
/// 获取动态内存类型的缓存句柄。运行时热路径应提前缓存该句柄,避免反复使用 Type 查找。
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryPoolHandle GetHandle(Type memoryType)
{
return MemoryPoolRegistry.GetHandle(memoryType);
}
/// <summary>
/// 慢速动态路径。禁止在运行时热路径调用;请提前通过 GetHandle(Type) 缓存 MemoryPoolHandle 后再获取对象。
/// </summary>
[Obsolete("慢速动态路径,禁止在运行时热路径使用。请缓存 MemoryPoolHandle或改用 MemoryPool<T>.Acquire / MemoryPool.Acquire<T>。", false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IMemory Acquire(Type memoryType) public static IMemory Acquire(Type memoryType)
{ {
@ -66,6 +79,10 @@ namespace AlicizaX
MemoryPool<T>.Release(memory); MemoryPool<T>.Release(memory);
} }
/// <summary>
/// 慢速动态路径。禁止在运行时热路径调用;请通过缓存的 MemoryPoolHandle 或 Release&lt;T&gt; 回收对象。
/// </summary>
[Obsolete("慢速动态路径,禁止在运行时热路径使用。请通过缓存的 MemoryPoolHandle或改用 MemoryPool<T>.Release / MemoryPool.Release<T>。", false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Release(IMemory memory) public static void Release(IMemory memory)
{ {

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b7195e879c74d7c9d013fa76df5e37c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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) public static IMemory Acquire(Type type)
{ {
if (type == null) if (type == null)

View File

@ -1,4 +1,4 @@
using AlicizaX; using AlicizaX;
namespace AlicizaX.Resource.Runtime namespace AlicizaX.Resource.Runtime
{ {

View File

@ -1,4 +1,4 @@
using AlicizaX.ObjectPool; using AlicizaX.ObjectPool;
using AlicizaX; using AlicizaX;
namespace AlicizaX.Resource.Runtime namespace AlicizaX.Resource.Runtime

View File

@ -1,4 +1,4 @@

using AlicizaX; using AlicizaX;
namespace AlicizaX.Resource.Runtime namespace AlicizaX.Resource.Runtime

View File

@ -1,21 +1,31 @@
using System; using System;
using UnityEngine; using AlicizaX;
namespace AlicizaX.Resource.Runtime namespace AlicizaX.Resource.Runtime
{ {
[Serializable] [Serializable]
public class LoadAssetObject public class LoadAssetObject : IMemory
{ {
public ISetAssetObject AssetObject { get; } public ISetAssetObject AssetObject { get; private set; }
public UnityEngine.Object AssetTarget { get; } public UnityEngine.Object AssetTarget { get; private set; }
#if UNITY_EDITOR #if UNITY_EDITOR
public bool IsSelect { get; set; } public bool IsSelect { get; set; }
#endif #endif
public LoadAssetObject(ISetAssetObject obj, UnityEngine.Object assetTarget) public static LoadAssetObject Create(ISetAssetObject obj, UnityEngine.Object assetTarget)
{ {
AssetObject = obj; LoadAssetObject item = MemoryPool.Acquire<LoadAssetObject>();
AssetTarget = assetTarget; item.AssetObject = obj;
item.AssetTarget = assetTarget;
return item;
}
public void Clear()
{
AssetObject = null;
AssetTarget = null;
#if UNITY_EDITOR
IsSelect = false;
#endif
} }
} }
} }

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b25c6b1f257bf3445b8e2651e169e314 guid: b25c6b1f257bf3445b8e2651e169e314
timeCreated: 1710733596 MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,6 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Text;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
namespace AlicizaX.Resource.Runtime namespace AlicizaX.Resource.Runtime
@ -51,7 +51,7 @@ namespace AlicizaX.Resource.Runtime
UnityEngine.Object assetObject = asset as UnityEngine.Object; UnityEngine.Object assetObject = asset as UnityEngine.Object;
if (assetObject == null) if (assetObject == null)
{ {
Log.Error($"Load failure asset type is {asset?.GetType()}."); Log.Error(ZString.Format("Load failure asset type is {0}.", asset?.GetType()));
return; return;
} }
@ -78,22 +78,22 @@ namespace AlicizaX.Resource.Runtime
CancelAndCleanupOldRequest(target); CancelAndCleanupOldRequest(target);
var linkedTokenSource = new CancellationTokenSource();
var loadingState = MemoryPool.Acquire<LoadingState>(); var loadingState = MemoryPool.Acquire<LoadingState>();
loadingState.Cts = linkedTokenSource; CancellationToken loadToken = cancellationToken;
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
var linkedTokenSource = new CancellationTokenSource();
loadingState.Cts = linkedTokenSource;
loadingState.Registration = cancellationToken.Register(static state => loadingState.Registration = cancellationToken.Register(static state =>
{ {
((CancellationTokenSource)state).Cancel(); ((CancellationTokenSource)state).Cancel();
}, linkedTokenSource); }, linkedTokenSource);
loadToken = linkedTokenSource.Token;
} }
loadingState.Location = location; loadingState.Location = location;
_loadingStates[target] = loadingState; _loadingStates[target] = loadingState;
try
{
if (!IsCurrentLocation(target, location)) if (!IsCurrentLocation(target, location))
{ {
return; return;
@ -113,26 +113,15 @@ namespace AlicizaX.Resource.Runtime
return; return;
} }
T resource = await _resourceService.LoadAssetAsync<T>(location, linkedTokenSource.Token); var loadResult = await _resourceService.LoadAssetAsync<T>(location, loadToken).SuppressCancellationThrow();
if (resource == null) if (loadResult.IsCanceled || loadResult.Result == null)
{ {
ClearLoadingState(target); ClearLoadingState(target);
return; return;
} }
OnLoadAssetSuccess(location, resource, 0f, setAssetObject); OnLoadAssetSuccess(location, loadResult.Result, 0f, setAssetObject);
} }
catch (OperationCanceledException)
{
ClearLoadingState(target);
}
catch (Exception ex)
{
Log.Error($"Failed to load asset '{location}': {ex}");
ClearLoadingState(target);
}
}
private void CancelAndCleanupOldRequest(UnityEngine.Object target) private void CancelAndCleanupOldRequest(UnityEngine.Object target)
{ {
if (_loadingStates.TryGetValue(target, out var oldState)) if (_loadingStates.TryGetValue(target, out var oldState))
@ -179,9 +168,10 @@ namespace AlicizaX.Resource.Runtime
UnityEngine.Application.lowMemory -= OnLowMemory; UnityEngine.Application.lowMemory -= OnLowMemory;
ReleaseTrackedAssets(); ReleaseTrackedAssets();
foreach (var state in _loadingStates.Values) var enumerator = _loadingStates.GetEnumerator();
while (enumerator.MoveNext())
{ {
MemoryPool.Release(state); MemoryPool.Release(enumerator.Current.Value);
} }
_loadingStates.Clear(); _loadingStates.Clear();

View File

@ -1,6 +1,6 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Text;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
@ -15,15 +15,60 @@ namespace AlicizaX.Resource.Runtime
{ {
private readonly Dictionary<string, SubAssetsHandle> _subAssetsHandles = new Dictionary<string, SubAssetsHandle>(); private readonly Dictionary<string, SubAssetsHandle> _subAssetsHandles = new Dictionary<string, SubAssetsHandle>();
private readonly Dictionary<string, int> _subSpriteReferences = new Dictionary<string, int>(); private readonly Dictionary<string, int> _subSpriteReferences = new Dictionary<string, int>();
private readonly Dictionary<string, UniTaskCompletionSource<SubAssetsHandle>> _subAssetLoadingOperations = new Dictionary<string, UniTaskCompletionSource<SubAssetsHandle>>(); private readonly Dictionary<string, AutoResetUniTaskCompletionSource<SubAssetsHandle>> _subAssetLoadingOperations = new Dictionary<string, AutoResetUniTaskCompletionSource<SubAssetsHandle>>();
private readonly Dictionary<SubSpriteKey, Sprite> _subSpriteCache = new Dictionary<SubSpriteKey, Sprite>(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<SubSpriteKey>
{
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) 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) 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); ReleaseSubAssetsIfUnused(location);
return; return;
} }
@ -39,11 +84,17 @@ namespace AlicizaX.Resource.Runtime
public async UniTask SetSubSprite(SpriteRenderer spriteRenderer, string location, string spriteName, CancellationToken cancellationToken = default) public async UniTask SetSubSprite(SpriteRenderer spriteRenderer, string location, string spriteName, CancellationToken cancellationToken = default)
{ {
var subSprite = await GetSubSpriteImp(location, spriteName, cancellationToken);
if (spriteRenderer == null) 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); ReleaseSubAssetsIfUnused(location);
return; return;
} }
@ -57,17 +108,28 @@ namespace AlicizaX.Resource.Runtime
var assetInfo = YooAssets.GetAssetInfo(location); var assetInfo = YooAssets.GetAssetInfo(location);
if (assetInfo.IsInvalid) 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); var subAssetsHandle = await GetOrLoadSubAssetsHandleAsync(location, cancellationToken);
if (!subAssetsHandle.IsValid)
{
return null;
}
var subSprite = subAssetsHandle.GetSubAssetObject<Sprite>(spriteName); var subSprite = subAssetsHandle.GetSubAssetObject<Sprite>(spriteName);
if (subSprite == null) 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; return subSprite;
} }
@ -104,6 +166,7 @@ namespace AlicizaX.Resource.Runtime
{ {
subAssetsHandle.Dispose(); subAssetsHandle.Dispose();
_subAssetsHandles.Remove(location); _subAssetsHandles.Remove(location);
ClearSubSpriteCache(location);
} }
} }
@ -130,29 +193,57 @@ namespace AlicizaX.Resource.Runtime
continue; continue;
} }
var completionSource = new UniTaskCompletionSource<SubAssetsHandle>(); var completionSource = AutoResetUniTaskCompletionSource<SubAssetsHandle>.Create();
_subAssetLoadingOperations.Add(location, completionSource); _subAssetLoadingOperations.Add(location, completionSource);
SubAssetsHandle subAssetsHandle = default; SubAssetsHandle subAssetsHandle = YooAssets.LoadSubAssetsAsync<Sprite>(location);
try while (subAssetsHandle is { IsValid: true, IsDone: false })
{ {
subAssetsHandle = YooAssets.LoadSubAssetsAsync<Sprite>(location); if (cancellationToken.IsCancellationRequested)
await subAssetsHandle.ToUniTask(); {
DisposeSubAssetsHandle(subAssetsHandle);
CompleteSubAssetLoading(location, completionSource, default);
return default;
}
await UniTask.Yield();
}
if (!subAssetsHandle.IsValid || subAssetsHandle.Status == EOperationStatus.Failed)
{
DisposeSubAssetsHandle(subAssetsHandle);
CompleteSubAssetLoading(location, completionSource, default);
return default;
}
_subAssetsHandles[location] = subAssetsHandle; _subAssetsHandles[location] = subAssetsHandle;
completionSource.TrySetResult(subAssetsHandle); CompleteSubAssetLoading(location, completionSource, subAssetsHandle);
return subAssetsHandle; return subAssetsHandle;
} }
catch (Exception ex)
{
subAssetsHandle?.Dispose();
completionSource.TrySetException(ex);
throw;
} }
finally
private void CompleteSubAssetLoading(string location, AutoResetUniTaskCompletionSource<SubAssetsHandle> completionSource, SubAssetsHandle subAssetsHandle)
{ {
_subAssetLoadingOperations.Remove(location); _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) private void ReleaseSubAssetsIfUnused(string location)
@ -171,6 +262,7 @@ namespace AlicizaX.Resource.Runtime
{ {
subAssetsHandle.Dispose(); subAssetsHandle.Dispose();
_subAssetsHandles.Remove(location); _subAssetsHandles.Remove(location);
ClearSubSpriteCache(location);
} }
} }
} }

View File

@ -1,10 +1,8 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using AlicizaX.ObjectPool; using AlicizaX.ObjectPool;
using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace AlicizaX.Resource.Runtime namespace AlicizaX.Resource.Runtime
@ -17,54 +15,27 @@ namespace AlicizaX.Resource.Runtime
{ {
public static ResourceExtComponent Instance { private set; get; } public static ResourceExtComponent Instance { private set; get; }
/// <summary>
/// 检查是否可以释放间隔。
/// </summary>
[SerializeField] [SerializeField]
private float checkCanReleaseInterval = 30f; private float checkCanReleaseInterval = 30f;
/// <summary>
/// 对象池自动释放时间间隔。
/// </summary>
[SerializeField] [SerializeField]
private float autoReleaseInterval = 60f; private float autoReleaseInterval = 60f;
/// <summary>
/// 每帧最大处理资源数量,用于分帧处理避免卡顿。
/// </summary>
[SerializeField] [SerializeField]
private int maxProcessPerFrame = 50; private int maxProcessPerFrame = 50;
[SerializeField] [SerializeField]
private int releaseCheckThreshold = 16; private int releaseCheckThreshold = 16;
/// <summary> private LoadAssetObject[] _loadAssetObjects;
/// 当前正在处理的节点,用于分帧处理。 private int _loadAssetObjectCount;
/// </summary> private int _currentProcessIndex;
private LinkedListNode<LoadAssetObject> _currentProcessNode; private Dictionary<Object, int> _trackedAssetIndices;
/// <summary>
/// 保存加载的图片对象。
/// </summary>
private LinkedList<LoadAssetObject> _loadAssetObjectsLinkedList;
private Dictionary<Object, LinkedListNode<LoadAssetObject>> _trackedAssetNodes;
private bool _releaseRequested; private bool _releaseRequested;
private float _nextReleaseCheckTime = float.MaxValue; private float _nextReleaseCheckTime = float.MaxValue;
/// <summary>
/// 散图集合对象池。
/// </summary>
private IObjectPool<AssetItemObject> _assetItemPool; private IObjectPool<AssetItemObject> _assetItemPool;
#if UNITY_EDITOR
public LinkedList<LoadAssetObject> LoadAssetObjectsLinkedList
{
get => _loadAssetObjectsLinkedList;
set => _loadAssetObjectsLinkedList = value;
}
#endif
private IEnumerator Start() private IEnumerator Start()
{ {
Instance = this; Instance = this;
@ -80,8 +51,8 @@ namespace AlicizaX.Resource.Runtime
capacity: 16, capacity: 16,
expireTime: 60, expireTime: 60,
priority: 0)); priority: 0));
_loadAssetObjectsLinkedList = new LinkedList<LoadAssetObject>(); _loadAssetObjects = new LoadAssetObject[16];
_trackedAssetNodes = new Dictionary<Object, LinkedListNode<LoadAssetObject>>(16); _trackedAssetIndices = new Dictionary<Object, int>(16);
InitializedResources(); InitializedResources();
} }
@ -102,50 +73,36 @@ namespace AlicizaX.Resource.Runtime
ReleaseUnused(); ReleaseUnused();
} }
/// <summary>
/// 回收无引用的缓存资产。
/// 使用分帧处理优化性能,避免一次性处理大量资源导致卡顿。
/// </summary>
public void ReleaseUnused() public void ReleaseUnused()
{ {
if (_loadAssetObjectsLinkedList == null || _loadAssetObjectsLinkedList.Count == 0) if (_loadAssetObjectCount == 0)
{ {
_currentProcessNode = null; _currentProcessIndex = 0;
_releaseRequested = false; _releaseRequested = false;
_nextReleaseCheckTime = float.MaxValue; _nextReleaseCheckTime = float.MaxValue;
enabled = false; enabled = false;
return; return;
} }
// 如果当前没有正在处理的节点,从头开始
if (_currentProcessNode == null)
{
_currentProcessNode = _loadAssetObjectsLinkedList.First;
}
int processedCount = 0; int processedCount = 0;
LinkedListNode<LoadAssetObject> current = _currentProcessNode; while (_currentProcessIndex < _loadAssetObjectCount && processedCount < maxProcessPerFrame)
// 分帧处理:每帧最多处理 maxProcessPerFrame 个资源
while (current != null && processedCount < maxProcessPerFrame)
{ {
var next = current.Next; LoadAssetObject item = _loadAssetObjects[_currentProcessIndex];
if (item.AssetObject.IsCanRelease())
if (current.Value.AssetObject.IsCanRelease())
{ {
RemoveTrackedNode(current, releaseAsset: true); RemoveTrackedAt(_currentProcessIndex, releaseAsset: true);
}
else
{
_currentProcessIndex++;
} }
current = next;
processedCount++; processedCount++;
} }
// 更新当前处理节点 if (_currentProcessIndex >= _loadAssetObjectCount)
_currentProcessNode = current;
// 如果已经处理完所有节点,重置状态
if (_currentProcessNode == null)
{ {
_currentProcessIndex = 0;
_releaseRequested = false; _releaseRequested = false;
_nextReleaseCheckTime = float.MaxValue; _nextReleaseCheckTime = float.MaxValue;
enabled = false; enabled = false;
@ -159,15 +116,17 @@ namespace AlicizaX.Resource.Runtime
private void SetAsset(ISetAssetObject setAssetObject, Object assetObject) private void SetAsset(ISetAssetObject setAssetObject, Object assetObject)
{ {
ReplaceTrackedAsset(setAssetObject.TargetObject); 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) if (setAssetObject.TargetObject != null)
{ {
_trackedAssetNodes[setAssetObject.TargetObject] = node; _trackedAssetIndices[setAssetObject.TargetObject] = index;
} }
setAssetObject.SetAsset(assetObject); setAssetObject.SetAsset(assetObject);
if (_loadAssetObjectsLinkedList.Count >= releaseCheckThreshold) if (_loadAssetObjectCount >= releaseCheckThreshold)
{ {
ScheduleReleaseSweep(); ScheduleReleaseSweep();
} }
@ -175,73 +134,105 @@ namespace AlicizaX.Resource.Runtime
private void ReplaceTrackedAsset(Object target) private void ReplaceTrackedAsset(Object target)
{ {
if (target == null || _trackedAssetNodes == null) if (target == null || _trackedAssetIndices == null)
{ {
return; return;
} }
if (_trackedAssetNodes.TryGetValue(target, out var existingNode)) if (_trackedAssetIndices.TryGetValue(target, out int existingIndex))
{ {
if (_currentProcessNode == existingNode) RemoveTrackedAt(existingIndex, releaseAsset: true);
{
_currentProcessNode = existingNode.Next;
}
RemoveTrackedNode(existingNode, releaseAsset: true);
} }
} }
private void RemoveTrackedNode(LinkedListNode<LoadAssetObject> node, bool releaseAsset) private void RemoveTrackedAt(int index, bool releaseAsset)
{ {
if (node == null) if (index < 0 || index >= _loadAssetObjectCount)
{ {
return; return;
} }
var trackedObject = node.Value.AssetObject?.TargetObject; LoadAssetObject item = _loadAssetObjects[index];
if (trackedObject != null && _trackedAssetNodes != null) 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); _resourceService?.UnloadAsset(item.AssetTarget);
_assetItemPool?.Unspawn(node.Value.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); MemoryPool.Release(item);
if (_loadAssetObjectsLinkedList != null && _loadAssetObjectsLinkedList.Count > 0)
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); ScheduleReleaseSweep(checkCanReleaseInterval);
} }
} }
private static void ReleaseSetAssetObject(ISetAssetObject assetObject)
{
if (assetObject is SetSpriteObject setSpriteObject)
{
MemoryPool.Release(setSpriteObject);
}
}
private void ReleaseTrackedAssets() 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; return;
} }
var current = _loadAssetObjectsLinkedList.First; int newCapacity = _loadAssetObjects.Length << 1;
while (current != null) while (newCapacity < capacity)
{ {
var next = current.Next; newCapacity <<= 1;
RemoveTrackedNode(current, releaseAsset: true);
current = next;
} }
_currentProcessNode = null; Array.Resize(ref _loadAssetObjects, newCapacity);
_trackedAssetNodes?.Clear();
_releaseRequested = false;
_nextReleaseCheckTime = float.MaxValue;
enabled = false;
} }
private void ScheduleReleaseSweep(float delay = 0f) private void ScheduleReleaseSweep(float delay = 0f)

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
@ -26,10 +25,12 @@ namespace AlicizaX.Resource.Runtime
private GameObject sourceGameObject; private GameObject sourceGameObject;
[SerializeField] [SerializeField]
private List<AssetsRefInfo> refAssetInfoList; private AssetsRefInfo[] refAssetInfoArray;
[SerializeField]
private int refAssetInfoCount;
private static IResourceService _resourceService; private static IResourceService _resourceService;
private HashSet<int> _refAssetIds;
private bool TryEnsureResourceModule() 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; AssetsRefInfo refInfo = refAssetInfoArray[i];
int refInstanceId = refInfo.instanceId != 0 ? refInfo.instanceId : refInfo.refAsset != null ? refInfo.refAsset.GetInstanceID() : 0;
if (refInstanceId == instanceId)
{
return true;
}
} }
_refAssetIds = new HashSet<int>(); return false;
if (refAssetInfoList == null)
{
return;
}
foreach (var refInfo in refAssetInfoList)
{
if (refInfo.refAsset != null)
{
_refAssetIds.Add(refInfo.instanceId != 0 ? refInfo.instanceId : refInfo.refAsset.GetInstanceID());
}
}
} }
private void OnDestroy() private void OnDestroy()
@ -78,40 +72,60 @@ namespace AlicizaX.Resource.Runtime
if (!TryEnsureResourceModule()) if (!TryEnsureResourceModule())
{ {
sourceGameObject = null; sourceGameObject = null;
refAssetInfoList?.Clear(); ClearRefAssetInfoArray();
_refAssetIds?.Clear();
return; return;
} }
ReleaseSourceReference(); 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(refAssetInfoArray[i].refAsset);
{
_resourceService.UnloadAsset(refInfo.refAsset);
} }
refAssetInfoList.Clear(); ClearRefAssetInfoArray();
} }
_refAssetIds?.Clear(); 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) public AssetsReference Ref(GameObject source, IResourceService resourceService = null)
{ {
if (source == null) if (source == null)
{ {
throw new GameFrameworkException($"Source gameObject is null."); throw new GameFrameworkException("Source gameObject is null.");
} }
if (source.scene.name != 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) if (resourceService != null)
@ -132,7 +146,7 @@ namespace AlicizaX.Resource.Runtime
{ {
if (source == null) if (source == null)
{ {
throw new GameFrameworkException($"Source gameObject is null."); throw new GameFrameworkException("Source gameObject is null.");
} }
if (resourceService != null) if (resourceService != null)
@ -140,19 +154,14 @@ namespace AlicizaX.Resource.Runtime
_resourceService = resourceService; _resourceService = resourceService;
} }
EnsureReferenceCache();
int instanceId = source.GetInstanceID(); int instanceId = source.GetInstanceID();
if (!_refAssetIds.Add(instanceId)) if (ContainsRefAsset(instanceId))
{ {
return this; return this;
} }
if (refAssetInfoList == null) EnsureRefAssetCapacity(refAssetInfoCount + 1);
{ refAssetInfoArray[refAssetInfoCount++] = new AssetsRefInfo(source);
refAssetInfoList = new List<AssetsRefInfo>();
}
refAssetInfoList.Add(new AssetsRefInfo(source));
return this; return this;
} }
@ -160,12 +169,12 @@ namespace AlicizaX.Resource.Runtime
{ {
if (source == null) if (source == null)
{ {
throw new GameFrameworkException($"Source gameObject is null."); throw new GameFrameworkException("Source gameObject is null.");
} }
if (source.scene.name != 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); GameObject instance = Object.Instantiate(source, parent);
@ -176,12 +185,12 @@ namespace AlicizaX.Resource.Runtime
{ {
if (source == null) if (source == null)
{ {
throw new GameFrameworkException($"Source gameObject is null."); throw new GameFrameworkException("Source gameObject is null.");
} }
if (source.scene.name != 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<AssetsReference>(); var comp = instance.GetComponent<AssetsReference>();
@ -192,7 +201,7 @@ namespace AlicizaX.Resource.Runtime
{ {
if (source == null) if (source == null)
{ {
throw new GameFrameworkException($"Source gameObject is null."); throw new GameFrameworkException("Source gameObject is null.");
} }
var comp = instance.GetComponent<AssetsReference>(); var comp = instance.GetComponent<AssetsReference>();

View File

@ -1,4 +1,5 @@
using AlicizaX; using AlicizaX;
using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
@ -6,6 +7,140 @@ namespace AlicizaX.Resource.Runtime
{ {
public static class AssetsSetHelper 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<MaterialSetRequest>();
request.TargetType = MaterialTargetType.Image;
request.Image = image;
return request;
}
public static MaterialSetRequest Create(SpriteRenderer spriteRenderer)
{
MaterialSetRequest request = MemoryPool.Acquire<MaterialSetRequest>();
request.TargetType = MaterialTargetType.SpriteRenderer;
request.SpriteRenderer = spriteRenderer;
return request;
}
public static MaterialSetRequest Create(MeshRenderer meshRenderer, bool needInstance, bool sharedMaterial)
{
MaterialSetRequest request = MemoryPool.Acquire<MaterialSetRequest>();
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 IResourceService _resourceService;
private static void CheckResourceManager() 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<MaterialInstanceReference>();
if (reference == null)
{
reference = meshRenderer.gameObject.AddComponent<MaterialInstanceReference>();
}
reference.Set(instance);
}
#region SetMaterial #region SetMaterial
public static void SetMaterial(this Image image, string location, bool isAsync = false, string packageName = "") public static void SetMaterial(this Image image, string location, bool isAsync = false, string packageName = "")
{ {
if (image == null) if (image == null)
{ {
throw new GameFrameworkException($"SetSprite failed. Because image is null."); throw new GameFrameworkException("SetSprite failed. Because image is null.");
} }
CheckResourceManager(); CheckResourceManager();
@ -32,29 +193,17 @@ namespace AlicizaX.Resource.Runtime
Material material = _resourceService.LoadAsset<Material>(location, packageName); Material material = _resourceService.LoadAsset<Material>(location, packageName);
image.material = material; image.material = material;
AssetsReference.Ref(material, image.gameObject); AssetsReference.Ref(material, image.gameObject);
}
else
{
_resourceService.LoadAsset<Material>(location, material =>
{
if (image == null || image.gameObject == null)
{
_resourceService.UnloadAsset(material);
material = null;
return; return;
} }
image.material = material; LoadMaterialAsync(location, packageName, MaterialSetRequest.Create(image));
AssetsReference.Ref(material, image.gameObject);
}, packageName);
}
} }
public static void SetMaterial(this SpriteRenderer spriteRenderer, string location, bool isAsync = false, string packageName = "") public static void SetMaterial(this SpriteRenderer spriteRenderer, string location, bool isAsync = false, string packageName = "")
{ {
if (spriteRenderer == null) if (spriteRenderer == null)
{ {
throw new GameFrameworkException($"SetSprite failed. Because image is null."); throw new GameFrameworkException("SetSprite failed. Because image is null.");
} }
CheckResourceManager(); CheckResourceManager();
@ -64,29 +213,17 @@ namespace AlicizaX.Resource.Runtime
Material material = _resourceService.LoadAsset<Material>(location, packageName); Material material = _resourceService.LoadAsset<Material>(location, packageName);
spriteRenderer.material = material; spriteRenderer.material = material;
AssetsReference.Ref(material, spriteRenderer.gameObject); AssetsReference.Ref(material, spriteRenderer.gameObject);
}
else
{
_resourceService.LoadAsset<Material>(location, material =>
{
if (spriteRenderer == null || spriteRenderer.gameObject == null)
{
_resourceService.UnloadAsset(material);
material = null;
return; return;
} }
spriteRenderer.material = material; LoadMaterialAsync(location, packageName, MaterialSetRequest.Create(spriteRenderer));
AssetsReference.Ref(material, spriteRenderer.gameObject);
}, packageName);
}
} }
public static void SetMaterial(this MeshRenderer meshRenderer, string location, bool needInstance = true, bool isAsync = false, string packageName = "") public static void SetMaterial(this MeshRenderer meshRenderer, string location, bool needInstance = true, bool isAsync = false, string packageName = "")
{ {
if (meshRenderer == null) if (meshRenderer == null)
{ {
throw new GameFrameworkException($"SetSprite failed. Because image is null."); throw new GameFrameworkException("SetSprite failed. Because image is null.");
} }
CheckResourceManager(); CheckResourceManager();
@ -94,31 +231,18 @@ namespace AlicizaX.Resource.Runtime
if (!isAsync) if (!isAsync)
{ {
Material material = _resourceService.LoadAsset<Material>(location, packageName); Material material = _resourceService.LoadAsset<Material>(location, packageName);
meshRenderer.material = needInstance ? Object.Instantiate(material) : material; SetMeshMaterial(meshRenderer, material, needInstance);
AssetsReference.Ref(material, meshRenderer.gameObject);
}
else
{
_resourceService.LoadAsset<Material>(location, material =>
{
if (meshRenderer == null || meshRenderer.gameObject == null)
{
_resourceService.UnloadAsset(material);
material = null;
return; return;
} }
meshRenderer.material = needInstance ? Object.Instantiate(material) : material; LoadMaterialAsync(location, packageName, MaterialSetRequest.Create(meshRenderer, needInstance, false));
AssetsReference.Ref(material, meshRenderer.gameObject);
}, packageName);
}
} }
public static void SetSharedMaterial(this MeshRenderer meshRenderer, string location, bool isAsync = false, string packageName = "") public static void SetSharedMaterial(this MeshRenderer meshRenderer, string location, bool isAsync = false, string packageName = "")
{ {
if (meshRenderer == null) if (meshRenderer == null)
{ {
throw new GameFrameworkException($"SetSprite failed. Because image is null."); throw new GameFrameworkException("SetSprite failed. Because image is null.");
} }
CheckResourceManager(); CheckResourceManager();
@ -128,22 +252,10 @@ namespace AlicizaX.Resource.Runtime
Material material = _resourceService.LoadAsset<Material>(location, packageName); Material material = _resourceService.LoadAsset<Material>(location, packageName);
meshRenderer.sharedMaterial = material; meshRenderer.sharedMaterial = material;
AssetsReference.Ref(material, meshRenderer.gameObject); AssetsReference.Ref(material, meshRenderer.gameObject);
}
else
{
_resourceService.LoadAsset<Material>(location, material =>
{
if (meshRenderer == null || meshRenderer.gameObject == null)
{
_resourceService.UnloadAsset(material);
material = null;
return; return;
} }
meshRenderer.sharedMaterial = material; LoadMaterialAsync(location, packageName, MaterialSetRequest.Create(meshRenderer, false, true));
AssetsReference.Ref(material, meshRenderer.gameObject);
}, packageName);
}
} }
#endregion #endregion

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 370ab69f738b11b429fbcc1d9e7a2fb1 guid: 370ab69f738b11b429fbcc1d9e7a2fb1
timeCreated: 1708490345 MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eec0821e60a7c3946b3b434cfdbd999d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,4 +1,5 @@
using System; using System;
using Cysharp.Text;
using AlicizaX; using AlicizaX;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization; using UnityEngine.Serialization;
@ -17,6 +18,8 @@ namespace AlicizaX.Resource.Runtime
private bool _forceUnloadUnusedAssets = false; private bool _forceUnloadUnusedAssets = false;
private bool _forceSystemUnloadUnusedAssets = false;
private bool _preorderUnloadUnusedAssets = false; private bool _preorderUnloadUnusedAssets = false;
private bool _performGCCollect = false; private bool _performGCCollect = false;
@ -153,7 +156,9 @@ namespace AlicizaX.Resource.Runtime
Application.lowMemory += OnLowMemory; 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() private void Start()
{ {
@ -173,7 +178,7 @@ namespace AlicizaX.Resource.Runtime
_resourceService.AssetExpireTime = assetExpireTime; _resourceService.AssetExpireTime = assetExpireTime;
_resourceService.AssetPriority = assetPriority; _resourceService.AssetPriority = assetPriority;
_resourceService.SetForceUnloadUnusedAssetsAction(ForceUnloadUnusedAssets); _resourceService.SetForceUnloadUnusedAssetsAction(ForceUnloadUnusedAssets);
Log.Info($"ResourceModule Run Mode {_playMode}"); Log.Info(ZString.Format("ResourceModule Run Mode {0}", _playMode));
} }
private void OnApplicationQuit() private void OnApplicationQuit()
@ -189,6 +194,7 @@ namespace AlicizaX.Resource.Runtime
if (performGCCollect) if (performGCCollect)
{ {
_performGCCollect = true; _performGCCollect = true;
_forceSystemUnloadUnusedAssets = true;
} }
} }
@ -200,12 +206,13 @@ namespace AlicizaX.Resource.Runtime
if (_asyncOperation == null && (_forceUnloadUnusedAssets || _lastUnloadUnusedAssetsOperationElapseSeconds >= maxUnloadUnusedAssetsInterval || if (_asyncOperation == null && (_forceUnloadUnusedAssets || _lastUnloadUnusedAssetsOperationElapseSeconds >= maxUnloadUnusedAssetsInterval ||
_preorderUnloadUnusedAssets && _lastUnloadUnusedAssetsOperationElapseSeconds >= minUnloadUnusedAssetsInterval)) _preorderUnloadUnusedAssets && _lastUnloadUnusedAssetsOperationElapseSeconds >= minUnloadUnusedAssetsInterval))
{ {
Log.Info("Unload unused assets..."); bool useSystemUnload = _forceSystemUnloadUnusedAssets && useSystemUnloadUnusedAssets;
_forceUnloadUnusedAssets = false; _forceUnloadUnusedAssets = false;
_forceSystemUnloadUnusedAssets = false;
_preorderUnloadUnusedAssets = false; _preorderUnloadUnusedAssets = false;
_lastUnloadUnusedAssetsOperationElapseSeconds = 0f; _lastUnloadUnusedAssetsOperationElapseSeconds = 0f;
_resourceService.UnloadUnusedAssets(); _resourceService.UnloadUnusedAssets();
_asyncOperation = useSystemUnloadUnusedAssets ? Resources.UnloadUnusedAssets() : null; _asyncOperation = useSystemUnload ? Resources.UnloadUnusedAssets() : null;
} }
if (_asyncOperation == null && _performGCCollect) if (_asyncOperation == null && _performGCCollect)

View File

@ -1,4 +1,4 @@
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using AlicizaX.ObjectPool; using AlicizaX.ObjectPool;
using AlicizaX; using AlicizaX;
@ -46,8 +46,6 @@ namespace AlicizaX.Resource.Runtime
} }
protected internal override void Release(bool isShutdown) protected internal override void Release(bool isShutdown)
{
if (!isShutdown)
{ {
AssetHandle handle = m_AssetHandle; AssetHandle handle = m_AssetHandle;
if (handle is { IsValid: true }) if (handle is { IsValid: true })
@ -58,4 +56,3 @@ namespace AlicizaX.Resource.Runtime
} }
} }
} }
}

View File

@ -1,4 +1,5 @@
using System; using System;
using Cysharp.Text;
using UnityEngine; using UnityEngine;
using YooAsset; using YooAsset;
@ -90,7 +91,7 @@ namespace AlicizaX.Resource.Runtime
// 小游戏缓存根目录 // 小游戏缓存根目录
// 注意:此处代码根据微信插件配置来填写! // 注意:此处代码根据微信插件配置来填写!
WeChatWASM.WXBase.PreloadConcurrent(10); 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); webRemoteFileSystemParams = WechatFileSystemCreater.CreateFileSystemParameters(packageRoot, remoteServices, null);
#endif #endif

View File

@ -1,4 +1,4 @@
using AlicizaX.ObjectPool; using AlicizaX.ObjectPool;
using AlicizaX; using AlicizaX;
namespace AlicizaX.Resource.Runtime namespace AlicizaX.Resource.Runtime

View File

@ -1,4 +1,5 @@
using System.IO; using System.IO;
using Cysharp.Text;
using UnityEngine; using UnityEngine;
using YooAsset; using YooAsset;
@ -20,12 +21,12 @@ namespace AlicizaX.Resource.Runtime
string IRemoteServices.GetRemoteMainURL(string fileName) string IRemoteServices.GetRemoteMainURL(string fileName)
{ {
return $"{_defaultHostServer}/{fileName}"; return ZString.Concat(_defaultHostServer, "/", fileName);
} }
string IRemoteServices.GetRemoteFallbackURL(string fileName) string IRemoteServices.GetRemoteFallbackURL(string fileName)
{ {
return $"{_fallbackHostServer}/{fileName}"; return ZString.Concat(_fallbackHostServer, "/", fileName);
} }
} }

View File

@ -1,9 +1,10 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using AlicizaX.ObjectPool; using AlicizaX.ObjectPool;
using AlicizaX; using AlicizaX;
using Cysharp.Text;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
using YooAsset; using YooAsset;
@ -28,7 +29,7 @@ namespace AlicizaX.Resource.Runtime
public string DecryptionServices { get; set; } public string DecryptionServices { get; set; }
/// <summary> /// <summary>
/// 自动释放资源引用计数为0的资源包 /// 自动释放资源引用计数。。的资源包
/// </summary> /// </summary>
public bool AutoUnloadBundleWhenUnused { get; set; } = false; public bool AutoUnloadBundleWhenUnused { get; set; } = false;
@ -60,7 +61,6 @@ namespace AlicizaX.Resource.Runtime
public int FailedTryAgain { get; set; } public int FailedTryAgain { get; set; }
#region internal #region internal
/// <summary> /// <summary>
@ -76,39 +76,89 @@ namespace AlicizaX.Resource.Runtime
/// <summary> /// <summary>
/// 资源信息列表。 /// 资源信息列表。
/// </summary> /// </summary>
private readonly Dictionary<string, AssetInfo> _assetInfoMap = new Dictionary<string, AssetInfo>(); private readonly Dictionary<AssetCacheKey, AssetInfo> _assetInfoMap = new Dictionary<AssetCacheKey, AssetInfo>(AssetCacheKeyComparer.Instance);
/// <summary> /// <summary>
/// 正在加载的资源任务。 /// 正在加载的资源任务。
/// </summary> /// </summary>
private readonly Dictionary<string, UniTaskCompletionSource<bool>> _assetLoadingOperations = new Dictionary<string, UniTaskCompletionSource<bool>>(); private readonly Dictionary<string, AutoResetUniTaskCompletionSource<bool>> _assetLoadingOperations = new Dictionary<string, AutoResetUniTaskCompletionSource<bool>>();
private readonly Dictionary<AssetCacheKey, string> _assetObjectKeyMap = new Dictionary<AssetCacheKey, string>(AssetCacheKeyComparer.Instance);
private const float ProgressCallbackThreshold = 0.01f; 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<AssetCacheKey>
{
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 #endregion
public void Initialize() public void Initialize()
{ {
// 初始化资源系统 // 初始化资源系。。
YooAssets.Initialize(new ResourceLogger()); YooAssets.Initialize(new ResourceLogger());
YooAssets.SetOperationSystemMaxTimeSlice(Milliseconds); YooAssets.SetOperationSystemMaxTimeSlice(Milliseconds);
// 创建默认的资源包 // 创建默认的资源包
string packageName = DefaultPackageName; string packageName = DefaultPackageName;
var defaultPackage = YooAssets.TryGetPackage(packageName); var defaultPackage = YooAssets.TryGetPackage(packageName);
if (defaultPackage == null) if (defaultPackage == null)
{ {
defaultPackage = YooAssets.CreatePackage(packageName); defaultPackage = YooAssets.CreatePackage(packageName);
YooAssets.SetDefaultPackage(defaultPackage); YooAssets.SetDefaultPackage(defaultPackage);
} }
DefaultPackage = defaultPackage; DefaultPackage = defaultPackage;
PackageMap[packageName] = defaultPackage; PackageMap[packageName] = defaultPackage;
CreateAssetPool(); CreateAssetPool();
} }
protected override void OnInitialize() protected override void OnInitialize()
{ {
} }
@ -124,6 +174,7 @@ namespace AlicizaX.Resource.Runtime
_assetPool = null; _assetPool = null;
_assetLoadingOperations.Clear(); _assetLoadingOperations.Clear();
_assetInfoMap.Clear(); _assetInfoMap.Clear();
_assetObjectKeyMap.Clear();
} }
public UniTask<bool> InitPackageAsync(string packageName = "", string hostServerURL = "", string fallbackHostServerURL = "") public UniTask<bool> InitPackageAsync(string packageName = "", string hostServerURL = "", string fallbackHostServerURL = "")
@ -137,7 +188,7 @@ namespace AlicizaX.Resource.Runtime
{ {
if (resPackage.InitializeStatus is EOperationStatus.Processing or EOperationStatus.Succeed) 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<bool>(false); return new UniTask<bool>(false);
} }
else else
@ -150,7 +201,6 @@ namespace AlicizaX.Resource.Runtime
GameFrameworkGuard.NotNull(packageName, nameof(packageName)); GameFrameworkGuard.NotNull(packageName, nameof(packageName));
GameFrameworkGuard.NotNull(hostServerURL, nameof(hostServerURL)); GameFrameworkGuard.NotNull(hostServerURL, nameof(hostServerURL));
GameFrameworkGuard.NotNull(fallbackHostServerURL, nameof(fallbackHostServerURL)); GameFrameworkGuard.NotNull(fallbackHostServerURL, nameof(fallbackHostServerURL));
// 创建默认的资源包 // 创建默认的资源包
var resourcePackage = YooAssets.TryGetPackage(packageName); var resourcePackage = YooAssets.TryGetPackage(packageName);
if (resourcePackage == null) if (resourcePackage == null)
@ -159,7 +209,6 @@ namespace AlicizaX.Resource.Runtime
} }
PackageMap[packageName] = resourcePackage; PackageMap[packageName] = resourcePackage;
var initializationOperationHandler = CreateInitializationOperationHandler(resourcePackage, hostServerURL, fallbackHostServerURL, DecryptionServices); var initializationOperationHandler = CreateInitializationOperationHandler(resourcePackage, hostServerURL, fallbackHostServerURL, DecryptionServices);
initializationOperationHandler.Completed += asyncOperationBase => initializationOperationHandler.Completed += asyncOperationBase =>
{ {
@ -172,10 +221,10 @@ namespace AlicizaX.Resource.Runtime
taskCompletionSource.TrySetException(new Exception(asyncOperationBase.Error)); taskCompletionSource.TrySetException(new Exception(asyncOperationBase.Error));
} }
}; };
return taskCompletionSource.Task; return taskCompletionSource.Task;
} }
/// <summary> /// <summary>
/// 获取当前资源包版本。 /// 获取当前资源包版本。
/// </summary> /// </summary>
@ -186,6 +235,7 @@ namespace AlicizaX.Resource.Runtime
var package = string.IsNullOrEmpty(customPackageName) var package = string.IsNullOrEmpty(customPackageName)
? YooAssets.GetPackage(DefaultPackageName) ? YooAssets.GetPackage(DefaultPackageName)
: YooAssets.GetPackage(customPackageName); : YooAssets.GetPackage(customPackageName);
if (package == null) if (package == null)
{ {
return string.Empty; return string.Empty;
@ -204,44 +254,29 @@ namespace AlicizaX.Resource.Runtime
public RequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks = false, int timeout = 60, public RequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks = false, int timeout = 60,
string customPackageName = "") string customPackageName = "")
{ {
var package = string.IsNullOrEmpty(customPackageName) var package = GetPackageOrThrow(customPackageName);
? YooAssets.GetPackage(DefaultPackageName)
: YooAssets.GetPackage(customPackageName);
return package.RequestPackageVersionAsync(appendTimeTicks, timeout); return package.RequestPackageVersionAsync(appendTimeTicks, timeout);
} }
/// <summary> /// <summary>
/// 向网络端请求并更新清 /// 向网络端请求并更新清。。
/// </summary> /// </summary>
/// <param name="packageVersion">更新的包裹版</param> /// <param name="packageVersion">更新的包裹版。。</param>
/// <param name="timeout">超时时间默认值60秒</param> /// <param name="timeout">超时时间默认值60秒</param>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param> /// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
public UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, int timeout = 60, string customPackageName = "") public UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, int timeout = 60, string customPackageName = "")
{ {
var package = string.IsNullOrEmpty(customPackageName) var package = GetPackageOrThrow(customPackageName);
? YooAssets.GetPackage(this.DefaultPackageName)
: YooAssets.GetPackage(customPackageName);
return package.UpdatePackageManifestAsync(packageVersion, timeout); return package.UpdatePackageManifestAsync(packageVersion, timeout);
} }
/// <summary> /// <summary>
/// 创建资源下载器,用于下载当前资源版本所有的资源包文件。 /// 创建资源下载器,用于下载当前资源版本所有的资源包文件。
/// </summary> /// </summary>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param> /// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
public ResourceDownloaderOperation CreateResourceDownloader(string customPackageName = "") public ResourceDownloaderOperation CreateResourceDownloader(string customPackageName = "")
{ {
ResourcePackage package = null; ResourcePackage package = GetPackageOrThrow(customPackageName);
if (string.IsNullOrEmpty(customPackageName))
{
package = YooAssets.GetPackage(this.DefaultPackageName);
}
else
{
package = YooAssets.GetPackage(customPackageName);
}
return package.CreateResourceDownloader(DownloadingMaxNum, FailedTryAgain); return package.CreateResourceDownloader(DownloadingMaxNum, FailedTryAgain);
} }
@ -254,10 +289,8 @@ namespace AlicizaX.Resource.Runtime
EFileClearMode clearMode = EFileClearMode.ClearUnusedBundleFiles, EFileClearMode clearMode = EFileClearMode.ClearUnusedBundleFiles,
string customPackageName = "") string customPackageName = "")
{ {
var package = string.IsNullOrEmpty(customPackageName) var package = GetPackageOrThrow(customPackageName);
? YooAssets.GetPackage(DefaultPackageName) return package.ClearCacheFilesAsync(clearMode);
: YooAssets.GetPackage(customPackageName);
return package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles);
} }
/// <summary> /// <summary>
@ -266,9 +299,7 @@ namespace AlicizaX.Resource.Runtime
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param> /// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
public void ClearAllBundleFiles(string customPackageName = "") public void ClearAllBundleFiles(string customPackageName = "")
{ {
var package = string.IsNullOrEmpty(customPackageName) var package = GetPackageOrThrow(customPackageName);
? YooAssets.GetPackage(DefaultPackageName)
: YooAssets.GetPackage(customPackageName);
package.ClearCacheFilesAsync(EFileClearMode.ClearAllBundleFiles); package.ClearCacheFilesAsync(EFileClearMode.ClearAllBundleFiles);
} }
@ -297,11 +328,16 @@ namespace AlicizaX.Resource.Runtime
public void UnloadUnusedAssets() public void UnloadUnusedAssets()
{ {
_assetPool.ReleaseAllUnused(); _assetPool.ReleaseAllUnused();
if (_unloadUnusedAssetsOperation is { IsDone: false })
{
return;
}
foreach (var package in PackageMap.Values) foreach (var package in PackageMap.Values)
{ {
if (package is { InitializeStatus: EOperationStatus.Succeed }) if (package is { InitializeStatus: EOperationStatus.Succeed })
{ {
package.UnloadUnusedAssetsAsync(); _unloadUnusedAssetsOperation = package.UnloadUnusedAssetsAsync();
} }
} }
} }
@ -312,15 +348,19 @@ namespace AlicizaX.Resource.Runtime
public void ForceUnloadAllAssets() public void ForceUnloadAllAssets()
{ {
#if UNITY_WEBGL #if UNITY_WEBGL
Log.Warning($"WebGL not support invoke {nameof(ForceUnloadAllAssets)}"); Log.Warning(ZString.Format("WebGL not support invoke {0}", nameof(ForceUnloadAllAssets)));
return; return;
#else #else
if (_unloadAllAssetsOperation is { IsDone: false })
{
return;
}
foreach (var package in PackageMap.Values) foreach (var package in PackageMap.Values)
{ {
if (package is { InitializeStatus: EOperationStatus.Succeed }) if (package is { InitializeStatus: EOperationStatus.Succeed })
{ {
package.UnloadAllAssetsAsync(); _unloadAllAssetsOperation = package.UnloadAllAssetsAsync();
} }
} }
#endif #endif
@ -331,6 +371,19 @@ namespace AlicizaX.Resource.Runtime
_forceUnloadUnusedAssetsAction?.Invoke(performGCCollect); _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 Public Methods
#region #region
@ -346,12 +399,10 @@ namespace AlicizaX.Resource.Runtime
{ {
return YooAssets.IsNeedDownloadFromRemote(location); return YooAssets.IsNeedDownloadFromRemote(location);
} }
else
{
var package = YooAssets.GetPackage(packageName); var package = YooAssets.GetPackage(packageName);
return package.IsNeedDownloadFromRemote(location); return package.IsNeedDownloadFromRemote(location);
} }
}
/// <summary> /// <summary>
/// 是否需要从远端更新下载。 /// 是否需要从远端更新下载。
@ -364,12 +415,10 @@ namespace AlicizaX.Resource.Runtime
{ {
return YooAssets.IsNeedDownloadFromRemote(assetInfo); return YooAssets.IsNeedDownloadFromRemote(assetInfo);
} }
else
{
var package = YooAssets.GetPackage(packageName); var package = YooAssets.GetPackage(packageName);
return package.IsNeedDownloadFromRemote(assetInfo); return package.IsNeedDownloadFromRemote(assetInfo);
} }
}
/// <summary> /// <summary>
/// 获取资源信息列表。 /// 获取资源信息列表。
@ -383,12 +432,10 @@ namespace AlicizaX.Resource.Runtime
{ {
return YooAssets.GetAssetInfos(tag); return YooAssets.GetAssetInfos(tag);
} }
else
{
var package = YooAssets.GetPackage(packageName); var package = YooAssets.GetPackage(packageName);
return package.GetAssetInfos(tag); return package.GetAssetInfos(tag);
} }
}
/// <summary> /// <summary>
/// 获取资源信息列表。 /// 获取资源信息列表。
@ -402,12 +449,10 @@ namespace AlicizaX.Resource.Runtime
{ {
return YooAssets.GetAssetInfos(tags); return YooAssets.GetAssetInfos(tags);
} }
else
{
var package = YooAssets.GetPackage(packageName); var package = YooAssets.GetPackage(packageName);
return package.GetAssetInfos(tags); return package.GetAssetInfos(tags);
} }
}
/// <summary> /// <summary>
/// 获取资源信息。 /// 获取资源信息。
@ -424,18 +469,19 @@ namespace AlicizaX.Resource.Runtime
if (string.IsNullOrEmpty(packageName)) 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; return assetInfo;
} }
assetInfo = YooAssets.GetAssetInfo(location); assetInfo = YooAssets.GetAssetInfo(location);
_assetInfoMap[location] = assetInfo; _assetInfoMap[key] = assetInfo;
return assetInfo; return assetInfo;
} }
else else
{ {
string key = $"{packageName}/{location}"; AssetCacheKey key = new AssetCacheKey(packageName, location);
if (_assetInfoMap.TryGetValue(key, out AssetInfo assetInfo)) if (_assetInfoMap.TryGetValue(key, out AssetInfo assetInfo))
{ {
return assetInfo; return assetInfo;
@ -444,7 +490,7 @@ namespace AlicizaX.Resource.Runtime
var package = YooAssets.GetPackage(packageName); var package = YooAssets.GetPackage(packageName);
if (package == null) 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); assetInfo = package.GetAssetInfo(location);
@ -467,10 +513,9 @@ namespace AlicizaX.Resource.Runtime
} }
AssetInfo assetInfo = GetAssetInfo(location, packageName); AssetInfo assetInfo = GetAssetInfo(location, packageName);
if (!CheckLocationValid(location, packageName)) if (!CheckLocationValid(location, packageName))
{ {
return HasAssetResult.Valid; return HasAssetResult.NotExist;
} }
if (assetInfo == null) if (assetInfo == null)
@ -499,7 +544,7 @@ namespace AlicizaX.Resource.Runtime
} }
var package = YooAssets.GetPackage(packageName); var package = YooAssets.GetPackage(packageName);
return package.CheckLocationValid(location); return package != null && package.CheckLocationValid(location);
} }
#endregion #endregion
@ -546,6 +591,7 @@ namespace AlicizaX.Resource.Runtime
private AssetHandle GetHandleAsync(string location, Type assetType, string packageName = "", uint priority = 0) private AssetHandle GetHandleAsync(string location, Type assetType, string packageName = "", uint priority = 0)
{ {
if (string.IsNullOrEmpty(packageName)) if (string.IsNullOrEmpty(packageName))
{ {
return YooAssets.LoadAssetAsync(location, assetType, priority); return YooAssets.LoadAssetAsync(location, assetType, priority);
} }
@ -574,7 +620,16 @@ namespace AlicizaX.Resource.Runtime
return location; 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<T>(string location, string packageName = "") where T : UnityEngine.Object public T LoadAsset<T>(string location, string packageName = "") where T : UnityEngine.Object
@ -586,21 +641,20 @@ namespace AlicizaX.Resource.Runtime
if (!CheckLocationValid(location, packageName)) if (!CheckLocationValid(location, packageName))
{ {
Log.Error($"Could not found location [{location}]."); Log.Error(ZString.Format("Could not found location [{0}].", location));
return null; return null;
} }
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
AssetObject assetObject = _assetPool.Spawn(assetObjectKey); AssetObject assetObject = _assetPool.Spawn(assetObjectKey);
if (assetObject != null) if (assetObject != null)
{ {
return assetObject.Target as T; return assetObject.Target as T;
} }
AssetHandle handle = GetHandleSync<T>(location, packageName: packageName); AssetHandle handle = GetHandleSync<T>(location, packageName: packageName);
T ret = handle.AssetObject as T; T ret = handle.AssetObject as T;
assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle); assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle);
_assetPool.Register(assetObject, true); _assetPool.Register(assetObject, true);
@ -616,21 +670,20 @@ namespace AlicizaX.Resource.Runtime
if (!CheckLocationValid(location, packageName)) if (!CheckLocationValid(location, packageName))
{ {
Log.Error($"Could not found location [{location}]."); Log.Error(ZString.Format("Could not found location [{0}].", location));
return null; return null;
} }
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
AssetObject assetObject = _assetPool.Spawn(assetObjectKey); AssetObject assetObject = _assetPool.Spawn(assetObjectKey);
if (assetObject != null) if (assetObject != null)
{ {
return AssetsReference.Instantiate(assetObject.Target as GameObject, parent, this).gameObject; return AssetsReference.Instantiate(assetObject.Target as GameObject, parent, this).gameObject;
} }
AssetHandle handle = GetHandleSync<GameObject>(location, packageName: packageName); AssetHandle handle = GetHandleSync<GameObject>(location, packageName: packageName);
GameObject gameObject = AssetsReference.Instantiate(handle.AssetObject as GameObject, parent, this).gameObject; GameObject gameObject = AssetsReference.Instantiate(handle.AssetObject as GameObject, parent, this).gameObject;
assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle); assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle);
_assetPool.Register(assetObject, true); _assetPool.Register(assetObject, true);
@ -654,23 +707,15 @@ namespace AlicizaX.Resource.Runtime
if (!CheckLocationValid(location, packageName)) if (!CheckLocationValid(location, packageName))
{ {
Log.Error($"Could not found location [{location}]."); Log.Error(ZString.Format("Could not found location [{0}].", location));
callback?.Invoke(null); callback?.Invoke(null);
return; return;
} }
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
try
{
var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey); var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey);
callback?.Invoke(asset as T); callback?.Invoke(asset as T);
} }
catch (Exception ex)
{
Log.Error($"Can not load asset '{location}'. {ex}");
callback?.Invoke(null);
}
}
public async UniTask<T> LoadAssetAsync<T>(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object public async UniTask<T> LoadAssetAsync<T>(string location, CancellationToken cancellationToken = default, string packageName = "") where T : UnityEngine.Object
{ {
@ -681,13 +726,11 @@ namespace AlicizaX.Resource.Runtime
if (!CheckLocationValid(location, packageName)) if (!CheckLocationValid(location, packageName))
{ {
Log.Error($"Could not found location [{location}]."); Log.Error(ZString.Format("Could not found location [{0}].", location));
return null; return null;
} }
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey, cancellationToken: cancellationToken); var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey, cancellationToken: cancellationToken);
return asset as T; return asset as T;
} }
@ -701,12 +744,11 @@ namespace AlicizaX.Resource.Runtime
if (!CheckLocationValid(location, packageName)) if (!CheckLocationValid(location, packageName))
{ {
Log.Error($"Could not found location [{location}]."); Log.Error(ZString.Format("Could not found location [{0}].", location));
return null; return null;
} }
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
var asset = await GetOrLoadAssetAsync(location, typeof(GameObject), packageName, assetObjectKey, cancellationToken: cancellationToken); var asset = await GetOrLoadAssetAsync(location, typeof(GameObject), packageName, assetObjectKey, cancellationToken: cancellationToken);
return asset != null ? AssetsReference.Instantiate(asset as GameObject, parent, this).gameObject : null; return asset != null ? AssetsReference.Instantiate(asset as GameObject, parent, this).gameObject : null;
} }
@ -736,7 +778,7 @@ namespace AlicizaX.Resource.Runtime
if (!CheckLocationValid(location, packageName)) 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); Log.Error(errorMessage);
if (loadAssetCallbacks.LoadAssetFailureCallback != null) if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{ {
@ -747,14 +789,11 @@ namespace AlicizaX.Resource.Runtime
} }
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
float duration = Time.time; float duration = Time.time;
AssetInfo assetInfo = GetAssetInfo(location, packageName); AssetInfo assetInfo = GetAssetInfo(location, packageName);
if (!string.IsNullOrEmpty(assetInfo.Error)) 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) if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{ {
loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotExist, errorMessage, userData); loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotExist, errorMessage, userData);
@ -764,24 +803,17 @@ namespace AlicizaX.Resource.Runtime
throw new GameFrameworkException(errorMessage); throw new GameFrameworkException(errorMessage);
} }
try
{
var asset = await GetOrLoadAssetAsync(location, assetType, packageName, assetObjectKey, NormalizePriority(priority), default, var asset = await GetOrLoadAssetAsync(location, assetType, packageName, assetObjectKey, NormalizePriority(priority), default,
handle => StartProgressTask(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData)); loadAssetCallbacks.LoadAssetUpdateCallback, userData);
loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData);
} if (asset == null)
catch (Exception ex)
{ {
string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", location); string errorMessage = ZString.Format("Can not load asset '{0}'.", location);
Log.Error($"{errorMessage} {ex}"); loadAssetCallbacks.LoadAssetFailureCallback?.Invoke(location, LoadResourceStatus.NotReady, errorMessage, userData);
if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{
loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotReady, errorMessage, userData);
return; return;
} }
throw; loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData);
}
} }
/// <summary> /// <summary>
@ -806,7 +838,7 @@ namespace AlicizaX.Resource.Runtime
if (!CheckLocationValid(location, packageName)) 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); Log.Error(errorMessage);
if (loadAssetCallbacks.LoadAssetFailureCallback != null) if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{ {
@ -817,14 +849,11 @@ namespace AlicizaX.Resource.Runtime
} }
string assetObjectKey = GetCacheKey(location, packageName); string assetObjectKey = GetCacheKey(location, packageName);
float duration = Time.time; float duration = Time.time;
AssetInfo assetInfo = GetAssetInfo(location, packageName); AssetInfo assetInfo = GetAssetInfo(location, packageName);
if (!string.IsNullOrEmpty(assetInfo.Error)) 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) if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{ {
loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotExist, errorMessage, userData); loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotExist, errorMessage, userData);
@ -834,24 +863,17 @@ namespace AlicizaX.Resource.Runtime
throw new GameFrameworkException(errorMessage); throw new GameFrameworkException(errorMessage);
} }
try
{
var asset = await GetOrLoadAssetAsync(location, assetInfo.AssetType, packageName, assetObjectKey, NormalizePriority(priority), default, var asset = await GetOrLoadAssetAsync(location, assetInfo.AssetType, packageName, assetObjectKey, NormalizePriority(priority), default,
handle => StartProgressTask(location, handle, loadAssetCallbacks.LoadAssetUpdateCallback, userData)); loadAssetCallbacks.LoadAssetUpdateCallback, userData);
loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData);
} if (asset == null)
catch (Exception ex)
{ {
string errorMessage = Utility.Text.Format("Can not load asset '{0}'.", location); string errorMessage = ZString.Format("Can not load asset '{0}'.", location);
Log.Error($"{errorMessage} {ex}"); loadAssetCallbacks.LoadAssetFailureCallback?.Invoke(location, LoadResourceStatus.NotReady, errorMessage, userData);
if (loadAssetCallbacks.LoadAssetFailureCallback != null)
{
loadAssetCallbacks.LoadAssetFailureCallback(location, LoadResourceStatus.NotReady, errorMessage, userData);
return; return;
} }
throw; loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData);
}
} }
private async UniTaskVoid InvokeProgress(string location, AssetHandle assetHandle, LoadAssetUpdateCallback loadAssetUpdateCallback, object userData) private async UniTaskVoid InvokeProgress(string location, AssetHandle assetHandle, LoadAssetUpdateCallback loadAssetUpdateCallback, object userData)
@ -921,12 +943,11 @@ namespace AlicizaX.Resource.Runtime
#endregion #endregion
private async UniTask<UnityEngine.Object> GetOrLoadAssetAsync(string location, Type assetType, string packageName, private async UniTask<UnityEngine.Object> GetOrLoadAssetAsync(string location, Type assetType, string packageName,
string assetObjectKey, uint priority = 0, CancellationToken cancellationToken = default, Action<AssetHandle> onHandleCreated = null) string assetObjectKey, uint priority = 0, CancellationToken cancellationToken = default, LoadAssetUpdateCallback loadAssetUpdateCallback = null, object userData = null)
{ {
while (true) while (true)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
AssetObject cachedAssetObject = _assetPool.Spawn(assetObjectKey); AssetObject cachedAssetObject = _assetPool.Spawn(assetObjectKey);
if (cachedAssetObject != null) if (cachedAssetObject != null)
{ {
@ -939,16 +960,25 @@ namespace AlicizaX.Resource.Runtime
continue; continue;
} }
AssetHandle handle = null; AssetHandle handle = GetHandleAsync(location, assetType, packageName: packageName, priority: priority);
try StartProgressTask(location, handle, loadAssetUpdateCallback, userData);
while (handle is { IsValid: true, IsDone: false })
{ {
handle = GetHandleAsync(location, assetType, packageName: packageName, priority: priority); if (cancellationToken.IsCancellationRequested)
onHandleCreated?.Invoke(handle); {
await handle.ToUniTask(); DisposeHandle(handle);
FailLoading(assetObjectKey, null);
return null;
}
await UniTask.Yield();
}
if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed) if (handle.AssetObject == null || handle.Status == EOperationStatus.Failed)
{ {
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); var assetObject = AssetObject.Create(assetObjectKey, handle.AssetObject, handle);
@ -956,23 +986,16 @@ namespace AlicizaX.Resource.Runtime
CompleteLoading(assetObjectKey); CompleteLoading(assetObjectKey);
return handle.AssetObject as UnityEngine.Object; return handle.AssetObject as UnityEngine.Object;
} }
catch (Exception ex)
{
DisposeHandle(handle);
FailLoading(assetObjectKey, ex);
throw;
}
}
} }
private bool TryBeginLoading(string assetObjectKey) private bool TryBeginLoading(string assetObjectKey)
{ {
if (_assetLoadingOperations.ContainsKey(assetObjectKey)) if (_assetLoadingOperations.TryGetValue(assetObjectKey, out _))
{ {
return false; return false;
} }
_assetLoadingOperations.Add(assetObjectKey, new UniTaskCompletionSource<bool>()); _assetLoadingOperations.Add(assetObjectKey, AutoResetUniTaskCompletionSource<bool>.Create());
return true; return true;
} }
@ -1007,12 +1030,13 @@ namespace AlicizaX.Resource.Runtime
private void FailLoading(string assetObjectKey, Exception exception) private void FailLoading(string assetObjectKey, Exception exception)
{ {
if (!_assetLoadingOperations.TryGetValue(assetObjectKey, out var loadingOperation)) if (!_assetLoadingOperations.TryGetValue(assetObjectKey, out var loadingOperation))
{ {
return; return;
} }
_assetLoadingOperations.Remove(assetObjectKey); _assetLoadingOperations.Remove(assetObjectKey);
loadingOperation.TrySetException(exception); loadingOperation.TrySetResult(false);
} }
private void DisposeHandle(AssetHandle handle) private void DisposeHandle(AssetHandle handle)
@ -1038,7 +1062,7 @@ namespace AlicizaX.Resource.Runtime
/// <summary> /// <summary>
/// 设置下载系统参数,自定义下载请求。 /// 设置下载系统参数,自定义下载请求。
/// </summary> /// </summary>
/// <param name="downloadSystemUnityWebRequest">自定义下载器的请求委托<see cref="UnityWebRequestDelegate"/></param> /// <param name="downloadSystemUnityWebRequest">自定义下载器的请求委托<see cref="UnityWebRequestDelegate"/></param>
public void SetDownloadSystemUnityWebRequest(UnityWebRequestDelegate downloadSystemUnityWebRequest) public void SetDownloadSystemUnityWebRequest(UnityWebRequestDelegate downloadSystemUnityWebRequest)
{ {
YooAssets.SetDownloadSystemUnityWebRequest(downloadSystemUnityWebRequest); YooAssets.SetDownloadSystemUnityWebRequest(downloadSystemUnityWebRequest);
@ -1054,9 +1078,9 @@ namespace AlicizaX.Resource.Runtime
private string GetAuthorization(string userName, string password) 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); var bytes = System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(auth);
return $"Basic {Convert.ToBase64String(bytes)}"; return ZString.Concat("Basic ", Convert.ToBase64String(bytes));
} }
#endregion #endregion