表格设置单元格为预制体的情况下不会刷新这个预制体,因为条件写反了。

This commit is contained in:
2026-07-30 14:58:12 +08:00
parent a5616b6a06
commit f7931d41f1
8 changed files with 272 additions and 223 deletions
@@ -7,8 +7,7 @@ namespace XericUI.XTable.Rendering.Elements
{
/// <summary>
/// 基于计数器的槽位对象池(泛型)。
/// 每个 cellId 映射到固定索引的 SlotCounter,释放槽位保留在原计数器内复用。
/// 避免跨计数器拆分碰撞组导致的对象位置冲突。
/// cellId 在绑定期间稳定地映射到同一槽位;计数器仅在全部槽位解除绑定后整体待命复用。
/// </summary>
/// <typeparam name="T">池对象类型</typeparam>
public class SlotCounterPool<T> where T : class
@@ -17,32 +16,30 @@ namespace XericUI.XTable.Rendering.Elements
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);
public readonly List<Slot> Slots = new List<Slot>(capacity: 4);
public int BoundSlotCount;
}
#endregion
#region 字段
/// <summary>计数器数组大小(= 池上限,也是 cellId 取模的模数)</summary>
/// <summary>计数器数组大小(= 池上限,也是 cellId 路由键的模数)</summary>
public const int POOL_SIZE = 256;
private SlotCounter[] m_Counters = new SlotCounter[POOL_SIZE];
/// <summary>已创建对象总数(不含预制体 Instantiate)</summary>
private readonly SlotCounter[] m_Counters = new SlotCounter[POOL_SIZE];
private readonly Stack<SlotCounter> m_StandbyCounters = new Stack<SlotCounter>();
private readonly Func<T> m_Factory;
private readonly Action<IReadOnlyList<T>> m_OnCounterBorrowed;
private readonly Action<IReadOnlyList<T>> m_OnCounterReturned;
private int m_CreatedCount;
/// <summary>对象工厂(构造函数传入)</summary>
private Func<T> m_Factory;
/// <summary>已创建对象总数(只读)</summary>
public int CreatedCount => m_CreatedCount;
@@ -50,106 +47,116 @@ namespace XericUI.XTable.Rendering.Elements
#region 构造
public SlotCounterPool(Func<T> factory)
public SlotCounterPool(Func<T> factory, Action<IReadOnlyList<T>> onCounterBorrowed = null,
Action<IReadOnlyList<T>> onCounterReturned = null)
{
m_Factory = factory ?? throw new ArgumentNullException(nameof(factory));
m_OnCounterBorrowed = onCounterBorrowed;
m_OnCounterReturned = onCounterReturned;
}
#endregion
#region 池操作
/// <summary>
/// 为指定 cellId 获取一个池对象。
/// 优先复用本计数器内的空闲槽位,最后创建新对象。
/// </summary>
public T Acquire(int cellId)
/// <summary>为指定 cellId 获取池对象,并指示是否建立了新槽位绑定。</summary>
public T Acquire(int cellId, out bool isNewBinding)
{
int index = cellId % POOL_SIZE;
if (index < 0) index += POOL_SIZE; // 处理负 cellId
int index = GetRouteIndex(cellId);
var counter = m_Counters[index];
bool borrowedCounter = false;
if (counter == null)
{
counter = new SlotCounter();
counter = m_StandbyCounters.Count > 0 ? m_StandbyCounters.Pop() : new SlotCounter();
m_Counters[index] = counter;
borrowedCounter = true;
}
// 1. 在计数器内查找空闲槽位
for (int i = 0; i < counter.Slots.Count; i++)
{
var slot = counter.Slots[i];
if (slot.CellId == cellId)
{
isNewBinding = false;
return slot.PooledObject;
}
}
Slot availableSlot = null;
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;
availableSlot = counter.Slots[i];
break;
}
}
// 2. 创建新对象。槽位不能跨计数器流转,避免破坏碰撞单元格的稳定关系。
if (m_CreatedCount < POOL_SIZE)
if (availableSlot == null)
{
var obj = m_Factory();
var slot = new Slot { CellId = cellId, PooledObject = obj };
counter.Slots.Add(slot);
if (m_CreatedCount >= POOL_SIZE)
{
if (borrowedCounter)
{
m_Counters[index] = null;
m_StandbyCounters.Push(counter);
}
Debug.LogWarning($"[SlotCounterPool<{typeof(T).Name}>] 已达上限 {POOL_SIZE},无法分配新对象");
isNewBinding = false;
return null;
}
availableSlot = new Slot { PooledObject = m_Factory() };
counter.Slots.Add(availableSlot);
m_CreatedCount++;
return obj;
}
// 4. 已达上限
Debug.LogWarning($"[SlotCounterPool<{typeof(T).Name}>] 已达上限 {POOL_SIZE},无法分配新对象");
return null;
availableSlot.CellId = cellId;
counter.BoundSlotCount++;
isNewBinding = true;
if (borrowedCounter)
m_OnCounterBorrowed?.Invoke(GetObjects(counter));
return availableSlot.PooledObject;
}
/// <summary>
/// 释放指定 cellId 占用的槽位,并保留在原计数器中供相同碰撞组复用。
/// </summary>
public void Release(int cellId)
/// <summary>为指定 cellId 获取一个池对象。</summary>
public T Acquire(int cellId)
{
int index = cellId % POOL_SIZE;
if (index < 0) index += POOL_SIZE;
return Acquire(cellId, out _);
}
/// <summary>解除 cellId 的槽位绑定;仅计数器完全空闲时才将其整体归还待命栈。</summary>
public bool ReleaseCell(int cellId)
{
int index = GetRouteIndex(cellId);
var counter = m_Counters[index];
if (counter == null) return;
if (counter == null) return false;
for (int i = 0; i < counter.Slots.Count; i++)
{
if (counter.Slots[i].CellId == cellId)
{
counter.Slots[i].CellId = -1;
return;
}
}
}
var slot = counter.Slots[i];
if (slot.CellId != cellId) continue;
/// <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))
{
counter.Slots[i].CellId = -1;
return true;
}
}
slot.CellId = -1;
counter.BoundSlotCount--;
if (counter.BoundSlotCount == 0)
ReturnCounter(index, counter);
return true;
}
return false;
}
/// <summary>释放所有活跃槽位;回调在槽位变为空闲前执行。</summary>
public void ReturnAll(Action<T> beforeRelease = null)
/// <summary>兼容旧 API:解除指定 cellId 的绑定。</summary>
public void Release(int cellId)
{
ReleaseCell(cellId);
}
/// <summary>通过池对象引用解除对应 cellId 的槽位绑定。</summary>
public bool ReleaseByObject(T pooledObject)
{
if (pooledObject == null) return false;
for (int ci = 0; ci < POOL_SIZE; ci++)
{
var counter = m_Counters[ci];
@@ -157,33 +164,81 @@ namespace XericUI.XTable.Rendering.Elements
for (int i = 0; i < counter.Slots.Count; i++)
{
var slot = counter.Slots[i];
if (slot.CellId == -1) continue;
beforeRelease?.Invoke(slot.PooledObject);
slot.CellId = -1;
if (slot.CellId != -1 && ReferenceEquals(slot.PooledObject, pooledObject))
return ReleaseCell(slot.CellId);
}
}
return false;
}
/// <summary>
/// 彻底销毁所有池对象。
/// </summary>
public void DestroyAll(Action<T> destroyFunc)
/// <summary>解除全部槽位绑定,并将所有计数器整体归还待命栈。</summary>
public void ReleaseAllBindings()
{
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();
for (int i = 0; i < counter.Slots.Count; i++)
counter.Slots[i].CellId = -1;
counter.BoundSlotCount = 0;
ReturnCounter(ci, counter);
}
}
/// <summary>兼容旧 API:解除所有活跃槽位绑定。</summary>
public void ReturnAll(Action<T> beforeRelease = null)
{
if (beforeRelease != null)
{
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 (counter.Slots[i].CellId != -1) beforeRelease(counter.Slots[i].PooledObject);
}
}
ReleaseAllBindings();
}
/// <summary>彻底销毁所有池对象。</summary>
public void DestroyAll(Action<T> destroyFunc)
{
if (destroyFunc == null) return;
var destroyedCounters = new HashSet<SlotCounter>();
for (int ci = 0; ci < POOL_SIZE; ci++)
{
var counter = m_Counters[ci];
if (counter != null) destroyedCounters.Add(counter);
m_Counters[ci] = null;
}
foreach (var counter in m_StandbyCounters) destroyedCounters.Add(counter);
m_StandbyCounters.Clear();
foreach (var counter in destroyedCounters)
foreach (var slot in counter.Slots) destroyFunc(slot.PooledObject);
m_CreatedCount = 0;
}
private static int GetRouteIndex(int cellId)
{
int index = cellId % POOL_SIZE;
return index < 0 ? index + POOL_SIZE : index;
}
private void ReturnCounter(int index, SlotCounter counter)
{
m_OnCounterReturned?.Invoke(GetObjects(counter));
m_Counters[index] = null;
m_StandbyCounters.Push(counter);
}
private static IReadOnlyList<T> GetObjects(SlotCounter counter)
{
var objects = new List<T>(counter.Slots.Count);
for (int i = 0; i < counter.Slots.Count; i++) objects.Add(counter.Slots[i].PooledObject);
return objects;
}
#endregion
}
}