Overview
CoroFlow is a C++-first library that brings C++20 coroutines to Unreal Engine. Asynchronous work that normally needs timers, delegates, latent-action classes and callback chains becomes sequential code: co_await a delay, an asset load, a line trace, a thread switch or another coroutine and the function continues where it left off.
- Latent UFUNCTIONs without boilerplate: return
FVoidCoroutine from a meta=(Latent) function and Blueprints get a normal latent node; the coroutine is cleaned up if its object is destroyed.
- Plain C++ coroutines:
TCoroutine<T> with results, continuations, waiting and cancellation.
- Game-thread timing: next tick, N ticks, seconds (dilated, unpaused, real, audio), predicates and per-actor dilation.
- Threads: move between the game thread, UE::Tasks, named threads, thread pools and new threads.
- Engine integration: async loading, async traces/sweeps/overlaps, timelines, tick time budgets, HTTP, animation montages and notifies, AI movement and pathfinding.
- Composition: WhenAny, WhenAll, Race, awaitable events and semaphores, generators with
co_yield.
- Gameplay Ability System integration (optional module) and a Gameplay Debugger category.
Requirements
| Requirement | Details |
| Unreal Engine | 5.8 (verified clean) |
| Modules | CoroFlow (Runtime), CoroFlowAI (Runtime), CoroFlowDebug (Runtime), CoroFlowGAS (Runtime), CoroFlowK2 (UncookedOnly) |
| Dependencies | Optional: engine plugin GameplayAbilities (only for the CoroFlowGAS module; CoroFlow does not require it) |
| Language | C++ (C++20, the UE 5.8 default); coroutines can be exposed to Blueprints as latent nodes |
| Platforms | No platform restriction in the plugin; 64-bit targets only (enforced at compile time). Win64 is built and verified by Alpha XP; other 64-bit platforms are supported by the code, not verified by Alpha XP. MSVC builds need toolset 14.41 or newer. |
Installation
- Install CoroFlow from the launcher into your engine, or copy the
CoroFlow folder into your project's Plugins folder, and enable it in Edit > Plugins.
- Make sure every
Target.cs uses the latest settings: DefaultBuildSettings = BuildSettingsVersion.Latest; and IncludeOrderVersion = EngineIncludeOrderVersion.Latest;. Do not force CppStandard to C++17 in modules that use CoroFlow.
- Add the modules you use to your
.Build.cs, e.g. PublicDependencyModuleNames.AddRange(new[] { "CoroFlow", "CoroFlowAI" });, and include only the header named after each module (CoroFlow.h, CoroFlowAI.h, CoroFlowGAS.h).
- For
CoroFlowGAS, enable the engine's GameplayAbilities plugin in your project.
- Regenerate project files and build.
Verifying the installAdd the Quick start function below to an actor, build, and search the actor's Blueprint graph for its name — it appears as a latent node with a clock icon.
IncludesDo not include headers from the CoroFlow/ sub-folders directly; they stop with a compile error. Always include the module header. Types in CoroFlow::Private are implementation details — store them with auto.
Quick start
C++ (latent UFUNCTION)
// MyActor.h
#include "CoroFlow.h"
UFUNCTION(BlueprintCallable, meta = (Latent, LatentInfo = LatentInfo))
FVoidCoroutine LoadAndSpin(TSoftObjectPtr<UStaticMesh> Mesh, FLatentActionInfo LatentInfo);
// MyActor.cpp
using namespace CoroFlow;
FVoidCoroutine AMyActor::LoadAndSpin(TSoftObjectPtr<UStaticMesh> Mesh, FLatentActionInfo LatentInfo)
{
co_await Latent::Seconds(0.5); // does not block the game thread
UStaticMesh* Loaded = co_await Latent::AsyncLoadObject(Mesh);
co_await Async::MoveToTask(); // continue on a worker thread
double Sum = 0;
for (int32 i = 0; i < 1000000; ++i)
Sum += FMath::Sqrt(static_cast<double>(i));
co_await Async::MoveToGameThread(); // back to the game thread
co_await Latent::Timeline(this, 0, 360, 2.0, [this](double Angle)
{
SetActorRotation(FRotator(0, Angle, 0));
});
}
Blueprint
- Build the code above, open the actor's Blueprint and search for
Load And Spin: it is a latent node (clock icon) with a Mesh input; the coroutine return value is hidden.
- Connect the exec pins; the output exec fires when the coroutine finishes. Destroying the actor while it runs cancels the coroutine cleanly.
C++ (plain coroutine with a result)
TCoroutine<int32> CountAsync()
{
co_await CoroFlow::Async::MoveToTask();
co_return 42;
}
TCoroutine<> UseIt()
{
int32 Value = co_await CountAsync(); // awaiting a TCoroutine<T> yields T
co_await CoroFlow::Async::MoveToGameThread();
UE_LOG(LogTemp, Display, TEXT("Value %d"), Value);
}
Blueprint latent nodes
CoroFlow has no Blueprint function library of its own. Instead, the CoroFlowK2 editor module turns every BlueprintCallable UFUNCTION that returns FVoidCoroutine into a call node with the coroutine pin hidden (default category Call Coroutine when the function has none). With meta=(Latent, LatentInfo=...) the node is latent and continues when the coroutine completes.
| Node | Type | Description |
| FVoidCoroutine | Class | USTRUCT wrapper of TCoroutine<>; return it from UFUNCTIONs. IsValid() tells whether it represents a real call. |
| FForceLatentCoroutine | Class | Take it as a parameter to force latent mode without an FLatentActionInfo; hidden on Blueprint nodes. |
| UK2Node_CoroFlowCallCoroutine | Class | Editor node class that hides the coroutine pins (created automatically). |
| On Succeeded / On Failed | Event | Delegates of UCoroFlowSimpleAbilityTask, available on your ability-task subclasses (see GAS). |
Coroutine types
| Node | Type | Description |
| TCoroutine | Class | Return type for C++ coroutines (TCoroutine<> or TCoroutine<T>). Runs in async mode, or latent mode when given FLatentActionInfo, FForceLatentCoroutine or TLatentContext. |
| GetResult / MoveResult | Callable | Read (or move) the result of a completed TCoroutine<T>. |
| IsDone | Callable | Whether the coroutine has completed. |
| WasSuccessful | Callable | Completed without being cancelled. |
| Wait | Callable | Block the calling thread until done or timeout (milliseconds). |
| ContinueWith / ContinueWithWeak | Callable | Run a callback when it completes (weak variant only if the given object/pointer is still alive). |
| Cancel | Callable | Request cancellation. |
| GetDebugName / SetDebugName | Callable | Debug name shown in the Gameplay Debugger and debuggers. |
| CompletedCoroutine / FailedCoroutine | Callable | Static already-finished coroutines. |
| FromResult / FromFailure | Callable | Create an already-completed (or failed) TCoroutine<T>. |
| TManualCoroutine | Class | A coroutine completed by hand with SetResult / TrySetResult (bridges callback APIs). |
| TLatentContext | Class | Parameter that forces latent mode and supplies the owning object/world for lambdas and non-member coroutines. |
Latent awaiters (namespace CoroFlow::Latent)
Latent awaiters run on the game thread through the latent action manager. They can be used from latent and async coroutines.
| Node | Type | Description |
| NextTick | Callable | Resume on the next tick. |
| Ticks | Callable | Resume after N ticks. |
| Until | Callable | Resume once a predicate returns true (checked every tick). |
| Seconds | Callable | Delay in game time (affected by pause and dilation). |
| UnpausedSeconds | Callable | Delay that ignores pause. |
| RealSeconds | Callable | Delay in real time (ignores pause and dilation). |
| AudioSeconds | Callable | Delay in audio time. |
| SecondsForActor / UnpausedSecondsForActor | Callable | Delays that honour an actor's custom time dilation. |
| UntilTime / UntilUnpausedTime / UntilRealTime / UntilAudioTime | Callable | Resume when the respective world clock reaches a value. |
| Timeline | Callable | Call a function every tick with a value interpolated from From to To over a duration; UnpausedTimeline, RealTimeline, AudioTimeline use the other clocks. |
| FTickTimeBudget | Class | co_await it inside a loop to continue on the next tick only once the per-tick budget (Seconds, Milliseconds, Microseconds) is used up. |
| Chain / ChainEx | Callable | Wrap any existing latent UFUNCTION and await its completion. |
| FOnAbnormalExit / FOnActionAborted / FOnObjectDestroyed | Class | Scoped callbacks that run if a latent coroutine is aborted or its owner destroyed. |
Loading & collision (namespace CoroFlow::Latent)
| Node | Type | Description |
| AsyncLoadObject / AsyncLoadObjects | Callable | Load soft object pointers; the result is the loaded object(s). |
| AsyncLoadClass / AsyncLoadClasses | Callable | Load soft class pointers. |
| AsyncLoadPrimaryAsset / AsyncLoadPrimaryAssets | Callable | Load primary assets (optionally with bundles) through the Asset Manager. |
| AsyncPreloadPrimaryAssets | Callable | Preload primary assets and bundles. |
| AsyncChangeBundleStateForPrimaryAssets / AsyncChangeBundleStateForMatchingPrimaryAssets | Callable | Change loaded bundles and wait for completion. |
| AsyncLoadPackage | Callable | Load a package by path; the result is the package. |
| AsyncLineTraceByChannel / AsyncLineTraceByObjectType / AsyncLineTraceByProfile | Callable | Asynchronous line traces; the result is a TArray of hit results. |
| AsyncSweepByChannel / AsyncSweepByObjectType / AsyncSweepByProfile | Callable | Asynchronous shape sweeps. |
| AsyncOverlapByChannel / AsyncOverlapByObjectType / AsyncOverlapByProfile | Callable | Asynchronous overlap tests. |
Threads & timing (namespace CoroFlow::Async)
| Node | Type | Description |
| MoveToGameThread | Callable | Continue on the game thread. |
| MoveToTask | Callable | Continue in the UE::Tasks system on a worker thread. |
| MoveToThread | Callable | Continue on a named thread (ENamedThreads::Type). |
| MoveToSimilarThread | Callable | Re-queue on the same kind of thread as now. |
| MoveToThreadPool | Callable | Continue on a queued thread pool (default GThreadPool). |
| MoveToNewThread | Callable | Continue on a newly created thread (priority, affinity, flags). |
| Yield | Callable | Give other work on the current thread a chance to run. |
| PlatformSeconds / PlatformSecondsAnyThread | Callable | Wait for platform time to pass (resume on the original thread / any thread). |
| UntilPlatformTime / UntilPlatformTimeAnyThread | Callable | Wait until a platform time. |
| Chain | Callable | Call a function that takes a completion delegate and await the delegate. |
Implicitly awaitable without wrappers: TCoroutine<T>, UE::Tasks::TTask<T>, TFuture<T> and engine delegates, including dynamic and multicast delegates (the await result holds the delegate's parameters).
Aggregates & threading primitives
| Node | Type | Description |
| WhenAny | Callable | Resume when any of the awaitables completes; the result is its index. CoroFlow::Latent::WhenAny runs the wait in latent mode. |
| WhenAll | Callable | Resume when all awaitables complete (latent variant available). |
| Race | Callable | Resume when the first coroutine completes and cancel the others. |
| FAwaitableEvent | Class | Awaitable event with Trigger / Reset (auto- or manual-reset). |
| FAwaitableSemaphore | Class | Awaitable counting semaphore with Unlock. |
Cancellation
| Node | Type | Description |
| Cancel | Callable | TCoroutine::Cancel() requests cancellation; it is processed at the coroutine's next co_await. |
| FSelfCancellation | Class | co_await FSelfCancellation(); cancels the current coroutine (latent callers will not continue). |
| FinishNowIfCanceled | Callable | Process a pending cancellation immediately. |
| IsCurrentCoroutineCanceled | Callable | Poll for cancellation in long loops. |
| FCancellationGuard | Class | While in scope, cancellation requests are deferred. |
| FOnCoroutineCanceled | Class | Scoped callback that runs only if the coroutine is cancelled. |
Generators, HTTP, animation
| Node | Type | Description |
| TGenerator | Class | Lazy sequences with co_yield; iterate with Resume/Current, range-for or CreateIterator. |
| ProcessAsync | Callable | CoroFlow::Http::ProcessAsync(Request): send an HTTP request; the result is the response pointer and a connected flag. |
| MontageEnded / MontageBlendingOut | Callable | CoroFlow::Anim: wait for a montage to end or start blending out. |
| NextNotify | Callable | Wait for a named anim notify on an anim instance. |
| PlayMontageNotifyBegin / PlayMontageNotifyEnd | Callable | Wait for Play Montage notify begin/end (optionally by name). |
AI (module CoroFlowAI, namespace CoroFlow::AI)
| Node | Type | Description |
| AIMoveTo | Callable | Issue an AI move to a location or actor; the result is EPathFollowingResult. |
| SimpleMoveTo | Callable | Simple move for any controller (location or actor). |
| FindPath | Callable | Asynchronous pathfinding; the result is the query result and the path. |
Gameplay Ability System (module CoroFlowGAS)
Optional: enable the engine's GameplayAbilities plugin and add "CoroFlowGAS" to your Build.cs. Ability coroutines return CoroFlow::GAS::FAbilityCoroutine.
| Node | Type | Description |
| UCoroFlowGameplayAbility | Class | Override ExecuteAbility with a coroutine instead of ActivateAbility; its completion ends the ability. Task(Object) awaits an ability task or async action. Property Replicate Ability End. |
| UCoroFlowAbilityTask | Class | Override Execute with a coroutine; Succeeded / Failed are called on completion or cancellation. |
| UCoroFlowSimpleAbilityTask | Class | Ability task with ready-made On Succeeded / On Failed delegates. |
CoroFlow::GAS::FAbilityCoroutine UMyAbility::ExecuteAbility(FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo, FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
co_return;
co_await CoroFlow::Latent::Seconds(1); // ability ends when the coroutine returns
}
Gameplay Debugger
The CoroFlowDebug module adds a CoroFlow Gameplay Debugger category listing running coroutines. It needs coroutine tracking, which is off by default because of its runtime cost: add CoroFlowModuleRules.bEnableCoroutineTracking = true; to your Target.cs. Console variables: CoroFlow.MaxDisplayedCoroutines (default 20) and CoroFlow.MaxDisplayedCoroutinesOnTarget (default 5). CoroFlowDebug.bRegisterConditionalTextFormatArgumentModifier = true; (in every Target.cs) makes the |conditional text-format modifier available to your own localized text.
Troubleshooting
| Symptom | Cause and fix |
co_await / coroutine errors, or "requires C++20" | A Target.cs or Build.cs forces an older standard or legacy settings. Use BuildSettingsVersion.Latest / EngineIncludeOrderVersion.Latest and remove any CppStandard = Cpp17. |
| "Do not #include individual headers directly" | Include CoroFlow.h (or CoroFlowAI.h / CoroFlowGAS.h) instead of files under CoroFlow/. |
| "CoroFlow requires MSVC v14.41 or newer" | Update the Visual Studio C++ toolset to 14.41 or later. |
| The Blueprint node is not latent | The UFUNCTION must return FVoidCoroutine and have meta = (Latent, LatentInfo = LatentInfo) with an FLatentActionInfo parameter. |
Code after co_await runs on the wrong thread | Async coroutines resume where the awaited work completes. Add co_await Async::MoveToGameThread(); before touching UObjects. |
| CoroFlowGAS fails to link or abilities do not start | Enable the GameplayAbilities plugin and add CoroFlowGAS to Build.cs; implement ExecuteAbility as a coroutine (do not call EndAbility yourself). |
| Gameplay Debugger says tracking is disabled | Set CoroFlowModuleRules.bEnableCoroutineTracking = true; in the Target.cs and rebuild. |