Skip to content

2.0

Compare
Choose a tag to compare
@landelare landelare released this 31 Aug 13:32
· 4 commits to master since this release

Breaking changes

This release of UE5Coro requires C++20, and Unreal Engine 5.3 or newer. It has been tested on Unreal Engine 5.3.2, 5.4.0, 5.4.1, 5.4.2, 5.4.3, and 5.4.4, using MSVC 14.41. No version of Clang is known to be compatible at the time of release, due to llvm/llvm-project#91983.

Support for C++17, older engine versions going back as far as 4.27, and some legacy/non-compliant platforms is still available in UE5Coro 1.

Major changes

  • There is just one plugin, UE5Coro. AI and GAS support are still in optional modules.
  • C++17 is no longer supported. Workarounds for certain broken platforms that falsely claim to be C++20 have been removed.
  • The UE5CORO_CPP20 macro is no longer #defined, it is assumed to be true.
  • #including individual public headers is no longer supported, it's only "UE5Coro.h", "UE5CoroAI.h", and "UE5CoroGAS.h".
  • FAsyncCoroutine has been renamed to FVoidCoroutine. A core redirect is provided.
  • The UE5Coro::Tasks namespace has been removed, its contents are now in UE5Coro::Async.
  • UE5Coro::Latent::Cancel() has been removed. This functionality is now provided by UE5Coro::FSelfCancellation, which does not require the coroutine to be latent.
  • Awaiting a (non-const) TCoroutine rvalue will move its result if it's movable, instead of always making a copy.
  • Awaiting UE::Tasks::TTask<T> will result in T&, not T. This matches TTask::GetResult().
  • Latent coroutines must take an explicit owner/world context parameter, which will determine the object that's registered with the engine. For nonstatic UObject member functions, it is this, for everything else, the first parameter. The coroutine body doesn't have to read or otherwise use the parameter. See the documentation for more details.
    • If you're using Clang and latent lambdas, your existing code is likely affected by llvm/llvm-project#91983 wrongly determining what the first parameter is. This was usually harmless in 1.x. For most lambdas, wrapping the world context parameter in UE5Coro::TLatentContext will retain existing behavior.
    • A latent coroutine not having a valid world context at start is now considered fatal instead of automatically being matched to a world. While this mostly worked, it also led to otherwise-unfixable bugs #22 #23 that necessitate this breaking change.
  • The object-and-member-function-pointer overload of Latent::Chain now takes the object as the first parameter instead of the function, making it follow Unreal conventions instead of std::invoke/std::bind. Replace Chain(&Class::Fn, Object, ...) with Chain(Object, &Class::Fn, ...).
    • The non-member-function-pointer overload is not affected, and ChainEx continues to follow std::bind order, since it relies on std::placeholders.

