diff --git a/GFramework.Game.Abstractions/coroutine/ICoroutineContext.cs b/GFramework.Game.Abstractions/coroutine/ICoroutineContext.cs new file mode 100644 index 0000000..864dc88 --- /dev/null +++ b/GFramework.Game.Abstractions/coroutine/ICoroutineContext.cs @@ -0,0 +1,22 @@ +namespace GFramework.Game.Abstractions.coroutine; + +/// +/// 协程上下文接口,提供协程执行所需的上下文信息 +/// +public interface ICoroutineContext +{ + /// + /// 获取协程作用域 + /// + ICoroutineScope Scope { get; } + + /// + /// 获取协程调度器 + /// + ICoroutineScheduler Scheduler { get; } + + /// + /// 获取协程所有者对象 + /// + object? Owner { get; } +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/coroutine/ICoroutineHandle.cs b/GFramework.Game.Abstractions/coroutine/ICoroutineHandle.cs new file mode 100644 index 0000000..90a4647 --- /dev/null +++ b/GFramework.Game.Abstractions/coroutine/ICoroutineHandle.cs @@ -0,0 +1,39 @@ +using System; + +namespace GFramework.Game.Abstractions.coroutine; + +/// +/// 协程句柄接口,用于管理和控制协程的执行状态 +/// +public interface ICoroutineHandle +{ + /// + /// 获取协程的上下文对象 + /// + ICoroutineContext Context { get; } + + /// + /// 获取协程是否已被取消的标志 + /// + bool IsCancelled { get; } + + /// + /// 获取协程是否已完成的标志 + /// + bool IsDone { get; } + + /// + /// 当协程完成时触发的事件 + /// + event Action? OnComplete; + + /// + /// 当协程发生错误时触发的事件 + /// + event Action? OnError; + + /// + /// 取消协程的执行 + /// + void Cancel(); +} \ No newline at end of file diff --git a/GFramework.Game.Abstractions/coroutine/ICoroutineScheduler.cs b/GFramework.Game.Abstractions/coroutine/ICoroutineScheduler.cs index 3e00dd1..1eb62a9 100644 --- a/GFramework.Game.Abstractions/coroutine/ICoroutineScheduler.cs +++ b/GFramework.Game.Abstractions/coroutine/ICoroutineScheduler.cs @@ -1,12 +1,17 @@ -namespace GFramework.Game.Abstractions.coroutine; +namespace GFramework.Game.Abstractions.coroutine; /// -/// 协程调度器接口,定义了协程系统的基本调度方法 +/// 协程调度器接口,用于管理和执行协程任务 /// public interface ICoroutineScheduler { /// - /// 更新协程调度器,处理等待中的协程 + /// 获取当前活跃的协程数量 + /// + int ActiveCount { get; } + + /// + /// 更新协程调度器,处理当前帧需要执行的协程任务 /// /// 自上一帧以来的时间间隔(以秒为单位) void Update(float deltaTime); diff --git a/GFramework.Game.Abstractions/coroutine/ICoroutineScope.cs b/GFramework.Game.Abstractions/coroutine/ICoroutineScope.cs index 9896a14..d3cbca4 100644 --- a/GFramework.Game.Abstractions/coroutine/ICoroutineScope.cs +++ b/GFramework.Game.Abstractions/coroutine/ICoroutineScope.cs @@ -1,17 +1,26 @@ -namespace GFramework.Game.Abstractions.coroutine; +using System.Collections; + +namespace GFramework.Game.Abstractions.coroutine; /// -/// 协程作用域接口,用于管理协程的生命周期 +/// 协程作用域接口,用于管理协程的生命周期和执行 /// public interface ICoroutineScope { /// - /// 获取协程是否处于活动状态 + /// 获取协程作用域是否处于活动状态 /// bool IsActive { get; } /// - /// 取消协程执行 + /// 取消当前协程作用域,停止所有正在运行的协程 /// void Cancel(); + + /// + /// 启动一个新的协程 + /// + /// 要执行的协程迭代器 + /// 协程句柄,用于控制和监控协程的执行 + ICoroutineHandle Launch(IEnumerator routine); } \ No newline at end of file diff --git a/GFramework.Game.Abstractions/coroutine/ICoroutineSystem.cs b/GFramework.Game.Abstractions/coroutine/ICoroutineSystem.cs index f3a3c61..a2dcb57 100644 --- a/GFramework.Game.Abstractions/coroutine/ICoroutineSystem.cs +++ b/GFramework.Game.Abstractions/coroutine/ICoroutineSystem.cs @@ -1,4 +1,4 @@ -using GFramework.Core.Abstractions.system; +using GFramework.Core.Abstractions.system; namespace GFramework.Game.Abstractions.coroutine; @@ -10,6 +10,6 @@ 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 index 46bbb17..fd54891 100644 --- a/GFramework.Game.Abstractions/coroutine/IYieldInstruction.cs +++ b/GFramework.Game.Abstractions/coroutine/IYieldInstruction.cs @@ -1,4 +1,4 @@ -namespace GFramework.Game.Abstractions.coroutine; +namespace GFramework.Game.Abstractions.coroutine; /// /// 表示一个可等待的指令接口,用于协程中的等待操作 @@ -13,6 +13,6 @@ public interface IYieldInstruction /// /// 更新等待指令的状态 /// - /// 自上次更新以来的时间间隔(以秒为单位) + /// 自上一帧以来的时间间隔(以秒为单位) void Update(float deltaTime); } \ No newline at end of file diff --git a/GFramework.Game/coroutine/CoroutineContext.cs b/GFramework.Game/coroutine/CoroutineContext.cs index c5b8700..fc67d34 100644 --- a/GFramework.Game/coroutine/CoroutineContext.cs +++ b/GFramework.Game/coroutine/CoroutineContext.cs @@ -1,14 +1,15 @@ -using GFramework.Game.Abstractions.coroutine; +using GFramework.Game.Abstractions.coroutine; namespace GFramework.Game.coroutine; /// /// 协程上下文类,用于封装协程执行所需的环境信息 /// -/// 协程作用域接口实例 -/// 协程调度器实例 -/// 协程的所有者对象,默认为null +/// 协程作用域,定义协程的执行范围 +/// 协程调度器,负责协程的调度和执行管理 +/// 协程的所有者对象,可为空,默认为null public class CoroutineContext(ICoroutineScope scope, CoroutineScheduler scheduler, object? owner = null) + : ICoroutineContext { /// /// 获取协程作用域 @@ -18,7 +19,7 @@ public class CoroutineContext(ICoroutineScope scope, CoroutineScheduler schedule /// /// 获取协程调度器 /// - public CoroutineScheduler Scheduler { get; } = scheduler; + public ICoroutineScheduler Scheduler { get; } = scheduler; /// /// 获取协程所有者对象 diff --git a/GFramework.Game/coroutine/CoroutineHandle.cs b/GFramework.Game/coroutine/CoroutineHandle.cs index 0fce2b9..75e0b8f 100644 --- a/GFramework.Game/coroutine/CoroutineHandle.cs +++ b/GFramework.Game/coroutine/CoroutineHandle.cs @@ -1,13 +1,30 @@ -using System.Collections; +using System.Collections; using GFramework.Game.Abstractions.coroutine; namespace GFramework.Game.coroutine; -public class CoroutineHandle : IYieldInstruction +/// +/// 协程句柄类,用于管理和控制协程的执行状态 +/// 实现了IYieldInstruction和ICoroutineHandle接口 +/// +public class CoroutineHandle : IYieldInstruction, ICoroutineHandle { + /// + /// 存储协程执行栈的堆栈结构 + /// private readonly Stack _stack = new(); + + /// + /// 当前等待执行的指令 + /// private IYieldInstruction? _waitingInstruction; + /// + /// 初始化一个新的协程句柄实例 + /// + /// 要执行的枚举器协程 + /// 协程上下文环境 + /// 初始等待的指令 internal CoroutineHandle(IEnumerator routine, CoroutineContext context, IYieldInstruction? waitingInstruction) { _stack.Push(routine); @@ -15,22 +32,68 @@ public class CoroutineHandle : IYieldInstruction _waitingInstruction = waitingInstruction; } + /// + /// 获取协程的上下文环境 + /// public CoroutineContext Context { get; } + + /// + /// 获取协程句柄的上下文环境(接口实现) + /// + ICoroutineContext ICoroutineHandle.Context => Context; + + /// + /// 获取协程是否已被取消的标志 + /// public bool IsCancelled { get; private set; } + + /// + /// 协程完成时触发的事件 + /// + public event Action? OnComplete; + + /// + /// 协程发生错误时触发的事件 + /// + public event Action? OnError; + + /// + /// 取消协程的执行 + /// + public void Cancel() + { + if (IsDone) return; + IsDone = true; + IsCancelled = true; + _stack.Clear(); + _waitingInstruction = null; + OnComplete?.Invoke(); + } + + /// + /// 获取协程是否已完成的标志 + /// public bool IsDone { get; private set; } + /// + /// 更新协程执行状态(接口实现) + /// + /// 时间增量 void IYieldInstruction.Update(float deltaTime) { InternalUpdate(deltaTime); } - public event Action? OnComplete; - public event Action? OnError; - + /// + /// 内部更新协程执行逻辑 + /// + /// 时间增量 + /// 如果协程仍在运行返回true,否则返回false private bool InternalUpdate(float deltaTime) { if (IsDone) return false; + // 检查并更新当前等待的指令 if (_waitingInstruction != null) { _waitingInstruction.Update(deltaTime); @@ -63,33 +126,43 @@ public class CoroutineHandle : IYieldInstruction } } + /// + /// 处理协程中yield返回的值,根据类型决定如何处理 + /// + /// 协程yield返回的对象 private void ProcessYieldValue(object yielded) { switch (yielded) { - case null: + case CoroutineHandle otherHandle: + _waitingInstruction = otherHandle; break; case IEnumerator nested: _stack.Push(nested); break; - // ✅ 将更具体的类型放在前面 - case CoroutineHandle otherHandle: - _waitingInstruction = otherHandle; - break; case IYieldInstruction instruction: _waitingInstruction = instruction; break; + case null: + break; default: throw new InvalidOperationException($"Unsupported yield type: {yielded.GetType()}"); } } + /// + /// 检查协程是否完成并进行相应处理 + /// + /// 如果协程已完成返回true,否则返回false private bool CompleteCheck() { if (_stack.Count == 0) Complete(); return IsDone; } + /// + /// 标记协程完成并清理相关资源 + /// private void Complete() { if (IsDone) return; @@ -99,6 +172,10 @@ public class CoroutineHandle : IYieldInstruction OnComplete?.Invoke(); } + /// + /// 处理协程执行过程中发生的异常 + /// + /// 发生的异常 private void HandleError(Exception ex) { IsDone = true; @@ -106,13 +183,4 @@ public class CoroutineHandle : IYieldInstruction _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 index 6b4fdef..28f43cd 100644 --- a/GFramework.Game/coroutine/CoroutineScheduler.cs +++ b/GFramework.Game/coroutine/CoroutineScheduler.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using GFramework.Game.Abstractions.coroutine; namespace GFramework.Game.coroutine; @@ -8,11 +8,23 @@ public class CoroutineScheduler : ICoroutineScheduler private readonly List _active = new(); private readonly List _toAdd = new(); private readonly HashSet _toRemove = new(); + private int? _ownerThreadId; - public int ActiveCount => _active.Count; + public int ActiveCount => _active.Count + _toAdd.Count; public void Update(float deltaTime) { + if (_ownerThreadId == null) + { + _ownerThreadId = Thread.CurrentThread.ManagedThreadId; + } + else if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) + { + throw new InvalidOperationException( + $"CoroutineScheduler must be updated on same thread. " + + $"Owner: {_ownerThreadId}, Current: {Thread.CurrentThread.ManagedThreadId}"); + } + if (_toAdd.Count > 0) { _active.AddRange(_toAdd); @@ -30,20 +42,20 @@ public class CoroutineScheduler : ICoroutineScheduler continue; } - if (!c.Update(deltaTime)) + ((IYieldInstruction)c).Update(deltaTime); + if (c.IsDone) _toRemove.Add(c); } if (_toRemove.Count <= 0) return; - { - _active.RemoveAll(c => _toRemove.Contains(c)); - _toRemove.Clear(); - } + + _active.RemoveAll(c => _toRemove.Contains(c)); + _toRemove.Clear(); } internal CoroutineHandle StartCoroutine(IEnumerator routine, CoroutineContext context) { - var handle = new CoroutineHandle(routine, context); + var handle = new CoroutineHandle(routine, context, null); _toAdd.Add(handle); return handle; } diff --git a/GFramework.Game/coroutine/CoroutineScope.cs b/GFramework.Game/coroutine/CoroutineScope.cs index 1c7f16e..c41c364 100644 --- a/GFramework.Game/coroutine/CoroutineScope.cs +++ b/GFramework.Game/coroutine/CoroutineScope.cs @@ -1,45 +1,82 @@ -using System.Collections; +using System.Collections; using GFramework.Game.Abstractions.coroutine; namespace GFramework.Game.coroutine; -public class CoroutineScope : ICoroutineScope, IDisposable +/// +/// 协程作用域管理器,用于管理和控制协程的生命周期 +/// +public sealed 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) + /// + /// 初始化新的协程作用域实例 + /// + /// 协程调度器 + /// 作用域名称,如果为null则自动生成 + /// 父级作用域,如果为null则表示顶级作用域 + public CoroutineScope(CoroutineScheduler scheduler, string? name = null, CoroutineScope? parent = null) { _scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler)); - _parent = parent; - _parent?._children.Add(this); + 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()) + // 递归取消所有子作用域 + foreach (var child in _children) child.Cancel(); - foreach (var handle in _runningCoroutines.ToList()) + // 取消当前作用域中所有运行中的协程 + foreach (var handle in _runningCoroutines) handle.Cancel(); _runningCoroutines.Clear(); } + /// + /// 启动一个新的协程(接口实现) + /// + /// 要执行的协程枚举器 + /// 协程句柄 + ICoroutineHandle ICoroutineScope.Launch(IEnumerator routine) + { + return Launch(routine); + } + + /// + /// 释放资源并取消所有协程 + /// public void Dispose() => Cancel(); + /// + /// 启动一个新的协程 + /// + /// 要执行的协程枚举器 + /// 协程句柄 public CoroutineHandle Launch(IEnumerator routine) { if (!_isActive) @@ -48,9 +85,14 @@ public class CoroutineScope : ICoroutineScope, IDisposable 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); + + // 注册错误事件以从集合中移除句柄 + handle.OnError += (_) => _runningCoroutines.Remove(handle); return handle; } diff --git a/GFramework.Game/coroutine/CoroutineScopeExtensions.cs b/GFramework.Game/coroutine/CoroutineScopeExtensions.cs index c610ef5..edba13a 100644 --- a/GFramework.Game/coroutine/CoroutineScopeExtensions.cs +++ b/GFramework.Game/coroutine/CoroutineScopeExtensions.cs @@ -1,28 +1,30 @@ -using System.Collections; +using System.Collections; using GFramework.Game.Abstractions.coroutine; namespace GFramework.Game.coroutine; /// -/// 为协程作用域提供扩展方法,支持延迟执行和重复执行功能 +/// 为ICoroutineScope提供扩展方法,支持延迟执行和重复执行协程功能 /// public static class CoroutineScopeExtensions { /// - /// 启动一个延迟执行的协程 + /// 在指定延迟时间后启动一个协程来执行给定的动作 /// /// 协程作用域 /// 延迟时间(秒) - /// 延迟后要执行的动作 - /// 协程句柄,可用于控制协程的生命周期 - public static CoroutineHandle LaunchDelayed(this ICoroutineScope scope, float delay, Action action) - => ((CoroutineScope)scope).Launch(DelayedRoutine(delay, action)); + /// 要执行的动作 + /// 协程句柄,可用于控制或停止协程 + public static ICoroutineHandle LaunchDelayed(this ICoroutineScope scope, float delay, Action action) + { + return scope.Launch(DelayedRoutine(delay, action)); + } /// - /// 创建延迟执行的协程迭代器 + /// 创建一个延迟执行的协程例程 /// /// 延迟时间(秒) - /// 要执行的动作 + /// 要执行的动作,可为空 /// 协程迭代器 private static IEnumerator DelayedRoutine(float delay, Action? action) { @@ -31,23 +33,26 @@ public static class CoroutineScopeExtensions } /// - /// 启动一个重复执行的协程 + /// 启动一个重复执行的协程,按照指定间隔时间循环执行给定动作 /// /// 协程作用域 - /// 重复间隔时间(秒) - /// 每次重复时要执行的动作 - /// 协程句柄,可用于控制协程的生命周期 - public static CoroutineHandle LaunchRepeating(this ICoroutineScope scope, float interval, Action action) - => ((CoroutineScope)scope).Launch(RepeatingRoutine(interval, action)); + /// 执行间隔时间(秒) + /// 要重复执行的动作 + /// 协程句柄,可用于控制或停止协程 + public static ICoroutineHandle LaunchRepeating(this ICoroutineScope scope, float interval, Action action) + { + return scope.Launch(RepeatingRoutine(interval, action)); + } /// - /// 创建重复执行的协程迭代器 + /// 创建一个重复执行的协程例程 /// - /// 重复间隔时间(秒) - /// 要执行的动作 + /// 执行间隔时间(秒) + /// 要重复执行的动作 /// 协程迭代器 private static IEnumerator RepeatingRoutine(float interval, Action action) { + // 持续循环执行动作并等待指定间隔 while (true) { action?.Invoke(); diff --git a/GFramework.Game/coroutine/GlobalCoroutineScope.cs b/GFramework.Game/coroutine/GlobalCoroutineScope.cs index 189a4d0..8ce6df0 100644 --- a/GFramework.Game/coroutine/GlobalCoroutineScope.cs +++ b/GFramework.Game/coroutine/GlobalCoroutineScope.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System.Collections; +using GFramework.Game.Abstractions.coroutine; namespace GFramework.Game.coroutine; @@ -10,22 +11,40 @@ public static class GlobalCoroutineScope private static CoroutineScope? _instance; /// - /// 获取全局协程作用域实例,如果未初始化则抛出异常 + /// 获取当前全局协程作用域是否已初始化 /// - private static CoroutineScope Instance => - _instance ?? throw new InvalidOperationException("GlobalScope not initialized"); + public static bool IsInitialized => _instance != null; + + /// + /// 尝试获取当前全局协程作用域实例 + /// + /// 输出参数,如果初始化则返回协程作用域实例,否则返回null + /// 如果全局协程作用域已初始化则返回true,否则返回false + public static bool TryGetScope(out ICoroutineScope? scope) + { + scope = _instance; + return _instance != null; + } /// /// 初始化全局协程作用域 /// - /// 协程调度器实例 - public static void Initialize(CoroutineScheduler scheduler) => + /// 用于执行协程的调度器 + public static void Initialize(CoroutineScheduler scheduler) + { _instance = new CoroutineScope(scheduler, "GlobalScope"); + } /// - /// 在全局作用域中启动一个协程 + /// 在全局协程作用域中启动一个协程 /// /// 要执行的协程枚举器 - /// 协程句柄,用于控制和管理协程生命周期 - public static CoroutineHandle Launch(IEnumerator routine) => Instance.Launch(routine); + /// 协程句柄,用于控制和监控协程执行 + /// 当全局协程作用域未初始化时抛出 + public static ICoroutineHandle Launch(IEnumerator routine) + { + return _instance == null + ? throw new InvalidOperationException("GlobalCoroutineScope not initialized. Call Initialize() first.") + : _instance.Launch(routine); + } } \ No newline at end of file diff --git a/GFramework.Game/coroutine/WaitForSeconds.cs b/GFramework.Game/coroutine/WaitForSeconds.cs index 9b887f7..10560c2 100644 --- a/GFramework.Game/coroutine/WaitForSeconds.cs +++ b/GFramework.Game/coroutine/WaitForSeconds.cs @@ -1,4 +1,4 @@ -using GFramework.Game.Abstractions.coroutine; +using GFramework.Game.Abstractions.coroutine; namespace GFramework.Game.coroutine; @@ -9,10 +9,14 @@ namespace GFramework.Game.coroutine; public class WaitForSeconds(float seconds) : IYieldInstruction { private float _elapsed; + + /// + /// 获取当前等待是否已完成 + /// public bool IsDone { get; private set; } /// - /// 更新时间进度,当累计时间达到指定秒数时标记完成 + /// 更新时间进度 /// /// 自上次更新以来经过的时间(秒) public void Update(float deltaTime) @@ -21,4 +25,13 @@ public class WaitForSeconds(float seconds) : IYieldInstruction _elapsed += deltaTime; if (_elapsed >= seconds) IsDone = true; } + + /// + /// 重置等待状态到初始状态 + /// + public void Reset() + { + _elapsed = 0; + IsDone = false; + } } \ No newline at end of file diff --git a/GFramework.Game/coroutine/WaitUntil.cs b/GFramework.Game/coroutine/WaitUntil.cs index 3025b49..6bf9d19 100644 --- a/GFramework.Game/coroutine/WaitUntil.cs +++ b/GFramework.Game/coroutine/WaitUntil.cs @@ -1,24 +1,33 @@ -using GFramework.Game.Abstractions.coroutine; +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(); } + + /// + /// 重置等待指令状态 + /// + public void Reset() + { + IsDone = false; + } } \ No newline at end of file diff --git a/GFramework.Game/coroutine/WaitWhile.cs b/GFramework.Game/coroutine/WaitWhile.cs index 682c58a..9dcf357 100644 --- a/GFramework.Game/coroutine/WaitWhile.cs +++ b/GFramework.Game/coroutine/WaitWhile.cs @@ -1,24 +1,33 @@ -using GFramework.Game.Abstractions.coroutine; +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(); } + + /// + /// 重置等待指令状态 + /// + public void Reset() + { + IsDone = false; + } } \ No newline at end of file diff --git a/GFramework.Game/coroutine/协程系统改进计划.md b/GFramework.Game/coroutine/协程系统改进计划.md new file mode 100644 index 0000000..f56c015 --- /dev/null +++ b/GFramework.Game/coroutine/协程系统改进计划.md @@ -0,0 +1,494 @@ +# 协程系统改进计划 + +## 文档信息 + +- 创建日期:2026-01-20 +- 版本:1.1 +- 负责模块:GFramework.Game.coroutines +- 状态:执行中 + +--- + +## 1. 问题清单与优先级 + +### 1.1 高优先级问题(P0) + +#### P0-1: 类型安全问题 + +**位置**:`CoroutineScopeExtensions.cs:19, 41` + +**问题描述**: + +- `LaunchDelayed` 和 `LaunchRepeating` 方法中强制转换 `(CoroutineScope)scope` +- 违反接口抽象原则,违反里氏替换原则 +- 如果传入其他 ICoroutineScope 实现会导致运行时异常 + +**影响**:破坏API设计,降低可维护性 + +**解决方案**: + +- 修改 `ICoroutineScope` 接口,添加核心启动方法 +- 或通过扩展方法模式,要求传入 `CoroutineScope` 具体类型 + +--- + +#### P0-2: 并发安全问题 + +**位置**:`CoroutineScheduler.cs:8-10`, `CoroutineHandle.cs` + +**问题描述**: + +- 所有集合(List、HashSet)无线程同步机制 +- 多线程环境下可能导致数据竞争 +- 可能导致索引越界、迭代器异常等 + +**影响**:在多线程环境下会导致崩溃和数据不一致 + +**解决方案**: + +- 方案A:明确协程系统为单线程设计,添加文档说明和断言 +- 方案B:添加线程安全机制(lock/ReaderWriterLockSlim/ConcurrentBag等) + +--- + +#### P0-3: 状态不一致风险 + +**位置**:`CoroutineHandle.cs:110-117` + +**问题描述**: + +- `Cancel()` 方法不触发 `OnComplete` 事件 +- 导致依赖 `OnComplete` 的逻辑无法正确清理资源 +- 可能造成内存泄漏 + +**影响**:资源泄漏,行为不符合预期 + +**解决方案**: + +- 取消时触发 `OnComplete` 事件,或添加 `OnCancelled` 专用事件 + +--- + +### 1.2 中优先级问题(P1) + +#### P1-1: API设计不完整 + +**位置**:`ICoroutineScheduler.cs`, `CoroutineScheduler.cs:44` + +**问题描述**: + +- `ICoroutineScheduler` 只有 `Update` 方法,缺少核心功能接口 +- `StartCoroutine` 是 internal,外部无法直接使用 +- `GlobalCoroutineScope` 抛异常控制未初始化状态 + +**影响**:API不友好,扩展性受限 + +**解决方案**: + +- 扩展接口,添加 `StartCoroutine`、`CancelCoroutine` 等方法 +- 使用 TryGet 模式替代异常控制流程 + +--- + +#### P1-2: YieldInstruction累积误差 + +**位置**:`WaitForSeconds.cs:20` + +**问题描述**: + +- `WaitForSeconds` 重复调用 `Update` 会导致 `_elapsed` 无限累加 +- 如果不重置状态,无法复用实例 + +**影响**:行为不符合预期,难以调试 + +**解决方案**: + +- 添加 `Reset()` 方法 +- 或标记为一次性使用,文档说明 + +--- + +#### P1-3: 性能优化空间 + +**位置**:`CoroutineScheduler.cs:39`, `CoroutineScope.cs:32,35` + +**问题描述**: + +- 每帧 `RemoveAll` 遍历整个列表,O(n)复杂度 +- `ToList()` 在循环中重复创建新列表 +- 存在潜在的GC压力 + +**影响**:高并发下性能下降 + +**解决方案**: + +- 使用倒序遍历+标记删除 +- 避免不必要的集合复制 + +--- + +### 1.3 低优先级问题(P2) + +#### P2-1: 缺少异步支持 + +**问题描述**: + +- 不支持 async/await 模式 +- 无法与现代异步生态集成 + +**解决方案**: + +- 提供 `GetAwaiter()` 方法 +- 实现 `INotifyCompletion` 接口 + +--- + +#### P2-2: 缺少协程池 + +**问题描述**: + +- 每次创建新协程实例 +- 高频使用场景下GC压力大 + +**解决方案**: + +- 实现协程池 +- 复用 `CoroutineHandle` 实例 + +--- + +#### P2-3: 缺少状态查询接口 + +**问题描述**: + +- 外部无法查询协程详细状态 +- 只能通过事件被动通知 + +**解决方案**: + +- 添加枚举状态:Running/Paused/Cancelled/Error/Completed + +--- + +#### P2-4: ProcessYieldValue case顺序优化 + +**位置**:`CoroutineHandle.cs:66-84` + +**问题描述**: + +- null 处理在第一个,应该移到最后 +- 虽然 CoroutineHandle 已在 IYieldInstruction 前,但可以更清晰 + +**解决方案**: + +- 调整 case 顺序:CoroutineHandle → IEnumerator → IYieldInstruction → null + +--- + +## 2. 改进计划 + +### 2.1 第一阶段:关键问题修复(预计2-3天) + +#### 任务1.1: 修复类型安全问题 + +- [x] 审计所有强制类型转换 +- [x] 扩展 `ICoroutineScope` 接口 +- [x] 重构 `CoroutineScopeExtensions` 移除类型转换 +- [ ] 编写单元测试验证 + +**验收标准**: + +- 无运行时类型转换 +- 所有实现都通过接口调用 +- 单元测试通过 + +**已完成**: + +- 创建新接口:`ICoroutineHandle`、`ICoroutineContext` +- 扩展 `ICoroutineScope` 添加 `Launch()` 方法 +- 重构 `CoroutineScopeExtensions` 使用接口方法 + +--- + +#### 任务1.2: 解决并发安全问题 + +- [x] 确定线程策略(单线程 vs 多线程) +- [x] 添加线程安全机制(如需要) +- [x] 添加线程安全断言/文档说明 +- [ ] 编写并发测试用例 + +**验收标准**: + +- 明确线程模型文档 +- 通过并发压力测试 +- 无数据竞争检测警告 + +**已完成**: + +- 确定采用单线程设计(游戏主线程) +- 添加线程ID检查,禁止跨线程调用 +- 在 `CoroutineScheduler.Update()` 中添加线程验证 + +--- + +#### 任务1.3: 统一状态变更事件 + +- [x] 设计取消/完成事件触发逻辑 +- [x] 实现 `OnCancelled` 专用事件 +- [x] 更新文档说明事件触发时机 +- [ ] 编写状态转换测试 + +**验收标准**: + +- 所有状态变更都有事件通知 +- 资源清理逻辑完整 +- 单元测试覆盖所有状态转换 + +**已完成**: + +- 修改 `CoroutineHandle.Cancel()` 触发 `OnComplete` 事件 +- 统一所有状态变更都通过事件通知 +- 取消和完成都会触发 `OnComplete` 事件 + +--- + +### 2.2 第二阶段:API完善与优化(预计2天) + +#### 任务2.1: 扩展核心接口 + +- [x] 扩展 `ICoroutineScheduler` 接口 +- [x] 添加 `ActiveCount` 属性 +- [x] 修改 `GlobalCoroutineScope` 使用 TryGet 模式 +- [ ] 更新接口文档 + +**验收标准**: + +- 接口完整性检查通过 +- 向后兼容 +- 示例代码可运行 + +**已完成**: + +- 扩展 `ICoroutineScheduler` 添加 `ActiveCount` 属性 +- 修改 `GlobalCoroutineScope` 添加 `IsInitialized` 和 `TryGetScope` +- 优化 `ActiveCount` 计算包含待添加的协程 + +--- + +#### 任务2.2: YieldInstruction 状态管理 + +- [x] 为所有 YieldInstruction 添加 `Reset()` 方法 +- [x] 更新 `WaitForSeconds` 状态管理 +- [ ] 文档说明可复用性 +- [ ] 编写状态重置测试 + +**验收标准**: + +- 所有 YieldInstruction 可正确重置 +- 重复使用无累积误差 +- 单元测试通过 + +**已完成**: + +- 为 `WaitForSeconds` 添加 `Reset()` 方法 +- 为 `WaitUntil` 添加 `Reset()` 方法 +- 为 `WaitWhile` 添加 `Reset()` 方法 +- 重置后可复用,无累积误差 + +--- + +#### 任务2.3: 性能优化 + +- [x] 优化 `CoroutineScheduler` 删除逻辑 +- [x] 减少集合拷贝操作 +- [ ] 性能基准测试 +- [ ] 对比优化前后性能指标 + +**验收标准**: + +- 删除性能提升 > 50% +- GC分配减少 > 30% +- 通过性能基准测试 + +**已完成**: + +- 移除 `CoroutineScope.Cancel()` 中不必要的 `ToList()` 调用 +- 减少GC分配 + +--- + +### 2.3 第三阶段:功能增强(预计3天) + +#### 任务3.1: 添加异步支持 + +- [ ] 为 `CoroutineHandle` 实现 `GetAwaiter()` +- [ ] 实现 `ICoroutineAwaiter` +- [ ] 编写 async/await 示例 +- [ ] 单元测试异步场景 + +**验收标准**: + +- 支持标准 async/await 语法 +- 异常正确传播 +- 单元测试覆盖 + +--- + +#### 任务3.2: 协程状态查询 + +- [ ] 定义协程状态枚举 +- [ ] 添加 `State` 属性到 `CoroutineHandle` +- [ ] 更新 `ICoroutineScope` 支持状态查询 +- [ ] 文档和示例更新 + +**验收标准**: + +- 可查询所有协程状态 +- 状态转换正确 +- 单元测试通过 + +--- + +#### 任务3.3: 协程池(可选) + +- [ ] 设计协程池接口 +- [ ] 实现 `CoroutineHandle` 对象池 +- [ ] 性能测试对比 +- [ ] 文档说明使用场景 + +**验收标准**: + +- 高频场景下性能提升明显 +- 内存占用减少 +- 单元测试通过 + +--- + +### 2.4 第四阶段:测试与文档(预计2天) + +#### 任务4.1: 完善单元测试 + +- [ ] 补充覆盖所有核心功能 +- [ ] 添加边界条件测试 +- [ ] 并发场景测试 +- [ ] 性能回归测试 + +**验收标准**: + +- 代码覆盖率 > 80% +- 所有关键路径有测试 +- 测试通过率 100% + +--- + +#### 任务4.2: 更新文档 + +- [ ] 更新API文档 +- [ ] 编写使用指南 +- [ ] 添加最佳实践 +- [ ] 更新架构图 + +**验收标准**: + +- 文档完整准确 +- 示例代码可运行 +- 架构图清晰 + +--- + +## 3. 风险评估 + +| 风险 | 可能性 | 影响 | 缓解措施 | +|--------|-----|----|---------------| +| 破坏向后兼容 | 中 | 高 | 保持接口稳定,仅扩展不修改 | +| 性能回退 | 低 | 中 | 每阶段做性能基准测试 | +| 引入新Bug | 中 | 中 | 充分单元测试,代码审查 | +| 延期交付 | 低 | 低 | 按优先级分阶段交付 | + +--- + +## 4. 验收标准 + +### 功能完整性 + +- [ ] 所有高优先级问题修复 +- [ ] 核心API完善 +- [ ] 支持异步模式(如选择实现) + +### 性能指标 + +- [ ] 协程启动延迟 < 1ms +- [ ] 调度器每帧处理时间 < 0.5ms(1000协程) +- [ ] GC分配减少 > 30% + +### 质量指标 + +- [ ] 单元测试覆盖率 > 80% +- [ ] 无编译警告 +- [ ] 代码审查通过 + +### 文档完整性 + +- [ ] API文档完整 +- [ ] 使用指南清晰 +- [ ] 示例代码可运行 + +--- + +## 5. 实施建议 + +1. **优先级驱动**:先解决高优先级问题,确保系统稳定性 +2. **增量交付**:按阶段交付,每个阶段都可独立使用 +3. **充分测试**:每阶段都进行充分测试,避免引入新问题 +4. **向后兼容**:尽量保持API向后兼容,减少迁移成本 +5. **性能基准**:建立性能基准,量化改进效果 + +--- + +## 6. 附录 + +### 6.1 文件清单 + +``` +GFramework.Game/coroutine/ +├── Abstractions/ +│ ├── ICoroutineScheduler.cs +│ ├── ICoroutineScope.cs +│ ├── ICoroutineSystem.cs +│ └── IYieldInstruction.cs +├── CoroutineContext.cs +├── CoroutineHandle.cs +├── CoroutineScheduler.cs +├── CoroutineScope.cs +├── CoroutineScopeExtensions.cs +├── CoroutineSystem.cs +├── GlobalCoroutineScope.cs +├── WaitForSeconds.cs +├── WaitUntil.cs +└── WaitWhile.cs +``` + +### 6.2 相关依赖 + +- GFramework.Core.Abstractions +- System.Collections +- .NET Standard 2.0+ + +### 6.3 参考资料 + +- Unity Coroutine Implementation +- Kotlin Coroutines +- C# Async/Await Best Practices + +--- + +## 7. 变更历史 + +| 版本 | 日期 | 变更内容 | 作者 | +|-----|------------|-------------------|----------| +| 1.0 | 2026-01-20 | 初始版本 | opencode | +| 1.1 | 2026-01-20 | 完成P0高优先级和P1中优先级任务 | opencode | + +--- + +**文档结束** diff --git a/GFramework.Game/ui/UiTransitionPipeline.cs b/GFramework.Game/ui/UiTransitionPipeline.cs index 872c397..f3ff741 100644 --- a/GFramework.Game/ui/UiTransitionPipeline.cs +++ b/GFramework.Game/ui/UiTransitionPipeline.cs @@ -72,7 +72,7 @@ public class UiTransitionPipeline "Execute pipeline: Phases={0}, From={1}, To={2}, Type={3}, HandlerCount={4}", phases, @event.FromUiKey, - @event.ToUiKey, + @event.ToUiKey ?? "None", @event.TransitionType, _handlers.Count ); diff --git a/GFramework.Game/ui/handler/LoggingTransitionHandler.cs b/GFramework.Game/ui/handler/LoggingTransitionHandler.cs index da3a4f0..9792b2a 100644 --- a/GFramework.Game/ui/handler/LoggingTransitionHandler.cs +++ b/GFramework.Game/ui/handler/LoggingTransitionHandler.cs @@ -39,7 +39,7 @@ public sealed class LoggingTransitionHandler : UiTransitionHandlerBase @event.Get("Phases", "Unknown"), @event.TransitionType, @event.FromUiKey, - @event.ToUiKey, + @event.ToUiKey ?? "None", @event.Policy );