UnrealThread
class AR51SDK_API UnrealThread // hidden singleton: private ctor + Instance()
A game-thread dispatcher: it marshals work from worker/background threads onto the Unreal main (game) thread, where it is safe to touch UObjects and engine state. Implemented as a hidden singleton — you never construct it or reach for its instance directly; you call the static methods below.
This type is C++ only — none of its members are UFUNCTIONs, so nothing here is reachable from Blueprint. It exists for SDK and advanced integration C++ code that runs off the game thread (e.g. a gRPC consume loop) and needs to hop back onto it.
Pick a method by what you need:
- On the game thread already? Guard with
IsMainThread. - Need the result now, blocking?
Invoke(synchronous). - Fire-and-forget, inspect later?
BeginInvoke(asynchronous; returns a task handle).
None of these methods carry positional/length values, so no cm↔m conversion applies here. (Unreal is centimeters/degrees; AR 51 mocap data is meters — conversion lives in the data/transform layer, not the threading utilities.)
Method summary
Static — threading
| Method | Returns | |
|---|---|---|
IsMainThread | bool | C++ only |
Invoke | void / T | C++ only · synchronous · overloads |
BeginInvoke | shared_ptr<TaskBase> / shared_ptr<Task<T>> | C++ only · asynchronous · overloads |
Static — lifecycle ⚠️ likely internal
| Method | Returns | |
|---|---|---|
Tick / Start / Stop / Clear | void | C++ only |
→ Full descriptions in Method details below.
Method details
IsMainThread
static bool IsMainThread();
Whether the calling code is currently running on the Unreal main/game thread.
true if called from the game thread; false from any worker thread// Skip the marshal cost when we're already on the game thread.
auto Apply = [Char]{ Char->ApplyOnSkeletalMeshes(); };
if (UnrealThread::IsMainThread())
Apply();
else
UnrealThread::Invoke(Apply, TEXT("ApplyPose"));
Invoke
// (1) void result — blocks until done
static void Invoke(const std::function<void()>& action,
const FString& name = "Untitled");
// (2) typed result — runs on the game thread, returns its value
template<typename T>
static T Invoke(const std::function<T()>& action,
const FString& name = "Untitled");
// (3) typed result with cancellation fallback
template<typename T>
static T Invoke(const std::function<T()>& action,
const T& canceledValue);
Schedules action on the main thread and blocks the caller until it completes (a synchronous marshal). Use this from a worker thread when you must safely read or mutate UObjects / engine state and need the call to finish — or its result returned — before continuing.
- (1) runs a
voidaction and waits for it. - (2) runs an action returning
Tand hands back its result. - (3) same as (2) but, if the task is canceled (e.g. the dispatcher is not running), returns
canceledValueinstead of the action's result.
The optional name labels the queued task for diagnostics/logging.
void or T)"Untitled")canceledValue for (3)// From a worker thread, read engine state synchronously and use the result.
const FVector Head = UnrealThread::Invoke<FVector>(
[Char]{ return Char->GetHeadPosition(); }, // UE cm
/*canceledValue*/ FVector::ZeroVector);
Invoke blocks. Never call it from the game thread waiting on the game thread — that deadlocks. Guard with IsMainThread (run the work inline if already on the game thread).
BeginInvoke
// (1) void result — returns an untyped handle
static std::shared_ptr<TaskBase> BeginInvoke(const std::function<void()>& action,
const FString& name = "Untitled");
// (2) typed result — returns a typed handle carrying T
template<typename T>
static std::shared_ptr<Task<T>> BeginInvoke(const std::function<T()>& action,
const FString& name = "Untitled");
Fire-and-forget asynchronous dispatch: queues action to run on the main thread and returns immediately with a task handle you can await or inspect later — TaskBase for the void overload, Task<T> for the typed one. If the dispatcher is not running, the typed overload returns an already-canceled task.
void or T)"Untitled")// Queue work on the game thread without blocking; grab the result when ready.
auto Task = UnrealThread::BeginInvoke<int32>(
[Consumer]{ return Consumer->GetCharacters().Num(); });
// ... later, on any thread:
Task->Wait();
if (Task->IsCompleted() && !Task->IsCanceled())
const int32 Count = Task->GetResult();
Task handles
std::shared_ptr<TaskBase> // returned by the void BeginInvoke
std::shared_ptr<Task<T>> // returned by the typed BeginInvoke
The lightweight handles returned by BeginInvoke. You consume these as return values; you do not construct them (their constructors and the Run/Cancel machinery are internal). Both expose:
bool IsCompleted()— the task has finished running.bool IsCanceled()— the task was canceled (e.g. the dispatcher was not running) and never produced a result.void Wait()— block the caller until the task completes (or is canceled).GetResult()— onTask<T>, the action'sTresult (valid once completed and not canceled).
Lifecycle
static void Tick(); // drain + run queued tasks on the game thread
static void Start(); // enable processing
static void Stop(); // disable processing
static void Clear(); // empty the queue
Dispatcher pump / lifecycle controls. Tick() drains the queue and runs pending tasks on the main thread (typically once per frame); Start/Stop enable/disable processing; Clear empties the queue.
The SDK is expected to pump these itself (e.g. driving Tick() from its per-frame loop). Integrators normally call only IsMainThread, Invoke, and BeginInvoke. Calling Stop/Clear from game code can strand or drop queued work. ⚠️ needs confirmation that any of these are intended for integrator use.
See also
UNetworkUtils— the other game-dev-facing C++ utility helper (networking)- Utilities & Platform — platform enum, logging, singletons, and these threading helpers
- Class index — all Unreal SDK types