Files
zeling_v2/Assets/Scripts/Core/Pool/GlobalObjectPool.cs
2026-05-12 15:34:08 +08:00

213 lines
8.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace BaseGames.Core.Pool
{
/// <summary>
/// 全局对象池Persistent 场景)。
/// 先调用 <see cref="WarmupAsync"/> 预热后,再通过 <see cref="Spawn"/> 取出对象。
/// </summary>
[DefaultExecutionOrder(-800)]
public class GlobalObjectPool : MonoBehaviour, IObjectPoolService
{
[System.Serializable]
public struct PoolConfig
{
public string AddressKey;
public int InitialCount;
/// <summary>0 = 无上限;> 0 强制限制池中 + 活跃对象总数。</summary>
public int MaxCount;
}
[SerializeField] private PoolConfig[] _warmupConfigs;
private readonly Dictionary<string, Queue<PooledObject>> _pools = new();
private readonly Dictionary<string, LinkedList<PooledObject>> _alive = new();
private readonly Dictionary<string, GameObject> _prefabCache = new();
private readonly Dictionary<string, int> _maxCounts = new();
private void Awake()
{
if (ServiceLocator.GetOrDefault<IObjectPoolService>() != null) { Destroy(gameObject); return; }
ServiceLocator.Register<IObjectPoolService>(this);
}
private void OnDestroy()
{
ServiceLocator.Unregister<IObjectPoolService>(this);
}
// ── 预热 ──────────────────────────────────────────────────────────
/// <summary>
/// 异步预热所有配置中的对象。
/// MonoBehaviour 中可用 <c>StartCoroutine(pool.WarmupAsync().AsIEnumerator())</c> 桥接,
/// 或直接 awaitUniTask / Awaitable
/// </summary>
public async Task WarmupAsync()
{
foreach (var cfg in _warmupConfigs)
{
_maxCounts[cfg.AddressKey] = cfg.MaxCount;
await WarmupSingleAsync(cfg.AddressKey, cfg.InitialCount);
}
}
private async Task WarmupSingleAsync(string key, int count)
{
var prefab = await Addressables.LoadAssetAsync<GameObject>(key).Task;
_prefabCache[key] = prefab;
EnsureCollections(key, count);
for (int i = 0; i < count; i++)
EnqueueNew(key, prefab);
}
private void EnsureCollections(string key, int capacity)
{
if (!_pools.ContainsKey(key)) _pools[key] = new Queue<PooledObject>(capacity);
// MaxCount==0 表示无上限,无需追踪活跃对象,跳过 _alive 分配
if (_maxCounts.GetValueOrDefault(key, 0) > 0 && !_alive.ContainsKey(key))
_alive[key] = new LinkedList<PooledObject>();
}
private void EnqueueNew(string key, GameObject prefab)
{
var go = Instantiate(prefab);
var po = go.GetComponent<PooledObject>();
if (po == null) po = go.AddComponent<PooledObject>();
po.Setup(key, this);
go.SetActive(false);
_pools[key].Enqueue(po);
}
// ── Spawn ─────────────────────────────────────────────────────────
public T Spawn<T>(string key, Vector3 position, Quaternion rotation) where T : Component
=> SpawnInternal(key, position, rotation)?.GetComponentCached<T>();
public GameObject Spawn(string key, Vector3 position, Quaternion rotation)
=> SpawnInternal(key, position, rotation)?.gameObject;
private PooledObject SpawnInternal(string key, Vector3 pos, Quaternion rot)
{
if (!_pools.TryGetValue(key, out var queue))
{
Debug.LogError($"[GlobalObjectPool] '{key}' 未预热,请先调用 WarmupAsync。");
return null;
}
PooledObject po;
int maxCount = _maxCounts.GetValueOrDefault(key, 0);
// maxCount==0 时不追踪活跃对象aliveList 保持 null
LinkedList<PooledObject> aliveList = maxCount > 0 ? GetAliveList(key) : null;
if (queue.Count > 0)
{
po = queue.Dequeue();
}
else if (aliveList != null && aliveList.Count >= maxCount)
{
// 已达上限LRU 回收最早 Spawn 的活跃对象LinkedList 头节点即最老)
po = aliveList.First.Value;
aliveList.RemoveFirst(); // O(1)
po.AliveNode = null;
po.ForceReturnToPool();
Debug.LogWarning($"[GlobalObjectPool] '{key}' 已达 MaxCount={maxCount}LRU 回收中。");
}
else
{
// 池空且未达上限:同步实例化,并在后台协程补池
if (!_prefabCache.TryGetValue(key, out var pfx))
{
Debug.LogError($"[GlobalObjectPool] '{key}' Prefab 未缓存。");
return null;
}
var go = Instantiate(pfx);
po = go.GetComponent<PooledObject>();
if (po == null) po = go.AddComponent<PooledObject>();
po.Setup(key, this);
StartCoroutine(BackgroundRefillCoroutine(key, 1));
}
po.transform.SetPositionAndRotation(pos, rot);
po.gameObject.SetActive(true);
po.OnSpawn();
// 存储节点引用,供 Despawn 时 O(1) 移除
if (aliveList != null)
po.AliveNode = aliveList.AddLast(po); // 尾部 = 最新,头部 = 最老LRU
return po;
}
// ── Despawn ───────────────────────────────────────────────────────
public void Despawn(string key, PooledObject po)
{
// 通过存储的节点 O(1) 移除,避免 LinkedList.Remove(value) 的 O(n) 遍历
if (po.AliveNode != null)
{
if (_alive.TryGetValue(key, out var aliveRef))
aliveRef.Remove(po.AliveNode);
po.AliveNode = null;
}
po.gameObject.SetActive(false);
po.OnDespawn();
int maxCount = _maxCounts.GetValueOrDefault(key, 0);
int queueSize = _pools.TryGetValue(key, out var queue) ? queue.Count : 0;
int aliveCount = (maxCount > 0 && _alive.TryGetValue(key, out var al)) ? al.Count : 0;
if (maxCount > 0 && queueSize + aliveCount >= maxCount)
{
Destroy(po.gameObject);
return;
}
if (queue == null) { _pools[key] = queue = new Queue<PooledObject>(); }
queue.Enqueue(po);
}
// ── 后台补池 ──────────────────────────────────────────────────────
private IEnumerator BackgroundRefillCoroutine(string key, int count)
{
if (!_prefabCache.TryGetValue(key, out var pfx)) yield break;
for (int i = 0; i < count; i++)
{
yield return null; // 下一帧
var go = Instantiate(pfx);
var po = go.GetComponent<PooledObject>();
if (po == null) po = go.AddComponent<PooledObject>();
po.Setup(key, this);
go.SetActive(false);
if (_pools.TryGetValue(key, out var q)) q.Enqueue(po);
}
}
// ── 清空 ──────────────────────────────────────────────────────────
public void ClearPool(string key)
{
if (_pools.TryGetValue(key, out var queue))
{
while (queue.Count > 0)
{
var po = queue.Dequeue();
if (po != null) Destroy(po.gameObject);
}
_pools.Remove(key);
}
_alive.Remove(key);
if (_prefabCache.TryGetValue(key, out var pfx))
{
Addressables.Release(pfx);
_prefabCache.Remove(key);
}
}
private LinkedList<PooledObject> GetAliveList(string key)
{
if (!_alive.TryGetValue(key, out var list))
_alive[key] = list = new LinkedList<PooledObject>();
return list;
}
}
}