Skip to content

Feat/coroutine core and godot integration#176

Merged
GeWuYou merged 6 commits into
mainfrom
feat/coroutine-core-and-godot-integration
Apr 5, 2026
Merged

Feat/coroutine core and godot integration#176
GeWuYou merged 6 commits into
mainfrom
feat/coroutine-core-and-godot-integration

Conversation

@GeWuYou
Copy link
Copy Markdown
Owner

@GeWuYou GeWuYou commented Apr 5, 2026

Summary by CodeRabbit

  • 新特性

    • 协程取消与完成状态跟踪(支持 CancellationToken、WaitForCompletionAsync、Completed/Cancelled/Faulted/Unknown)
    • 运行时快照与可观测性(TryGetSnapshot/GetActiveSnapshots、快照包含等待指令与执行阶段)
    • 实时时间源与执行阶段控制(Update/FixedUpdate/EndOfFrame)
    • 节点生命周期绑定协程与新 RunCoroutine 重载(自动随节点退出清理)
  • 测试

    • 新增多项协程与时间源集成测试,覆盖取消、阶段推进、故障与历史边界
  • 文档

    • 更新核心与 Godot 集成文档与教程,示例与最佳实践同步调整
  • 其他

    • CI 增加 Godot 测试运行,项目/解决方案与全局引用调整

GeWuYou added 2 commits April 5, 2026 15:06
- 实现CoroutineMetadata类存储协程元数据信息
- 创建CoroutineScheduler协程调度器管理协程生命周期
- 添加CoroutineSlot类管理单个协程执行状态
- 实现GodotTimeSource时间源支持缩放和真实时间
- 添加Timing类提供Godot协程管理功能
- 实现CoroutineNodeExtensions扩展方法支持节点生命周期管理
- 支持协程分组、标签、优先级等功能
- 提供协程暂停、恢复、终止等控制接口
- 实现协程统计和快照功能
- 添加等待指令处理机制支持多种等待类型
- 重构 Core 协程系统文档,优化概述和核心概念说明
- 新增 Godot 协程系统集成文档
- 添加协程系统使用教程
- 更新等待指令说明,包括时间、条件、Task 和事件等待
- 补充协程控制、快照查询和生命周期管理相关内容
- 修正代码示例和 API 使用说明
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @GeWuYou, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Apr 5, 2026

DeepSource Code Review

We reviewed changes in a22e522...d213707 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Apr 5, 2026 4:37p.m. Review ↗
Secrets Apr 5, 2026 4:37p.m. Review ↗


var executionTime = _timeSource.CurrentTime * 1000 - meta.StartTime;
_statistics?.RecordComplete(executionTime, meta.Priority, meta.Tag);
switch (completionStatus)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch expression does not have `default` case


The default case is executed when none of the specified cases in the switch statement match. This is particularly useful when switch fails to cover all the possible values, either because all the cases weren't specified or that the range of values was later expanded.

Comment thread GFramework.Godot/Coroutine/GodotTimeSource.cs
GeWuYou added 2 commits April 5, 2026 15:18
- 为 CoroutineCompletionStatus 枚举添加默认分支处理
- 抛出 ArgumentOutOfRangeException 以处理不支持的协程完成状态
- 防止因未知状态值导致的运行时错误
- 提高协程调度器的健壮性和错误处理能力
- 移除私有字段 _executionStage,直接使用构造函数参数 executionStage
- 更新 ExecutionStage 属性实现,直接返回构造函数参数
- 修改协程元数据设置时使用参数而非私有字段
- 调整等待指令判断逻辑,直接比较参数值
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

本次变更为协程框架引入了完成状态追踪、取消令牌支持、执行阶段感知调度、节点所有权生命周期和运行时快照查询能力,并在 Godot 集成层重构了时间源以支持绝对/实时语义与实例化测试用例。

Changes

