feat(coroutine): 添加完整的协程系统实现

- 实现了协程调度器和句柄管理机制
- 添加了多种等待指令包括延时、帧数、条件等待等
- 创建了协程辅助方法和扩展功能
- 集成了Godot引擎的时间源和生命周期管理
- 实现了协程的暂停、恢复、终止等控制功能
- 添加了协程标签管理和按标签批量操作功能
- 提供了与Godot节点生命周期绑定的取消机制
This commit is contained in:
GeWuYou 2026-01-21 20:34:10 +08:00
parent 16a72e85af
commit ddbf7af572
18 changed files with 1386 additions and 0 deletions

View File

@ -0,0 +1,32 @@
namespace GFramework.Core.Abstractions.coroutine;
/// <summary>
/// 表示协程的执行状态枚举
/// </summary>
public enum CoroutineState
{
/// <summary>
/// 协程正在运行中
/// </summary>
Running,
/// <summary>
/// 协程已暂停
/// </summary>
Paused,
/// <summary>
/// 协程被锁定或等待其他协程完成
/// </summary>
Held,
/// <summary>
/// 协程已完成执行
/// </summary>
Completed,
/// <summary>
/// 协程已被取消
/// </summary>
Cancelled,
}

View File

@ -0,0 +1,22 @@
namespace GFramework.Core.Abstractions.coroutine;
/// <summary>
/// 时间源接口,提供当前时间、时间增量以及更新功能
/// </summary>
public interface ITimeSource
{
/// <summary>
/// 获取当前时间
/// </summary>
double CurrentTime { get; }
/// <summary>
/// 获取时间增量(上一帧到当前帧的时间差)
/// </summary>
double DeltaTime { get; }
/// <summary>
/// 更新时间源的状态
/// </summary>
void Update();
}

View File

@ -0,0 +1,18 @@
namespace GFramework.Core.Abstractions.coroutine;
/// <summary>
/// 定义一个可等待指令的接口,用于协程系统中的异步操作控制
/// </summary>
public interface IYieldInstruction
{
/// <summary>
/// 获取当前等待指令是否已完成执行
/// </summary>
bool IsDone { get; }
/// <summary>
/// 每帧由调度器调用,用于更新当前等待指令的状态
/// </summary>
/// <param name="deltaTime">自上一帧以来的时间间隔(以秒为单位)</param>
void Update(double deltaTime);
}

View File

@ -0,0 +1,94 @@
namespace GFramework.Core.coroutine;
/// <summary>
/// 协程句柄
/// 用于唯一标识和管理协程实例的结构体通过ID系统实现协程的跟踪和比较功能
/// </summary>
public readonly struct CoroutineHandle : IEquatable<CoroutineHandle>
{
/// <summary>
/// 预留空间常量用于ID分配的基数
/// </summary>
private const byte ReservedSpace = 0x0F;
/// <summary>
/// 下一个索引数组用于跟踪每个实例ID的下一个可用索引位置
/// 索引范围0-15对应16个不同的实例槽位
/// </summary>
private static readonly int[] NextIndex = new int[16];
/// <summary>
/// 协程句柄的内部ID用于唯一标识协程实例
/// </summary>
private readonly int _id;
/// <summary>
/// 静态构造函数初始化NextIndex数组的默认值
/// 将索引0的下一个可用位置设置为ReservedSpace + 1
/// </summary>
static CoroutineHandle()
{
NextIndex[0] = ReservedSpace + 1;
}
/// <summary>
/// 获取当前协程句柄的键值低4位
/// </summary>
public byte Key => (byte)(_id & ReservedSpace);
/// <summary>
/// 判断当前协程句柄是否有效
/// 有效性通过Key是否为0来判断
/// </summary>
public bool IsValid => Key != 0;
/// <summary>
/// 构造函数,创建一个新的协程句柄
/// </summary>
/// <param name="instanceId">实例ID用于区分不同的协程实例槽位</param>
public CoroutineHandle(byte instanceId)
{
if (instanceId > ReservedSpace)
instanceId -= ReservedSpace;
_id = NextIndex[instanceId] + instanceId;
NextIndex[instanceId] += ReservedSpace + 1;
}
/// <summary>
/// 比较当前协程句柄与另一个协程句柄是否相等
/// </summary>
/// <param name="other">要比较的协程句柄</param>
/// <returns>如果两个句柄的ID相同则返回true否则返回false</returns>
public bool Equals(CoroutineHandle other) => _id == other._id;
/// <summary>
/// 比较当前对象与指定对象是否相等
/// </summary>
/// <param name="obj">要比较的对象</param>
/// <returns>如果对象是协程句柄且ID相同则返回true否则返回false</returns>
public override bool Equals(object? obj) => obj is CoroutineHandle handle && Equals(handle);
/// <summary>
/// 获取当前协程句柄的哈希码
/// </summary>
/// <returns>基于内部ID计算的哈希码</returns>
public override int GetHashCode() => _id;
/// <summary>
/// 比较两个协程句柄是否相等
/// </summary>
/// <param name="a">第一个协程句柄</param>
/// <param name="b">第二个协程句柄</param>
/// <returns>如果两个句柄的ID相同则返回true否则返回false</returns>
public static bool operator ==(CoroutineHandle a, CoroutineHandle b) => a._id == b._id;
/// <summary>
/// 比较两个协程句柄是否不相等
/// </summary>
/// <param name="a">第一个协程句柄</param>
/// <param name="b">第二个协程句柄</param>
/// <returns>如果两个句柄的ID不同则返回true否则返回false</returns>
public static bool operator !=(CoroutineHandle a, CoroutineHandle b) => a._id != b._id;
}

