Skip to content

feat(coroutine): 添加多种协程等待指令及对应单元测试#13

Merged
GeWuYou merged 3 commits into
mainfrom
feature/coroutine-wait-instructions
Feb 10, 2026
Merged

feat(coroutine): 添加多种协程等待指令及对应单元测试#13
GeWuYou merged 3 commits into
mainfrom
feature/coroutine-wait-instructions

Conversation

@GeWuYou
Copy link
Copy Markdown
Owner

@GeWuYou GeWuYou commented Feb 10, 2026

  • 实现 WaitForConditionChange 指令,支持等待条件状态变化
  • 实现 WaitForEndOfFrame 指令,支持等待当前帧渲染完成
  • 实现 WaitForFixedUpdate 指令,支持等待物理固定更新周期
  • 实现 WaitForMultipleEvents 指令,支持等待多个事件中的任意一个触发
  • 实现 WaitForNextFrame 指令,支持等待下一帧开始
  • 实现 WaitForPredicate 指令,支持通用谓词等待功能
  • 实现 WaitForSecondsRealtime 指令,支持基于真实时间的等待
  • 实现 WaitForSecondsScaled 指令,支持受时间缩放影响的等待
  • 实现 WaitUntilOrTimeout 指令,支持带超时的条件等待
  • 为所有新指令添加完整的单元测试覆盖

Summary by Sourcery

Add a set of coroutine waiting instructions covering frame, time, predicate, condition-change, timeout, and multi-event scenarios, along with corresponding unit test coverage.

New Features:

  • Introduce WaitForConditionChange to complete when a boolean condition transitions to a target state.
  • Introduce WaitUntilOrTimeout to wait for a predicate with a configurable timeout result.
  • Introduce WaitForPredicate as a generic predicate-based coroutine wait that can represent WaitUntil/WaitWhile semantics.
  • Introduce WaitForSecondsRealtime and WaitForSecondsScaled to support real-time and time-scaled duration waits in coroutines.
  • Introduce WaitForEndOfFrame, WaitForFixedUpdate, and WaitForNextFrame to synchronize coroutines with different frame phases.
  • Introduce WaitForMultipleEvents to await the first of two events from the event bus and capture which fired.

Tests:

  • Add comprehensive unit tests for all new coroutine wait instructions to validate completion semantics, edge cases, and interface conformance.

- 实现 WaitForConditionChange 指令,支持等待条件状态变化
- 实现 WaitForEndOfFrame 指令,支持等待当前帧渲染完成
- 实现 WaitForFixedUpdate 指令,支持等待物理固定更新周期
- 实现 WaitForMultipleEvents 指令,支持等待多个事件中的任意一个触发
- 实现 WaitForNextFrame 指令,支持等待下一帧开始
- 实现 WaitForPredicate 指令,支持通用谓词等待功能
- 实现 WaitForSecondsRealtime 指令,支持基于真实时间的等待
- 实现 WaitForSecondsScaled 指令,支持受时间缩放影响的等待
- 实现 WaitUntilOrTimeout 指令,支持带超时的条件等待
- 为所有新指令添加完整的单元测试覆盖
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Feb 10, 2026

Reviewer's Guide

Adds a set of coroutine yield instructions for various waiting scenarios (condition changes, predicates, next frame/fixed update/end-of-frame, timed waits, multi-event waits) along with comprehensive NUnit unit tests for each instruction.

Sequence diagram for WaitForMultipleEvents responding to event bus

sequenceDiagram
    participant CoroutineScheduler
    participant WaitForMultipleEvents
    participant EventBus as IEventBus
    participant Publisher

    CoroutineScheduler->>WaitForMultipleEvents: new WaitForMultipleEvents(eventBus)
    WaitForMultipleEvents->>EventBus: Register~TEvent1~(OnFirstEvent)
    WaitForMultipleEvents->>EventBus: Register~TEvent2~(OnSecondEvent)

    Publisher-->>EventBus: Publish TEvent1(eventData)
    EventBus-->>WaitForMultipleEvents: OnFirstEvent(eventData)
    activate WaitForMultipleEvents
    WaitForMultipleEvents-->WaitForMultipleEvents: store FirstEventData
    WaitForMultipleEvents-->WaitForMultipleEvents: set TriggeredBy = 1
    WaitForMultipleEvents-->WaitForMultipleEvents: set _done = true
    deactivate WaitForMultipleEvents

    loop game_loop
        CoroutineScheduler->>WaitForMultipleEvents: Update(deltaTime)
        alt IsDone and handlers_not_unregistered
            WaitForMultipleEvents->>EventBus: _unRegister1.UnRegister()
            WaitForMultipleEvents->>EventBus: _unRegister2.UnRegister()
        end
    end
