From 3b47516fe5a365134758a04f7404753f95f5201e Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 10 Feb 2026 23:14:00 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(coroutine):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=A4=9A=E7=A7=8D=E5=8D=8F=E7=A8=8B=E7=AD=89=E5=BE=85=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E5=8F=8A=E5=AF=B9=E5=BA=94=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 WaitForConditionChange 指令,支持等待条件状态变化 - 实现 WaitForEndOfFrame 指令,支持等待当前帧渲染完成 - 实现 WaitForFixedUpdate 指令,支持等待物理固定更新周期 - 实现 WaitForMultipleEvents 指令,支持等待多个事件中的任意一个触发 - 实现 WaitForNextFrame 指令,支持等待下一帧开始 - 实现 WaitForPredicate 指令,支持通用谓词等待功能 - 实现 WaitForSecondsRealtime 指令,支持基于真实时间的等待 - 实现 WaitForSecondsScaled 指令,支持受时间缩放影响的等待 - 实现 WaitUntilOrTimeout 指令,支持带超时的条件等待 - 为所有新指令添加完整的单元测试覆盖 --- .../coroutine/WaitForConditionChangeTests.cs | 124 ++++++++++++++++++ .../coroutine/WaitForEndOfFrameTests.cs | 61 +++++++++ .../coroutine/WaitForFixedUpdateTests.cs | 61 +++++++++ .../coroutine/WaitForNextFrameTests.cs | 61 +++++++++ .../coroutine/WaitForPredicateTests.cs | 87 ++++++++++++ .../coroutine/WaitForSecondsRealtimeTests.cs | 80 +++++++++++ .../coroutine/WaitForSecondsScaledTests.cs | 80 +++++++++++ .../coroutine/WaitUntilOrTimeoutTests.cs | 109 +++++++++++++++ .../instructions/WaitForConditionChange.cs | 46 +++++++ .../instructions/WaitForEndOfFrame.cs | 27 ++++ .../instructions/WaitForFixedUpdate.cs | 27 ++++ .../instructions/WaitForMultipleEvents.cs | 99 ++++++++++++++ .../instructions/WaitForNextFrame.cs | 26 ++++ .../instructions/WaitForPredicate.cs | 28 ++++ .../instructions/WaitForSecondsRealtime.cs | 30 +++++ .../instructions/WaitForSecondsScaled.cs | 30 +++++ .../instructions/WaitUntilOrTimeout.cs | 40 ++++++ 17 files changed, 1016 insertions(+) create mode 100644 GFramework.Core.Tests/coroutine/WaitForConditionChangeTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForEndOfFrameTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForFixedUpdateTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForNextFrameTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForPredicateTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForSecondsRealtimeTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForSecondsScaledTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitUntilOrTimeoutTests.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitForConditionChange.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitForEndOfFrame.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitForFixedUpdate.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitForNextFrame.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitForPredicate.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitForSecondsRealtime.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitForSecondsScaled.cs create mode 100644 GFramework.Core/coroutine/instructions/WaitUntilOrTimeout.cs diff --git a/GFramework.Core.Tests/coroutine/WaitForConditionChangeTests.cs b/GFramework.Core.Tests/coroutine/WaitForConditionChangeTests.cs new file mode 100644 index 00000000..db91ee28 --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForConditionChangeTests.cs @@ -0,0 +1,124 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForConditionChange的单元测试类 +/// +[TestFixture] +public class WaitForConditionChangeTests +{ + /// + /// 验证WaitForConditionChange初始状态为未完成 + /// + [Test] + public void WaitForConditionChange_Should_Not_Be_Done_Initially() + { + var condition = false; + var wait = new WaitForConditionChange(() => condition, true); + + Assert.That(wait.IsDone, Is.False); + } + + /// + /// 验证WaitForConditionChange从false变为true时完成 + /// + [Test] + public void WaitForConditionChange_Should_Be_Done_When_Changing_From_False_To_True() + { + var condition = false; + var wait = new WaitForConditionChange(() => condition, true); + + // 初始状态记录 + wait.Update(0.1); + + condition = true; + wait.Update(0.1); + + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForConditionChange从true变为false时完成 + /// + [Test] + public void WaitForConditionChange_Should_Be_Done_When_Changing_From_True_To_False() + { + var condition = true; + var wait = new WaitForConditionChange(() => condition, false); + + // 初始状态记录 + wait.Update(0.1); + + condition = false; + wait.Update(0.1); + + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForConditionChange不响应相同状态的变化 + /// + [Test] + public void WaitForConditionChange_Should_Not_Be_Done_When_No_State_Change() + { + var condition = false; + var wait = new WaitForConditionChange(() => condition, true); + + // 初始状态记录 + wait.Update(0.1); + + // 仍然是false,没有状态改变 + wait.Update(0.1); + + Assert.That(wait.IsDone, Is.False); + } + + /// + /// 验证WaitForConditionChange多次状态切换只响应第一次 + /// + [Test] + public void WaitForConditionChange_Should_Only_Respond_To_First_Transition() + { + var condition = false; + var wait = new WaitForConditionChange(() => condition, true); + + // 记录初始状态 + wait.Update(0.1); + Assert.That(wait.IsDone, Is.False); + + // 触发状态转换到目标状态 + condition = true; + wait.Update(0.1); + Assert.That(wait.IsDone, Is.True); + + // 再次切换回原始状态 + condition = false; + wait.Update(0.1); + + // 应该仍然保持完成状态 + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForConditionChange抛出ArgumentNullException当conditionGetter为null + /// + [Test] + public void WaitForConditionChange_Should_Throw_ArgumentNullException_When_ConditionGetter_Is_Null() + { + Assert.Throws(() => new WaitForConditionChange(null!, true)); + } + + /// + /// 验证WaitForConditionChange实现IYieldInstruction接口 + /// + [Test] + public void WaitForConditionChange_Should_Implement_IYieldInstruction() + { + var wait = new WaitForConditionChange(() => false, true); + + Assert.That(wait, Is.InstanceOf()); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForEndOfFrameTests.cs b/GFramework.Core.Tests/coroutine/WaitForEndOfFrameTests.cs new file mode 100644 index 00000000..d57ff72c --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForEndOfFrameTests.cs @@ -0,0 +1,61 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForEndOfFrame的单元测试类 +/// +[TestFixture] +public class WaitForEndOfFrameTests +{ + /// + /// 验证WaitForEndOfFrame初始状态为未完成 + /// + [Test] + public void WaitForEndOfFrame_Should_Not_Be_Done_Initially() + { + var wait = new WaitForEndOfFrame(); + + Assert.That(wait.IsDone, Is.False); + } + + /// + /// 验证WaitForEndOfFrame在Update后应该完成 + /// + [Test] + public void WaitForEndOfFrame_Should_Be_Done_After_Update() + { + var wait = new WaitForEndOfFrame(); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForEndOfFrame多次Update后仍保持完成状态 + /// + [Test] + public void WaitForEndOfFrame_Should_Remain_Done_After_Multiple_Updates() + { + var wait = new WaitForEndOfFrame(); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForEndOfFrame实现IYieldInstruction接口 + /// + [Test] + public void WaitForEndOfFrame_Should_Implement_IYieldInstruction() + { + var wait = new WaitForEndOfFrame(); + + Assert.That(wait, Is.InstanceOf()); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForFixedUpdateTests.cs b/GFramework.Core.Tests/coroutine/WaitForFixedUpdateTests.cs new file mode 100644 index 00000000..a5d981fb --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForFixedUpdateTests.cs @@ -0,0 +1,61 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForFixedUpdate的单元测试类 +/// +[TestFixture] +public class WaitForFixedUpdateTests +{ + /// + /// 验证WaitForFixedUpdate初始状态为未完成 + /// + [Test] + public void WaitForFixedUpdate_Should_Not_Be_Done_Initially() + { + var wait = new WaitForFixedUpdate(); + + Assert.That(wait.IsDone, Is.False); + } + + /// + /// 验证WaitForFixedUpdate在Update后应该完成 + /// + [Test] + public void WaitForFixedUpdate_Should_Be_Done_After_Update() + { + var wait = new WaitForFixedUpdate(); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForFixedUpdate多次Update后仍保持完成状态 + /// + [Test] + public void WaitForFixedUpdate_Should_Remain_Done_After_Multiple_Updates() + { + var wait = new WaitForFixedUpdate(); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForFixedUpdate实现IYieldInstruction接口 + /// + [Test] + public void WaitForFixedUpdate_Should_Implement_IYieldInstruction() + { + var wait = new WaitForFixedUpdate(); + + Assert.That(wait, Is.InstanceOf()); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForNextFrameTests.cs b/GFramework.Core.Tests/coroutine/WaitForNextFrameTests.cs new file mode 100644 index 00000000..fabff301 --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForNextFrameTests.cs @@ -0,0 +1,61 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForNextFrame的单元测试类 +/// +[TestFixture] +public class WaitForNextFrameTests +{ + /// + /// 验证WaitForNextFrame初始状态为未完成 + /// + [Test] + public void WaitForNextFrame_Should_Not_Be_Done_Initially() + { + var wait = new WaitForNextFrame(); + + Assert.That(wait.IsDone, Is.False); + } + + /// + /// 验证WaitForNextFrame在Update后应该完成 + /// + [Test] + public void WaitForNextFrame_Should_Be_Done_After_Update() + { + var wait = new WaitForNextFrame(); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForNextFrame多次Update后仍保持完成状态 + /// + [Test] + public void WaitForNextFrame_Should_Remain_Done_After_Multiple_Updates() + { + var wait = new WaitForNextFrame(); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + + wait.Update(0.016); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForNextFrame实现IYieldInstruction接口 + /// + [Test] + public void WaitForNextFrame_Should_Implement_IYieldInstruction() + { + var wait = new WaitForNextFrame(); + + Assert.That(wait, Is.InstanceOf()); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForPredicateTests.cs b/GFramework.Core.Tests/coroutine/WaitForPredicateTests.cs new file mode 100644 index 00000000..d46f808d --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForPredicateTests.cs @@ -0,0 +1,87 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForPredicate的单元测试类 +/// +[TestFixture] +public class WaitForPredicateTests +{ + /// + /// 验证WaitForPredicate默认等待条件为真时完成 + /// + [Test] + public void WaitForPredicate_Should_Wait_For_True_By_Default() + { + var condition = false; + var wait = new WaitForPredicate(() => condition); + + Assert.That(wait.IsDone, Is.False); + + condition = true; + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForPredicate可以等待条件为假时完成 + /// + [Test] + public void WaitForPredicate_Should_Wait_For_False_When_Specified() + { + var condition = true; + var wait = new WaitForPredicate(() => condition, false); + + Assert.That(wait.IsDone, Is.False); + + condition = false; + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForPredicate多次检查条件 + /// + [Test] + public void WaitForPredicate_Should_Check_Condition_Multiple_Times() + { + var callCount = 0; + var wait = new WaitForPredicate(() => + { + callCount++; + return callCount >= 3; + }); + + Assert.That(wait.IsDone, Is.False); + Assert.That(callCount, Is.EqualTo(1)); + + wait.Update(0.1); + Assert.That(wait.IsDone, Is.False); + Assert.That(callCount, Is.EqualTo(2)); + + wait.Update(0.1); + Assert.That(wait.IsDone, Is.True); + Assert.That(callCount, Is.EqualTo(3)); + } + + /// + /// 验证WaitForPredicate抛出ArgumentNullException当predicate为null + /// + [Test] + public void WaitForPredicate_Should_Throw_ArgumentNullException_When_Predicate_Is_Null() + { + Assert.Throws(() => new WaitForPredicate(null!)); + } + + /// + /// 验证WaitForPredicate实现IYieldInstruction接口 + /// + [Test] + public void WaitForPredicate_Should_Implement_IYieldInstruction() + { + var wait = new WaitForPredicate(() => true); + + Assert.That(wait, Is.InstanceOf()); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForSecondsRealtimeTests.cs b/GFramework.Core.Tests/coroutine/WaitForSecondsRealtimeTests.cs new file mode 100644 index 00000000..af86fc9b --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForSecondsRealtimeTests.cs @@ -0,0 +1,80 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForSecondsRealtime的单元测试类 +/// +[TestFixture] +public class WaitForSecondsRealtimeTests +{ + /// + /// 验证WaitForSecondsRealtime初始状态根据时间设置 + /// + [Test] + public void WaitForSecondsRealtime_Should_Handle_Initial_State_Correctly() + { + var waitZero = new WaitForSecondsRealtime(0); + var waitPositive = new WaitForSecondsRealtime(1.0); + + Assert.That(waitZero.IsDone, Is.True); + Assert.That(waitPositive.IsDone, Is.False); + } + + /// + /// 验证WaitForSecondsRealtime应该在指定时间后完成 + /// + [Test] + public void WaitForSecondsRealtime_Should_Be_Done_After_Specified_Time() + { + var wait = new WaitForSecondsRealtime(1.0); + + wait.Update(0.5); + Assert.That(wait.IsDone, Is.False); + + wait.Update(0.5); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForSecondsRealtime可以处理负数时间 + /// + [Test] + public void WaitForSecondsRealtime_Should_Handle_Negative_Time() + { + var wait = new WaitForSecondsRealtime(-1.0); + + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForSecondsRealtime多次更新累积时间 + /// + [Test] + public void WaitForSecondsRealtime_Should_Accumulate_Time_Over_Multiple_Updates() + { + var wait = new WaitForSecondsRealtime(2.0); + + wait.Update(0.5); + Assert.That(wait.IsDone, Is.False); + + wait.Update(1.0); + Assert.That(wait.IsDone, Is.False); + + wait.Update(0.5); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForSecondsRealtime实现IYieldInstruction接口 + /// + [Test] + public void WaitForSecondsRealtime_Should_Implement_IYieldInstruction() + { + var wait = new WaitForSecondsRealtime(1.0); + + Assert.That(wait, Is.InstanceOf()); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForSecondsScaledTests.cs b/GFramework.Core.Tests/coroutine/WaitForSecondsScaledTests.cs new file mode 100644 index 00000000..a017fb34 --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForSecondsScaledTests.cs @@ -0,0 +1,80 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForSecondsScaled的单元测试类 +/// +[TestFixture] +public class WaitForSecondsScaledTests +{ + /// + /// 验证WaitForSecondsScaled初始状态根据时间设置 + /// + [Test] + public void WaitForSecondsScaled_Should_Handle_Initial_State_Correctly() + { + var waitZero = new WaitForSecondsScaled(0); + var waitPositive = new WaitForSecondsScaled(1.0); + + Assert.That(waitZero.IsDone, Is.True); + Assert.That(waitPositive.IsDone, Is.False); + } + + /// + /// 验证WaitForSecondsScaled应该在指定时间后完成 + /// + [Test] + public void WaitForSecondsScaled_Should_Be_Done_After_Specified_Time() + { + var wait = new WaitForSecondsScaled(1.0); + + wait.Update(0.5); + Assert.That(wait.IsDone, Is.False); + + wait.Update(0.5); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForSecondsScaled可以处理负数时间 + /// + [Test] + public void WaitForSecondsScaled_Should_Handle_Negative_Time() + { + var wait = new WaitForSecondsScaled(-1.0); + + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForSecondsScaled多次更新累积时间 + /// + [Test] + public void WaitForSecondsScaled_Should_Accumulate_Time_Over_Multiple_Updates() + { + var wait = new WaitForSecondsScaled(2.0); + + wait.Update(0.5); + Assert.That(wait.IsDone, Is.False); + + wait.Update(1.0); + Assert.That(wait.IsDone, Is.False); + + wait.Update(0.5); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForSecondsScaled实现IYieldInstruction接口 + /// + [Test] + public void WaitForSecondsScaled_Should_Implement_IYieldInstruction() + { + var wait = new WaitForSecondsScaled(1.0); + + Assert.That(wait, Is.InstanceOf()); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitUntilOrTimeoutTests.cs b/GFramework.Core.Tests/coroutine/WaitUntilOrTimeoutTests.cs new file mode 100644 index 00000000..c9b04d36 --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitUntilOrTimeoutTests.cs @@ -0,0 +1,109 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitUntilOrTimeout的单元测试类 +/// +[TestFixture] +public class WaitUntilOrTimeoutTests +{ + /// + /// 验证WaitUntilOrTimeout初始状态为未完成 + /// + [Test] + public void WaitUntilOrTimeout_Should_Not_Be_Done_Initially() + { + var condition = false; + var wait = new WaitUntilOrTimeout(() => condition, 5.0); + + Assert.That(wait.IsDone, Is.False); + Assert.That(wait.ConditionMet, Is.False); + Assert.That(wait.IsTimedOut, Is.False); + } + + /// + /// 验证WaitUntilOrTimeout应该在条件满足时完成 + /// + [Test] + public void WaitUntilOrTimeout_Should_Be_Done_When_Condition_Met() + { + var condition = false; + var wait = new WaitUntilOrTimeout(() => condition, 5.0); + + condition = true; + wait.Update(0.1); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.ConditionMet, Is.True); + Assert.That(wait.IsTimedOut, Is.False); + } + + /// + /// 验证WaitUntilOrTimeout应该在超时时完成 + /// + [Test] + public void WaitUntilOrTimeout_Should_Be_Done_When_Timed_Out() + { + var condition = false; + var wait = new WaitUntilOrTimeout(() => condition, 1.0); + + wait.Update(1.5); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.ConditionMet, Is.False); + Assert.That(wait.IsTimedOut, Is.True); + } + + /// + /// 验证WaitUntilOrTimeout可以处理零超时时间 + /// + [Test] + public void WaitUntilOrTimeout_Should_Handle_Zero_Timeout() + { + var condition = false; + var wait = new WaitUntilOrTimeout(() => condition, 0); + + wait.Update(0.1); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimedOut, Is.True); + } + + /// + /// 验证WaitUntilOrTimeout可以处理负数超时时间 + /// + [Test] + public void WaitUntilOrTimeout_Should_Handle_Negative_Timeout() + { + var condition = false; + var wait = new WaitUntilOrTimeout(() => condition, -1.0); + + wait.Update(0.1); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimedOut, Is.True); + } + + /// + /// 验证WaitUntilOrTimeout抛出ArgumentNullException当predicate为null + /// + [Test] + public void WaitUntilOrTimeout_Should_Throw_ArgumentNullException_When_Predicate_Is_Null() + { + Assert.Throws(() => new WaitUntilOrTimeout(null!, 1.0)); + } + + /// + /// 验证WaitUntilOrTimeout实现IYieldInstruction接口 + /// + [Test] + public void WaitUntilOrTimeout_Should_Implement_IYieldInstruction() + { + var wait = new WaitUntilOrTimeout(() => false, 1.0); + + Assert.That(wait, Is.InstanceOf()); + } +} \ No newline at end of file diff --git a/GFramework.Core/coroutine/instructions/WaitForConditionChange.cs b/GFramework.Core/coroutine/instructions/WaitForConditionChange.cs new file mode 100644 index 00000000..0380daf8 --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitForConditionChange.cs @@ -0,0 +1,46 @@ +using GFramework.Core.Abstractions.coroutine; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 等待条件状态发生变化的指令 +/// 当条件从一种状态切换到另一种状态时完成 +/// +/// 获取当前条件状态的函数 +/// 期望转换到的目标状态 +public sealed class WaitForConditionChange(Func conditionGetter, bool waitForTransitionTo) : IYieldInstruction +{ + private readonly Func _conditionGetter = + conditionGetter ?? throw new ArgumentNullException(nameof(conditionGetter)); + + private bool? _initialState; + private bool _isCompleted; + + /// + /// 更新方法,检测条件变化 + /// + /// 时间增量 + public void Update(double deltaTime) + { + if (_isCompleted) + return; + + if (!_initialState.HasValue) + { + _initialState = _conditionGetter(); + return; + } + + // 检查是否发生了期望的状态转换 + var currentState = _conditionGetter(); + if (currentState == waitForTransitionTo && _initialState.Value != waitForTransitionTo) + { + _isCompleted = true; + } + } + + /// + /// 获取等待是否已完成(条件发生了期望的状态转换) + /// + public bool IsDone => _isCompleted; +} \ No newline at end of file diff --git a/GFramework.Core/coroutine/instructions/WaitForEndOfFrame.cs b/GFramework.Core/coroutine/instructions/WaitForEndOfFrame.cs new file mode 100644 index 00000000..f03c48fa --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitForEndOfFrame.cs @@ -0,0 +1,27 @@ +using GFramework.Core.Abstractions.coroutine; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 等待当前帧渲染完成的指令 +/// 通常用于需要在渲染完成后执行的操作 +/// +public sealed class WaitForEndOfFrame : IYieldInstruction +{ + private bool _completed; + + /// + /// 更新方法,在帧末尾被调用 + /// + /// 时间增量 + public void Update(double deltaTime) + { + // 在帧结束时标记完成 + _completed = true; + } + + /// + /// 获取等待是否已完成 + /// + public bool IsDone => _completed; +} \ No newline at end of file diff --git a/GFramework.Core/coroutine/instructions/WaitForFixedUpdate.cs b/GFramework.Core/coroutine/instructions/WaitForFixedUpdate.cs new file mode 100644 index 00000000..a2ebe6c7 --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitForFixedUpdate.cs @@ -0,0 +1,27 @@ +using GFramework.Core.Abstractions.coroutine; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 等待下一个物理固定更新周期的指令 +/// 主要用于需要与物理系统同步的操作 +/// +public sealed class WaitForFixedUpdate : IYieldInstruction +{ + private bool _completed; + + /// + /// 更新方法,在固定更新时被调用 + /// + /// 时间增量 + public void Update(double deltaTime) + { + // 在固定更新周期中标记完成 + _completed = true; + } + + /// + /// 获取等待是否已完成 + /// + public bool IsDone => _completed; +} \ No newline at end of file diff --git a/GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs b/GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs new file mode 100644 index 00000000..e06a7b6d --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs @@ -0,0 +1,99 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.Abstractions.events; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 等待多个事件中的任意一个触发的指令 +/// 实现了 IDisposable 接口,支持资源释放 +/// +/// 第一个事件类型 +/// 第二个事件类型 +public sealed class WaitForMultipleEvents : IYieldInstruction, IDisposable +{ + private bool _disposed; + private volatile bool _done; + private IUnRegister? _unRegister1; + private IUnRegister? _unRegister2; + + /// + /// 初始化 WaitForMultipleEvents 实例 + /// + /// 事件总线实例 + public WaitForMultipleEvents(IEventBus eventBus) + { + var eventBus1 = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + + // 注册两个事件的监听器 + _unRegister1 = eventBus1.Register(OnFirstEvent); + _unRegister2 = eventBus1.Register(OnSecondEvent); + } + + /// + /// 获取第一个事件的数据(如果已触发) + /// + public TEvent1? FirstEventData { get; private set; } + + /// + /// 获取第二个事件的数据(如果已触发) + /// + public TEvent2? SecondEventData { get; private set; } + + /// + /// 获取是哪个事件先触发(1表示第一个事件,2表示第二个事件) + /// + public int TriggeredBy { get; private set; } + + /// + /// 释放资源 + /// + public void Dispose() + { + if (_disposed) return; + + _unRegister1?.UnRegister(); + _unRegister2?.UnRegister(); + _unRegister1 = null; + _unRegister2 = null; + _disposed = true; + } + + /// + /// 获取等待是否已完成 + /// + public bool IsDone => _done; + + /// + /// 更新方法 + /// + /// 时间增量 + public void Update(double deltaTime) + { + if (!_done || (_unRegister1 == null && _unRegister2 == null)) return; + + _unRegister1?.UnRegister(); + _unRegister2?.UnRegister(); + _unRegister1 = null; + _unRegister2 = null; + } + + /// + /// 第一个事件触发处理 + /// + private void OnFirstEvent(TEvent1 eventData) + { + FirstEventData = eventData; + TriggeredBy = 1; + _done = true; + } + + /// + /// 第二个事件触发处理 + /// + private void OnSecondEvent(TEvent2 eventData) + { + SecondEventData = eventData; + TriggeredBy = 2; + _done = true; + } +} \ No newline at end of file diff --git a/GFramework.Core/coroutine/instructions/WaitForNextFrame.cs b/GFramework.Core/coroutine/instructions/WaitForNextFrame.cs new file mode 100644 index 00000000..8f146445 --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitForNextFrame.cs @@ -0,0 +1,26 @@ +using GFramework.Core.Abstractions.coroutine; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 等待下一帧的指令(与WaitOneFrame功能相同,提供另一种命名选择) +/// 用于需要明确表达"等待到下一帧开始"的场景 +/// +public sealed class WaitForNextFrame : IYieldInstruction +{ + private bool _completed; + + /// + /// 更新方法,在下一帧被调用时将完成状态设置为true + /// + /// 时间间隔 + public void Update(double deltaTime) + { + _completed = true; + } + + /// + /// 获取当前等待指令是否已完成 + /// + public bool IsDone => _completed; +} \ No newline at end of file diff --git a/GFramework.Core/coroutine/instructions/WaitForPredicate.cs b/GFramework.Core/coroutine/instructions/WaitForPredicate.cs new file mode 100644 index 00000000..5c77a05e --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitForPredicate.cs @@ -0,0 +1,28 @@ +using GFramework.Core.Abstractions.coroutine; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 通用谓词等待指令 +/// 支持自定义比较逻辑,可以替代 WaitUntil 和 WaitWhile +/// +/// 条件判断函数 +/// true表示等待条件为真时完成,false表示等待条件为假时完成 +public sealed class WaitForPredicate(Func predicate, bool waitForTrue = true) : IYieldInstruction +{ + private readonly Func _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + + /// + /// 更新协程状态 + /// + /// 时间增量 + public void Update(double deltaTime) + { + // 不需要特殊处理时间 + } + + /// + /// 获取协程指令是否已完成 + /// + public bool IsDone => waitForTrue ? _predicate() : !_predicate(); +} \ No newline at end of file diff --git a/GFramework.Core/coroutine/instructions/WaitForSecondsRealtime.cs b/GFramework.Core/coroutine/instructions/WaitForSecondsRealtime.cs new file mode 100644 index 00000000..541d02ab --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitForSecondsRealtime.cs @@ -0,0 +1,30 @@ +using GFramework.Core.Abstractions.coroutine; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 基于真实时间的等待指令(不受时间缩放影响) +/// 适用于需要精确计时的场景,如UI动画、计时器等 +/// +/// 需要等待的秒数 +public sealed class WaitForSecondsRealtime(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/instructions/WaitForSecondsScaled.cs b/GFramework.Core/coroutine/instructions/WaitForSecondsScaled.cs new file mode 100644 index 00000000..2ae6ee4e --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitForSecondsScaled.cs @@ -0,0 +1,30 @@ +using GFramework.Core.Abstractions.coroutine; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 受时间缩放影响的等待指令 +/// 明确表示会受到游戏时间缩放的影响 +/// +/// 需要等待的秒数 +public sealed class WaitForSecondsScaled(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/instructions/WaitUntilOrTimeout.cs b/GFramework.Core/coroutine/instructions/WaitUntilOrTimeout.cs new file mode 100644 index 00000000..b90ce2a4 --- /dev/null +++ b/GFramework.Core/coroutine/instructions/WaitUntilOrTimeout.cs @@ -0,0 +1,40 @@ +using GFramework.Core.Abstractions.coroutine; + +namespace GFramework.Core.coroutine.instructions; + +/// +/// 带超时的条件等待指令 +/// 当条件满足或超时时间到达时完成 +/// +/// 条件判断函数 +/// 超时时间(秒) +public sealed class WaitUntilOrTimeout(Func predicate, double timeoutSeconds) : IYieldInstruction +{ + private readonly Func _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + private readonly double _timeout = Math.Max(0, timeoutSeconds); + private double _elapsedTime; + + /// + /// 获取是否因条件满足而完成 + /// + public bool ConditionMet => _predicate(); + + /// + /// 获取是否因超时而完成 + /// + public bool IsTimedOut => _elapsedTime >= _timeout; + + /// + /// 更新方法,累计时间和检查条件 + /// + /// 时间增量 + public void Update(double deltaTime) + { + _elapsedTime += deltaTime; + } + + /// + /// 获取等待是否已完成(条件满足或超时) + /// + public bool IsDone => ConditionMet || IsTimedOut; +} \ No newline at end of file From a4e22a3a506fc735d7cbd00a90bec3f25a86d874 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 10 Feb 2026 23:27:27 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(coroutine):=20=E4=BF=AE=E5=A4=8D=20Wait?= =?UTF-8?q?ForMultipleEvents=20=E7=9A=84=E4=BA=8B=E4=BB=B6=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了完成状态检查,避免在已完成或释放后继续处理事件 - 立即注销事件监听器以防止内存泄漏 - 在事件触发后清理注册器引用 - 添加了完整的单元测试覆盖各种事件场景 --- .../coroutine/WaitForMultipleEventsTests.cs | 148 ++++++++++++++++++ .../instructions/WaitForMultipleEvents.cs | 18 +++ 2 files changed, 166 insertions(+) create mode 100644 GFramework.Core.Tests/coroutine/WaitForMultipleEventsTests.cs diff --git a/GFramework.Core.Tests/coroutine/WaitForMultipleEventsTests.cs b/GFramework.Core.Tests/coroutine/WaitForMultipleEventsTests.cs new file mode 100644 index 00000000..e059db2a --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForMultipleEventsTests.cs @@ -0,0 +1,148 @@ +using GFramework.Core.Abstractions.events; +using GFramework.Core.coroutine.instructions; +using GFramework.Core.events; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine +{ + [TestFixture] + public class WaitForMultipleEventsTests + { + [SetUp] + public void SetUp() + { + eventBus = new EventBus(); + } + + [TearDown] + public void TearDown() + { + (eventBus as IDisposable)?.Dispose(); + } + + private IEventBus eventBus; + + [Test] + public void Constructor_RegistersBothEventTypes() + { + // Arrange & Act + var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); + + // Assert + Assert.That(waitForMultipleEvents.IsDone, Is.False); + Assert.That(waitForMultipleEvents.TriggeredBy, Is.EqualTo(0)); + } + + [Test] + public Task FirstEventWins_WhenBothEventsFired() + { + // Arrange + var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); + + // Act + eventBus.Send(new TestEvent1 { Data = "first_event" }); + eventBus.Send(new TestEvent2 { Data = "second_event" }); + + // Assert + Assert.That(waitForMultipleEvents.IsDone, Is.True); + Assert.That(waitForMultipleEvents.TriggeredBy, Is.EqualTo(1)); // First event should win + Assert.That(waitForMultipleEvents.FirstEventData?.Data, Is.EqualTo("first_event")); + Assert.That(waitForMultipleEvents.SecondEventData, Is.Null); + return Task.CompletedTask; + } + + [Test] + public Task SecondEventWins_WhenOnlySecondEventFired() + { + // Arrange + var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); + + // Act + eventBus.Send(new TestEvent2 { Data = "second_event" }); + + // Assert + Assert.That(waitForMultipleEvents.IsDone, Is.True); + Assert.That(waitForMultipleEvents.TriggeredBy, Is.EqualTo(2)); // Second event should win + Assert.That(waitForMultipleEvents.SecondEventData?.Data, Is.EqualTo("second_event")); + Assert.That(waitForMultipleEvents.FirstEventData, Is.Null); + return Task.CompletedTask; + } + + [Test] + public Task FirstEventWins_WhenBothEventsFiredInReverseOrder() + { + // Arrange + var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); + + // Act + eventBus.Send(new TestEvent2 { Data = "second_event" }); + eventBus.Send(new TestEvent1 { Data = "first_event" }); + + // Assert + Assert.That(waitForMultipleEvents.IsDone, Is.True); + // Second event should win because it fired first and set _done = true + Assert.That(waitForMultipleEvents.TriggeredBy, + Is.EqualTo(2)); // Second event actually won since it fired first + Assert.That(waitForMultipleEvents.SecondEventData?.Data, Is.EqualTo("second_event")); + Assert.That(waitForMultipleEvents.FirstEventData, Is.Null); + return Task.CompletedTask; + } + + [Test] + public Task MultipleEvents_AfterCompletion_DoNotOverrideState() + { + // Arrange + var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); + + // Act - Fire first event + eventBus.Send(new TestEvent1 { Data = "first_event" }); + + // Verify first event was processed + Assert.That(waitForMultipleEvents.IsDone, Is.True); + Assert.That(waitForMultipleEvents.TriggeredBy, Is.EqualTo(1)); + Assert.That(waitForMultipleEvents.FirstEventData?.Data, Is.EqualTo("first_event")); + + // Fire second event after completion + eventBus.Send(new TestEvent2 { Data = "second_event" }); + + // Assert - The state should not change + Assert.That(waitForMultipleEvents.IsDone, Is.True); + Assert.That(waitForMultipleEvents.TriggeredBy, Is.EqualTo(1)); // Should remain as 1, not change to 2 + Assert.That(waitForMultipleEvents.FirstEventData?.Data, + Is.EqualTo("first_event")); // Should remain unchanged + Assert.That(waitForMultipleEvents.SecondEventData, Is.Null); // Should remain null + return Task.CompletedTask; + } + + [Test] + public Task Disposal_PreventsFurtherEventHandling() + { + // Arrange + var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); + + // Act - Dispose the instance + waitForMultipleEvents.Dispose(); + + // Fire an event after disposal + eventBus.Send(new TestEvent1 { Data = "after_disposal" }); + + // Assert - Event should not be processed due to disposal + // Since we disposed, no event data should be captured + Assert.That(waitForMultipleEvents.FirstEventData, Is.Null); + Assert.That(waitForMultipleEvents.IsDone, Is.False); // Should remain false after disposal + + return Task.CompletedTask; + } + + // Test event classes + private class TestEvent1 + { + public string Data { get; init; } = string.Empty; + } + + private class TestEvent2 + { + public string Data { get; init; } = string.Empty; + } + } +} \ No newline at end of file diff --git a/GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs b/GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs index e06a7b6d..3b5a7c7d 100644 --- a/GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs +++ b/GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs @@ -82,9 +82,18 @@ public void Update(double deltaTime) /// private void OnFirstEvent(TEvent1 eventData) { + // 如果已经完成或者被释放,则直接返回 + if (_done || _disposed) return; + FirstEventData = eventData; TriggeredBy = 1; _done = true; + + // 立即注销事件监听器 + _unRegister1?.UnRegister(); + _unRegister2?.UnRegister(); + _unRegister1 = null; + _unRegister2 = null; } /// @@ -92,8 +101,17 @@ private void OnFirstEvent(TEvent1 eventData) /// private void OnSecondEvent(TEvent2 eventData) { + // 如果已经完成或者被释放,则直接返回 + if (_done || _disposed) return; + SecondEventData = eventData; TriggeredBy = 2; _done = true; + + // 立即注销事件监听器 + _unRegister1?.UnRegister(); + _unRegister2?.UnRegister(); + _unRegister1 = null; + _unRegister2 = null; } } \ No newline at end of file From 62390c43ab3a0b0dfae9f042bd8efb603a566059 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 10 Feb 2026 23:35:14 +0800 Subject: [PATCH 3/3] =?UTF-8?q?test(coroutine):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=8D=8F=E7=A8=8B=E6=8C=87=E4=BB=A4=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=B9=B6=E6=94=B9=E8=BF=9B=E7=8E=B0=E6=9C=89=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 Delay 指令添加完整的单元测试覆盖各种时间情况 - 为 WaitForCoroutine 指令添加单元测试验证协程等待功能 - 为 WaitForFrames 指令添加单元测试覆盖帧计数逻辑 - 为 WaitForTask 指令添加单元测试包括异常处理场景 - 为 WaitOneFrame 指令添加单元测试验证单帧等待 - 为 WaitUntil 和 WaitWhile 指令添加单元测试覆盖谓词逻辑 - 将 WaitForMultipleEventsTests 中的异步方法标记为 async Task 类型 - 修改测试事件类的 Data 属性为可变的 set 访问器而不是只读 init - 优化 WaitForMultipleEventsTests 中的断言注释描述 --- GFramework.Core.Tests/coroutine/DelayTests.cs | 66 +++++++++++++ .../coroutine/WaitForCoroutineTests.cs | 42 ++++++++ .../coroutine/WaitForFramesTests.cs | 74 ++++++++++++++ .../coroutine/WaitForMultipleEventsTests.cs | 25 ++--- .../coroutine/WaitForTaskTTests.cs | 96 +++++++++++++++++++ .../coroutine/WaitOneFrameTests.cs | 46 +++++++++ .../coroutine/WaitUntilTests.cs | 55 +++++++++++ .../coroutine/WaitWhileTests.cs | 66 +++++++++++++ 8 files changed, 454 insertions(+), 16 deletions(-) create mode 100644 GFramework.Core.Tests/coroutine/DelayTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForCoroutineTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForFramesTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitForTaskTTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitOneFrameTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitUntilTests.cs create mode 100644 GFramework.Core.Tests/coroutine/WaitWhileTests.cs diff --git a/GFramework.Core.Tests/coroutine/DelayTests.cs b/GFramework.Core.Tests/coroutine/DelayTests.cs new file mode 100644 index 00000000..e3ff5dd5 --- /dev/null +++ b/GFramework.Core.Tests/coroutine/DelayTests.cs @@ -0,0 +1,66 @@ +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine +{ + [TestFixture] + public class DelayTests + { + [Test] + public void Constructor_SetsInitialRemainingTime() + { + // Arrange & Act + var delay = new Delay(2.5); + + // Assert + Assert.That(delay.IsDone, Is.False); + } + + [Test] + public void Update_ReducesRemainingTime() + { + // Arrange + var delay = new Delay(2.0); + + // Act + delay.Update(0.5); + + // Assert + Assert.That(delay.IsDone, Is.False); + } + + [Test] + public void Update_MultipleTimes_EventuallyCompletes() + { + // Arrange + var delay = new Delay(1.0); + + // Act + delay.Update(0.5); + delay.Update(0.6); // Total: 1.1 > 1.0, so should be done + + // Assert + Assert.That(delay.IsDone, Is.True); + } + + [Test] + public void NegativeTime_TreatedAsZero() + { + // Arrange & Act + var delay = new Delay(-1.0); + + // Assert + Assert.That(delay.IsDone, Is.True); + } + + [Test] + public void ZeroTime_CompletesImmediately() + { + // Arrange & Act + var delay = new Delay(0.0); + + // Assert + Assert.That(delay.IsDone, Is.True); + } + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForCoroutineTests.cs b/GFramework.Core.Tests/coroutine/WaitForCoroutineTests.cs new file mode 100644 index 00000000..768072dc --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForCoroutineTests.cs @@ -0,0 +1,42 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine +{ + [TestFixture] + public class WaitForCoroutineTests + { + [Test] + public void Constructor_CreatesInstance() + { + // Arrange + var coroutine = CreateDummyCoroutine(); + + // Act + var waitForCoroutine = new WaitForCoroutine(coroutine); + + // Assert + Assert.That(waitForCoroutine.IsDone, Is.False); + } + + [Test] + public void Update_DoesNotChangeState() + { + // Arrange + var coroutine = CreateDummyCoroutine(); + var waitForCoroutine = new WaitForCoroutine(coroutine); + + // Act + waitForCoroutine.Update(1.0); + + // Assert + Assert.That(waitForCoroutine.IsDone, Is.False); + } + + private static IEnumerator CreateDummyCoroutine() + { + return new List().GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForFramesTests.cs b/GFramework.Core.Tests/coroutine/WaitForFramesTests.cs new file mode 100644 index 00000000..43c2a682 --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForFramesTests.cs @@ -0,0 +1,74 @@ +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine +{ + [TestFixture] + public class WaitForFramesTests + { + [Test] + public void Constructor_SetsInitialFrameCount() + { + // Arrange & Act + var waitForFrames = new WaitForFrames(3); + + // Assert + Assert.That(waitForFrames.IsDone, Is.False); + } + + [Test] + public void Update_ReducesFrameCount() + { + // Arrange + var waitForFrames = new WaitForFrames(2); + + // Act + waitForFrames.Update(0.1); + + // Assert + Assert.That(waitForFrames.IsDone, Is.False); + } + + [Test] + public void MultipleUpdates_EventuallyCompletes() + { + // Arrange + var waitForFrames = new WaitForFrames(2); + + // Act + waitForFrames.Update(0.1); // 2-1 = 1 frame remaining + waitForFrames.Update(0.1); // 1-1 = 0 frames remaining + + // Assert + Assert.That(waitForFrames.IsDone, Is.True); + } + + [Test] + public void NegativeFrameCount_TreatedAsOne() + { + // Arrange & Act + var waitForFrames = new WaitForFrames(-1); + + // Assert + Assert.That(waitForFrames.IsDone, Is.False); + + // One update should complete it + waitForFrames.Update(0.1); + Assert.That(waitForFrames.IsDone, Is.True); + } + + [Test] + public void ZeroFrameCount_TreatedAsOne() + { + // Arrange & Act + var waitForFrames = new WaitForFrames(0); + + // Assert + Assert.That(waitForFrames.IsDone, Is.False); + + // One update should complete it + waitForFrames.Update(0.1); + Assert.That(waitForFrames.IsDone, Is.True); + } + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForMultipleEventsTests.cs b/GFramework.Core.Tests/coroutine/WaitForMultipleEventsTests.cs index e059db2a..8bc30132 100644 --- a/GFramework.Core.Tests/coroutine/WaitForMultipleEventsTests.cs +++ b/GFramework.Core.Tests/coroutine/WaitForMultipleEventsTests.cs @@ -34,7 +34,7 @@ public void Constructor_RegistersBothEventTypes() } [Test] - public Task FirstEventWins_WhenBothEventsFired() + public async Task FirstEventWins_WhenBothEventsFired() { // Arrange var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); @@ -48,11 +48,10 @@ public Task FirstEventWins_WhenBothEventsFired() Assert.That(waitForMultipleEvents.TriggeredBy, Is.EqualTo(1)); // First event should win Assert.That(waitForMultipleEvents.FirstEventData?.Data, Is.EqualTo("first_event")); Assert.That(waitForMultipleEvents.SecondEventData, Is.Null); - return Task.CompletedTask; } [Test] - public Task SecondEventWins_WhenOnlySecondEventFired() + public async Task SecondEventWins_WhenOnlySecondEventFired() { // Arrange var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); @@ -65,11 +64,10 @@ public Task SecondEventWins_WhenOnlySecondEventFired() Assert.That(waitForMultipleEvents.TriggeredBy, Is.EqualTo(2)); // Second event should win Assert.That(waitForMultipleEvents.SecondEventData?.Data, Is.EqualTo("second_event")); Assert.That(waitForMultipleEvents.FirstEventData, Is.Null); - return Task.CompletedTask; } [Test] - public Task FirstEventWins_WhenBothEventsFiredInReverseOrder() + public async Task FirstEventWins_WhenBothEventsFiredInReverseOrder() { // Arrange var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); @@ -80,16 +78,14 @@ public Task FirstEventWins_WhenBothEventsFiredInReverseOrder() // Assert Assert.That(waitForMultipleEvents.IsDone, Is.True); - // Second event should win because it fired first and set _done = true - Assert.That(waitForMultipleEvents.TriggeredBy, - Is.EqualTo(2)); // Second event actually won since it fired first + // Actually, the second event should win because it fired first and set _done = true + Assert.That(waitForMultipleEvents.TriggeredBy, Is.EqualTo(2)); // Second event wins since it fired first Assert.That(waitForMultipleEvents.SecondEventData?.Data, Is.EqualTo("second_event")); Assert.That(waitForMultipleEvents.FirstEventData, Is.Null); - return Task.CompletedTask; } [Test] - public Task MultipleEvents_AfterCompletion_DoNotOverrideState() + public async Task MultipleEvents_AfterCompletion_DoNotOverrideState() { // Arrange var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); @@ -111,11 +107,10 @@ public Task MultipleEvents_AfterCompletion_DoNotOverrideState() Assert.That(waitForMultipleEvents.FirstEventData?.Data, Is.EqualTo("first_event")); // Should remain unchanged Assert.That(waitForMultipleEvents.SecondEventData, Is.Null); // Should remain null - return Task.CompletedTask; } [Test] - public Task Disposal_PreventsFurtherEventHandling() + public async Task Disposal_PreventsFurtherEventHandling() { // Arrange var waitForMultipleEvents = new WaitForMultipleEvents(eventBus); @@ -130,19 +125,17 @@ public Task Disposal_PreventsFurtherEventHandling() // Since we disposed, no event data should be captured Assert.That(waitForMultipleEvents.FirstEventData, Is.Null); Assert.That(waitForMultipleEvents.IsDone, Is.False); // Should remain false after disposal - - return Task.CompletedTask; } // Test event classes private class TestEvent1 { - public string Data { get; init; } = string.Empty; + public string Data { get; set; } = string.Empty; } private class TestEvent2 { - public string Data { get; init; } = string.Empty; + public string Data { get; set; } = string.Empty; } } } \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForTaskTTests.cs b/GFramework.Core.Tests/coroutine/WaitForTaskTTests.cs new file mode 100644 index 00000000..ab446cda --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForTaskTTests.cs @@ -0,0 +1,96 @@ +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine +{ + [TestFixture] + public class WaitForTaskTTests + { + [Test] + public void Constructor_WithNullTask_ThrowsArgumentNullException() + { + // Act & Assert + Assert.Throws(() => new WaitForTask(null!)); + } + + [Test] + public async Task Constructor_WithCompletedTask_IsDoneImmediately() + { + // Arrange + var completedTask = Task.FromResult("test"); + + // Act + var waitForTask = new WaitForTask(completedTask); + + // Assert + Assert.That(waitForTask.IsDone, Is.True); + Assert.That(waitForTask.Result, Is.EqualTo("test")); + } + + [Test] + public async Task Constructor_WithIncompleteTask_IsNotDoneInitially() + { + // Arrange + var tcs = new TaskCompletionSource(); + var incompleteTask = tcs.Task; + + // Act + var waitForTask = new WaitForTask(incompleteTask); + + // Assert + Assert.That(waitForTask.IsDone, Is.False); + } + + [Test] + public async Task TaskCompletes_CallbackSetsDoneFlag() + { + // Arrange + var tcs = new TaskCompletionSource(); + var task = tcs.Task; + var waitForTask = new WaitForTask(task); + + // Assert initial state + Assert.That(waitForTask.IsDone, Is.False); + + // Act + tcs.SetResult("completed"); + await Task.Delay(10); // Allow time for continuation + + // Assert final state + Assert.That(waitForTask.IsDone, Is.True); + Assert.That(waitForTask.Result, Is.EqualTo("completed")); + } + + [Test] + public async Task Update_DoesNotChangeState() + { + // Arrange + var completedTask = Task.FromResult("test"); + var waitForTask = new WaitForTask(completedTask); + + // Act + waitForTask.Update(0.1); + + // Assert + Assert.That(waitForTask.IsDone, Is.True); + } + + [Test] + public async Task TaskWithException_HoldsException() + { + // Arrange + var tcs = new TaskCompletionSource(); + var task = tcs.Task; + var waitForTask = new WaitForTask(task); + + // Act + tcs.SetException(new InvalidOperationException("Test exception")); + await Task.Delay(10); // Allow time for continuation + + // Assert + Assert.That(waitForTask.IsDone, Is.True); + Assert.That(waitForTask.Exception, Is.Not.Null); + Assert.That(waitForTask.Exception?.InnerException, Is.TypeOf()); + } + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitOneFrameTests.cs b/GFramework.Core.Tests/coroutine/WaitOneFrameTests.cs new file mode 100644 index 00000000..1f2af7ad --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitOneFrameTests.cs @@ -0,0 +1,46 @@ +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine +{ + [TestFixture] + public class WaitOneFrameTests + { + [Test] + public void Constructor_CreatesInstance() + { + // Act + var waitOneFrame = new WaitOneFrame(); + + // Assert + Assert.That(waitOneFrame.IsDone, Is.False); + } + + [Test] + public void Update_MakesItDone() + { + // Arrange + var waitOneFrame = new WaitOneFrame(); + + // Act + waitOneFrame.Update(0.1); + + // Assert + Assert.That(waitOneFrame.IsDone, Is.True); + } + + [Test] + public void Update_MultipleTimes_RemainsDone() + { + // Arrange + var waitOneFrame = new WaitOneFrame(); + + // Act + waitOneFrame.Update(0.1); + waitOneFrame.Update(0.1); + + // Assert + Assert.That(waitOneFrame.IsDone, Is.True); + } + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitUntilTests.cs b/GFramework.Core.Tests/coroutine/WaitUntilTests.cs new file mode 100644 index 00000000..47fffb77 --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitUntilTests.cs @@ -0,0 +1,55 @@ +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine +{ + [TestFixture] + public class WaitUntilTests + { + [Test] + public void Constructor_WithNullPredicate_ThrowsArgumentNullException() + { + // Act & Assert + Assert.Throws(() => new WaitUntil(null!)); + } + + [Test] + public void IsDone_ReturnsPredicateResult_True() + { + // Arrange + var condition = false; + var waitUntil = new WaitUntil(() => condition); + + // Act + condition = true; + + // Assert + Assert.That(waitUntil.IsDone, Is.True); + } + + [Test] + public void IsDone_ReturnsPredicateResult_False() + { + // Arrange + var condition = false; + var waitUntil = new WaitUntil(() => condition); + + // Assert + Assert.That(waitUntil.IsDone, Is.False); + } + + [Test] + public void Update_DoesNotChangeState() + { + // Arrange + var condition = false; + var waitUntil = new WaitUntil(() => condition); + + // Act + waitUntil.Update(0.1); + + // Assert + Assert.That(waitUntil.IsDone, Is.False); + } + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitWhileTests.cs b/GFramework.Core.Tests/coroutine/WaitWhileTests.cs new file mode 100644 index 00000000..9829b7ed --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitWhileTests.cs @@ -0,0 +1,66 @@ +using GFramework.Core.coroutine.instructions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine +{ + [TestFixture] + public class WaitWhileTests + { + [Test] + public void Constructor_WithNullPredicate_ThrowsArgumentNullException() + { + // Act & Assert + Assert.Throws(() => new WaitWhile(null!)); + } + + [Test] + public void IsDone_ReturnsInverseOfPredicateResult_True() + { + // Arrange + var condition = true; + var waitWhile = new WaitWhile(() => condition); + + // Act + condition = false; + + // Assert + Assert.That(waitWhile.IsDone, Is.True); + } + + [Test] + public void IsDone_ReturnsInverseOfPredicateResult_False() + { + // Arrange + var condition = false; + var waitWhile = new WaitWhile(() => condition); + + // Assert + Assert.That(waitWhile.IsDone, Is.True); // Because !false = true + } + + [Test] + public void IsDone_WhenPredicateReturnsTrue() + { + // Arrange + var condition = true; + var waitWhile = new WaitWhile(() => condition); + + // Assert + Assert.That(waitWhile.IsDone, Is.False); // Because !true = false + } + + [Test] + public void Update_DoesNotChangeState() + { + // Arrange + var condition = true; + var waitWhile = new WaitWhile(() => condition); + + // Act + waitWhile.Update(0.1); + + // Assert + Assert.That(waitWhile.IsDone, Is.False); + } + } +} \ No newline at end of file