Godot 协程系统
概述
GFramework.Godot.Coroutine 在 Core 协程内核之上提供 Godot 宿主集成,负责把 Godot 的不同更新循环映射为真实的协程阶段语义:
Segment.ProcessSegment.ProcessIgnorePauseSegment.PhysicsProcessSegment.DeferredProcess
它同时补充了以下宿主能力:
- 节点归属协程运行入口
- 节点退树自动终止
- Godot 真实时间源
- 句柄控制与快照查询
启动协程
直接运行枚举器
csharp
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine.Instructions;
using GFramework.Godot.Coroutine;
public partial class DemoNode : Node
{
public override void _Ready()
{
Demo().RunCoroutine();
}
private IEnumerator<IYieldInstruction> Demo()
{
GD.Print("start");
yield return new Delay(1.0);
yield return new WaitForEndOfFrame();
GD.Print("done");
}
}默认情况下,RunCoroutine() 会在 Segment.Process 上运行。
以 Node 作为生命周期所有者运行
更推荐的方式是以节点为入口运行协程:
csharp
public override void _Ready()
{
this.RunCoroutine(LongRunningTask(), Segment.Process, tag: "ui-blink");
}这会自动把协程登记为该节点归属协程,并在节点退出场景树时终止它。
你仍然可以继续使用 CancelWith(...) 包装已有枚举器;它适合把一个协程显式绑定到多个节点生命周期。
Segment 与阶段语义
Godot 层会把不同 segment 映射为不同的 CoroutineExecutionStage:
Segment.Process- 对应默认更新阶段
- 场景树暂停时不会推进
Segment.ProcessIgnorePause- 同样对应默认更新阶段
- 场景树暂停时仍会推进
Segment.PhysicsProcess- 对应固定更新阶段
WaitForFixedUpdate会在这里真实完成
Segment.DeferredProcess- 对应帧结束阶段
WaitForEndOfFrame会在这里真实完成
示例:
csharp
this.RunCoroutine(PhysicsRoutine(), Segment.PhysicsProcess);
this.RunCoroutine(UiAnimation(), Segment.ProcessIgnorePause);时间等待语义
Godot 集成层为每个调度器同时提供了两套时间源:
- 缩放时间
- 来自
_Process/_PhysicsProcess的帧增量
- 来自
- 真实时间
- 来自 Godot 单调时钟,不受时间缩放和暂停影响
因此:
Delay/WaitForSecondsScaled使用宿主帧增量WaitForSecondsRealtime使用真实时间
这意味着 UI 或暂停菜单中的协程可以安全使用 WaitForSecondsRealtime 保持真实计时。
生命周期管理
自动归属
csharp
var handle = this.RunCoroutine(LoadAvatar(), tag: "avatar");手动绑定多个节点
csharp
LongRunningTask()
.CancelWith(this, panelNode)
.RunCoroutine();主动清理
csharp
Timing.KillCoroutine(handle);
Timing.KillCoroutines(this);
Timing.KillCoroutines("avatar");
Timing.KillAllCoroutines();调试与查询
csharp
if (Timing.TryGetCoroutineSnapshot(handle, out var snapshot))
{
GD.Print(snapshot.ExecutionStage);
GD.Print(snapshot.WaitingInstructionType);
}
var ownedCount = Timing.GetOwnedCoroutineCount(this);实例级计数器:
Timing.Instance.ProcessCoroutinesTiming.Instance.ProcessIgnorePauseCoroutinesTiming.Instance.PhysicsCoroutinesTiming.Instance.DeferredCoroutines
延迟调用
csharp
Timing.CallDelayed(1.0, () => GD.Print("1 秒后执行"));
Timing.CallDelayed(1.0, () => GD.Print("节点仍然有效时执行"), this);第二个重载内部使用节点归属语义,因此节点退树后不会再触发动作。
与 IContextAware 集成
Godot 层还提供以下扩展方法,用于把命令、查询和通知直接包装成协程并交给 Timing 调度:
RunCommandCoroutine(...)RunCommandCoroutine<TResponse>(...)RunQueryCoroutine<TResponse>(...)RunPublishCoroutine(...)
这些 API 仍然可以与 Segment、节点归属和标签控制一起使用。