Files
test/Assets/PerfectWorld/Scripts/PoolingManager/ObjectPool.cs
T
2026-04-28 15:55:45 +07:00

261 lines
7.6 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace BrewMonster.Scripts
{
internal sealed class ObjectPool
{
private readonly string _addressableKey;
private readonly PoolManager _owner;
private readonly Transform _poolRoot;
private readonly Stack<GameObject> _idleInstances = new();
private readonly HashSet<GameObject> _activeInstances = new();
private readonly HashSet<GameObject> _knownInstances = new();
private readonly Dictionary<GameObject, int> _spawnVersions = new();
private AsyncOperationHandle<GameObject> _prefabHandle;
private GameObject _prefab;
private Task<GameObject> _loadTask;
private Coroutine _releaseCoroutine;
private float _memoryReleaseTTL;
public ObjectPool(string addressableKey, PoolManager owner, Transform poolRoot, float memoryReleaseTTL)
{
_addressableKey = addressableKey;
_owner = owner;
_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<GameObject> 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<GameObject> GetKnownInstances()
{
return _knownInstances;
}
public void ReleaseNow()
{
CancelReleaseCountdown();
DestroyAllInstances();
ReleasePrefabHandle();
}
private GameObject GetIdleInstance()
{
while (_idleInstances.Count > 0)
{
GameObject instance = _idleInstances.Pop();
if (instance != null)
{
return instance;
}
}
return null;
}
private async Task<GameObject> 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<GameObject> LoadPrefabInternalAsync()
{
_prefabHandle = Addressables.LoadAssetAsync<GameObject>(_addressableKey);
await _prefabHandle.Task;
if (_prefabHandle.Status != AsyncOperationStatus.Succeeded || _prefabHandle.Result == null)
{
Debug.LogError($"ObjectPool: Failed to load Addressable prefab '{_addressableKey}'.");
ReleasePrefabHandle();
return null;
}
_prefab = _prefabHandle.Result;
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 WaitForSeconds(_memoryReleaseTTL);
}
_releaseCoroutine = null;
if (_activeInstances.Count > 0)
{
yield break;
}
_owner.RemovePool(this);
DestroyAllInstances();
ReleasePrefabHandle();
}
private void DestroyAllInstances()
{
foreach (GameObject instance in _knownInstances)
{
if (instance != null)
{
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 ReleasePrefabHandle()
{
if (_prefabHandle.IsValid())
{
Addressables.Release(_prefabHandle);
}
_prefabHandle = default;
_prefab = null;
_loadTask = null;
}
private static void NotifyPoolablesSpawned(GameObject instance)
{
IPoolable[] poolables = instance.GetComponentsInChildren<IPoolable>(true);
for (int i = 0; i < poolables.Length; i++)
{
poolables[i].OnSpawn();
}
}
private static void NotifyPoolablesDespawned(GameObject instance)
{
IPoolable[] poolables = instance.GetComponentsInChildren<IPoolable>(true);
for (int i = 0; i < poolables.Length; i++)
{
poolables[i].OnDespawn();
}
}
}
}