Skip to content

1.8

Compare
Choose a tag to compare
@landelare landelare released this 23 Apr 01:45
· 11 commits to master since this release

Tested against UE 5.0.3, 5.1.1, and 5.2.0p2.

Integrated cancellation support:

  • TCoroutine<>::Cancel requests the coroutine to cancel after the next co_await, clean up its locals, and return instead of resuming. The return value will be T().
    • This is processed automatically in every TCoroutine without you having to write additional code to handle being canceled.
      At the same time, existing code is unaffected because nothing could have called Cancel() yet.
  • co_await UE5Coro::FinishNowIfCanceled(); can be used in case there's nothing else to co_await, e.g., in a tight loop.
  • For advanced scenarios, the presence of a UE5Coro::FCancellationGuard delays the processing of incoming cancellation requests until the last one has gone out of scope. Making one on the first line of a coroutine body essentially opts it out of user-requested cancellation.
    • This does not affect cancellations caused by delete from the latent action manager.
    • Latent::Cancel() will check() if it's co_awaited with an active FCancellationGuard.
  • For even more advanced usage, UE5Coro::IsCurrentCoroutineCanceled() checks if there was an incoming cancellation request but does not process it.
    Do not use it to manually process cancellations (if (IsCurrentCoroutineCanceled()) co_return;), prefer FinishNowIfCanceled.

Additional features and improvements:

  • UE5Coro::Race() behaves similarly to WhenAny, but the first coroutine to finish cancels the others. Unlike WhenAny, this only works on TCoroutines, not anything awaitable.
  • TCoroutines are now valid keys for collections: they come with comparison operators (==, !=, < in C++17; ==, <=> in C++20), GetTypeHash, and a specialization of std::hash.
    • The order of TCoroutines is meaningless, but it is strict and total.
  • Added new static shortcuts on TCoroutine to get already-completed coroutines.
  • Latent::Cancel() is now free-threaded. The cancellation will happen on the game thread regardless of where it was co_awaited.
  • Async::MoveTo...Thread() is now more efficient if it targets the same thread it's already running on, continuing immediately and synchronously.
    • If this is not desired, Async::Yield() is added: it always suspends and resumes later on the same kind of thread that it was co_awaited on (game thread to game thread, AnyThread to AnyThread, high priority to high priority, etc.).
  • Latent coroutines no longer need to explicitly end on the game thread; this is now automatically done behind the scenes if needed. Note that locals' destructors run on the current thread BEFORE the coroutine ends.
  • If a latent coroutine does end (or self-cancel) on the game thread, its completion and cleanup are now processed immediately instead of on the latent action manager's next poll. Timings don't change for BP, but this improves responsiveness when ContinueWith is used or coroutines are chained with co_await.

Breaking/behavioral changes:

  • If a latent coroutine ends on the game thread, due to the immediate cleanup mentioned above, locals' destructors (notably, ON_SCOPE_EXIT) now run before the latent exec pin is triggered in BP, with their effects observable from BP.
  • Latent coroutines used to be able to co_await to other threads and run indefinitely after a latent action manager deleted their underlying BP latent action; it would only be processed once execution returned to the game thread.
    This will now get processed at the next co_await on ALL threads: if canceled by a delete, the coroutine will move back to the game thread instead of resuming and clean up there.
    FCancellationGuard does NOT guard against this: the latent action is gone and the coroutine would not be able to continue normally, leading to a memory leak that's avoided this way.
  • Behavior has slightly changed if a non-latent coroutine is co_awaiting a latent action (e.g., Chain()/ChainEx()) that the engine decides to delete early. This used to force cleanup of the awaiter coroutine no matter what, but it's now translated to a regular cancellation that's processed immediately and CAN be guarded against.
  • A coroutine's destruction is now assumed to be noexcept (i.e., destructors should not throw). This is highly unlikely to affect even code that uses exceptions, and completely irrelevant if UE's default exception settings are used.

Fixes:

  • Fixed UWorld async query awaiters using lvalue overloads even if an rvalue was co_awaited, resulting in an unnecessary copy of the result TArray.

Deprecations/removals:

  • This is the last version that's tested against UE 5.0 and the VS2019 toolchain.
  • The debug-only resume stack no longer exists and has been removed from UE5Coro.natvis. UE5Coro::Private::FPromise::Resume entries from the regular call stack can be used to obtain the same information.