diff --git a/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj b/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj index 88924f4..3921c34 100644 --- a/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj +++ b/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj @@ -16,11 +16,11 @@ - + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers diff --git a/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj b/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj index b383b5d..8d399cc 100644 --- a/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj +++ b/GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj @@ -18,11 +18,11 @@ - + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers diff --git a/GFramework.Game.Abstractions/coroutine/ICoroutineScheduler.cs b/GFramework.Game.Abstractions/coroutine/ICoroutineScheduler.cs new file mode 100644 index 0000000..3e00dd1 --- /dev/null +++ b/GFramework.Game.Abstractions/coroutine/ICoroutineScheduler.cs @@ -0,0 +1,13 @@ +namespace GFramework.Game.Abstractions.coroutine; + +/// +/// 协程调度器接口,定义了协程系统的基本调度方法 +/// +public interface ICoroutineScheduler +{ + /// + /// 更新协程调度器,处理等待中的协程 + /// + /// 自上一帧以来的时间间隔(以秒为单位) + void Update(float deltaTime); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/coroutine/ICoroutineScope.cs b/GFramework.Game.Abstractions/coroutine/ICoroutineScope.cs new file mode 100644 index 0000000..9896a14 --- /dev/null +++ b/GFramework.Game.Abstractions/coroutine/ICoroutineScope.cs @@ -0,0 +1,17 @@ +namespace GFramework.Game.Abstractions.coroutine; + +/// +/// 协程作用域接口,用于管理协程的生命周期 +/// +public interface ICoroutineScope +{ + /// + /// 获取协程是否处于活动状态 + /// + bool IsActive { get; } + + /// + /// 取消协程执行 + /// + void Cancel(); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/coroutine/ICoroutineSystem.cs b/GFramework.Game.Abstractions/coroutine/ICoroutineSystem.cs new file mode 100644 index 0000000..f3a3c61 --- /dev/null +++ b/GFramework.Game.Abstractions/coroutine/ICoroutineSystem.cs @@ -0,0 +1,15 @@ +using GFramework.Core.Abstractions.system; + +namespace GFramework.Game.Abstractions.coroutine; + +/// +/// 协程系统接口,继承自ISystem,用于管理游戏中的协程执行 +/// +public interface ICoroutineSystem : ISystem +{ + /// + /// 更新协程系统,在每一帧调用以处理协程逻辑 + /// + /// 距离上一帧的时间间隔(秒) + void OnUpdate(float deltaTime); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/coroutine/IYieldInstruction.cs b/GFramework.Game.Abstractions/coroutine/IYieldInstruction.cs new file mode 100644 index 0000000..46bbb17 --- /dev/null +++ b/GFramework.Game.Abstractions/coroutine/IYieldInstruction.cs @@ -0,0 +1,18 @@ +namespace GFramework.Game.Abstractions.coroutine; + +/// +/// 表示一个可等待的指令接口,用于协程中的等待操作 +/// +public interface IYieldInstruction +{ + /// + /// 获取当前等待指令是否已完成 + /// + bool IsDone { get; } + + /// + /// 更新等待指令的状态 + /// + /// 自上次更新以来的时间间隔(以秒为单位) + void Update(float deltaTime); +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/CoroutineContext.cs b/GFramework.Game/coroutine/CoroutineContext.cs new file mode 100644 index 0000000..c5b8700 --- /dev/null +++ b/GFramework.Game/coroutine/CoroutineContext.cs @@ -0,0 +1,27 @@ +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +/// +/// 协程上下文类,用于封装协程执行所需的环境信息 +/// +/// 协程作用域接口实例 +/// 协程调度器实例 +/// 协程的所有者对象,默认为null +public class CoroutineContext(ICoroutineScope scope, CoroutineScheduler scheduler, object? owner = null) +{ + /// + /// 获取协程作用域 + /// + public ICoroutineScope Scope { get; } = scope; + + /// + /// 获取协程调度器 + /// + public CoroutineScheduler Scheduler { get; } = scheduler; + + /// + /// 获取协程所有者对象 + /// + public object? Owner { get; } = owner; +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/CoroutineHandle.cs b/GFramework.Game/coroutine/CoroutineHandle.cs new file mode 100644 index 0000000..0fce2b9 --- /dev/null +++ b/GFramework.Game/coroutine/CoroutineHandle.cs @@ -0,0 +1,118 @@ +using System.Collections; +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +public class CoroutineHandle : IYieldInstruction +{ + private readonly Stack _stack = new(); + private IYieldInstruction? _waitingInstruction; + + internal CoroutineHandle(IEnumerator routine, CoroutineContext context, IYieldInstruction? waitingInstruction) + { + _stack.Push(routine); + Context = context; + _waitingInstruction = waitingInstruction; + } + + public CoroutineContext Context { get; } + public bool IsCancelled { get; private set; } + public bool IsDone { get; private set; } + + void IYieldInstruction.Update(float deltaTime) + { + InternalUpdate(deltaTime); + } + + public event Action? OnComplete; + public event Action? OnError; + + private bool InternalUpdate(float deltaTime) + { + if (IsDone) return false; + + if (_waitingInstruction != null) + { + _waitingInstruction.Update(deltaTime); + if (!_waitingInstruction.IsDone) return true; + _waitingInstruction = null; + } + + if (_stack.Count == 0) + { + Complete(); + return false; + } + + try + { + var current = _stack.Peek(); + if (current.MoveNext()) + { + ProcessYieldValue(current.Current); + return true; + } + + _stack.Pop(); + return _stack.Count > 0 || !CompleteCheck(); + } + catch (Exception ex) + { + HandleError(ex); + return false; + } + } + + private void ProcessYieldValue(object yielded) + { + switch (yielded) + { + case null: + break; + case IEnumerator nested: + _stack.Push(nested); + break; + // ✅ 将更具体的类型放在前面 + case CoroutineHandle otherHandle: + _waitingInstruction = otherHandle; + break; + case IYieldInstruction instruction: + _waitingInstruction = instruction; + break; + default: + throw new InvalidOperationException($"Unsupported yield type: {yielded.GetType()}"); + } + } + + private bool CompleteCheck() + { + if (_stack.Count == 0) Complete(); + return IsDone; + } + + private void Complete() + { + if (IsDone) return; + IsDone = true; + _stack.Clear(); + _waitingInstruction = null; + OnComplete?.Invoke(); + } + + private void HandleError(Exception ex) + { + IsDone = true; + _stack.Clear(); + _waitingInstruction = null; + OnError?.Invoke(ex); + } + + public void Cancel() + { + if (IsDone) return; + IsDone = true; + IsCancelled = true; + _stack.Clear(); + _waitingInstruction = null; + } +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/CoroutineScheduler.cs b/GFramework.Game/coroutine/CoroutineScheduler.cs new file mode 100644 index 0000000..6b4fdef --- /dev/null +++ b/GFramework.Game/coroutine/CoroutineScheduler.cs @@ -0,0 +1,52 @@ +using System.Collections; +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +public class CoroutineScheduler : ICoroutineScheduler +{ + private readonly List _active = new(); + private readonly List _toAdd = new(); + private readonly HashSet _toRemove = new(); + + public int ActiveCount => _active.Count; + + public void Update(float deltaTime) + { + if (_toAdd.Count > 0) + { + _active.AddRange(_toAdd); + _toAdd.Clear(); + } + + for (var i = _active.Count - 1; i >= 0; i--) + { + var c = _active[i]; + + if (!c.Context.Scope.IsActive) + { + c.Cancel(); + _toRemove.Add(c); + continue; + } + + if (!c.Update(deltaTime)) + _toRemove.Add(c); + } + + if (_toRemove.Count <= 0) return; + { + _active.RemoveAll(c => _toRemove.Contains(c)); + _toRemove.Clear(); + } + } + + internal CoroutineHandle StartCoroutine(IEnumerator routine, CoroutineContext context) + { + var handle = new CoroutineHandle(routine, context); + _toAdd.Add(handle); + return handle; + } + + internal void RemoveCoroutine(CoroutineHandle handle) => _toRemove.Add(handle); +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/CoroutineScope.cs b/GFramework.Game/coroutine/CoroutineScope.cs new file mode 100644 index 0000000..1c7f16e --- /dev/null +++ b/GFramework.Game/coroutine/CoroutineScope.cs @@ -0,0 +1,57 @@ +using System.Collections; +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +public class CoroutineScope : ICoroutineScope, IDisposable +{ + private readonly List _children = new(); + private readonly CoroutineScope _parent; + private readonly HashSet _runningCoroutines = new(); + private readonly CoroutineScheduler _scheduler; + + private bool _isActive = true; + + public CoroutineScope(CoroutineScheduler scheduler, string name = null, CoroutineScope parent = null) + { + _scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler)); + _parent = parent; + _parent?._children.Add(this); + Name = name ?? $"Scope_{GetHashCode()}"; + } + + public string Name { get; } + public bool IsActive => _isActive; + + public void Cancel() + { + if (!_isActive) return; + + _isActive = false; + + foreach (var child in _children.ToList()) + child.Cancel(); + + foreach (var handle in _runningCoroutines.ToList()) + handle.Cancel(); + + _runningCoroutines.Clear(); + } + + public void Dispose() => Cancel(); + + public CoroutineHandle Launch(IEnumerator routine) + { + if (!_isActive) + throw new InvalidOperationException($"Scope '{Name}' is not active"); + + var context = new CoroutineContext(this, _scheduler, this); + var handle = _scheduler.StartCoroutine(routine, context); + + _runningCoroutines.Add(handle); + handle.OnComplete += () => _runningCoroutines.Remove(handle); + handle.OnError += (ex) => _runningCoroutines.Remove(handle); + + return handle; + } +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/CoroutineScopeExtensions.cs b/GFramework.Game/coroutine/CoroutineScopeExtensions.cs new file mode 100644 index 0000000..c610ef5 --- /dev/null +++ b/GFramework.Game/coroutine/CoroutineScopeExtensions.cs @@ -0,0 +1,57 @@ +using System.Collections; +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +/// +/// 为协程作用域提供扩展方法,支持延迟执行和重复执行功能 +/// +public static class CoroutineScopeExtensions +{ + /// + /// 启动一个延迟执行的协程 + /// + /// 协程作用域 + /// 延迟时间(秒) + /// 延迟后要执行的动作 + /// 协程句柄,可用于控制协程的生命周期 + public static CoroutineHandle LaunchDelayed(this ICoroutineScope scope, float delay, Action action) + => ((CoroutineScope)scope).Launch(DelayedRoutine(delay, action)); + + /// + /// 创建延迟执行的协程迭代器 + /// + /// 延迟时间(秒) + /// 要执行的动作 + /// 协程迭代器 + private static IEnumerator DelayedRoutine(float delay, Action? action) + { + yield return new WaitForSeconds(delay); + action?.Invoke(); + } + + /// + /// 启动一个重复执行的协程 + /// + /// 协程作用域 + /// 重复间隔时间(秒) + /// 每次重复时要执行的动作 + /// 协程句柄,可用于控制协程的生命周期 + public static CoroutineHandle LaunchRepeating(this ICoroutineScope scope, float interval, Action action) + => ((CoroutineScope)scope).Launch(RepeatingRoutine(interval, action)); + + /// + /// 创建重复执行的协程迭代器 + /// + /// 重复间隔时间(秒) + /// 要执行的动作 + /// 协程迭代器 + private static IEnumerator RepeatingRoutine(float interval, Action action) + { + while (true) + { + action?.Invoke(); + yield return new WaitForSeconds(interval); + } + } +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/CoroutineSystem.cs b/GFramework.Game/coroutine/CoroutineSystem.cs new file mode 100644 index 0000000..6bd87e2 --- /dev/null +++ b/GFramework.Game/coroutine/CoroutineSystem.cs @@ -0,0 +1,28 @@ +using GFramework.Core.system; +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +/// +/// 协程系统类,负责管理和更新协程调度器 +/// +/// 协程调度器实例 +public class CoroutineSystem(CoroutineScheduler scheduler) : AbstractSystem, ICoroutineSystem +{ + /// + /// 更新协程系统,驱动协程调度器执行协程逻辑 + /// + /// 时间间隔,表示自上一帧以来经过的时间(秒) + public void OnUpdate(float deltaTime) + { + // 更新协程调度器,处理等待中的协程 + scheduler.Update(deltaTime); + } + + /// + /// 初始化协程系统 + /// + protected override void OnInit() + { + } +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/GlobalCoroutineScope.cs b/GFramework.Game/coroutine/GlobalCoroutineScope.cs new file mode 100644 index 0000000..189a4d0 --- /dev/null +++ b/GFramework.Game/coroutine/GlobalCoroutineScope.cs @@ -0,0 +1,31 @@ +using System.Collections; + +namespace GFramework.Game.coroutine; + +/// +/// 全局协程作用域管理器,提供全局唯一的协程执行环境 +/// +public static class GlobalCoroutineScope +{ + private static CoroutineScope? _instance; + + /// + /// 获取全局协程作用域实例,如果未初始化则抛出异常 + /// + private static CoroutineScope Instance => + _instance ?? throw new InvalidOperationException("GlobalScope not initialized"); + + /// + /// 初始化全局协程作用域 + /// + /// 协程调度器实例 + public static void Initialize(CoroutineScheduler scheduler) => + _instance = new CoroutineScope(scheduler, "GlobalScope"); + + /// + /// 在全局作用域中启动一个协程 + /// + /// 要执行的协程枚举器 + /// 协程句柄,用于控制和管理协程生命周期 + public static CoroutineHandle Launch(IEnumerator routine) => Instance.Launch(routine); +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/WaitForSeconds.cs b/GFramework.Game/coroutine/WaitForSeconds.cs new file mode 100644 index 0000000..9b887f7 --- /dev/null +++ b/GFramework.Game/coroutine/WaitForSeconds.cs @@ -0,0 +1,24 @@ +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +/// +/// 表示一个等待指定秒数的时间延迟指令 +/// +/// 需要等待的秒数 +public class WaitForSeconds(float seconds) : IYieldInstruction +{ + private float _elapsed; + public bool IsDone { get; private set; } + + /// + /// 更新时间进度,当累计时间达到指定秒数时标记完成 + /// + /// 自上次更新以来经过的时间(秒) + public void Update(float deltaTime) + { + if (IsDone) return; + _elapsed += deltaTime; + if (_elapsed >= seconds) IsDone = true; + } +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/WaitUntil.cs b/GFramework.Game/coroutine/WaitUntil.cs new file mode 100644 index 0000000..3025b49 --- /dev/null +++ b/GFramework.Game/coroutine/WaitUntil.cs @@ -0,0 +1,24 @@ +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +/// +/// 等待直到指定条件满足的协程等待指令 +/// +/// 用于判断等待条件是否满足的布尔函数委托 +public class WaitUntil(Func predicate) : IYieldInstruction +{ + /// + /// 获取等待指令是否已完成 + /// + public bool IsDone { get; private set; } + + /// + /// 更新等待状态,在每一帧调用以检查条件是否满足 + /// + /// 自上一帧以来的时间间隔 + public void Update(float deltaTime) + { + if (!IsDone) IsDone = predicate(); + } +} \ No newline at end of file diff --git a/GFramework.Game/coroutine/WaitWhile.cs b/GFramework.Game/coroutine/WaitWhile.cs new file mode 100644 index 0000000..682c58a --- /dev/null +++ b/GFramework.Game/coroutine/WaitWhile.cs @@ -0,0 +1,24 @@ +using GFramework.Game.Abstractions.coroutine; + +namespace GFramework.Game.coroutine; + +/// +/// 等待条件为假的等待指令,当指定的谓词条件变为false时完成等待 +/// +/// 用于判断是否继续等待的条件函数,返回true表示继续等待,返回false表示等待结束 +public class WaitWhile(Func predicate) : IYieldInstruction +{ + /// + /// 获取等待指令是否已完成 + /// + public bool IsDone { get; private set; } + + /// + /// 更新等待状态,检查谓词条件是否满足结束等待的要求 + /// + /// 自上次更新以来的时间间隔 + public void Update(float deltaTime) + { + if (!IsDone) IsDone = !predicate(); + } +} \ No newline at end of file diff --git a/GFramework.Godot.SourceGenerators.Abstractions/GFramework.Godot.SourceGenerators.Abstractions.csproj b/GFramework.Godot.SourceGenerators.Abstractions/GFramework.Godot.SourceGenerators.Abstractions.csproj index 002c951..811a1b9 100644 --- a/GFramework.Godot.SourceGenerators.Abstractions/GFramework.Godot.SourceGenerators.Abstractions.csproj +++ b/GFramework.Godot.SourceGenerators.Abstractions/GFramework.Godot.SourceGenerators.Abstractions.csproj @@ -19,11 +19,11 @@ - + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers diff --git a/GFramework.SourceGenerators.Abstractions/GFramework.SourceGenerators.Abstractions.csproj b/GFramework.SourceGenerators.Abstractions/GFramework.SourceGenerators.Abstractions.csproj index dd0fb77..3aa8fd9 100644 --- a/GFramework.SourceGenerators.Abstractions/GFramework.SourceGenerators.Abstractions.csproj +++ b/GFramework.SourceGenerators.Abstractions/GFramework.SourceGenerators.Abstractions.csproj @@ -18,11 +18,11 @@ - + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers diff --git a/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj b/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj index 65193bf..05cbd7b 100644 --- a/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj +++ b/GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj @@ -22,11 +22,11 @@ runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers