Skip to content

Releases: landelare/ue5coro

2.0

31 Aug 13:32
Compare
Choose a tag to compare

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.

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

1.10.3, UE4 edition

31 Aug 13:31
Compare
Choose a tag to compare

This release contains a few fixes backported from 2.0.

Caution

The UE4 edition is completely unsupported.
Please do not report bugs against it, unless they can be reproduced on UE5 and a regular, supported UE5Coro release.

  • UE5Coro will let some undefined behavior through from coroutines. #26
  • Fixed a checkf in UE5CoroGAS when using multiplayer PIE. #30

1.10.3

31 Aug 13:32
Compare
Choose a tag to compare

This release contains a few fixes backported from 2.0.

  • UE5Coro will let some undefined behavior through from coroutines. #26
  • Fixed a checkf in UE5CoroGAS when using multiplayer PIE. #30

1.10.2, UE4 edition

26 Jul 13:37
Compare
Choose a tag to compare

Caution

This special release is completely unsupported.
Please do not report bugs against it, unless they can be reproduced on UE5 and a regular, supported UE5Coro release.

This release targets Unreal Engine 4.27.2. No other versions were tested, but they might work.
Do not use this release with Unreal Engine 5.

Some features had to be removed or changed. Compared to the regular 1.10.2:

  • UE5Coro::Async::Yield is not available.
  • UE5Coro::Latent::AsyncLoadPackage takes different parameters.
  • UE5Coro::Latent::AsyncOverlapByProfile is not available.
  • UE5Coro::Http::ProcessAsync will resume its coroutine on the game thread.
  • The UE5Coro::Tasks namespace has been removed.
  • UE::Tasks::TTask is not implicitly awaitable. It doesn't exist in UE4.
  • There's no support for pending kill disabled. It cannot be disabled in UE4.
  • Latent timelines and delays are float based. Async::PlatformSeconds is still using double.
  • Since UCLASS(Hidden) does not exist in UE4, various internal classes have been fully exposed. These should not be used directly.

1.10.2

23 Apr 15:51
Compare
Choose a tag to compare

This is a maintenance release, improving compatibility with Unreal Engine 5.4 and LLVM 18.

1.10.1

04 Feb 19:38
Compare
Choose a tag to compare

This is a bugfix release, but due to the nature of the fixes, you'll probably need to change your code interacting with the plugins. Various invalid usages that might have gotten lucky and usually worked are now being checked instead of invoking undefined behavior. In other places, a world context now needs to be provided explicitly instead of it being (sometimes wrongly) inferred.

To ease migration to the next version, it is recommended to make world context objects the first parameter of methods (this counts as the first parameter for non-static members). This is not currently a requirement, and parameters at any position are candidates to be world objects, like in previous versions.

UE5Coro

  • Numerous checks were added to validate being on the game thread or in a valid world when this is required for correct operation. This mostly affects latent coroutines, or using latent awaiters in async mode around world startup/teardown or in the editor.
  • Fixed world context object parameters sometimes not being read to determine a latent coroutine's world. This might change which world was detected in multi-world scenarios.
  • Latent coroutines are stricter when determining the world to use. Some functions that used to work might require an explicit world context parameter now.
  • The latent timeline coroutines now take an explicit world context object as the first parameter.

UE5CoroAI

  • MoveTo overloads now allow more types of goals (such as APawn*). This was overly limited by mistake.

UE5CoroGAS

  • Gameplay ability coroutines are now more robust in finding a world. This improves reliability of NonInstanced abilities.

1.10

25 Oct 15:41
Compare
Choose a tag to compare

This is the last release that supports C++17.

Due to issues with the engine's Android/Linux toolchain, some C++20-only features are not available on these platforms even if C++20 is used. #16

