优化资源模块性能

缓存命中的异步加载不再额外 Yield 一帧
改成按进度增量触发,避免每帧无意义 delegate 调用
减少了高频换图时的小对象分配
This commit is contained in:
陈思海 2026-03-24 19:05:41 +08:00
parent 3d6caf4028
commit 76cc128c4f
4 changed files with 137 additions and 78 deletions

View File

@ -14,10 +14,14 @@ namespace AlicizaX.Resource.Runtime
private class LoadingState : IMemory
{
public CancellationTokenSource Cts { get; set; }
public CancellationTokenRegistration Registration { get; set; }
public string Location { get; set; }
public void Clear()
{
Registration.Dispose();
Registration = default;
if (Cts != null)
{
Cts.Cancel();
@ -25,7 +29,7 @@ namespace AlicizaX.Resource.Runtime
Cts = null;
}
Location = String.Empty;
Location = string.Empty;
}
}
@ -43,35 +47,26 @@ namespace AlicizaX.Resource.Runtime
{
return;
}
UnityEngine.Object assetObject = asset as UnityEngine.Object;
if (assetObject != null)
if (assetObject == null)
{
// 检查资源是否仍然是当前需要的。
if (IsCurrentLocation(setAssetObject.TargetObject, setAssetObject.Location))
{
ClearLoadingState(setAssetObject.TargetObject);
Log.Error($"Load failure asset type is {asset?.GetType()}.");
return;
}
SetAsset(setAssetObject, TrackLoadedAsset(setAssetObject.Location, assetObject));
}
else
{
// 资源已经过期,卸载。
_resourceModule.UnloadAsset(assetObject);
}
if (IsCurrentLocation(setAssetObject.TargetObject, setAssetObject.Location))
{
ClearLoadingState(setAssetObject.TargetObject);
SetAsset(setAssetObject, TrackLoadedAsset(setAssetObject.Location, assetObject));
}
else
{
Log.Error($"Load failure asset type is {asset?.GetType()}.");
_resourceModule.UnloadAsset(assetObject);
}
}
/// <summary>
/// 通过Unity对象加载资源。
/// </summary>
/// <param name="setAssetObject">ISetAssetObject。</param>
/// <typeparam name="T">Unity对象类型。</typeparam>
public async UniTaskVoid SetAssetByResources<T>(ISetAssetObject setAssetObject,CancellationToken cancellationToken) where T : UnityEngine.Object
public async UniTaskVoid SetAssetByResources<T>(ISetAssetObject setAssetObject, CancellationToken cancellationToken) where T : UnityEngine.Object
{
var target = setAssetObject.TargetObject;
var location = setAssetObject.Location;
@ -81,27 +76,29 @@ namespace AlicizaX.Resource.Runtime
return;
}
// 取消并清理旧的加载请求。
CancelAndCleanupOldRequest(target);
// 创建新的加载状态
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var linkedTokenSource = new CancellationTokenSource();
var loadingState = MemoryPool.Acquire<LoadingState>();
loadingState.Cts = linkedTokenSource;
if (cancellationToken.CanBeCanceled)
{
loadingState.Registration = cancellationToken.Register(static state =>
{
((CancellationTokenSource)state).Cancel();
}, linkedTokenSource);
}
loadingState.Location = location;
_loadingStates[target] = loadingState;
try
{
// 等待其他可能正在进行的加载。
// 再次检查是否被新请求替换。
if (!IsCurrentLocation(target, location))
{
return;
}
// 检查缓存。
if (_assetItemPool.CanSpawn(location))
{
ClearLoadingState(target);
@ -110,36 +107,24 @@ namespace AlicizaX.Resource.Runtime
SetAsset(setAssetObject, assetObject);
return;
}
else
if (!IsCurrentLocation(target, location))
{
// 最后一次检查是否被替换。
if (!IsCurrentLocation(target, location))
{
return;
}
// 防止重复加载同一资源。
if (!IsCurrentLocation(target, location))
{
// 已经在加载中,等待回调处理。
return;
}
T resource = await _resourceModule.LoadAssetAsync<T>(location, linkedTokenSource.Token);
if (resource == null)
{
ClearLoadingState(target);
return;
}
OnLoadAssetSuccess(location, resource, 0f, setAssetObject);
return;
}
T resource = await _resourceModule.LoadAssetAsync<T>(location, linkedTokenSource.Token);
if (resource == null)
{
ClearLoadingState(target);
return;
}
OnLoadAssetSuccess(location, resource, 0f, setAssetObject);
}
catch (OperationCanceledException)
{
ClearLoadingState(target);
// 请求被取消,正常情况,无需处理。
}
catch (Exception ex)
{
@ -148,10 +133,6 @@ namespace AlicizaX.Resource.Runtime
}
}
/// <summary>
/// 取消并清理旧的加载请求。
/// <param name="target">Unity对象。</param>
/// </summary>
private void CancelAndCleanupOldRequest(UnityEngine.Object target)
{
if (_loadingStates.TryGetValue(target, out var oldState))
@ -161,10 +142,6 @@ namespace AlicizaX.Resource.Runtime
}
}
/// <summary>
/// 清理加载状态。
/// <param name="target">Unity对象。</param>
/// </summary>
private void ClearLoadingState(UnityEngine.Object target)
{
if (_loadingStates.TryGetValue(target, out var state))
@ -174,15 +151,13 @@ namespace AlicizaX.Resource.Runtime
}
}
/// <summary>
/// 检查指定位置是否仍是该目标的当前加载位置。
/// </summary>
private bool IsCurrentLocation(UnityEngine.Object target, string location)
{
if (target == null)
{
return false;
}
return _loadingStates.TryGetValue(target, out var state) && state.Location == location;
}
@ -199,11 +174,10 @@ namespace AlicizaX.Resource.Runtime
return assetObject;
}
/// <summary>
/// 组件销毁时清理所有资源。
/// </summary>
private void OnDestroy()
{
ReleaseTrackedAssets();
foreach (var state in _loadingStates.Values)
{
MemoryPool.Release(state);

View File

@ -47,6 +47,8 @@ namespace AlicizaX.Resource.Runtime
/// </summary>
private LinkedList<LoadAssetObject> _loadAssetObjectsLinkedList;
private Dictionary<Object, LinkedListNode<LoadAssetObject>> _trackedAssetNodes;
/// <summary>
/// 散图集合对象池。
/// </summary>
@ -69,6 +71,7 @@ namespace AlicizaX.Resource.Runtime
"SetAssetPool",
autoReleaseInterval, 16, 60, 0);
_loadAssetObjectsLinkedList = new LinkedList<LoadAssetObject>();
_trackedAssetNodes = new Dictionary<Object, LinkedListNode<LoadAssetObject>>(16);
InitializedResources();
}
@ -113,9 +116,7 @@ namespace AlicizaX.Resource.Runtime
if (current.Value.AssetObject.IsCanRelease())
{
_assetItemPool.Unspawn(current.Value.AssetTarget);
MemoryPool.Release(current.Value.AssetObject);
_loadAssetObjectsLinkedList.Remove(current);
RemoveTrackedNode(current, releaseAsset: true);
}
current = next;
@ -134,8 +135,78 @@ namespace AlicizaX.Resource.Runtime
private void SetAsset(ISetAssetObject setAssetObject, Object assetObject)
{
_loadAssetObjectsLinkedList.AddLast(new LoadAssetObject(setAssetObject, assetObject));
ReplaceTrackedAsset(setAssetObject.TargetObject);
var node = _loadAssetObjectsLinkedList.AddLast(new LoadAssetObject(setAssetObject, assetObject));
if (setAssetObject.TargetObject != null)
{
_trackedAssetNodes[setAssetObject.TargetObject] = node;
}
setAssetObject.SetAsset(assetObject);
}
private void ReplaceTrackedAsset(Object target)
{
if (target == null || _trackedAssetNodes == null)
{
return;
}
if (_trackedAssetNodes.TryGetValue(target, out var existingNode))
{
if (_currentProcessNode == existingNode)
{
_currentProcessNode = existingNode.Next;
}
RemoveTrackedNode(existingNode, releaseAsset: true);
}
}
private void RemoveTrackedNode(LinkedListNode<LoadAssetObject> node, bool releaseAsset)
{
if (node == null)
{
return;
}
var trackedObject = node.Value.AssetObject?.TargetObject;
if (trackedObject != null && _trackedAssetNodes != null)
{
_trackedAssetNodes.Remove(trackedObject);
}
if (releaseAsset && node.Value.AssetTarget != null && _assetItemPool != null)
{
_assetItemPool.Unspawn(node.Value.AssetTarget);
}
if (node.Value.AssetObject != null)
{
MemoryPool.Release(node.Value.AssetObject);
}
_loadAssetObjectsLinkedList?.Remove(node);
}
private void ReleaseTrackedAssets()
{
if (_loadAssetObjectsLinkedList == null)
{
return;
}
var current = _loadAssetObjectsLinkedList.First;
while (current != null)
{
var next = current.Next;
RemoveTrackedNode(current, releaseAsset: true);
current = next;
}
_currentProcessNode = null;
_trackedAssetNodes?.Clear();
}
}
}

