using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; namespace BrewMonster.Scripts { internal sealed class ObjectPool { private readonly string _addressableKey; private readonly PoolManager _owner; private readonly AddressableManager _addressableManager; private readonly Transform _poolRoot; private readonly Stack _idleInstances = new(); private readonly HashSet _activeInstances = new(); private readonly HashSet _knownInstances = new(); private readonly Dictionary _spawnVersions = new(); private GameObject _prefab; private Task _loadTask; private Coroutine _releaseCoroutine; private float _memoryReleaseTTL; public ObjectPool( string addressableKey, PoolManager owner, AddressableManager addressableManager, Transform poolRoot, float memoryReleaseTTL) { _addressableKey = addressableKey; _owner = owner; _addressableManager = addressableManager; _poolRoot = poolRoot; _memoryReleaseTTL = Mathf.Max(0f, memoryReleaseTTL); } public string AddressableKey => _addressableKey; public int ActiveCount => _activeInstances.Count; public int IdleCount => _idleInstances.Count; public void UpdateMemoryReleaseTTL(float memoryReleaseTTL) { _memoryReleaseTTL = Mathf.Max(0f, memoryReleaseTTL); } public async Task SpawnAsync(Vector3 position, Quaternion rotation, Transform parent) { CancelReleaseCountdown(); GameObject instance = GetIdleInstance(); if (instance == null) { GameObject prefab = await LoadPrefabAsync(); if (prefab == null) { return null; } instance = Object.Instantiate(prefab); _knownInstances.Add(instance); _spawnVersions[instance] = 0; } _activeInstances.Add(instance); _spawnVersions[instance]++; instance.transform.SetParent(parent, true); instance.transform.SetPositionAndRotation(position, rotation); instance.SetActive(true); NotifyPoolablesSpawned(instance); return instance; } public bool Despawn(GameObject instance) { if (instance == null || !_activeInstances.Remove(instance)) { return false; } NotifyPoolablesDespawned(instance); _spawnVersions[instance]++; instance.SetActive(false); instance.transform.SetParent(_poolRoot, false); _idleInstances.Push(instance); if (_activeInstances.Count == 0) { StartReleaseCountdown(); } return true; } public bool IsActiveInstance(GameObject instance, int spawnVersion) { return instance != null && _activeInstances.Contains(instance) && _spawnVersions.TryGetValue(instance, out int currentVersion) && currentVersion == spawnVersion; } public int GetSpawnVersion(GameObject instance) { return instance != null && _spawnVersions.TryGetValue(instance, out int version) ? version : -1; } public IEnumerable GetKnownInstances() { return _knownInstances; } public void ReleaseNow() { CancelReleaseCountdown(); DestroyAllInstances(); ReleasePrefabAsset(); } private GameObject GetIdleInstance() { while (_idleInstances.Count > 0) { GameObject instance = _idleInstances.Pop(); if (instance != null) { return instance; } } return null; } private async Task LoadPrefabAsync() { if (_prefab != null) { return _prefab; } if (_loadTask != null) { return await _loadTask; } _loadTask = LoadPrefabInternalAsync(); GameObject prefab = await _loadTask; if (prefab == null) { _loadTask = null; } return prefab; } private async Task LoadPrefabInternalAsync() { if (_addressableManager == null) { BMLogger.LogError($"ObjectPool: AddressableManager is not available for '{_addressableKey}'."); return null; } await _addressableManager.WaitUntilInitializedAsync(); GameObject prefab = await _addressableManager.LoadPrefabAsync(_addressableKey); if (prefab == null) { BMLogger.LogError($"ObjectPool: Failed to load Addressable prefab '{_addressableKey}'."); return null; } _prefab = prefab; return _prefab; } private void StartReleaseCountdown() { CancelReleaseCountdown(); _releaseCoroutine = _owner.StartCoroutine(ReleaseMemoryCountdown()); } private void CancelReleaseCountdown() { if (_releaseCoroutine == null) { return; } _owner.StopCoroutine(_releaseCoroutine); _releaseCoroutine = null; } private IEnumerator ReleaseMemoryCountdown() { if (_memoryReleaseTTL > 0f) { yield return new WaitForSecondsRealtime(_memoryReleaseTTL); } _releaseCoroutine = null; if (_activeInstances.Count > 0) { yield break; } _owner.RemovePool(this); DestroyAllInstances(); ReleasePrefabAsset(); } private void DestroyAllInstances() { foreach (GameObject instance in _knownInstances) { if (instance == null) { continue; } if (_activeInstances.Contains(instance)) { NotifyPoolablesDespawned(instance); } Object.Destroy(instance); } _idleInstances.Clear(); _activeInstances.Clear(); _knownInstances.Clear(); _spawnVersions.Clear(); if (_poolRoot != null) { Object.Destroy(_poolRoot.gameObject); } } private void ReleasePrefabAsset() { if (_prefab != null && _addressableManager != null) { _addressableManager.ReleaseAsset(_addressableKey); } _prefab = null; _loadTask = null; } private static void NotifyPoolablesSpawned(GameObject instance) { IPoolable[] poolables = instance.GetComponentsInChildren(true); for (int i = 0; i < poolables.Length; i++) { poolables[i].OnSpawn(); } } private static void NotifyPoolablesDespawned(GameObject instance) { IPoolable[] poolables = instance.GetComponentsInChildren(true); for (int i = 0; i < poolables.Length; i++) { poolables[i].OnDespawn(); } } } }