Plugin Documentation

CoroFlow
C++20 coroutines for Unreal Engine gameplay code

Write delays, asset loading, multithreading, timelines, AI moves and gameplay abilities as straight-line code with co_await — and expose coroutines to Blueprints as ordinary latent nodes.

v1.0
UE 5.8
Win64 (verified)
Category Programming
Alpha XP

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

RequirementDetails
Unreal Engine5.8 (verified clean)
ModulesCoroFlow (Runtime), CoroFlowAI (Runtime), CoroFlowDebug (Runtime), CoroFlowGAS (Runtime), CoroFlowK2 (UncookedOnly)
DependenciesOptional: engine plugin GameplayAbilities (only for the CoroFlowGAS module; CoroFlow does not require it)
LanguageC++ (C++20, the UE 5.8 default); coroutines can be exposed to Blueprints as latent nodes
PlatformsNo 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

  1. 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.
  2. 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.
  3. 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).
  4. For CoroFlowGAS, enable the engine's GameplayAbilities plugin in your project.
  5. 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

  1. 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.
  2. 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.

NodeTypeDescription
FVoidCoroutineClassUSTRUCT wrapper of TCoroutine<>; return it from UFUNCTIONs. IsValid() tells whether it represents a real call.
FForceLatentCoroutineClassTake it as a parameter to force latent mode without an FLatentActionInfo; hidden on Blueprint nodes.
UK2Node_CoroFlowCallCoroutineClassEditor node class that hides the coroutine pins (created automatically).
On Succeeded / On FailedEventDelegates of UCoroFlowSimpleAbilityTask, available on your ability-task subclasses (see GAS).

Coroutine types

NodeTypeDescription
TCoroutineClassReturn type for C++ coroutines (TCoroutine<> or TCoroutine<T>). Runs in async mode, or latent mode when given FLatentActionInfo, FForceLatentCoroutine or TLatentContext.
GetResult / MoveResultCallableRead (or move) the result of a completed TCoroutine<T>.
IsDoneCallableWhether the coroutine has completed.
WasSuccessfulCallableCompleted without being cancelled.
WaitCallableBlock the calling thread until done or timeout (milliseconds).
ContinueWith / ContinueWithWeakCallableRun a callback when it completes (weak variant only if the given object/pointer is still alive).
CancelCallableRequest cancellation.
GetDebugName / SetDebugNameCallableDebug name shown in the Gameplay Debugger and debuggers.
CompletedCoroutine / FailedCoroutineCallableStatic already-finished coroutines.
FromResult / FromFailureCallableCreate an already-completed (or failed) TCoroutine<T>.
TManualCoroutineClassA coroutine completed by hand with SetResult / TrySetResult (bridges callback APIs).
TLatentContextClassParameter 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.

NodeTypeDescription
NextTickCallableResume on the next tick.
TicksCallableResume after N ticks.
UntilCallableResume once a predicate returns true (checked every tick).
SecondsCallableDelay in game time (affected by pause and dilation).
UnpausedSecondsCallableDelay that ignores pause.
RealSecondsCallableDelay in real time (ignores pause and dilation).
AudioSecondsCallableDelay in audio time.
SecondsForActor / UnpausedSecondsForActorCallableDelays that honour an actor's custom time dilation.
UntilTime / UntilUnpausedTime / UntilRealTime / UntilAudioTimeCallableResume when the respective world clock reaches a value.
TimelineCallableCall a function every tick with a value interpolated from From to To over a duration; UnpausedTimeline, RealTimeline, AudioTimeline use the other clocks.
FTickTimeBudgetClassco_await it inside a loop to continue on the next tick only once the per-tick budget (Seconds, Milliseconds, Microseconds) is used up.
Chain / ChainExCallableWrap any existing latent UFUNCTION and await its completion.
FOnAbnormalExit / FOnActionAborted / FOnObjectDestroyedClassScoped callbacks that run if a latent coroutine is aborted or its owner destroyed.

Loading & collision (namespace CoroFlow::Latent)

