更换表格对象池系统,新的对象池系统会尽可能减小对象的回收操作,同时引入基于曼哈顿点距的对象池索引逻辑,引入计数器,避免野对象或对象冲突。
This commit is contained in:
@@ -229,11 +229,12 @@ namespace XericUI.XTable.Rendering.Component
|
||||
XTableCellData data = tableData?.GetCell(r, c);
|
||||
string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
|
||||
? data.StyleNamespace : defaultNs;
|
||||
int cellId = r * colWidths.Length + c;
|
||||
|
||||
// 选择高亮
|
||||
if (IsInSelectionRange != null && IsInSelectionRange(r, c))
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_multiselect");
|
||||
var selImg = m_Assembler.GetImage(cellId, "cell_multiselect");
|
||||
if (selImg != null)
|
||||
{
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
@@ -243,7 +244,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
else if (r == selRow && c == selCol)
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_select");
|
||||
var selImg = m_Assembler.GetImage(cellId, "cell_select");
|
||||
if (selImg != null)
|
||||
{
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
@@ -253,7 +254,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
|
||||
// 网格线
|
||||
var hLine = m_Assembler.GetImage("cell_hline");
|
||||
var hLine = m_Assembler.GetImage(cellId, "cell_hline");
|
||||
if (hLine != null)
|
||||
{
|
||||
hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH);
|
||||
@@ -261,7 +262,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
hLine.Image.color = borderColor;
|
||||
}
|
||||
|
||||
var vLine = m_Assembler.GetImage("cell_vline");
|
||||
var vLine = m_Assembler.GetImage(cellId, "cell_vline");
|
||||
if (vLine != null)
|
||||
{
|
||||
vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - borderWidth, cellY);
|
||||
@@ -276,7 +277,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
case CellContentType.Text:
|
||||
if (!string.IsNullOrEmpty(data.Text))
|
||||
{
|
||||
var txt = m_Assembler.GetText("cell_text", cellNS);
|
||||
var txt = m_Assembler.GetText(cellId, "cell_text", cellNS);
|
||||
if (txt != null)
|
||||
{
|
||||
txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
|
||||
@@ -296,7 +297,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
if (data.Image != null)
|
||||
{
|
||||
Sprite sprite = GetOrCreateSprite(data.Image);
|
||||
var img = m_Assembler.GetImage("cell_image", cellNS);
|
||||
var img = m_Assembler.GetImage(cellId, "cell_image", cellNS);
|
||||
if (img != null)
|
||||
{
|
||||
img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
|
||||
@@ -308,7 +309,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
case CellContentType.Prefab:
|
||||
if (data.Prefab != null && PrefabPool != null)
|
||||
{
|
||||
GameObject instance = PrefabPool.Acquire(data.Prefab, r, c);
|
||||
GameObject instance = PrefabPool.Acquire(data.Prefab, cellId);
|
||||
if (instance != null)
|
||||
{
|
||||
RectTransform prt = instance.GetComponent<RectTransform>();
|
||||
@@ -357,10 +358,11 @@ namespace XericUI.XTable.Rendering.Component
|
||||
XTableCellData data = tableData?.GetCell(r, c);
|
||||
string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
|
||||
? data.StyleNamespace : defaultNs;
|
||||
int cellId = r * colWidths.Length + c;
|
||||
|
||||
if (IsInSelectionRange != null && IsInSelectionRange(r, c))
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_multiselect");
|
||||
var selImg = m_Assembler.GetImage(cellId, "cell_multiselect");
|
||||
if (selImg != null)
|
||||
{
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
@@ -370,7 +372,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
else if (r == selRow && c == selCol)
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_select");
|
||||
var selImg = m_Assembler.GetImage(cellId, "cell_select");
|
||||
if (selImg != null)
|
||||
{
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
@@ -379,7 +381,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
}
|
||||
|
||||
var hLine = m_Assembler.GetImage("cell_hline");
|
||||
var hLine = m_Assembler.GetImage(cellId, "cell_hline");
|
||||
if (hLine != null)
|
||||
{
|
||||
hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH);
|
||||
@@ -387,7 +389,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
hLine.Image.color = Config.RuntimeBorderColor;
|
||||
}
|
||||
|
||||
var vLine = m_Assembler.GetImage("cell_vline");
|
||||
var vLine = m_Assembler.GetImage(cellId, "cell_vline");
|
||||
if (vLine != null)
|
||||
{
|
||||
vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - Config.RuntimeBorderWidth, cellY);
|
||||
@@ -402,7 +404,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
case CellContentType.Text:
|
||||
if (!string.IsNullOrEmpty(data.Text))
|
||||
{
|
||||
var txt = m_Assembler.GetText("cell_text", cellNS);
|
||||
var txt = m_Assembler.GetText(cellId, "cell_text", cellNS);
|
||||
if (txt != null)
|
||||
{
|
||||
txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
|
||||
@@ -415,7 +417,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
if (data.Image != null)
|
||||
{
|
||||
Sprite sprite = GetOrCreateSprite(data.Image);
|
||||
var img = m_Assembler.GetImage("cell_image", cellNS);
|
||||
var img = m_Assembler.GetImage(cellId, "cell_image", cellNS);
|
||||
if (img != null)
|
||||
{
|
||||
img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
|
||||
@@ -427,7 +429,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
case CellContentType.Prefab:
|
||||
if (data.Prefab != null && PrefabPool != null)
|
||||
{
|
||||
GameObject instance = PrefabPool.Acquire(data.Prefab, r, c);
|
||||
GameObject instance = PrefabPool.Acquire(data.Prefab, cellId);
|
||||
if (instance != null)
|
||||
{
|
||||
RectTransform prt = instance.GetComponent<RectTransform>();
|
||||
|
||||
@@ -154,14 +154,15 @@ namespace XericUI.XTable.Rendering.Component
|
||||
cellH += m_RowHeights[row + i];
|
||||
}
|
||||
|
||||
int cellId = row * m_ColWidths.Length + col;
|
||||
var objs = new CellTrackedObjs();
|
||||
|
||||
// ── 选择高亮 ──
|
||||
ApplySelectionHighlight(row, col, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
|
||||
ApplySelectionHighlight(cellId, row, col, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
|
||||
|
||||
// ── 网格线(仅完整刷新时)──
|
||||
if (withGridLines)
|
||||
ApplyGridLines(cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
|
||||
ApplyGridLines(cellId, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
|
||||
|
||||
// ── 单元格内容 ──
|
||||
if (data != null)
|
||||
@@ -169,13 +170,13 @@ namespace XericUI.XTable.Rendering.Component
|
||||
switch (data.ContentType)
|
||||
{
|
||||
case CellContentType.Text:
|
||||
RenderCellText(data, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
|
||||
RenderCellText(cellId, data, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
|
||||
break;
|
||||
case CellContentType.Image:
|
||||
RenderCellImage(data, cellX, cellY, cellW, cellH, cellNS, ref objs);
|
||||
RenderCellImage(cellId, data, cellX, cellY, cellW, cellH, cellNS, ref objs);
|
||||
break;
|
||||
case CellContentType.Prefab:
|
||||
RenderCellPrefab(data, row, col, cellX, cellY, cellW, cellH);
|
||||
RenderCellPrefab(cellId, data, row, col, cellX, cellY, cellW, cellH);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -184,7 +185,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
m_LastRenderedCells.Add((row, col));
|
||||
}
|
||||
|
||||
private void ApplySelectionHighlight(int row, int col,
|
||||
private void ApplySelectionHighlight(int cellId, int row, int col,
|
||||
float cellX, float cellY, float cellW, float cellH,
|
||||
string cellNS, bool editorMode, ref CellTrackedObjs objs)
|
||||
{
|
||||
@@ -194,7 +195,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
if (!inRange && !isFocused) return;
|
||||
|
||||
string key = inRange ? "cell_multiselect" : "cell_select";
|
||||
var img = m_Assembler.GetImage(key, cellNS);
|
||||
var img = m_Assembler.GetImage(cellId, key, cellNS);
|
||||
if (img == null) return;
|
||||
img.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
img.Rect.sizeDelta = new Vector2(cellW, cellH);
|
||||
@@ -222,7 +223,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
objs.SelectBg = img;
|
||||
}
|
||||
|
||||
private void ApplyGridLines(float cellX, float cellY, float cellW, float cellH,
|
||||
private void ApplyGridLines(int cellId, float cellX, float cellY, float cellW, float cellH,
|
||||
string cellNS, bool editorMode, ref CellTrackedObjs objs)
|
||||
{
|
||||
Color borderColor;
|
||||
@@ -240,7 +241,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
|
||||
// 水平网格线(底部)
|
||||
var hLine = m_Assembler.GetImage("cell_hline", cellNS);
|
||||
var hLine = m_Assembler.GetImage(cellId, "cell_hline", cellNS);
|
||||
if (hLine != null)
|
||||
{
|
||||
hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH);
|
||||
@@ -250,7 +251,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
|
||||
// 垂直网格线(右侧)
|
||||
var vLine = m_Assembler.GetImage("cell_vline", cellNS);
|
||||
var vLine = m_Assembler.GetImage(cellId, "cell_vline", cellNS);
|
||||
if (vLine != null)
|
||||
{
|
||||
vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - borderWidth, cellY);
|
||||
@@ -260,13 +261,13 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderCellText(XTableCellData data,
|
||||
private void RenderCellText(int cellId, XTableCellData data,
|
||||
float cellX, float cellY, float cellW, float cellH,
|
||||
string cellNS, bool editorMode, ref CellTrackedObjs objs)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data.Text)) return;
|
||||
|
||||
var txt = m_Assembler.GetText("cell_text", cellNS);
|
||||
var txt = m_Assembler.GetText(cellId, "cell_text", cellNS);
|
||||
if (txt == null) return;
|
||||
txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
|
||||
txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
|
||||
@@ -283,14 +284,14 @@ namespace XericUI.XTable.Rendering.Component
|
||||
objs.CellText = txt;
|
||||
}
|
||||
|
||||
private void RenderCellImage(XTableCellData data,
|
||||
private void RenderCellImage(int cellId, XTableCellData data,
|
||||
float cellX, float cellY, float cellW, float cellH,
|
||||
string cellNS, ref CellTrackedObjs objs)
|
||||
{
|
||||
if (data.Image == null) return;
|
||||
|
||||
Sprite sprite = GetOrCreateSprite(data.Image);
|
||||
var img = m_Assembler.GetImage("cell_image", cellNS);
|
||||
var img = m_Assembler.GetImage(cellId, "cell_image", cellNS);
|
||||
if (img == null) return;
|
||||
img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
|
||||
img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
|
||||
@@ -299,12 +300,12 @@ namespace XericUI.XTable.Rendering.Component
|
||||
objs.CellImage = img;
|
||||
}
|
||||
|
||||
private void RenderCellPrefab(XTableCellData data, int row, int col,
|
||||
private void RenderCellPrefab(int cellId, XTableCellData data, int row, int col,
|
||||
float cellX, float cellY, float cellW, float cellH)
|
||||
{
|
||||
if (data.Prefab == null) return;
|
||||
|
||||
GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col);
|
||||
GameObject instance = m_PrefabPool.Acquire(data.Prefab, cellId);
|
||||
if (instance == null) return;
|
||||
RectTransform prt = instance.GetComponent<RectTransform>();
|
||||
prt.anchoredPosition = new Vector2(cellX, cellY);
|
||||
@@ -442,7 +443,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
ReleaseTrackedCell(cell.Item1, cell.Item2);
|
||||
}
|
||||
|
||||
// 3. 回收不再可见的预制体
|
||||
// 3. 回收不再可见的预制体(仅从映射表移除,预制体保留在计数器槽位中复用)
|
||||
if (m_ActivePrefabMap != null && m_ActivePrefabMap.Count > 0)
|
||||
{
|
||||
m_ReusableCellList.Clear();
|
||||
@@ -452,9 +453,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
foreach (var key in m_ReusableCellList)
|
||||
{
|
||||
var cellData = TableData.GetCell(key.Item1, key.Item2);
|
||||
cellData?.OnPrefabRelease?.Invoke(m_ActivePrefabMap[key]);
|
||||
m_PrefabPool.Release(m_ActivePrefabMap[key]);
|
||||
// 不调用 OnPrefabRelease 和 Release——预制体保留在槽位中
|
||||
m_ActivePrefabMap.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using TMPro;
|
||||
|
||||
using UnityEngine;
|
||||
@@ -8,7 +6,8 @@ using UnityEngine.UI;
|
||||
namespace XericUI.XTable.Rendering.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// 表格单元动态拼装器——通过对象池管理 Image + TMP_Text 的组合创建与回收。
|
||||
/// 表格单元动态拼装器——通过 SlotCounterPool 管理 Image + TMP_Text 的组合创建与回收。
|
||||
/// 每个单元格通过 cellId 映射到固定索引的计数器槽位,空闲槽位经 FrozenSlots 全局流转复用。
|
||||
/// 所有元素统一使用 anchor=(0,1) pivot=(0,1) 左上角坐标系。
|
||||
/// 所有动态生成对象标记 HideFlags.DontSave | HideFlags.HideInHierarchy,防止泄漏到场景。
|
||||
/// </summary>
|
||||
@@ -22,6 +21,8 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
public RectTransform Rect;
|
||||
public Image Image;
|
||||
public StyleBoundElement Style;
|
||||
/// <summary>绑定的单元格 ID,用于 Release 定位槽位</summary>
|
||||
public int CellId;
|
||||
}
|
||||
|
||||
public class PooledText
|
||||
@@ -30,6 +31,8 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
public RectTransform Rect;
|
||||
public TMP_Text Text;
|
||||
public StyleBoundElement Style;
|
||||
/// <summary>绑定的单元格 ID,用于 Release 定位槽位</summary>
|
||||
public int CellId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -44,20 +47,14 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
/// <summary>所有已绑定的 StyleBoundElement 回调集合(用于样式变更时通知外部)</summary>
|
||||
public event System.Action OnStyleChangeDetected;
|
||||
|
||||
private Queue<PooledImage> m_ImagePool = new Queue<PooledImage>();
|
||||
private Queue<PooledText> m_TextPool = new Queue<PooledText>();
|
||||
private List<PooledImage> m_ActiveImages = new List<PooledImage>();
|
||||
private List<PooledText> m_ActiveTexts = new List<PooledText>();
|
||||
/// <summary>基于计数器的 Image 槽位对象池</summary>
|
||||
private SlotCounterPool<PooledImage> m_ImagePool;
|
||||
/// <summary>基于计数器的 Text 槽位对象池</summary>
|
||||
private SlotCounterPool<PooledText> m_TextPool;
|
||||
|
||||
/// <summary>HideFlags 应用于所有动态创建的对象(不保存、不在 Hierarchy 显示)</summary>
|
||||
private const HideFlags POOL_FLAGS = HideFlags.DontSave | HideFlags.HideInHierarchy;
|
||||
|
||||
/// <summary>渲染项目总数上限,超出后 GetImage/GetText 返回 null 并发出警告</summary>
|
||||
private const int MAX_POOL_ITEMS = 256;
|
||||
|
||||
private int m_ImageCreatedCount;
|
||||
private int m_TextCreatedCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数与析构
|
||||
@@ -66,33 +63,15 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
{
|
||||
m_Parent = parent;
|
||||
m_DefaultStyleNamespace = defaultStyleNamespace;
|
||||
m_ImagePool = new SlotCounterPool<PooledImage>(() => CreateImage("cell_img"));
|
||||
m_TextPool = new SlotCounterPool<PooledText>(() => CreateText("cell_text"));
|
||||
}
|
||||
|
||||
/// <summary>彻底销毁所有池对象(包括活跃和休眠的)</summary>
|
||||
/// <summary>彻底销毁所有池对象(包括计数器内活跃槽位和冻结槽位)</summary>
|
||||
public void DestroyAll()
|
||||
{
|
||||
// 杀死活跃对象
|
||||
for (int i = m_ActiveImages.Count - 1; i >= 0; i--)
|
||||
{
|
||||
KillObject(m_ActiveImages[i].Root);
|
||||
}
|
||||
m_ActiveImages.Clear();
|
||||
|
||||
for (int i = m_ActiveTexts.Count - 1; i >= 0; i--)
|
||||
{
|
||||
KillObject(m_ActiveTexts[i].Root);
|
||||
}
|
||||
m_ActiveTexts.Clear();
|
||||
|
||||
// 杀死池中对象
|
||||
while (m_ImagePool.Count > 0)
|
||||
{
|
||||
KillObject(m_ImagePool.Dequeue().Root);
|
||||
}
|
||||
while (m_TextPool.Count > 0)
|
||||
{
|
||||
KillObject(m_TextPool.Dequeue().Root);
|
||||
}
|
||||
m_ImagePool.DestroyAll(item => KillObject(item.Root));
|
||||
m_TextPool.DestroyAll(item => KillObject(item.Root));
|
||||
}
|
||||
|
||||
private static void KillObject(GameObject go)
|
||||
@@ -116,49 +95,29 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
|
||||
#region 池操作
|
||||
|
||||
public PooledImage GetImage(string name = "cell_img", string styleNamespace = null)
|
||||
/// <summary>为指定 cellId 获取一个池化 Image</summary>
|
||||
public PooledImage GetImage(int cellId, string name = "cell_img", string styleNamespace = null)
|
||||
{
|
||||
PooledImage item;
|
||||
if (m_ImagePool.Count > 0)
|
||||
{
|
||||
item = m_ImagePool.Dequeue();
|
||||
ResetImageDefaults(item);
|
||||
}
|
||||
else if (m_ImageCreatedCount < MAX_POOL_ITEMS)
|
||||
{
|
||||
item = CreateImage(name);
|
||||
m_ImageCreatedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[XericUIActionTable] Image 池已达上限 {MAX_POOL_ITEMS},无法分配新对象");
|
||||
return null;
|
||||
}
|
||||
var item = m_ImagePool.Acquire(cellId);
|
||||
if (item == null) return null;
|
||||
|
||||
item.CellId = cellId;
|
||||
item.Root.name = name;
|
||||
ResetImageDefaults(item);
|
||||
BindStyle(item.Style, styleNamespace);
|
||||
m_ActiveImages.Add(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
public PooledText GetText(string name = "cell_text", string styleNamespace = null)
|
||||
/// <summary>为指定 cellId 获取一个池化 TMP_Text</summary>
|
||||
public PooledText GetText(int cellId, string name = "cell_text", string styleNamespace = null)
|
||||
{
|
||||
PooledText item;
|
||||
if (m_TextPool.Count > 0)
|
||||
{
|
||||
item = m_TextPool.Dequeue();
|
||||
ResetTextDefaults(item);
|
||||
}
|
||||
else if (m_TextCreatedCount < MAX_POOL_ITEMS)
|
||||
{
|
||||
item = CreateText(name);
|
||||
m_TextCreatedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[XericUIActionTable] Text 池已达上限 {MAX_POOL_ITEMS},无法分配新对象");
|
||||
return null;
|
||||
}
|
||||
var item = m_TextPool.Acquire(cellId);
|
||||
if (item == null) return null;
|
||||
|
||||
item.CellId = cellId;
|
||||
item.Root.name = name;
|
||||
ResetTextDefaults(item);
|
||||
BindStyle(item.Style, styleNamespace);
|
||||
m_ActiveTexts.Add(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -183,41 +142,28 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
OnStyleChangeDetected?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>归还所有槽位至 FrozenSlots(全量刷新时调用)</summary>
|
||||
public void ReturnAll()
|
||||
{
|
||||
foreach (var img in m_ActiveImages) ReturnImage(img);
|
||||
m_ActiveImages.Clear();
|
||||
|
||||
foreach (var txt in m_ActiveTexts) ReturnText(txt);
|
||||
m_ActiveTexts.Clear();
|
||||
// Unbind 所有活跃槽位的样式回调
|
||||
m_ImagePool.ReturnAll();
|
||||
m_TextPool.ReturnAll();
|
||||
}
|
||||
|
||||
/// <summary>归还单个 Image 池对象(从活跃列表移除并推入休眠栈)</summary>
|
||||
/// <summary>归还单个 Image 池对象(通过 CellId 定位槽位并移入 FrozenSlots)</summary>
|
||||
public void ReleaseImage(PooledImage item)
|
||||
{
|
||||
if (item == null) return;
|
||||
m_ActiveImages.Remove(item);
|
||||
ReturnImage(item);
|
||||
item.Style?.Unbind();
|
||||
m_ImagePool.Release(item.CellId);
|
||||
}
|
||||
|
||||
/// <summary>归还单个 Text 池对象(从活跃列表移除并推入休眠栈)</summary>
|
||||
/// <summary>归还单个 Text 池对象(通过 CellId 定位槽位并移入 FrozenSlots)</summary>
|
||||
public void ReleaseText(PooledText item)
|
||||
{
|
||||
if (item == null) return;
|
||||
m_ActiveTexts.Remove(item);
|
||||
ReturnText(item);
|
||||
}
|
||||
|
||||
private void ReturnImage(PooledImage item)
|
||||
{
|
||||
item.Style?.Unbind();
|
||||
m_ImagePool.Enqueue(item);
|
||||
}
|
||||
|
||||
private void ReturnText(PooledText item)
|
||||
{
|
||||
item.Style?.Unbind();
|
||||
m_TextPool.Enqueue(item);
|
||||
m_TextPool.Release(item.CellId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -5,8 +5,8 @@ using UnityEngine;
|
||||
namespace XericUI.XTable.Rendering.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// 预制体对象池——按原型分组管理,每个预制体对应一个独立的对象池。
|
||||
/// 使用 Queue + HashSet 模式,FIFO 保证对象按回收顺序均匀复用。
|
||||
/// 预制体对象池——按原型分组管理,每个原型使用计数器槽位绑定 cellId 到预制体实例。
|
||||
/// 预制体不参与 FrozenSlots 流转:始终绑定在 cellId 槽位上,仅在表格销毁时释放。
|
||||
/// </summary>
|
||||
public class PrefabPrototypePool
|
||||
{
|
||||
@@ -15,10 +15,8 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
private class PrefabPool
|
||||
{
|
||||
public GameObject Prototype;
|
||||
/// <summary>休眠实例队列(FIFO,先归还的优先复用)</summary>
|
||||
public Queue<GameObject> Available = new Queue<GameObject>();
|
||||
/// <summary>活跃实例集合(O(1) 查找 + 防止重复归还)</summary>
|
||||
public HashSet<GameObject> Active = new HashSet<GameObject>();
|
||||
/// <summary>cellId → 预制体实例 的计数器槽位绑定</summary>
|
||||
public Dictionary<int, GameObject> Slots = new Dictionary<int, GameObject>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -28,7 +26,7 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
private Dictionary<GameObject, PrefabPool> m_Pools = new Dictionary<GameObject, PrefabPool>();
|
||||
private Transform m_Parent;
|
||||
|
||||
/// <summary>渲染项目总数上限</summary>
|
||||
/// <summary>渲染项目总数上限(全局,跨所有原型)</summary>
|
||||
private const int MAX_PREFAB_ITEMS = 256;
|
||||
private int m_TotalCreated;
|
||||
|
||||
@@ -46,21 +44,18 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
m_Parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>彻底销毁所有池对象(活跃 + 休眠)</summary>
|
||||
/// <summary>彻底销毁所有池对象(所有原型的活跃槽位)</summary>
|
||||
public void DestroyAll()
|
||||
{
|
||||
foreach (var kv in m_Pools)
|
||||
{
|
||||
var pool = kv.Value;
|
||||
// 销毁活跃实例
|
||||
foreach (var go in pool.Active)
|
||||
KillObject(go);
|
||||
pool.Active.Clear();
|
||||
// 销毁休眠实例
|
||||
while (pool.Available.Count > 0)
|
||||
KillObject(pool.Available.Dequeue());
|
||||
foreach (var instance in pool.Slots.Values)
|
||||
KillObject(instance);
|
||||
pool.Slots.Clear();
|
||||
}
|
||||
m_Pools.Clear();
|
||||
m_TotalCreated = 0;
|
||||
}
|
||||
|
||||
private static void KillObject(GameObject go)
|
||||
@@ -76,31 +71,31 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
|
||||
#region 池操作
|
||||
|
||||
/// <summary>从池中获取实例(FIFO 复用;池空且未达上限则 Instantiate;已达上限返回 null)</summary>
|
||||
public GameObject Acquire(GameObject prefab, int row, int col)
|
||||
/// <summary>
|
||||
/// 为指定 cellId 获取预制体实例。
|
||||
/// 若该 cellId 已有绑定的实例,直接返回;否则创建新实例(需未达上限)。
|
||||
/// </summary>
|
||||
public GameObject Acquire(GameObject prefab, int cellId)
|
||||
{
|
||||
if (prefab == null) return null;
|
||||
|
||||
var pool = GetOrCreatePool(prefab);
|
||||
|
||||
GameObject instance;
|
||||
if (pool.Available.Count > 0)
|
||||
{
|
||||
instance = pool.Available.Dequeue();
|
||||
}
|
||||
else if (m_TotalCreated < MAX_PREFAB_ITEMS)
|
||||
{
|
||||
instance = Object.Instantiate(prefab, m_Parent);
|
||||
instance.hideFlags = POOL_FLAGS;
|
||||
m_TotalCreated++;
|
||||
}
|
||||
else
|
||||
// 已绑定 → 直接复用
|
||||
if (pool.Slots.TryGetValue(cellId, out var existing))
|
||||
return existing;
|
||||
|
||||
// 创建新实例
|
||||
if (m_TotalCreated >= MAX_PREFAB_ITEMS)
|
||||
{
|
||||
Debug.LogWarning($"[XericUIActionTable] Prefab 池已达上限 {MAX_PREFAB_ITEMS},无法分配新实例");
|
||||
return null;
|
||||
}
|
||||
|
||||
instance.name = $"_prefab_{row}_{col}";
|
||||
var instance = Object.Instantiate(prefab, m_Parent);
|
||||
instance.hideFlags = POOL_FLAGS;
|
||||
instance.name = $"_prefab_{cellId}";
|
||||
m_TotalCreated++;
|
||||
|
||||
// 强制 RectTransform 左上角坐标系
|
||||
RectTransform rt = instance.GetComponent<RectTransform>();
|
||||
@@ -109,21 +104,36 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
rt.anchorMax = AnchorTopLeft;
|
||||
rt.pivot = PivotTopLeft;
|
||||
|
||||
pool.Active.Add(instance);
|
||||
pool.Slots[cellId] = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>将指定实例归还对象池(不入池根,原地保留避免 OnEnable/OnDisable)</summary>
|
||||
/// <summary>
|
||||
/// 归还指定实例至槽位(仅从槽位移除 + 销毁对象,不在滚动时调用)。
|
||||
/// 预制体的回收仅发生在表格销毁时。
|
||||
/// </summary>
|
||||
public void Release(GameObject instance)
|
||||
{
|
||||
if (instance == null) return;
|
||||
|
||||
// 查找所属池
|
||||
foreach (var kv in m_Pools)
|
||||
{
|
||||
if (kv.Value.Active.Remove(instance))
|
||||
var pool = kv.Value;
|
||||
// 查找并移除
|
||||
int? foundKey = null;
|
||||
foreach (var slot in pool.Slots)
|
||||
{
|
||||
kv.Value.Available.Enqueue(instance);
|
||||
if (slot.Value == instance)
|
||||
{
|
||||
foundKey = slot.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundKey.HasValue)
|
||||
{
|
||||
pool.Slots.Remove(foundKey.Value);
|
||||
m_TotalCreated--;
|
||||
KillObject(instance);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -132,18 +142,17 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
KillObject(instance);
|
||||
}
|
||||
|
||||
/// <summary>将所有活跃实例回收至池(原地保留,仅从活跃集移除并入队)</summary>
|
||||
/// <summary>将所有活跃槽位清空并销毁对象</summary>
|
||||
public void ReturnAll()
|
||||
{
|
||||
foreach (var kv in m_Pools)
|
||||
{
|
||||
var pool = kv.Value;
|
||||
foreach (var go in pool.Active)
|
||||
{
|
||||
pool.Available.Enqueue(go);
|
||||
}
|
||||
pool.Active.Clear();
|
||||
foreach (var instance in pool.Slots.Values)
|
||||
KillObject(instance);
|
||||
pool.Slots.Clear();
|
||||
}
|
||||
m_TotalCreated = 0;
|
||||
}
|
||||
|
||||
/// <summary>获取当前所有活跃实例(用于外部遍历回调)</summary>
|
||||
@@ -151,7 +160,7 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
{
|
||||
if (prefab == null || !m_Pools.TryGetValue(prefab, out var pool))
|
||||
return System.Array.Empty<GameObject>();
|
||||
return pool.Active;
|
||||
return pool.Slots.Values;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91facbccfc5ebff44b47c4b4fe51135b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user