refactor(state): 将历史快照属性改为方法并优化批处理逻辑

- 将 HistoryEntries 属性替换为 GetHistoryEntriesSnapshot() 方法,明确表达会返回集合副本
- 在批处理清理逻辑中添加 else 条件避免无效操作
- 更新测试代码使用新的快照获取方法
- 添加 System.Diagnostics 命名空间引用
- 修复批处理深度为零时的异常处理逻辑
This commit is contained in:
GeWuYou 2026-03-24 19:45:59 +08:00
parent bd29475748
commit e5da5aa801
3 changed files with 142 additions and 138 deletions

View File

@ -52,15 +52,16 @@ public interface IStoreDiagnostics<TState>
/// </summary>
int HistoryIndex { get; }
/// <summary>
/// 获取当前历史快照列表。
/// 调试工具可以基于该列表渲染时间旅行面板,而不需要访问 Store 内部结构。
/// </summary>
IReadOnlyList<StoreHistoryEntry<TState>> HistoryEntries { get; }
/// <summary>
/// 获取当前是否处于批处理阶段。
/// 该值为 <see langword="true"/> 时,状态变更通知会延迟到最外层批处理结束后再统一发送。
/// </summary>
bool IsBatching { get; }
/// <summary>
/// 获取当前历史快照列表的只读快照。
/// 该方法会返回一份独立快照,供调试工具渲染时间旅行面板,而不暴露 Store 的内部可变集合。
/// </summary>
/// <returns>当前历史快照列表;若未启用历史记录或当前没有历史,则返回空数组。</returns>
IReadOnlyList<StoreHistoryEntry<TState>> GetHistoryEntriesSnapshot();
}

View File

@ -536,7 +536,8 @@ public class StoreTests
Assert.That(store.State.Count, Is.EqualTo(12));
Assert.That(store.CanRedo, Is.False, "新 dispatch 应清除 redo 分支");
Assert.That(store.HistoryEntries.Select(entry => entry.State.Count), Is.EqualTo(new[] { 0, 1, 2, 12 }));
Assert.That(store.GetHistoryEntriesSnapshot().Select(entry => entry.State.Count),
Is.EqualTo(new[] { 0, 1, 2, 12 }));
}
/// <summary>
@ -556,7 +557,7 @@ public class StoreTests
Assert.That(store.HistoryCount, Is.EqualTo(1));
Assert.That(store.HistoryIndex, Is.EqualTo(0));
Assert.That(store.CanUndo, Is.False);
Assert.That(store.HistoryEntries[0].State.Count, Is.EqualTo(2));
Assert.That(store.GetHistoryEntriesSnapshot()[0].State.Count, Is.EqualTo(2));
}
/// <summary>

View File

