A lightweight C++20 coroutine implementation for Unreal Engine 5.

Related tags

Game ue5coro
Overview

UE5Coro

This library implements basic C++20 coroutine support for Unreal Engine.

Getting started

Obviously you'll need to switch your project to C++20. In your Build.cs file, add or change this line:

CppStandard = CppStandardVersion.Cpp20;

If you're using a prebuilt 5.x from the future you might also need these:

PCHUsage = PCHUsageMode.NoSharedPCHs;
PrivatePCHHeaderFile = "YourPCH.h";

Add "UE5Coro" to your dependency module names, enable the plugin, and you're ready to go!

Examples

Generators

Generators can be used to return an arbitrary number of items from a function without having to pass them through temp arrays, etc. You can even make a function co_yield infinite elements and have the caller decide how many it wants!

using namespace UE5Coro;

TGenerator<int> CountToThree()
{
    for (int i = 1; i <= 3; ++i)
        co_yield i;
}

You can run the generator manually with full control over when it resumes:

TGenerator<int> G1 = CountToThree(); // Runs to co_yield i; with i==1
do
{
    // This check can be omitted if you're guaranteed at least one
    // co_yield from the generator.
    if (G1)
        int Value = G1.Current();

    // Resume() continues the function until the next co_yield or until
    // control leaves scope. As a convenience it returns operator bool().
} while (G1.Resume());

You can use generators as UE-style iterators:

TGenerator<int> G2 = CountToThree();
for (auto It = G2.CreateIterator(); It; ++It)
    int Value = *It;

They also work with range-based for (or STL algorithms):

TGenerator<int> G3 = CountToThree();
for (int Value : G3)
    DoSomethingWith(Value);

Your caller can stop you at any point so feel free to go wild:

TGenerator 
   Every64BitPrime()
{
    
   // You probably don't want to run this on tick...
    
   for (uint64 i = 
   2; i <= UINT64_MAX; ++i)
        
   if (
   IsPrime(i))
            
   co_yield i;
}

TGenerator
   
     Primes = Every64BitPrime();
uint64 Two = Primes.Current();
Primes.Resume();
uint64 Three = Primes.Current();
Primes.Resume();
uint64 Five = Primes.Current();

    // Done! Primes going out of scope will destroy the coroutine.
   
  

This is also fine, fetch as many values as you want:

