diff --git a/GFramework.Core/Coroutine/CoroutineScheduler.cs b/GFramework.Core/Coroutine/CoroutineScheduler.cs index 21f326ad..d34e7d54 100644 --- a/GFramework.Core/Coroutine/CoroutineScheduler.cs +++ b/GFramework.Core/Coroutine/CoroutineScheduler.cs @@ -211,58 +211,10 @@ public sealed class CoroutineScheduler( return default; } - if (_nextSlot >= _slots.Length) - { - Expand(); - } - var handle = new CoroutineHandle(instanceId); - var slotIndex = _nextSlot++; - - var slot = new CoroutineSlot - { - CancellationToken = cancellationToken, - Enumerator = coroutine, - State = CoroutineState.Running, - Handle = handle, - Priority = priority - }; - - if (cancellationToken.CanBeCanceled) - { - // 取消回调可能在任意线程触发,因此这里只做排队,真正清理由 Update 主线程完成。 - slot.CancellationRegistration = cancellationToken.Register(() => _pendingKills.Enqueue(handle)); - } - - _slots[slotIndex] = slot; - _metadata[handle] = new CoroutineMetadata - { - ExecutionStage = executionStage, - Group = group, - Priority = priority, - SlotIndex = slotIndex, - StartTime = _timeSource.CurrentTime * 1000, - State = CoroutineState.Running, - Tag = tag - }; - - _completionSources[handle] = - new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _completionStatuses.Remove(handle); - - if (!string.IsNullOrEmpty(tag)) - { - AddTag(tag, handle); - } - - if (!string.IsNullOrEmpty(group)) - { - AddGroup(group, handle); - } - - _statistics?.RecordStart(priority, tag); - ActiveCoroutineCount++; - + var slotIndex = AllocateSlotIndex(); + var slot = CreateRunningSlot(handle, coroutine, priority, cancellationToken); + RegisterStartedCoroutine(handle, slotIndex, slot, priority, tag, group); Prewarm(slotIndex); UpdateStatisticsSnapshot(); @@ -662,70 +614,14 @@ public sealed class CoroutineScheduler( CoroutineCompletionStatus completionStatus, Exception? exception = null) { - var slot = _slots[slotIndex]; - if (slot == null) + if (!TryGetFinalizableCoroutine(slotIndex, out var slot, out var handle)) { return; } - var handle = slot.Handle; - if (!handle.IsValid) - { - return; - } - - if (_metadata.TryGetValue(handle, out var meta)) - { - if (meta.State == CoroutineState.Paused && _pausedCount > 0) - { - _pausedCount--; - } - - var executionTime = _timeSource.CurrentTime * 1000 - meta.StartTime; - switch (completionStatus) - { - case CoroutineCompletionStatus.Completed: - meta.State = CoroutineState.Completed; - _statistics?.RecordComplete(executionTime, meta.Priority, meta.Tag); - break; - - case CoroutineCompletionStatus.Faulted: - meta.State = CoroutineState.Completed; - _statistics?.RecordFailure(meta.Priority, meta.Tag); - break; - - case CoroutineCompletionStatus.Cancelled: - meta.State = CoroutineState.Cancelled; - break; - - default: - throw new ArgumentOutOfRangeException( - nameof(completionStatus), - completionStatus, - "Unsupported coroutine completion status."); - } - } - - DisposeSlotResources(slot); - - _slots[slotIndex] = null; - if (ActiveCoroutineCount > 0) - { - ActiveCoroutineCount--; - } - - RemoveTag(handle); - RemoveGroup(handle); - _metadata.Remove(handle); - - WakeWaiters(handle); - - if (_completionSources.Remove(handle, out var source)) - { - source.TrySetResult(completionStatus); - } - - RecordCompletionStatus(handle, completionStatus); + UpdateCompletionMetadata(handle, completionStatus); + ReleaseCompletedCoroutine(slotIndex, slot, handle); + CompleteCoroutineLifecycle(handle, completionStatus); OnCoroutineFinished?.Invoke(handle, completionStatus, exception); } @@ -799,6 +695,139 @@ public sealed class CoroutineScheduler( } } + /// + /// 为新协程分配槽位索引,并在需要时扩容槽位数组。 + /// + /// 可写入的新槽位索引。 + private int AllocateSlotIndex() + { + if (_nextSlot >= _slots.Length) + { + Expand(); + } + + return _nextSlot++; + } + + /// + /// 创建处于运行态的协程槽位,并在需要时挂接跨线程取消回调。 + /// + /// 新协程句柄。 + /// 协程枚举器。 + /// 协程优先级。 + /// 外部取消令牌。 + /// 已初始化的协程槽位。 + private CoroutineSlot CreateRunningSlot( + CoroutineHandle handle, + IEnumerator coroutine, + CoroutinePriority priority, + CancellationToken cancellationToken) + { + var slot = new CoroutineSlot + { + CancellationToken = cancellationToken, + Enumerator = coroutine, + State = CoroutineState.Running, + Handle = handle, + Priority = priority + }; + + RegisterCancellationCallback(slot, handle, cancellationToken); + return slot; + } + + /// + /// 为支持取消的协程注册待终止排队回调。 + /// + /// 目标协程槽位。 + /// 协程句柄。 + /// 外部取消令牌。 + private void RegisterCancellationCallback( + CoroutineSlot slot, + CoroutineHandle handle, + CancellationToken cancellationToken) + { + if (!cancellationToken.CanBeCanceled) + { + return; + } + + // 取消回调可能在任意线程触发,因此这里只做排队,真正清理由 Update 主线程完成。 + slot.CancellationRegistration = cancellationToken.Register(() => _pendingKills.Enqueue(handle)); + } + + /// + /// 将新协程写入调度器的槽位、元数据、标签分组和完成状态跟踪结构。 + /// + /// 协程句柄。 + /// 槽位索引。 + /// 已初始化的协程槽位。 + /// 协程优先级。 + /// 可选标签。 + /// 可选分组。 + private void RegisterStartedCoroutine( + CoroutineHandle handle, + int slotIndex, + CoroutineSlot slot, + CoroutinePriority priority, + string? tag, + string? group) + { + _slots[slotIndex] = slot; + _metadata[handle] = CreateCoroutineMetadata(slotIndex, priority, tag, group); + ResetCompletionTracking(handle); + + if (!string.IsNullOrEmpty(tag)) + { + AddTag(tag, handle); + } + + if (!string.IsNullOrEmpty(group)) + { + AddGroup(group, handle); + } + + _statistics?.RecordStart(priority, tag); + ActiveCoroutineCount++; + } + + /// + /// 创建新协程的初始元数据。 + /// + /// 槽位索引。 + /// 协程优先级。 + /// 可选标签。 + /// 可选分组。 + /// 与新槽位对应的元数据对象。 + private CoroutineMetadata CreateCoroutineMetadata( + int slotIndex, + CoroutinePriority priority, + string? tag, + string? group) + { + return new CoroutineMetadata + { + ExecutionStage = executionStage, + Group = group, + Priority = priority, + SlotIndex = slotIndex, + StartTime = _timeSource.CurrentTime * 1000, + State = CoroutineState.Running, + Tag = tag + }; + } + + /// + /// 重置协程完成跟踪,使复用句柄不会携带上一轮完成结果。 + /// + /// 协程句柄。 + private void ResetCompletionTracking(CoroutineHandle handle) + { + _completionSources[handle] = + new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _completionStatuses.Remove(handle); + } + /// /// 释放单个槽位持有的资源。 /// @@ -824,6 +853,125 @@ public sealed class CoroutineScheduler( slot.Waiting = null; } + /// + /// 读取可被完成处理的协程槽位与句柄。 + /// 当槽位已空或句柄已失效时,说明该协程已经被其他路径清理,无需重复执行结束逻辑。 + /// + /// 槽位索引。 + /// 若成功则返回槽位。 + /// 若成功则返回句柄。 + /// 当存在可完成的协程时返回 + private bool TryGetFinalizableCoroutine(int slotIndex, out CoroutineSlot slot, out CoroutineHandle handle) + { + var candidate = _slots[slotIndex]; + if (candidate == null) + { + slot = null!; + handle = default; + return false; + } + + handle = candidate.Handle; + if (!handle.IsValid) + { + slot = null!; + return false; + } + + slot = candidate; + return true; + } + + /// + /// 根据最终状态更新协程元数据与统计信息。 + /// + /// 协程句柄。 + /// 最终结果。 + private void UpdateCompletionMetadata(CoroutineHandle handle, CoroutineCompletionStatus completionStatus) + { + if (!_metadata.TryGetValue(handle, out var meta)) + { + return; + } + + if (meta.State == CoroutineState.Paused && _pausedCount > 0) + { + _pausedCount--; + } + + ApplyCompletionMetadata(meta, completionStatus); + } + + /// + /// 将最终结果映射到元数据状态和统计记录。 + /// + /// 协程元数据。 + /// 最终结果。 + private void ApplyCompletionMetadata(CoroutineMetadata meta, CoroutineCompletionStatus completionStatus) + { + var executionTime = _timeSource.CurrentTime * 1000 - meta.StartTime; + switch (completionStatus) + { + case CoroutineCompletionStatus.Completed: + meta.State = CoroutineState.Completed; + _statistics?.RecordComplete(executionTime, meta.Priority, meta.Tag); + break; + + case CoroutineCompletionStatus.Faulted: + meta.State = CoroutineState.Completed; + _statistics?.RecordFailure(meta.Priority, meta.Tag); + break; + + case CoroutineCompletionStatus.Cancelled: + meta.State = CoroutineState.Cancelled; + break; + + default: + throw new ArgumentOutOfRangeException( + nameof(completionStatus), + completionStatus, + "Unsupported coroutine completion status."); + } + } + + /// + /// 释放已结束协程占用的槽位和索引结构。 + /// + /// 槽位索引。 + /// 已结束的协程槽位。 + /// 协程句柄。 + private void ReleaseCompletedCoroutine(int slotIndex, CoroutineSlot slot, CoroutineHandle handle) + { + DisposeSlotResources(slot); + + _slots[slotIndex] = null; + if (ActiveCoroutineCount > 0) + { + ActiveCoroutineCount--; + } + + RemoveTag(handle); + RemoveGroup(handle); + _metadata.Remove(handle); + } + + /// + /// 完成协程的等待者唤醒、任务结果和完成历史记录。 + /// + /// 协程句柄。 + /// 最终结果。 + private void CompleteCoroutineLifecycle(CoroutineHandle handle, CoroutineCompletionStatus completionStatus) + { + WakeWaiters(handle); + + if (_completionSources.Remove(handle, out var source)) + { + source.TrySetResult(completionStatus); + } + + RecordCompletionStatus(handle, completionStatus); + } + /// /// 唤醒所有等待目标协程完成的协程。 /// 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 5f56a056..38f82d08 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,21 +7,20 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-006` -- 当前阶段:`Phase 6` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-007` +- 当前阶段:`Phase 7` - 当前焦点: - - 已完成 `GFramework.Core/StateManagement/Store.cs` 的 `MA0051` 收口:将 `Dispatch` 拆分为“进入分发域 / 提交结果 / 退出分发域” - 三个辅助阶段,并将 reducer 快照创建拆分为精确匹配与多态匹配两条路径 - - 本轮保持 `Store` 的锁顺序、middleware 执行时机、batch 通知折叠和多态 reducer 排序规则不变,未改公共 API - - 下一轮若继续推进,优先只处理 `GFramework.Core/Coroutine/CoroutineScheduler.cs` 的剩余 `MA0051`,不回到已完成的 - `Store` 或 `PauseStackManager` + - 已完成 `GFramework.Core/Coroutine/CoroutineScheduler.cs` 的 `MA0051` 收口:将 `Run` 拆分为槽位分配、运行槽创建和启动注册阶段, + 将 `FinalizeCoroutine` 拆分为可完成目标读取、完成元数据更新、资源释放和生命周期收尾阶段 + - 本轮保持取消回调入队、统计记录、标签/分组清理、等待者唤醒和完成任务语义不变,未改公共 API + - 当前 `MA0051` 主线已完成;下一轮若继续推进,应先判断剩余 `MA0048`、`MA0046`、`MA0002`、`MA0016` 是否值得继续低风险收敛 ## 当前状态摘要 - 已完成 `GFramework.Core`、`GFramework.Cqrs`、`GFramework.Godot` 与部分 source generator 的低风险 warning 清理 - 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险 -- 当前 `PauseStackManager` 与 `Store` 的长方法 warning 已从 active 入口移除;主题内剩余 warning 主要集中在 - `GFramework.Core/Coroutine/CoroutineScheduler.cs`、文件/类型命名冲突、delegate 形状和少量公共集合抽象接口问题 +- 当前 `PauseStackManager`、`Store` 与 `CoroutineScheduler` 的长方法 warning 已从 active 入口移除;主题内剩余 warning + 主要集中在文件/类型命名冲突、delegate 形状、字符串 comparer 重载和少量公共集合抽象接口问题 ## 当前活跃事实 @@ -33,6 +32,8 @@ - `RP-005` 已在不改公共 API 的前提下完成 `PauseStackManager` 两个 `MA0051` 的结构拆分,并补充销毁通知回归测试 - `RP-006` 已在不改公共 API 的前提下完成 `Store` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证 dispatch、 多态 reducer 匹配与历史语义未回归 +- `RP-007` 已在不改公共 API 的前提下完成 `CoroutineScheduler` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证 + 调度、取消与完成状态语义未回归 - 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射 ## 当前风险 @@ -71,11 +72,16 @@ - 结果:`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` +- `RP-007` 的定向验证结果: + - `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"` + - 结果:`23 Warning(s)`,`0 Error(s)`;`CoroutineScheduler.cs` 已不再出现在 `MA0051` 列表中 + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~CoroutineScheduler -p:RestoreFallbackFolders="" -p:RestorePackagesPath= -nologo` + - 结果:`34 Passed`,`0 Failed` - active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史 ## 下一步 1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录 -2. 优先在 `GFramework.Core/Coroutine/CoroutineScheduler.cs` 的 `Run` 与 `FinalizeCoroutine` 两个 `MA0051` - 中继续,保持“单文件、单 warning family”的节奏 +2. 先基于当前 `23 Warning(s)` 的唯一源位置清单,判断 `MA0048` 文件命名冲突与 `MA0046` delegate 形状是否存在低风险切入点, + 再决定是否开启下一轮 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 088f0a7b..85a99ca8 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-007 + +### 阶段:CoroutineScheduler `MA0051` 收口(RP-007) + +- 依据 active tracking 中“继续只选一个 `GFramework.Core` 结构性切入点”的约束,本轮选择 + `GFramework.Core/Coroutine/CoroutineScheduler.cs`,因为剩余两个 `MA0051` 都集中在协程启动与完成清理路径,且已有 + `CoroutineSchedulerTests`、`CoroutineSchedulerAdvancedTests` 覆盖句柄创建、取消、完成状态、标签分组和等待语义 +- 将 `Run` 拆分为: + - `AllocateSlotIndex` + - `CreateRunningSlot` + - `RegisterCancellationCallback` + - `RegisterStartedCoroutine` + - `CreateCoroutineMetadata` + - `ResetCompletionTracking` +- 将 `FinalizeCoroutine` 拆分为: + - `TryGetFinalizableCoroutine` + - `UpdateCompletionMetadata` + - `ApplyCompletionMetadata` + - `ReleaseCompletedCoroutine` + - `CompleteCoroutineLifecycle` +- 保持取消回调只做跨线程入队、`Prewarm` 时机、统计记录文本、`RemoveTag` / `RemoveGroup` / `WakeWaiters` 顺序以及 + `OnCoroutineFinished` 的同步触发时机不变,只收缩主方法长度并补齐辅助方法意图注释 +- 验证通过: + - `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"` + - 结果:`23 Warning(s)`,`0 Error(s)`;`CoroutineScheduler.cs` 已不再出现在 `MA0051` 列表 + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~CoroutineScheduler -p:RestoreFallbackFolders="" -p:RestorePackagesPath= -nologo` + - 结果:`34 Passed`,`0 Failed` +- 当前 `MA0051` 主线已经在本主题下完成;下一步若继续,应先重新评估剩余 `MA0048`、`MA0046`、`MA0002`、`MA0016` 的 + 收敛价值与改动风险,再决定是否开启下一轮 warning family + ## 2026-04-21 — RP-006 ### 阶段:Store `MA0051` 收口(RP-006)