From ddbf7af5721086da2465eab6ecd4636e1a95ed03 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Wed, 21 Jan 2026 20:34:10 +0800
Subject: [PATCH] =?UTF-8?q?feat(coroutine):=20=E6=B7=BB=E5=8A=A0=E5=AE=8C?=
=?UTF-8?q?=E6=95=B4=E7=9A=84=E5=8D=8F=E7=A8=8B=E7=B3=BB=E7=BB=9F=E5=AE=9E?=
=?UTF-8?q?=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 实现了协程调度器和句柄管理机制
- 添加了多种等待指令包括延时、帧数、条件等待等
- 创建了协程辅助方法和扩展功能
- 集成了Godot引擎的时间源和生命周期管理
- 实现了协程的暂停、恢复、终止等控制功能
- 添加了协程标签管理和按标签批量操作功能
- 提供了与Godot节点生命周期绑定的取消机制
---
.../coroutine/CoroutineState.cs | 32 ++
.../coroutine/ITimeSource.cs | 22 +
.../coroutine/IYieldInstruction.cs | 18 +
GFramework.Core/coroutine/CoroutineHandle.cs | 94 +++++
GFramework.Core/coroutine/CoroutineHelper.cs | 101 +++++
.../coroutine/CoroutineMetadata.cs | 32 ++
.../coroutine/CoroutineScheduler.cs | 392 ++++++++++++++++++
GFramework.Core/coroutine/CoroutineSlot.cs | 24 ++
GFramework.Core/coroutine/Delay.cs | 29 ++
GFramework.Core/coroutine/WaitForCoroutine.cs | 29 ++
GFramework.Core/coroutine/WaitForFrames.cs | 29 ++
GFramework.Core/coroutine/WaitOneFrame.cs | 26 ++
GFramework.Core/coroutine/WaitUntil.cs | 26 ++
GFramework.Core/coroutine/WaitWhile.cs | 26 ++
.../coroutine/CoroutineExtensions.cs | 66 +++
GFramework.Godot/coroutine/GodotTimeSource.cs | 44 ++
GFramework.Godot/coroutine/Segment.cs | 22 +
GFramework.Godot/coroutine/Timing.cs | 374 +++++++++++++++++
18 files changed, 1386 insertions(+)
create mode 100644 GFramework.Core.Abstractions/coroutine/CoroutineState.cs
create mode 100644 GFramework.Core.Abstractions/coroutine/ITimeSource.cs
create mode 100644 GFramework.Core.Abstractions/coroutine/IYieldInstruction.cs
create mode 100644 GFramework.Core/coroutine/CoroutineHandle.cs
create mode 100644 GFramework.Core/coroutine/CoroutineHelper.cs
create mode 100644 GFramework.Core/coroutine/CoroutineMetadata.cs
create mode 100644 GFramework.Core/coroutine/CoroutineScheduler.cs
create mode 100644 GFramework.Core/coroutine/CoroutineSlot.cs
create mode 100644 GFramework.Core/coroutine/Delay.cs
create mode 100644 GFramework.Core/coroutine/WaitForCoroutine.cs
create mode 100644 GFramework.Core/coroutine/WaitForFrames.cs
create mode 100644 GFramework.Core/coroutine/WaitOneFrame.cs
create mode 100644 GFramework.Core/coroutine/WaitUntil.cs
create mode 100644 GFramework.Core/coroutine/WaitWhile.cs
create mode 100644 GFramework.Godot/coroutine/CoroutineExtensions.cs
create mode 100644 GFramework.Godot/coroutine/GodotTimeSource.cs
create mode 100644 GFramework.Godot/coroutine/Segment.cs
create mode 100644 GFramework.Godot/coroutine/Timing.cs
diff --git a/GFramework.Core.Abstractions/coroutine/CoroutineState.cs b/GFramework.Core.Abstractions/coroutine/CoroutineState.cs
new file mode 100644
index 0000000..0ee6e3c
--- /dev/null
+++ b/GFramework.Core.Abstractions/coroutine/CoroutineState.cs
@@ -0,0 +1,32 @@
+namespace GFramework.Core.Abstractions.coroutine;
+
+///
+/// 表示协程的执行状态枚举
+///
+public enum CoroutineState
+{
+ ///
+ /// 协程正在运行中
+ ///
+ Running,
+
+ ///
+ /// 协程已暂停
+ ///
+ Paused,
+
+ ///
+ /// 协程被锁定或等待其他协程完成
+ ///
+ Held,
+
+ ///
+ /// 协程已完成执行
+ ///
+ Completed,
+
+ ///
+ /// 协程已被取消
+ ///
+ Cancelled,
+}
\ No newline at end of file
diff --git a/GFramework.Core.Abstractions/coroutine/ITimeSource.cs b/GFramework.Core.Abstractions/coroutine/ITimeSource.cs
new file mode 100644
index 0000000..f15e065
--- /dev/null
+++ b/GFramework.Core.Abstractions/coroutine/ITimeSource.cs
@@ -0,0 +1,22 @@
+namespace GFramework.Core.Abstractions.coroutine;
+
+///
+/// 时间源接口,提供当前时间、时间增量以及更新功能
+///
+public interface ITimeSource
+{
+ ///
+ /// 获取当前时间
+ ///
+ double CurrentTime { get; }
+
+ ///
+ /// 获取时间增量(上一帧到当前帧的时间差)
+ ///
+ double DeltaTime { get; }
+
+ ///
+ /// 更新时间源的状态
+ ///
+ void Update();
+}
\ No newline at end of file
diff --git a/GFramework.Core.Abstractions/coroutine/IYieldInstruction.cs b/GFramework.Core.Abstractions/coroutine/IYieldInstruction.cs
new file mode 100644
index 0000000..761a57a
--- /dev/null
+++ b/GFramework.Core.Abstractions/coroutine/IYieldInstruction.cs
@@ -0,0 +1,18 @@
+namespace GFramework.Core.Abstractions.coroutine;
+
+///
+/// 定义一个可等待指令的接口,用于协程系统中的异步操作控制
+///
+public interface IYieldInstruction
+{
+ ///
+ /// 获取当前等待指令是否已完成执行
+ ///
+ bool IsDone { get; }
+
+ ///
+ /// 每帧由调度器调用,用于更新当前等待指令的状态
+ ///
+ /// 自上一帧以来的时间间隔(以秒为单位)
+ void Update(double deltaTime);
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/CoroutineHandle.cs b/GFramework.Core/coroutine/CoroutineHandle.cs
new file mode 100644
index 0000000..54d6427
--- /dev/null
+++ b/GFramework.Core/coroutine/CoroutineHandle.cs
@@ -0,0 +1,94 @@
+namespace GFramework.Core.coroutine;
+
+///
+/// 协程句柄
+/// 用于唯一标识和管理协程实例的结构体,通过ID系统实现协程的跟踪和比较功能
+///
+public readonly struct CoroutineHandle : IEquatable
+{
+ ///
+ /// 预留空间常量,用于ID分配的基数
+ ///
+ private const byte ReservedSpace = 0x0F;
+
+ ///
+ /// 下一个索引数组,用于跟踪每个实例ID的下一个可用索引位置
+ /// 索引范围:0-15,对应16个不同的实例槽位
+ ///
+ private static readonly int[] NextIndex = new int[16];
+
+ ///
+ /// 协程句柄的内部ID,用于唯一标识协程实例
+ ///
+ private readonly int _id;
+
+ ///
+ /// 静态构造函数,初始化NextIndex数组的默认值
+ /// 将索引0的下一个可用位置设置为ReservedSpace + 1
+ ///
+ static CoroutineHandle()
+ {
+ NextIndex[0] = ReservedSpace + 1;
+ }
+
+
+ ///
+ /// 获取当前协程句柄的键值(低4位)
+ ///
+ public byte Key => (byte)(_id & ReservedSpace);
+
+ ///
+ /// 判断当前协程句柄是否有效
+ /// 有效性通过Key是否为0来判断
+ ///
+ public bool IsValid => Key != 0;
+
+ ///
+ /// 构造函数,创建一个新的协程句柄
+ ///
+ /// 实例ID,用于区分不同的协程实例槽位
+ public CoroutineHandle(byte instanceId)
+ {
+ if (instanceId > ReservedSpace)
+ instanceId -= ReservedSpace;
+
+ _id = NextIndex[instanceId] + instanceId;
+ NextIndex[instanceId] += ReservedSpace + 1;
+ }
+
+ ///
+ /// 比较当前协程句柄与另一个协程句柄是否相等
+ ///
+ /// 要比较的协程句柄
+ /// 如果两个句柄的ID相同则返回true,否则返回false
+ public bool Equals(CoroutineHandle other) => _id == other._id;
+
+ ///
+ /// 比较当前对象与指定对象是否相等
+ ///
+ /// 要比较的对象
+ /// 如果对象是协程句柄且ID相同则返回true,否则返回false
+ public override bool Equals(object? obj) => obj is CoroutineHandle handle && Equals(handle);
+
+ ///
+ /// 获取当前协程句柄的哈希码
+ ///
+ /// 基于内部ID计算的哈希码
+ public override int GetHashCode() => _id;
+
+ ///
+ /// 比较两个协程句柄是否相等
+ ///
+ /// 第一个协程句柄
+ /// 第二个协程句柄
+ /// 如果两个句柄的ID相同则返回true,否则返回false
+ public static bool operator ==(CoroutineHandle a, CoroutineHandle b) => a._id == b._id;
+
+ ///
+ /// 比较两个协程句柄是否不相等
+ ///
+ /// 第一个协程句柄
+ /// 第二个协程句柄
+ /// 如果两个句柄的ID不同则返回true,否则返回false
+ public static bool operator !=(CoroutineHandle a, CoroutineHandle b) => a._id != b._id;
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/CoroutineHelper.cs b/GFramework.Core/coroutine/CoroutineHelper.cs
new file mode 100644
index 0000000..b6054b9
--- /dev/null
+++ b/GFramework.Core/coroutine/CoroutineHelper.cs
@@ -0,0 +1,101 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 协程辅助方法
+///
+public static class CoroutineHelper
+{
+ ///
+ /// 等待指定秒数
+ ///
+ /// 要等待的秒数
+ /// 延迟等待指令
+ public static Delay WaitForSeconds(double seconds)
+ {
+ return new Delay(seconds);
+ }
+
+ ///
+ /// 等待一帧
+ ///
+ /// 等待一帧的指令
+ public static WaitOneFrame WaitForOneFrame()
+ {
+ return new WaitOneFrame();
+ }
+
+ ///
+ /// 等待指定帧数
+ ///
+ /// 要等待的帧数
+ /// 等待帧数指令
+ public static WaitForFrames WaitForFrames(int frames)
+ {
+ return new WaitForFrames(frames);
+ }
+
+ ///
+ /// 等待直到条件满足
+ ///
+ /// 条件判断函数
+ /// 等待条件指令
+ public static WaitUntil WaitUntil(Func predicate)
+ {
+ return new WaitUntil(predicate);
+ }
+
+ ///
+ /// 等待当条件为真时持续等待
+ ///
+ /// 条件判断函数
+ /// 等待条件指令
+ public static WaitWhile WaitWhile(Func predicate)
+ {
+ return new WaitWhile(predicate);
+ }
+
+ ///
+ /// 延迟调用指定的委托
+ ///
+ /// 延迟时间(秒)
+ /// 要执行的动作委托
+ /// 返回一个枚举器,用于协程执行
+ public static IEnumerator DelayedCall(double delay, Action? action)
+ {
+ yield return new Delay(delay);
+ action?.Invoke();
+ }
+
+ ///
+ /// 重复调用指定的委托指定次数
+ ///
+ /// 每次调用之间的间隔时间(秒)
+ /// 调用次数
+ /// 要执行的动作委托
+ /// 返回一个枚举器,用于协程执行
+ public static IEnumerator RepeatCall(double interval, int count, Action? action)
+ {
+ for (var i = 0; i < count; i++)
+ {
+ action?.Invoke();
+ yield return new Delay(interval);
+ }
+ }
+
+ ///
+ /// 无限重复调用指定的委托
+ ///
+ /// 每次调用之间的间隔时间(秒)
+ /// 要执行的动作委托
+ /// 返回一个枚举器,用于协程执行
+ public static IEnumerator RepeatCallForever(double interval, Action? action)
+ {
+ while (true)
+ {
+ action?.Invoke();
+ yield return new Delay(interval);
+ }
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/CoroutineMetadata.cs b/GFramework.Core/coroutine/CoroutineMetadata.cs
new file mode 100644
index 0000000..4707831
--- /dev/null
+++ b/GFramework.Core/coroutine/CoroutineMetadata.cs
@@ -0,0 +1,32 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 存储协程元数据信息的内部类,包含协程的状态、枚举器、标签等信息
+///
+internal class CoroutineMetadata
+{
+ ///
+ /// 协程在调度器中的槽位索引
+ ///
+ public int SlotIndex;
+
+ ///
+ /// 协程当前的执行状态
+ ///
+ public CoroutineState State;
+
+ ///
+ /// 协程的标签标识符,用于协程的分类和查找
+ ///
+ public string? Tag;
+
+ ///
+ /// 判断协程是否处于活跃状态(运行中、暂停或挂起)
+ ///
+ public bool IsActive =>
+ State is CoroutineState.Running
+ or CoroutineState.Paused
+ or CoroutineState.Held;
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/CoroutineScheduler.cs b/GFramework.Core/coroutine/CoroutineScheduler.cs
new file mode 100644
index 0000000..89b3753
--- /dev/null
+++ b/GFramework.Core/coroutine/CoroutineScheduler.cs
@@ -0,0 +1,392 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 协程调度器,用于管理和执行协程
+///
+/// 时间源接口,提供时间相关数据
+/// 实例ID,默认为1
+/// 初始容量,默认为256
+public sealed class CoroutineScheduler(
+ ITimeSource timeSource,
+ byte instanceId = 1,
+ int initialCapacity = 256)
+{
+ private readonly Dictionary _metadata = new();
+ private readonly Dictionary> _tagged = new();
+ private readonly ITimeSource _timeSource = timeSource ?? throw new ArgumentNullException(nameof(timeSource));
+ private readonly Dictionary> _waiting = new();
+ private int _activeCount;
+ private int _nextSlot;
+
+ private CoroutineSlot?[] _slots = new CoroutineSlot?[initialCapacity];
+
+ ///
+ /// 获取时间差值
+ ///
+ public double DeltaTime => _timeSource.DeltaTime;
+
+ ///
+ /// 获取活跃协程数量
+ ///
+ public int ActiveCoroutineCount => _activeCount;
+
+ #region Run / Update
+
+ ///
+ /// 运行协程
+ ///
+ /// 要运行的协程枚举器
+ /// 协程标签,可选
+ /// 协程句柄
+ public CoroutineHandle Run(
+ IEnumerator? coroutine,
+ string? tag = null)
+ {
+ if (coroutine == null)
+ return default;
+
+ if (_nextSlot >= _slots.Length)
+ Expand();
+
+ var handle = new CoroutineHandle(instanceId);
+ var slotIndex = _nextSlot++;
+
+ var slot = new CoroutineSlot
+ {
+ Enumerator = coroutine,
+ State = CoroutineState.Running
+ };
+
+ _slots[slotIndex] = slot;
+ _metadata[handle] = new CoroutineMetadata
+ {
+ SlotIndex = slotIndex,
+ State = CoroutineState.Running,
+ Tag = tag
+ };
+
+ if (!string.IsNullOrEmpty(tag))
+ AddTag(tag, handle);
+
+ Prewarm(slotIndex);
+ _activeCount++;
+
+ return handle;
+ }
+
+ ///
+ /// 更新所有协程状态
+ ///
+ public void Update()
+ {
+ _timeSource.Update();
+ var delta = _timeSource.DeltaTime;
+
+ // 遍历所有槽位并更新协程状态
+ for (var i = 0; i < _nextSlot; i++)
+ {
+ var slot = _slots[i];
+ if (slot == null || slot.State != CoroutineState.Running)
+ continue;
+
+ try
+ {
+ // 1️⃣ 处理等待指令
+ if (slot.Waiting != null)
+ {
+ slot.Waiting.Update(delta);
+ if (!slot.Waiting.IsDone)
+ continue;
+
+ slot.Waiting = null;
+ }
+
+ // 2️⃣ 推进协程
+ if (!slot.Enumerator.MoveNext())
+ {
+ Complete(i);
+ }
+ else
+ {
+ slot.Waiting = slot.Enumerator.Current;
+ }
+ }
+ catch (Exception ex)
+ {
+ OnError(i, ex);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Pause / Resume / Kill
+
+ ///
+ /// 暂停指定协程
+ ///
+ /// 协程句柄
+ /// 是否成功暂停
+ public bool Pause(CoroutineHandle handle)
+ {
+ if (!_metadata.TryGetValue(handle, out var meta))
+ return false;
+
+ var slot = _slots[meta.SlotIndex];
+ if (slot == null || slot.State != CoroutineState.Running)
+ return false;
+
+ slot.State = CoroutineState.Paused;
+ meta.State = CoroutineState.Paused;
+ return true;
+ }
+
+ ///
+ /// 恢复指定协程
+ ///
+ /// 协程句柄
+ /// 是否成功恢复
+ public bool Resume(CoroutineHandle handle)
+ {
+ if (!_metadata.TryGetValue(handle, out var meta))
+ return false;
+
+ var slot = _slots[meta.SlotIndex];
+ if (slot == null || slot.State != CoroutineState.Paused)
+ return false;
+
+ slot.State = CoroutineState.Running;
+ meta.State = CoroutineState.Running;
+ return true;
+ }
+
+ ///
+ /// 终止指定协程
+ ///
+ /// 协程句柄
+ /// 是否成功终止
+ public bool Kill(CoroutineHandle handle)
+ {
+ if (!_metadata.TryGetValue(handle, out var meta))
+ return false;
+
+ Complete(meta.SlotIndex);
+ return true;
+ }
+
+ #endregion
+
+ #region Wait / Tag / Clear
+
+ ///
+ /// 让当前协程等待目标协程完成
+ ///
+ /// 当前协程句柄
+ /// 目标协程句柄
+ public void WaitForCoroutine(
+ CoroutineHandle current,
+ CoroutineHandle target)
+ {
+ if (current == target)
+ throw new InvalidOperationException("Coroutine cannot wait for itself.");
+
+ if (!_metadata.ContainsKey(target))
+ return;
+
+ if (_metadata.TryGetValue(current, out var meta))
+ {
+ var slot = _slots[meta.SlotIndex];
+ if (slot != null)
+ {
+ slot.State = CoroutineState.Held;
+ meta.State = CoroutineState.Held;
+ }
+ }
+
+ if (!_waiting.TryGetValue(target, out var set))
+ {
+ set = new HashSet();
+ _waiting[target] = set;
+ }
+
+ set.Add(current);
+ }
+
+ ///
+ /// 根据标签终止协程
+ ///
+ /// 协程标签
+ /// 被终止的协程数量
+ public int KillByTag(string tag)
+ {
+ if (!_tagged.TryGetValue(tag, out var handles))
+ return 0;
+
+ var copy = handles.ToArray();
+ var count = 0;
+
+ foreach (var h in copy)
+ if (Kill(h))
+ count++;
+
+ return count;
+ }
+
+ ///
+ /// 清空所有协程
+ ///
+ /// 被清除的协程数量
+ public int Clear()
+ {
+ var count = _activeCount;
+
+ Array.Clear(_slots);
+ _metadata.Clear();
+ _tagged.Clear();
+ _waiting.Clear();
+
+ _nextSlot = 0;
+ _activeCount = 0;
+
+ return count;
+ }
+
+ #endregion
+
+ #region Internal
+
+ ///
+ /// 预热协程槽位,执行协程的第一步
+ ///
+ /// 槽位索引
+ private void Prewarm(int slotIndex)
+ {
+ var slot = _slots[slotIndex];
+ if (slot == null)
+ return;
+
+ try
+ {
+ if (!slot.Enumerator.MoveNext())
+ {
+ Complete(slotIndex);
+ }
+ else
+ {
+ slot.Waiting = slot.Enumerator.Current;
+ }
+ }
+ catch (Exception ex)
+ {
+ OnError(slotIndex, ex);
+ }
+ }
+
+ ///
+ /// 完成指定槽位的协程
+ ///
+ /// 槽位索引
+ private void Complete(int slotIndex)
+ {
+ var slot = _slots[slotIndex];
+ if (slot == null)
+ return;
+
+ _slots[slotIndex] = null;
+ _activeCount--;
+
+ CoroutineHandle handle = default;
+ foreach (var kv in _metadata)
+ {
+ if (kv.Value.SlotIndex == slotIndex)
+ {
+ handle = kv.Key;
+ break;
+ }
+ }
+
+ if (!handle.IsValid)
+ return;
+
+ RemoveTag(handle);
+ _metadata.Remove(handle);
+
+ // 唤醒等待者
+ if (_waiting.TryGetValue(handle, out var waiters))
+ {
+ foreach (var waiter in waiters)
+ {
+ if (_metadata.TryGetValue(waiter, out var meta))
+ {
+ var s = _slots[meta.SlotIndex];
+ if (s != null)
+ {
+ s.State = CoroutineState.Running;
+ meta.State = CoroutineState.Running;
+ }
+ }
+ }
+
+ _waiting.Remove(handle);
+ }
+ }
+
+ ///
+ /// 处理协程执行中的错误
+ ///
+ /// 槽位索引
+ /// 异常对象
+ private void OnError(int slotIndex, Exception ex)
+ {
+ Console.Error.WriteLine(ex);
+ Complete(slotIndex);
+ }
+
+ ///
+ /// 扩展协程槽位数组容量
+ ///
+ private void Expand()
+ {
+ Array.Resize(ref _slots, _slots.Length * 2);
+ }
+
+ ///
+ /// 为协程添加标签
+ ///
+ /// 标签名称
+ /// 协程句柄
+ private void AddTag(string tag, CoroutineHandle handle)
+ {
+ if (!_tagged.TryGetValue(tag, out var set))
+ {
+ set = new HashSet();
+ _tagged[tag] = set;
+ }
+
+ set.Add(handle);
+ _metadata[handle].Tag = tag;
+ }
+
+ ///
+ /// 移除协程标签
+ ///
+ /// 协程句柄
+ private void RemoveTag(CoroutineHandle handle)
+ {
+ if (!_metadata.TryGetValue(handle, out var meta) || meta.Tag == null)
+ return;
+
+ if (_tagged.TryGetValue(meta.Tag, out var set))
+ {
+ set.Remove(handle);
+ if (set.Count == 0)
+ _tagged.Remove(meta.Tag);
+ }
+
+ meta.Tag = null;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/CoroutineSlot.cs b/GFramework.Core/coroutine/CoroutineSlot.cs
new file mode 100644
index 0000000..9176161
--- /dev/null
+++ b/GFramework.Core/coroutine/CoroutineSlot.cs
@@ -0,0 +1,24 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 协程槽位类,用于管理单个协程的执行状态和调度信息
+///
+internal sealed class CoroutineSlot
+{
+ ///
+ /// 协程枚举器,包含协程的执行逻辑
+ ///
+ public required IEnumerator Enumerator;
+
+ ///
+ /// 协程当前状态
+ ///
+ public CoroutineState State;
+
+ ///
+ /// 当前等待的指令,用于控制协程的暂停和恢复
+ ///
+ public IYieldInstruction? Waiting;
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/Delay.cs b/GFramework.Core/coroutine/Delay.cs
new file mode 100644
index 0000000..15a3fa0
--- /dev/null
+++ b/GFramework.Core/coroutine/Delay.cs
@@ -0,0 +1,29 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 延迟等待指令,实现IYieldInstruction接口,用于协程中的时间延迟
+///
+/// 需要延迟的秒数
+public sealed class Delay(double seconds) : IYieldInstruction
+{
+ ///
+ /// 剩余等待时间
+ ///
+ private double _remaining = Math.Max(0, seconds);
+
+ ///
+ /// 更新延迟计时器
+ ///
+ /// 时间增量
+ public void Update(double deltaTime)
+ {
+ _remaining -= deltaTime;
+ }
+
+ ///
+ /// 获取延迟是否完成
+ ///
+ public bool IsDone => _remaining <= 0;
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/WaitForCoroutine.cs b/GFramework.Core/coroutine/WaitForCoroutine.cs
new file mode 100644
index 0000000..d2fb8da
--- /dev/null
+++ b/GFramework.Core/coroutine/WaitForCoroutine.cs
@@ -0,0 +1,29 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 等待协程完成的指令类,实现IYieldInstruction接口
+///
+public sealed class WaitForCoroutine : IYieldInstruction
+{
+ private bool _done;
+
+ ///
+ /// 更新方法,用于处理时间更新逻辑
+ ///
+ /// 时间增量
+ public void Update(double delta)
+ {
+ }
+
+ ///
+ /// 获取协程是否已完成的状态
+ ///
+ public bool IsDone => _done;
+
+ ///
+ /// 内部方法,用于标记协程完成状态
+ ///
+ internal void Complete() => _done = true;
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/WaitForFrames.cs b/GFramework.Core/coroutine/WaitForFrames.cs
new file mode 100644
index 0000000..6441463
--- /dev/null
+++ b/GFramework.Core/coroutine/WaitForFrames.cs
@@ -0,0 +1,29 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 等待指定帧数的等待指令类
+///
+/// 需要等待的帧数,最小值为1
+public sealed class WaitForFrames(int frames) : IYieldInstruction
+{
+ ///
+ /// 剩余等待帧数
+ ///
+ private int _remaining = Math.Max(1, frames);
+
+ ///
+ /// 更新方法,在每一帧调用时减少剩余帧数
+ ///
+ /// 时间间隔(秒)
+ public void Update(double deltaTime)
+ {
+ _remaining--;
+ }
+
+ ///
+ /// 获取等待是否完成的状态
+ ///
+ public bool IsDone => _remaining <= 0;
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/WaitOneFrame.cs b/GFramework.Core/coroutine/WaitOneFrame.cs
new file mode 100644
index 0000000..7fdeb19
--- /dev/null
+++ b/GFramework.Core/coroutine/WaitOneFrame.cs
@@ -0,0 +1,26 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 表示等待一帧的等待指令实现
+/// 实现IYieldInstruction接口,用于协程中等待一个游戏帧的执行
+///
+public sealed class WaitOneFrame : IYieldInstruction
+{
+ private bool _done;
+
+ ///
+ /// 更新方法,在每一帧被调用时将完成状态设置为true
+ ///
+ /// 时间间隔,表示当前帧与上一帧的时间差
+ public void Update(double deltaTime)
+ {
+ _done = true;
+ }
+
+ ///
+ /// 获取当前等待指令是否已完成
+ ///
+ public bool IsDone => _done;
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/WaitUntil.cs b/GFramework.Core/coroutine/WaitUntil.cs
new file mode 100644
index 0000000..d0e3f94
--- /dev/null
+++ b/GFramework.Core/coroutine/WaitUntil.cs
@@ -0,0 +1,26 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 表示一个等待直到指定条件满足的协程指令
+///
+/// 用于判断条件是否满足的函数委托
+public sealed class WaitUntil(Func predicate) : IYieldInstruction
+{
+ private readonly Func _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
+
+ ///
+ /// 更新协程状态(此实现中不需要处理时间)
+ ///
+ /// 时间增量
+ public void Update(double deltaTime)
+ {
+ // 不需要时间
+ }
+
+ ///
+ /// 获取协程指令是否已完成
+ ///
+ public bool IsDone => _predicate();
+}
\ No newline at end of file
diff --git a/GFramework.Core/coroutine/WaitWhile.cs b/GFramework.Core/coroutine/WaitWhile.cs
new file mode 100644
index 0000000..430fe45
--- /dev/null
+++ b/GFramework.Core/coroutine/WaitWhile.cs
@@ -0,0 +1,26 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Core.coroutine;
+
+///
+/// 表示一个等待条件为假时才完成的协程指令
+///
+/// 用于判断是否继续等待的条件函数,当返回true时继续等待,返回false时完成
+public sealed class WaitWhile(Func predicate) : IYieldInstruction
+{
+ private readonly Func _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
+
+ ///
+ /// 更新协程状态(此实现中为空方法)
+ ///
+ /// 时间增量
+ public void Update(double deltaTime)
+ {
+ }
+
+ ///
+ /// 获取协程指令是否已完成
+ /// 当谓词函数返回false时,表示条件不再满足,指令完成
+ ///
+ public bool IsDone => !_predicate();
+}
\ No newline at end of file
diff --git a/GFramework.Godot/coroutine/CoroutineExtensions.cs b/GFramework.Godot/coroutine/CoroutineExtensions.cs
new file mode 100644
index 0000000..4728b47
--- /dev/null
+++ b/GFramework.Godot/coroutine/CoroutineExtensions.cs
@@ -0,0 +1,66 @@
+using GFramework.Core.Abstractions.coroutine;
+using GFramework.Core.coroutine;
+using Godot;
+
+namespace GFramework.Godot.coroutine;
+
+public static class CoroutineExtensions
+{
+ ///
+ /// 启动协程的扩展方法
+ ///
+ public static CoroutineHandle RunCoroutine(
+ this IEnumerator coroutine,
+ Segment segment = Segment.Process,
+ string? tag = null)
+ {
+ return Timing.RunCoroutine(coroutine, segment, tag);
+ }
+
+ ///
+ /// 让协程在指定节点被销毁时自动取消
+ ///
+ public static IEnumerator CancelWith(
+ this IEnumerator coroutine,
+ Node node)
+ {
+ while (Timing.IsNodeAlive(node) && coroutine.MoveNext())
+ yield return coroutine.Current;
+ }
+
+ ///
+ /// 让协程在任一节点被销毁时自动取消
+ ///
+ public static IEnumerator CancelWith(
+ this IEnumerator coroutine,
+ Node node1,
+ Node node2)
+ {
+ while (Timing.IsNodeAlive(node1) &&
+ Timing.IsNodeAlive(node2) &&
+ coroutine.MoveNext())
+ yield return coroutine.Current;
+ }
+
+ ///
+ /// 让协程在多个节点都被销毁时自动取消
+ ///
+ public static IEnumerator CancelWith(
+ this IEnumerator coroutine,
+ params Node[] nodes)
+ {
+ while (AllNodesAlive(nodes) && coroutine.MoveNext())
+ yield return coroutine.Current;
+ }
+
+ private static bool AllNodesAlive(Node[] nodes)
+ {
+ foreach (var node in nodes)
+ {
+ if (!Timing.IsNodeAlive(node))
+ return false;
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Godot/coroutine/GodotTimeSource.cs b/GFramework.Godot/coroutine/GodotTimeSource.cs
new file mode 100644
index 0000000..be3ce80
--- /dev/null
+++ b/GFramework.Godot/coroutine/GodotTimeSource.cs
@@ -0,0 +1,44 @@
+using GFramework.Core.Abstractions.coroutine;
+
+namespace GFramework.Godot.coroutine;
+
+///
+/// Godot时间源实现,用于提供基于Godot引擎的时间信息
+///
+/// 获取增量时间的函数委托
+public class GodotTimeSource(Func getDeltaFunc) : ITimeSource
+{
+ private readonly Func _getDeltaFunc = getDeltaFunc ?? throw new ArgumentNullException(nameof(getDeltaFunc));
+ private double _currentTime;
+ private double _deltaTime;
+
+ ///
+ /// 获取当前累计时间
+ ///
+ public double CurrentTime => _currentTime;
+
+ ///
+ /// 获取上一帧的时间增量
+ ///
+ public double DeltaTime => _deltaTime;
+
+ ///
+ /// 更新时间源,计算新的增量时间和累计时间
+ ///
+ public void Update()
+ {
+ // 调用外部提供的函数获取当前帧的时间增量
+ _deltaTime = _getDeltaFunc();
+ // 累加到总时间中
+ _currentTime += _deltaTime;
+ }
+
+ ///
+ /// 重置时间源到初始状态
+ ///
+ public void Reset()
+ {
+ _currentTime = 0;
+ _deltaTime = 0;
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Godot/coroutine/Segment.cs b/GFramework.Godot/coroutine/Segment.cs
new file mode 100644
index 0000000..0c13309
--- /dev/null
+++ b/GFramework.Godot/coroutine/Segment.cs
@@ -0,0 +1,22 @@
+namespace GFramework.Godot.coroutine;
+
+///
+/// 定义协程执行的不同时间段枚举
+///
+public enum Segment
+{
+ ///
+ /// 普通处理阶段,在每一帧的常规处理过程中执行
+ ///
+ Process,
+
+ ///
+ /// 物理处理阶段,在物理更新循环中执行,通常用于需要与物理引擎同步的操作
+ ///
+ PhysicsProcess,
+
+ ///
+ /// 延迟处理阶段,在当前帧结束后延迟执行,通常用于需要等待当前帧完成后再执行的操作
+ ///
+ DeferredProcess
+}
\ No newline at end of file
diff --git a/GFramework.Godot/coroutine/Timing.cs b/GFramework.Godot/coroutine/Timing.cs
new file mode 100644
index 0000000..1951ad1
--- /dev/null
+++ b/GFramework.Godot/coroutine/Timing.cs
@@ -0,0 +1,374 @@
+using System.Reflection;
+using GFramework.Core.Abstractions.coroutine;
+using GFramework.Core.coroutine;
+using Godot;
+
+namespace GFramework.Godot.coroutine;
+
+public partial class Timing : Node
+{
+ private static Timing? _instance;
+ private static readonly Timing?[] ActiveInstances = new Timing?[16];
+ private CoroutineScheduler _deferredScheduler;
+ private GodotTimeSource? _deferredTimeSource;
+ private ushort _frameCounter;
+
+ private byte _instanceId = 1;
+ private CoroutineScheduler _physicsScheduler;
+ private GodotTimeSource? _physicsTimeSource;
+
+ private CoroutineScheduler _processScheduler;
+
+ private GodotTimeSource? _processTimeSource;
+
+ #region 单例
+
+ public static Timing Instance
+ {
+ get
+ {
+ if (_instance != null)
+ return _instance;
+
+ var tree = (SceneTree)Engine.GetMainLoop();
+ _instance = tree.Root.GetNodeOrNull(nameof(Timing));
+
+ if (_instance == null)
+ {
+ _instance = new Timing
+ {
+ Name = nameof(Timing)
+ };
+ tree.Root.AddChild(_instance);
+ }
+
+ return _instance;
+ }
+ }
+
+ #endregion
+
+ #region Debug 信息
+
+ public int ProcessCoroutines => _processScheduler?.ActiveCoroutineCount ?? 0;
+
+ public int PhysicsCoroutines => _physicsScheduler?.ActiveCoroutineCount ?? 0;
+
+ public int DeferredCoroutines => _deferredScheduler?.ActiveCoroutineCount ?? 0;
+
+ #endregion
+
+ #region 生命周期
+
+ public override void _Ready()
+ {
+ ProcessPriority = -1;
+
+ TrySetPhysicsPriority(-1);
+
+ InitializeSchedulers();
+ RegisterInstance();
+ }
+
+ public override void _ExitTree()
+ {
+ if (_instanceId < ActiveInstances.Length)
+ ActiveInstances[_instanceId] = null;
+
+ CleanupInstanceIfNecessary();
+ }
+
+ private static void CleanupInstanceIfNecessary()
+ {
+ _instance = null;
+ }
+
+ public override void _Process(double delta)
+ {
+ _processScheduler?.Update();
+ _frameCounter++;
+
+ CallDeferred(nameof(ProcessDeferred));
+ }
+
+ public override void _PhysicsProcess(double delta)
+ {
+ _physicsScheduler?.Update();
+ }
+
+ private void ProcessDeferred()
+ {
+ _deferredScheduler?.Update();
+ }
+
+ #endregion
+
+ #region 初始化
+
+ private void InitializeSchedulers()
+ {
+ _processTimeSource = new GodotTimeSource(GetProcessDeltaTime);
+ _physicsTimeSource = new GodotTimeSource(GetPhysicsProcessDeltaTime);
+ _deferredTimeSource = new GodotTimeSource(GetProcessDeltaTime);
+
+ _processScheduler = new CoroutineScheduler(
+ _processTimeSource,
+ _instanceId,
+ initialCapacity: 256
+ );
+
+ _physicsScheduler = new CoroutineScheduler(
+ _physicsTimeSource,
+ _instanceId,
+ initialCapacity: 128
+ );
+
+ _deferredScheduler = new CoroutineScheduler(
+ _deferredTimeSource,
+ _instanceId,
+ initialCapacity: 64
+ );
+ }
+
+ private void RegisterInstance()
+ {
+ if (ActiveInstances[_instanceId] == null)
+ {
+ ActiveInstances[_instanceId] = this;
+ return;
+ }
+
+ for (byte i = 1; i < ActiveInstances.Length; i++)
+ {
+ if (ActiveInstances[i] == null)
+ {
+ _instanceId = i;
+ ActiveInstances[i] = this;
+ return;
+ }
+ }
+
+ throw new OverflowException("最多只能存在 15 个 Timing 实例");
+ }
+
+ private static void TrySetPhysicsPriority(int priority)
+ {
+ try
+ {
+ typeof(Node)
+ .GetProperty(
+ "ProcessPhysicsPriority",
+ BindingFlags.Instance |
+ BindingFlags.Public)
+ ?.SetValue(Instance, priority);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ #endregion
+
+ #region 协程启动 API
+
+ public static CoroutineHandle RunCoroutine(
+ IEnumerator coroutine,
+ Segment segment = Segment.Process,
+ string? tag = null)
+ {
+ return Instance.RunCoroutineOnInstance(coroutine, segment, tag);
+ }
+
+ public CoroutineHandle RunCoroutineOnInstance(
+ IEnumerator? coroutine,
+ Segment segment = Segment.Process,
+ string? tag = null)
+ {
+ if (coroutine == null)
+ return default;
+
+ return segment switch
+ {
+ Segment.Process => _processScheduler.Run(coroutine, tag),
+ Segment.PhysicsProcess => _physicsScheduler.Run(coroutine, tag),
+ Segment.DeferredProcess => _deferredScheduler.Run(coroutine, tag),
+ _ => default
+ };
+ }
+
+ #endregion
+
+ #region 协程控制 API
+
+ public static bool PauseCoroutine(CoroutineHandle handle)
+ {
+ return GetInstance(handle.Key)?.PauseOnInstance(handle) ?? false;
+ }
+
+ public static bool ResumeCoroutine(CoroutineHandle handle)
+ {
+ return GetInstance(handle.Key)?.ResumeOnInstance(handle) ?? false;
+ }
+
+ public static bool KillCoroutine(CoroutineHandle handle)
+ {
+ return GetInstance(handle.Key)?.KillOnInstance(handle) ?? false;
+ }
+
+ public static int KillCoroutines(string tag)
+ {
+ return Instance.KillByTagOnInstance(tag);
+ }
+
+ public static int KillAllCoroutines()
+ {
+ return Instance.ClearOnInstance();
+ }
+
+ private bool PauseOnInstance(CoroutineHandle handle)
+ {
+ return _processScheduler.Pause(handle)
+ || _physicsScheduler.Pause(handle)
+ || _deferredScheduler.Pause(handle);
+ }
+
+ private bool ResumeOnInstance(CoroutineHandle handle)
+ {
+ return _processScheduler.Resume(handle)
+ || _physicsScheduler.Resume(handle)
+ || _deferredScheduler.Resume(handle);
+ }
+
+ private bool KillOnInstance(CoroutineHandle handle)
+ {
+ return _processScheduler.Kill(handle)
+ || _physicsScheduler.Kill(handle)
+ || _deferredScheduler.Kill(handle);
+ }
+
+ private int KillByTagOnInstance(string tag)
+ {
+ int count = 0;
+ count += _processScheduler.KillByTag(tag);
+ count += _physicsScheduler.KillByTag(tag);
+ count += _deferredScheduler.KillByTag(tag);
+ return count;
+ }
+
+ private int ClearOnInstance()
+ {
+ int count = 0;
+ count += _processScheduler.Clear();
+ count += _physicsScheduler.Clear();
+ count += _deferredScheduler.Clear();
+ return count;
+ }
+
+ #endregion
+
+ #region 工具方法
+
+ public static Timing? GetInstance(byte id)
+ {
+ return id < ActiveInstances.Length ? ActiveInstances[id] : null;
+ }
+
+ ///
+ /// 创建等待指定秒数的指令
+ ///
+ public static Delay WaitForSeconds(double seconds)
+ {
+ return new Delay(seconds);
+ }
+
+ ///
+ /// 创建等待一帧的指令
+ ///
+ public static WaitOneFrame WaitForOneFrame()
+ {
+ return new WaitOneFrame();
+ }
+
+ ///
+ /// 创建等待指定帧数的指令
+ ///
+ public static WaitForFrames WaitForFrames(int frames)
+ {
+ return new WaitForFrames(frames);
+ }
+
+ ///
+ /// 创建等待直到条件满足的指令
+ ///
+ public static WaitUntil WaitUntil(Func predicate)
+ {
+ return new WaitUntil(predicate);
+ }
+
+ ///
+ /// 创建等待当条件为真时持续等待的指令
+ ///
+ public static WaitWhile WaitWhile(Func predicate)
+ {
+ return new WaitWhile(predicate);
+ }
+
+ public static bool IsNodeAlive(Node? node)
+ {
+ return node != null
+ && IsInstanceValid(node)
+ && !node.IsQueuedForDeletion()
+ && node.IsInsideTree();
+ }
+
+ #endregion
+
+ #region 延迟调用
+
+ public static CoroutineHandle CallDelayed(
+ double delay,
+ Action? action,
+ Segment segment = Segment.Process)
+ {
+ if (action == null)
+ return default;
+
+ return RunCoroutine(DelayedCallCoroutine(delay, action), segment);
+ }
+
+ public static CoroutineHandle CallDelayed(
+ double delay,
+ Action? action,
+ Node cancelWith,
+ Segment segment = Segment.Process)
+ {
+ if (action == null)
+ return default;
+
+ return RunCoroutine(
+ DelayedCallWithCancelCoroutine(delay, action, cancelWith),
+ segment);
+ }
+
+ private static IEnumerator DelayedCallCoroutine(
+ double delay,
+ Action action)
+ {
+ yield return new Delay(delay);
+ action();
+ }
+
+ private static IEnumerator DelayedCallWithCancelCoroutine(
+ double delay,
+ Action action,
+ Node cancelWith)
+ {
+ yield return new Delay(delay);
+
+ if (IsNodeAlive(cancelWith))
+ action();
+ }
+
+ #endregion
+}
\ No newline at end of file