df23868b40
fix: update spawn NPC.
271 lines
7.8 KiB
C#
271 lines
7.8 KiB
C#
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<GameObject> _idleInstances = new();
|
|
private readonly HashSet<GameObject> _activeInstances = new();
|
|
private readonly HashSet<GameObject> _knownInstances = new();
|
|
private readonly Dictionary<GameObject, int> _spawnVersions = new();
|
|
|
|
private GameObject _prefab;
|
|
private Task<GameObject> _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<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();
|
|
ReleasePrefabAsset();
|
|
}
|
|
|
|
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()
|
|
{
|
|
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<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();
|
|
}
|
|
}
|
|
}
|
|
}
|