215 lines
5.3 KiB
C#
215 lines
5.3 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
|
||
using UnityEngine;
|
||
|
||
namespace XericUI.XTable.Rendering.Elements
|
||
{
|
||
/// <summary>
|
||
/// 基于计数器的槽位对象池(泛型)。
|
||
/// 每个 cellId 映射到固定索引的 SlotCounter,槽位在计数器间通过 FrozenSlots 流转复用。
|
||
/// 避免 Queue 模式下对象与位置解耦导致的堆叠问题,同时不触发 SetActive/SetParent 等生命周期事件。
|
||
/// </summary>
|
||
/// <typeparam name="T">池对象类型</typeparam>
|
||
public class SlotCounterPool<T> where T : class
|
||
{
|
||
#region 内部类型
|
||
|
||
private class Slot
|
||
{
|
||
/// <summary>当前绑定的单元格 ID(-1 = 空闲,可被偷取)</summary>
|
||
public int CellId = -1;
|
||
/// <summary>池对象引用</summary>
|
||
public T PooledObject;
|
||
}
|
||
|
||
private class SlotCounter
|
||
{
|
||
public List<Slot> Slots = new List<Slot>(capacity: 4);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 字段
|
||
|
||
/// <summary>计数器数组大小(= 池上限,也是 cellId 取模的模数)</summary>
|
||
public const int POOL_SIZE = 256;
|
||
|
||
private SlotCounter[] m_Counters = new SlotCounter[POOL_SIZE];
|
||
|
||
/// <summary>冻结槽位列表——全局可偷取的休眠槽位(末尾偷取,避免 Canvas 重建)</summary>
|
||
private List<Slot> m_FrozenSlots = new List<Slot>();
|
||
|
||
/// <summary>已创建对象总数(不含预制体 Instantiate)</summary>
|
||
private int m_CreatedCount;
|
||
|
||
/// <summary>对象工厂(构造函数传入)</summary>
|
||
private Func<T> m_Factory;
|
||
|
||
/// <summary>已创建对象总数(只读)</summary>
|
||
public int CreatedCount => m_CreatedCount;
|
||
|
||
#endregion
|
||
|
||
#region 构造
|
||
|
||
public SlotCounterPool(Func<T> factory)
|
||
{
|
||
m_Factory = factory ?? throw new ArgumentNullException(nameof(factory));
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 池操作
|
||
|
||
/// <summary>
|
||
/// 为指定 cellId 获取一个池对象。
|
||
/// 优先复用本计数器内的空闲槽位,其次从 FrozenSlots 偷取,最后创建新对象。
|
||
/// </summary>
|
||
public T Acquire(int cellId)
|
||
{
|
||
int index = cellId % POOL_SIZE;
|
||
if (index < 0) index += POOL_SIZE; // 处理负 cellId
|
||
|
||
var counter = m_Counters[index];
|
||
if (counter == null)
|
||
{
|
||
counter = new SlotCounter();
|
||
m_Counters[index] = counter;
|
||
}
|
||
|
||
// 1. 在计数器内查找空闲槽位
|
||
for (int i = 0; i < counter.Slots.Count; i++)
|
||
{
|
||
if (counter.Slots[i].CellId == -1)
|
||
{
|
||
counter.Slots[i].CellId = cellId;
|
||
return counter.Slots[i].PooledObject;
|
||
}
|
||
}
|
||
|
||
// 2. 从 FrozenSlots 末尾偷取
|
||
if (m_FrozenSlots.Count > 0)
|
||
{
|
||
int lastIdx = m_FrozenSlots.Count - 1;
|
||
var stolen = m_FrozenSlots[lastIdx];
|
||
m_FrozenSlots.RemoveAt(lastIdx);
|
||
stolen.CellId = cellId;
|
||
counter.Slots.Add(stolen);
|
||
return stolen.PooledObject;
|
||
}
|
||
|
||
// 3. 创建新对象
|
||
if (m_CreatedCount < POOL_SIZE)
|
||
{
|
||
var obj = m_Factory();
|
||
var slot = new Slot { CellId = cellId, PooledObject = obj };
|
||
counter.Slots.Add(slot);
|
||
m_CreatedCount++;
|
||
return obj;
|
||
}
|
||
|
||
// 4. 已达上限
|
||
Debug.LogWarning($"[SlotCounterPool<{typeof(T).Name}>] 已达上限 {POOL_SIZE},无法分配新对象");
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放指定 cellId 占用的槽位,将其移入 FrozenSlots 供其他计数器复用。
|
||
/// </summary>
|
||
public void Release(int cellId)
|
||
{
|
||
int index = cellId % POOL_SIZE;
|
||
if (index < 0) index += POOL_SIZE;
|
||
|
||
var counter = m_Counters[index];
|
||
if (counter == null) return;
|
||
|
||
for (int i = 0; i < counter.Slots.Count; i++)
|
||
{
|
||
if (counter.Slots[i].CellId == cellId)
|
||
{
|
||
var slot = counter.Slots[i];
|
||
counter.Slots.RemoveAt(i);
|
||
slot.CellId = -1;
|
||
m_FrozenSlots.Add(slot);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试通过池对象引用查找并释放对应槽位。
|
||
/// 用于 Release(PooledObject) 场景,需要遍历查找。
|
||
/// </summary>
|
||
public bool ReleaseByObject(T pooledObject)
|
||
{
|
||
if (pooledObject == null) return false;
|
||
|
||
// 先遍历所有计数器的活跃槽位
|
||
for (int ci = 0; ci < POOL_SIZE; ci++)
|
||
{
|
||
var counter = m_Counters[ci];
|
||
if (counter == null) continue;
|
||
|
||
for (int i = 0; i < counter.Slots.Count; i++)
|
||
{
|
||
if (ReferenceEquals(counter.Slots[i].PooledObject, pooledObject))
|
||
{
|
||
var slot = counter.Slots[i];
|
||
counter.Slots.RemoveAt(i);
|
||
slot.CellId = -1;
|
||
m_FrozenSlots.Add(slot);
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>将所有活跃槽位移入 FrozenSlots</summary>
|
||
public void ReturnAll()
|
||
{
|
||
for (int ci = 0; ci < POOL_SIZE; ci++)
|
||
{
|
||
var counter = m_Counters[ci];
|
||
if (counter == null) continue;
|
||
|
||
for (int i = counter.Slots.Count - 1; i >= 0; i--)
|
||
{
|
||
var slot = counter.Slots[i];
|
||
counter.Slots.RemoveAt(i);
|
||
slot.CellId = -1;
|
||
m_FrozenSlots.Add(slot);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 彻底销毁所有池对象(包括计数器内活跃槽位和 FrozenSlots 中的冻结槽位)。
|
||
/// </summary>
|
||
public void DestroyAll(Action<T> destroyFunc)
|
||
{
|
||
if (destroyFunc == null) return;
|
||
|
||
for (int ci = 0; ci < POOL_SIZE; ci++)
|
||
{
|
||
var counter = m_Counters[ci];
|
||
if (counter == null) continue;
|
||
|
||
foreach (var slot in counter.Slots)
|
||
destroyFunc(slot.PooledObject);
|
||
counter.Slots.Clear();
|
||
}
|
||
|
||
foreach (var slot in m_FrozenSlots)
|
||
destroyFunc(slot.PooledObject);
|
||
m_FrozenSlots.Clear();
|
||
|
||
m_CreatedCount = 0;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|