fix(resource-kit): 修复场景资源加载器生命周期与初始化时序

Made-with: Cursor
This commit is contained in:
2026-03-26 16:08:41 +08:00
parent 7291dfb031
commit 0ebbc9c341
6 changed files with 70 additions and 79 deletions
@@ -10,7 +10,6 @@ GameObject:
m_Component:
- component: {fileID: 4256475766480134775}
- component: {fileID: 667196269251774960}
- component: {fileID: 9119428735370279783}
m_Layer: 0
m_Name: Fix System Center
m_TagString: Untagged
@@ -46,16 +45,3 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
defaultState: 1
--- !u!114 &9119428735370279783
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 422366950737630991}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ef81dc021856ce1458b39c19b69cfc9b, type: 3}
m_Name:
m_EditorClassIdentifier:
isPersistent: 0
@@ -46,6 +46,10 @@ namespace AibisDream.FixSystem
{
// 获取组件索引
InitReference();
}
private void Start()
{
StartCoroutine(LoadMaterial());
}
@@ -15,37 +15,46 @@ namespace AibisDream.Framework
private static SceneResourceLoader _persistentLoader;
private static SceneResourceLoader _sceneLoader;
#region Loader Registration
#region Loader Management
public static void RegisterPersistentLoader(SceneResourceLoader loader)
/// <summary>
/// 创建场景级 Loader。由 SceneLoader 在场景加载完成后调用。
/// </summary>
public static void CreateSceneLoader()
{
if (_persistentLoader != null && _persistentLoader != loader)
Debug.LogWarning("[ResourceSystem] Replacing existing persistent loader.");
_persistentLoader = loader;
if (_sceneLoader != null)
{
Debug.LogWarning("[ResourceSystem] Previous scene loader exists, releasing it.");
_sceneLoader.Dispose();
}
_sceneLoader = new SceneResourceLoader(isPersistent: false);
Debug.Log("[ResourceSystem] Scene loader created.");
}
public static void UnregisterPersistentLoader(SceneResourceLoader loader)
/// <summary>
/// 释放场景级 Loader。由 SceneLoader 在场景卸载前调用。
/// </summary>
public static void ReleaseSceneLoader()
{
if (_persistentLoader == loader)
_persistentLoader = null;
}
public static void RegisterSceneLoader(SceneResourceLoader loader)
{
if (_sceneLoader != null && _sceneLoader != loader)
Debug.LogWarning("[ResourceSystem] Replacing existing scene loader. Was the previous scene properly unloaded?");
_sceneLoader = loader;
}
public static void UnregisterSceneLoader(SceneResourceLoader loader)
{
if (_sceneLoader == loader)
if (_sceneLoader != null)
{
int releasedCount = _sceneLoader.TotalHandleCount;
_sceneLoader.Dispose();
_sceneLoader = null;
Debug.Log($"[ResourceSystem] Scene loader released. {releasedCount} handles released.");
}
}
public static void UnregisterSceneLoader()
/// <summary>
/// 懒加载 Persistent Loader。首次调用 LoadPersistentAsync 时自动创建。
/// </summary>
private static void EnsurePersistentLoader()
{
_sceneLoader = null;
if (_persistentLoader == null)
{
_persistentLoader = new SceneResourceLoader(isPersistent: true);
Debug.Log("[ResourceSystem] Persistent loader created (lazy).");
}
}
#endregion
@@ -61,7 +70,7 @@ namespace AibisDream.Framework
{
if (_sceneLoader != null) return _sceneLoader;
if (_persistentLoader != null) return _persistentLoader;
Debug.LogError("[ResourceSystem] No loader registered. Ensure a SceneResourceLoader exists in the scene.");
Debug.LogError("[ResourceSystem] No loader available. Ensure SceneLoader.CreateSceneLoader() was called.");
return null;
}
}
@@ -82,14 +91,14 @@ namespace AibisDream.Framework
public static AsyncOperationHandle<T> LoadPersistentAsync<T>(string key) where T : Object
{
EnsureLoader(_persistentLoader, "persistent");
EnsurePersistentLoader();
return _persistentLoader.LoadAsync<T>(key);
}
public static void LoadPersistentAsync<T>(string key, Action<T> onComplete, Action<string> onError = null)
where T : Object
{
EnsureLoader(_persistentLoader, "persistent");
EnsurePersistentLoader();
_persistentLoader.LoadAsync(key, onComplete, onError);
}
@@ -99,14 +108,22 @@ namespace AibisDream.Framework
public static AsyncOperationHandle<T> LoadSceneAsync<T>(string key) where T : Object
{
EnsureLoader(_sceneLoader, "scene");
if (_sceneLoader == null)
{
Debug.LogError("[ResourceSystem] No scene loader. Ensure CreateSceneLoader() was called by SceneLoader.");
return default;
}
return _sceneLoader.LoadAsync<T>(key);
}
public static void LoadSceneAsync<T>(string key, Action<T> onComplete, Action<string> onError = null)
where T : Object
{
EnsureLoader(_sceneLoader, "scene");
if (_sceneLoader == null)
{
Debug.LogError("[ResourceSystem] No scene loader. Ensure CreateSceneLoader() was called by SceneLoader.");
return;
}
_sceneLoader.LoadAsync(key, onComplete, onError);
}
@@ -124,11 +141,5 @@ namespace AibisDream.Framework
=> ActiveLoader.EarlyRelease(handle);
#endregion
private static void EnsureLoader(SceneResourceLoader loader, string loaderName)
{
if (loader == null)
Debug.LogError($"[ResourceSystem] No {loaderName} loader registered.");
}
}
}
@@ -7,31 +7,24 @@ using Object = UnityEngine.Object;
namespace AibisDream.Framework
{
public class SceneResourceLoader : MonoBehaviour
public class SceneResourceLoader : IDisposable
{
[SerializeField] private bool isPersistent;
private readonly bool _isPersistent;
private readonly Dictionary<string, AsyncOperationHandle> _cache = new();
private readonly List<AsyncOperationHandle> _uncachedHandles = new();
private void Awake()
public SceneResourceLoader(bool isPersistent = false)
{
if (isPersistent)
ResourceSystem.RegisterPersistentLoader(this);
else
ResourceSystem.RegisterSceneLoader(this);
_isPersistent = isPersistent;
}
private void OnDestroy()
public void Dispose()
{
ReleaseAll();
if (isPersistent)
ResourceSystem.UnregisterPersistentLoader(this);
else
ResourceSystem.UnregisterSceneLoader(this);
}
public bool IsPersistent => _isPersistent;
/// <summary>
/// 异步加载资源(带缓存,同 Key 不重复加载)。
/// 返回的 Handle 可在协程中 yield return。
+8 -17
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using AibisDream.Framework;
using AibisDream.Kit;
@@ -65,19 +65,19 @@ namespace AibisDream
FixSystemCenter.SystemDic.Clear();
}
// 释放场景级资源(在卸载场景之前,避免 OnDestroy 顺序问题
ResourceSystem.CurrentSceneLoader?.ReleaseAll();
ResourceSystem.UnregisterSceneLoader();
// 释放场景级资源(在卸载场景之前)
ResourceSystem.ReleaseSceneLoader();
yield return Addressables.UnloadSceneAsync(_loadHandle);
}
yield return PreloadSceneAssets(_sceneToLoad);
// 显式创建 Scene Loader(在所有业务组件 Start 之前)
ResourceSystem.CreateSceneLoader();
_loadHandle = Addressables.LoadSceneAsync(_sceneToLoad, LoadSceneMode.Additive);
yield return _loadHandle;
// 场景中的 SceneResourceLoader 会在 Awake 中自动注册到 ResourceSystem
// 场景加载完成,通知各系统
EnumEventSystem.Global.Send(EventEnum.SceneLoad, _sceneToLoad);
_data.sceneName = _sceneToLoad;
UIManager.Instance.GetPanel<InfoPanel>().HideLoading();
@@ -97,8 +97,7 @@ namespace AibisDream
if (_loadHandle.IsValid())
{
ResourceSystem.CurrentSceneLoader?.ReleaseAll();
ResourceSystem.UnregisterSceneLoader();
ResourceSystem.ReleaseSceneLoader();
yield return Addressables.UnloadSceneAsync(_loadHandle);
_data.sceneName = null;
@@ -107,14 +106,6 @@ namespace AibisDream
_isLoading = false;
}
}
/// <summary>
/// 预加载目标场景所需的资源。后续 Label 配置完成后在此处填充具体逻辑。
/// </summary>
private IEnumerator PreloadSceneAssets(string targetScene)
{
yield break;
}
}
[Serializable]
+7 -1
View File
@@ -58,7 +58,13 @@ Timeline 等大资源应使用 `LoadUncachedAsync` 加载,播放完后通过 `
### 2.2 注意事项
**ActiveLoader 回退风险**`LoadAsync`(自动路由)在 Scene Loader 不存在时会静默回退到 Persistent Loader。这意味着在场景切换间隙(旧场景已卸载、新场景 `SceneResourceLoader.Awake` 尚未执行)调用 `LoadAsync`,资源会被加载到 Persistent Loader 中,**永远不会随场景卸载释放**。避免在此时间窗口内调用 `LoadAsync`;若有明确场景归属,优先使用 `LoadSceneAsync``LoadPersistentAsync`
**禁止在 Awake 中加载资源**`SceneResourceLoader``Awake` 中注册自身到 `ResourceSystem`,而 Unity 的 `Awake` 执行顺序是不确定的。如果在另一个组件的 `Awake` 中调用 `ResourceSystem.LoadAsync`,此时当前场景的 `SceneResourceLoader` 可能尚未完成注册,`ResourceSystem` 会因找不到 Scene Loader 而回退到 Persistent Loader 加载资源。这会导致:
1) 资源被错误地缓存到 Persistent Loader,永远不会随场景卸载释放;
2) 后续在协程中再次加载同 Key 资源时,由于缓存命中,返回的是 Persistent Loader 中的 Handle,进一步掩盖问题。
**解决方案**:所有资源加载必须在协程(如 `Start``IEnumerator` 流程)中执行,确保 `SceneResourceLoader.Awake()` 已完成执行。
- **ActiveLoader 回退风险**`LoadAsync`(自动路由)在 Scene Loader 不存在时会静默回退到 Persistent Loader。这意味着在场景切换间隙(旧场景已卸载、新场景 `SceneResourceLoader.Awake` 尚未执行)调用 `LoadAsync`,资源会被加载到 Persistent Loader 中,**永远不会随场景卸载释放**。避免在此时间窗口内调用 `LoadAsync`;若有明确场景归属,优先使用 `LoadSceneAsync``LoadPersistentAsync`
**同一 Key 只能以一种类型加载**`SceneResourceLoader` 的缓存以 Key 字符串为索引。如果先调用 `LoadAsync<Sprite>(key)` 再调用 `LoadAsync<Texture2D>(key)`,第二次会返回已缓存的 Handle 并尝试类型转换,可能导致 `InvalidCastException`。确保同一 Key 在整个场景生命周期内只以一种类型加载。