Files
zeling_v2/Assets/Scripts/Core/Pool/GlobalObjectPool.cs
2026-05-08 11:04:00 +08:00

215 lines
8.4 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
{
public static GlobalObjectPool Instance { get; private set; }
[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, List<PooledObject>> _alive = new();
private readonly Dictionary<string, GameObject> _prefabCache = new();
private readonly Dictionary<string, int> _maxCounts = new();
private void Awake()
{
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
}
// ── 预热 ──────────────────────────────────────────────────────────
/// <summary>在场景加载完成后StartCoroutine调用预热。</summary>
public IEnumerator WarmupCoroutine()
{
foreach (var cfg in _warmupConfigs)
{
_maxCounts[cfg.AddressKey] = cfg.MaxCount;
yield return WarmupSingleCoroutine(cfg.AddressKey, cfg.InitialCount);
}
}
/// <summary>async Task 版本(可 await供非 MonoBehaviour 调用)。</summary>
public async Task WarmupAsync()
{
foreach (var cfg in _warmupConfigs)
{
_maxCounts[cfg.AddressKey] = cfg.MaxCount;
await WarmupSingleAsync(cfg.AddressKey, cfg.InitialCount);
}
}
private IEnumerator WarmupSingleCoroutine(string key, int count)
{
var loadOp = Addressables.LoadAssetAsync<GameObject>(key);
yield return loadOp;
var prefab = loadOp.Result;
_prefabCache[key] = prefab;
EnsureCollections(key, count);
for (int i = 0; i < count; i++)
EnqueueNew(key, prefab);
}
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);
if (!_alive.ContainsKey(key)) _alive[key] = new List<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/WarmupCoroutine。");
return null;
}
PooledObject po;
var aliveList = GetAliveList(key);
int maxCount = _maxCounts.GetValueOrDefault(key, 0);
if (queue.Count > 0)
{
po = queue.Dequeue();
}
else if (maxCount > 0 && aliveList.Count >= maxCount)
{
// 已达上限LRU 回收最早 Spawn 的活跃对象
po = aliveList[0];
aliveList.RemoveAt(0);
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();
aliveList.Add(po);
return po;
}
// ── Despawn ───────────────────────────────────────────────────────
public void Despawn(string key, PooledObject po)
{
var aliveList = GetAliveList(key);
aliveList.Remove(po);
po.gameObject.SetActive(false);
po.OnDespawn();
int maxCount = _maxCounts.GetValueOrDefault(key, 0);
int queueSize = _pools.TryGetValue(key, out var queue) ? queue.Count : 0;
if (maxCount > 0 && queueSize + aliveList.Count >= 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 List<PooledObject> GetAliveList(string key)
{
if (!_alive.TryGetValue(key, out var list))
_alive[key] = list = new List<PooledObject>();
return list;
}
}
}