[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 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<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()
{
using (s_InfoMarker.Auto())

View File

@ -53,6 +53,19 @@ namespace AlicizaX
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)]
public static IMemory Acquire(Type memoryType)
{
@ -66,6 +79,10 @@ namespace AlicizaX
MemoryPool<T>.Release(memory);
}
/// <summary>
/// 慢速动态路径。禁止在运行时热路径调用;请通过缓存的 MemoryPoolHandle 或 Release&lt;T&gt; 回收对象。
/// </summary>
[Obsolete("慢速动态路径,禁止在运行时热路径使用。请通过缓存的 MemoryPoolHandle或改用 MemoryPool<T>.Release / MemoryPool.Release<T>。", false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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)
{
if (type == null)

View File

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

View File

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

View File

@ -1,4 +1,4 @@

using AlicizaX;
namespace AlicizaX.Resource.Runtime

View File

@ -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<LoadAssetObject>();
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
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 Cysharp.Text;
using Cysharp.Threading.Tasks;
namespace AlicizaX.Resource.Runtime
@ -51,7 +51,7 @@ namespace AlicizaX.Resource.Runtime
UnityEngine.Object assetObject = asset as UnityEngine.Object;
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;
}
@ -78,22 +78,22 @@ namespace AlicizaX.Resource.Runtime
CancelAndCleanupOldRequest(target);
var linkedTokenSource = new CancellationTokenSource();
var loadingState = MemoryPool.Acquire<LoadingState>();
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))
{
return;
@ -113,26 +113,15 @@ namespace AlicizaX.Resource.Runtime
return;
}
T resource = await _resourceService.LoadAssetAsync<T>(location, linkedTokenSource.Token);
if (resource == null)
var loadResult = await _resourceService.LoadAssetAsync<T>(location, loadToken).SuppressCancellationThrow();
if (loadResult.IsCanceled || loadResult.Result == null)
{
ClearLoadingState(target);
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)
{
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();

View File

@ -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<string, SubAssetsHandle> _subAssetsHandles = new Dictionary<string, SubAssetsHandle>();
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)
{
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<Sprite>(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,29 +193,57 @@ namespace AlicizaX.Resource.Runtime
continue;
}
var completionSource = new UniTaskCompletionSource<SubAssetsHandle>();
var completionSource = AutoResetUniTaskCompletionSource<SubAssetsHandle>.Create();
_subAssetLoadingOperations.Add(location, completionSource);
SubAssetsHandle subAssetsHandle = default;
try
SubAssetsHandle subAssetsHandle = YooAssets.LoadSubAssetsAsync<Sprite>(location);
while (subAssetsHandle is { IsValid: true, IsDone: false })
{
subAssetsHandle = YooAssets.LoadSubAssetsAsync<Sprite>(location);
await subAssetsHandle.ToUniTask();
if (cancellationToken.IsCancellationRequested)
{
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;
completionSource.TrySetResult(subAssetsHandle);
CompleteSubAssetLoading(location, completionSource, 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);
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)
@ -171,6 +262,7 @@ namespace AlicizaX.Resource.Runtime
{
subAssetsHandle.Dispose();
_subAssetsHandles.Remove(location);
ClearSubSpriteCache(location);
}
}
}

View File

