# Godot 协程系统

## 概述

`GFramework.Godot.Coroutine` 在 Core 协程内核之上提供 Godot 宿主集成，负责把 Godot 的不同更新循环映射为真实的协程阶段语义：

- `Segment.Process`
- `Segment.ProcessIgnorePause`
- `Segment.PhysicsProcess`
- `Segment.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.ProcessCoroutines`
- `Timing.Instance.ProcessIgnorePauseCoroutines`
- `Timing.Instance.PhysicsCoroutines`
- `Timing.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`、节点归属和标签控制一起使用。
