From e5da5aa80157cf46205d88ac9d6b1c09f59f37a5 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 24 Mar 2026 19:45:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor(state):=20=E5=B0=86=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E5=B1=9E=E6=80=A7=E6=94=B9=E4=B8=BA=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=B9=B6=E4=BC=98=E5=8C=96=E6=89=B9=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 HistoryEntries 属性替换为 GetHistoryEntriesSnapshot() 方法,明确表达会返回集合副本 - 在批处理清理逻辑中添加 else 条件避免无效操作 - 更新测试代码使用新的快照获取方法 - 添加 System.Diagnostics 命名空间引用 - 修复批处理深度为零时的异常处理逻辑 --- .../StateManagement/IStoreDiagnostics.cs | 13 +- .../StateManagement/StoreTests.cs | 5 +- GFramework.Core/StateManagement/Store.cs | 262 +++++++++--------- 3 files changed, 142 insertions(+), 138 deletions(-) diff --git a/GFramework.Core.Abstractions/StateManagement/IStoreDiagnostics.cs b/GFramework.Core.Abstractions/StateManagement/IStoreDiagnostics.cs index e852b79..73b5aab 100644 --- a/GFramework.Core.Abstractions/StateManagement/IStoreDiagnostics.cs +++ b/GFramework.Core.Abstractions/StateManagement/IStoreDiagnostics.cs @@ -52,15 +52,16 @@ public interface IStoreDiagnostics /// int HistoryIndex { get; } - /// - /// 获取当前历史快照列表。 - /// 调试工具可以基于该列表渲染时间旅行面板,而不需要访问 Store 内部结构。 - /// - IReadOnlyList> HistoryEntries { get; } - /// /// 获取当前是否处于批处理阶段。 /// 该值为 时,状态变更通知会延迟到最外层批处理结束后再统一发送。 /// bool IsBatching { get; } + + /// + /// 获取当前历史快照列表的只读快照。 + /// 该方法会返回一份独立快照,供调试工具渲染时间旅行面板,而不暴露 Store 的内部可变集合。 + /// + /// 当前历史快照列表;若未启用历史记录或当前没有历史,则返回空数组。 + IReadOnlyList> GetHistoryEntriesSnapshot(); } \ No newline at end of file diff --git a/GFramework.Core.Tests/StateManagement/StoreTests.cs b/GFramework.Core.Tests/StateManagement/StoreTests.cs index c3fbca0..8ecbbdf 100644 --- a/GFramework.Core.Tests/StateManagement/StoreTests.cs +++ b/GFramework.Core.Tests/StateManagement/StoreTests.cs @@ -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 })); } /// @@ -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)); } /// diff --git a/GFramework.Core/StateManagement/Store.cs b/GFramework.Core/StateManagement/Store.cs index 28c93b1..789eaea 100644 --- a/GFramework.Core/StateManagement/Store.cs +++ b/GFramework.Core/StateManagement/Store.cs @@ -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 : IStore, IStoreDiagnostics } } - /// - /// 获取最近一次分发的 action 类型。 - /// - public Type? LastActionType - { - get - { - lock (_lock) - { - return _lastActionType; - } - } - } - - /// - /// 获取最近一次真正改变状态的时间戳。 - /// - public DateTimeOffset? LastStateChangedAt - { - get - { - lock (_lock) - { - return _lastStateChangedAt; - } - } - } - - /// - /// 获取当前历史快照列表。 - /// - public IReadOnlyList> HistoryEntries - { - get - { - lock (_lock) - { - return _history.Count == 0 ? Array.Empty>() : _history.ToArray(); - } - } - } - /// /// 获取当前状态快照。 /// @@ -369,86 +328,6 @@ public sealed class Store : IStore, IStoreDiagnostics } } - /// - /// 获取当前订阅者数量。 - /// - public int SubscriberCount - { - get - { - lock (_lock) - { - return _listeners.Count; - } - } - } - - /// - /// 获取最近一次分发记录。 - /// - public StoreDispatchRecord? LastDispatchRecord - { - get - { - lock (_lock) - { - return _lastDispatchRecord; - } - } - } - - /// - /// 获取当前 Store 使用的 action 匹配策略。 - /// - public StoreActionMatchingMode ActionMatchingMode => _actionMatchingMode; - - /// - /// 获取历史缓冲区容量。 - /// - public int HistoryCapacity => _historyCapacity; - - /// - /// 获取当前可见历史记录数量。 - /// - public int HistoryCount - { - get - { - lock (_lock) - { - return _history.Count; - } - } - } - - /// - /// 获取当前状态在历史缓冲区中的索引。 - /// - public int HistoryIndex - { - get - { - lock (_lock) - { - return _historyIndex; - } - } - } - - /// - /// 获取当前是否处于批处理阶段。 - /// - public bool IsBatching - { - get - { - lock (_lock) - { - return _batchDepth > 0; - } - } - } - /// /// 将多个状态操作合并到一个批处理中执行。 /// 批处理内的状态变化会立即提交,但通知会在最外层批处理结束后折叠为一次最终回放。 @@ -477,16 +356,18 @@ public sealed class Store : IStore, IStoreDiagnostics { 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 : IStore, IStoreDiagnostics } } + /// + /// 获取最近一次分发的 action 类型。 + /// + public Type? LastActionType + { + get + { + lock (_lock) + { + return _lastActionType; + } + } + } + + /// + /// 获取最近一次真正改变状态的时间戳。 + /// + public DateTimeOffset? LastStateChangedAt + { + get + { + lock (_lock) + { + return _lastStateChangedAt; + } + } + } + + /// + /// 获取当前订阅者数量。 + /// + public int SubscriberCount + { + get + { + lock (_lock) + { + return _listeners.Count; + } + } + } + + /// + /// 获取最近一次分发记录。 + /// + public StoreDispatchRecord? LastDispatchRecord + { + get + { + lock (_lock) + { + return _lastDispatchRecord; + } + } + } + + /// + /// 获取当前 Store 使用的 action 匹配策略。 + /// + public StoreActionMatchingMode ActionMatchingMode => _actionMatchingMode; + + /// + /// 获取历史缓冲区容量。 + /// + public int HistoryCapacity => _historyCapacity; + + /// + /// 获取当前可见历史记录数量。 + /// + public int HistoryCount + { + get + { + lock (_lock) + { + return _history.Count; + } + } + } + + /// + /// 获取当前状态在历史缓冲区中的索引。 + /// + public int HistoryIndex + { + get + { + lock (_lock) + { + return _historyIndex; + } + } + } + + /// + /// 获取当前是否处于批处理阶段。 + /// + public bool IsBatching + { + get + { + lock (_lock) + { + return _batchDepth > 0; + } + } + } + + /// + /// 获取当前历史快照列表的只读快照。 + /// 该方法以方法语义显式表达会分配并返回集合副本,避免把快照克隆隐藏在属性访问中。 + /// + /// 当前历史快照列表;若未启用历史记录或当前没有历史,则返回空数组。 + public IReadOnlyList> GetHistoryEntriesSnapshot() + { + lock (_lock) + { + return _history.Count == 0 ? Array.Empty>() : _history.ToArray(); + } + } + /// /// 创建一个用于当前状态类型的 Store 构建器。 ///