修复表格在数据删除方面的用法,增加了CheckUpdate函数用于记录操作栈,合并数据调用。
This commit is contained in:
@@ -194,6 +194,7 @@ 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);
|
||||
@@ -212,6 +213,7 @@ 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);
|
||||
@@ -256,6 +258,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
UnityEditor.Undo.RecordObject(this, "Remove Table Row");
|
||||
#endif
|
||||
int colCount = m_ColWidths.Length;
|
||||
CaptureActivePrefabReleaseCallbacks();
|
||||
RecordStructuralChange(true);
|
||||
|
||||
// 先解除涉及该行的合并
|
||||
for (int c = 0; c < colCount; c++)
|
||||
@@ -303,6 +307,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
UnityEditor.Undo.RecordObject(this, "Remove Table Column");
|
||||
#endif
|
||||
int rowCount = m_RowHeights.Length;
|
||||
CaptureActivePrefabReleaseCallbacks();
|
||||
RecordStructuralChange(true);
|
||||
|
||||
// 先解除涉及该列的合并
|
||||
for (int r = 0; r < rowCount; r++)
|
||||
@@ -410,7 +416,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
private void OnHandleSelectionChanged(XTableSelectionHandle handle)
|
||||
{
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
RefreshSelectionOverlays();
|
||||
if (!IsCheckingUpdate)
|
||||
RefreshSelectionOverlays();
|
||||
}
|
||||
|
||||
/// <summary>裁剪选区到当前行列尺寸范围内</summary>
|
||||
@@ -562,6 +569,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
/// </summary>
|
||||
public void ClearTable()
|
||||
{
|
||||
ThrowIfCheckingUpdate(nameof(ClearTable));
|
||||
m_IsRebuilding = true;
|
||||
try
|
||||
{
|
||||
@@ -624,6 +632,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
public void ApplyClipboardData(XTableClipboardData data)
|
||||
{
|
||||
if (data == null) return;
|
||||
CaptureActivePrefabReleaseCallbacks();
|
||||
RecordStructuralChange(true);
|
||||
|
||||
// 确保数据层存在
|
||||
if (m_TableData == null)
|
||||
@@ -640,9 +650,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
// 写回单元格文本与合并信息
|
||||
data.PopulateToTable(this);
|
||||
|
||||
MarkDirty(TableDirtyType.DataChanged);
|
||||
RecalculateTotalSize();
|
||||
RefreshView();
|
||||
RequestRefresh(TableDirtyType.DataChanged | TableDirtyType.LayoutChanged, true);
|
||||
SetChildrenVisible(m_ChildrenVisible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_IsRebuilding || !m_DirtyFlag.IsDirty) return;
|
||||
if (m_IsRebuilding || IsCheckingUpdate || !m_DirtyFlag.IsDirty) return;
|
||||
|
||||
var flags = m_DirtyFlag.Flags;
|
||||
if ((flags & ~(TableDirtyType.ScrollChanged | TableDirtyType.SelectionChanged)) == 0)
|
||||
|
||||
@@ -262,9 +262,10 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
|
||||
// 标记脏并刷新
|
||||
MarkDirty(TableDirtyType.DataChanged | TableDirtyType.LayoutChanged);
|
||||
CaptureActivePrefabReleaseCallbacks();
|
||||
RecordStructuralChange(true);
|
||||
RecalculateTotalSize();
|
||||
RefreshView();
|
||||
RequestRefresh(TableDirtyType.DataChanged | TableDirtyType.LayoutChanged, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -225,6 +225,37 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
#region 完整刷新
|
||||
|
||||
private void CommitStructuralRenderCleanup(bool invalidatePrefabBindings,
|
||||
Dictionary<GameObject, System.Action<GameObject>> prefabReleaseCallbacks)
|
||||
{
|
||||
ReleaseAllTrackedCells();
|
||||
ReleaseFrozenCells();
|
||||
m_LastRenderedCells.Clear();
|
||||
|
||||
if (m_ActivePrefabMap != null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public void RefreshView()
|
||||
{
|
||||
if (m_Assembler == null)
|
||||
@@ -300,6 +331,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
private void RefreshViewLight()
|
||||
{
|
||||
TableDirtyType refreshedFlags = m_DirtyFlag.Flags;
|
||||
if (m_Assembler == null)
|
||||
{
|
||||
if (rectTransform != null)
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.XTable.Rendering.Component
|
||||
{
|
||||
public partial class XericUIActionTable
|
||||
{
|
||||
private sealed class UpdateContext
|
||||
{
|
||||
public TableDirtyType DirtyFlags;
|
||||
public bool HasStructuralChange;
|
||||
public bool InvalidatePrefabBindings;
|
||||
public bool RequiresImmediateRefresh;
|
||||
public readonly Dictionary<GameObject, Action<GameObject>> PrefabReleaseCallbacks = new();
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
DirtyFlags = TableDirtyType.None;
|
||||
HasStructuralChange = false;
|
||||
InvalidatePrefabBindings = false;
|
||||
RequiresImmediateRefresh = false;
|
||||
PrefabReleaseCallbacks.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);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UpdateScope : IDisposable
|
||||
{
|
||||
private XericUIActionTable m_Table;
|
||||
private readonly long m_Token;
|
||||
private bool m_Disposed;
|
||||
|
||||
internal UpdateScope(XericUIActionTable table, long token)
|
||||
{
|
||||
m_Table = table;
|
||||
m_Token = token;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_Disposed) return;
|
||||
m_Disposed = true;
|
||||
if (m_Table != null)
|
||||
m_Table.EndCheckUpdate(m_Token);
|
||||
m_Table = null;
|
||||
}
|
||||
}
|
||||
|
||||
[NonSerialized] private readonly Stack<UpdateContext> m_UpdateContextStack = new();
|
||||
[NonSerialized] private readonly Stack<UpdateContext> m_UpdateContextPool = new();
|
||||
[NonSerialized] private long m_NextUpdateToken;
|
||||
[NonSerialized] private readonly Stack<long> m_UpdateTokenStack = new();
|
||||
|
||||
private bool IsCheckingUpdate => m_UpdateContextStack.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// 开始一个可嵌套的批量更新作用域。作用域内的数据修改立即生效,渲染与结构资源整理延迟到最外层 Dispose。
|
||||
/// </summary>
|
||||
public UpdateScope CheckUpdate()
|
||||
{
|
||||
var context = m_UpdateContextPool.Count > 0 ? m_UpdateContextPool.Pop() : new UpdateContext();
|
||||
context.Reset();
|
||||
m_UpdateContextStack.Push(context);
|
||||
long token = ++m_NextUpdateToken;
|
||||
m_UpdateTokenStack.Push(token);
|
||||
return new UpdateScope(this, token);
|
||||
}
|
||||
|
||||
private void EndCheckUpdate(long token)
|
||||
{
|
||||
if (m_UpdateTokenStack.Count == 0 || m_UpdateTokenStack.Peek() != token)
|
||||
{
|
||||
Debug.LogError("[XericUIActionTable] CheckUpdate 作用域必须按 LIFO 顺序释放。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
m_UpdateTokenStack.Pop();
|
||||
UpdateContext context = m_UpdateContextStack.Pop();
|
||||
if (m_UpdateContextStack.Count > 0)
|
||||
{
|
||||
m_UpdateContextStack.Peek().MergeFrom(context);
|
||||
ReturnUpdateContext(context);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CommitUpdateContext(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReturnUpdateContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnUpdateContext(UpdateContext context)
|
||||
{
|
||||
context.Reset();
|
||||
m_UpdateContextPool.Push(context);
|
||||
}
|
||||
|
||||
private void CommitUpdateContext(UpdateContext context)
|
||||
{
|
||||
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;
|
||||
foreach (var pair in m_ActivePrefabMap)
|
||||
{
|
||||
var callback = TableData.GetCell(pair.Key.Item1, pair.Key.Item2)?.OnPrefabRelease;
|
||||
if (callback != null && !callbacks.ContainsKey(pair.Value))
|
||||
callbacks.Add(pair.Value, callback);
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestRefresh(TableDirtyType type, bool immediate)
|
||||
{
|
||||
MarkDirty(type);
|
||||
if (IsCheckingUpdate)
|
||||
{
|
||||
m_UpdateContextStack.Peek().RequiresImmediateRefresh |= immediate;
|
||||
return;
|
||||
}
|
||||
if (immediate)
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
private void ThrowIfCheckingUpdate(string operation)
|
||||
{
|
||||
if (IsCheckingUpdate)
|
||||
throw new InvalidOperationException($"{operation} 不能在 CheckUpdate 作用域内调用。");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28fb43aa1d17ba14b9fa4cf1bd2ad055
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -347,7 +347,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
private void Update()
|
||||
{
|
||||
UpdateSelectionAutoScroll();
|
||||
if (m_IsRebuilding || !m_DirtyFlag.IsDirty) return;
|
||||
if (m_IsRebuilding || IsCheckingUpdate || !m_DirtyFlag.IsDirty) return;
|
||||
|
||||
var flags = m_DirtyFlag.Flags;
|
||||
// 仅滚动或选区变更 → 轻刷新(不重建边框/背景/预制体全回收)
|
||||
@@ -385,6 +385,16 @@ namespace XericUI.XTable.Rendering.Component
|
||||
#region 脏标记与滚动回调
|
||||
|
||||
public void MarkDirty(TableDirtyType type)
|
||||
{
|
||||
if (IsCheckingUpdate)
|
||||
{
|
||||
m_UpdateContextStack.Peek().DirtyFlags |= type;
|
||||
return;
|
||||
}
|
||||
ApplyDirty(type);
|
||||
}
|
||||
|
||||
private void ApplyDirty(TableDirtyType type)
|
||||
{
|
||||
m_DirtyFlag.Mark(type);
|
||||
|
||||
@@ -402,6 +412,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
private void OnScrollValueChanged(Vector2 _)
|
||||
{
|
||||
if (IsCheckingUpdate) return;
|
||||
// 防重入:RefreshViewLight 中操作 content 可能触发 ScrollRect 再次回调
|
||||
if (m_IsRefreshingFromScroll) return;
|
||||
m_IsRefreshingFromScroll = true;
|
||||
|
||||
@@ -147,6 +147,22 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user