From ec0c9a7bc85cdb9babfe940fd47ed720157885aa Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:30:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(analyzer):=20=E6=94=B6=E6=95=9B=20Store=20?= =?UTF-8?q?=E9=95=BF=E6=96=B9=E6=B3=95=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构 Store 的 dispatch 进入提交退出阶段,降低 MA0051 并保持锁与通知语义 - 重构 reducer 快照创建流程,保留多态匹配的稳定排序规则 - 更新 analyzer warning reduction 的恢复点与验证记录 --- GFramework.Core/StateManagement/Store.cs | 242 ++++++++++++------ .../analyzer-warning-reduction-tracking.md | 30 ++- .../analyzer-warning-reduction-trace.md | 30 +++ 3 files changed, 217 insertions(+), 85 deletions(-) diff --git a/GFramework.Core/StateManagement/Store.cs b/GFramework.Core/StateManagement/Store.cs index 789eaea5..f7484734 100644 --- a/GFramework.Core/StateManagement/Store.cs +++ b/GFramework.Core/StateManagement/Store.cs @@ -212,10 +212,6 @@ public sealed class Store : IStore, IStoreDiagnostics ArgumentNullException.ThrowIfNull(action); Action[] listenersSnapshot = Array.Empty>(); - IStoreMiddleware[] middlewaresSnapshot = Array.Empty>(); - IStoreReducerAdapter[] reducersSnapshot = Array.Empty(); - IEqualityComparer stateComparerSnapshot = _stateComparer; - StoreDispatchContext? context = null; TState notificationState = default!; var hasNotification = false; var enteredDispatchScope = false; @@ -224,49 +220,25 @@ public sealed class Store : IStore, IStoreDiagnostics { try { - lock (_lock) - { - EnsureNotDispatching(); - _isDispatching = true; - enteredDispatchScope = true; - context = new StoreDispatchContext(action!, _state); - stateComparerSnapshot = _stateComparer; - middlewaresSnapshot = CreateMiddlewareSnapshotCore(); - reducersSnapshot = CreateReducerSnapshotCore(context.ActionType); - } + var context = EnterDispatchScope( + action, + out var middlewaresSnapshot, + out var reducersSnapshot, + out var stateComparerSnapshot); + + enteredDispatchScope = true; // middleware 和 reducer 可能包含较重的同步逻辑,因此仅持有 dispatch 串行门, // 不占用状态锁,让读取、订阅和注册操作只在需要访问共享状态时短暂阻塞。 ExecuteDispatchPipeline(context, middlewaresSnapshot, reducersSnapshot, stateComparerSnapshot); - lock (_lock) - { - _lastActionType = context.ActionType; - _lastDispatchRecord = new StoreDispatchRecord( - context.Action, - context.PreviousState, - context.NextState, - context.HasStateChanged, - context.DispatchedAt); - - if (!context.HasStateChanged) - { - return; - } - - ApplyCommittedStateChange(context.NextState, context.DispatchedAt, context.Action); - listenersSnapshot = CaptureListenersOrDeferNotification(context.NextState, out notificationState); - hasNotification = listenersSnapshot.Length > 0; - } + hasNotification = TryCommitDispatchResult(context, out listenersSnapshot, out notificationState); } finally { if (enteredDispatchScope) { - lock (_lock) - { - _isDispatching = false; - } + ExitDispatchScope(); } } } @@ -831,6 +803,90 @@ public sealed class Store : IStore, IStoreDiagnostics context.HasStateChanged = !stateComparer.Equals(context.PreviousState, nextState); } + /// + /// 进入一次新的 dispatch 作用域,并在状态锁内抓取本次执行所需的上下文快照。 + /// 该方法只做最短路径的共享状态访问,把 middleware/reducer 的实际执行留到锁外完成。 + /// + /// action 的具体类型。 + /// 本次分发的 action。 + /// 返回本次 dispatch 使用的中间件快照。 + /// 返回本次 dispatch 使用的 reducer 快照。 + /// 返回本次 dispatch 使用的状态比较器快照。 + /// 已初始化的 dispatch 上下文。 + private StoreDispatchContext EnterDispatchScope( + TAction action, + out IStoreMiddleware[] middlewaresSnapshot, + out IStoreReducerAdapter[] reducersSnapshot, + out IEqualityComparer stateComparerSnapshot) + { + Debug.Assert( + Monitor.IsEntered(_dispatchGate), + "Caller must hold _dispatchGate before entering a dispatch scope."); + + lock (_lock) + { + EnsureNotDispatching(); + _isDispatching = true; + + var context = new StoreDispatchContext(action!, _state); + stateComparerSnapshot = _stateComparer; + middlewaresSnapshot = CreateMiddlewareSnapshotCore(); + reducersSnapshot = CreateReducerSnapshotCore(context.ActionType); + return context; + } + } + + /// + /// 在 dispatch 管线执行完成后提交诊断信息和状态变更。 + /// 状态与订阅集合的更新统一在该阶段完成,从而保证 dispatch 与 time-travel 共享同一提交流程。 + /// + /// 刚完成 middleware/reducer 管线的 dispatch 上下文。 + /// 若需要立即通知,则返回锁外回放的监听器快照。 + /// 若需要立即通知,则返回要通知的状态。 + /// 本次 dispatch 是否需要在锁外执行监听器通知。 + private bool TryCommitDispatchResult( + StoreDispatchContext context, + out Action[] listenersSnapshot, + out TState notificationState) + { + Debug.Assert( + Monitor.IsEntered(_dispatchGate), + "Caller must hold _dispatchGate before committing a dispatch result."); + + lock (_lock) + { + _lastActionType = context.ActionType; + _lastDispatchRecord = new StoreDispatchRecord( + context.Action, + context.PreviousState, + context.NextState, + context.HasStateChanged, + context.DispatchedAt); + + if (!context.HasStateChanged) + { + listenersSnapshot = Array.Empty>(); + notificationState = default!; + return false; + } + + ApplyCommittedStateChange(context.NextState, context.DispatchedAt, context.Action); + listenersSnapshot = CaptureListenersOrDeferNotification(context.NextState, out notificationState); + return listenersSnapshot.Length > 0; + } + } + + /// + /// 退出当前 dispatch 作用域,允许后续 dispatch 或历史控制继续进入。 + /// + private void ExitDispatchScope() + { + lock (_lock) + { + _isDispatching = false; + } + } + /// /// 确保当前 Store 没有发生重入分发或在 dispatch 中执行历史控制。 /// @@ -949,20 +1005,65 @@ public sealed class Store : IStore, IStoreDiagnostics if (_actionMatchingMode == StoreActionMatchingMode.ExactTypeOnly) { - if (!_reducers.TryGetValue(actionType, out var exactReducers) || exactReducers.Count == 0) - { - return Array.Empty(); - } - - var exactSnapshot = new IStoreReducerAdapter[exactReducers.Count]; - for (var i = 0; i < exactReducers.Count; i++) - { - exactSnapshot[i] = exactReducers[i].Adapter; - } - - return exactSnapshot; + return CreateExactReducerSnapshot(actionType); } + return CreateAssignableReducerSnapshot(actionType); + } + + /// + /// 为精确类型匹配模式创建 reducer 快照。 + /// + /// 当前 action 的运行时类型。 + /// 精确匹配到的 reducer 快照;若未注册则返回空数组。 + private IStoreReducerAdapter[] CreateExactReducerSnapshot(Type actionType) + { + if (!_reducers.TryGetValue(actionType, out var exactReducers) || exactReducers.Count == 0) + { + return Array.Empty(); + } + + var exactSnapshot = new IStoreReducerAdapter[exactReducers.Count]; + for (var i = 0; i < exactReducers.Count; i++) + { + exactSnapshot[i] = exactReducers[i].Adapter; + } + + return exactSnapshot; + } + + /// + /// 为多态匹配模式创建 reducer 快照。 + /// 该路径会收集所有可赋值的注册桶,并按“精确类型 -> 基类距离 -> 接口 -> 注册顺序”的稳定规则排序。 + /// + /// 当前 action 的运行时类型。 + /// 多态模式下的 reducer 快照;若未注册则返回空数组。 + private IStoreReducerAdapter[] CreateAssignableReducerSnapshot(Type actionType) + { + var matches = CollectReducerMatches(actionType); + if (matches is null || matches.Count == 0) + { + return Array.Empty(); + } + + matches.Sort(CompareReducerMatch); + + var snapshot = new IStoreReducerAdapter[matches.Count]; + for (var i = 0; i < matches.Count; i++) + { + snapshot[i] = matches[i].Adapter; + } + + return snapshot; + } + + /// + /// 收集当前 action 类型可命中的 reducer 注册,并附带稳定排序所需的匹配元数据。 + /// + /// 当前 action 的运行时类型。 + /// 匹配结果列表;若没有任何匹配则返回 + private List? CollectReducerMatches(Type actionType) + { List? matches = null; foreach (var reducerBucket in _reducers) @@ -984,35 +1085,30 @@ public sealed class Store : IStore, IStoreDiagnostics } } - if (matches is null || matches.Count == 0) + return matches; + } + + /// + /// 比较两个 reducer 匹配结果的优先级,保证多态匹配下的执行顺序稳定可预测。 + /// + /// 左侧匹配结果。 + /// 右侧匹配结果。 + /// 排序比较结果。 + private static int CompareReducerMatch(ReducerMatch left, ReducerMatch right) + { + var categoryComparison = left.MatchCategory.CompareTo(right.MatchCategory); + if (categoryComparison != 0) { - return Array.Empty(); + return categoryComparison; } - matches.Sort(static (left, right) => + var distanceComparison = left.InheritanceDistance.CompareTo(right.InheritanceDistance); + if (distanceComparison != 0) { - var categoryComparison = left.MatchCategory.CompareTo(right.MatchCategory); - if (categoryComparison != 0) - { - return categoryComparison; - } - - var distanceComparison = left.InheritanceDistance.CompareTo(right.InheritanceDistance); - if (distanceComparison != 0) - { - return distanceComparison; - } - - return left.Sequence.CompareTo(right.Sequence); - }); - - var snapshot = new IStoreReducerAdapter[matches.Count]; - for (var i = 0; i < matches.Count; i++) - { - snapshot[i] = matches[i].Adapter; + return distanceComparison; } - return snapshot; + return left.Sequence.CompareTo(right.Sequence); } /// @@ -1412,4 +1508,4 @@ public sealed class Store : IStore, IStoreDiagnostics /// public TState PendingState { get; set; } = default!; } -} \ No newline at end of file +} diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md index 2e7b710f..5f56a056 100644 --- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md +++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md @@ -7,22 +7,21 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-005` -- 当前阶段:`Phase 5` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-006` +- 当前阶段:`Phase 6` - 当前焦点: - - 已完成 `GFramework.Core/Pause/PauseStackManager.cs` 的 `MA0051` 收口:将 `DestroyAsync` 与 `Pop` 拆分为锁内状态迁移、 - 栈调整和锁外通知三个阶段,同时保持日志、事件与销毁补发语义不变 - - 已为销毁路径补充 `PauseStackManagerTests.DestroyAsync_Should_NotifyResumedGroups` 回归测试,覆盖“销毁时向所有仍暂停组补发恢复通知” - - 下一轮若继续推进,优先在 `CoroutineScheduler` 或 `Store` 的剩余 `MA0051` 中只选一个切入点,不回到已完成的 - `PauseStackManager` + - 已完成 `GFramework.Core/StateManagement/Store.cs` 的 `MA0051` 收口:将 `Dispatch` 拆分为“进入分发域 / 提交结果 / 退出分发域” + 三个辅助阶段,并将 reducer 快照创建拆分为精确匹配与多态匹配两条路径 + - 本轮保持 `Store` 的锁顺序、middleware 执行时机、batch 通知折叠和多态 reducer 排序规则不变,未改公共 API + - 下一轮若继续推进,优先只处理 `GFramework.Core/Coroutine/CoroutineScheduler.cs` 的剩余 `MA0051`,不回到已完成的 + `Store` 或 `PauseStackManager` ## 当前状态摘要 - 已完成 `GFramework.Core`、`GFramework.Cqrs`、`GFramework.Godot` 与部分 source generator 的低风险 warning 清理 - 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险 -- 当前 `PauseStackManager` 的长方法 warning 已从 active 入口移除;主题内剩余 warning 主要集中在 - `GFramework.Core/Coroutine/CoroutineScheduler.cs`、`GFramework.Core/StateManagement/Store.cs`、文件/类型命名冲突、 - delegate 形状和少量公共集合抽象接口问题 +- 当前 `PauseStackManager` 与 `Store` 的长方法 warning 已从 active 入口移除;主题内剩余 warning 主要集中在 + `GFramework.Core/Coroutine/CoroutineScheduler.cs`、文件/类型命名冲突、delegate 形状和少量公共集合抽象接口问题 ## 当前活跃事实 @@ -32,6 +31,8 @@ - `RP-003` 已在不改生命周期契约的前提下完成 `ArchitectureLifecycle` 初始化主流程拆分,并通过定向 build/test 验证 - `RP-004` 已完成当前 PR review follow-up:修复 `TryCreateGeneratedRegistry` 的可空 `out` 契约并清理 trace 文档重复标题 - `RP-005` 已在不改公共 API 的前提下完成 `PauseStackManager` 两个 `MA0051` 的结构拆分,并补充销毁通知回归测试 +- `RP-006` 已在不改公共 API 的前提下完成 `Store` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证 dispatch、 + 多态 reducer 匹配与历史语义未回归 - 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射 ## 当前风险 @@ -65,11 +66,16 @@ - 结果:`27 Warning(s)`,`0 Error(s)`;`PauseStackManager.cs` 已不再出现在 `MA0051` 列表中 - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~PauseStackManagerTests -p:RestoreFallbackFolders=` - 结果:`25 Passed`,`0 Failed` +- `RP-006` 的定向验证结果: + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -p:RestorePackagesPath= -nologo -clp:"Summary;WarningsOnly"` + - 结果:`25 Warning(s)`,`0 Error(s)`;`Store.cs` 已不再出现在 `MA0051` 列表中 + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~StoreTests -p:RestoreFallbackFolders="" -p:RestorePackagesPath= -nologo` + - 结果:`30 Passed`,`0 Failed` - active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史 ## 下一步 1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录 -2. 优先在 `GFramework.Core/Coroutine/CoroutineScheduler.cs` 与 `GFramework.Core/StateManagement/Store.cs` - 的 `MA0051` 中只选一个继续,不要在同一轮同时扩多个风险面 +2. 优先在 `GFramework.Core/Coroutine/CoroutineScheduler.cs` 的 `Run` 与 `FinalizeCoroutine` 两个 `MA0051` + 中继续,保持“单文件、单 warning family”的节奏 3. 若本主题确认暂缓,可保持当前归档状态,不需要再恢复 `local-plan/` diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md index 33fb83c8..088f0a7b 100644 --- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md +++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md @@ -1,5 +1,35 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-21 — RP-006 + +### 阶段:Store `MA0051` 收口(RP-006) + +- 依据 active tracking 中“继续只选一个 `GFramework.Core` 结构性切入点”的约束,本轮选择 + `GFramework.Core/StateManagement/Store.cs`,因为该文件的两个 `MA0051` 都集中在 dispatch / reducer snapshot 逻辑, + 且已有 `StoreTests` 覆盖 dispatch、batch、history 和多态 reducer 匹配语义 +- 在正式验证前先处理 WSL 环境噪音:当前 worktree 的 `GFramework.Core/obj/project.assets.json` 是 Windows 侧 restore + 产物,`--no-restore` 构建会继续引用宿主 Windows fallback package folder;本轮先执行一次 Linux 侧 + `dotnet restore GFramework.Core/GFramework.Core.csproj -p:RestoreFallbackFolders="" -p:RestorePackagesPath= --ignore-failed-sources -nologo` + 刷新资产文件,再继续 warnings-only build +- 将 `Dispatch` 拆分为: + - `EnterDispatchScope` + - `TryCommitDispatchResult` + - `ExitDispatchScope` +- 将 `CreateReducerSnapshotCore` 拆分为: + - `CreateExactReducerSnapshot` + - `CreateAssignableReducerSnapshot` + - `CollectReducerMatches` + - `CompareReducerMatch` +- 保持 `_dispatchGate -> _lock` 的锁顺序、middleware 锁外执行、批处理通知折叠以及“精确类型 -> 基类 -> 接口 -> + 注册顺序”的 reducer 稳定排序语义不变,只收缩主方法长度并补齐辅助方法意图注释 +- 验证通过: + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -p:RestorePackagesPath= -nologo -clp:"Summary;WarningsOnly"` + - 结果:`25 Warning(s)`,`0 Error(s)`;`Store.cs` 已不再出现在 `MA0051` 列表 + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~StoreTests -p:RestoreFallbackFolders="" -p:RestorePackagesPath= -nologo` + - 结果:`30 Passed`,`0 Failed` +- 下一步保持同一节奏:只在 `CoroutineScheduler.cs` 的 `Run` / `FinalizeCoroutine` 两个 `MA0051` 中继续,不与其他 + warning 家族混做 + ## 2026-04-21 — RP-005 ### 阶段:PauseStackManager `MA0051` 收口(RP-005)