Cohort / File(s) Summary
完成状态与执行阶段枚举
GFramework.Core.Abstractions/Coroutine/CoroutineCompletionStatus.cs, GFramework.Core.Abstractions/Coroutine/CoroutineExecutionStage.cs
新增两个公共枚举:CoroutineCompletionStatus(Unknown、Completed、Cancelled、Faulted)和 CoroutineExecutionStage(Update、FixedUpdate、EndOfFrame)。
核心调度器与元数据
GFramework.Core/Coroutine/CoroutineScheduler.cs, GFramework.Core/Coroutine/CoroutineSlot.cs, GFramework.Core/Coroutine/CoroutineMetadata.cs
扩展 CoroutineScheduler 构造与行为:支持可选实时时间源与执行阶段、暴露 RealtimeDeltaTime/ExecutionStageRun 接受 CancellationToken、增加完成追踪(WaitForCompletionAsync/历史状态)、并发取消队列处理与 OnCoroutineFinished 事件;CoroutineSlotCoroutineMetadata 新增取消令牌与执行阶段字段。
快照类型
GFramework.Core/Coroutine/CoroutineSnapshot.cs
新增只读记录结构 CoroutineSnapshot,用于导出协程运行时快照(含 Handle、状态、优先级、tag/group、启动时间、等待状态、等待指令类型与执行阶段)。
调度器高级测试
GFramework.Core.Tests/Coroutine/CoroutineSchedulerAdvancedTests.cs
新增集成风格测试组合(实时时间源、阶段约束、取消语义、快照、异常/历史边界、嵌套协程等)。
Godot 时间源重构
GFramework.Godot/Coroutine/GodotTimeSource.cs
构造由 delta 委托改为通用时间提供者并新增 useAbsoluteTime 模式;支持绝对时间差分与单调性抑制;新增 CreateRealtime() 工厂;Reset() 清理绝对时间状态。
Godot 协程集成与所有权
GFramework.Godot/Coroutine/Timing.cs, GFramework.Godot/Coroutine/CoroutineNodeExtensions.cs
引入节点所有权协程(RunOwnedCoroutine / RunCoroutine overload),在节点 TreeExiting 时自动清理,支持 CancellationToken 透传;新增所有权索引、TryGetCoroutineSnapshotGetOwnedCoroutineCountKillCoroutines(Node) 等查询/管理 API;将延迟调用等改为受所有权管理。
Godot 测试项目
GFramework.Godot.Tests/GFramework.Godot.Tests.csproj, GFramework.Godot.Tests/Coroutine/GodotTimeSourceTests.cs
新增 Godot 测试项目及 GodotTimeSource 的三个单元测试(增量/绝对模式与单调性校验)。
工程/CI/全局 using
GFramework.csproj, GFramework.sln, .github/workflows/ci.yml, GFramework.Godot/GlobalUsings.cs
在解决方案中添加 Godot 测试项目,排除测试目录于主包,CI 增加对 Godot 测试项目的并发运行,新增 global using Godot;
文档
docs/zh-CN/core/coroutine.md, docs/zh-CN/godot/coroutine.md, docs/zh-CN/tutorials/coroutine-tutorial.md
重组并更新文档以反映新的调度器参数、完成状态、快照查询、节点所有权与时间源语义,调整示例与推荐用法。

Sequence Diagram(s)

sequenceDiagram
    participant App as 应用代码
    participant Scheduler as CoroutineScheduler
    participant CQueue as ConcurrentCancelQueue
    participant Slot as CoroutineSlot
    participant TCS as TaskCompletionSource

    App->>Scheduler: Run(coroutine, cancellationToken)
    Scheduler->>Slot: 创建并初始化 slot(保存 token & registration)
    App->>App: cancellationTokenSource.Cancel()
    App->>Scheduler: Update()
    Scheduler->>CQueue: 处理待取消项
    CQueue->>Scheduler: 标记对应 slot 为取消
    Scheduler->>Scheduler: FinalizeCoroutine(handle, Cancelled)
    Scheduler->>Slot: 释放资源并注销取消注册
    Scheduler->>TCS: SetResult(Cancelled)
    Scheduler->>App: OnCoroutineFinished(handle, Cancelled, null)
    App->>Scheduler: await WaitForCompletionAsync(handle)
    TCS-->>App: 返回 CoroutineCompletionStatus.Cancelled
Loading
sequenceDiagram
    participant Node as Godot Node
    participant Ext as CoroutineNodeExtensions
    participant Timing as Timing (singleton)
    participant Scheduler as CoroutineScheduler

    Node->>Ext: RunCoroutine(coroutine, segment, tag, token)
    Ext->>Timing: RunOwnedCoroutine(owner, coroutine, segment, tag, token)
    Timing->>Scheduler: Run(coroutine, tag, ..., token)
    Scheduler-->>Timing: 返回 CoroutineHandle
    Timing->>Timing: 注册 Handle -> weak Owner 映射
    Timing->>Node: 订阅 TreeExiting 清理回调
    Note over Node: 节点退出场景树
    Node->>Timing: 触发 TreeExiting
    Timing->>Scheduler: Kill(handle)
    Timing->>Timing: 移除所有权索引 & 取消订阅
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 我在草丛里算帧,
完成有标记,取消有印;
实时时钟来敲门,
节点离开即别亲,
快照一瞥安心。

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了主要变更:为协程系统(Core)和 Godot 集成添加功能,涵盖新的枚举、元数据扩展、调度器功能、测试和文档更新。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/coroutine-core-and-godot-integration

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/zh-CN/godot/coroutine.md (1)

28-45: ⚠️ Potential issue | 🟡 Minor

默认示例和 WaitForEndOfFrame 的阶段语义冲突。