View File

@ -0,0 +1,101 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 协程辅助方法
/// </summary>
public static class CoroutineHelper
{
/// <summary>
/// 等待指定秒数
/// </summary>
/// <param name="seconds">要等待的秒数</param>
/// <returns>延迟等待指令</returns>
public static Delay WaitForSeconds(double seconds)
{
return new Delay(seconds);
}
/// <summary>
/// 等待一帧
/// </summary>
/// <returns>等待一帧的指令</returns>
public static WaitOneFrame WaitForOneFrame()
{
return new WaitOneFrame();
}
/// <summary>
/// 等待指定帧数
/// </summary>
/// <param name="frames">要等待的帧数</param>
/// <returns>等待帧数指令</returns>
public static WaitForFrames WaitForFrames(int frames)
{
return new WaitForFrames(frames);
}
/// <summary>
/// 等待直到条件满足
/// </summary>
/// <param name="predicate">条件判断函数</param>
/// <returns>等待条件指令</returns>
public static WaitUntil WaitUntil(Func<bool> predicate)
{
return new WaitUntil(predicate);
}
/// <summary>
/// 等待当条件为真时持续等待
/// </summary>
/// <param name="predicate">条件判断函数</param>
/// <returns>等待条件指令</returns>
public static WaitWhile WaitWhile(Func<bool> predicate)
{
return new WaitWhile(predicate);
}
/// <summary>
/// 延迟调用指定的委托
/// </summary>
/// <param name="delay">延迟时间(秒)</param>
/// <param name="action">要执行的动作委托</param>
/// <returns>返回一个枚举器,用于协程执行</returns>
public static IEnumerator<IYieldInstruction> DelayedCall(double delay, Action? action)
{
yield return new Delay(delay);
action?.Invoke();
}
/// <summary>
/// 重复调用指定的委托指定次数
/// </summary>
/// <param name="interval">每次调用之间的间隔时间(秒)</param>
/// <param name="count">调用次数</param>
/// <param name="action">要执行的动作委托</param>
/// <returns>返回一个枚举器,用于协程执行</returns>
public static IEnumerator<IYieldInstruction> RepeatCall(double interval, int count, Action? action)
{
for (var i = 0; i < count; i++)
{
action?.Invoke();
yield return new Delay(interval);
}
}
/// <summary>
/// 无限重复调用指定的委托
/// </summary>
/// <param name="interval">每次调用之间的间隔时间(秒)</param>
/// <param name="action">要执行的动作委托</param>
/// <returns>返回一个枚举器,用于协程执行</returns>
public static IEnumerator<IYieldInstruction> RepeatCallForever(double interval, Action? action)
{
while (true)
{
action?.Invoke();
yield return new Delay(interval);
}
}
}

View File

@ -0,0 +1,32 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 存储协程元数据信息的内部类,包含协程的状态、枚举器、标签等信息
/// </summary>
internal class CoroutineMetadata
{
/// <summary>
/// 协程在调度器中的槽位索引
/// </summary>
public int SlotIndex;
/// <summary>
/// 协程当前的执行状态
/// </summary>
public CoroutineState State;
/// <summary>
/// 协程的标签标识符,用于协程的分类和查找
/// </summary>
public string? Tag;
/// <summary>
/// 判断协程是否处于活跃状态(运行中、暂停或挂起)
/// </summary>
public bool IsActive =>
State is CoroutineState.Running
or CoroutineState.Paused
or CoroutineState.Held;
}

View File

