mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(analyzer): 收敛 Store 长方法 warning
- 重构 Store 的 dispatch 进入提交退出阶段,降低 MA0051 并保持锁与通知语义 - 重构 reducer 快照创建流程,保留多态匹配的稳定排序规则 - 更新 analyzer warning reduction 的恢复点与验证记录
This commit is contained in:
parent
b553d7cbc6
commit
ec0c9a7bc8
@ -212,10 +212,6 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
|
||||
ArgumentNullException.ThrowIfNull(action);
|
||||
|
||||
Action<TState>[] listenersSnapshot = Array.Empty<Action<TState>>();
|
||||
IStoreMiddleware<TState>[] middlewaresSnapshot = Array.Empty<IStoreMiddleware<TState>>();
|
||||
IStoreReducerAdapter[] reducersSnapshot = Array.Empty<IStoreReducerAdapter>();
|
||||
IEqualityComparer<TState> stateComparerSnapshot = _stateComparer;
|
||||
StoreDispatchContext<TState>? context = null;
|
||||
TState notificationState = default!;
|
||||
var hasNotification = false;
|
||||
var enteredDispatchScope = false;
|
||||
@ -224,49 +220,25 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
EnsureNotDispatching();
|
||||
_isDispatching = true;
|
||||
enteredDispatchScope = true;
|
||||
context = new StoreDispatchContext<TState>(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<TState>(
|
||||
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<TState> : IStore<TState>, IStoreDiagnostics<TState>
|
||||
context.HasStateChanged = !stateComparer.Equals(context.PreviousState, nextState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入一次新的 dispatch 作用域,并在状态锁内抓取本次执行所需的上下文快照。
|
||||
/// 该方法只做最短路径的共享状态访问,把 middleware/reducer 的实际执行留到锁外完成。
|
||||
/// </summary>
|
||||
/// <typeparam name="TAction">action 的具体类型。</typeparam>
|
||||
/// <param name="action">本次分发的 action。</param>
|
||||
/// <param name="middlewaresSnapshot">返回本次 dispatch 使用的中间件快照。</param>
|
||||
/// <param name="reducersSnapshot">返回本次 dispatch 使用的 reducer 快照。</param>
|
||||
/// <param name="stateComparerSnapshot">返回本次 dispatch 使用的状态比较器快照。</param>
|
||||
/// <returns>已初始化的 dispatch 上下文。</returns>
|
||||
private StoreDispatchContext<TState> EnterDispatchScope<TAction>(
|
||||
TAction action,
|
||||
out IStoreMiddleware<TState>[] middlewaresSnapshot,
|
||||
out IStoreReducerAdapter[] reducersSnapshot,
|
||||
out IEqualityComparer<TState> stateComparerSnapshot)
|
||||
{
|
||||
Debug.Assert(
|
||||
Monitor.IsEntered(_dispatchGate),
|
||||
"Caller must hold _dispatchGate before entering a dispatch scope.");
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
EnsureNotDispatching();
|
||||
_isDispatching = true;
|
||||
|
||||
var context = new StoreDispatchContext<TState>(action!, _state);
|
||||
stateComparerSnapshot = _stateComparer;
|
||||
middlewaresSnapshot = CreateMiddlewareSnapshotCore();
|
||||
reducersSnapshot = CreateReducerSnapshotCore(context.ActionType);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 dispatch 管线执行完成后提交诊断信息和状态变更。
|
||||
/// 状态与订阅集合的更新统一在该阶段完成,从而保证 dispatch 与 time-travel 共享同一提交流程。
|
||||
/// </summary>
|
||||
/// <param name="context">刚完成 middleware/reducer 管线的 dispatch 上下文。</param>
|
||||
/// <param name="listenersSnapshot">若需要立即通知,则返回锁外回放的监听器快照。</param>
|
||||
/// <param name="notificationState">若需要立即通知,则返回要通知的状态。</param>
|
||||
/// <returns>本次 dispatch 是否需要在锁外执行监听器通知。</returns>
|
||||
private bool TryCommitDispatchResult(
|
||||
StoreDispatchContext<TState> context,
|
||||
out Action<TState>[] 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<TState>(
|
||||
context.Action,
|
||||
context.PreviousState,
|
||||
context.NextState,
|
||||
context.HasStateChanged,
|
||||
context.DispatchedAt);
|
||||
|
||||
if (!context.HasStateChanged)
|
||||
{
|
||||
listenersSnapshot = Array.Empty<Action<TState>>();
|
||||
notificationState = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplyCommittedStateChange(context.NextState, context.DispatchedAt, context.Action);
|
||||
listenersSnapshot = CaptureListenersOrDeferNotification(context.NextState, out notificationState);
|
||||
return listenersSnapshot.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退出当前 dispatch 作用域,允许后续 dispatch 或历史控制继续进入。
|
||||
/// </summary>
|
||||
private void ExitDispatchScope()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_isDispatching = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保当前 Store 没有发生重入分发或在 dispatch 中执行历史控制。
|
||||
/// </summary>
|
||||
@ -949,20 +1005,65 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
|
||||
|
||||
if (_actionMatchingMode == StoreActionMatchingMode.ExactTypeOnly)
|
||||
{
|
||||
if (!_reducers.TryGetValue(actionType, out var exactReducers) || exactReducers.Count == 0)
|
||||
{
|
||||
return Array.Empty<IStoreReducerAdapter>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为精确类型匹配模式创建 reducer 快照。
|
||||
/// </summary>
|
||||
/// <param name="actionType">当前 action 的运行时类型。</param>
|
||||
/// <returns>精确匹配到的 reducer 快照;若未注册则返回空数组。</returns>
|
||||
private IStoreReducerAdapter[] CreateExactReducerSnapshot(Type actionType)
|
||||
{
|
||||
if (!_reducers.TryGetValue(actionType, out var exactReducers) || exactReducers.Count == 0)
|
||||
{
|
||||
return Array.Empty<IStoreReducerAdapter>();
|
||||
}
|
||||
|
||||
var exactSnapshot = new IStoreReducerAdapter[exactReducers.Count];
|
||||
for (var i = 0; i < exactReducers.Count; i++)
|
||||
{
|
||||
exactSnapshot[i] = exactReducers[i].Adapter;
|
||||
}
|
||||
|
||||
return exactSnapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为多态匹配模式创建 reducer 快照。
|
||||
/// 该路径会收集所有可赋值的注册桶,并按“精确类型 -> 基类距离 -> 接口 -> 注册顺序”的稳定规则排序。
|
||||
/// </summary>
|
||||
/// <param name="actionType">当前 action 的运行时类型。</param>
|
||||
/// <returns>多态模式下的 reducer 快照;若未注册则返回空数组。</returns>
|
||||
private IStoreReducerAdapter[] CreateAssignableReducerSnapshot(Type actionType)
|
||||
{
|
||||
var matches = CollectReducerMatches(actionType);
|
||||
if (matches is null || matches.Count == 0)
|
||||
{
|
||||
return Array.Empty<IStoreReducerAdapter>();
|
||||
}
|
||||
|
||||
matches.Sort(CompareReducerMatch);
|
||||
|
||||
var snapshot = new IStoreReducerAdapter[matches.Count];
|
||||
for (var i = 0; i < matches.Count; i++)
|
||||
{
|
||||
snapshot[i] = matches[i].Adapter;
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收集当前 action 类型可命中的 reducer 注册,并附带稳定排序所需的匹配元数据。
|
||||
/// </summary>
|
||||
/// <param name="actionType">当前 action 的运行时类型。</param>
|
||||
/// <returns>匹配结果列表;若没有任何匹配则返回 <see langword="null"/>。</returns>
|
||||
private List<ReducerMatch>? CollectReducerMatches(Type actionType)
|
||||
{
|
||||
List<ReducerMatch>? matches = null;
|
||||
|
||||
foreach (var reducerBucket in _reducers)
|
||||
@ -984,35 +1085,30 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
|
||||
}
|
||||
}
|
||||
|
||||
if (matches is null || matches.Count == 0)
|
||||
return matches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 比较两个 reducer 匹配结果的优先级,保证多态匹配下的执行顺序稳定可预测。
|
||||
/// </summary>
|
||||
/// <param name="left">左侧匹配结果。</param>
|
||||
/// <param name="right">右侧匹配结果。</param>
|
||||
/// <returns>排序比较结果。</returns>
|
||||
private static int CompareReducerMatch(ReducerMatch left, ReducerMatch right)
|
||||
{
|
||||
var categoryComparison = left.MatchCategory.CompareTo(right.MatchCategory);
|
||||
if (categoryComparison != 0)
|
||||
{
|
||||
return Array.Empty<IStoreReducerAdapter>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1412,4 +1508,4 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
|
||||
/// </summary>
|
||||
public TState PendingState { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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=<linux-nuget-cache> -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=<linux-nuget-cache> -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/`
|
||||
|
||||
@ -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=<linux-nuget-cache> --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=<linux-nuget-cache> -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=<linux-nuget-cache> -nologo`
|
||||
- 结果:`30 Passed`,`0 Failed`
|
||||
- 下一步保持同一节奏:只在 `CoroutineScheduler.cs` 的 `Run` / `FinalizeCoroutine` 两个 `MA0051` 中继续,不与其他
|
||||
warning 家族混做
|
||||
|
||||
## 2026-04-21 — RP-005
|
||||
|
||||
### 阶段:PauseStackManager `MA0051` 收口(RP-005)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user