Loading

Class diagram for new coroutine time-based wait instructions

classDiagram
    class IYieldInstruction {
        <<interface>>
        +bool IsDone
        +void Update(double deltaTime)
    }

    class WaitForSecondsRealtime {
        -double _remaining
        +WaitForSecondsRealtime(double seconds)
        +void Update(double deltaTime)
        +bool IsDone
    }

    class WaitForSecondsScaled {
        -double _remaining
        +WaitForSecondsScaled(double seconds)
        +void Update(double deltaTime)
        +bool IsDone
    }

    class WaitForEndOfFrame {
        -bool _completed
        +WaitForEndOfFrame()
        +void Update(double deltaTime)
        +bool IsDone
    }

    class WaitForFixedUpdate {
        -bool _completed
        +WaitForFixedUpdate()
        +void Update(double deltaTime)
        +bool IsDone
    }

    class WaitForNextFrame {
        -bool _completed
        +WaitForNextFrame()
        +void Update(double deltaTime)
        +bool IsDone
    }

    IYieldInstruction <|.. WaitForSecondsRealtime
    IYieldInstruction <|.. WaitForSecondsScaled
    IYieldInstruction <|.. WaitForEndOfFrame
    IYieldInstruction <|.. WaitForFixedUpdate
    IYieldInstruction <|.. WaitForNextFrame
Loading

Class diagram for new coroutine predicate and condition wait instructions

classDiagram
    class IYieldInstruction {
        <<interface>>
        +bool IsDone
        +void Update(double deltaTime)
    }

    class WaitForPredicate {
        -Func~bool~ _predicate
        +WaitForPredicate(Func~bool~ predicate, bool waitForTrue)
        +void Update(double deltaTime)
        +bool IsDone
    }

    class WaitForConditionChange {
        -Func~bool~ _conditionGetter
        -bool _isCompleted
        -bool _initialState
        +WaitForConditionChange(Func~bool~ conditionGetter, bool waitForTransitionTo)
        +void Update(double deltaTime)
        +bool IsDone
    }

    class WaitUntilOrTimeout {
        -Func~bool~ _predicate
        -double _timeout
        -double _elapsedTime
        +WaitUntilOrTimeout(Func~bool~ predicate, double timeoutSeconds)
        +bool ConditionMet
        +bool IsTimedOut
        +void Update(double deltaTime)
        +bool IsDone
    }

    IYieldInstruction <|.. WaitForPredicate
    IYieldInstruction <|.. WaitForConditionChange
    IYieldInstruction <|.. WaitUntilOrTimeout
Loading

Class diagram for WaitForMultipleEvents coroutine instruction

classDiagram
    class IYieldInstruction {
        <<interface>>
        +bool IsDone
        +void Update(double deltaTime)
    }

    class IDisposable {
        <<interface>>
        +void Dispose()
    }

    class IUnRegister {
        <<interface>>
        +void UnRegister()
    }

    class IEventBus {
        <<interface>>
        +IUnRegister Register~TEvent~(Action~TEvent~ handler)
    }

    class WaitForMultipleEvents~TEvent1,TEvent2~ {
        -bool _disposed
        -bool _done
        -IUnRegister _unRegister1
        -IUnRegister _unRegister2
        +WaitForMultipleEvents(IEventBus eventBus)
        +TEvent1 FirstEventData
        +TEvent2 SecondEventData
        +int TriggeredBy
        +bool IsDone
        +void Update(double deltaTime)
        +void Dispose()
        -void OnFirstEvent(TEvent1 eventData)
        -void OnSecondEvent(TEvent2 eventData)
    }

    IYieldInstruction <|.. WaitForMultipleEvents
    IDisposable <|.. WaitForMultipleEvents

    IEventBus --> IUnRegister : uses
    WaitForMultipleEvents --> IEventBus : registers_handlers
    WaitForMultipleEvents --> IUnRegister : holds