这里 Demo().RunCoroutine() 默认跑在 Segment.Process,但同一篇文档后面已经说明 WaitForEndOfFrame 只有在 Segment.DeferredProcess 上才会真正完成。所以这段示例会卡在帧尾等待,GD.Print("done") 不会执行。若这里想继续保留默认 RunCoroutine(),把等待指令改成 WaitOneFrame 更合适;如果要演示帧尾语义,则需要把运行段显式改成 Segment.DeferredProcess

🛠️ 一个最小修正方案
-        yield return new WaitForEndOfFrame();
+        yield return new WaitOneFrame();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/zh-CN/godot/coroutine.md` around lines 28 - 45, The example uses
Demo().RunCoroutine() which defaults to Segment.Process but then yields
WaitForEndOfFrame (which only completes on Segment.DeferredProcess), causing the
coroutine to never progress to GD.Print("done"); fix by either changing the
yield in Demo() from WaitForEndOfFrame to WaitOneFrame if you want to keep the
default RunCoroutine() behavior, or explicitly run the coroutine in the deferred
stage (e.g., call RunCoroutine(Segment.DeferredProcess)) to preserve the
WaitForEndOfFrame semantics; update the Demo() method or the RunCoroutine()
invocation accordingly.
🧹 Nitpick comments (5)
docs/zh-CN/core/coroutine.md (1)

142-151: Task 桥接示例可考虑收敛为单一“推荐写法”。

当前同时展示两种入口,读者可能不清楚优先级;可在该段保留一种主路径,另一种放到“补充/兼容写法”。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/zh-CN/core/coroutine.md` around lines 142 - 151, 示例中同时展示
LoadDataAsync().AsCoroutineInstruction() 与
scheduler.StartTaskAsCoroutine(LoadDataAsync())
两种入口会让读者不清晰优先级;将文档收敛为单一“推荐写法”(选择一个作为主路径,例如推荐使用 AsCoroutineInstruction() 或者推荐使用
scheduler.StartTaskAsCoroutine(...)),在该段保留选定的主写法并把另一种以“补充/兼容写法”小节或注释形式移动到段落末尾,明确标注哪种为首选并保留对
LoadDataAsync、AsCoroutineInstruction、StartTaskAsCoroutine 和 scheduler
的说明以便定位代码示例。
GFramework.Godot.Tests/GFramework.Godot.Tests.csproj (1)

10-10: 建议移除测试项目中的 <WarningLevel>0</WarningLevel> 配置。

GFramework.Godot.Tests/GFramework.Godot.Tests.csprojGFramework.Core.Tests/GFramework.Core.Tests.csproj 中,将 WarningLevel 设为 0 会全局压制编译告警,降低潜在问题的可见性。建议恢复默认级别(级别 4)或使用 NoWarn 仅抑制特定告警。

♻️ 建议修改
-        <WarningLevel>0</WarningLevel>
+        <WarningLevel>4</WarningLevel>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GFramework.Godot.Tests/GFramework.Godot.Tests.csproj` at line 10, 当前测试项目的
MSBuild 配置包含 <WarningLevel>0</WarningLevel>,这会全局抑制编译警告;请在对应测试项目的 .csproj 中移除该
<WarningLevel>0</WarningLevel> 元素或将其改为
<WarningLevel>4</WarningLevel>,如需仅屏蔽特定警告则改用 <NoWarn> 列表来有选择地禁用特定警告;定位并修改包含
<WarningLevel>0</WarningLevel> 的块以恢复默认警告可见性。
GFramework.Core/Coroutine/CoroutineScheduler.cs (3)

582-606: Clear() 方法应同时清理 _completionSources

虽然 FinalizeCoroutine 会移除 _completionSources 中的条目,但作为防御性编程,建议在 Clear() 末尾显式清理此字典,以处理潜在的边缘情况。

♻️ 建议修复
     _waiting.Clear();
+    _completionSources.Clear();
+    _completionStatuses.Clear();

     _nextSlot = 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GFramework.Core/Coroutine/CoroutineScheduler.cs` around lines 582 - 606, The
Clear() method currently relies on FinalizeCoroutine to remove entries from
_completionSources but should defensively clear the dictionary itself to avoid
edge-case leaks; update Clear() to explicitly call _completionSources.Clear()
(e.g., near the other collection clears) so that _completionSources is always
emptied when Clear() completes while leaving FinalizeCoroutine unchanged.

140-152: 可考虑减少 LINQ 链中的中间分配

GetActiveSnapshots() 创建了多个中间对象(匿名类型、多次投影)。如果此方法会在性能敏感路径中调用,可考虑使用 List<T> 手动迭代以减少 GC 压力。

