This library implements C++ coroutines for Unreal Engine 5 with a focus on gameplay logic and BP integration.
Download the release that you wish to use from the
Releases page, extract it to
your project's Plugins folder and in case you downloaded a source code zip,
rename the folder that it contains to just UE5Coro
so that you end up with
YourProject\Plugins\UE5Coro\UE5Coro.uplugin
.
In modules where you wish to use coroutines, add or change this line in the corresponding Build.cs file:
CppStandard = CppStandardVersion.Cpp20;
Add "UE5Coro"
to your dependency module names in the same Build.cs file – up
to you if it's private or public – enable the plugin in the Unreal editor,
and you're ready to go!
In your Target.cs files for projects where you wish to use coroutines, add this line:
bEnableCppCoroutinesForEvaluation = true;
No additional per-module setup is needed in this case, add "UE5Coro"
to your
dependency module names and enable the plugin like any other.
Potential UE5.1 bug: if you're building the engine from source and it seems to be rebuilding itself for no reason once you've done the Target.cs change above, edit TargetRules.cs in the engine instead so that this flag is true by default.
Click these links for the detailed description of the main features provided by this plugin, or keep reading for a quick overview.
- Async coroutines control their own resumption by awaiting various awaiter objects. They can be used to implement BP latent actions such as Delay, or as a generic fork in code execution like AsyncTask, but not necessarily involving multithreading.
- Generators are caller-controlled and return a variable number of results without having to allocate and go through a temporary TArray.
- Overview of built-in awaiters that you can use with async coroutines.
Return FAsyncCoroutine
from a function (regular or UFUNCTION) to make it
coroutine enabled and support co_await inside.
Having a FLatentActionInfo
parameter makes the coroutine implement a BP latent
action.
You do not need to do anything with the parameter, just have it and the plugin
will register it with the latent action manager.
World context objects are also supported and automatically processed, it's
recommended to have them as the first parameter.
Don't forget the necessary UFUNCTION metadata to make this a latent node in BP!
UFUNCTION(BlueprintCallable, Meta = (Latent, LatentInfo = "LatentInfo"))
FAsyncCoroutine AExampleActor::Latent(FLatentActionInfo LatentInfo)
{
// This will *not* block the game thread for a second!
co_await UE5Coro::Latent::Seconds(1.0);
OneSecondLater();
}
You're not limited to BP latent actions, or UCLASS members:
FAsyncCoroutine MyGlobalHelperFunction()
{
co_await UE5Coro::Latent::Seconds(1.0);
OneSecondLater();
}
Or even regular functions:
void Example(int Value)
{
auto Lambda = [Value]() -> FAsyncCoroutine
{
co_await UE5Coro::Tasks::MoveToTask();
PerformExpensiveTask(Value);
};
Lambda();
}
Both BP latent actions and free-running asynchronous coroutines have a unified feature set: you can seamlessly co_await the same things from both and if needed, your BP latent action becomes a threading placeholder or additional behind-the-scenes latent actions are started as needed.
BP Latent actions are considered complete for BP when control leaves the scope
of the coroutine body completely, either implicitly (running to the final }
)
or explicitly via co_return;
.
Asynchronous coroutines (in both modes) synchronously return to their callers at the first co_await or co_return that they encounter and the rest of the function body runs either independently (no LatentInfo) or through the latent action manager (with LatentInfo).
Everything co_awaitable works in every asynchronous coroutine, regardless of its BP integration:
using namespace UE5Coro;
UFUNCTION(BlueprintCallable, Meta = (Latent, LatentInfo = "LatentInfo"))
FAsyncCoroutine UExampleFunctionLibrary::K2_Foo(FLatentActionInfo LatentInfo)
{
// You can freely hop between threads even though this is BP:
co_await Async::MoveToThread(ENamedThreads::AnyBackgroundThreadNormalTask);
DoExpensiveThingOnBackgroundThread();
// However, awaiting latent actions has to be started from the game thread:
co_await Async::MoveToGameThread();
co_await Latent::Seconds(1.0f);
}
There are various other engine features with coroutine support including some engine types that are made directly co_awaitable by the plugin. Check out the Awaiters page for an overview.
Generators can be used to return an arbitrary number of items from a function without having to pass them through temp arrays, etc. In C# they're known as iterators.
Returning UE5Coro::TGenerator<T>
makes a function coroutine enabled,
supporting co_yield
:
using namespace UE5Coro;
TGenerator<FString> MakeParkingSpaces(int Num)
{
for (int i = 1; i <= Num; ++i)
co_yield FString::Printf(TEXT("🅿️ %d"), i);
}
// Elsewhere
for (const FString& Str : MakeParkingSpaces(123))
Process(Str);
co_yield and co_await cannot be mixed. Asynchronous coroutines control their own execution and wait for certain events, while generators are caller-controlled and yield values on demand.
In particular, it's not guaranteed that your generator function body will even run to completion if your caller decides to stop early. This enables scenarios where generators may co_yield an infinite number of elements and callers only taking a finite few:
using namespace UE5Coro;
TGenerator<int> NotTrulyInfinite()
{
FString WillBeDestroyed = TEXT("Read on");
int* Dangerous = new int;
for (;;)
co_yield 1;
delete Dangerous;
}
// Elsewhere:
TGenerator<int> Generator = NotTrulyInfinite();
for (int i = 0; i < 5; ++i)
Generator.Resume();
In this case, your coroutine stack will be unwound when the TGenerator object
is destroyed, and the destructors of locals within the coroutine run like usual,
as if the last co_yield
was a throw
(but no exceptions are involved).
In the example above, the FString will be freed but the delete
line will never
run.
Use RAII or helpers such as ON_SCOPE_EXIT
if you expect to not run to
completion.