fix(coroutine): 修复 WaitForMultipleEvents 的事件处理逻辑

- 添加了完成状态检查,避免在已完成或释放后继续处理事件
- 立即注销事件监听器以防止内存泄漏
- 在事件触发后清理注册器引用
- 添加了完整的单元测试覆盖各种事件场景
This commit is contained in:
GeWuYou 2026-02-10 23:27:27 +08:00 committed by gewuyou
parent 4748198696
commit 42a1ab0f29
2 changed files with 166 additions and 0 deletions

View File

@ -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<TestEvent1, TestEvent2>(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<TestEvent1, TestEvent2>(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<TestEvent1, TestEvent2>(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<TestEvent1, TestEvent2>(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<TestEvent1, TestEvent2>(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<TestEvent1, TestEvent2>(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;
}
}
}

View File

@ -82,9 +82,18 @@ public sealed class WaitForMultipleEvents<TEvent1, TEvent2> : IYieldInstruction,
/// </summary>
private void OnFirstEvent(TEvent1 eventData)
{
// 如果已经完成或者被释放,则直接返回
if (_done || _disposed) return;
FirstEventData = eventData;
TriggeredBy = 1;
_done = true;
// 立即注销事件监听器
_unRegister1?.UnRegister();
_unRegister2?.UnRegister();
_unRegister1 = null;
_unRegister2 = null;
}
/// <summary>
@ -92,8 +101,17 @@ public sealed class WaitForMultipleEvents<TEvent1, TEvent2> : IYieldInstruction,
/// </summary>
private void OnSecondEvent(TEvent2 eventData)
{
// 如果已经完成或者被释放,则直接返回
if (_done || _disposed) return;
SecondEventData = eventData;
TriggeredBy = 2;
_done = true;
// 立即注销事件监听器
_unRegister1?.UnRegister();
_unRegister2?.UnRegister();
_unRegister1 = null;
_unRegister2 = null;
}
}