AllTheParkingSpaces = InfiniteString(); for (int i = 0; i < 10; ++i) { TCHAR P = AllTheParkingSpaces.Current(); AllTheParkingSpaces.Resume(); }">
TGenerator 
   InfiniteString()
{
    
   // Warranty void on integer overflow
    
   for (
   int i = 
   1; 
   /*forever*/; ++i)
    {
        
   UE_LOG(LogTemp, Display, 
   TEXT(
   "%d parking space(s) served.", i);
        
   co_yield 
   TEXT(
   '🅿️');
    }
}


   // Only 10 needed for the company fleet
TGenerator
   
     AllTheParkingSpaces = InfiniteString();

    for (
    int i = 
    0; i < 
    10; ++i)
{
    TCHAR P = AllTheParkingSpaces.
    Current();
    AllTheParkingSpaces.
    Resume();
}
   
  

If you're used to Unity coroutines or just regular .NET IEnumerable , do note that unlike C# iterators these generators run immediately when called, not at the first Resume(). This behavior fits the semantics of C++ iterators better that expect begin() to already be on the first element.

Latent actions

FLatentCoroutine::Start in namespace UE5Coro can be used to start any function including lambdas as a latent action so you don't need to author entire types per UFUNCTION. Returning UE5Coro::FLatentCoroutine makes your function coroutine-enabled:

FLatentCoroutine to your lambda to make it coroutine enabled! FLatentCoroutine::Start(LatentInfo, []() -> FLatentCoroutine { // You're now in the next tick. co_await Latent::Ticks(10); // An additional 10 ticks later... co_await Latent::Seconds(1.0f); // 1 second later, regardless of FPS... // As you return from this lambda, your BP node will fire its // latent exec pin. }); }">
// .h
UFUNCTION(BlueprintCallable, Meta = (Latent, LatentInfo = "LatentInfo"))
static void Foo(int EpicPleaseFixUE22342, FLatentActionInfo LatentInfo);

// .cpp
using namespace UE5Coro;

void UExampleFunctionLibrary::Foo(int, FLatentActionInfo LatentInfo)
{
    // Add -> FLatentCoroutine to your lambda to make it coroutine enabled!
    FLatentCoroutine::Start(LatentInfo, []() -> FLatentCoroutine
    {
        // You're now in the next tick.
        co_await Latent::Ticks(10);
        // An additional 10 ticks later...
        co_await Latent::Seconds(1.0f);
        // 1 second later, regardless of FPS...

        // As you return from this lambda, your BP node will fire its
        // latent exec pin.
    });
}

See LatentAwaiters.h for a list of built-in awaiters and how to add your own. Just like regular latent actions, these all run on the game thread.

Async tasks

Similarly to latent actions, returning UE5Coro::FAsyncCoroutine from a function makes it coroutine-enabled and usable by, e.g. AsyncTask(). You can use co_await to easily hop threads:

using namespace UE5Coro;

AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, []() -> FAsyncCoroutine)
{
    // You're now in the thread that was given to AsyncTask.
    DoExpensiveThing();
    co_await Async::MoveToThread(ENamedThreads::GameThread);
    // You're now on the game thread!
    co_await Async::MoveToThread(ENamedThreads::AnyHiPriThreadHiPriTask);
    // You can move in and out as you wish.
    if (bShouldSwitchThreadsAgain)
        co_await Async::MoveToThread(ENamedThreads::GameThread);
    // Runtime-decided thread!
});

// No need to start with AsyncTask:
FAsyncCoroutine DoBackgroundThing()
{
    // This part works just like any regular function call.
    // You're in your caller's thread.

    co_await Async::MoveToThread(ENamedThreads::AnyBackgroundThreadNormalTask);
    // You're now in an AsyncTask()

    co_await Async::MoveToThread(ENamedThreads::GameThread);
    // You're back on the game thread in another AsyncTask()
}

// Use like this, the return value does not need to be stored:
DoBackgroundThing();

Behind the scenes, each co_await will run the rest of your coroutine in a new AsyncTask() on the specified ENamedThreads. This is important because any local UObject that you might make in a coroutine is technically living in a "vanilla C++" struct without a UPROPERTY and can be eligible for garbage collection while you're on another thread:

FAsyncCoroutine DontDoThisAtHome()
{
    UObject* Obj = NewObject
   ();

    
   co_await 
   Async::MoveToThread(ENamedThreads::AnyBackgroundThreadNormalTask);
    
   // You're no longer synchronously running on the game thread,
    
   // Obj *IS* eligible for garbage collection!

    
   co_await 
   Async::MoveToThread(ENamedThreads::GameThread);
    
   // You're back on the game thread, but Obj could be a dangling
    
   // pointer by now. Use TStrongObjectPtr or another method of keeping
    
   // UObjects alive if you find yourself in this scenario.
}
  
You might also like...
Creating Unreal Engine infinite landscapes/oceans using the editor shader graph and rendering them using Geometry ClipMap. It also allows to spawn mesh on landscape surface. UE5 required
Creating Unreal Engine infinite landscapes/oceans using the editor shader graph and rendering them using Geometry ClipMap. It also allows to spawn mesh on landscape surface. UE5 required

Procedural Landscapes and Oceans in Unreal Engine 5 using Editor Shader Graph Latest version of this project is available as a plugin for UE 4.26+ on

Unreal Engine Plugin to wrap Nuitrack SDK ( skeleton tracking solution by 3DiVi )
Unreal Engine Plugin to wrap Nuitrack SDK ( skeleton tracking solution by 3DiVi )

Nuitrack for Unreal Engine Unreal Engine plugin to bridge Nuitrack. Nuitrack is a middleware to provide 3d skeleton tracking solution using a depth se

Unreal Engine 4 plugin for SteamVR passthrough camera support

SteamVR Passthrough Plugin This Unreal Engine 4 plugin adds SteamVR passthrough camera support using the OpenVR TrackedCamera API. Example project: ht

HLSL Material for Unreal Engine

HLSL Material for Unreal Engine Ever wanted to write complex material functions directly in HLSL? Now you can! Unreal Engine 4.26, 4.27 and 5.0 are su

Building Escape is a simple room escape game made with Unreal Engine 4.27 and C++.
Building Escape is a simple room escape game made with Unreal Engine 4.27 and C++.

Building-Escape Building Escape is a simple room escape game made with Unreal Engine and C++. The main purpose of the game is to find a way to escape

An Unreal Engine 4 SDK generator using SdkGenny

UE4Genny UE4Genny is an SDK generator for Unreal Engine 4 games. It aims to provide a functional SDK that requires little to no editing after generati

Unreal Engine OpenDRIVE plugin
Unreal Engine OpenDRIVE plugin

Unreal Engine OpenDRIVE plugin This plugin allows you to manipulate your OpenDRIVE road network in Unreal Engine. It is built around esmini's RoadMana

An Unreal Engine 4 silent aim method, not usable on all games. Tested on Fortnite, Rogue Company, Bloodhunt, and Splitgate.

UE4-Silent-Aim An Unreal Engine 4 silent aim method, not usable on all games. Only tested on Fortnite, Rogue Company, Bloodhunt, and Splitgate. Done t

Shows Unreal Engine logs in-game using ImGui
Shows Unreal Engine logs in-game using ImGui

BYG Imgui Logger Displays Unreal's UE_LOG output in an ImGui window. Heavily based on the Console example from imgui_demo.cpp included with ImGui. Fea

Comments
  • Undefined behaviour

    Undefined behaviour

    https://github.com/landelare/ue5coro/blob/ec6f8c644cd46ad6ff0cdd2fc68d068dd3fd2d0f/Source/UE5Coro/Public/UE5Coro/Generator.h#L190

    co_yield float{} in generator ==> your promise saves pointer to float value in void*, so next step .Current() returns reference to int, but float under it! And dont use static cast with void* please!

    opened by kelbon 1
  • Handle *this argument for world context + MSVC crash avoidance

    Handle *this argument for world context + MSVC crash avoidance

    I totally understand if you don't want to make the conversion to normal templates (which was necessary to get around an MSVC crash on 17.1.3 - latest VS2022) - I can separate that out and just have the *this bit if you want.

    When a coroutine is called as a member function (rather than a static/global) the compiler attempts to pass *this as the first argument. For a UObject that'd end up being UObject& so the existing Init(const UObject*) overload wasn't catching it.

    Additionally, I had to do some SFINAE trickery on the "default" Init(TFirstArg&, TArgs&...) or else it would get called instead of the UObject overloads (probably because the object being passed in was a derivative of UObject). Not sure if there's a more elegant way to handle that.

    opened by redxdev 1
Releases(v1.5)
  • v1.5(Dec 24, 2022)

  • v1.4(Nov 16, 2022)

    Tested against UE 5.0, 5.1, with preliminary support for 5.2. Note that 5.0 is still affected by UE-22342 a.k.a. UE-159823, which in turn affects latent actions.

    Features:

    • TFuture is now directly co_awaitable. #include "UE5Coro/AsyncAwaiters.h"
    • co_awaiting WhenAny now returns the (0-based) index of the first awaitable that finished.
    • co_awaiting AsyncLoadObject and AsyncLoadClass now returns the loaded object.
      • AsyncLoadObject is now a template returning the same type as the type parameter to TSoftObjectPtr.
    • Latent::AsyncLoadPackage has been added.
    • Async::MoveToNewThread has been added. It lets you co_await into a newly-started thread with additional control over priority, affinity, etc. instead of using thread pools, without having to implement FRunnable yourself.
    • All 9 async collision queries from UWorld have received coroutine wrappers in the Latent namespace. co_awaiting these will return a result array, with possibly 0 or 1 element depending on the kind of query and its success.
    • Functions taking double time values (Seconds, Timeline) support diagnostic NaN checks (EnsureOnNaNFail cvar, ENABLE_NAN_DIAGNOSTIC)
    • Natvis and additional debug features when compiled with UE5CORO_DEBUG (default on for Debug and Development and off for Shipping, see UE5Coro/Definitions.h)
      • Most of these are deliberately undocumented without any expectation of stability across versions, see the beginning of UE5Coro::Private::FPromise.
      • FAsyncCoroutine::SetDebugName is a new static method that can be called from any coroutine returning FAsyncCoroutine to attach a debug string to it. To simplify caller code, it's available but has no effect when UE5CORO_DEBUG is off so that usages of it don't need to be guarded with #ifs.

    Update notes and incompatibilities:

    • co_awaiting UE::Tasks might need #include "UE5Coro/TaskAwaiters.h" after this update to work if you're not using the UE5Coro.h meta-header.
    • Awaiters in the UE5Coro::Latent namespace came with an implicit promise that they'd react to the latent action being aborted within 1 tick and unwind your stack. This has been relaxed to reacting within 2 ticks (in practice, very often still 1).
    Source code(tar.gz)
    Source code(zip)
  • v1.3.2(Oct 8, 2022)

  • v1.3.1(Sep 27, 2022)

  • v1.3(Sep 5, 2022)

    Features:

    • Support for the UE::Tasks system
      • UE5Coro::Tasks::MoveToTask lets you launch and co_await into a UE::Tasks::TTask<void>
      • UE::Tasks::TTask<T> is co_awaitable. Non-void TTask<T>s have a value of T& (not T!) which matches TTask<T>::GetResult()
    • Aggregate awaiters: UE5Coro::WhenAny and UE5Coro::WhenAll
    • UE5Coro::TAwaitable concept for everything that can be co_awaited (now and future versions)
    • Support for C++ exceptions when UE5Coro is compiled with bEnableExceptions = true;. Don't expect much.
    • Preliminary UE5.1 support

    Fix:

    • Latent::NextTick() now resumes on time when not used in a latent action

    Deprecations:

    • UE5Coro::Latent::FramesUE5Coro::Latent::Ticks provides better timing behavior
    Source code(tar.gz)
    Source code(zip)
  • v1.2(Jun 28, 2022)

    Features:

    • #include "UE5Coro.h" now works as a one-liner that includes every other header, IWYU still remains supported
    • FAsyncCoroutine received a new multicast delegate for tying it to code that expects callbacks
    • FAsyncCoroutine is now co_awaitable
    • New awaiter for HTTP requests
    • Latent timeline coroutine
    • Internal UWorld timekeeping changed to use doubles to prepare for UE5.1 (it's technically a waste of CPU on UE5.0 for now)

    Fix:

    • TGenerator was incorrectly reinterpreting co_yielded values as its type, they're now properly using automatic type conversion (#2)
    Source code(tar.gz)
    Source code(zip)
  • v1.1.1(Apr 25, 2022)

  • v1.1(Apr 15, 2022)

    • Exposed FPendingLatentAction's virtuals to coroutines in the form of RAII scope guards (see LatentCallbacks.h)
    • Latent::Abort renamed to Latent::Cancel to better separate it from NotifyActionAborted (breaking change)
    • New awaiter: Latent::Until
    Source code(tar.gz)
    Source code(zip)
  • v1.0(Apr 5, 2022)

Owner
Laura Andelare
Just a red panda writing Unreal Engine code.
Laura Andelare
Design-agnostic node editor for scripting game’s flow in Unreal Engine

Flow plug-in for Unreal Engine provides a graph editor tailored for scripting flow of events in virtual worlds. It's based on a decade of experie

Moth Cocoon 555 Jan 4, 2023
Niagara UI Renderer | Free Plugin for Unreal Engine 4

Niagara UI Renderer | Free Plugin for Unreal Engine 4 Niagara UI Plugin adds Niagara Particle System Widget that allows you to render Niagara particle

null 156 Dec 19, 2022
Third-person survival game for Unreal Engine 4 made entirely in C++.

Third-person survival game for Unreal Engine 4 made entirely in C++. Originally built as a 6 section tutorial series, now available as open-source C++ sample project.

Tom Looman 2.8k Dec 30, 2022
RenderStream plugin for Unreal Engine

This project relies on http://disguise.one software to function. For the plugin setup process - please see https://help.disguise.one/Content/Configuri

disguise 41 Dec 19, 2022
Simple CSV localization system for Unreal Engine 4

BYG Localization We wanted to support fan localization for Industries of Titan and found that Unreal's built-in localization system was not exactly wh

Brace Yourself Games 56 Dec 8, 2022
Edycja PianoFall zrobiona na Unreal Engine

PianoFall - Unreal Engine Edition Edycja PianoFall zrobiona na Unreal Engine (mój pierwszy projekt w UE) Obsługa Po uruchomieniu programu i wciśnięciu

Nadwey 4 Jun 17, 2021
Lambda support for Unreal Engine dynamic delegates

DynamicLambda Lambda support for Unreal Engine dynamic delegates This is experimental feature. Now only parametless lambdas are supported To see more

Andrew Derkach 28 Nov 29, 2022
Dialogue scripting language for Unreal Engine

Supertalk Welcome to Supertalk! This is a simple dialogue scripting language for Unreal Engine created due to frustration with visual dialogue tree wo

Sam Bloomberg 41 Nov 18, 2022
An Unreal Engine 4 Dungeon Generator

DungeonGenerator A (hopefully) handy Unreal Engine 4 Dungeon Generator Read the full docs here: https://github.com/orfeasel/DungeonGenerator/blob/main

Orfeas Eleftheriou 69 Dec 28, 2022
Procedural Mesh Modeling Toolkit for Unreal Engine Artists

OpenLand Mesh Procedural Mesh Modeling Toolkit for Unreal Engine Artists. Installation Get it via the marketplace ??️ For non-commercial projects, you

GDi4K 26 Nov 19, 2022