Loading

File-Level Changes

Change Details Files
Introduce WaitForConditionChange instruction to complete when a boolean condition transitions to a specified target state.
  • Store initial condition state on first Update call and only complete when the condition transitions from its initial value to the target state.
  • Validate the condition getter is non-null and expose completion through the IYieldInstruction IsDone property.
  • Add NUnit tests covering initial state, false→true and true→false transitions, ignoring no-op updates, ignoring subsequent transitions after completion, null argument validation, and interface implementation.
GFramework.Core/coroutine/instructions/WaitForConditionChange.cs
GFramework.Core.Tests/coroutine/WaitForConditionChangeTests.cs
Introduce WaitUntilOrTimeout instruction supporting predicate-based waits with timeout semantics.
  • Wrap the predicate with null checking and clamp timeoutSeconds to be non-negative.
  • Accumulate elapsed time in Update and expose ConditionMet and IsTimedOut flags, with IsDone true when either is satisfied.
  • Add NUnit tests for initial state, completion via condition, completion via timeout including zero/negative timeouts, null predicate handling, and interface implementation.
GFramework.Core/coroutine/instructions/WaitUntilOrTimeout.cs
GFramework.Core.Tests/coroutine/WaitUntilOrTimeoutTests.cs
Introduce time-based wait instructions for real-time and scaled-time delays.
  • Implement WaitForSecondsRealtime with a non-negative remaining timer decremented by Update and completed when remaining <= 0, independent of time scaling.
  • Implement WaitForSecondsScaled with identical behavior but semantically representing scaled-time waits.
  • Add NUnit tests validating initial state for zero/positive durations, completion after cumulative updates, handling of negative durations, and IYieldInstruction implementation for both classes.
GFramework.Core/coroutine/instructions/WaitForSecondsRealtime.cs
GFramework.Core/coroutine/instructions/WaitForSecondsScaled.cs
GFramework.Core.Tests/coroutine/WaitForSecondsRealtimeTests.cs
GFramework.Core.Tests/coroutine/WaitForSecondsScaledTests.cs
Introduce simple frame-synchronization instructions for next frame, end-of-frame, and fixed-update waits.
  • Implement WaitForNextFrame, WaitForEndOfFrame, and WaitForFixedUpdate as single-frame instructions that flip an internal completion flag on the first Update call and remain completed thereafter.
  • Expose completion via IsDone without using deltaTime semantics.
  • Add NUnit tests verifying initial not-done state, completion after first Update, idempotent behavior across multiple updates, and IYieldInstruction implementation for each instruction.
GFramework.Core/coroutine/instructions/WaitForNextFrame.cs
GFramework.Core/coroutine/instructions/WaitForEndOfFrame.cs
GFramework.Core/coroutine/instructions/WaitForFixedUpdate.cs
GFramework.Core.Tests/coroutine/WaitForNextFrameTests.cs
GFramework.Core.Tests/coroutine/WaitForEndOfFrameTests.cs
GFramework.Core.Tests/coroutine/WaitForFixedUpdateTests.cs
Introduce WaitForPredicate instruction as a generic predicate-based wait abstraction.
  • Store a non-null predicate and a waitForTrue flag and implement IsDone to either wait for predicate==true or predicate==false.
  • Implement a no-op Update since time is not used, relying on polling via IsDone to repeatedly evaluate the predicate.
  • Add NUnit tests verifying default wait-for-true semantics, explicit wait-for-false behavior, repeated evaluations across Update calls, null predicate argument validation, and IYieldInstruction implementation.
GFramework.Core/coroutine/instructions/WaitForPredicate.cs
GFramework.Core.Tests/coroutine/WaitForPredicateTests.cs
Introduce WaitForMultipleEvents instruction to wait for any of two event types on an event bus, with resource cleanup support.
  • Register handlers for two event types on an injected IEventBus, capturing IUnRegister handles and throwing if the event bus is null.
  • Track which event fired first via TriggeredBy and store the associated FirstEventData or SecondEventData, setting an internal done flag.
  • Implement IDisposable and an Update method that, once done, unregisters event handlers and clears references, ensuring handlers are removed either via Dispose or after completion.
  • Expose completion state via IYieldInstruction IsDone property.
GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In WaitForMultipleEvents, consider short‑circuiting the event handlers when _done or _disposed is already true so that a second event firing cannot overwrite TriggeredBy and the captured event data intended to represent “the first event that arrived.”
  • For WaitForMultipleEvents, you might want to unregister from the event bus immediately inside the first event handler (or via a shared completion method) instead of deferring unregistration to the next Update call, so that handlers are released even if the coroutine is never updated again after completion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In WaitForMultipleEvents, consider short‑circuiting the event handlers when `_done` or `_disposed` is already true so that a second event firing cannot overwrite `TriggeredBy` and the captured event data intended to represent “the first event that arrived.”
- For WaitForMultipleEvents, you might want to unregister from the event bus immediately inside the first event handler (or via a shared completion method) instead of deferring unregistration to the next `Update` call, so that handlers are released even if the coroutine is never updated again after completion.

## Individual Comments

### Comment 1
<location> `GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs:83-87` </location>
<code_context>
+    /// <summary>
+    ///     第一个事件触发处理
+    /// </summary>
+    private void OnFirstEvent(TEvent1 eventData)
+    {
+        FirstEventData = eventData;
+        TriggeredBy = 1;
+        _done = true;
+    }
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Guard against multiple event invocations so that the first event wins consistently.

`OnFirstEvent` (and the corresponding second handler) always overwrite `TriggeredBy` and the event data, so if both events can fire, the later one wins non‑deterministically. Use `_done` as a guard in each handler (e.g., `if (_done) return;` at the top) so that once one event sets the state, subsequent invocations do nothing and the first event reliably wins.
</issue_to_address>

### Comment 2
<location> `GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs:70-57` </location>
<code_context>
+    ///     更新方法,检测条件变化
+    /// </summary>
+    /// <param name="deltaTime">时间增量</param>
+    public void Update(double deltaTime)
+    {
+        if (_isCompleted)
</code_context>

<issue_to_address>
**issue (bug_risk):** Current auto-unregister logic can leak subscriptions if the scheduler stops calling Update once IsDone is true.

The callbacks are only unregistered if `_isCompleted` is true *and* `Update` is called again:

```csharp
if (!_done || (_unRegister1 == null && _unRegister2 == null)) return;
```

If the scheduler stops invoking `Update` once `IsDone`/`_isCompleted` is true (as many coroutine schedulers do), those event subscriptions are never released unless `Dispose` is called manually.

Consider unregistering directly in `OnFirstEvent`/`OnSecondEvent` when `_done` is set, or factoring the unregister logic into a helper that’s called from both `Dispose` and the event handlers so cleanup doesn’t depend on a final `Update` call.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs
Comment thread GFramework.Core/coroutine/instructions/WaitForMultipleEvents.cs
- 添加了完成状态检查,避免在已完成或释放后继续处理事件
- 立即注销事件监听器以防止内存泄漏
- 在事件触发后清理注册器引用
- 添加了完整的单元测试覆盖各种事件场景
- 为 Delay 指令添加完整的单元测试覆盖各种时间情况
- 为 WaitForCoroutine 指令添加单元测试验证协程等待功能
- 为 WaitForFrames 指令添加单元测试覆盖帧计数逻辑
- 为 WaitForTask<T> 指令添加单元测试包括异常处理场景
- 为 WaitOneFrame 指令添加单元测试验证单帧等待
- 为 WaitUntil 和 WaitWhile 指令添加单元测试覆盖谓词逻辑
- 将 WaitForMultipleEventsTests 中的异步方法标记为 async Task 类型
- 修改测试事件类的 Data 属性为可变的 set 访问器而不是只读 init
- 优化 WaitForMultipleEventsTests 中的断言注释描述
@GeWuYou GeWuYou merged commit e83dd27 into main Feb 10, 2026
6 checks passed
@GeWuYou GeWuYou deleted the feature/coroutine-wait-instructions branch February 10, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant