GeWuYou a79f02c987 docs(api): 添加 Core API 参考文档与事件系统接口文档
- 新增 Core API 参考文档,涵盖架构与模块、数据模型与系统、命令与查询等核心组件
- 添加事件系统接口详细文档,包括 IEvent、IEventBus、IUnRegister 等接口说明
- 提供完整的 API 使用示例路径、最佳实践与性能建议
- 包含架构图、依赖关系图与故障排查指南
- 添加测试用例参考与扩展方法说明
- [skip ci]
2026-01-21 23:45:10 +08:00

21 KiB
Raw Blame History

等待指令系统

**本文档引用的文件** - [IYieldInstruction.cs](file://GFramework.Core.Abstractions/coroutine/IYieldInstruction.cs) - [ITimeSource.cs](file://GFramework.Core.Abstractions/coroutine/ITimeSource.cs) - [Delay.cs](file://GFramework.Core/coroutine/Delay.cs) - [WaitOneFrame.cs](file://GFramework.Core/coroutine/WaitOneFrame.cs) - [WaitForFrames.cs](file://GFramework.Core/coroutine/WaitForFrames.cs) - [WaitUntil.cs](file://GFramework.Core/coroutine/WaitUntil.cs) - [WaitWhile.cs](file://GFramework.Core/coroutine/WaitWhile.cs) - [WaitForCoroutine.cs](file://GFramework.Core/coroutine/WaitForCoroutine.cs) - [CoroutineScheduler.cs](file://GFramework.Core/coroutine/CoroutineScheduler.cs) - [CoroutineSlot.cs](file://GFramework.Core/coroutine/CoroutineSlot.cs) - [CoroutineMetadata.cs](file://GFramework.Core/coroutine/CoroutineMetadata.cs) - [CoroutineHandle.cs](file://GFramework.Core/coroutine/CoroutineHandle.cs) - [CoroutineHelper.cs](file://GFramework.Core/coroutine/CoroutineHelper.cs) - [GodotTimeSource.cs](file://GFramework.Godot/coroutine/GodotTimeSource.cs) - [Timing.cs](file://GFramework.Godot/coroutine/Timing.cs) - [YieldInstructionTests.cs](file://GFramework.Core.Tests/coroutine/YieldInstructionTests.cs)

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能考量
  8. 故障排查指南
  9. 结论
  10. 附录

简介

本文件面向游戏开发中的协程等待指令系统提供完整的API文档与使用指南。内容涵盖等待指令接口设计、各等待指令的实现原理与使用场景、调度器工作流、时间源抽象、在Godot平台的集成方式、性能对比与选择建议、复杂等待组合的最佳实践以及调试技巧与常见陷阱。

项目结构

等待指令系统由“抽象接口层”“核心实现层”“平台集成层”“测试验证层”构成:

  • 抽象接口层:定义等待指令与时间源的契约,确保跨平台可替换性
  • 核心实现层:提供具体等待指令与调度器、句柄、元数据、槽位等
  • 平台集成层Godot时间源与Timing节点提供三段式Process/PhysicsProcess/Deferred协程调度
  • 测试验证层:对各等待指令行为进行单元测试,覆盖边界与正确性
graph TB
subgraph "抽象层"
IYield["IYieldInstruction 接口"]
ITime["ITimeSource 接口"]
end
subgraph "核心实现层"
DelayC["Delay"]
WOne["WaitOneFrame"]
WFrm["WaitForFrames"]
WUnt["WaitUntil"]
WWhl["WaitWhile"]
WCro["WaitForCoroutine"]
Sch["CoroutineScheduler"]
Slot["CoroutineSlot"]
Meta["CoroutineMetadata"]
Hdl["CoroutineHandle"]
Help["CoroutineHelper"]
end
subgraph "平台集成层"
GTime["GodotTimeSource"]
TimingN["Timing 节点"]
end
subgraph "测试层"
Tests["YieldInstructionTests"]
end
IYield --> DelayC
IYield --> WOne
IYield --> WFrm
IYield --> WUnt
IYield --> WWhl
IYield --> WCro
ITime --> GTime
GTime --> Sch
Sch --> Slot
Sch --> Meta
Sch --> Hdl
Sch --> Help
TimingN --> Sch
TimingN --> GTime
Tests --> DelayC
Tests --> WOne
Tests --> WFrm
Tests --> WUnt
Tests --> WWhl
Tests --> WCro

图表来源

章节来源

核心组件

  • IYieldInstruction等待指令契约定义每帧更新与完成判定
  • ITimeSource时间源契约提供当前时间与时间增量
  • Delay基于时间的延迟等待
  • WaitOneFrame单帧等待
  • WaitForFrames按帧数等待
  • WaitUntil/WaitWhile基于布尔谓词的条件等待
  • WaitForCoroutine等待另一个协程完成
  • CoroutineScheduler协程调度器驱动等待指令与推进协程
  • CoroutineSlot/Metadata/Handle调度器内部数据结构
  • CoroutineHelper等待指令工厂与常用组合
  • GodotTimeSource/TimingGodot平台的时间源与三段式调度入口

章节来源

架构总览

等待指令系统采用“接口抽象 + 可插拔时间源 + 调度器驱动”的架构。调度器在每帧读取时间源增量,驱动当前等待指令更新,若完成则推进协程枚举器;若遇到新的等待指令则切换等待对象。

sequenceDiagram
participant T as "Timing 节点"
participant S as "CoroutineScheduler"
participant TS as "ITimeSource/GodotTimeSource"
participant E as "IEnumerator<IYieldInstruction>"
participant Y as "IYieldInstruction"
T->>S : "Update()"
S->>TS : "Update()"
TS-->>S : "CurrentTime, DeltaTime"
loop 遍历所有协程槽位
S->>E : "MoveNext()"
alt 枚举器结束
S->>S : "Complete(slot)"
else 返回新等待指令
S->>Y : "Waiting = Current"
S->>Y : "Update(DeltaTime)"
alt 等待完成
S->>S : "Waiting = null"
else 仍在等待
S->>S : "跳过该协程本轮推进"
end
end
end

图表来源

详细组件分析

IYieldInstruction 接口与实现规范

  • 设计要点
    • 每帧由调度器调用 Update(deltaTime),用于更新内部状态
    • IsDone 用于表达等待是否完成,调度器据此决定是否推进协程
    • 实现应保持幂等与无副作用,避免在 Update 中产生不可预期的外部状态变化
  • 实现约定
    • 对于时间型等待,应在 Update 中消耗剩余时间并更新完成标志
    • 对于帧型等待,应在 Update 中减少剩余帧数或标记完成
    • 对于条件等待,应在 Update 中保持空实现,完成标志由 IsDone 的谓词决定

章节来源

Delay 时间延迟机制

  • 功能概述
    • 基于秒数的绝对时间等待,内部维护剩余时间并在每帧递减
  • 关键行为
    • 初始剩余时间为最大值秒数取非负Update 递减剩余时间
    • 完成条件为剩余时间小于等于 0
  • 精确度与性能
    • 精确度受 DeltaTime 影响,建议在高帧率场景下注意累积误差
    • 性能开销极小,仅需一次浮点减法与一次比较
  • 使用建议
    • 适合通用延迟、节流、倒计时等场景
    • 与 RepeatCall/RepeatCallForever 组合可实现周期性任务
flowchart TD
Start(["进入 Update"]) --> Dec["剩余时间 -= deltaTime"]
Dec --> Check{"剩余时间 <= 0 ?"}
Check --> |否| End(["返回未完成"])
Check --> |是| Done(["标记完成"])

图表来源

章节来源

WaitOneFrame 单帧等待

  • 功能概述
    • 等待恰好一帧,常用于“下一帧再执行”的语义
  • 关键行为
    • Update 第一次调用即标记完成
    • 适合需要严格帧序控制的场景
  • 性能
    • 开销极低,仅一次布尔赋值
  • 使用建议
    • 与动画同步、UI刷新节奏配合
    • 注意与其他按帧等待指令叠加时的语义
flowchart TD
Enter(["Update 调用"]) --> SetDone["标记完成"]
SetDone --> Exit(["返回完成"])

图表来源

章节来源

WaitForFrames 帧等待

  • 功能概述
    • 等待固定帧数,内部维护剩余帧数并在每帧递减
  • 关键行为
    • 最小等待帧数为 10 或负数均视为 1 帧
    • 每次 Update 减少 1 帧,完成条件为剩余帧数 ≤ 0
  • 性能
    • 每帧一次整数递减与比较,开销极低
  • 使用建议
    • 适合与渲染/物理更新节奏对齐的任务
    • 与 Delay 组合可实现“先延迟 N 秒,再等待 M 帧”的复合效果
flowchart TD
Start(["Update 调用"]) --> Dec["剩余帧数--"]
Dec --> Check{"剩余帧数 <= 0 ?"}
Check --> |否| End(["返回未完成"])
Check --> |是| Done(["标记完成"])

图表来源

章节来源

WaitUntil 与 WaitWhile 条件等待

  • 功能概述
    • WaitUntil当谓词返回 true 时完成
    • WaitWhile当谓词返回 false 时完成(即“条件为真时继续等待”)
  • 关键行为
    • Update 为空实现,完成标志直接由谓词结果决定
    • 谓词必须稳定、无副作用,避免外部状态在 Update 期间被修改
  • 性能
    • 每帧一次谓词调用与一次布尔比较
    • 建议谓词逻辑轻量,避免阻塞或昂贵运算
  • 使用建议
    • WaitUntil 适合“等待某事件发生”
    • WaitWhile 适合“持续等待某条件不再满足”
flowchart TD
Enter(["Update 调用"]) --> Eval["读取谓词结果"]
Eval --> Cond{"完成条件满足?"}
Cond --> |是| Done(["标记完成"])
Cond --> |否| End(["保持等待"])

图表来源

章节来源

WaitForCoroutine 等待协程完成

  • 功能概述
    • 等待另一个协程完成,通常通过调度器的等待机制实现
  • 关键行为
    • Update 不改变状态,完成标志由外部标记(内部方法)控制
    • 一般通过调度器的“等待协程”机制唤醒当前协程
  • 使用建议
    • 适合协程间同步执行顺序
    • 注意避免自等待(调度器会拒绝)

章节来源

调度器与时间源

  • CoroutineScheduler
    • 管理协程槽位、元数据与等待集合,负责推进协程与处理异常
    • 每帧更新时先驱动等待指令,再推进协程枚举器
    • 支持暂停/恢复/终止/按标签终止/清空等控制
  • ITimeSource/GodotTimeSource
    • 提供 DeltaTime 与 CurrentTimeGodotTimeSource 从引擎获取增量时间并累加总时间
  • TimingGodot
    • 提供三段式调度Process/PhysicsProcess/Deferred
    • 每帧驱动对应调度器,支持延迟调用与协程控制 API
classDiagram
class IYieldInstruction {
+bool IsDone
+void Update(deltaTime)
}
class ITimeSource {
+double CurrentTime
+double DeltaTime
+void Update()
}
class Delay {
-double _remaining
+void Update(deltaTime)
+bool IsDone
}
class WaitOneFrame {
-bool _done
+void Update(deltaTime)
+bool IsDone
}
class WaitForFrames {
-int _remaining
+void Update(deltaTime)
+bool IsDone
}
class WaitUntil {
-Func~bool~ _predicate
+void Update(deltaTime)
+bool IsDone
}
class WaitWhile {
-Func~bool~ _predicate
+void Update(deltaTime)
+bool IsDone
}
class CoroutineScheduler {
+double DeltaTime
+int ActiveCoroutineCount
+Run(coroutine, tag)
+Update()
+Pause(handle)
+Resume(handle)
+Kill(handle)
+KillByTag(tag)
+Clear()
}
class GodotTimeSource {
+double CurrentTime
+double DeltaTime
+void Update()
+void Reset()
}
IYieldInstruction <|.. Delay
IYieldInstruction <|.. WaitOneFrame
IYieldInstruction <|.. WaitForFrames
IYieldInstruction <|.. WaitUntil
IYieldInstruction <|.. WaitWhile
ITimeSource <|.. GodotTimeSource
CoroutineScheduler --> ITimeSource : "使用"
CoroutineScheduler --> IYieldInstruction : "驱动"

图表来源

章节来源

依赖关系分析

  • 组件耦合
    • IYieldInstruction 是所有等待指令的统一契约,降低调度器与具体等待逻辑的耦合
    • ITimeSource 抽象时间源,使调度器可在不同平台复用
    • Timing 将调度器与 Godot 的生命周期绑定,提供三段式调度入口
  • 外部依赖
    • GodotTimeSource 依赖引擎提供的增量时间函数
    • 调度器内部使用字典与数组管理协程,注意扩容与内存占用
  • 循环依赖
    • 未发现直接循环依赖WaitForCoroutine 通过调度器等待集合间接关联
graph LR
IYield["IYieldInstruction"] --> Impl["各等待指令实现"]
ITime["ITimeSource"] --> Sch["CoroutineScheduler"]
Sch --> Slot["CoroutineSlot"]
Sch --> Meta["CoroutineMetadata"]
Sch --> Hdl["CoroutineHandle"]
Sch --> Help["CoroutineHelper"]
GTime["GodotTimeSource"] --> Sch
TimingN["Timing"] --> Sch
TimingN --> GTime

图表来源

章节来源

性能考量

  • 时间型 vs 帧型 vs 条件型
    • Delay每帧一次浮点减法与比较开销极低
    • WaitOneFrame/WaitForFrames整数递减与比较开销极低
    • WaitUntil/WaitWhile每帧一次谓词调用与比较开销取决于谓词复杂度
  • 选择建议
    • 需要与帧率解耦的延迟:优先 Delay
    • 需要与渲染/物理节奏对齐:优先 WaitForFrames
    • 需要语义化条件:优先 WaitUntil/WaitWhile
    • 复杂等待组合:建议拆分为多个简单指令,便于调试与性能分析
  • 优化技巧
    • 将谓词逻辑尽量轻量化,避免在 Update 中执行昂贵操作
    • 合理使用标签与句柄,避免大规模遍历
    • 控制并发协程数量,防止调度器扩容与内存压力

[本节为通用性能指导,无需特定文件引用]

故障排查指南

  • 常见问题
    • 条件等待永不完成:检查谓词是否可终止,避免无限循环
    • 帧等待异常提前完成:确认传入帧数为正整数,注意最小帧数约束
    • 协程卡住:检查是否有未完成的等待指令或异常被捕获并导致提前结束
    • 自等待异常WaitForCoroutine 不允许等待自身
  • 调试技巧
    • 使用测试用例验证边界行为(零/负数秒、零帧、谓词瞬时满足)
    • 在 Timing 节点中观察各段活跃协程数量
    • 通过标签批量终止协程,定位问题协程
  • 常见陷阱
    • 在 Update 中修改共享状态导致谓词不稳定
    • 忽略异常处理,导致协程意外结束
    • 过度依赖高频率轮询,造成性能问题

章节来源

结论

等待指令系统通过清晰的接口抽象与可插拔时间源,提供了高效、可组合的协程等待能力。在 Godot 平台上Timing 节点进一步将调度器与引擎生命周期绑定,支持多段式调度。合理选择等待指令、遵循实现规范与性能建议,可显著提升游戏开发中的异步流程可控性与稳定性。

[本节为总结性内容,无需特定文件引用]

附录

API 一览与使用建议

  • Delay(seconds)
    • 适用:通用延迟、节流、倒计时
    • 建议:与 RepeatCall/RepeatCallForever 组合
  • WaitOneFrame()
    • 适用:严格帧序控制
    • 建议:与动画/UI刷新节奏配合
  • WaitForFrames(frames)
    • 适用:与渲染/物理节奏对齐
    • 建议:最小帧数为 1避免 0/- 帧
  • WaitUntil(predicate)
    • 适用:等待事件发生
    • 建议:谓词稳定、无副作用
  • WaitWhile(predicate)
    • 适用:持续等待条件不再满足
    • 建议:谓词稳定、无副作用
  • WaitForCoroutine()
    • 适用:协程间同步
    • 建议:避免自等待,使用调度器等待机制

章节来源

典型应用场景

  • 动画同步:使用 WaitForFrames 对齐关键帧
  • 延迟执行:使用 Delay 实现延迟调用
  • 条件触发:使用 WaitUntil/WaitWhile 实现事件驱动
  • 复杂等待组合:将多个简单等待指令串联,形成清晰的流程图

[本节为概念性内容,无需特定文件引用]