diff --git a/Runtime/ABase/GameObjectPool/GameObjectPoolService.cs b/Runtime/ABase/GameObjectPool/GameObjectPoolService.cs index 42fce8c..3f3ea2a 100644 --- a/Runtime/ABase/GameObjectPool/GameObjectPoolService.cs +++ b/Runtime/ABase/GameObjectPool/GameObjectPoolService.cs @@ -9,6 +9,14 @@ namespace AlicizaX { public sealed class GameObjectPool : MonoServiceBehaviour { + private static readonly Comparison SnapshotComparer = (left, right) => + { + if (left == null && right == null) return 0; + if (left == null) return 1; + if (right == null) return -1; + int groupCompare = string.Compare(left.group, right.group, StringComparison.Ordinal); + return groupCompare != 0 ? groupCompare : string.Compare(left.assetPath, right.assetPath, StringComparison.Ordinal); + }; [Header("检查间隔")] public float checkInterval = 10f; @@ -25,6 +33,7 @@ namespace AlicizaX private readonly Dictionary _poolsByKey = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _resolvedConfigCache = new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _groupLoaderCache = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _ownersByObject = new Dictionary(); private readonly Dictionary _resourceLoaders = new Dictionary(); private readonly List _configs = new List(); @@ -36,6 +45,8 @@ namespace AlicizaX private Exception _initializationException; private float _lastCleanupTime; + private bool _isShuttingDown; + public bool IsReady => _initializationCompleted && _initializationException == null; protected override void OnServiceInitialize() @@ -202,15 +213,18 @@ namespace AlicizaX public void ClearAllPools() { + _isShuttingDown = true; foreach (RuntimePrefabPool pool in _poolsByKey.Values) { pool.Shutdown(); MemoryPool.Release(pool); } + _isShuttingDown = false; _poolsByKey.Clear(); _ownersByObject.Clear(); _resolvedConfigCache.Clear(); + _groupLoaderCache.Clear(); ReleaseDebugSnapshots(); } @@ -223,31 +237,7 @@ namespace AlicizaX _debugSnapshots.Add(pool.CreateSnapshot()); } - _debugSnapshots.Sort((left, right) => - { - if (left == null && right == null) - { - return 0; - } - - if (left == null) - { - return 1; - } - - if (right == null) - { - return -1; - } - - int groupCompare = string.Compare(left.group, right.group, StringComparison.Ordinal); - if (groupCompare != 0) - { - return groupCompare; - } - - return string.Compare(left.assetPath, right.assetPath, StringComparison.Ordinal); - }); + _debugSnapshots.Sort(SnapshotComparer); return _debugSnapshots; } @@ -264,7 +254,7 @@ namespace AlicizaX internal void UnregisterOwnedObject(GameObject gameObject) { - if (gameObject == null) + if (gameObject == null || _isShuttingDown) { return; } @@ -375,7 +365,7 @@ namespace AlicizaX { if (!_resourceLoaders.ContainsKey(PoolResourceLoaderType.AssetBundle)) { - _resourceLoaders[PoolResourceLoaderType.AssetBundle] = new AlicizaResourceLoader(); + _resourceLoaders[PoolResourceLoaderType.AssetBundle] = new AssetBundleResourceLoader(); } if (!_resourceLoaders.ContainsKey(PoolResourceLoaderType.Resources)) @@ -400,9 +390,11 @@ namespace AlicizaX { _configs.Clear(); _resolvedConfigCache.Clear(); + _groupLoaderCache.Clear(); + IResourceModule resourceModule = ModuleSystem.GetModule(); PoolConfigScriptableObject configAsset = - ModuleSystem.GetModule().LoadAsset(poolConfigPath); + resourceModule.LoadAsset(poolConfigPath); if (configAsset == null || configAsset.configs == null) { @@ -429,6 +421,17 @@ namespace AlicizaX _configs.Sort(PoolConfig.CompareByPriority); LogConfigWarnings(); + + for (int i = 0; i < _configs.Count; i++) + { + PoolConfig config = _configs[i]; + if (!_groupLoaderCache.ContainsKey(config.group)) + { + _groupLoaderCache[config.group] = config.resourceLoaderType; + } + } + + resourceModule.UnloadAsset(configAsset); } private async UniTask PrewarmConfiguredPoolsAsync(CancellationToken cancellationToken) @@ -460,7 +463,8 @@ namespace AlicizaX return null; } - string cacheKey = $"{group ?? string.Empty}|{assetPath}"; + bool hasGroup = !string.IsNullOrEmpty(group); + string cacheKey = hasGroup ? string.Concat(group, "|", assetPath) : assetPath; if (_resolvedConfigCache.TryGetValue(cacheKey, out PoolConfig cachedConfig)) { return cachedConfig; @@ -516,16 +520,10 @@ namespace AlicizaX private IResourceLoader GetDirectLoadResourceLoader(string group) { - if (!string.IsNullOrWhiteSpace(group)) + if (!string.IsNullOrWhiteSpace(group) && + _groupLoaderCache.TryGetValue(group, out PoolResourceLoaderType loaderType)) { - for (int i = 0; i < _configs.Count; i++) - { - PoolConfig config = _configs[i]; - if (string.Equals(config.group, group, StringComparison.Ordinal)) - { - return GetResourceLoader(config.resourceLoaderType); - } - } + return GetResourceLoader(loaderType); } return GetResourceLoader(DefaultDirectLoadResourceLoaderType); @@ -567,28 +565,14 @@ namespace AlicizaX private void LogConfigWarnings() { + var seen = new HashSet<(string group, string assetPath)>(); for (int i = 0; i < _configs.Count; i++) { - PoolConfig left = _configs[i]; - for (int j = i + 1; j < _configs.Count; j++) + PoolConfig config = _configs[i]; + var key = (config.group, config.assetPath); + if (!seen.Add(key)) { - PoolConfig right = _configs[j]; - if (!string.Equals(left.group, right.group, StringComparison.Ordinal)) - { - continue; - } - - if (left.matchMode != right.matchMode) - { - continue; - } - - if (!string.Equals(left.assetPath, right.assetPath, StringComparison.Ordinal)) - { - continue; - } - - Log.Warning($"Duplicate pool config detected: '{left.group}:{left.assetPath}'."); + Log.Warning($"Duplicate pool config detected: '{config.group}:{config.assetPath}'."); } } } diff --git a/Runtime/ABase/GameObjectPool/IResourceLoader.cs b/Runtime/ABase/GameObjectPool/IResourceLoader.cs index 85a7d36..4b91a48 100644 --- a/Runtime/ABase/GameObjectPool/IResourceLoader.cs +++ b/Runtime/ABase/GameObjectPool/IResourceLoader.cs @@ -56,11 +56,12 @@ namespace AlicizaX public void UnloadAsset(GameObject gameObject) { - Resources.UnloadAsset(gameObject); + // Resources.UnloadAsset cannot unload GameObjects. + // The prefab reference is nulled by the caller; Unity handles cleanup via UnloadUnusedAssets. } } - public class AlicizaResourceLoader : IResourceLoader + public class AssetBundleResourceLoader : IResourceLoader { private IResourceModule _resourceModule; diff --git a/Runtime/ABase/GameObjectPool/RuntimePoolModels.cs b/Runtime/ABase/GameObjectPool/RuntimePoolModels.cs index 4b9da0a..d9c295b 100644 --- a/Runtime/ABase/GameObjectPool/RuntimePoolModels.cs +++ b/Runtime/ABase/GameObjectPool/RuntimePoolModels.cs @@ -117,6 +117,14 @@ namespace AlicizaX internal sealed class RuntimePrefabPool : IMemory { + private static readonly Comparison InstanceSnapshotComparer = (left, right) => + { + if (left == null && right == null) return 0; + if (left == null) return 1; + if (right == null) return -1; + if (left.isActive != right.isActive) return left.isActive ? -1 : 1; + return string.Compare(left.instanceName, right.instanceName, StringComparison.Ordinal); + }; private PoolConfig _config; private string _assetPath; private IResourceLoader _loader; @@ -124,6 +132,7 @@ namespace AlicizaX private CancellationToken _shutdownToken; private Dictionary _instancesByObject; private LinkedList _inactiveInstances; + private List _shutdownBuffer; private Transform _root; private GameObject _prefab; @@ -135,6 +144,7 @@ namespace AlicizaX { _instancesByObject = new Dictionary(); _inactiveInstances = new LinkedList(); + _shutdownBuffer = new List(); } public void Initialize( @@ -221,12 +231,13 @@ namespace AlicizaX instance.LastReleaseTime = Time.time; _activeCount = Mathf.Max(0, _activeCount - 1); + gameObject.SetActive(false); + Transform transform = gameObject.transform; transform.SetParent(_root, false); transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; transform.localScale = Vector3.one; - gameObject.SetActive(false); instance.InactiveNode = _inactiveInstances.AddLast(instance); TouchPrefab(); @@ -296,41 +307,19 @@ namespace AlicizaX snapshot.instances.Add(instanceSnapshot); } - snapshot.instances.Sort((left, right) => - { - if (left == null && right == null) - { - return 0; - } - - if (left == null) - { - return 1; - } - - if (right == null) - { - return -1; - } - - if (left.isActive != right.isActive) - { - return left.isActive ? -1 : 1; - } - - return string.Compare(left.instanceName, right.instanceName, StringComparison.Ordinal); - }); + snapshot.instances.Sort(InstanceSnapshotComparer); return snapshot; } public void Shutdown() { - var instances = new List(_instancesByObject.Values); - foreach (RuntimePooledInstance instance in instances) + _shutdownBuffer.AddRange(_instancesByObject.Values); + foreach (RuntimePooledInstance instance in _shutdownBuffer) { DestroyInstance(instance); } + _shutdownBuffer.Clear(); _inactiveInstances.Clear(); _instancesByObject.Clear(); @@ -455,7 +444,7 @@ namespace AlicizaX GameObject gameObject = instance.GameObject; if (parent != null) { - gameObject.transform.SetParent(parent); + gameObject.transform.SetParent(parent, false); } gameObject.SetActive(true); @@ -566,6 +555,7 @@ namespace AlicizaX _shutdownToken = default; _instancesByObject.Clear(); _inactiveInstances.Clear(); + _shutdownBuffer.Clear(); _root = null; _prefab = null; _prefabLoadSource = null;