using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; namespace BrewMonster.Scripts { public sealed class PoolManager : MonoSingleton { private readonly Dictionary _pools = new(); private readonly Dictionary _instanceToPool = new(); private AddressableManager _addressableManager; private Transform _poolContainer; protected override void Initialize() { base.Initialize(); _addressableManager = AddressableManager.Instance; _addressableManager.OnDispose += ReleaseAllPools; _poolContainer = new GameObject("Addressables Object Pools").transform; _poolContainer.SetParent(transform, false); } /// /// Spawns an Addressables prefab from its pool. The returned task completes after the prefab is loaded if needed. /// public async Task SpawnAsync( string addressableKey, Vector3 position, Quaternion rotation, float memoryReleaseTTL, float autoDespawnTime = 0f, Transform parent = null) { if (string.IsNullOrEmpty(addressableKey)) { BMLogger.LogError("PoolManager: Cannot spawn with a null or empty Addressables key."); return null; } ObjectPool pool = GetOrCreatePool(addressableKey, memoryReleaseTTL); pool.UpdateMemoryReleaseTTL(memoryReleaseTTL); GameObject instance = await pool.SpawnAsync(position, rotation, parent); if (instance == null) { return null; } _instanceToPool[instance] = pool; if (autoDespawnTime > 0f) { StartCoroutine(AutoDespawnAfter(pool, instance, pool.GetSpawnVersion(instance), autoDespawnTime)); } return instance; } /// /// Coroutine-friendly spawn API for callers that do not use async/await. /// public void Spawn( string addressableKey, Vector3 position, Quaternion rotation, float memoryReleaseTTL, float autoDespawnTime, Action onComplete, Transform parent = null) { StartCoroutine(SpawnRoutine(addressableKey, position, rotation, memoryReleaseTTL, autoDespawnTime, onComplete, parent)); } public bool Despawn(string addressableKey, GameObject instance) { if (string.IsNullOrEmpty(addressableKey) || instance == null) { return false; } if (!_pools.TryGetValue(addressableKey, out ObjectPool pool)) { return false; } bool despawned = pool.Despawn(instance); if (despawned) { _instanceToPool[instance] = pool; } return despawned; } public bool Despawn(GameObject instance) { if (instance == null || !_instanceToPool.TryGetValue(instance, out ObjectPool pool)) { return false; } return pool.Despawn(instance); } public bool TryGetPoolCounts(string addressableKey, out int activeCount, out int idleCount) { activeCount = 0; idleCount = 0; if (!_pools.TryGetValue(addressableKey, out ObjectPool pool)) { return false; } activeCount = pool.ActiveCount; idleCount = pool.IdleCount; return true; } public void ReleasePool(string addressableKey) { if (!_pools.TryGetValue(addressableKey, out ObjectPool pool)) { return; } UnregisterPoolInstances(pool); pool.ReleaseNow(); _pools.Remove(addressableKey); } public void ReleaseAllPools() { List pools = new(_pools.Values); for (int i = 0; i < pools.Count; i++) { pools[i].ReleaseNow(); } _pools.Clear(); _instanceToPool.Clear(); } internal void RemovePool(ObjectPool pool) { if (pool == null) { return; } UnregisterPoolInstances(pool); _pools.Remove(pool.AddressableKey); } private IEnumerator SpawnRoutine( string addressableKey, Vector3 position, Quaternion rotation, float memoryReleaseTTL, float autoDespawnTime, Action onComplete, Transform parent) { Task spawnTask = SpawnAsync(addressableKey, position, rotation, memoryReleaseTTL, autoDespawnTime, parent); while (!spawnTask.IsCompleted) { yield return null; } if (spawnTask.Exception != null) { BMLogger.LogError($"PoolManager: Spawn failed for '{addressableKey}': {spawnTask.Exception}"); onComplete?.Invoke(null); yield break; } onComplete?.Invoke(spawnTask.Result); } private IEnumerator AutoDespawnAfter(ObjectPool pool, GameObject instance, int spawnVersion, float autoDespawnTime) { yield return new WaitForSeconds(autoDespawnTime); if (pool != null && pool.IsActiveInstance(instance, spawnVersion)) { Despawn(instance); } } private ObjectPool GetOrCreatePool(string addressableKey, float memoryReleaseTTL) { if (_pools.TryGetValue(addressableKey, out ObjectPool pool)) { return pool; } Transform poolRoot = new GameObject(GetPoolRootName(addressableKey)).transform; poolRoot.SetParent(_poolContainer, false); pool = new ObjectPool(addressableKey, this, _addressableManager, poolRoot, memoryReleaseTTL); _pools[addressableKey] = pool; return pool; } private void UnregisterPoolInstances(ObjectPool pool) { foreach (GameObject instance in pool.GetKnownInstances()) { if (instance != null) { _instanceToPool.Remove(instance); } } } private static string GetPoolRootName(string addressableKey) { string name = addressableKey.Replace('\\', '/'); int lastSlash = name.LastIndexOf('/'); if (lastSlash >= 0 && lastSlash < name.Length - 1) { name = name.Substring(lastSlash + 1); } return $"Pool - {name}"; } protected override void OnDestroy() { if (_addressableManager != null) { _addressableManager.OnDispose -= ReleaseAllPools; } ReleaseAllPools(); base.OnDestroy(); } } }