mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
refactor(pause): 收口 PauseStackManager 长方法告警
- 重构 PauseStackManager 的销毁与 Pop 流程,拆分锁内状态迁移与锁外通知阶段 - 新增 PauseStackManager 销毁恢复通知回归测试,覆盖多暂停组销毁补发行为 - 更新 analyzer warning reduction 主题的 active tracking 与 trace,记录 RP-005 验证结果和下一恢复点
This commit is contained in:
parent
358b1e9cca
commit
ff1996e81b
@ -394,6 +394,35 @@ public class PauseStackManagerTests
|
||||
Assert.That(_manager.IsPaused(PauseGroup.Audio), Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证销毁时会向所有仍暂停的组补发恢复通知。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task DestroyAsync_Should_NotifyResumedGroups()
|
||||
{
|
||||
var resumedGroups = new List<PauseGroup>();
|
||||
var mockHandler = new MockPauseHandler();
|
||||
|
||||
_manager.RegisterHandler(mockHandler);
|
||||
_manager.OnPauseStateChanged += (_, e) =>
|
||||
{
|
||||
if (!e.IsPaused)
|
||||
{
|
||||
resumedGroups.Add(e.Group);
|
||||
}
|
||||
};
|
||||
|
||||
_manager.Push("Global", PauseGroup.Global);
|
||||
_manager.Push("Gameplay", PauseGroup.Gameplay);
|
||||
mockHandler.Reset();
|
||||
|
||||
await _manager.DestroyAsync();
|
||||
|
||||
Assert.That(mockHandler.CallCount, Is.EqualTo(2));
|
||||
Assert.That(mockHandler.LastIsPaused, Is.False);
|
||||
Assert.That(resumedGroups, Is.EquivalentTo(new[] { PauseGroup.Global, PauseGroup.Gameplay }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证并发Push是线程安全的
|
||||
/// </summary>
|
||||
@ -469,4 +498,4 @@ public class PauseStackManagerTests
|
||||
LastIsPaused = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,69 +26,17 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
|
||||
public ValueTask DestroyAsync()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
|
||||
List<PauseGroup> pausedGroups;
|
||||
IPauseHandler[] handlersSnapshot;
|
||||
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_disposed)
|
||||
return ValueTask.CompletedTask;
|
||||
|
||||
_disposed = true;
|
||||
|
||||
// 收集所有当前暂停的组
|
||||
pausedGroups = _pauseStacks
|
||||
.Where(kvp => kvp.Value.Count > 0)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
|
||||
// 获取处理器快照
|
||||
handlersSnapshot = _handlers.ToArray();
|
||||
|
||||
// 清理所有数据结构
|
||||
_pauseStacks.Clear();
|
||||
_tokenMap.Clear();
|
||||
_handlers.Clear();
|
||||
|
||||
_logger.Debug("PauseStackManager destroyed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
// 在锁外通知所有之前暂停的组恢复,保持生命周期信号一致
|
||||
foreach (var group in pausedGroups)
|
||||
var destroySnapshot = TryBeginDestroy();
|
||||
if (destroySnapshot == null)
|
||||
{
|
||||
_logger.Debug($"Notifying handlers of destruction: Group={group}, IsPaused=false");
|
||||
|
||||
foreach (var handler in handlersSnapshot.OrderBy(h => h.Priority))
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.OnPauseStateChanged(group, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Handler {handler.GetType().Name} failed during destruction", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
try
|
||||
{
|
||||
RaisePauseStateChanged(group, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Event subscriber failed during destruction for group {group}", ex);
|
||||
}
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
// 释放锁资源
|
||||
NotifyDestroyedGroups(destroySnapshot.Value);
|
||||
_lock.Dispose();
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
@ -163,74 +111,17 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
|
||||
public bool Pop(PauseToken token)
|
||||
{
|
||||
if (!token.IsValid)
|
||||
{
|
||||
return false;
|
||||
|
||||
bool found;
|
||||
bool shouldNotify = false;
|
||||
PauseGroup notifyGroup = PauseGroup.Global;
|
||||
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (!_tokenMap.TryGetValue(token.Id, out var entry))
|
||||
{
|
||||
_logger.Warn($"Attempted to pop invalid/expired token: {token.Id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var group = entry.Group;
|
||||
var stack = _pauseStacks[group];
|
||||
var wasPaused = stack.Count > 0;
|
||||
|
||||
// 从栈中移除
|
||||
var tempStack = new Stack<PauseEntry>();
|
||||
found = false;
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var current = stack.Pop();
|
||||
if (current.TokenId == token.Id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempStack.Push(current);
|
||||
}
|
||||
|
||||
// 恢复栈结构
|
||||
while (tempStack.Count > 0)
|
||||
{
|
||||
stack.Push(tempStack.Pop());
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
_tokenMap.Remove(token.Id);
|
||||
_logger.Debug($"Pause popped: {entry.Reason} (Group: {group}, Remaining: {stack.Count})");
|
||||
|
||||
// 状态变化检测:从暂停 → 未暂停
|
||||
if (wasPaused && stack.Count == 0)
|
||||
{
|
||||
shouldNotify = true;
|
||||
notifyGroup = group;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
// 在锁外通知处理器,避免死锁
|
||||
if (shouldNotify)
|
||||
var result = TryPopEntry(token);
|
||||
if (result.ShouldNotify)
|
||||
{
|
||||
NotifyHandlers(notifyGroup, false);
|
||||
NotifyHandlers(result.NotifyGroup, false);
|
||||
}
|
||||
|
||||
return found;
|
||||
return result.Found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -443,6 +334,200 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 采集销毁所需的快照并清空内部状态。
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// 成功进入销毁阶段时返回销毁快照;如果其他线程已先完成销毁,则返回 <see langword="null" />。
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 该方法只负责锁内状态迁移,把外部回调与事件派发留到锁外执行,
|
||||
/// 以避免在生命周期结束阶段持锁调用用户代码。
|
||||
/// </remarks>
|
||||
private DestroySnapshot? TryBeginDestroy()
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
var pausedGroups = CollectPausedGroups();
|
||||
var handlersSnapshot = CreateHandlerSnapshot();
|
||||
|
||||
_pauseStacks.Clear();
|
||||
_tokenMap.Clear();
|
||||
_handlers.Clear();
|
||||
|
||||
_logger.Debug("PauseStackManager destroyed");
|
||||
|
||||
return new DestroySnapshot(pausedGroups, handlersSnapshot);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在销毁后向所有先前处于暂停状态的分组补发恢复通知。
|
||||
/// </summary>
|
||||
/// <param name="destroySnapshot">销毁阶段采集的分组与处理器快照。</param>
|
||||
private void NotifyDestroyedGroups(DestroySnapshot destroySnapshot)
|
||||
{
|
||||
foreach (var group in destroySnapshot.PausedGroups)
|
||||
{
|
||||
_logger.Debug($"Notifying handlers of destruction: Group={group}, IsPaused=false");
|
||||
|
||||
NotifyHandlersSnapshot(group, false, destroySnapshot.HandlersSnapshot, isDestroying: true);
|
||||
RaiseDestroyStateChanged(group);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在锁内执行令牌移除,并返回锁外通知所需的信息。
|
||||
/// </summary>
|
||||
/// <param name="token">要移除的暂停令牌。</param>
|
||||
/// <returns>包含本次弹出结果和后续通知决策的快照。</returns>
|
||||
/// <remarks>
|
||||
/// Pop 支持移除非栈顶令牌,因此这里会先临时转移栈元素,再恢复原有顺序,
|
||||
/// 只在最后一个暂停请求被移除时触发恢复通知。
|
||||
/// </remarks>
|
||||
private PopResult TryPopEntry(PauseToken token)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (!_tokenMap.TryGetValue(token.Id, out var entry))
|
||||
{
|
||||
_logger.Warn($"Attempted to pop invalid/expired token: {token.Id}");
|
||||
return PopResult.NotFound;
|
||||
}
|
||||
|
||||
var stack = _pauseStacks[entry.Group];
|
||||
var wasPaused = stack.Count > 0;
|
||||
var found = RemoveEntryFromStack(stack, token.Id);
|
||||
if (!found)
|
||||
{
|
||||
return PopResult.NotFound;
|
||||
}
|
||||
|
||||
_tokenMap.Remove(token.Id);
|
||||
_logger.Debug($"Pause popped: {entry.Reason} (Group: {entry.Group}, Remaining: {stack.Count})");
|
||||
|
||||
return new PopResult(true, wasPaused && stack.Count == 0, entry.Group);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定暂停栈中移除目标令牌,并保持其他暂停请求的原始顺序。
|
||||
/// </summary>
|
||||
/// <param name="stack">要修改的暂停栈。</param>
|
||||
/// <param name="tokenId">目标令牌标识。</param>
|
||||
/// <returns>如果找到了目标令牌则返回 <see langword="true" />。</returns>
|
||||
private static bool RemoveEntryFromStack(Stack<PauseEntry> stack, Guid tokenId)
|
||||
{
|
||||
var tempStack = new Stack<PauseEntry>();
|
||||
var found = false;
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var current = stack.Pop();
|
||||
if (current.TokenId == tokenId)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tempStack.Push(current);
|
||||
}
|
||||
|
||||
while (tempStack.Count > 0)
|
||||
{
|
||||
stack.Push(tempStack.Pop());
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 收集当前仍处于暂停状态的分组列表。
|
||||
/// </summary>
|
||||
/// <returns>包含所有暂停中的分组的数组。</returns>
|
||||
private PauseGroup[] CollectPausedGroups()
|
||||
{
|
||||
return _pauseStacks
|
||||
.Where(kvp => kvp.Value.Count > 0)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按优先级创建处理器快照,确保锁外通知仍保持确定性顺序。
|
||||
/// </summary>
|
||||
/// <returns>已按优先级排序的处理器快照。</returns>
|
||||
private IPauseHandler[] CreateHandlerSnapshot()
|
||||
{
|
||||
return _handlers
|
||||
.OrderBy(handler => handler.Priority)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一使用给定的处理器快照派发暂停状态变化通知。
|
||||
/// </summary>
|
||||
/// <param name="group">发生状态变化的暂停组。</param>
|
||||
/// <param name="isPaused">新的暂停状态。</param>
|
||||
/// <param name="handlersSnapshot">要通知的处理器快照。</param>
|
||||
/// <param name="isDestroying">是否处于销毁补发路径。</param>
|
||||
private void NotifyHandlersSnapshot(
|
||||
PauseGroup group,
|
||||
bool isPaused,
|
||||
IReadOnlyList<IPauseHandler> handlersSnapshot,
|
||||
bool isDestroying)
|
||||
{
|
||||
foreach (var handler in handlersSnapshot)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.OnPauseStateChanged(group, isPaused);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = isDestroying
|
||||
? $"Handler {handler.GetType().Name} failed during destruction"
|
||||
: $"Handler {handler.GetType().Name} failed";
|
||||
_logger.Error(message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在销毁路径中独立保护事件通知,避免订阅方异常中断其他分组的恢复信号。
|
||||
/// </summary>
|
||||
/// <param name="group">需要补发恢复事件的暂停组。</param>
|
||||
private void RaiseDestroyStateChanged(PauseGroup group)
|
||||
{
|
||||
try
|
||||
{
|
||||
RaisePauseStateChanged(group, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Event subscriber failed during destruction for group {group}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部查询暂停状态的方法,不加锁。
|
||||
/// </summary>
|
||||
@ -467,7 +552,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
handlersSnapshot = _handlers.OrderBy(h => h.Priority).ToArray();
|
||||
handlersSnapshot = CreateHandlerSnapshot();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -475,17 +560,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
|
||||
}
|
||||
|
||||
// 在锁外遍历快照并通知处理器
|
||||
foreach (var handler in handlersSnapshot)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.OnPauseStateChanged(group, isPaused);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Handler {handler.GetType().Name} failed", ex);
|
||||
}
|
||||
}
|
||||
NotifyHandlersSnapshot(group, isPaused, handlersSnapshot, isDestroying: false);
|
||||
|
||||
// 触发事件
|
||||
RaisePauseStateChanged(group, isPaused);
|
||||
@ -508,4 +583,25 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
|
||||
protected override void OnInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 锁内采集的销毁快照,供锁外补发恢复通知使用。
|
||||
/// </summary>
|
||||
/// <param name="PausedGroups">销毁前仍处于暂停状态的分组。</param>
|
||||
/// <param name="HandlersSnapshot">按优先级排序后的处理器快照。</param>
|
||||
private readonly record struct DestroySnapshot(PauseGroup[] PausedGroups, IPauseHandler[] HandlersSnapshot);
|
||||
|
||||
/// <summary>
|
||||
/// Pop 操作的锁内结果快照。
|
||||
/// </summary>
|
||||
/// <param name="Found">是否成功移除了目标令牌。</param>
|
||||
/// <param name="ShouldNotify">是否需要在锁外发出恢复通知。</param>
|
||||
/// <param name="NotifyGroup">需要通知的暂停组。</param>
|
||||
private readonly record struct PopResult(bool Found, bool ShouldNotify, PauseGroup NotifyGroup)
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示未找到目标令牌时的默认结果。
|
||||
/// </summary>
|
||||
public static PopResult NotFound { get; } = new(false, false, PauseGroup.Global);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,21 +7,22 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-004`
|
||||
- 当前阶段:`Phase 4`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-005`
|
||||
- 当前阶段:`Phase 5`
|
||||
- 当前焦点:
|
||||
- 已完成当前分支 PR #263 的最新 review follow-up,本地确认并修复 `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs`
|
||||
的 `null!` 可空契约问题,同时消除 active trace 的重复标题 `MD024`
|
||||
- 已确认 PR 上的测试信号为 `2134 Passed / 0 Failed`;MegaLinter 唯一告警来自 CI 中 `dotnet-format` restore 失败,
|
||||
当前本地 follow-up 不需要额外处理
|
||||
- 下一轮若继续推进,优先从 `PauseStackManager`、`CoroutineScheduler` 或 `Store` 的剩余 `MA0051` 中只选一个切入点
|
||||
- 已完成 `GFramework.Core/Pause/PauseStackManager.cs` 的 `MA0051` 收口:将 `DestroyAsync` 与 `Pop` 拆分为锁内状态迁移、
|
||||
栈调整和锁外通知三个阶段,同时保持日志、事件与销毁补发语义不变
|
||||
- 已为销毁路径补充 `PauseStackManagerTests.DestroyAsync_Should_NotifyResumedGroups` 回归测试,覆盖“销毁时向所有仍暂停组补发恢复通知”
|
||||
- 下一轮若继续推进,优先在 `CoroutineScheduler` 或 `Store` 的剩余 `MA0051` 中只选一个切入点,不回到已完成的
|
||||
`PauseStackManager`
|
||||
|
||||
## 当前状态摘要
|
||||
|
||||
- 已完成 `GFramework.Core`、`GFramework.Cqrs`、`GFramework.Godot` 与部分 source generator 的低风险 warning 清理
|
||||
- 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险
|
||||
- 当前 `GFramework.Cqrs` 的剩余 warning 热点已从 active 入口移除;主题内剩余 warning 主要集中在 `GFramework.Core` 长方法、
|
||||
文件/类型命名冲突、delegate 形状和少量公共集合抽象接口问题
|
||||
- 当前 `PauseStackManager` 的长方法 warning 已从 active 入口移除;主题内剩余 warning 主要集中在
|
||||
`GFramework.Core/Coroutine/CoroutineScheduler.cs`、`GFramework.Core/StateManagement/Store.cs`、文件/类型命名冲突、
|
||||
delegate 形状和少量公共集合抽象接口问题
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
@ -30,6 +31,7 @@
|
||||
- `RP-002` 已在不改公共契约的前提下完成 `CqrsHandlerRegistrar` 结构拆分,并通过定向 build/test 验证
|
||||
- `RP-003` 已在不改生命周期契约的前提下完成 `ArchitectureLifecycle` 初始化主流程拆分,并通过定向 build/test 验证
|
||||
- `RP-004` 已完成当前 PR review follow-up:修复 `TryCreateGeneratedRegistry` 的可空 `out` 契约并清理 trace 文档重复标题
|
||||
- `RP-005` 已在不改公共 API 的前提下完成 `PauseStackManager` 两个 `MA0051` 的结构拆分,并补充销毁通知回归测试
|
||||
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||
|
||||
## 当前风险
|
||||
@ -58,11 +60,16 @@
|
||||
- `RP-004` 的定向验证结果:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:UseSharedCompilation=false -p:RestoreFallbackFolders=`
|
||||
- 结果:`0 Warning(s)`,`0 Error(s)`
|
||||
- `RP-005` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`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`
|
||||
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录
|
||||
2. 优先在 `GFramework.Core/Pause/PauseStackManager.cs`、`GFramework.Core/Coroutine/CoroutineScheduler.cs` 与
|
||||
`GFramework.Core/StateManagement/Store.cs` 的 `MA0051` 中只选一个继续,不要在同一轮同时扩多个风险面
|
||||
2. 优先在 `GFramework.Core/Coroutine/CoroutineScheduler.cs` 与 `GFramework.Core/StateManagement/Store.cs`
|
||||
的 `MA0051` 中只选一个继续,不要在同一轮同时扩多个风险面
|
||||
3. 若本主题确认暂缓,可保持当前归档状态,不需要再恢复 `local-plan/`
|
||||
|
||||
@ -1,5 +1,28 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-21 — RP-005
|
||||
|
||||
### 阶段:PauseStackManager `MA0051` 收口(RP-005)
|
||||
|
||||
- 按 active tracking 中“继续只选一个 `GFramework.Core` 结构性切入点”的约束,本轮选择
|
||||
`GFramework.Core/Pause/PauseStackManager.cs`,因为该文件体量明显小于 `CoroutineScheduler` 和 `Store`,
|
||||
且已有稳定的 `PauseStackManagerTests` 覆盖暂停栈、跨组独立性、事件通知与并发 `Push/Pop` 行为
|
||||
- 先用 `warnings-only` 定向构建确认 `DestroyAsync` 与 `Pop` 仍分别命中 `MA0051`,再把逻辑拆分为:
|
||||
- `TryBeginDestroy`
|
||||
- `NotifyDestroyedGroups`
|
||||
- `TryPopEntry`
|
||||
- `RemoveEntryFromStack`
|
||||
- 额外抽出 `CreateHandlerSnapshot` 与 `NotifyHandlersSnapshot`,统一普通通知与销毁补发路径的处理器排序和异常日志,
|
||||
保持原有“锁内采集快照、锁外调用处理器与事件”的并发策略不变
|
||||
- 为销毁路径新增 `DestroyAsync_Should_NotifyResumedGroups`,验证当多个暂停组在销毁前仍为暂停态时,
|
||||
处理器和事件订阅者都会收到 `IsPaused=false` 的恢复信号
|
||||
- 验证通过:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
|
||||
- 结果:`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`
|
||||
- 下一步保持原节奏:只在 `CoroutineScheduler` 或 `Store` 中二选一继续,不与其他 warning 家族混做
|
||||
|
||||
## 2026-04-21 — RP-003
|
||||
|
||||
### 阶段:Architecture 生命周期 `MA0051` 收口(RP-003)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user