@ -1,3 +1,4 @@
using System.Diagnostics;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.StateManagement;
using GFramework.Core.Events;
@ -157,48 +158,6 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
}
}
/// <summary>
/// 获取最近一次分发的 action 类型。
/// </summary>
public Type? LastActionType
{
get
{
lock (_lock)
{
return _lastActionType;
}
}
}
/// <summary>
/// 获取最近一次真正改变状态的时间戳。
/// </summary>
public DateTimeOffset? LastStateChangedAt
{
get
{
lock (_lock)
{
return _lastStateChangedAt;
}
}
}
/// <summary>
/// 获取当前历史快照列表。
/// </summary>
public IReadOnlyList<StoreHistoryEntry<TState>> HistoryEntries
{
get
{
lock (_lock)
{
return _history.Count == 0 ? Array.Empty<StoreHistoryEntry<TState>>() : _history.ToArray();
}
}
}
/// <summary>
/// 获取当前状态快照。
/// </summary>
@ -369,86 +328,6 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
}
}
/// <summary>
/// 获取当前订阅者数量。
/// </summary>
public int SubscriberCount
{
get
{
lock (_lock)
{
return _listeners.Count;
}
}
}
/// <summary>
/// 获取最近一次分发记录。
/// </summary>
public StoreDispatchRecord<TState>? LastDispatchRecord
{
get
{
lock (_lock)
{
return _lastDispatchRecord;
}
}
}
/// <summary>
/// 获取当前 Store 使用的 action 匹配策略。
/// </summary>
public StoreActionMatchingMode ActionMatchingMode => _actionMatchingMode;
/// <summary>
/// 获取历史缓冲区容量。
/// </summary>
public int HistoryCapacity => _historyCapacity;
/// <summary>
/// 获取当前可见历史记录数量。
/// </summary>
public int HistoryCount
{
get
{
lock (_lock)
{
return _history.Count;
}
}
}
/// <summary>
/// 获取当前状态在历史缓冲区中的索引。
/// </summary>
public int HistoryIndex
{
get
{
lock (_lock)
{
return _historyIndex;
}
}
}
/// <summary>
/// 获取当前是否处于批处理阶段。
/// </summary>
public bool IsBatching
{
get
{
lock (_lock)
{
return _batchDepth > 0;
}
}
}
/// <summary>
/// 将多个状态操作合并到一个批处理中执行。
/// 批处理内的状态变化会立即提交,但通知会在最外层批处理结束后折叠为一次最终回放。
@ -477,16 +356,18 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
{
if (_batchDepth == 0)
{
throw new InvalidOperationException("Batch depth is already zero.");
Debug.Fail("Batch depth is already zero during RunInBatch cleanup.");
}
_batchDepth--;
if (_batchDepth == 0 && _hasPendingBatchNotification)
else
{
notificationState = _pendingBatchState;
_pendingBatchState = default!;
_hasPendingBatchNotification = false;
listenersSnapshot = SnapshotListenersForNotification(notificationState);
_batchDepth--;
if (_batchDepth == 0 && _hasPendingBatchNotification)
{
notificationState = _pendingBatchState;
_pendingBatchState = default!;
_hasPendingBatchNotification = false;
listenersSnapshot = SnapshotListenersForNotification(notificationState);
}
}
}
}
@ -598,6 +479,127 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
}
}
/// <summary>
/// 获取最近一次分发的 action 类型。
/// </summary>
public Type? LastActionType
{
get
{
lock (_lock)
{
return _lastActionType;
}
}
}
/// <summary>
/// 获取最近一次真正改变状态的时间戳。
/// </summary>
public DateTimeOffset? LastStateChangedAt
{
get
{
lock (_lock)
{
return _lastStateChangedAt;
}
}
}
/// <summary>
/// 获取当前订阅者数量。
/// </summary>
public int SubscriberCount
{
get
{
lock (_lock)
{
return _listeners.Count;
}
}
}
/// <summary>
/// 获取最近一次分发记录。
/// </summary>
public StoreDispatchRecord<TState>? LastDispatchRecord
{
get
{
lock (_lock)
{
return _lastDispatchRecord;
}
}
}
/// <summary>
/// 获取当前 Store 使用的 action 匹配策略。
/// </summary>
public StoreActionMatchingMode ActionMatchingMode => _actionMatchingMode;
/// <summary>
/// 获取历史缓冲区容量。
/// </summary>
public int HistoryCapacity => _historyCapacity;
/// <summary>
/// 获取当前可见历史记录数量。
/// </summary>
public int HistoryCount
{
get
{
lock (_lock)
{
return _history.Count;
}
}
}
/// <summary>
/// 获取当前状态在历史缓冲区中的索引。
/// </summary>
public int HistoryIndex
{
get
{
lock (_lock)
{
return _historyIndex;
}
}
}
/// <summary>
/// 获取当前是否处于批处理阶段。
/// </summary>
public bool IsBatching
{
get
{
lock (_lock)
{
return _batchDepth > 0;
}
}
}
/// <summary>
/// 获取当前历史快照列表的只读快照。
/// 该方法以方法语义显式表达会分配并返回集合副本,避免把快照克隆隐藏在属性访问中。
/// </summary>
/// <returns>当前历史快照列表;若未启用历史记录或当前没有历史,则返回空数组。</returns>
public IReadOnlyList<StoreHistoryEntry<TState>> GetHistoryEntriesSnapshot()
{
lock (_lock)
{
return _history.Count == 0 ? Array.Empty<StoreHistoryEntry<TState>>() : _history.ToArray();
}
}
/// <summary>
/// 创建一个用于当前状态类型的 Store 构建器。
/// </summary>