Skip to content

1.6.1

Compare
Choose a tag to compare
@landelare landelare released this 07 Feb 10:19
· 17 commits to master since this release

Fixed the UE5Coro::TAwaitable concept (C++20 only) not telling lvalues and rvalues apart. TAwaitable<TFuture<int>&> is now correctly NOT satisfied, but TAwaitable<TFuture<int>> is.

The internal type UE5Coro::Private::FPromise (the base class of all FAsyncCoroutine promises) has received a "nice" incrementing ID in debug only. This is displayed by UE5Coro.natvis.

Fixed a crash bug in WhenAny and WhenAll. The fix required a breaking change in how they're used: noncopyable awaiters (mostly the ones returned by functions in the Latent namespace) will need to be explicitly moved into the call with MoveTemp/std::move/etc.

  • In C++20, invalid calls will generate a compiler error at the call site.
  • In C++17, invalid calls will hit a static_assert in WhenAny/WhenAll along with additional compiler errors coming from UE5Coro::Private. The error's notes hopefully contain the call site.

Note that WhenAny and WhenAll consume and co_await every input parameter; awaiters that aren't reusable may not be used after they're indirectly co_awaited this way. This hasn't changed.

As an example, the usual pattern to implement a timeout changes like so:

 using namespace UE5Coro;
 auto Awaiter = SomethingLengthy(); // lvalue for the sake of this example
-if (co_await WhenAny(Awaiter, Latent::Seconds(5)))
+if (co_await WhenAny(MoveTemp(Awaiter), Latent::Seconds(5)))
     TimedOut();

The return value of Latent::Seconds(5) is an rvalue to begin with in this example, so it doesn't need special treatment.

To better work with this new requirement, movability/copyability restrictions are relaxed in existing awaitables:

  • The return value of WhenAny/WhenAll is copyable and reusable. Additional co_awaits will continue immediately and synchronously.
  • The return value of Http::ProcessAsync is copyable and partially reusable. Copies are thread safe. Only one coroutine may co_await any copy of the original return value at the same time (this is check()ed), but once it has resumed, any copy may be co_awaited again and again to get the same HTTP result. This matters, e.g., if you attempt to co_await the result separately while it's also being held by a WhenAny.
  • The return value of AsyncLoadPackage is copyable and partially reusable in a similar way (one concurrent co_await, any number of additional ones once it's done), but it's NOT thread safe.
  • Async collision queries from the Latent namespace are copyable and partially reusable in the same way. Game thread only.
  • Attempting to co_await an invalid (e.g. moved-from) or spent TFuture<T> from the engine will check() earlier than it used to. As a reminder, co_awaiting a TFuture consumes it. TFuture itself from the engine is otherwise movable.
  • UE::Tasks::TTask<T> from the engine is copyable and may be co_awaited any number of times. Every co_await will continue after the original task has completed (immediately and synchronously without a task/thread switch if the TTask is already complete before co_await).
  • The return values of most other functions in the Latent namespace are movable and reusable. Additional co_awaits after the first one has completed will continue immediately and synchronously.

Additional awaiters are also relaxed for consistency, although it doesn't make much sense to pass these into WhenAny/WhenAll:

  • The return value of MoveToTask is copyable and reusable. Reusing it will co_await into a new Task every time.
  • The return values of MoveToThread and MoveToGameThread are copyable and reusable. Reusing them will co_await back into the same thread.
  • The return value of MoveToNewThread is copyable and reusable. Reusing it will co_await into a new thread every time.
  • The return value of Latent::Cancel is copyable and reusable. It will cancel any latent mode coroutine that co_awaits it if you decide to pass it around for some weird reason.

Moving from an awaiter of any type that's currently being co_awaited is undefined behavior.