View File

@ -28,12 +28,16 @@ namespace AlicizaX.Resource.Runtime
private float _lastUnloadUnusedAssetsOperationElapseSeconds = 0f;
private float _lastGCCollectElapseSeconds = float.MaxValue;
[SerializeField] private float minUnloadUnusedAssetsInterval = 60f;
[SerializeField] private float maxUnloadUnusedAssetsInterval = 300f;
[SerializeField] private bool useSystemUnloadUnusedAssets = true;
[SerializeField] private float minGCCollectInterval = 30f;
[SerializeField] private string decryptionServices = "";
/// <summary>
@ -239,6 +243,7 @@ namespace AlicizaX.Resource.Runtime
private void Update()
{
_lastUnloadUnusedAssetsOperationElapseSeconds += Time.unscaledDeltaTime;
_lastGCCollectElapseSeconds += Time.unscaledDeltaTime;
if (_asyncOperation == null && (_forceUnloadUnusedAssets || _lastUnloadUnusedAssetsOperationElapseSeconds >= maxUnloadUnusedAssetsInterval ||
_preorderUnloadUnusedAssets && _lastUnloadUnusedAssetsOperationElapseSeconds >= minUnloadUnusedAssetsInterval))
{
@ -252,9 +257,7 @@ namespace AlicizaX.Resource.Runtime
if (_asyncOperation == null && _performGCCollect)
{
Log.Info("GC.Collect...");
_performGCCollect = false;
GC.Collect();
TryCollectGarbage();
}
if (_asyncOperation is { isDone: true })
@ -262,13 +265,24 @@ namespace AlicizaX.Resource.Runtime
_asyncOperation = null;
if (_performGCCollect)
{
Log.Info("GC.Collect...");
_performGCCollect = false;
GC.Collect();
TryCollectGarbage();
}
}
}
private void TryCollectGarbage()
{
if (_lastGCCollectElapseSeconds < minGCCollectInterval)
{
return;
}
Log.Info("GC.Collect...");
_performGCCollect = false;
_lastGCCollectElapseSeconds = 0f;
GC.Collect();
}
private void OnLowMemory()
{
Log.Warning("Low memory reported...");

View File

@ -693,7 +693,7 @@ namespace AlicizaX.Resource.Runtime
string assetObjectKey = GetCacheKey(location, packageName);
var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey, cancellationToken);
var asset = await GetOrLoadAssetAsync(location, typeof(T), packageName, assetObjectKey, cancellationToken: cancellationToken);
return asset as T;
}
@ -712,7 +712,7 @@ namespace AlicizaX.Resource.Runtime
string assetObjectKey = GetCacheKey(location, packageName);
var asset = await GetOrLoadAssetAsync(location, typeof(GameObject), packageName, assetObjectKey, cancellationToken);
var asset = await GetOrLoadAssetAsync(location, typeof(GameObject), packageName, assetObjectKey, cancellationToken: cancellationToken);
return asset != null ? AssetsReference.Instantiate(asset as GameObject, parent, this).gameObject : null;
}