多轮审查和修复
This commit is contained in:
@@ -11,10 +11,8 @@ namespace BaseGames.Core.Pool
|
||||
/// 先调用 <see cref="WarmupAsync"/> 预热后,再通过 <see cref="Spawn"/> 取出对象。
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-800)]
|
||||
public class GlobalObjectPool : MonoBehaviour
|
||||
public class GlobalObjectPool : MonoBehaviour, IObjectPoolService
|
||||
{
|
||||
public static GlobalObjectPool Instance { get; private set; }
|
||||
|
||||
[System.Serializable]
|
||||
public struct PoolConfig
|
||||
{
|
||||
@@ -26,29 +24,28 @@ namespace BaseGames.Core.Pool
|
||||
|
||||
[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 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 (Instance != null) { Destroy(gameObject); return; }
|
||||
Instance = this;
|
||||
if (ServiceLocator.GetOrDefault<IObjectPoolService>() != null) { Destroy(gameObject); return; }
|
||||
ServiceLocator.Register<IObjectPoolService>(this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ServiceLocator.Unregister<IObjectPoolService>(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>
|
||||
/// <summary>
|
||||
/// 异步预热所有配置中的对象。
|
||||
/// MonoBehaviour 中可用 <c>StartCoroutine(pool.WarmupAsync().AsIEnumerator())</c> 桥接,
|
||||
/// 或直接 await(UniTask / Awaitable)。
|
||||
/// </summary>
|
||||
public async Task WarmupAsync()
|
||||
{
|
||||
foreach (var cfg in _warmupConfigs)
|
||||
@@ -58,17 +55,6 @@ namespace BaseGames.Core.Pool
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -81,7 +67,9 @@ namespace BaseGames.Core.Pool
|
||||
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>();
|
||||
// MaxCount==0 表示无上限,无需追踪活跃对象,跳过 _alive 分配
|
||||
if (_maxCounts.GetValueOrDefault(key, 0) > 0 && !_alive.ContainsKey(key))
|
||||
_alive[key] = new LinkedList<PooledObject>();
|
||||
}
|
||||
|
||||
private void EnqueueNew(string key, GameObject prefab)
|
||||
@@ -105,23 +93,25 @@ namespace BaseGames.Core.Pool
|
||||
{
|
||||
if (!_pools.TryGetValue(key, out var queue))
|
||||
{
|
||||
Debug.LogError($"[GlobalObjectPool] '{key}' 未预热,请先调用 WarmupAsync/WarmupCoroutine。");
|
||||
Debug.LogError($"[GlobalObjectPool] '{key}' 未预热,请先调用 WarmupAsync。");
|
||||
return null;
|
||||
}
|
||||
|
||||
PooledObject po;
|
||||
var aliveList = GetAliveList(key);
|
||||
int maxCount = _maxCounts.GetValueOrDefault(key, 0);
|
||||
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 (maxCount > 0 && aliveList.Count >= maxCount)
|
||||
else if (aliveList != null && aliveList.Count >= maxCount)
|
||||
{
|
||||
// 已达上限:LRU 回收最早 Spawn 的活跃对象
|
||||
po = aliveList[0];
|
||||
aliveList.RemoveAt(0);
|
||||
// 已达上限:LRU 回收最早 Spawn 的活跃对象(LinkedList 头节点即最老)
|
||||
po = aliveList.First.Value;
|
||||
aliveList.RemoveFirst(); // O(1)
|
||||
po.AliveNode = null;
|
||||
po.ForceReturnToPool();
|
||||
Debug.LogWarning($"[GlobalObjectPool] '{key}' 已达 MaxCount={maxCount},LRU 回收中。");
|
||||
}
|
||||
@@ -143,22 +133,30 @@ namespace BaseGames.Core.Pool
|
||||
po.transform.SetPositionAndRotation(pos, rot);
|
||||
po.gameObject.SetActive(true);
|
||||
po.OnSpawn();
|
||||
aliveList.Add(po);
|
||||
// 存储节点引用,供 Despawn 时 O(1) 移除
|
||||
if (aliveList != null)
|
||||
po.AliveNode = aliveList.AddLast(po); // 尾部 = 最新,头部 = 最老(LRU)
|
||||
return po;
|
||||
}
|
||||
|
||||
// ── Despawn ───────────────────────────────────────────────────────
|
||||
public void Despawn(string key, PooledObject po)
|
||||
{
|
||||
var aliveList = GetAliveList(key);
|
||||
aliveList.Remove(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 + aliveList.Count >= maxCount)
|
||||
if (maxCount > 0 && queueSize + aliveCount >= maxCount)
|
||||
{
|
||||
Destroy(po.gameObject);
|
||||
return;
|
||||
@@ -204,10 +202,10 @@ namespace BaseGames.Core.Pool
|
||||
}
|
||||
}
|
||||
|
||||
private List<PooledObject> GetAliveList(string key)
|
||||
private LinkedList<PooledObject> GetAliveList(string key)
|
||||
{
|
||||
if (!_alive.TryGetValue(key, out var list))
|
||||
_alive[key] = list = new List<PooledObject>();
|
||||
_alive[key] = list = new LinkedList<PooledObject>();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ namespace BaseGames.Core.Pool
|
||||
public string AddressKey { get; private set; }
|
||||
private GlobalObjectPool _pool;
|
||||
|
||||
/// <summary>
|
||||
/// 对象在 GlobalObjectPool._alive 链表中的节点引用。
|
||||
/// 存储节点可将 Despawn 时的链表移除从 O(n) 降为 O(1)。
|
||||
/// 仅 GlobalObjectPool 内部读写,业务代码不应访问。
|
||||
/// </summary>
|
||||
internal LinkedListNode<PooledObject> AliveNode;
|
||||
|
||||
// 组件缓存(避免反复 GetComponent)
|
||||
private readonly Dictionary<Type, Component> _componentCache = new();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user