@ -0,0 +1,392 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 协程调度器,用于管理和执行协程
/// </summary>
/// <param name="timeSource">时间源接口,提供时间相关数据</param>
/// <param name="instanceId">实例ID默认为1</param>
/// <param name="initialCapacity">初始容量默认为256</param>
public sealed class CoroutineScheduler(
ITimeSource timeSource,
byte instanceId = 1,
int initialCapacity = 256)
{
private readonly Dictionary<CoroutineHandle, CoroutineMetadata> _metadata = new();
private readonly Dictionary<string, HashSet<CoroutineHandle>> _tagged = new();
private readonly ITimeSource _timeSource = timeSource ?? throw new ArgumentNullException(nameof(timeSource));
private readonly Dictionary<CoroutineHandle, HashSet<CoroutineHandle>> _waiting = new();
private int _activeCount;
private int _nextSlot;
private CoroutineSlot?[] _slots = new CoroutineSlot?[initialCapacity];
/// <summary>
/// 获取时间差值
/// </summary>
public double DeltaTime => _timeSource.DeltaTime;
/// <summary>
/// 获取活跃协程数量
/// </summary>
public int ActiveCoroutineCount => _activeCount;
#region Run / Update
/// <summary>
/// 运行协程
/// </summary>
/// <param name="coroutine">要运行的协程枚举器</param>
/// <param name="tag">协程标签,可选</param>
/// <returns>协程句柄</returns>
public CoroutineHandle Run(
IEnumerator<IYieldInstruction>? 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;
}
/// <summary>
/// 更新所有协程状态
/// </summary>
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
/// <summary>
/// 暂停指定协程
/// </summary>
/// <param name="handle">协程句柄</param>
/// <returns>是否成功暂停</returns>
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;
}
/// <summary>
/// 恢复指定协程
/// </summary>
/// <param name="handle">协程句柄</param>
/// <returns>是否成功恢复</returns>
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;
}
/// <summary>
/// 终止指定协程
/// </summary>
/// <param name="handle">协程句柄</param>
/// <returns>是否成功终止</returns>
public bool Kill(CoroutineHandle handle)
{
if (!_metadata.TryGetValue(handle, out var meta))
return false;
Complete(meta.SlotIndex);
return true;
}
#endregion
#region Wait / Tag / Clear
/// <summary>
/// 让当前协程等待目标协程完成
/// </summary>
/// <param name="current">当前协程句柄</param>
/// <param name="target">目标协程句柄</param>
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<CoroutineHandle>();
_waiting[target] = set;
}
set.Add(current);
}
/// <summary>
/// 根据标签终止协程
/// </summary>
/// <param name="tag">协程标签</param>
/// <returns>被终止的协程数量</returns>
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;
}
/// <summary>
/// 清空所有协程
/// </summary>
/// <returns>被清除的协程数量</returns>
public int Clear()
{
var count = _activeCount;
Array.Clear(_slots);
_metadata.Clear();
_tagged.Clear();
_waiting.Clear();
_nextSlot = 0;
_activeCount = 0;
return count;
}
#endregion
#region Internal
/// <summary>
/// 预热协程槽位,执行协程的第一步
/// </summary>
/// <param name="slotIndex">槽位索引</param>
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);
}
}
/// <summary>
/// 完成指定槽位的协程
/// </summary>
/// <param name="slotIndex">槽位索引</param>
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);
}
}
/// <summary>
/// 处理协程执行中的错误
/// </summary>
/// <param name="slotIndex">槽位索引</param>
/// <param name="ex">异常对象</param>
private void OnError(int slotIndex, Exception ex)
{
Console.Error.WriteLine(ex);
Complete(slotIndex);
}
/// <summary>
/// 扩展协程槽位数组容量
/// </summary>
private void Expand()
{
Array.Resize(ref _slots, _slots.Length * 2);
}
/// <summary>
/// 为协程添加标签
/// </summary>
/// <param name="tag">标签名称</param>
/// <param name="handle">协程句柄</param>
private void AddTag(string tag, CoroutineHandle handle)
{
if (!_tagged.TryGetValue(tag, out var set))
{
set = new HashSet<CoroutineHandle>();
_tagged[tag] = set;
}
set.Add(handle);
_metadata[handle].Tag = tag;
}
/// <summary>
/// 移除协程标签
/// </summary>
/// <param name="handle">协程句柄</param>
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
}

View File

@ -0,0 +1,24 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 协程槽位类,用于管理单个协程的执行状态和调度信息
/// </summary>
internal sealed class CoroutineSlot
{
/// <summary>
/// 协程枚举器,包含协程的执行逻辑
/// </summary>
public required IEnumerator<IYieldInstruction> Enumerator;
/// <summary>
/// 协程当前状态
/// </summary>
public CoroutineState State;
/// <summary>
/// 当前等待的指令,用于控制协程的暂停和恢复
/// </summary>
public IYieldInstruction? Waiting;
}

