Releases: landelare/ue5coro
2.0.1 RC
This release is intended to become 2.0.1, no further changes are planned. Please open an issue for any regressions since 2.0.
If updating from 1.x, please see the release notes of 2.0 for the list of breaking changes. Clang builds are still affected by llvm/llvm-project#91983.
UE5Coro
- Unreal Engine 5.5 is supported.
- TCoroutine::ContinueWithWeak now uses the UObject pinning facility introduced in UE5.5 to avoid holding a GC lock. No change in 5.4 and below.
- Some
check
s have been made stricter. This might cause regressions, that I'm hoping to find during the RC period.
UE5CoroGAS
- NonInstanced gameplay abilities have been deprecated in Unreal Engine 5.5, and they're no longer supported. Full support remains if using older engine versions.
1.10.4
This release contains a few changes backported from 2.0.1. It has been tested against Unreal Engine 5.3, 5.4, and 5.5.
UE5Coro
- Unreal Engine 5.5 is supported.
- TCoroutine::ContinueWithWeak now uses the UObject pinning facility introduced in UE5.5 to avoid holding a GC lock. No change in 5.4 and below.
UE5CoroGAS
- NonInstanced gameplay abilities have been deprecated in Unreal Engine 5.5, and they're no longer supported. Full support remains if using older engine versions.
2.0
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.
- 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
- 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, ...)
withChain(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
check
ing 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 enginebugslimitations. - 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
check
s to internal errors, added new ones. As always, please file a bug report if you encounter an internal error. - Demoted the
check
s in everyunhandled_exception
tocheckSlow
s. 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
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.
1.10.3
1.10.2, UE4 edition
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
1.10.1
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 check
ed 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
check
s 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
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 usuallythis
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 nowcheck
, this does not happen normally.