@ -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; }
/// <summary>
/// 检查是否可以释放间隔。
/// </summary>
[SerializeField]
private float checkCanReleaseInterval = 30f;
/// <summary>
/// 对象池自动释放时间间隔。
/// </summary>
[SerializeField]
private float autoReleaseInterval = 60f;
/// <summary>
/// 每帧最大处理资源数量,用于分帧处理避免卡顿。
/// </summary>
[SerializeField]
private int maxProcessPerFrame = 50;
[SerializeField]
private int releaseCheckThreshold = 16;
/// <summary>
/// 当前正在处理的节点,用于分帧处理。
/// </summary>
private LinkedListNode<LoadAssetObject> _currentProcessNode;
/// <summary>
/// 保存加载的图片对象。
/// </summary>
private LinkedList<LoadAssetObject> _loadAssetObjectsLinkedList;
private Dictionary<Object, LinkedListNode<LoadAssetObject>> _trackedAssetNodes;
private LoadAssetObject[] _loadAssetObjects;
private int _loadAssetObjectCount;
private int _currentProcessIndex;
private Dictionary<Object, int> _trackedAssetIndices;
private bool _releaseRequested;
private float _nextReleaseCheckTime = float.MaxValue;
/// <summary>
/// 散图集合对象池。
/// </summary>
private IObjectPool<AssetItemObject> _assetItemPool;
#if UNITY_EDITOR
public LinkedList<LoadAssetObject> 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<LoadAssetObject>();
_trackedAssetNodes = new Dictionary<Object, LinkedListNode<LoadAssetObject>>(16);
_loadAssetObjects = new LoadAssetObject[16];
_trackedAssetIndices = new Dictionary<Object, int>(16);
InitializedResources();
}
@ -102,50 +73,36 @@ namespace AlicizaX.Resource.Runtime
ReleaseUnused();
}
/// <summary>
/// 回收无引用的缓存资产。
/// 使用分帧处理优化性能,避免一次性处理大量资源导致卡顿。
/// </summary>
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<LoadAssetObject> 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<LoadAssetObject> 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)

View File

@ -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<AssetsRefInfo> refAssetInfoList;
private AssetsRefInfo[] refAssetInfoArray;
[SerializeField]
private int refAssetInfoCount;
private static IResourceService _resourceService;
private HashSet<int> _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;
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>();
if (refAssetInfoList == null)
{
return;
}
foreach (var refInfo in refAssetInfoList)
{
if (refInfo.refAsset != null)
{
_refAssetIds.Add(refInfo.instanceId != 0 ? refInfo.instanceId : refInfo.refAsset.GetInstanceID());
}
}
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);
_resourceService.UnloadAsset(refAssetInfoArray[i].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)
{
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<AssetsRefInfo>();
}
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<AssetsReference>();
@ -192,7 +201,7 @@ 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<AssetsReference>();

View File