View File

@ -0,0 +1,29 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 延迟等待指令实现IYieldInstruction接口用于协程中的时间延迟
/// </summary>
/// <param name="seconds">需要延迟的秒数</param>
public sealed class Delay(double seconds) : IYieldInstruction
{
/// <summary>
/// 剩余等待时间
/// </summary>
private double _remaining = Math.Max(0, seconds);
/// <summary>
/// 更新延迟计时器
/// </summary>
/// <param name="deltaTime">时间增量</param>
public void Update(double deltaTime)
{
_remaining -= deltaTime;
}
/// <summary>
/// 获取延迟是否完成
/// </summary>
public bool IsDone => _remaining <= 0;
}

View File

@ -0,0 +1,29 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 等待协程完成的指令类实现IYieldInstruction接口
/// </summary>
public sealed class WaitForCoroutine : IYieldInstruction
{
private bool _done;
/// <summary>
/// 更新方法,用于处理时间更新逻辑
/// </summary>
/// <param name="delta">时间增量</param>
public void Update(double delta)
{
}
/// <summary>
/// 获取协程是否已完成的状态
/// </summary>
public bool IsDone => _done;
/// <summary>
/// 内部方法,用于标记协程完成状态
/// </summary>
internal void Complete() => _done = true;
}

View File

@ -0,0 +1,29 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 等待指定帧数的等待指令类
/// </summary>
/// <param name="frames">需要等待的帧数最小值为1</param>
public sealed class WaitForFrames(int frames) : IYieldInstruction
{
/// <summary>
/// 剩余等待帧数
/// </summary>
private int _remaining = Math.Max(1, frames);
/// <summary>
/// 更新方法,在每一帧调用时减少剩余帧数
/// </summary>
/// <param name="deltaTime">时间间隔(秒)</param>
public void Update(double deltaTime)
{
_remaining--;
}
/// <summary>
/// 获取等待是否完成的状态
/// </summary>
public bool IsDone => _remaining <= 0;
}

View File

@ -0,0 +1,26 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 表示等待一帧的等待指令实现
/// 实现IYieldInstruction接口用于协程中等待一个游戏帧的执行
/// </summary>
public sealed class WaitOneFrame : IYieldInstruction
{
private bool _done;
/// <summary>
/// 更新方法在每一帧被调用时将完成状态设置为true
/// </summary>
/// <param name="deltaTime">时间间隔,表示当前帧与上一帧的时间差</param>
public void Update(double deltaTime)
{
_done = true;
}
/// <summary>
/// 获取当前等待指令是否已完成
/// </summary>
public bool IsDone => _done;
}

View File

@ -0,0 +1,26 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 表示一个等待直到指定条件满足的协程指令
/// </summary>
/// <param name="predicate">用于判断条件是否满足的函数委托</param>
public sealed class WaitUntil(Func<bool> predicate) : IYieldInstruction
{
private readonly Func<bool> _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
/// <summary>
/// 更新协程状态(此实现中不需要处理时间)
/// </summary>
/// <param name="deltaTime">时间增量</param>
public void Update(double deltaTime)
{
// 不需要时间
}
/// <summary>
/// 获取协程指令是否已完成
/// </summary>
public bool IsDone => _predicate();
}

View File

@ -0,0 +1,26 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Core.coroutine;
/// <summary>
/// 表示一个等待条件为假时才完成的协程指令
/// </summary>
/// <param name="predicate">用于判断是否继续等待的条件函数当返回true时继续等待返回false时完成</param>
public sealed class WaitWhile(Func<bool> predicate) : IYieldInstruction
{
private readonly Func<bool> _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
/// <summary>
/// 更新协程状态(此实现中为空方法)
/// </summary>
/// <param name="deltaTime">时间增量</param>
public void Update(double deltaTime)
{
}
/// <summary>
/// 获取协程指令是否已完成
/// 当谓词函数返回false时表示条件不再满足指令完成
/// </summary>
public bool IsDone => !_predicate();
}

View File

