From 01d9fefa1d8add0e36bcc908f9500988de8070b7 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 26 Jan 2026 09:44:07 +0800
Subject: [PATCH] =?UTF-8?q?feat(coroutine):=20=E6=89=A9=E5=B1=95=E5=8D=8F?=
=?UTF-8?q?=E7=A8=8B=E5=8A=9F=E8=83=BD=E5=B9=B6=E9=87=8D=E6=9E=84=E6=8C=87?=
=?UTF-8?q?=E4=BB=A4=E7=BB=93=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将协程等待指令移动到instructions命名空间下
- 添加WaitForProgress指令支持带进度回调的时间等待
- 添加WaitForAllCoroutines指令用于等待多个协程完成
- 添加AsyncOperation类用于桥接协程与async/await模型
- 添加协程扩展方法包括RepeatEvery、ExecuteAfter、Sequence等功能
- 添加Task与协程的转换扩展方法AsCoroutineInstruction
- 添加协程调度器的ParallelCoroutines扩展方法
- 添加IsCoroutineAlive方法检查协程状态
- 更新相关测试文件以匹配新的命名空间结构
---
.../coroutine/AsyncOperationTests.cs | 342 ++++++++++++++++++
.../coroutine/CoroutineHelperTests.cs | 1 +
.../coroutine/CoroutineSchedulerTests.cs | 1 +
.../coroutine/TaskCoroutineExtensionsTests.cs | 295 +++++++++++++++
.../coroutine/WaitForProgressTests.cs | 294 +++++++++++++++
.../coroutine/WaitForTaskTests.cs | 311 ++++++++++++++++
.../coroutine/YieldInstructionTests.cs | 2 +-
GFramework.Core/coroutine/CoroutineHelper.cs | 15 +-
.../coroutine/CoroutineScheduler.cs | 4 +
.../extensions/CoroutineExtensions.cs | 131 +++++++
.../extensions/TaskCoroutineExtensions.cs | 70 ++++
.../coroutine/instructions/AsyncOperation.cs | 141 ++++++++
.../coroutine/{ => instructions}/Delay.cs | 2 +-
.../instructions/WaitForAllCoroutines.cs | 29 ++
.../{ => instructions}/WaitForCoroutine.cs | 2 +-
.../{ => instructions}/WaitForFrames.cs | 2 +-
.../coroutine/instructions/WaitForProgress.cs | 60 +++
.../coroutine/instructions/WaitForTask.T.cs | 60 +++
.../coroutine/instructions/WaitForTask.cs | 57 +++
.../{ => instructions}/WaitOneFrame.cs | 2 +-
.../coroutine/{ => instructions}/WaitUntil.cs | 2 +-
.../coroutine/{ => instructions}/WaitWhile.cs | 2 +-
GFramework.Godot/coroutine/Timing.cs | 1 +
23 files changed, 1816 insertions(+), 10 deletions(-)
create mode 100644 GFramework.Core.Tests/coroutine/AsyncOperationTests.cs
create mode 100644 GFramework.Core.Tests/coroutine/TaskCoroutineExtensionsTests.cs
create mode 100644 GFramework.Core.Tests/coroutine/WaitForProgressTests.cs
create mode 100644 GFramework.Core.Tests/coroutine/WaitForTaskTests.cs
create mode 100644 GFramework.Core/coroutine/extensions/CoroutineExtensions.cs
create mode 100644 GFramework.Core/coroutine/extensions/TaskCoroutineExtensions.cs
create mode 100644 GFramework.Core/coroutine/instructions/AsyncOperation.cs
rename GFramework.Core/coroutine/{ => instructions}/Delay.cs (93%)
create mode 100644 GFramework.Core/coroutine/instructions/WaitForAllCoroutines.cs
rename GFramework.Core/coroutine/{ => instructions}/WaitForCoroutine.cs (93%)
rename GFramework.Core/coroutine/{ => instructions}/WaitForFrames.cs (93%)
create mode 100644 GFramework.Core/coroutine/instructions/WaitForProgress.cs
create mode 100644 GFramework.Core/coroutine/instructions/WaitForTask.T.cs
create mode 100644 GFramework.Core/coroutine/instructions/WaitForTask.cs
rename GFramework.Core/coroutine/{ => instructions}/WaitOneFrame.cs (93%)
rename GFramework.Core/coroutine/{ => instructions}/WaitUntil.cs (93%)
rename GFramework.Core/coroutine/{ => instructions}/WaitWhile.cs (94%)
diff --git a/GFramework.Core.Tests/coroutine/AsyncOperationTests.cs b/GFramework.Core.Tests/coroutine/AsyncOperationTests.cs
new file mode 100644
index 0000000..fb41180
--- /dev/null
+++ b/GFramework.Core.Tests/coroutine/AsyncOperationTests.cs
@@ -0,0 +1,342 @@
+using GFramework.Core.Abstractions.coroutine;
+using GFramework.Core.coroutine.instructions;
+using NUnit.Framework;
+
+namespace GFramework.Core.Tests.coroutine;
+
+///
+/// AsyncOperation的单元测试类
+/// 测试内容包括:
+/// - 初始化状态
+/// - 完成和状态检查
+/// - 异常处理
+/// - 延续操作
+/// - GetAwaiter
+/// - IsCompleted属性
+///
+[TestFixture]
+public class AsyncOperationTests
+{
+ ///
+ /// 验证AsyncOperation初始状态为未完成
+ ///
+ [Test]
+ public void AsyncOperation_Should_Not_Be_Done_Initially()
+ {
+ var op = new AsyncOperation();
+
+ Assert.That(op.IsDone, Is.False);
+ }
+
+ ///
+ /// 验证AsyncOperation初始状态IsCompleted为false
+ ///
+ [Test]
+ public void AsyncOperation_Should_Not_Be_Completed_Initially()
+ {
+ var op = new AsyncOperation();
+
+ Assert.That(op.IsCompleted, Is.False);
+ }
+
+ ///
+ /// 验证SetCompleted后IsDone应该为true
+ ///
+ [Test]
+ public void SetCompleted_Should_Set_IsDone_To_True()
+ {
+ var op = new AsyncOperation();
+
+ op.SetCompleted();
+
+ Assert.That(op.IsDone, Is.True);
+ }
+
+ ///
+ /// 验证SetCompleted后IsCompleted应该为true
+ ///
+ [Test]
+ public void SetCompleted_Should_Set_IsCompleted_To_True()
+ {
+ var op = new AsyncOperation();
+
+ op.SetCompleted();
+
+ Assert.That(op.IsCompleted, Is.True);
+ }
+
+ ///
+ /// 验证SetCompleted只能被调用一次
+ ///
+ [Test]
+ public void SetCompleted_Should_Be_Idempotent()
+ {
+ var op = new AsyncOperation();
+
+ op.SetCompleted();
+ op.SetCompleted();
+ op.SetCompleted();
+
+ Assert.That(op.IsDone, Is.True);
+ }
+
+ ///
+ /// 验证SetException后IsDone应该为true
+ ///
+ [Test]
+ public void SetException_Should_Set_IsDone_To_True()
+ {
+ var op = new AsyncOperation();
+
+ op.SetException(new InvalidOperationException("Test exception"));
+
+ Assert.That(op.IsDone, Is.True);
+ }
+
+ ///
+ /// 验证SetException后Task应该包含异常
+ ///
+ [Test]
+ public void SetException_Should_Set_Exception_On_Task()
+ {
+ var op = new AsyncOperation();
+ var expectedException = new InvalidOperationException("Test exception");
+
+ op.SetException(expectedException);
+
+ Assert.That(async () => await op.Task, Throws.InstanceOf());
+ }
+
+ ///
+ /// 验证OnCompleted应该在已完成时立即执行延续
+ ///
+ [Test]
+ public void OnCompleted_Should_Execute_Immediately_When_Already_Completed()
+ {
+ var op = new AsyncOperation();
+ var continuationCalled = false;
+
+ op.SetCompleted();
+ op.OnCompleted(() => continuationCalled = true);
+
+ Assert.That(continuationCalled, Is.True);
+ }
+
+ ///
+ /// 验证OnCompleted应该在未完成时不立即执行延续
+ ///
+ [Test]
+ public void OnCompleted_Should_Not_Execute_Immediately_When_Not_Completed()
+ {
+ var op = new AsyncOperation();
+ var continuationCalled = false;
+
+ op.OnCompleted(() => continuationCalled = true);
+
+ Assert.That(continuationCalled, Is.False);
+ }
+
+ ///
+ /// 验证延续应该在SetCompleted后被调用
+ ///
+ [Test]
+ public void Continuation_Should_Be_Called_After_SetCompleted()
+ {
+ var op = new AsyncOperation();
+ var continuationCalled = false;
+
+ op.OnCompleted(() => continuationCalled = true);
+ op.SetCompleted();
+
+ Assert.That(continuationCalled, Is.True);
+ }
+
+ ///
+ /// 验证多个延续应该都能被调用
+ ///
+ [Test]
+ public void Multiple_Continuations_Should_All_Be_Called()
+ {
+ var op = new AsyncOperation();
+ var callCount = 0;
+
+ op.OnCompleted(() => callCount++);
+ op.OnCompleted(() => callCount++);
+ op.OnCompleted(() => callCount++);
+ op.SetCompleted();
+
+ Assert.That(callCount, Is.EqualTo(3));
+ }
+
+ ///
+ /// 验证延续应该在SetException后被调用
+ ///
+ [Test]
+ public void Continuation_Should_Be_Called_After_SetException()
+ {
+ var op = new AsyncOperation();
+ var continuationCalled = false;
+
+ op.OnCompleted(() => continuationCalled = true);
+ op.SetException(new InvalidOperationException("Test"));
+
+ Assert.That(continuationCalled, Is.True);
+ }
+
+ ///
+ /// 验证SetCompleted后设置的延续也应该被调用
+ ///
+ [Test]
+ public void Continuation_Registered_After_Completed_Should_Be_Called()
+ {
+ var op = new AsyncOperation();
+ var firstCalled = false;
+ var secondCalled = false;
+
+ op.OnCompleted(() => firstCalled = true);
+ op.SetCompleted();
+ op.OnCompleted(() => secondCalled = true);
+
+ Assert.That(firstCalled, Is.True);
+ Assert.That(secondCalled, Is.True);
+ }
+
+ ///
+ /// 验证GetAwaiter应该返回自身
+ ///
+ [Test]
+ public void GetAwaiter_Should_Return_Self()
+ {
+ var op = new AsyncOperation();
+
+ var awaiter = op.GetAwaiter();
+
+ Assert.That(awaiter, Is.SameAs(op));
+ }
+
+ ///
+ /// 验证Update方法不应该改变状态
+ ///
+ [Test]
+ public void Update_Should_Not_Change_State()
+ {
+ var op = new AsyncOperation();
+
+ op.Update(0.1);
+
+ Assert.That(op.IsDone, Is.False);
+ }
+
+ ///
+ /// 验证AsyncOperation实现IYieldInstruction接口
+ ///
+ [Test]
+ public void AsyncOperation_Should_Implement_IYieldInstruction()
+ {
+ var op = new AsyncOperation();
+
+ Assert.That(op, Is.InstanceOf());
+ }
+
+ ///
+ /// 验证Task属性应该返回有效的Task
+ ///
+ [Test]
+ public void Task_Property_Should_Return_Valid_Task()
+ {
+ var op = new AsyncOperation();
+
+ Assert.That(op.Task, Is.Not.Null);
+ }
+
+ ///
+ /// 验证SetCompleted后Task应该完成
+ ///
+ [Test]
+ public async Task Task_Should_Complete_After_SetCompleted()
+ {
+ var op = new AsyncOperation();
+
+ op.SetCompleted();
+
+ await op.Task;
+
+ Assert.That(op.Task.IsCompleted, Is.True);
+ }
+
+ ///
+ /// 验证SetException后Task应该失败
+ ///
+ [Test]
+ public void Task_Should_Fault_After_SetException()
+ {
+ var op = new AsyncOperation();
+
+ op.SetException(new InvalidOperationException("Test"));
+
+ Assert.That(op.Task.IsFaulted, Is.True);
+ }
+
+ ///
+ /// 验证SetCompleted只能设置一次
+ ///
+ [Test]
+ public void SetCompleted_Should_Only_Set_Once()
+ {
+ var op = new AsyncOperation();
+ var firstCallCompleted = false;
+ var secondCallCompleted = false;
+
+ op.OnCompleted(() => firstCallCompleted = true);
+ op.SetCompleted();
+
+ op.OnCompleted(() => secondCallCompleted = true);
+ op.SetCompleted();
+
+ Assert.That(firstCallCompleted, Is.True);
+ Assert.That(secondCallCompleted, Is.True);
+ }
+
+ ///
+ /// 验证SetException只能在未完成时设置
+ ///
+ [Test]
+ public void SetException_Should_Not_Work_After_SetCompleted()
+ {
+ var op = new AsyncOperation();
+
+ op.SetCompleted();
+ op.SetException(new InvalidOperationException("Test"));
+
+ Assert.That(op.Task.IsCompletedSuccessfully, Is.True);
+ Assert.That(op.Task.IsFaulted, Is.False);
+ }
+
+ ///
+ /// 验证SetCompleted不能在SetException后设置
+ ///
+ [Test]
+ public void SetCompleted_Should_Not_Work_After_SetException()
+ {
+ var op = new AsyncOperation();
+
+ op.SetException(new InvalidOperationException("Test"));
+ op.SetCompleted();
+
+ Assert.That(op.Task.IsFaulted, Is.True);
+ Assert.That(op.Task.IsCompletedSuccessfully, Is.False);
+ }
+
+ ///
+ /// 验证延续抛出的异常应该被捕获
+ ///
+ [Test]
+ public void Continuation_Exception_Should_Be_Caught()
+ {
+ var op = new AsyncOperation();
+
+ op.OnCompleted(() => throw new InvalidOperationException("Test exception"));
+
+ Assert.DoesNotThrow(() => op.SetCompleted());
+ }
+}
diff --git a/GFramework.Core.Tests/coroutine/CoroutineHelperTests.cs b/GFramework.Core.Tests/coroutine/CoroutineHelperTests.cs
index 1e89e24..965df34 100644
--- a/GFramework.Core.Tests/coroutine/CoroutineHelperTests.cs
+++ b/GFramework.Core.Tests/coroutine/CoroutineHelperTests.cs
@@ -1,5 +1,6 @@
using GFramework.Core.Abstractions.coroutine;
using GFramework.Core.coroutine;
+using GFramework.Core.coroutine.instructions;
using NUnit.Framework;
namespace GFramework.Core.Tests.coroutine;
diff --git a/GFramework.Core.Tests/coroutine/CoroutineSchedulerTests.cs b/GFramework.Core.Tests/coroutine/CoroutineSchedulerTests.cs
index 0a30adb..9e8e43d 100644
--- a/GFramework.Core.Tests/coroutine/CoroutineSchedulerTests.cs
+++ b/GFramework.Core.Tests/coroutine/CoroutineSchedulerTests.cs
@@ -1,5 +1,6 @@
using GFramework.Core.Abstractions.coroutine;
using GFramework.Core.coroutine;
+using GFramework.Core.coroutine.instructions;
using NUnit.Framework;
namespace GFramework.Core.Tests.coroutine;
diff --git a/GFramework.Core.Tests/coroutine/TaskCoroutineExtensionsTests.cs b/GFramework.Core.Tests/coroutine/TaskCoroutineExtensionsTests.cs
new file mode 100644
index 0000000..780d207
--- /dev/null
+++ b/GFramework.Core.Tests/coroutine/TaskCoroutineExtensionsTests.cs
@@ -0,0 +1,295 @@
+using GFramework.Core.Abstractions.coroutine;
+using GFramework.Core.coroutine;
+using GFramework.Core.coroutine.extensions;
+using GFramework.Core.coroutine.instructions;
+using NUnit.Framework;
+
+namespace GFramework.Core.Tests.coroutine;
+
+///
+/// TaskCoroutineExtensions的单元测试类
+/// 测试内容包括:
+/// - AsCoroutineInstruction方法
+/// - StartTaskAsCoroutine方法
+///
+[TestFixture]
+public class TaskCoroutineExtensionsTests
+{
+ ///
+ /// 验证AsCoroutineInstruction应该返回WaitForTask
+ ///
+ [Test]
+ public void AsCoroutineInstruction_Should_Return_WaitForTask()
+ {
+ var task = Task.CompletedTask;
+ var instruction = task.AsCoroutineInstruction();
+
+ Assert.That(instruction, Is.InstanceOf());
+ }
+
+ ///
+ /// 验证AsCoroutineInstruction应该返回WaitForTask
+ ///
+ [Test]
+ public void AsCoroutineInstructionOfT_Should_Return_WaitForTaskOfT()
+ {
+ var task = Task.FromResult(42);
+ var instruction = task.AsCoroutineInstruction();
+
+ Assert.That(instruction, Is.InstanceOf>());
+ }
+
+ ///
+ /// 验证AsCoroutineInstruction可以处理已完成的Task
+ ///
+ [Test]
+ public void AsCoroutineInstruction_Should_Handle_Completed_Task()
+ {
+ var task = Task.CompletedTask;
+ var instruction = task.AsCoroutineInstruction();
+
+ Assert.That(instruction, Is.InstanceOf());
+ }
+
+ ///
+ /// 验证AsCoroutineInstruction可以处理已完成的Task
+ ///
+ [Test]
+ public void AsCoroutineInstructionOfT_Should_Handle_Completed_Task()
+ {
+ var task = Task.FromResult(42);
+ var instruction = task.AsCoroutineInstruction();
+
+ Assert.That(instruction, Is.InstanceOf>());
+ }
+
+ ///
+ /// 验证AsCoroutineInstruction应该能够访问Task结果
+ ///
+ [Test]
+ public void AsCoroutineInstructionOfT_Should_Access_Task_Result()
+ {
+ var task = Task.FromResult(42);
+ var instruction = task.AsCoroutineInstruction();
+
+ task.Wait();
+
+ Assert.That(instruction.Result, Is.EqualTo(42));
+ }
+
+ ///
+ /// 验证AsCoroutineInstruction应该处理null Task(抛出异常)
+ ///
+ [Test]
+ public void AsCoroutineInstruction_Should_Handle_Null_Task()
+ {
+ Task task = null!;
+
+ Assert.Throws(() => task.AsCoroutineInstruction());
+ }
+
+ ///
+ /// 验证AsCoroutineInstruction应该处理null Task(抛出异常)
+ ///
+ [Test]
+ public void AsCoroutineInstructionOfT_Should_Handle_Null_Task()
+ {
+ Task task = null!;
+
+ Assert.Throws(() => task.AsCoroutineInstruction());
+ }
+
+ ///
+ /// 验证AsCoroutineInstruction应该处理失败的Task
+ ///
+ [Test]
+ public void AsCoroutineInstruction_Should_Handle_Faulted_Task()
+ {
+ var task = Task.FromException(new InvalidOperationException("Test exception"));
+ var instruction = task.AsCoroutineInstruction();
+
+ Assert.That(instruction, Is.InstanceOf());
+ }
+
+ ///
+ /// 验证AsCoroutineInstruction应该处理失败的Task
+ ///
+ [Test]
+ public void AsCoroutineInstructionOfT_Should_Handle_Faulted_Task()
+ {
+ var task = Task.FromException(new InvalidOperationException("Test exception"));
+ var instruction = task.AsCoroutineInstruction();
+
+ Assert.That(instruction, Is.InstanceOf>());
+ }
+
+ ///
+ /// 验证StartTaskAsCoroutine应该返回有效的协程句柄
+ ///
+ [Test]
+ public void StartTaskAsCoroutine_Should_Return_Valid_Handle()
+ {
+ var timeSource = new TestTimeSource();
+ var scheduler = new CoroutineScheduler(timeSource, instanceId: 1);
+ var task = Task.CompletedTask;
+
+ var handle = scheduler.StartTaskAsCoroutine(task);
+
+ Assert.That(handle.IsValid, Is.True);
+ }
+
+ ///
+ /// 验证StartTaskAsCoroutine应该返回有效的协程句柄
+ ///
+ [Test]
+ public void StartTaskAsCoroutineOfT_Should_Return_Valid_Handle()
+ {
+ var timeSource = new TestTimeSource();
+ var scheduler = new CoroutineScheduler(timeSource, instanceId: 1);
+ var task = Task.FromResult(42);
+
+ var handle = scheduler.StartTaskAsCoroutine(task);
+
+ Assert.That(handle.IsValid, Is.True);
+ }
+
+ ///
+ /// 验证StartTaskAsCoroutine应该等待Task完成
+ ///
+ [Test]
+ public void StartTaskAsCoroutine_Should_Wait_For_Task_Completion()
+ {
+ var timeSource = new TestTimeSource();
+ var scheduler = new CoroutineScheduler(timeSource, instanceId: 1);
+
+ var completed = false;
+ var tcs = new TaskCompletionSource