♻️ 可选优化方案
 public IReadOnlyList<CoroutineSnapshot> GetActiveSnapshots()
 {
-    return _metadata
-        .Select(pair => pair.Value)
-        .Select(meta => new
-        {
-            Metadata = meta,
-            Slot = _slots[meta.SlotIndex]
-        })
-        .Where(item => item.Slot is not null)
-        .Select(item => CreateSnapshot(item.Metadata, item.Slot!))
-        .ToArray();
+    var result = new List<CoroutineSnapshot>(_metadata.Count);
+    foreach (var meta in _metadata.Values)
+    {
+        var slot = _slots[meta.SlotIndex];
+        if (slot is not null)
+        {
+            result.Add(CreateSnapshot(meta, slot));
+        }
+    }
+    return result;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GFramework.Core/Coroutine/CoroutineScheduler.cs` around lines 140 - 152,
GetActiveSnapshots currently builds multiple intermediate allocations via LINQ
(anonymous objects and multiple projections). Replace the LINQ chain in
GetActiveSnapshots with an explicit loop: allocate a List<CoroutineSnapshot>
with an appropriate initial capacity, iterate over _metadata.Values (or
_metadata), look up the slot via _slots[meta.SlotIndex], skip null slots, call
CreateSnapshot(meta, slot) and Add the result to the list, then return
list.ToArray() (or the list if IReadOnlyList is acceptable). This minimizes
allocations while keeping behavior identical; refer to GetActiveSnapshots,
_metadata, _slots, and CreateSnapshot to locate the change.

754-762: 阶段门控依赖具体类型检查

CanAdvanceInstruction 使用具体类型模式匹配(is WaitForFixedUpdateis WaitForEndOfFrame)来判断是否允许推进。如果未来新增其他阶段绑定指令,需要同步修改此 switch 表达式。

考虑到 WaitForFixedUpdateWaitForEndOfFramesealed 类型,且这是内部实现细节,当前设计是可接受的。如需更好的扩展性,可考虑在 IYieldInstruction 接口中添加可选的阶段标记属性。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GFramework.Core/Coroutine/CoroutineScheduler.cs` around lines 754 - 762,
CanAdvanceInstruction currently uses concrete-type pattern matching
(WaitForFixedUpdate, WaitForEndOfFrame) to gate stages which makes adding new
stage-bound instructions error-prone; modify the design by adding an optional
execution-stage marker to IYieldInstruction (e.g. a nullable/optional property
like DesiredExecutionStage or ExecutionStage? on IYieldInstruction) and then
update CanAdvanceInstruction to first check instruction.ExecutionStage (if set,
compare to coroutine's CoroutineExecutionStage) and only fall back to the
existing type checks for backward compatibility with WaitForFixedUpdate and
WaitForEndOfFrame; update the interface (IYieldInstruction) and the
CanAdvanceInstruction method accordingly so new stage-bound instructions only
need to set the property.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@GFramework.Core/Coroutine/CoroutineScheduler.cs`:
- Line 40: _completionStatuses is never cleared and grows when FinalizeCoroutine
adds entries, causing a memory leak; update the Clear() method to clear
_completionStatuses (in addition to existing cleared collections) and/or remove
the specific entry from _completionStatuses when a coroutine is finalized inside
FinalizeCoroutine to avoid unbounded growth; alternatively implement a simple
eviction policy (time or capacity-based) that prunes old entries from
_completionStatuses—refer to the fields/methods _completionStatuses,
FinalizeCoroutine, and Clear to locate where to add clearing/removal logic.
- Around line 387-392: HandleYieldInstruction currently calls
Run(waitForCoroutine.Coroutine) without propagating the parent coroutine's
CancellationToken, so nested coroutines continue running when the parent is
cancelled; update CoroutineSlot to store the parent CancellationToken (in
addition to the existing CancellationTokenRegistration) and modify Run(...) (and
the call site in HandleYieldInstruction) to accept and pass that token into the
nested coroutine creation, ensuring the nested coroutine inherits cancellation;
alternatively, if this behavior is intended, add clear documentation on
CoroutineSlot/Run and WaitForCoroutine that nested coroutines are
cancellation-isolated from their parents.

In `@GFramework.Godot/Coroutine/GodotTimeSource.cs`:
- Around line 38-58: The absolute-time branch uses the raw value to set
_lastAbsoluteTime and CurrentTime which allows time to go backwards when the
provider is rewound; instead compute a clampedDelta = Math.Max(0, value -
_lastAbsoluteTime), assign DeltaTime = clampedDelta, then advance both
_lastAbsoluteTime and CurrentTime by clampedDelta (e.g. _lastAbsoluteTime +=
clampedDelta; CurrentTime = _lastAbsoluteTime) so CurrentTime never decreases;
keep the existing initialization path for _initialized that sets
_lastAbsoluteTime and CurrentTime from the first provider value.

In `@GFramework.Godot/Coroutine/Timing.cs`:
- Around line 516-566: KillCoroutines(Node) and GetOwnedCoroutineCount(Node)
only query the singleton Instance and thus miss coroutines owned by other Timing
instances; change them to iterate ActiveInstances and aggregate results. For
KillCoroutines(Node) call KillOwnedCoroutinesOnInstance(owner) on each entry in
ActiveInstances and sum the returned counts; for GetOwnedCoroutineCount(Node)
call GetOwnedCoroutineCountOnInstance(owner) on each ActiveInstances entry and
return the total. Keep references to Instance, ActiveInstances,
KillOwnedCoroutinesOnInstance, and GetOwnedCoroutineCountOnInstance to locate
the methods.
- Around line 450-475: 在 RunOwnedCoroutineOnInstance 中,RunCoroutineOnInstance
可能会同步完成/失败/取消并导致句柄在返回前被清理(见 HandleCoroutineFinished),因此在调用
RegisterOwnedCoroutine(owner, handle) 前再次确认句柄仍然有效以避免把已结束的协程注册为 owned
coroutine;具体修复是在 RunOwnedCoroutineOnInstance(调用 RunCoroutineOnInstance 后、调用
RegisterOwnedCoroutine 前)再次检查 handle.IsValid(或使用现有的句柄存活判断方法),仅当仍然有效时才调用
RegisterOwnedCoroutine(owner, handle),从而避免污染
_ownedCoroutineRegistrations/_ownedCoroutinesByNode 和不必要的 TreeExiting 订阅。

---

Outside diff comments:
In `@docs/zh-CN/godot/coroutine.md`:
- Around line 28-45: The example uses Demo().RunCoroutine() which defaults to
Segment.Process but then yields WaitForEndOfFrame (which only completes on
Segment.DeferredProcess), causing the coroutine to never progress to
GD.Print("done"); fix by either changing the yield in Demo() from
WaitForEndOfFrame to WaitOneFrame if you want to keep the default RunCoroutine()
behavior, or explicitly run the coroutine in the deferred stage (e.g., call
RunCoroutine(Segment.DeferredProcess)) to preserve the WaitForEndOfFrame
semantics; update the Demo() method or the RunCoroutine() invocation
accordingly.

---

Nitpick comments:
In `@docs/zh-CN/core/coroutine.md`:
- Around line 142-151: 示例中同时展示 LoadDataAsync().AsCoroutineInstruction() 与
scheduler.StartTaskAsCoroutine(LoadDataAsync())
两种入口会让读者不清晰优先级;将文档收敛为单一“推荐写法”(选择一个作为主路径,例如推荐使用 AsCoroutineInstruction() 或者推荐使用
scheduler.StartTaskAsCoroutine(...)),在该段保留选定的主写法并把另一种以“补充/兼容写法”小节或注释形式移动到段落末尾,明确标注哪种为首选并保留对
LoadDataAsync、AsCoroutineInstruction、StartTaskAsCoroutine 和 scheduler
的说明以便定位代码示例。

In `@GFramework.Core/Coroutine/CoroutineScheduler.cs`:
- Around line 582-606: The Clear() method currently relies on FinalizeCoroutine
to remove entries from _completionSources but should defensively clear the
dictionary itself to avoid edge-case leaks; update Clear() to explicitly call
_completionSources.Clear() (e.g., near the other collection clears) so that
_completionSources is always emptied when Clear() completes while leaving
FinalizeCoroutine unchanged.
- Around line 140-152: GetActiveSnapshots currently builds multiple intermediate
allocations via LINQ (anonymous objects and multiple projections). Replace the
LINQ chain in GetActiveSnapshots with an explicit loop: allocate a
List<CoroutineSnapshot> with an appropriate initial capacity, iterate over
_metadata.Values (or _metadata), look up the slot via _slots[meta.SlotIndex],
skip null slots, call CreateSnapshot(meta, slot) and Add the result to the list,
then return list.ToArray() (or the list if IReadOnlyList is acceptable). This
minimizes allocations while keeping behavior identical; refer to
GetActiveSnapshots, _metadata, _slots, and CreateSnapshot to locate the change.
- Around line 754-762: CanAdvanceInstruction currently uses concrete-type
pattern matching (WaitForFixedUpdate, WaitForEndOfFrame) to gate stages which
makes adding new stage-bound instructions error-prone; modify the design by
adding an optional execution-stage marker to IYieldInstruction (e.g. a
nullable/optional property like DesiredExecutionStage or ExecutionStage? on
IYieldInstruction) and then update CanAdvanceInstruction to first check
instruction.ExecutionStage (if set, compare to coroutine's
CoroutineExecutionStage) and only fall back to the existing type checks for
backward compatibility with WaitForFixedUpdate and WaitForEndOfFrame; update the
interface (IYieldInstruction) and the CanAdvanceInstruction method accordingly
so new stage-bound instructions only need to set the property.

In `@GFramework.Godot.Tests/GFramework.Godot.Tests.csproj`:
- Line 10: 当前测试项目的 MSBuild 配置包含
<WarningLevel>0</WarningLevel>,这会全局抑制编译警告;请在对应测试项目的 .csproj 中移除该
<WarningLevel>0</WarningLevel> 元素或将其改为
<WarningLevel>4</WarningLevel>,如需仅屏蔽特定警告则改用 <NoWarn> 列表来有选择地禁用特定警告;定位并修改包含
<WarningLevel>0</WarningLevel> 的块以恢复默认警告可见性。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ae438e8-841d-4571-adef-0a246fe944ec

📥 Commits

Reviewing files that changed from the base of the PR and between a22e522 and 6cac882.

📒 Files selected for processing (17)
  • GFramework.Core.Abstractions/Coroutine/CoroutineCompletionStatus.cs
  • GFramework.Core.Abstractions/Coroutine/CoroutineExecutionStage.cs
  • GFramework.Core.Tests/Coroutine/CoroutineSchedulerAdvancedTests.cs
  • GFramework.Core/Coroutine/CoroutineMetadata.cs
  • GFramework.Core/Coroutine/CoroutineScheduler.cs
  • GFramework.Core/Coroutine/CoroutineSlot.cs
  • GFramework.Core/Coroutine/CoroutineSnapshot.cs
  • GFramework.Godot.Tests/Coroutine/GodotTimeSourceTests.cs
  • GFramework.Godot.Tests/GFramework.Godot.Tests.csproj
  • GFramework.Godot/Coroutine/CoroutineNodeExtensions.cs
  • GFramework.Godot/Coroutine/GodotTimeSource.cs
  • GFramework.Godot/Coroutine/Timing.cs
  • GFramework.csproj
  • GFramework.sln
  • docs/zh-CN/core/coroutine.md
  • docs/zh-CN/godot/coroutine.md
  • docs/zh-CN/tutorials/coroutine-tutorial.md

Comment thread GFramework.Core/Coroutine/CoroutineScheduler.cs
Comment thread GFramework.Core/Coroutine/CoroutineScheduler.cs
Comment thread GFramework.Godot/Coroutine/GodotTimeSource.cs
Comment thread GFramework.Godot/Coroutine/Timing.cs
Comment thread GFramework.Godot/Coroutine/Timing.cs
GeWuYou added 2 commits April 6, 2026 00:33
- 实现了 CoroutineScheduler 类,支持协程的运行、暂停、恢复和终止管理
- 添加了协程槽位管理机制 CoroutineSlot,用于跟踪单个协程状态
- 实现了协程的优先级、标签和分组功能,支持批量操作
- 集成了等待指令系统,包括 WaitForSecondsRealtime、WaitForFixedUpdate 等
- 添加了协程统计功能和异常处理机制
- 实现了 Godot 平台的时间源适配器 GodotTimeSource
- 创建了协程调度器的高级功能测试用例
- 添加了 Timing 节点用于在 Godot 引擎中管理协程生命周期
- 在CI工作流中添加GFramework.Godot.Tests项目的测试执行
- 配置dotnet test命令运行Godot测试套件
- 添加TRX格式的日志输出和测试结果目录设置
- 更新Godot模块的全局引用配置
- 在Timing.cs中添加必要的命名空间引用
- 在GodotTimeSourceTests.cs中添加NUnit框架引用
@GeWuYou
Copy link
Copy Markdown
Owner Author

GeWuYou commented Apr 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@GFramework.Godot/Coroutine/Timing.cs`:
- Around line 1-4: The file is missing the required using for types from
GFramework.Core.Coroutine, causing unresolved symbols like CoroutineScheduler,
CoroutineHandle, and CoroutineSnapshot; fix it by adding an explicit using
directive for GFramework.Core.Coroutine at the top of Timing.cs so those types
resolve (do not rely on implicit imports), then rebuild to ensure
CoroutineScheduler, CoroutineHandle and CoroutineSnapshot are recognized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f99b6cf-8fd7-48fe-a9a1-9fba32839d67

📥 Commits

Reviewing files that changed from the base of the PR and between 6cac882 and 2053451.

📒 Files selected for processing (6)
  • GFramework.Core.Tests/Coroutine/CoroutineSchedulerAdvancedTests.cs
  • GFramework.Core/Coroutine/CoroutineScheduler.cs
  • GFramework.Core/Coroutine/CoroutineSlot.cs
  • GFramework.Godot.Tests/Coroutine/GodotTimeSourceTests.cs
  • GFramework.Godot/Coroutine/GodotTimeSource.cs
  • GFramework.Godot/Coroutine/Timing.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • GFramework.Core/Coroutine/CoroutineSlot.cs
  • GFramework.Core/Coroutine/CoroutineScheduler.cs

Comment thread GFramework.Godot/Coroutine/Timing.cs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
GFramework.Godot/Coroutine/Timing.cs (2)

536-548: 多实例场景下 API 行为不一致。

KillCoroutines(Node)GetOwnedCoroutineCount(Node) 已修复为遍历 ActiveInstances,但 KillCoroutines(string tag)KillAllCoroutines() 仍仅操作单例 Instance。当存在多个 Timing 实例时,按标签终止或全部清除将遗漏非单例实例上的协程。

♻️ 建议与其他 API 保持一致
 public static int KillCoroutines(string tag)
 {
-    return Instance.KillByTagOnInstance(tag);
+    var count = 0;
+    foreach (var timing in EnumerateActiveInstances())
+    {
+        count += timing.KillByTagOnInstance(tag);
+    }
+    return count;
 }

 public static int KillAllCoroutines()
 {
-    return Instance.ClearOnInstance();
+    var count = 0;
+    foreach (var timing in EnumerateActiveInstances())
+    {
+        count += timing.ClearOnInstance();
+    }
+    return count;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GFramework.Godot/Coroutine/Timing.cs` around lines 536 - 548,
KillCoroutines(string tag) and KillAllCoroutines() currently call
Instance.KillByTagOnInstance(tag) and Instance.ClearOnInstance(), which misses
coroutines on other Timing instances; change them to iterate ActiveInstances
(like KillCoroutines(Node) and GetOwnedCoroutineCount(Node) do), call
KillByTagOnInstance(tag) / ClearOnInstance() on each non-null Timing in
ActiveInstances, sum the returned counts, and return the total so behavior is
consistent across multiple Timing instances.

783-789: 考虑记录异常以便调试。

exception 参数未使用。虽然异常可能在调度器层已处理,但在 Timing 层记录调试日志有助于 Godot 侧排查协程失败原因。

💡 可选:添加异常日志
 private void HandleCoroutineFinished(
     CoroutineHandle handle,
     CoroutineCompletionStatus status,
     Exception? exception)
 {
+    if (exception != null)
+    {
+        GD.PrintErr($"[Timing] Coroutine {handle} failed: {exception}");
+    }
     CleanupOwnedCoroutineRegistration(handle);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@GFramework.Godot/Coroutine/Timing.cs` around lines 783 - 789,
HandleCoroutineFinished currently ignores the exception parameter; add a
debug/error log when exception is non-null so coroutine failures are visible
from the Timing layer. Inside HandleCoroutineFinished (which already calls
CleanupOwnedCoroutineRegistration), check if the exception is not null and log
it (including handle and status) via the project's logging mechanism (e.g.,
GD.PrintErr or the existing logger) before or after
CleanupOwnedCoroutineRegistration to aid Godot-side debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 159-165: The workflow runs multiple background `dotnet test` jobs
(e.g., the `dotnet test GFramework.Godot.Tests ... --results-directory
TestResults` invocation) and then uses a bare `wait`, which only returns the
last job's exit code and can hide earlier failures; change to capture each
background PID (store $! for every launched test), wait on each PID
individually, collect and aggregate their exit codes (e.g., track a non-zero
flag or compute max exit code), and then `exit` non-zero if any test returned
failure so the CI step fails when any background test fails.

---

Nitpick comments:
In `@GFramework.Godot/Coroutine/Timing.cs`:
- Around line 536-548: KillCoroutines(string tag) and KillAllCoroutines()
currently call Instance.KillByTagOnInstance(tag) and Instance.ClearOnInstance(),
which misses coroutines on other Timing instances; change them to iterate
ActiveInstances (like KillCoroutines(Node) and GetOwnedCoroutineCount(Node) do),
call KillByTagOnInstance(tag) / ClearOnInstance() on each non-null Timing in
ActiveInstances, sum the returned counts, and return the total so behavior is
consistent across multiple Timing instances.
- Around line 783-789: HandleCoroutineFinished currently ignores the exception
parameter; add a debug/error log when exception is non-null so coroutine
failures are visible from the Timing layer. Inside HandleCoroutineFinished
(which already calls CleanupOwnedCoroutineRegistration), check if the exception
is not null and log it (including handle and status) via the project's logging
mechanism (e.g., GD.PrintErr or the existing logger) before or after
CleanupOwnedCoroutineRegistration to aid Godot-side debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7bc1e405-c8fa-434e-a834-681acf81b974

📥 Commits

Reviewing files that changed from the base of the PR and between 2053451 and d213707.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • GFramework.Godot.Tests/Coroutine/GodotTimeSourceTests.cs
  • GFramework.Godot/Coroutine/Timing.cs
  • GFramework.Godot/GlobalUsings.cs
✅ Files skipped from review due to trivial changes (1)
  • GFramework.Godot/GlobalUsings.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • GFramework.Godot.Tests/Coroutine/GodotTimeSourceTests.cs

Comment thread .github/workflows/ci.yml
Comment on lines +159 to 165
dotnet test GFramework.Godot.Tests \
-c Release \
--no-build \
--logger "trx;LogFileName=godot-$RANDOM.trx" \
--results-directory TestResults &
# 等待所有后台测试完成
wait
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -u
( sleep 0.1; exit 1 ) &
( sleep 0.2; exit 0 ) &
wait
echo "wait exit code: $?"
# 常见结果为 0,说明早期失败可能未反映到最终退出码

Repository: GeWuYou/GFramework

Length of output: 79


🏁 Script executed:

# 查看ci.yml文件的实际内容,特别是第159-165行周围的上下文
head -180 .github/workflows/ci.yml | tail -40

Repository: GeWuYou/GFramework

Length of output: 1320


🏁 Script executed:

# 搜索所有并发执行的dotnet test命令
rg -n "dotnet test.*&" .github/workflows/ci.yml -A 2 -B 2

Repository: GeWuYou/GFramework

Length of output: 44


🏁 Script executed:

# 查看完整的Test All Projects步骤
sed -n '140,180p' .github/workflows/ci.yml

Repository: GeWuYou/GFramework

Length of output: 1321


🏁 Script executed:

# 查看完整的"Test All Projects"步骤
grep -n "Test All Projects" .github/workflows/ci.yml

Repository: GeWuYou/GFramework

Length of output: 97


🏁 Script executed:

# 获取从"Test All Projects"到下一个步骤的完整内容
sed -n '/Test All Projects/,/name:/p' .github/workflows/ci.yml | head -100

Repository: GeWuYou/GFramework

Length of output: 1216


并发测试的失败状态可能被忽略,导致 CI 误判为通过

第 159-165 行把多个测试(包括新增的 Godot 测试)放到后台并发执行,但仍使用裸 wait 等待。在 bash 中,不带参数的 wait 只返回最后完成任务的退出码,导致早期失败的测试不会传播到 step 的最终状态。

建议按 PID 聚合所有后台任务的退出码,确保任何失败都会导致 step 失败:

🔧 建议修复(保留并发、确保失败可见)
       - name: Test All Projects
         run: |
+          pids=()
+
           dotnet test GFramework.Core.Tests \
             -c Release \
             --no-build \
             --logger "trx;LogFileName=core-$RANDOM.trx" \
-            --results-directory TestResults &
+            --results-directory TestResults &
+          pids+=($!)
 
           dotnet test GFramework.Game.Tests \
             -c Release \
             --no-build \
             --logger "trx;LogFileName=game-$RANDOM.trx" \
-            --results-directory TestResults &
+            --results-directory TestResults &
+          pids+=($!)
 
           dotnet test GFramework.SourceGenerators.Tests \
             -c Release \
             --no-build \
             --logger "trx;LogFileName=sg-$RANDOM.trx" \
-            --results-directory TestResults &
+            --results-directory TestResults &
+          pids+=($!)
 
           dotnet test GFramework.Ecs.Arch.Tests \
             -c Release \
             --no-build \
             --logger "trx;LogFileName=ecs-arch-$RANDOM.trx" \
-            --results-directory TestResults &
+            --results-directory TestResults &
+          pids+=($!)
 
           dotnet test GFramework.Godot.Tests \
             -c Release \
             --no-build \
             --logger "trx;LogFileName=godot-$RANDOM.trx" \
-            --results-directory TestResults &
-          # 等待所有后台测试完成
-          wait
+            --results-directory TestResults &
+          pids+=($!)
+
+          # 等待所有后台测试完成,并汇总失败
+          status=0
+          for pid in "${pids[@]}"; do
+            wait "$pid" || status=1
+          done
+          exit "$status"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 159 - 165, The workflow runs multiple
background `dotnet test` jobs (e.g., the `dotnet test GFramework.Godot.Tests ...
--results-directory TestResults` invocation) and then uses a bare `wait`, which
only returns the last job's exit code and can hide earlier failures; change to
capture each background PID (store $! for every launched test), wait on each PID
individually, collect and aggregate their exit codes (e.g., track a non-zero
flag or compute max exit code), and then `exit` non-zero if any test returned
failure so the CI step fails when any background test fails.

@GeWuYou GeWuYou merged commit e67cfd4 into main Apr 5, 2026
10 checks passed
@GeWuYou GeWuYou deleted the feat/coroutine-core-and-godot-integration branch April 5, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant