优化资源模块性能

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

View File

@ -47,6 +47,8 @@ namespace AlicizaX.Resource.Runtime
/// </summary> /// </summary>
private LinkedList<LoadAssetObject> _loadAssetObjectsLinkedList; private LinkedList<LoadAssetObject> _loadAssetObjectsLinkedList;
private Dictionary<Object, LinkedListNode<LoadAssetObject>> _trackedAssetNodes;
/// <summary> /// <summary>
/// 散图集合对象池。 /// 散图集合对象池。
/// </summary> /// </summary>
@ -69,6 +71,7 @@ namespace AlicizaX.Resource.Runtime
"SetAssetPool", "SetAssetPool",
autoReleaseInterval, 16, 60, 0); autoReleaseInterval, 16, 60, 0);
_loadAssetObjectsLinkedList = new LinkedList<LoadAssetObject>(); _loadAssetObjectsLinkedList = new LinkedList<LoadAssetObject>();
_trackedAssetNodes = new Dictionary<Object, LinkedListNode<LoadAssetObject>>(16);
InitializedResources(); InitializedResources();
} }
@ -113,9 +116,7 @@ namespace AlicizaX.Resource.Runtime
if (current.Value.AssetObject.IsCanRelease()) if (current.Value.AssetObject.IsCanRelease())
{ {
_assetItemPool.Unspawn(current.Value.AssetTarget); RemoveTrackedNode(current, releaseAsset: true);
MemoryPool.Release(current.Value.AssetObject);
_loadAssetObjectsLinkedList.Remove(current);
} }
current = next; current = next;
@ -134,8 +135,78 @@ namespace AlicizaX.Resource.Runtime
private void SetAsset(ISetAssetObject setAssetObject, Object assetObject) 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); 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 _lastUnloadUnusedAssetsOperationElapseSeconds = 0f;
private float _lastGCCollectElapseSeconds = float.MaxValue;
[SerializeField] private float minUnloadUnusedAssetsInterval = 60f; [SerializeField] private float minUnloadUnusedAssetsInterval = 60f;
[SerializeField] private float maxUnloadUnusedAssetsInterval = 300f; [SerializeField] private float maxUnloadUnusedAssetsInterval = 300f;
[SerializeField] private bool useSystemUnloadUnusedAssets = true; [SerializeField] private bool useSystemUnloadUnusedAssets = true;
[SerializeField] private float minGCCollectInterval = 30f;
[SerializeField] private string decryptionServices = ""; [SerializeField] private string decryptionServices = "";
/// <summary> /// <summary>
@ -239,6 +243,7 @@ namespace AlicizaX.Resource.Runtime
private void Update() private void Update()
{ {
_lastUnloadUnusedAssetsOperationElapseSeconds += Time.unscaledDeltaTime; _lastUnloadUnusedAssetsOperationElapseSeconds += Time.unscaledDeltaTime;
_lastGCCollectElapseSeconds += Time.unscaledDeltaTime;
if (_asyncOperation == null && (_forceUnloadUnusedAssets || _lastUnloadUnusedAssetsOperationElapseSeconds >= maxUnloadUnusedAssetsInterval || if (_asyncOperation == null && (_forceUnloadUnusedAssets || _lastUnloadUnusedAssetsOperationElapseSeconds >= maxUnloadUnusedAssetsInterval ||
_preorderUnloadUnusedAssets && _lastUnloadUnusedAssetsOperationElapseSeconds >= minUnloadUnusedAssetsInterval)) _preorderUnloadUnusedAssets && _lastUnloadUnusedAssetsOperationElapseSeconds >= minUnloadUnusedAssetsInterval))
{ {
@ -252,9 +257,7 @@ namespace AlicizaX.Resource.Runtime
if (_asyncOperation == null && _performGCCollect) if (_asyncOperation == null && _performGCCollect)
{ {
Log.Info("GC.Collect..."); TryCollectGarbage();
_performGCCollect = false;
GC.Collect();
} }
if (_asyncOperation is { isDone: true }) if (_asyncOperation is { isDone: true })
@ -262,13 +265,24 @@ namespace AlicizaX.Resource.Runtime
_asyncOperation = null; _asyncOperation = null;
if (_performGCCollect) if (_performGCCollect)
{ {
Log.Info("GC.Collect..."); TryCollectGarbage();
_performGCCollect = false;
GC.Collect();
} }
} }
} }
private void TryCollectGarbage()
{
if (_lastGCCollectElapseSeconds < minGCCollectInterval)
{
return;
}
Log.Info("GC.Collect...");
_performGCCollect = false;
_lastGCCollectElapseSeconds = 0f;
GC.Collect();
}
private void OnLowMemory() private void OnLowMemory()
{ {
Log.Warning("Low memory reported..."); Log.Warning("Low memory reported...");

View File

@ -693,7 +693,7 @@ namespace AlicizaX.Resource.Runtime
string assetObjectKey = GetCacheKey(location, packageName); 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; return asset as T;
} }
@ -712,7 +712,7 @@ namespace AlicizaX.Resource.Runtime
string assetObjectKey = GetCacheKey(location, packageName); 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; return asset != null ? AssetsReference.Instantiate(asset as GameObject, parent, this).gameObject : null;
} }