优化对象池释放逻辑,计数器仅作为对象池复用封装

This commit is contained in:
2026-07-30 08:26:11 +08:00
parent 541053c170
commit 353cedcf1c
9 changed files with 459 additions and 304 deletions
@@ -105,6 +105,7 @@ namespace XericUI.XTable.Rendering.Elements
if (parent != null && item.Root.transform.parent != parent)
item.Root.transform.SetParent(parent, false);
item.CellId = cellId;
item.Root.SetActive(true);
item.Root.name = name;
ResetImageDefaults(item);
BindStyle(item.Style, styleNamespace);
@@ -121,6 +122,7 @@ namespace XericUI.XTable.Rendering.Elements
if (parent != null && item.Root.transform.parent != parent)
item.Root.transform.SetParent(parent, false);
item.CellId = cellId;
item.Root.SetActive(true);
item.Root.name = name;
ResetTextDefaults(item);
BindStyle(item.Style, styleNamespace);
@@ -148,30 +150,41 @@ namespace XericUI.XTable.Rendering.Elements
OnStyleChangeDetected?.Invoke();
}
/// <summary>归还所有槽位至 FrozenSlots(全量刷新时调用)</summary>
/// <summary>归还所有槽位(全量刷新时调用)。被归还对象会隐藏,直到下次 Acquire。</summary>
public void ReturnAll()
{
// Unbind 所有活跃槽位的样式回调
m_ImagePool.ReturnAll();
m_TextPool.ReturnAll();
m_ImagePool.ReturnAll(HideImage);
m_TextPool.ReturnAll(HideText);
}
/// <summary>归还单个 Image 池对象(通过 CellId 定位槽位并移入 FrozenSlots)</summary>
/// <summary>归还单个 Image 池对象并隐藏。</summary>
public void ReleaseImage(PooledImage item)
{
if (item == null) return;
item.Style?.Unbind();
HideImage(item);
m_ImagePool.ReleaseByObject(item);
}
/// <summary>归还单个 Text 池对象(通过 CellId 定位槽位并移入 FrozenSlots)</summary>
/// <summary>归还单个 Text 池对象并隐藏。</summary>
public void ReleaseText(PooledText item)
{
if (item == null) return;
item.Style?.Unbind();
HideText(item);
m_TextPool.ReleaseByObject(item);
}
private static void HideImage(PooledImage item)
{
item.Style?.Unbind();
item.Root.SetActive(false);
}
private static void HideText(PooledText item)
{
item.Style?.Unbind();
item.Root.SetActive(false);
}
#endregion
#region 默认值重置
@@ -1,195 +1,133 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace XericUI.XTable.Rendering.Elements
{
/// <summary>
/// 预制体对象池——按原型分组管理,每个原型使用计数器槽位绑定 cellId 到预制体实例。
/// 预制体不参与 FrozenSlots 流转:始终绑定在 cellId 槽位上,仅在表格销毁时释放。
/// </summary>
public class PrefabPrototypePool
{
#region 内部类型
/// <summary>按原型管理的预制体池。占用槽位绑定 cellId,归还后隐藏并留在同原型待命池中复用。</summary>
public class PrefabPrototypePool
{
private class PrefabPool
{
public readonly Dictionary<int, GameObject> ActiveSlots = new();
public readonly Stack<GameObject> Standby = new();
}
private class PrefabPool
{
public GameObject Prototype;
/// <summary>cellId → 预制体实例 的计数器槽位绑定</summary>
public Dictionary<int, GameObject> Slots = new Dictionary<int, GameObject>();
}
private readonly Dictionary<GameObject, PrefabPool> m_Pools = new();
private readonly Transform m_Parent;
private const int MAX_PREFAB_ITEMS = 256;
private int m_TotalCreated;
private const HideFlags POOL_FLAGS = HideFlags.DontSave | HideFlags.HideInHierarchy;
private static readonly Vector2 AnchorTopLeft = new(0, 1);
private static readonly Vector2 PivotTopLeft = new(0, 1);
#endregion
public PrefabPrototypePool(Transform parent) => m_Parent = parent;
#region 字段
public GameObject Acquire(GameObject prefab, int cellId, Transform parent = null)
{
if (prefab == null) return null;
PrefabPool pool = GetOrCreatePool(prefab);
Transform targetParent = parent ?? m_Parent;
if (pool.ActiveSlots.TryGetValue(cellId, out GameObject existing))
{
Prepare(existing, targetParent, cellId);
return existing;
}
private Dictionary<GameObject, PrefabPool> m_Pools = new Dictionary<GameObject, PrefabPool>();
private Transform m_Parent;
GameObject instance = pool.Standby.Count > 0 ? pool.Standby.Pop() : Create(prefab, targetParent);
if (instance == null) return null;
pool.ActiveSlots[cellId] = instance;
Prepare(instance, targetParent, cellId);
return instance;
}
/// <summary>渲染项目总数上限(全局,跨所有原型)</summary>
private const int MAX_PREFAB_ITEMS = 256;
private int m_TotalCreated;
public void ReturnAll(Action<GameObject> beforeRelease = null)
{
foreach (PrefabPool pool in m_Pools.Values)
{
foreach (GameObject instance in pool.ActiveSlots.Values)
{
beforeRelease?.Invoke(instance);
instance.SetActive(false);
pool.Standby.Push(instance);
}
pool.ActiveSlots.Clear();
}
}
private const HideFlags POOL_FLAGS = HideFlags.DontSave | HideFlags.HideInHierarchy;
public bool Release(GameObject instance, Action<GameObject> beforeRelease = null)
{
if (instance == null) return false;
foreach (PrefabPool pool in m_Pools.Values)
{
int foundCellId = -1;
foreach (var pair in pool.ActiveSlots)
{
if (pair.Value != instance) continue;
foundCellId = pair.Key;
break;
}
if (foundCellId < 0) continue;
beforeRelease?.Invoke(instance);
pool.ActiveSlots.Remove(foundCellId);
instance.SetActive(false);
pool.Standby.Push(instance);
return true;
}
return false;
}
private static readonly Vector2 AnchorTopLeft = new Vector2(0, 1);
private static readonly Vector2 PivotTopLeft = new Vector2(0, 1);
public void DestroyAll()
{
foreach (PrefabPool pool in m_Pools.Values)
{
foreach (GameObject instance in pool.ActiveSlots.Values) KillObject(instance);
foreach (GameObject instance in pool.Standby) KillObject(instance);
}
m_Pools.Clear();
m_TotalCreated = 0;
}
#endregion
private PrefabPool GetOrCreatePool(GameObject prefab)
{
if (!m_Pools.TryGetValue(prefab, out PrefabPool pool))
{
pool = new PrefabPool();
m_Pools.Add(prefab, pool);
}
return pool;
}
#region 构造 / 销毁
private GameObject Create(GameObject prefab, Transform parent)
{
if (m_TotalCreated >= MAX_PREFAB_ITEMS)
{
Debug.LogWarning($"[XericUIActionTable] Prefab 池已达上限 {MAX_PREFAB_ITEMS},无法分配新实例");
return null;
}
GameObject instance = UnityEngine.Object.Instantiate(prefab, parent);
instance.hideFlags = POOL_FLAGS;
m_TotalCreated++;
return instance;
}
public PrefabPrototypePool(Transform parent)
{
m_Parent = parent;
}
private static void Prepare(GameObject instance, Transform parent, int cellId)
{
if (parent != null && instance.transform.parent != parent)
instance.transform.SetParent(parent, false);
instance.name = $"_prefab_{cellId}";
RectTransform rt = instance.GetComponent<RectTransform>() ?? instance.AddComponent<RectTransform>();
rt.anchorMin = AnchorTopLeft;
rt.anchorMax = AnchorTopLeft;
rt.pivot = PivotTopLeft;
instance.SetActive(true);
}
/// <summary>彻底销毁所有池对象(所有原型的活跃槽位)</summary>
public void DestroyAll()
{
foreach (var kv in m_Pools)
{
var pool = kv.Value;
foreach (var instance in pool.Slots.Values)
KillObject(instance);
pool.Slots.Clear();
}
m_Pools.Clear();
m_TotalCreated = 0;
}
private static void KillObject(GameObject go)
{
if (go == null) return;
if (Application.isPlaying)
Object.Destroy(go);
else
Object.DestroyImmediate(go);
}
#endregion
#region 池操作
/// <summary>
/// 为指定 cellId 获取预制体实例。
/// 若该 cellId 已有绑定的实例,直接返回;否则创建新实例(需未达上限)。
/// </summary>
public GameObject Acquire(GameObject prefab, int cellId, Transform parent = null)
{
if (prefab == null) return null;
var pool = GetOrCreatePool(prefab);
Transform targetParent = parent ?? m_Parent;
// 已绑定 → 直接复用并迁移到当前渲染区域。
if (pool.Slots.TryGetValue(cellId, out var existing))
{
if (targetParent != null && existing.transform.parent != targetParent)
existing.transform.SetParent(targetParent, false);
return existing;
}
// 创建新实例
if (m_TotalCreated >= MAX_PREFAB_ITEMS)
{
Debug.LogWarning($"[XericUIActionTable] Prefab 池已达上限 {MAX_PREFAB_ITEMS},无法分配新实例");
return null;
}
var instance = Object.Instantiate(prefab, targetParent);
instance.hideFlags = POOL_FLAGS;
instance.name = $"_prefab_{cellId}";
m_TotalCreated++;
// 强制 RectTransform 左上角坐标系
RectTransform rt = instance.GetComponent<RectTransform>();
if (rt == null) rt = instance.AddComponent<RectTransform>();
rt.anchorMin = AnchorTopLeft;
rt.anchorMax = AnchorTopLeft;
rt.pivot = PivotTopLeft;
pool.Slots[cellId] = instance;
return instance;
}
/// <summary>
/// 归还指定实例至槽位(仅从槽位移除 + 销毁对象,不在滚动时调用)。
/// 预制体的回收仅发生在表格销毁时。
/// </summary>
public void Release(GameObject instance)
{
if (instance == null) return;
foreach (var kv in m_Pools)
{
var pool = kv.Value;
// 查找并移除
int? foundKey = null;
foreach (var slot in pool.Slots)
{
if (slot.Value == instance)
{
foundKey = slot.Key;
break;
}
}
if (foundKey.HasValue)
{
pool.Slots.Remove(foundKey.Value);
m_TotalCreated--;
KillObject(instance);
return;
}
}
// 不属于任何池(异常情况),直接销毁
KillObject(instance);
}
/// <summary>销毁所有按 cellId 绑定的实例,并在销毁前通知调用方。</summary>
public void ReleaseAll(System.Action<GameObject> beforeRelease = null)
{
foreach (var pool in m_Pools.Values)
{
foreach (var instance in pool.Slots.Values)
{
beforeRelease?.Invoke(instance);
KillObject(instance);
}
pool.Slots.Clear();
}
m_Pools.Clear();
m_TotalCreated = 0;
}
/// <summary>结束当前渲染周期。实例保留在全局槽位中,以便跨区域复用。</summary>
public void ReturnAll()
{
}
/// <summary>获取当前所有活跃实例(用于外部遍历回调)</summary>
public IReadOnlyCollection<GameObject> GetActive(GameObject prefab)
{
if (prefab == null || !m_Pools.TryGetValue(prefab, out var pool))
return System.Array.Empty<GameObject>();
return pool.Slots.Values;
}
#endregion
#region 内部方法
private PrefabPool GetOrCreatePool(GameObject prefab)
{
if (!m_Pools.TryGetValue(prefab, out var pool))
{
pool = new PrefabPool { Prototype = prefab };
m_Pools[prefab] = pool;
}
return pool;
}
#endregion
}
private static void KillObject(GameObject go)
{
if (go == null) return;
if (Application.isPlaying) UnityEngine.Object.Destroy(go);
else UnityEngine.Object.DestroyImmediate(go);
}
}
}
@@ -147,15 +147,20 @@ namespace XericUI.XTable.Rendering.Elements
return false;
}
/// <summary>释放所有活跃槽位,但保留其所属计数器。</summary>
public void ReturnAll()
/// <summary>释放所有活跃槽位;回调在槽位变为空闲前执行。</summary>
public void ReturnAll(Action<T> 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++)
counter.Slots[i].CellId = -1;
{
var slot = counter.Slots[i];
if (slot.CellId == -1) continue;
beforeRelease?.Invoke(slot.PooledObject);
slot.CellId = -1;
}
}
}