From 353cedcf1cc7ca76b3c56d1f438810f23fa1cf87 Mon Sep 17 00:00:00 2001
From: LiRuoChen <571244399@qq.com>
Date: Thu, 30 Jul 2026 08:25:44 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AF=B9=E8=B1=A1=E6=B1=A0?=
=?UTF-8?q?=E9=87=8A=E6=94=BE=E9=80=BB=E8=BE=91=EF=BC=8C=E8=AE=A1=E6=95=B0?=
=?UTF-8?q?=E5=99=A8=E4=BB=85=E4=BD=9C=E4=B8=BA=E5=AF=B9=E8=B1=A1=E6=B1=A0?=
=?UTF-8?q?=E5=A4=8D=E7=94=A8=E5=B0=81=E8=A3=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Runtime/Core/Base/XericUIBehaviour.cs | 42 +++
.../Rendering/Component/TableRenderer.cs | 4 -
.../Component/XericUIActionTable.Api.cs | 148 ++++++++-
.../Component/XericUIActionTable.ExcelIO.cs | 4 +-
.../XericUIActionTable.RendererFilter.cs | 56 +---
.../XericUIActionTable.UpdateScope.cs | 175 +++++++----
.../Rendering/Elements/CellAssembler.cs | 29 +-
.../Rendering/Elements/PrefabPrototypePool.cs | 294 +++++++-----------
.../Rendering/Elements/SlotCounterPool.cs | 11 +-
9 files changed, 459 insertions(+), 304 deletions(-)
diff --git a/Runtime/Core/Base/XericUIBehaviour.cs b/Runtime/Core/Base/XericUIBehaviour.cs
index 0b49b72..ae6eb28 100644
--- a/Runtime/Core/Base/XericUIBehaviour.cs
+++ b/Runtime/Core/Base/XericUIBehaviour.cs
@@ -268,7 +268,49 @@ namespace XericUI.Core.Base
rt.anchoredPosition = anchorLocalPos + pivotOffset;
}
}
+
+ ///
+ /// 根据目标单元格与父级画布的可用空间定位弹窗。
+ /// 默认以左上角对齐单元格左下角;下方空间不足时改用底部锚点显示在单元格上方,
+ /// 右侧空间不足时改为从右侧向左展开。
+ ///
+ public void AutoSnapAlignedToRect(RectTransform cellRect)
+ {
+ var rt = transform as RectTransform;
+ if (rt == null || cellRect == null) return;
+ var parentRT = rt.parent as RectTransform;
+ if (parentRT == null) return;
+
+ Canvas.ForceUpdateCanvases();
+
+ var cellCorners = new Vector3[4];
+ cellRect.GetWorldCorners(cellCorners);
+ for (int i = 0; i < cellCorners.Length; i++)
+ cellCorners[i] = parentRT.InverseTransformPoint(cellCorners[i]);
+
+ var popupWidth = parentRT.InverseTransformVector(
+ rt.TransformVector(Vector3.right * rt.rect.width)).magnitude;
+ var popupHeight = parentRT.InverseTransformVector(
+ rt.TransformVector(Vector3.up * rt.rect.height)).magnitude;
+ var parentRect = parentRT.rect;
+
+ var leftSpace = cellCorners[2].x - parentRect.xMin;
+ var rightSpace = parentRect.xMax - cellCorners[0].x;
+ var placeRight = rightSpace >= popupWidth || rightSpace >= leftSpace;
+
+ var bottomSpace = cellCorners[0].y - parentRect.yMin;
+ var topSpace = parentRect.yMax - cellCorners[1].y;
+ var placeBelow = bottomSpace >= popupHeight || bottomSpace >= topSpace;
+
+ rt.pivot = new Vector2(placeRight ? 0f : 1f, placeBelow ? 1f : 0f);
+ var target = new Vector3(
+ placeRight ? cellCorners[0].x : cellCorners[2].x,
+ placeBelow ? cellCorners[0].y : cellCorners[1].y,
+ 0f);
+ rt.position = parentRT.TransformPoint(target);
+ }
+
#endregion
#region 显影
diff --git a/Runtime/XTable/Rendering/Component/TableRenderer.cs b/Runtime/XTable/Rendering/Component/TableRenderer.cs
index 42cbb7a..4cbaab2 100644
--- a/Runtime/XTable/Rendering/Component/TableRenderer.cs
+++ b/Runtime/XTable/Rendering/Component/TableRenderer.cs
@@ -120,10 +120,6 @@ namespace XericUI.XTable.Rendering.Component
ReleaseTracked(pair.Value);
m_Tracked.Clear();
m_Rendered.Clear();
- if (ActivePrefabMap == null) return;
- var keys = new List<(int, int)>();
- foreach (var pair in ActivePrefabMap) keys.Add(pair.Key);
- foreach (var key in keys) ActivePrefabMap.Remove(key);
}
private void ReleaseExcept(HashSet<(int, int)> target)
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs
index 7283c7d..a22242e 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs
@@ -19,15 +19,28 @@ namespace XericUI.XTable.Rendering.Component
{
/// 设置冻结行列数并重建视图
public void SetFreezeCount(int rows, int cols)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetFreezeCount, rows, cols))) return;
+ SetFreezeCountImmediate(rows, cols);
+ }
+
+ private void SetFreezeCountImmediate(int rows, int cols, bool refresh = true)
{
m_FreezeRowCount = Mathf.Max(0, rows);
m_FreezeColCount = Mathf.Max(0, cols);
CreateViewportRenderer();
MarkDirty(TableDirtyType.LayoutChanged);
- RefreshView();
+ if (refresh && !IsCheckingUpdate)
+ RefreshView();
}
public void SetCellData(int row, int col, XTableCellData data)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetCellData, row, col, cellData: data))) return;
+ SetCellDataImmediate(row, col, data);
+ }
+
+ private void SetCellDataImmediate(int row, int col, XTableCellData data)
{
TableData.SetCell(row, col, data);
MarkDirty(TableDirtyType.DataChanged);
@@ -35,6 +48,12 @@ namespace XericUI.XTable.Rendering.Component
/// 设置文本内容(ContentType 自动设为 Text)
public XTableCellData SetCellText(int row, int col, string text)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetCellText, row, col, stringValue1: text))) return null;
+ return SetCellTextImmediate(row, col, text);
+ }
+
+ private XTableCellData SetCellTextImmediate(int row, int col, string text)
{
var data = TableData.GetOrCreateCell(row, col);
data.Text = text;
@@ -45,6 +64,12 @@ namespace XericUI.XTable.Rendering.Component
/// 设置图片纹理(ContentType 自动设为 Image)
public XTableCellData SetCellImage(int row, int col, Texture2D image)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetCellImage, row, col, texture: image))) return null;
+ return SetCellImageImmediate(row, col, image);
+ }
+
+ private XTableCellData SetCellImageImmediate(int row, int col, Texture2D image)
{
var data = TableData.GetOrCreateCell(row, col);
data.Image = image;
@@ -56,6 +81,12 @@ namespace XericUI.XTable.Rendering.Component
/// 设置预制体(ContentType 自动设为 Prefab)
/// 注意是设置预制体而不是实例,设置后订阅单元格预制体生成事件 OnPrefabAcquire 处理获取事件
public XTableCellData SetCellPrefab(int row, int col, GameObject prefab)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetCellPrefab, row, col, prefab: prefab))) return null;
+ return SetCellPrefabImmediate(row, col, prefab);
+ }
+
+ private XTableCellData SetCellPrefabImmediate(int row, int col, GameObject prefab)
{
var data = TableData.GetOrCreateCell(row, col);
data.Prefab = prefab;
@@ -66,6 +97,12 @@ namespace XericUI.XTable.Rendering.Component
/// 设置单元格或合并矩形的样式,并覆写其四条外边范围。
public XTableCellData SetCellStyle(int row, int col, string styleNamespace, string stylePath)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetCellStyle, row, col, stringValue1: styleNamespace, stringValue2: stylePath))) return null;
+ return SetCellStyleImmediate(row, col, styleNamespace, stylePath);
+ }
+
+ private XTableCellData SetCellStyleImmediate(int row, int col, string styleNamespace, string stylePath)
{
ResolveStyleRect(row, col, out int rowStart, out int rowEnd, out int colStart, out int colEnd);
XTableCellData result = null;
@@ -86,6 +123,12 @@ namespace XericUI.XTable.Rendering.Component
/// 设置单元格的样式命名空间(兼容旧版,等同于 StyleNamespace 字段)
public void SetCellStyleNamespace(int row, int col, string styleNamespace)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetCellStyleNamespace, row, col, stringValue1: styleNamespace))) return;
+ SetCellStyleNamespaceImmediate(row, col, styleNamespace);
+ }
+
+ private void SetCellStyleNamespaceImmediate(int row, int col, string styleNamespace)
{
ResolveStyleRect(row, col, out int rowStart, out int rowEnd, out int colStart, out int colEnd);
for (int r = rowStart; r < rowEnd; r++)
@@ -108,6 +151,12 @@ namespace XericUI.XTable.Rendering.Component
/// 清除文本内容
public void ClearCellText(int row, int col)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.ClearCellText, row, col))) return;
+ ClearCellTextImmediate(row, col);
+ }
+
+ private void ClearCellTextImmediate(int row, int col)
{
var data = TableData.GetCell(row, col);
if (data == null) return;
@@ -119,6 +168,12 @@ namespace XericUI.XTable.Rendering.Component
/// 清除图片纹理
public void ClearCellImage(int row, int col)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.ClearCellImage, row, col))) return;
+ ClearCellImageImmediate(row, col);
+ }
+
+ private void ClearCellImageImmediate(int row, int col)
{
var data = TableData.GetCell(row, col);
if (data == null) return;
@@ -128,6 +183,12 @@ namespace XericUI.XTable.Rendering.Component
/// 清除预制体及委托
public void ClearCellPrefab(int row, int col)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.ClearCellPrefab, row, col))) return;
+ ClearCellPrefabImmediate(row, col);
+ }
+
+ private void ClearCellPrefabImmediate(int row, int col)
{
var data = TableData.GetCell(row, col);
if (data == null) return;
@@ -139,6 +200,12 @@ namespace XericUI.XTable.Rendering.Component
/// 清除单元格样式覆盖
public void ClearCellStyle(int row, int col)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.ClearCellStyle, row, col))) return;
+ ClearCellStyleImmediate(row, col);
+ }
+
+ private void ClearCellStyleImmediate(int row, int col)
{
ResolveStyleRect(row, col, out int rowStart, out int rowEnd, out int colStart, out int colEnd);
for (int r = rowStart; r < rowEnd; r++)
@@ -154,7 +221,10 @@ namespace XericUI.XTable.Rendering.Component
}
public XTableCellData GetCellData(int row, int col)
- => TableData.GetOrCreateCell(row, col);
+ {
+ ThrowIfCheckingUpdate(nameof(GetCellData));
+ return TableData.GetOrCreateCell(row, col);
+ }
/// 获取单元格的样式命名空间(含回退逻辑)
public string GetCellStyleNamespace(int row, int col)
@@ -164,12 +234,24 @@ namespace XericUI.XTable.Rendering.Component
}
public void MergeCells(int startRow, int startCol, int rowSpan, int colSpan)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.MergeCells, startRow, startCol, rowSpan, colSpan))) return;
+ MergeCellsImmediate(startRow, startCol, rowSpan, colSpan);
+ }
+
+ private void MergeCellsImmediate(int startRow, int startCol, int rowSpan, int colSpan)
{
TableData.MergeCells(startRow, startCol, rowSpan, colSpan);
MarkDirty(TableDirtyType.DataChanged);
}
public void UnmergeCells(int startRow, int startCol)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.UnmergeCells, startRow, startCol))) return;
+ UnmergeCellsImmediate(startRow, startCol);
+ }
+
+ private void UnmergeCellsImmediate(int startRow, int startCol)
{
TableData.UnmergeCells(startRow, startCol);
MarkDirty(TableDirtyType.DataChanged);
@@ -184,6 +266,12 @@ namespace XericUI.XTable.Rendering.Component
/// 在末尾追加一行,默认行高来自 Config
public void AddRow(float height = -1f)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.AddRow, floatValue: height))) return;
+ AddRowImmediate(height);
+ }
+
+ private void AddRowImmediate(float height)
{
if (height <= 0) height = Config.DefaultRowHeight;
#if UNITY_EDITOR
@@ -194,7 +282,6 @@ namespace XericUI.XTable.Rendering.Component
System.Array.Copy(m_RowHeights, newArr, m_RowHeights.Length);
newArr[newLen - 1] = height;
m_RowHeights = newArr;
- RecordStructuralChange(false);
MarkDirty(TableDirtyType.LayoutChanged);
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(this);
@@ -203,6 +290,12 @@ namespace XericUI.XTable.Rendering.Component
/// 在末尾追加一列,默认列宽来自 Config
public void AddColumn(float width = -1f)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.AddColumn, floatValue: width))) return;
+ AddColumnImmediate(width);
+ }
+
+ private void AddColumnImmediate(float width)
{
if (width <= 0) width = Config.DefaultColumnWidth;
#if UNITY_EDITOR
@@ -213,7 +306,6 @@ namespace XericUI.XTable.Rendering.Component
System.Array.Copy(m_ColWidths, newArr, m_ColWidths.Length);
newArr[newLen - 1] = width;
m_ColWidths = newArr;
- RecordStructuralChange(false);
MarkDirty(TableDirtyType.LayoutChanged);
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(this);
@@ -222,6 +314,12 @@ namespace XericUI.XTable.Rendering.Component
/// 设置指定行的高度
public void SetRowHeight(int row, float height)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetRowHeight, row, floatValue: height))) return;
+ SetRowHeightImmediate(row, height);
+ }
+
+ private void SetRowHeightImmediate(int row, float height)
{
if (row < 0 || row >= m_RowHeights.Length) return;
if (height <= 0) return;
@@ -237,6 +335,12 @@ namespace XericUI.XTable.Rendering.Component
/// 设置指定列的宽度
public void SetColWidth(int col, float width)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.SetColWidth, col: col, floatValue: width))) return;
+ SetColWidthImmediate(col, width);
+ }
+
+ private void SetColWidthImmediate(int col, float width)
{
if (col < 0 || col >= m_ColWidths.Length) return;
if (width <= 0) return;
@@ -252,15 +356,18 @@ namespace XericUI.XTable.Rendering.Component
/// 删除指定行,单元格数据上移
public void RemoveRow(int row)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.RemoveRow, row))) return;
+ RemoveRowImmediate(row);
+ }
+
+ private void RemoveRowImmediate(int row)
{
if (row < 0 || row >= m_RowHeights.Length) return;
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Remove Table Row");
#endif
int colCount = m_ColWidths.Length;
- CaptureActivePrefabReleaseCallbacks();
- RecordStructuralChange(true);
-
// 先解除涉及该行的合并
for (int c = 0; c < colCount; c++)
TableData.UnmergeCells(row, c);
@@ -289,6 +396,7 @@ namespace XericUI.XTable.Rendering.Component
for (int i = row; i < newLen; i++)
newArr[i] = m_RowHeights[i + 1];
m_RowHeights = newArr;
+ TableData.ClearAllBorderCache();
// 裁剪选区到新尺寸
ClampSelectionToBounds();
@@ -301,15 +409,18 @@ namespace XericUI.XTable.Rendering.Component
/// 删除指定列,单元格数据左移
public void RemoveColumn(int col)
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.RemoveColumn, col: col))) return;
+ RemoveColumnImmediate(col);
+ }
+
+ private void RemoveColumnImmediate(int col)
{
if (col < 0 || col >= m_ColWidths.Length) return;
#if UNITY_EDITOR
UnityEditor.Undo.RecordObject(this, "Remove Table Column");
#endif
int rowCount = m_RowHeights.Length;
- CaptureActivePrefabReleaseCallbacks();
- RecordStructuralChange(true);
-
// 先解除涉及该列的合并
for (int r = 0; r < rowCount; r++)
TableData.UnmergeCells(r, col);
@@ -338,6 +449,7 @@ namespace XericUI.XTable.Rendering.Component
for (int i = col; i < newLen; i++)
newArr[i] = m_ColWidths[i + 1];
m_ColWidths = newArr;
+ TableData.ClearAllBorderCache();
// 裁剪选区到新尺寸
ClampSelectionToBounds();
@@ -432,6 +544,7 @@ namespace XericUI.XTable.Rendering.Component
[ContextMenu("Xeric UI/完全重建表格")]
public void FullRebuild()
{
+ ThrowIfCheckingUpdate(nameof(FullRebuild));
m_IsRebuilding = true;
try
{
@@ -470,6 +583,7 @@ namespace XericUI.XTable.Rendering.Component
/// 重新生成表格但保留已有文本数据——先保存,重建后再回填
public void FullRebuildPreservingData()
{
+ ThrowIfCheckingUpdate(nameof(FullRebuildPreservingData));
m_IsRebuilding = true;
try
{
@@ -540,6 +654,7 @@ namespace XericUI.XTable.Rendering.Component
///
public void ClearData()
{
+ ThrowIfCheckingUpdate(nameof(ClearData));
if (m_TableData != null)
{
// 清理预制体:先调用 OnPrefabRelease 再清理映射
@@ -616,10 +731,17 @@ namespace XericUI.XTable.Rendering.Component
/// 仅强制刷新样式,不重建数据结构
public void ForceStyleRefresh()
+ {
+ if (TryEnqueueUpdateCommand(new UpdateCommand(UpdateCommandType.ForceStyleRefresh))) return;
+ ForceStyleRefreshImmediate();
+ }
+
+ private void ForceStyleRefreshImmediate()
{
StyleManager.ForceRefresh();
MarkDirty(TableDirtyType.StyleChanged);
- RefreshView();
+ if (!IsCheckingUpdate)
+ RefreshView();
}
/// 构建剪贴板数据对象(尺寸 + 单元格文本 + 合并信息)
@@ -631,10 +753,8 @@ namespace XericUI.XTable.Rendering.Component
/// 将剪贴板数据应用到表格——覆盖尺寸、命名空间、单元格文本、合并信息并刷新
public void ApplyClipboardData(XTableClipboardData data)
{
+ ThrowIfCheckingUpdate(nameof(ApplyClipboardData));
if (data == null) return;
- CaptureActivePrefabReleaseCallbacks();
- RecordStructuralChange(true);
-
// 确保数据层存在
if (m_TableData == null)
m_TableData = new XTableData();
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.ExcelIO.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.ExcelIO.cs
index 6620fd6..b963c60 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.ExcelIO.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.ExcelIO.cs
@@ -24,6 +24,7 @@ namespace XericUI.XTable.Rendering.Component
/// 工作表索引(0-based),默认 0 即第一个工作表
public void LoadFromExcelFile(string filePath, int sheetIndex = 0)
{
+ ThrowIfCheckingUpdate(nameof(LoadFromExcelFile));
if (!TryOpenPackage(filePath, out ExcelPackage package)) return;
using (package)
{
@@ -46,6 +47,7 @@ namespace XericUI.XTable.Rendering.Component
/// 工作表名称
public void LoadFromExcelFile(string filePath, string sheetName)
{
+ ThrowIfCheckingUpdate(nameof(LoadFromExcelFile));
if (string.IsNullOrEmpty(sheetName))
{
Debug.LogWarning("[XTable] LoadFromExcelFile: 工作表名称为空");
@@ -262,8 +264,6 @@ namespace XericUI.XTable.Rendering.Component
}
// 标记脏并刷新
- CaptureActivePrefabReleaseCallbacks();
- RecordStructuralChange(true);
RecalculateTotalSize();
RequestRefresh(TableDirtyType.DataChanged | TableDirtyType.LayoutChanged, true);
}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs
index 77572ab..5c4733a 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs
@@ -225,39 +225,22 @@ namespace XericUI.XTable.Rendering.Component
#region 完整刷新
- private void CommitStructuralRenderCleanup(bool invalidatePrefabBindings,
- Dictionary> prefabReleaseCallbacks)
+ private void ReleaseRenderedBindings(Dictionary> prefabReleaseCallbacks = null)
{
ReleaseAllTrackedCells();
ReleaseFrozenCells();
m_LastRenderedCells.Clear();
-
- if (m_ActivePrefabMap != null)
+ m_PrefabPool?.ReturnAll(instance =>
{
- foreach (var pair in m_ActivePrefabMap)
- {
- if (prefabReleaseCallbacks != null && prefabReleaseCallbacks.ContainsKey(pair.Value)) continue;
- var data = TableData.GetCell(pair.Key.Item1, pair.Key.Item2);
- if (data?.OnPrefabRelease != null && prefabReleaseCallbacks != null
- && !prefabReleaseCallbacks.ContainsKey(pair.Value))
- prefabReleaseCallbacks.Add(pair.Value, data.OnPrefabRelease);
- }
- m_ActivePrefabMap.Clear();
- }
-
- if (invalidatePrefabBindings)
- m_PrefabPool?.ReleaseAll(instance =>
- {
- if (prefabReleaseCallbacks != null && prefabReleaseCallbacks.TryGetValue(instance, out var callback))
- callback(instance);
- });
-
- m_QuadtreeDirty = true;
- m_StyleNamespaceDirty = true;
+ if (prefabReleaseCallbacks != null && prefabReleaseCallbacks.TryGetValue(instance, out var callback))
+ callback(instance);
+ });
+ m_ActivePrefabMap?.Clear();
}
- public void RefreshView()
+ public void RefreshView(bool releaseBindings = true)
{
+ TableDirtyType refreshedFlags = m_DirtyFlag.Flags;
if (m_Assembler == null)
{
if (rectTransform != null)
@@ -288,19 +271,8 @@ namespace XericUI.XTable.Rendering.Component
UpdateBackground();
- // 归还所有追踪对象 + 预制体
- if (m_ActivePrefabMap != null)
- {
- foreach (var kv in m_ActivePrefabMap)
- {
- var cellData = TableData.GetCell(kv.Key.Item1, kv.Key.Item2);
- cellData?.OnPrefabRelease?.Invoke(kv.Value);
- }
- m_ActivePrefabMap.Clear();
- }
- m_PrefabPool?.ReturnAll();
- ReleaseAllTrackedCells();
- ReleaseFrozenCells();
+ if (releaseBindings)
+ ReleaseRenderedBindings(CaptureActivePrefabReleaseCallbacks());
// 清理孤立子对象
CleanOrphanChildren();
@@ -320,7 +292,7 @@ namespace XericUI.XTable.Rendering.Component
RenderFrozenCells(true);
RebuildBorders();
- m_DirtyFlag.Clear();
+ m_DirtyFlag.ClearFlags(refreshedFlags);
OnTableRefreshed?.Invoke();
SetChildrenVisible(m_ChildrenVisible);
}
@@ -380,7 +352,9 @@ namespace XericUI.XTable.Rendering.Component
foreach (var key in m_ReusableCellList)
{
- // 不调用 OnPrefabRelease 和 Release——预制体保留在槽位中
+ GameObject instance = m_ActivePrefabMap[key];
+ var data = TableData.GetCell(key.Item1, key.Item2);
+ m_PrefabPool?.Release(instance, data?.OnPrefabRelease);
m_ActivePrefabMap.Remove(key);
}
}
@@ -398,7 +372,7 @@ namespace XericUI.XTable.Rendering.Component
// 5. 交换:将 newVisible 作为下一帧的比较基准
(m_LastRenderedCells, newVisible) = (newVisible, m_LastRenderedCells);
- m_DirtyFlag.ClearFlags(TableDirtyType.ScrollChanged | TableDirtyType.SelectionChanged);
+ m_DirtyFlag.ClearFlags(refreshedFlags & (TableDirtyType.ScrollChanged | TableDirtyType.SelectionChanged));
OnTableRefreshed?.Invoke();
SetChildrenVisible(m_ChildrenVisible);
}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.UpdateScope.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.UpdateScope.cs
index 4fb2523..b7850e0 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.UpdateScope.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.UpdateScope.cs
@@ -1,37 +1,84 @@
using System;
using System.Collections.Generic;
using UnityEngine;
+using XericUI.XTable.Core;
namespace XericUI.XTable.Rendering.Component
{
public partial class XericUIActionTable
{
+ private enum UpdateCommandType
+ {
+ SetFreezeCount,
+ SetCellData,
+ SetCellText,
+ SetCellImage,
+ SetCellPrefab,
+ ClearCellText,
+ ClearCellImage,
+ ClearCellPrefab,
+ SetCellStyle,
+ SetCellStyleNamespace,
+ ClearCellStyle,
+ MergeCells,
+ UnmergeCells,
+ AddRow,
+ AddColumn,
+ RemoveRow,
+ RemoveColumn,
+ SetRowHeight,
+ SetColWidth,
+ ForceStyleRefresh
+ }
+
+ private readonly struct UpdateCommand
+ {
+ public readonly UpdateCommandType Type;
+ public readonly int Row;
+ public readonly int Col;
+ public readonly int IntValue1;
+ public readonly int IntValue2;
+ public readonly float FloatValue;
+ public readonly string StringValue1;
+ public readonly string StringValue2;
+ public readonly XTableCellData CellData;
+ public readonly Texture2D Texture;
+ public readonly GameObject Prefab;
+
+ public UpdateCommand(UpdateCommandType type, int row = 0, int col = 0,
+ int intValue1 = 0, int intValue2 = 0, float floatValue = 0f,
+ string stringValue1 = null, string stringValue2 = null,
+ XTableCellData cellData = null, Texture2D texture = null, GameObject prefab = null)
+ {
+ Type = type;
+ Row = row;
+ Col = col;
+ IntValue1 = intValue1;
+ IntValue2 = intValue2;
+ FloatValue = floatValue;
+ StringValue1 = stringValue1;
+ StringValue2 = stringValue2;
+ CellData = cellData;
+ Texture = texture;
+ Prefab = prefab;
+ }
+ }
+
private sealed class UpdateContext
{
public TableDirtyType DirtyFlags;
- public bool HasStructuralChange;
- public bool InvalidatePrefabBindings;
- public bool RequiresImmediateRefresh;
- public readonly Dictionary> PrefabReleaseCallbacks = new();
+ public readonly List Commands = new();
public void Reset()
{
DirtyFlags = TableDirtyType.None;
- HasStructuralChange = false;
- InvalidatePrefabBindings = false;
- RequiresImmediateRefresh = false;
- PrefabReleaseCallbacks.Clear();
+ Commands.Clear();
}
public void MergeFrom(UpdateContext child)
{
DirtyFlags |= child.DirtyFlags;
- HasStructuralChange |= child.HasStructuralChange;
- InvalidatePrefabBindings |= child.InvalidatePrefabBindings;
- RequiresImmediateRefresh |= child.RequiresImmediateRefresh;
- foreach (var pair in child.PrefabReleaseCallbacks)
- if (!PrefabReleaseCallbacks.ContainsKey(pair.Key))
- PrefabReleaseCallbacks.Add(pair.Key, pair.Value);
+ Commands.AddRange(child.Commands);
}
}
@@ -51,25 +98,22 @@ namespace XericUI.XTable.Rendering.Component
{
if (m_Disposed) return;
m_Disposed = true;
- if (m_Table != null)
- m_Table.EndCheckUpdate(m_Token);
+ m_Table?.EndCheckUpdate(m_Token);
m_Table = null;
}
}
[NonSerialized] private readonly Stack m_UpdateContextStack = new();
[NonSerialized] private readonly Stack m_UpdateContextPool = new();
- [NonSerialized] private long m_NextUpdateToken;
[NonSerialized] private readonly Stack m_UpdateTokenStack = new();
+ [NonSerialized] private long m_NextUpdateToken;
private bool IsCheckingUpdate => m_UpdateContextStack.Count > 0;
- ///
- /// 开始一个可嵌套的批量更新作用域。作用域内的数据修改立即生效,渲染与结构资源整理延迟到最外层 Dispose。
- ///
+ /// 开始一个可嵌套的命令缓冲作用域。可变 API 在最外层 Dispose 时按调用顺序执行。
public UpdateScope CheckUpdate()
{
- var context = m_UpdateContextPool.Count > 0 ? m_UpdateContextPool.Pop() : new UpdateContext();
+ UpdateContext context = m_UpdateContextPool.Count > 0 ? m_UpdateContextPool.Pop() : new UpdateContext();
context.Reset();
m_UpdateContextStack.Push(context);
long token = ++m_NextUpdateToken;
@@ -77,6 +121,13 @@ namespace XericUI.XTable.Rendering.Component
return new UpdateScope(this, token);
}
+ private bool TryEnqueueUpdateCommand(UpdateCommand command)
+ {
+ if (!IsCheckingUpdate) return false;
+ m_UpdateContextStack.Peek().Commands.Add(command);
+ return true;
+ }
+
private void EndCheckUpdate(long token)
{
if (m_UpdateTokenStack.Count == 0 || m_UpdateTokenStack.Peek() != token)
@@ -94,65 +145,81 @@ namespace XericUI.XTable.Rendering.Component
return;
}
+ m_UpdateContextStack.Push(context);
try
{
- CommitUpdateContext(context);
+ Dictionary> prefabCallbacks = CaptureActivePrefabReleaseCallbacks();
+ ExecuteUpdateCommands(context);
+ if (context.DirtyFlags == TableDirtyType.None) return;
+
+ m_UpdateContextStack.Pop();
+ ReleaseRenderedBindings(prefabCallbacks);
+ ApplyDirty(context.DirtyFlags);
+ RefreshView(false);
+ RefreshSelectionOverlays();
}
finally
{
+ if (m_UpdateContextStack.Count > 0 && ReferenceEquals(m_UpdateContextStack.Peek(), context))
+ m_UpdateContextStack.Pop();
ReturnUpdateContext(context);
}
}
+ private void ExecuteUpdateCommands(UpdateContext context)
+ {
+ foreach (UpdateCommand command in context.Commands)
+ {
+ switch (command.Type)
+ {
+ case UpdateCommandType.SetFreezeCount: SetFreezeCountImmediate(command.Row, command.Col, false); break;
+ case UpdateCommandType.SetCellData: SetCellDataImmediate(command.Row, command.Col, command.CellData); break;
+ case UpdateCommandType.SetCellText: SetCellTextImmediate(command.Row, command.Col, command.StringValue1); break;
+ case UpdateCommandType.SetCellImage: SetCellImageImmediate(command.Row, command.Col, command.Texture); break;
+ case UpdateCommandType.SetCellPrefab: SetCellPrefabImmediate(command.Row, command.Col, command.Prefab); break;
+ case UpdateCommandType.ClearCellText: ClearCellTextImmediate(command.Row, command.Col); break;
+ case UpdateCommandType.ClearCellImage: ClearCellImageImmediate(command.Row, command.Col); break;
+ case UpdateCommandType.ClearCellPrefab: ClearCellPrefabImmediate(command.Row, command.Col); break;
+ case UpdateCommandType.SetCellStyle: SetCellStyleImmediate(command.Row, command.Col, command.StringValue1, command.StringValue2); break;
+ case UpdateCommandType.SetCellStyleNamespace: SetCellStyleNamespaceImmediate(command.Row, command.Col, command.StringValue1); break;
+ case UpdateCommandType.ClearCellStyle: ClearCellStyleImmediate(command.Row, command.Col); break;
+ case UpdateCommandType.MergeCells: MergeCellsImmediate(command.Row, command.Col, command.IntValue1, command.IntValue2); break;
+ case UpdateCommandType.UnmergeCells: UnmergeCellsImmediate(command.Row, command.Col); break;
+ case UpdateCommandType.AddRow: AddRowImmediate(command.FloatValue); break;
+ case UpdateCommandType.AddColumn: AddColumnImmediate(command.FloatValue); break;
+ case UpdateCommandType.RemoveRow: RemoveRowImmediate(command.Row); break;
+ case UpdateCommandType.RemoveColumn: RemoveColumnImmediate(command.Col); break;
+ case UpdateCommandType.SetRowHeight: SetRowHeightImmediate(command.Row, command.FloatValue); break;
+ case UpdateCommandType.SetColWidth: SetColWidthImmediate(command.Col, command.FloatValue); break;
+ case UpdateCommandType.ForceStyleRefresh: ForceStyleRefreshImmediate(); break;
+ }
+ }
+ }
+
private void ReturnUpdateContext(UpdateContext context)
{
context.Reset();
m_UpdateContextPool.Push(context);
}
- private void CommitUpdateContext(UpdateContext context)
+ private Dictionary> CaptureActivePrefabReleaseCallbacks()
{
- if (context.DirtyFlags == TableDirtyType.None) return;
-
- if (context.HasStructuralChange)
- CommitStructuralRenderCleanup(context.InvalidatePrefabBindings, context.PrefabReleaseCallbacks);
-
- ApplyDirty(context.DirtyFlags);
- if (context.RequiresImmediateRefresh)
- RefreshView();
- RefreshSelectionOverlays();
- }
-
- private void RecordStructuralChange(bool invalidatePrefabBindings)
- {
- if (!IsCheckingUpdate) return;
- UpdateContext context = m_UpdateContextStack.Peek();
- context.HasStructuralChange = true;
- context.InvalidatePrefabBindings |= invalidatePrefabBindings;
- }
-
- private void CaptureActivePrefabReleaseCallbacks()
- {
- if (!IsCheckingUpdate || m_ActivePrefabMap == null) return;
- var callbacks = m_UpdateContextStack.Peek().PrefabReleaseCallbacks;
+ var callbacks = new Dictionary>();
+ if (m_ActivePrefabMap == null) return callbacks;
foreach (var pair in m_ActivePrefabMap)
{
- var callback = TableData.GetCell(pair.Key.Item1, pair.Key.Item2)?.OnPrefabRelease;
+ Action callback = TableData.GetCell(pair.Key.Item1, pair.Key.Item2)?.OnPrefabRelease;
if (callback != null && !callbacks.ContainsKey(pair.Value))
callbacks.Add(pair.Value, callback);
}
+ return callbacks;
}
private void RequestRefresh(TableDirtyType type, bool immediate)
{
MarkDirty(type);
- if (IsCheckingUpdate)
- {
- m_UpdateContextStack.Peek().RequiresImmediateRefresh |= immediate;
- return;
- }
- if (immediate)
- RefreshView();
+ if (IsCheckingUpdate) return;
+ if (immediate) RefreshView();
}
private void ThrowIfCheckingUpdate(string operation)
diff --git a/Runtime/XTable/Rendering/Elements/CellAssembler.cs b/Runtime/XTable/Rendering/Elements/CellAssembler.cs
index 179fdc6..61079ca 100644
--- a/Runtime/XTable/Rendering/Elements/CellAssembler.cs
+++ b/Runtime/XTable/Rendering/Elements/CellAssembler.cs
@@ -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();
}
- /// 归还所有槽位至 FrozenSlots(全量刷新时调用)
+ /// 归还所有槽位(全量刷新时调用)。被归还对象会隐藏,直到下次 Acquire。
public void ReturnAll()
{
- // Unbind 所有活跃槽位的样式回调
- m_ImagePool.ReturnAll();
- m_TextPool.ReturnAll();
+ m_ImagePool.ReturnAll(HideImage);
+ m_TextPool.ReturnAll(HideText);
}
- /// 归还单个 Image 池对象(通过 CellId 定位槽位并移入 FrozenSlots)
+ /// 归还单个 Image 池对象并隐藏。
public void ReleaseImage(PooledImage item)
{
if (item == null) return;
- item.Style?.Unbind();
+ HideImage(item);
m_ImagePool.ReleaseByObject(item);
}
- /// 归还单个 Text 池对象(通过 CellId 定位槽位并移入 FrozenSlots)
+ /// 归还单个 Text 池对象并隐藏。
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 默认值重置
diff --git a/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs b/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
index 9e99e8e..62b8519 100644
--- a/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
+++ b/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
@@ -1,195 +1,133 @@
+using System;
using System.Collections.Generic;
-
using UnityEngine;
namespace XericUI.XTable.Rendering.Elements
{
- ///
- /// 预制体对象池——按原型分组管理,每个原型使用计数器槽位绑定 cellId 到预制体实例。
- /// 预制体不参与 FrozenSlots 流转:始终绑定在 cellId 槽位上,仅在表格销毁时释放。
- ///
- public class PrefabPrototypePool
- {
- #region 内部类型
+ /// 按原型管理的预制体池。占用槽位绑定 cellId,归还后隐藏并留在同原型待命池中复用。
+ public class PrefabPrototypePool
+ {
+ private class PrefabPool
+ {
+ public readonly Dictionary ActiveSlots = new();
+ public readonly Stack Standby = new();
+ }
- private class PrefabPool
- {
- public GameObject Prototype;
- /// cellId → 预制体实例 的计数器槽位绑定
- public Dictionary Slots = new Dictionary();
- }
+ private readonly Dictionary 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 m_Pools = new Dictionary();
- 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;
+ }
- /// 渲染项目总数上限(全局,跨所有原型)
- private const int MAX_PREFAB_ITEMS = 256;
- private int m_TotalCreated;
+ public void ReturnAll(Action 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 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() ?? instance.AddComponent();
+ rt.anchorMin = AnchorTopLeft;
+ rt.anchorMax = AnchorTopLeft;
+ rt.pivot = PivotTopLeft;
+ instance.SetActive(true);
+ }
- /// 彻底销毁所有池对象(所有原型的活跃槽位)
- 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 池操作
-
- ///
- /// 为指定 cellId 获取预制体实例。
- /// 若该 cellId 已有绑定的实例,直接返回;否则创建新实例(需未达上限)。
- ///
- 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();
- if (rt == null) rt = instance.AddComponent();
- rt.anchorMin = AnchorTopLeft;
- rt.anchorMax = AnchorTopLeft;
- rt.pivot = PivotTopLeft;
-
- pool.Slots[cellId] = instance;
- return instance;
- }
-
- ///
- /// 归还指定实例至槽位(仅从槽位移除 + 销毁对象,不在滚动时调用)。
- /// 预制体的回收仅发生在表格销毁时。
- ///
- 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);
- }
-
- /// 销毁所有按 cellId 绑定的实例,并在销毁前通知调用方。
- public void ReleaseAll(System.Action 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;
- }
-
- /// 结束当前渲染周期。实例保留在全局槽位中,以便跨区域复用。
- public void ReturnAll()
- {
- }
-
- /// 获取当前所有活跃实例(用于外部遍历回调)
- public IReadOnlyCollection GetActive(GameObject prefab)
- {
- if (prefab == null || !m_Pools.TryGetValue(prefab, out var pool))
- return System.Array.Empty();
- 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);
+ }
+ }
}
diff --git a/Runtime/XTable/Rendering/Elements/SlotCounterPool.cs b/Runtime/XTable/Rendering/Elements/SlotCounterPool.cs
index efe7857..a8c2dce 100644
--- a/Runtime/XTable/Rendering/Elements/SlotCounterPool.cs
+++ b/Runtime/XTable/Rendering/Elements/SlotCounterPool.cs
@@ -147,15 +147,20 @@ namespace XericUI.XTable.Rendering.Elements
return false;
}
- /// 释放所有活跃槽位,但保留其所属计数器。
- public void ReturnAll()
+ /// 释放所有活跃槽位;回调在槽位变为空闲前执行。
+ public void ReturnAll(Action 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;
+ }
}
}