
A deep dive into every threading layer Unreal Engine exposes. Core-pinned FRunnable workers eliminate cross-core cache invalidation; an MPSC lock-free task queue handles cross-thread dispatch; a DAG task graph fires tasks only when all upstream dependencies complete. Game-thread result marshalling and a background prime-sieve demo round out the coverage.
RunnableGoon) — each thread affinity-masked to 1 dedicated CPU core via SetThreadAffinityMask, eliminating cross-core cache invalidation with 0 lock contention on affinity.TQueue<TSharedPtr<ITask>, EQueueMode::Mpsc> with 1 scoped critical section per drain cycle for safe cross-thread task dispatch.TaskGraphManager) — arbitrary dependency chains; each node fires its lambda enqueue only when all upstream dependencies signal completion, supporting 0 premature executions.UpdatePositionsTask) — background threads compute transform data; 1 async task per batch marshals results back to the game thread for actor updates.PrimeCalculationTask) — heavyweight CPU sieve offloaded to worker threads with 1 thread-safe dequeue for result delivery to the game thread.
Each worker is pinned to a specific CPU core on startup. Tasks arrive via MPSC queue; ExecuteTasks() drains the queue under a single scoped lock, keeping contention windows minimal.
class RunnableGoon : public FRunnable
{
public:
RunnableGoon(EThreadPriority InPriority,
TQueue<TSharedPtr<ITask>, EQueueMode::Mpsc>& InTaskQueue,
FCriticalSection& InCritSec,
int32 CoreAffinity);
virtual uint32 Run() override
{
// Pin to specific CPU core — eliminates cross-core cache invalidation
HANDLE hThread = GetCurrentThread();
SetThreadAffinityMask(hThread, static_cast<DWORD_PTR>(1ULL << CoreAffinity));
while (StopTaskCounter.GetValue() == 0)
ExecuteTasks();
return 0;
}
virtual void Stop() override { StopTaskCounter.Increment(); }
private:
FThreadSafeCounter StopTaskCounter;
TQueue<TSharedPtr<ITask>, EQueueMode::Mpsc>& TaskQueue;
FCriticalSection& TaskQueueCritSec;
int32 CoreAffinity;
void ExecuteTasks()
{
FScopeLock Lock(&TaskQueueCritSec);
TSharedPtr<ITask> Task;
while (TaskQueue.Dequeue(Task))
if (Task.IsValid()) Task->Execute();
}
};
TaskGraphManager tracks an arbitrary DAG via FTaskNode. When all of a node's upstream dependencies complete, OnReadyToExecute fires and enqueues it into the thread pool.
class TaskGraphManager
{
public:
void AddTask(TSharedPtr<FTaskNode> Node)
{
FScopeLock Lock(&TaskCritSec);
NewTaskNodes.Add(Node);
// Lambda fires when all upstream deps are done
Node->OnReadyToExecute.AddLambda([this, Node]()
{
PoolManager->AddTask(Node->GetTask());
});
}
void Execute()
{
{
FScopeLock Lock(&TaskCritSec);
if (bIsExecuting) return;
bIsExecuting = true;
ExecutingTaskNodes.Append(NewTaskNodes);
NewTaskNodes.Empty();
}
// Kick off any node whose dependencies are already satisfied
for (auto& Node : ExecutingTaskNodes)
if (Node->AreDependenciesCompleted())
Node->MarkReady(); // triggers OnReadyToExecute → enqueue
{ FScopeLock Lock(&TaskCritSec); bIsExecuting = false; }
}
private:
TArray<TSharedPtr<FTaskNode>> ExecutingTaskNodes;
TArray<TSharedPtr<FTaskNode>> NewTaskNodes;
ThreadPoolManager* PoolManager;
mutable FCriticalSection TaskCritSec;
bool bIsExecuting = false;
};