NodeTypeDescription
AsyncLoadObject / AsyncLoadObjectsCallableLoad soft object pointers; the result is the loaded object(s).
AsyncLoadClass / AsyncLoadClassesCallableLoad soft class pointers.
AsyncLoadPrimaryAsset / AsyncLoadPrimaryAssetsCallableLoad primary assets (optionally with bundles) through the Asset Manager.
AsyncPreloadPrimaryAssetsCallablePreload primary assets and bundles.
AsyncChangeBundleStateForPrimaryAssets / AsyncChangeBundleStateForMatchingPrimaryAssetsCallableChange loaded bundles and wait for completion.
AsyncLoadPackageCallableLoad a package by path; the result is the package.
AsyncLineTraceByChannel / AsyncLineTraceByObjectType / AsyncLineTraceByProfileCallableAsynchronous line traces; the result is a TArray of hit results.
AsyncSweepByChannel / AsyncSweepByObjectType / AsyncSweepByProfileCallableAsynchronous shape sweeps.
AsyncOverlapByChannel / AsyncOverlapByObjectType / AsyncOverlapByProfileCallableAsynchronous overlap tests.

Threads & timing (namespace CoroFlow::Async)

NodeTypeDescription
MoveToGameThreadCallableContinue on the game thread.
MoveToTaskCallableContinue in the UE::Tasks system on a worker thread.
MoveToThreadCallableContinue on a named thread (ENamedThreads::Type).
MoveToSimilarThreadCallableRe-queue on the same kind of thread as now.
MoveToThreadPoolCallableContinue on a queued thread pool (default GThreadPool).
MoveToNewThreadCallableContinue on a newly created thread (priority, affinity, flags).
YieldCallableGive other work on the current thread a chance to run.
PlatformSeconds / PlatformSecondsAnyThreadCallableWait for platform time to pass (resume on the original thread / any thread).
UntilPlatformTime / UntilPlatformTimeAnyThreadCallableWait until a platform time.
ChainCallableCall 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

NodeTypeDescription
WhenAnyCallableResume when any of the awaitables completes; the result is its index. CoroFlow::Latent::WhenAny runs the wait in latent mode.
WhenAllCallableResume when all awaitables complete (latent variant available).
RaceCallableResume when the first coroutine completes and cancel the others.
FAwaitableEventClassAwaitable event with Trigger / Reset (auto- or manual-reset).
FAwaitableSemaphoreClassAwaitable counting semaphore with Unlock.

Cancellation

NodeTypeDescription
CancelCallableTCoroutine::Cancel() requests cancellation; it is processed at the coroutine's next co_await.
FSelfCancellationClassco_await FSelfCancellation(); cancels the current coroutine (latent callers will not continue).
FinishNowIfCanceledCallableProcess a pending cancellation immediately.
IsCurrentCoroutineCanceledCallablePoll for cancellation in long loops.
FCancellationGuardClassWhile in scope, cancellation requests are deferred.
FOnCoroutineCanceledClassScoped callback that runs only if the coroutine is cancelled.

Generators, HTTP, animation

NodeTypeDescription
TGeneratorClassLazy sequences with co_yield; iterate with Resume/Current, range-for or CreateIterator.
ProcessAsyncCallableCoroFlow::Http::ProcessAsync(Request): send an HTTP request; the result is the response pointer and a connected flag.
MontageEnded / MontageBlendingOutCallableCoroFlow::Anim: wait for a montage to end or start blending out.
NextNotifyCallableWait for a named anim notify on an anim instance.
PlayMontageNotifyBegin / PlayMontageNotifyEndCallableWait for Play Montage notify begin/end (optionally by name).

AI (module CoroFlowAI, namespace CoroFlow::AI)

NodeTypeDescription
AIMoveToCallableIssue an AI move to a location or actor; the result is EPathFollowingResult.
SimpleMoveToCallableSimple move for any controller (location or actor).
FindPathCallableAsynchronous 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.

NodeTypeDescription
UCoroFlowGameplayAbilityClassOverride 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.
UCoroFlowAbilityTaskClassOverride Execute with a coroutine; Succeeded / Failed are called on completion or cancellation.
UCoroFlowSimpleAbilityTaskClassAbility 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

SymptomCause 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 latentThe UFUNCTION must return FVoidCoroutine and have meta = (Latent, LatentInfo = LatentInfo) with an FLatentActionInfo parameter.
Code after co_await runs on the wrong threadAsync coroutines resume where the awaited work completes. Add co_await Async::MoveToGameThread(); before touching UObjects.
CoroFlowGAS fails to link or abilities do not startEnable the GameplayAbilities plugin and add CoroFlowGAS to Build.cs; implement ExecuteAbility as a coroutine (do not call EndAbility yourself).
Gameplay Debugger says tracking is disabledSet CoroFlowModuleRules.bEnableCoroutineTracking = true; in the Target.cs and rebuild.

Credits

© 2026 Alpha XP — alphaxp.net · Support: support@alphaxp.net. Built and verified for Unreal Engine 5.8.