Minor changes

  • Everything in the UE5Coro::Latent namespace now returns a true latent awaiter, satisfying the new UE5Coro::TLatentAwaiter concept. Some previously-copyable awaiters are now move-only, but they enjoy improved reaction to cancellations, and take the fast path when used from latent coroutines.
  • Latent awaiters are now much more aggressive in checking that they're used on the game thread. Using them elsewhere was never supported, but a few edge cases might have slipped through.
  • FAwaitableEvent and FAwaitableSemaphore are no longer their own awaiters. In practice, this means that WhenAll/WhenAny now accept these as parameters, despite being immovable. Note that WhenAny will reliably reset an auto-reset event, and take one count from a semaphore, even if another parameter completes first.
  • Cancellation processing has been redefined to occur at an unspecified time between the cancellation itself and the next resume, to allow for future improvements. #27
  • Async coroutines awaiting a TLatentAwaiter now enjoy the same next-tick cancellation response as latent coroutines. #27
  • FAbilityCoroutine now attempts to prevent this type being passed around by deleting some constructors (it's passable as its base type, TCoroutine<>).
  • Latent::Chain parameter matching heuristics have been improved, it supports more engine latent functions now. There's a possibility that 2.0 will select a different (hopefully better) overload than 1.x if there are non-UFUNCTION overloads.
  • Latent coroutines that are currently awaiting something that's NOT a TLatentAwaiter, AND have an active cancellation guard, AND have their latent action deleted by the latent action manager will process the resulting forced cancellation at the next co_await (or possibly at an unspecified time before it in a future version). This used to be the next co_await on the game thread, which might not happen, resulting in an extremely rare memory leak. Cleanup will still happen on the game thread.
  • Coroutines in classes containing a UFUNCTION called None are no longer supported due to engine bugslimitations.
  • Anim notifies named None are no longer supported, for similar reasons. The engine never invokes them.

New features

  • Latent::AsyncChangeBundleStateForPrimaryAssets() and Latent::AsyncChangeBundleStateForMatchingPrimaryAssets() were added.
  • Async::MoveToThreadPool() was added that lets coroutines target a particular thread pool to run on.
  • Latent::FTickTimeBudget was added: it provides an easy way to dynamically target a coroutine processing time of x ms per tick.
  • FVoidCoroutine (née FAsyncCoroutine) has received an IsValid() method.
  • TCoroutine::ContinueWithWeak() now supports NotThreadSafe TSharedPtrs. Previously, this was limited to ThreadSafe.
  • Latent::UntilCoroutine was added. This forces a coroutine's await to be performed in latent mode, for advanced usage. TCoroutines continue to be directly awaitable without wrappers. #28
  • UUE5CoroGameplayAbility::Task() now lets callers disable the auto-activation for well-known task types.
  • Latent coroutines now let you override the world context and callback target for the underlying latent action (UE5Coro::TLatentContext).
  • Whether an awaiter takes the latent fast path or not is exposed via the new UE5Coro::TLatentAwaiter concept. This is unlikely to be useful in code, but documentation refers to it to provide a clear definition instead of "some, but not all things in UE5Coro::Latent".

Additional, backwards-compatible improvements

  • A large amount of documentation was added.
  • UE5CoroAI is promoted to beta.
  • Many C++ error messages related to using the plugin are now less cryptic as a result of the C++17 -> C++20 changes.
  • Aggregate awaiters now have well-defined behavior when awaiting 0 things: they instantly succeed. Awaiting WhenAny and Race will result in a negative value in this case (0 would mean the first parameter).
  • TGenerator is now move assignable.
  • Additional functions were made noexcept.
    • Exceptions are still an opt-in, unsupported way of using the plugin (and the engine).
  • Minor optimizations were done, mainly around reducing the number of heap allocations, object sizes, eliminating indirections, and caching intermediate results across multiple calls.
    • If you're directly using the private/unstable API, FLatentAwaiters were technically never object sliced before, but they are now.
  • Minor threading improvements when UObjects and/or latent coroutines are used off the game thread.
  • Improvements for incremental GC.

Backwards-compatible fixes

  • Latent coroutines started before UUE5CoroSubsystem initializes (e.g., in OnConstruction) no longer crash.
  • Fixed a checkf in UE5CoroGAS that can happen in multiplayer PIE. #30
  • Fixed a race condition that may happen if the latent action manager decides to delete the latent action of a coroutine as it finishes on another thread. This could result in the coroutine cleaning up on the wrong thread, a memory leak, or ContinueWith callbacks not being processed. Latent coroutines that complete on the game thread were not affected.
  • Fixed UE5Coro.Latent.Completion test cases that were failing extremely rarely (with <0.5‰ chance) and a few others that would only fail in unusual, but valid setups having certain combinations of GC settings and custom memory allocators. The problem was with the tests themselves, game code was not affected.
  • Promoted a few checks to internal errors, added new ones. As always, please file a bug report if you encounter an internal error.
  • Demoted the checks in every unhandled_exception to checkSlows. These can indicate undefined behavior in game code that would otherwise silently corrupt data, but their presence might hinder debugging. #26