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