@ -0,0 +1,66 @@
using GFramework.Core.Abstractions.coroutine;
using GFramework.Core.coroutine;
using Godot;
namespace GFramework.Godot.coroutine;
public static class CoroutineExtensions
{
/// <summary>
/// 启动协程的扩展方法
/// </summary>
public static CoroutineHandle RunCoroutine(
this IEnumerator<IYieldInstruction> coroutine,
Segment segment = Segment.Process,
string? tag = null)
{
return Timing.RunCoroutine(coroutine, segment, tag);
}
/// <summary>
/// 让协程在指定节点被销毁时自动取消
/// </summary>
public static IEnumerator<IYieldInstruction> CancelWith(
this IEnumerator<IYieldInstruction> coroutine,
Node node)
{
while (Timing.IsNodeAlive(node) && coroutine.MoveNext())
yield return coroutine.Current;
}
/// <summary>
/// 让协程在任一节点被销毁时自动取消
/// </summary>
public static IEnumerator<IYieldInstruction> CancelWith(
this IEnumerator<IYieldInstruction> coroutine,
Node node1,
Node node2)
{
while (Timing.IsNodeAlive(node1) &&
Timing.IsNodeAlive(node2) &&
coroutine.MoveNext())
yield return coroutine.Current;
}
/// <summary>
/// 让协程在多个节点都被销毁时自动取消
/// </summary>
public static IEnumerator<IYieldInstruction> CancelWith(
this IEnumerator<IYieldInstruction> 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;
}
}

View File

@ -0,0 +1,44 @@
using GFramework.Core.Abstractions.coroutine;
namespace GFramework.Godot.coroutine;
/// <summary>
/// Godot时间源实现用于提供基于Godot引擎的时间信息
/// </summary>
/// <param name="getDeltaFunc">获取增量时间的函数委托</param>
public class GodotTimeSource(Func<double> getDeltaFunc) : ITimeSource
{
private readonly Func<double> _getDeltaFunc = getDeltaFunc ?? throw new ArgumentNullException(nameof(getDeltaFunc));
private double _currentTime;
private double _deltaTime;
/// <summary>
/// 获取当前累计时间
/// </summary>
public double CurrentTime => _currentTime;
/// <summary>
/// 获取上一帧的时间增量
/// </summary>
public double DeltaTime => _deltaTime;
/// <summary>
/// 更新时间源,计算新的增量时间和累计时间
/// </summary>
public void Update()
{
// 调用外部提供的函数获取当前帧的时间增量
_deltaTime = _getDeltaFunc();
// 累加到总时间中
_currentTime += _deltaTime;
}
/// <summary>
/// 重置时间源到初始状态
/// </summary>
public void Reset()
{
_currentTime = 0;
_deltaTime = 0;
}
}

View File

@ -0,0 +1,22 @@
namespace GFramework.Godot.coroutine;
/// <summary>
/// 定义协程执行的不同时间段枚举
/// </summary>
public enum Segment
{
/// <summary>
/// 普通处理阶段,在每一帧的常规处理过程中执行
/// </summary>
Process,
/// <summary>
/// 物理处理阶段,在物理更新循环中执行,通常用于需要与物理引擎同步的操作
/// </summary>
PhysicsProcess,
/// <summary>
/// 延迟处理阶段,在当前帧结束后延迟执行,通常用于需要等待当前帧完成后再执行的操作
/// </summary>
DeferredProcess
}

View File

@ -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<Timing>(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<IYieldInstruction> coroutine,
Segment segment = Segment.Process,
string? tag = null)
{
return Instance.RunCoroutineOnInstance(coroutine, segment, tag);
}
public CoroutineHandle RunCoroutineOnInstance(
IEnumerator<IYieldInstruction>? 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;
}
/// <summary>
/// 创建等待指定秒数的指令
/// </summary>
public static Delay WaitForSeconds(double seconds)
{
return new Delay(seconds);
}
/// <summary>
/// 创建等待一帧的指令
/// </summary>
public static WaitOneFrame WaitForOneFrame()
{
return new WaitOneFrame();
}
/// <summary>
/// 创建等待指定帧数的指令
/// </summary>
public static WaitForFrames WaitForFrames(int frames)
{
return new WaitForFrames(frames);
}
/// <summary>
/// 创建等待直到条件满足的指令
/// </summary>
public static WaitUntil WaitUntil(Func<bool> predicate)
{
return new WaitUntil(predicate);
}
/// <summary>
/// 创建等待当条件为真时持续等待的指令
/// </summary>
public static WaitWhile WaitWhile(Func<bool> 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<IYieldInstruction> DelayedCallCoroutine(
double delay,
Action action)
{
yield return new Delay(delay);
action();
}
private static IEnumerator<IYieldInstruction> DelayedCallWithCancelCoroutine(
double delay,
Action action,
Node cancelWith)
{
yield return new Delay(delay);
if (IsNodeAlive(cancelWith))
action();
}
#endregion
}