@ -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<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 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
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<Material>(location, packageName);
image.material = material;
AssetsReference.Ref(material, image.gameObject);
}
else
{
_resourceService.LoadAsset<Material>(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<Material>(location, packageName);
spriteRenderer.material = material;
AssetsReference.Ref(material, spriteRenderer.gameObject);
}
else
{
_resourceService.LoadAsset<Material>(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<Material>(location, packageName);
meshRenderer.material = needInstance ? Object.Instantiate(material) : material;
AssetsReference.Ref(material, meshRenderer.gameObject);
}
else
{
_resourceService.LoadAsset<Material>(location, material =>
{
if (meshRenderer == null || meshRenderer.gameObject == null)
{
_resourceService.UnloadAsset(material);
material = null;
SetMeshMaterial(meshRenderer, material, needInstance);
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<Material>(location, packageName);
meshRenderer.sharedMaterial = material;
AssetsReference.Ref(material, meshRenderer.gameObject);
}
else
{
_resourceService.LoadAsset<Material>(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

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2
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 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)

View File

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

View File

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

View File

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

View File

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

View File

@ -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; }
/// <summary>
/// 自动释放资源引用计数为0的资源包
/// 自动释放资源引用计数。。的资源包
/// </summary>
public bool AutoUnloadBundleWhenUnused { get; set; } = false;
@ -60,7 +61,6 @@ namespace AlicizaX.Resource.Runtime
public int FailedTryAgain { get; set; }
#region internal
/// <summary>
@ -76,39 +76,89 @@ namespace AlicizaX.Resource.Runtime
/// <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>
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 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
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<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)
{
Log.Error($"ResourceSystem has already init package : {packageName}");
Log.Error(ZString.Format("ResourceSystem has already init package : {0}", packageName));
return new UniTask<bool>(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;
}
/// <summary>
/// 获取当前资源包版本。
/// </summary>
@ -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);
}
/// <summary>
/// 向网络端请求并更新清
/// 向网络端请求并更新清。。
/// </summary>
/// <param name="packageVersion">更新的包裹版</param>
/// <param name="packageVersion">更新的包裹版。。</param>
/// <param name="timeout">超时时间默认值60秒</param>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
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);
}
/// <summary>
/// 创建资源下载器,用于下载当前资源版本所有的资源包文件。
/// </summary>
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
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);
}
/// <summary>
@ -266,9 +299,7 @@ namespace AlicizaX.Resource.Runtime
/// <param name="customPackageName">指定资源包的名称。不传使用默认资源包</param>
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,12 +399,10 @@ namespace AlicizaX.Resource.Runtime
{
return YooAssets.IsNeedDownloadFromRemote(location);
}
else
{
var package = YooAssets.GetPackage(packageName);
return package.IsNeedDownloadFromRemote(location);
}
}
/// <summary>
/// 是否需要从远端更新下载。
@ -364,12 +415,10 @@ namespace AlicizaX.Resource.Runtime
{
return YooAssets.IsNeedDownloadFromRemote(assetInfo);
}
else
{
var package = YooAssets.GetPackage(packageName);
return package.IsNeedDownloadFromRemote(assetInfo);
}
}
/// <summary>
/// 获取资源信息列表。
@ -383,12 +432,10 @@ namespace AlicizaX.Resource.Runtime
{
return YooAssets.GetAssetInfos(tag);
}
else
{
var package = YooAssets.GetPackage(packageName);
return package.GetAssetInfos(tag);
}
}
/// <summary>
/// 获取资源信息列表。
@ -402,12 +449,10 @@ namespace AlicizaX.Resource.Runtime
{
return YooAssets.GetAssetInfos(tags);
}
else
{
var package = YooAssets.GetPackage(packageName);
return package.GetAssetInfos(tags);
}
}
/// <summary>
/// 获取资源信息。
@ -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<T>(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<T>(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<GameObject>(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,23 +707,15 @@ 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);
}
}
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))
{
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)
loadAssetCallbacks.LoadAssetUpdateCallback, userData);
if (asset == null)
{
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);
string errorMessage = ZString.Format("Can not load asset '{0}'.", location);
loadAssetCallbacks.LoadAssetFailureCallback?.Invoke(location, LoadResourceStatus.NotReady, errorMessage, userData);
return;
}
throw;
}
loadAssetCallbacks.LoadAssetSuccessCallback?.Invoke(location, asset, Time.time - duration, userData);
}
/// <summary>
@ -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)
loadAssetCallbacks.LoadAssetUpdateCallback, userData);
if (asset == null)
{
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);
string errorMessage = ZString.Format("Can not load asset '{0}'.", location);
loadAssetCallbacks.LoadAssetFailureCallback?.Invoke(location, LoadResourceStatus.NotReady, errorMessage, userData);
return;
}
throw;
}
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<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)
{
cancellationToken.ThrowIfCancellationRequested();
AssetObject cachedAssetObject = _assetPool.Spawn(assetObjectKey);
if (cachedAssetObject != null)
{
@ -939,16 +960,25 @@ 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 (cancellationToken.IsCancellationRequested)
{
DisposeHandle(handle);
FailLoading(assetObjectKey, null);
return null;
}
await UniTask.Yield();
}
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);
@ -956,23 +986,16 @@ namespace AlicizaX.Resource.Runtime
CompleteLoading(assetObjectKey);
return handle.AssetObject as UnityEngine.Object;
}
catch (Exception ex)
{
DisposeHandle(handle);
FailLoading(assetObjectKey, ex);
throw;
}
}
}
private bool TryBeginLoading(string assetObjectKey)
{
if (_assetLoadingOperations.ContainsKey(assetObjectKey))
if (_assetLoadingOperations.TryGetValue(assetObjectKey, out _))
{
return false;
}
_assetLoadingOperations.Add(assetObjectKey, new UniTaskCompletionSource<bool>());
_assetLoadingOperations.Add(assetObjectKey, AutoResetUniTaskCompletionSource<bool>.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
/// <summary>
/// 设置下载系统参数,自定义下载请求。
/// </summary>
/// <param name="downloadSystemUnityWebRequest">自定义下载器的请求委托<see cref="UnityWebRequestDelegate"/></param>
/// <param name="downloadSystemUnityWebRequest">自定义下载器的请求委托<see cref="UnityWebRequestDelegate"/></param>
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