UE5Coro

  • New threading primitives were added: FAwaitableSemaphore and FAwaitableEvent. They can be co_awaited directly.
  • (C++20 only) WhenAny and WhenAll have received new overloads taking TArray<TCoroutine<>>.
  • Async::MoveToSimilarThread was added for returning to a thread that was previously known at runtime.
  • Async::PlatformSeconds, PlatformSecondsAnyThread, UntilPlatformTime, and UntilPlatformTimeAnyThread were added for Delay-like functionality on any thread at any time, even without a world.
  • 4 new latent awaiters (UntilTime, UntilUnpausedTime, UntilRealTime, UntilAudioTime) were added for waiting until a point in time instead of an amount of time.
  • Http::ProcessAsync supports Unreal 5.3 HTTP request delegate thread policies. If the request is configured to complete on the HTTP thread, the coroutine will resume on the HTTP thread.
    • This feature is affected by engine bugs related to the HTTP thread not processing everything that it should. Please don't open bugs if the equivalent callback-based code doesn't work either.
  • TCoroutine::ContinueWithWeak now supports UObjects even if the coroutine finishes off the game thread. This used to ensure().
  • Improved performance in some high-contention multithreaded scenarios.

Fixes/breaking changes:

  • Using FForceLatentCoroutine to run a coroutine in latent mode with no FLatentActionInfo now uses heuristics to find an object with an appropriate lifetime instead of tying execution to the world's lifetime. In practice, this will be usually this or the world itself. See the documentation for more details. #19
  • co_awaiting a TCoroutine from a coroutine running in async mode no longer marshals back to the original thread. The coroutine will resume immediately on the thread where the awaited coroutine finished.
    • This improves responsiveness when coroutines co_await each other, and the T in TCoroutine<T> no longer needs to have a thread-safe copy constructor in this case.
    • Async::MoveToSimilarThread may be used to restore this behavior if it's required.
  • Fixed a minor memory leak when co_awaiting Async::MoveToNewThread.
  • Fixed use-after-free bugs in debug builds.

UE5CoroAI

New, optional and highly experimental plugin targeting AIModule and NavigationSystem. It has awaiters for various "Move To" methods and async pathfinding for now.

  • UE5CoroAI does not support C++17.

UE5CoroGAS

  • Fixed a check caused by GAS attempting to cancel non-instanced abilities with an invalid prediction key. Abilities being started with invalid prediction keys will now check, this does not happen normally.

1.9.1

20 Jul 19:48
Compare
Choose a tag to compare

Build fixes only, no functional change.

1.9

20 Jun 04:11
Compare
Choose a tag to compare
1.9

The folder structure of the repository and releases changed, there are multiple plugins now.
Tested on Unreal Engine 5.1.1, 5.2.0, 5.2.1, with preliminary support for 5.3 and BuildSettingsVersion.V4.
README.md was updated with new installation instructions.

UE5Coro

  • Delegates are now directly co_awaitable. All of them. See the documentation for more details.
  • TCoroutine::WasSuccessful was added to complement IsDone. It's true if and only if the coroutine successfully ran to completion.
    • If you're using exceptions, an unhandled exception leads to IsDone() && !WasSuccessful().
  • UE5CoroGAS support.

Improvements:

  • Coroutines returning TCoroutine start a little faster and use slightly less memory.
  • Latent awaiters have marginally lower CPU overhead when co_awaited.

Fixes:

  • Fixed that coroutines returning TCoroutine and having multiple FForceLatentCoroutine parameters compiled. They weren't supposed to.
  • Worked around an MSVC regression in CoroutineHandleTest.cpp.

Deprecations:

  • TCoroutine::OnCompletion has been removed entirely.
  • VS2019 compatibility is no longer tested or maintained.
  • UE5.0 compatibility is no longer tested or maintained.

UE5CoroGAS

A brand-new and optional plugin to integrate with Unreal's Gameplay Ability System.
This plugin is currently experimental. Although it has been tested, its API is subject to change without deprecations for now.

  • UUE5CoroGameplayAbility: A coroutine-based UGameplayAbility implementation that deals with most lifetime control methods for you and translates them to and from native C++ coroutine behavior.
    • There's a new awaiter only available from this class (Task()) that lets you co_await many built-in tasks with a more convenient syntax than Latent::UntilDelegate.
  • UUE5CoroAbilityTask: The same, but for UAbilityTask.
  • UUE5CoroSimpleAbilityTask: A convenience base class with two premade delegates for coroutine success/failure.

All of these are drop-in replacements for their engine base classes. Normal coroutine behavior is tweaked to match what BP-authored abilities or tasks would do. There's a full list of these behavior changes in the documentation.

1.8

23 Apr 01:45
Compare
Choose a tag to compare
1.8

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.