UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
TaskPrivate.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "Async/EventCount.h"
8#include "Async/Mutex.h"
10#include "Async/TaskTrace.h"
11#include "Async/UniqueLock.h"
12#include "Containers/Array.h"
15#include "CoreGlobals.h"
16#include "CoreTypes.h"
18#include "HAL/Event.h"
19#include "HAL/PlatformProcess.h"
20#include "HAL/PlatformTLS.h"
21#include "HAL/Thread.h"
22#include "Logging/LogCategory.h"
23#include "Logging/LogMacros.h"
24#include "Math/NumericLimits.h"
27#include "Misc/Timeout.h"
28#include "Misc/Timespan.h"
30#include "Templates/EnableIf.h"
31#include "Templates/Invoke.h"
32#include "Templates/MemoryOps.h"
38
39#include <atomic>
40#include <type_traits>
41
42#ifndef WITH_TASKGRAPH_VERBOSE_TRACE
43#define WITH_TASKGRAPH_VERBOSE_TRACE 0
44#endif
45
46#if WITH_TASKGRAPH_VERBOSE_TRACE
47#define TASKGRAPH_VERBOSE_EVENT_SCOPE(Name) TRACE_CPUPROFILER_EVENT_SCOPE(Name)
48#else
49#define TASKGRAPH_VERBOSE_EVENT_SCOPE(Name)
50#endif
51
52namespace UE::Tasks
53{
57
58 // special task priorities for tasks that are never sent to the scheduler
60 {
61 None,
62 Inline, // a task priority for "inline" task execution - a task is executed "inline" by the thread that unlocked it, w/o scheduling
63 TaskEvent, // a task priority used by task events, allows to shortcut task execution
64
65
66 // for integration with named threads
71
76
81
82 Count
83 };
84
85 const TCHAR* ToString(EExtendedTaskPriority ExtendedPriority);
87
88 enum class ETaskFlags
89 {
90 None,
91 DoNotRunInsideBusyWait // do not pick this task for busy-waiting
92 };
93
94 namespace Private
95 {
98 }
99
100 class FPipe;
101
102 namespace Private
103 {
104 class FTaskBase;
105
106 // returns the task (if any) that is being executed by the current thread
107 CORE_API FTaskBase* GetCurrentTask();
108 // sets the current task and returns the previous current task
109 CORE_API FTaskBase* ExchangeCurrentTask(FTaskBase* Task);
110
111 // Returns true if called from inside a task that is being retracted
112 UE_DEPRECATED(5.1, "You should not use this function as it exists only to patch another system and can be removed any time.")
114
115 // An abstract base class for task implementation.
116 // Implements internal logic of task prerequisites, nested tasks and deep task retraction.
117 // Implements intrusive ref-counting and so can be used with TRefCountPtr.
118 // It doesn't store task body, instead it expects a derived class to provide a task body as a parameter to `TryExecute` method. @see TExecutableTask
120 {
122
123 // `ExecutionFlag` is set at the beginning of execution as the most significant bit of `NumLocks` and indicates a switch
124 // of `NumLocks` from "execution prerequisites" (a number of uncompleted prerequisites that block task execution) to
125 // "completion prerequisites" (a number of nested uncompleted tasks that block task completion)
126 static constexpr uint32 ExecutionFlag = 0x80000000;
127
129 // ref-count
130 public:
131 void AddRef()
132 {
133 RefCount.fetch_add(1, std::memory_order_relaxed);
134 }
135
136 void Release()
137 {
138 uint32 LocalRefCount = RefCount.fetch_sub(1, std::memory_order_acq_rel) - 1;
139 if (LocalRefCount == 0)
140 {
141#if !defined(__clang_analyzer__)
142 delete this;
143#endif
144 }
145 }
146
147 uint32 GetRefCount(std::memory_order MemoryOrder = std::memory_order_relaxed) const
148 {
149 return RefCount.load(MemoryOrder);
150 }
151
152 private:
153 std::atomic<uint32> RefCount;
155
156 protected:
158 : RefCount(InitRefCount)
159 {
161 {
162 Prerequisites.Unlock();
163 }
164 }
165
167 {
168 // store debug name, priority and an adaptor for task execution in low-level task. The task body can't be stored as this task
169 // implementation needs to do some accounting before the task is executed (e.g. maintainance of TLS "current task")
171 LowLevelTask.Init(InDebugName, InPriority,
172 [
173 this,
174 // releasing scheduler's task reference can cause task's automatic destruction and so must be done after the low-level task
175 // task is flagged as completed. The task is flagged as completed after the continuation is executed but before its destroyed.
176 // `Deleter` is captured by value and is destroyed along with the continuation, calling the given functor on destruction
178 ]
179 {
180 TryExecuteTask();
181 },
183 );
184 ExtendedPriority = InExtendedPriority;
185
186 CaptureInheritedContext();
187 }
188
189 virtual ~FTaskBase()
190 {
192 TaskTrace::Destroyed(GetTraceId());
193 }
194
195 virtual void ExecuteTask() = 0;
196
197 public:
198 // returns true if it's valid to wait for the task completion.
199 // it's not valid to wait for a task e.g. from inside task's execution, as this would deadlock
200 bool IsAwaitable() const
201 {
202 return FPlatformTLS::GetCurrentThreadId() != ExecutingThreadId.load(std::memory_order_relaxed);
203 }
204
205 bool IsNamedThreadTask() const
206 {
207 return ExtendedPriority >= EExtendedTaskPriority::GameThreadNormalPri;
208 }
209
210 ETaskPriority GetPriority() const
211 {
212 return LowLevelTask.GetPriority();
213 }
214
216 {
217 return ExtendedPriority;
218 }
219
220 // The task will be executed only when all prerequisites are completed. The task type must be a task handle that holds a pointer to
221 // FTaskBase as its `Pimpl` member (see Tasks::TTaskBase).
222 // Must not be called concurrently
223 bool AddPrerequisites(FTaskBase& Prerequisite)
224 {
225 TASKGRAPH_VERBOSE_EVENT_SCOPE(FTaskBase::AddPrerequisites_Single);
226
227 checkf(NumLocks.load(std::memory_order_relaxed) >= NumInitialLocks && NumLocks.load(std::memory_order_relaxed) < ExecutionFlag, TEXT("Prerequisites can be added only before the task is launched"));
228
229 // registering the task as a subsequent of the given prerequisite can cause its immediate launch by the prerequisite
230 // (if the prerequisite has been completed on another thread), so we need to keep the task locked by assuming that the
231 // prerequisite can be added successfully, and release the lock if it wasn't
232 uint32 PrevNumLocks = NumLocks.fetch_add(1, std::memory_order_relaxed); // relaxed because the following
233 // `AddSubsequent` provides required sync
234 checkf(PrevNumLocks + 1 < ExecutionFlag, TEXT("Max number of task prerequisites reached: %d"), ExecutionFlag);
235
236 if (!Prerequisite.AddSubsequent(*this)) // linearisation point, acq_rel semantic
237 {
238 // failed to add the prerequisite (too late), correct the number
239 NumLocks.fetch_sub(1, std::memory_order_relaxed); // relaxed because the previous `AddSubsequent` call provides required sync
240 return false;
241 }
242
243 Prerequisite.AddRef(); // keep it alive until this task's execution
244 Prerequisites.Push(&Prerequisite); // release memory order
245 return true;
246 }
247
248 // The task will be executed only when all prerequisites are completed. The task type must be a task handle that holds a pointer to
249 // FTaskBase as its `Pimpl` member (see Tasks::TTaskBase).
250 // Must not be called concurrently
252 bool AddPrerequisites(const HigherLevelTaskType& Prerequisite)
253 {
254 return Prerequisite.IsValid() ? AddPrerequisites(*Prerequisite.Pimpl) : false;
255 }
256
257 // The task will be executed only when all prerequisites are completed. The task type must be a task handle that holds a pointer to
258 // Must not be called concurrently
259 template<typename HigherLevelTaskType, std::enable_if_t<std::is_same_v<HigherLevelTaskType, FGraphEventRef>>* = nullptr>
260 bool AddPrerequisites(const HigherLevelTaskType& Prerequisite)
261 {
262 return Prerequisite.IsValid() ? AddPrerequisites(*Prerequisite.GetReference()) : false;
263 }
264
265protected:
266 // The task will be executed only when all prerequisites are completed.
267 // Must not be called concurrently.
268 // @param InPrerequisites - an iterable collection of tasks
271 {
273 if (UNLIKELY(PrerequisiteCount <= 0))
274 {
275 return;
276 }
277
278 TASKGRAPH_VERBOSE_EVENT_SCOPE(FTaskBase::AddPrerequisites_Collection);
279
280 checkf(NumLocks.load(std::memory_order_relaxed) >= NumInitialLocks && NumLocks.load(std::memory_order_relaxed) < ExecutionFlag, TEXT("Prerequisites can be added only before the task is launched"));
281
282 // registering the task as a subsequent of the given prerequisite can cause its immediate launch by the prerequisite
283 // (if the prerequisite has been completed on another thread), so we need to keep the task locked by assuming that the
284 // prerequisite can be added successfully, and release the lock if it wasn't
285 uint32 PrevNumLocks = NumLocks.fetch_add(PrerequisiteCount, std::memory_order_relaxed); // relaxed because the following
286 // `AddSubsequent` provides required sync
287
288 bool bUnlockPrerequisites = false;
289
291 for (auto& Prereq : InPrerequisites)
292 {
293 // prerequisites can be either `FTaskBase*` or its Pimpl handle
294 FTaskBase* Prerequisite;
295 using FPrerequisiteType = std::decay_t<decltype(*std::declval<PrerequisiteCollectionType>().begin())>;
296 if constexpr (std::is_same_v<FPrerequisiteType, FTaskBase*>)
297 {
298 Prerequisite = Prereq;
299 }
300 else if constexpr (std::is_same_v<FPrerequisiteType, FGraphEventRef>)
301 {
302 Prerequisite = Prereq.GetReference();
303 }
304 else if constexpr (std::is_pointer_v<FPrerequisiteType>)
305 {
306 Prerequisite = Prereq->Pimpl;
307 }
308 else
309 {
310 Prerequisite = Prereq.Pimpl;
311 }
312
313 if (Prerequisite == nullptr)
314 {
316 continue;
317 }
318
319 if (Prerequisite->AddSubsequent(*this)) // acq_rel memory order
320 {
321 Prerequisite->AddRef(); // keep it alive until this task's execution
323 {
324 Prerequisites.Lock();
325 bLockPrerequisite = false; // don't lock again
327 }
328
329 Prerequisites.PushNoLock(Prerequisite); // relaxed memory order
330 }
331 else
332 {
334 }
335 }
336
337 // This check is here to avoid the data dependency on PrevNumLocks.
338 checkf(PrevNumLocks + GetNum(InPrerequisites) < ExecutionFlag, TEXT("Max number of nested tasks reached: %d"), ExecutionFlag);
339
341 {
342 Prerequisites.Unlock();
343 }
344
345 // unlock for prerequisites that weren't added
347 {
348 NumLocks.fetch_sub(NumCompletedPrerequisites, std::memory_order_release);
349 }
350 }
351public:
352 // The task will be executed only when all prerequisites are completed.
353 // Must not be called concurrently.
354 // @param InPrerequisites - an iterable collection of tasks
357 {
358 AddPrerequisites(InPrerequisites, true /* bLockPrerequisites */);
359 }
360
361 // the task unlocks all its subsequents on completion.
362 // returns false if the task is already completed and the subsequent wasn't added
364 {
365 if (Subsequents.PushIfNotClosed(&Subsequent))
366 {
367 TaskTrace::SubsequentAdded(GetTraceId(), Subsequent.GetTraceId());
368 return true;
369 }
370 return false;
371 }
372
373 // A piped task is executed after the previous task from this pipe is completed. Tasks from the same pipe are not executed
374 // concurrently (so don't require synchronization), but not necessarily on the same thread.
375 // @See FPipe
377 {
378 // keep the task locked until it's pushed into the pipe
379 NumLocks.fetch_add(1, std::memory_order_relaxed); // the order doesn't matter as this happens before the task is launched
380 Pipe = &InPipe;
381 }
382
383 FPipe* GetPipe() const
384 {
385 return Pipe;
386 }
387
388 // Tries to schedule task execution but has additional tracking to ensure a task is not scheduled more than once.
389 // This method should only be used when scheduling more than once is allowed/practical (e.g. FTaskEvent).
390 // Returns false if the task has been triggered before or has incomplete dependencies (prerequisites or is blocked by a pipe).
391 // In the latter case, the task will be automatically scheduled when all dependencies are completed.
393 {
394 if (TaskTriggered.exchange(true, std::memory_order_relaxed))
395 {
396 // Task is already launched
397 return false;
398 }
399
400 // Now that we know we have triggered the task (it might not yet be launched/running based on dependencies) it's now in the
401 // task system and can outlive external references, so we need to keep it alive by holding an internal reference.
402 // It will be released when completed.
403 AddRef();
404 return TryLaunch(TaskSize);
405 }
406
407 // Tries to schedule task execution. Returns false if the task has incomplete dependencies (prerequisites or is blocked by a pipe).
408 // In this case the task will be automatically scheduled when all dependencies are completed.
410 {
411#if UE_TASK_TRACE_ENABLED
412 if (UE_TRACE_CHANNELEXPR_IS_ENABLED(TaskTrace::TaskChannel))
413 {
414 // The params to this are slow to compute
415 TaskTrace::Launched(GetTraceId(), LowLevelTask.GetDebugName(), true, TranslatePriority(LowLevelTask.GetPriority(), ExtendedPriority), TaskSize);
416 }
417#endif
418
419 bool bWakeUpWorker = true;
420 return TryUnlock(bWakeUpWorker);
421 }
422
423 // @return true if the task was executed and all its nested tasks are completed
424 bool IsCompleted() const
425 {
426 return Subsequents.IsClosed();
427 }
428
429 // Tries to pull out the task from the system and execute it. If the task is locked by either prerequisites or nested tasks, tries to
430 // retract and execute them recursively.
431 // WARNING: the function can return `true` even if the task is not completed yet. The `true` means only that the task is already
432 // executed and has no other pending dependencies, but can be in the process of completion (concurrently). The caller still needs
433 // to wait for completion explicitly.
434 CORE_API bool TryRetractAndExecute(FTimeout Timeout, uint32 RecursionDepth = 0);
435
436 // releases internal reference and maintains low-level task state. must be called iff the task was never launched, otherwise
437 // the scheduler will do this in due course
439 {
440 verify(LowLevelTask.TryCancel());
441 }
442
443 // adds a nested task that must be completed before the parent (this) is completed
445 {
447
448 uint32 PrevNumLocks = NumLocks.fetch_add(1, std::memory_order_relaxed); // in case we'll succeed in adding subsequent,
449 // "happens before" registering this task as a subsequent
450 checkf(PrevNumLocks + 1 < TNumericLimits<uint32>::Max(), TEXT("Max number of nested tasks reached: %d"), TNumericLimits<uint32>::Max() - ExecutionFlag);
451 checkf(PrevNumLocks > ExecutionFlag, TEXT("Internal error: nested tasks can be added only during parent's execution (%u)"), PrevNumLocks);
452
453 if (Nested.AddSubsequent(*this)) // "release" memory order
454 {
455 Nested.AddRef(); // keep it alive as we store it in `Prerequisites` and we can need it to try to retract it. it's released on closing the task
456 Prerequisites.Push(&Nested);
457 }
458 else
459 {
460 NumLocks.fetch_sub(1, std::memory_order_relaxed);
461 }
462 }
463
464 // waits for task's completion, with optional timeout. Tries to retract the task and execute it in-place, if failed - blocks until the task
465 // is completed by another thread. If timeout is zero, tries to retract the task and returns immedially after that.
466 // `Wait(FTimespan::Zero())` still tries to retract and execute the task, use `IsCompleted()` to check for completeness.
467 // The version w/o timeout is slightly more efficient.
468 // @return true if the task is completed
470
471 // waits for task's completion. Tries to retract the task and execute it in-place, if failed - blocks until the task
472 // is completed by another thread.
473 CORE_API void Wait();
474
475 // mimics the old tasks (TaskGraph) behaviour on named threads: waiting for a task on a named thread pulls other tasks from this
476 // named thread queue and executes them
477 CORE_API void WaitWithNamedThreadsSupport();
478
480 {
481#if UE_TASK_TRACE_ENABLED
482 return TraceId.load(std::memory_order_relaxed);
483#else
485#endif
486 }
487
488 protected:
489 // tries to get execution permission and if successful, executes given task body and completes the task if there're no pending nested tasks.
490 // does all required accounting before/after task execution. the task can be deleted as a result of this call.
491 // @returns true if the task was executed by the current thread
493 {
495
496 if (!TrySetExecutionFlag())
497 {
498 return false;
499 }
500
501 AddRef(); // `LowLevelTask` will automatically release the internal reference after execution, but there can be pending nested tasks, so keep it alive
502 // it's released either later here if the task is closed, or when the last nested task is completed and unlocks its parent (in `TryUnlock`)
503
504 ReleasePrerequisites();
505
507 ExecutingThreadId.store(FPlatformTLS::GetCurrentThreadId(), std::memory_order_relaxed);
508
509 if (GetPipe() != nullptr)
510 {
511 StartPipeExecution();
512 }
513
514 {
515 UE::FInheritedContextScope InheritedContextScope = RestoreInheritedContext();
518 ExecuteTask();
519 }
520
521 if (GetPipe() != nullptr)
522 {
523 FinishPipeExecution();
524 }
525
526 ExecutingThreadId.store(FThread::InvalidThreadId, std::memory_order_relaxed); // no need to sync with loads as they matter only if
527 // executed by the same thread
529
530 // close the task if there are no pending nested tasks
531 uint32 LocalNumLocks = NumLocks.fetch_sub(1, std::memory_order_acq_rel) - 1; // "release" to make task execution "happen before" this, and "acquire" to
532 // "sync with" another thread that completed the last nested task
533 if (LocalNumLocks == ExecutionFlag) // unlocked (no pending nested tasks)
534 {
535 Close();
536 Release(); // the internal reference that kept the task alive for nested tasks
537 } // else there're non completed nested tasks, the last one will unlock, close and release the parent (this task)
538
539 return true;
540 }
541
542 // closes task by unlocking its subsequents and flagging it as completed
543 void Close()
544 {
547
548 // Push the first subsequent to the local queue so we pick it up directly as our next task.
549 // This saves us the cost of going to the global queue and performing a wake-up.
550 // But if we're a task event, always wake up new workers because the current task could continue executing for a long time after the trigger.
551 bool bWakeUpWorker = ExtendedPriority == EExtendedTaskPriority::TaskEvent;
552
553 for (FTaskBase* Subsequent : Subsequents.Close())
554 {
555 // bWakeUpWorker is passed by reference and is automatically set to true if we successfully schedule a task on the local queue.
556 // so all the remaining ones are sent to the global queue.
557 Subsequent->TryUnlock(bWakeUpWorker);
558 }
559
560 // Clear the pipe after the task is completed (subsequents closed) so that any tasks part of the
561 // pipe are not seen still being executed after FPipe::WaitUntilEmpty has returned.
562 if (GetPipe() != nullptr)
563 {
564 ClearPipe();
565 }
566
567 // release nested tasks
568 ReleasePrerequisites();
569
570 TaskTrace::Completed(GetTraceId());
571
572 // In case a thread is waiting on us to perform retraction, now is the time to try retraction again.
573 StateChangeEvent.NotifyWeak();
574 }
575
576 CORE_API void ClearPipe();
577
578 private:
579 // A task can be locked for execution (by prerequisites or if it's not launched yet) or for completion (by nested tasks).
580 // This method is called to unlock the task and so can result in its scheduling (and execution) or completion
581 bool TryUnlock(bool& bWakeUpWorker)
582 {
583 TASKGRAPH_VERBOSE_EVENT_SCOPE(FTaskBase::TryUnlock);
584
585 FPipe* LocalPipe = GetPipe(); // cache data locally so we won't need to touch the member (read below)
586
587 uint32 PrevNumLocks = NumLocks.fetch_sub(1, std::memory_order_acq_rel); // `acq_rel` to make it happen after task
588 // preparation and before launching it
589 // the task can be dead already as the prev line can remove the lock hold for this execution path, another thread(s) can unlock
590 // the task, execute, complete and delete it. thus before touching any members or calling methods we need to make sure
591 // the task can't be destroyed concurrently
592
594
595 if (PrevNumLocks < ExecutionFlag)
596 {
597 // pre-execution state, try to schedule the task
598
599 checkf(PrevNumLocks != 0, TEXT("The task is not locked"));
600
601 bool bPrerequisitesCompleted = LocalPipe == nullptr ? LocalNumLocks == 0 : LocalNumLocks <= 1; // the only remaining lock is pipe's one (if any)
603 {
604 return false;
605 }
606
607 // this thread unlocked the task, no other thread can reach this point concurrently, we can touch the task again
608
609 if (LocalPipe != nullptr)
610 {
613 {
614 FTaskBase* PrevPipedTask = TryPushIntoPipe();
615 if (PrevPipedTask != nullptr) // the pipe is blocked
616 {
617 // the prev task in pipe's chain becomes this task's prerequisite, to enabled piped task retraction.
618 // its ref count already accounted for this ref. the ref will be released when the prereq is not needed anymore
620 return false;
621 }
622
623 NumLocks.store(0, std::memory_order_release); // release pipe's lock
624 }
625 }
626
627 if (ExtendedPriority == EExtendedTaskPriority::Inline)
628 {
629 // "inline" tasks are not scheduled but executed straight away
630 TryExecuteTask(); // result doesn't matter, this can fail if task retraction jumped in and got execution
631 // permission between this thread unlocked the task and tried to execute it
632 ReleaseInternalReference();
633
634 // Use-after-free territory, do not touch any of the task's properties here.
635 }
636 else if (ExtendedPriority == EExtendedTaskPriority::TaskEvent)
637 {
638 // task events have nothing to execute, try to close it. task retraction can jump in and close the task event,
639 // so this thread still needs to check execution permission
640 if (TrySetExecutionFlag())
641 {
642 // task events are used as an empty prerequisites/subsequents
643 ReleasePrerequisites();
644 Close();
645 ReleaseInternalReference();
646
647 // Use-after-free territory, do not touch any of the task's properties here.
648 }
649 }
650 else
651 {
652 Schedule(bWakeUpWorker);
653
654 // Use-after-free territory, do not touch any of the task's properties here.
655 }
656
657 return true;
658 }
659
660 // execution already started (at least), this is nested tasks unlocking their parent
661 checkf(PrevNumLocks != ExecutionFlag, TEXT("The task is not locked"));
662 if (LocalNumLocks != ExecutionFlag) // still locked
663 {
664 return false;
665 }
666
667 // this thread unlocked the task, no other thread can reach this point concurrently, we can touch the task again
668 Close();
669 Release(); // the internal reference that kept the task alive for nested tasks
670
671 // Use-after-free territory, do not touch any of the task's properties here.
672
673 return true;
674 }
675
676 CORE_API void Schedule(bool& bWakeUpWorker);
677
678 // is called when the task has no pending prerequisites. Returns the previous piped task if any
679 CORE_API FTaskBase* TryPushIntoPipe();
680
681 // only one thread can successfully set execution flag, that grants task execution permission
682 // @returns false if another thread got execution permission first
683 bool TrySetExecutionFlag()
684 {
686 // set the execution flag and simultenously lock it (+1) so a nested task completion doesn't close it before its execution is finished
687 return NumLocks.compare_exchange_strong(ExpectedUnlocked, ExecutionFlag + 1, std::memory_order_acq_rel, std::memory_order_relaxed); // on success
688 // - linearisation point for task execution, on failure - load order doesn't matter
689 }
690
691 void ReleasePrerequisites()
692 {
693 TASKGRAPH_VERBOSE_EVENT_SCOPE(FTaskBase::ReleasePrerequisites);
694 for (FTaskBase* Prerequisite : Prerequisites.PopAll())
695 {
696 TASKGRAPH_VERBOSE_EVENT_SCOPE(FTaskBase::ReleasePrerequisite);
697 Prerequisite->Release();
698 }
699 }
700
701 CORE_API void StartPipeExecution();
702 CORE_API void FinishPipeExecution();
703
704 CORE_API bool WaitImpl(FTimeout Timeout);
705
706 private:
707 // the number of times that the task should be unlocked before it can be scheduled or completed
708 // initial count is 1 for launching the task (it can't be scheduled before it's launched)
709 // reaches 0 the task is scheduled for execution.
710 // NumLocks's the most significant bit (see `ExecutionFlag`) is set on task execution start, and indicates that now
711 // NumLocks is about how many times the task must be unlocked to be completed
712 static constexpr uint32 NumInitialLocks = 1;
713 std::atomic<uint32> NumLocks{ NumInitialLocks };
714
715 FPipe* Pipe{ nullptr };
716
717 FEventCount StateChangeEvent;
718
719 EExtendedTaskPriority ExtendedPriority; // internal priorities, if any
720
721 // Note: Only ever set when the task is launched from Trigger()
722 std::atomic<bool> TaskTriggered = false;
723
724 std::atomic<uint32> ExecutingThreadId = FThread::InvalidThreadId;
725
726#if UE_TASK_TRACE_ENABLED
727 std::atomic<TaskTrace::FId> TraceId{ TaskTrace::GenerateTaskId() };
728#endif
729
730 // stores backlinks to prerequsites, either execution prerequisites or nested tasks (completion prerequisites).
731 // It's populated in three stages:
732 // 1) by adding execution prerequisites, before the task is launched.
733 // 2) by piping, when the previous piped task (if any) is added as a prerequisite. can happen concurrently with other threads accessing prerequisites for
734 // task retraction.
735 // 3) by adding nested tasks. after piping. during task execution.
736 template <typename AllocatorType = FDefaultAllocator>
737 class FPrerequisites
738 {
739 public:
740 void Push(FTaskBase* Prerequisite)
741 {
742 TASKGRAPH_VERBOSE_EVENT_SCOPE(FPrerequisites::Push);
743 UE::TUniqueLock Lock(Mutex);
744 Prerequisites.Emplace(Prerequisite);
745 }
746
747 void PushNoLock(FTaskBase* Prerequisite)
748 {
749 TASKGRAPH_VERBOSE_EVENT_SCOPE(FPrerequisites::PushNoLock);
750 Prerequisites.Emplace(Prerequisite);
751 }
752
754 {
755 TASKGRAPH_VERBOSE_EVENT_SCOPE(FPrerequisites::PopAll);
756 UE::TUniqueLock Lock(Mutex);
757 return MoveTemp(Prerequisites);
758 }
759
760 void Lock()
761 {
762 Mutex.Lock();
763 }
764
765 void Unlock()
766 {
767 Mutex.Unlock();
768 }
769 private:
771 UE::FMutex Mutex{ UE::AcquireLock }; // Start locked by default to avoid compare exchange during construction.
772 };
773
775
776 LowLevelTasks::FTask LowLevelTask;
777
778 // the task is completed when its subsequents list is closed and no more can be added
779 template <typename AllocatorType = FDefaultAllocator>
780 class FSubsequents
781 {
782 public:
783 bool PushIfNotClosed(FTaskBase* NewItem)
784 {
785 TASKGRAPH_VERBOSE_EVENT_SCOPE(FSubsequents::PushIfNotClosed);
786 // AddSubsequent expects acquire as some code can use the result here
787 // to decide if a task is finished and if the memory it produced/modified
788 // is safe to access.
789 if (bIsClosed.load(std::memory_order_acquire))
790 {
791 return false;
792 }
793 UE::TUniqueLock Lock(Mutex);
794 if (bIsClosed)
795 {
796 return false;
797 }
798 Subsequents.Emplace(NewItem);
799 return true;
800 }
801
803 {
804 TASKGRAPH_VERBOSE_EVENT_SCOPE(FSubsequents::Close);
805 UE::TUniqueLock Lock(Mutex);
806 bIsClosed = true;
807 return MoveTemp(Subsequents);
808 }
809
810 bool IsClosed() const
811 {
812 return bIsClosed;
813 }
814
815 private:
817 std::atomic<bool> bIsClosed = false;
819 };
820
822
823
824protected:
826 {
827 Prerequisites.Unlock();
828 }
829 };
830
831 // an extension of FTaskBase for tasks that return a result.
832 // Stores task execution result and provides an access to it.
833 template<typename ResultType>
835 {
836 protected:
842
843 virtual ~TTaskWithResult() override
844 {
846 }
847
848 public:
849 ResultType& GetResult()
850 {
851 checkf(IsCompleted(), TEXT("The task must be completed to obtain its result"));
852 return *ResultStorage.GetTypedPtr();
853 }
854
855 protected:
857 };
858
859 // Task implementation that can be executed, as it stores task body. Generic version (for tasks that return non-void results).
860 // In most cases it should be allocated on the heap and used with TRefCountPtr, e.g. @see FTaskHandle.
861 template<typename TaskBodyType, typename ResultType = TInvokeResult_T<TaskBodyType>, typename Enable = void>
862 class TExecutableTaskBase : public TTaskWithResult<ResultType>
863 {
865
866 public:
867 virtual void ExecuteTask() override final
868 {
869 new(&this->ResultStorage) ResultType{ Invoke(*TaskBodyStorage.GetTypedPtr()) };
870
871 // destroy the task body as soon as we are done with it, as it can have captured data sensitive to destruction order
872 DestructItem(TaskBodyStorage.GetTypedPtr());
873 }
874
875 protected:
878 // 2 init refs: one for the initial reference (we don't increment it on passing to `TRefCountPtr`), and one for the internal
879 // reference that keeps the task alive while it's in the system. is released either on task completion or by the scheduler after
880 // trying to execute the task
881 {
882 new(&TaskBodyStorage) TaskBodyType(MoveTemp(TaskBody));
883 }
884
885 private:
887 };
888
889 // a specialization for tasks that don't return results
890 template<typename TaskBodyType>
891 class TExecutableTaskBase<TaskBodyType, typename TEnableIf<std::is_same_v<TInvokeResult_T<TaskBodyType>, void>>::Type> : public FTaskBase
892 {
894
895 public:
896 virtual void ExecuteTask() override final
897 {
898 Invoke(*TaskBodyStorage.GetTypedPtr());
899
900 // destroy the task body as soon as we are done with it, as it can have captured data sensitive to destruction order
901 DestructItem(TaskBodyStorage.GetTypedPtr());
902 }
903
904 protected:
906 FTaskBase(2) // 2 init refs: one for the initial reference (we don't increment it on passing to `TRefCountPtr`), and one for the internal
907 // reference that keeps the task alive while it's in the system. is released either on task completion or by the scheduler after
908 // trying to execute the task
909 {
911 new(&TaskBodyStorage) TaskBodyType(MoveTemp(TaskBody));
912 }
913
914 private:
916 };
917
918 inline constexpr int32 SmallTaskSize = 256;
921
922 // a separate derived class to add "small task" allocation optimization to both base class specializations
923 template<typename TaskBodyType>
925 {
926 public:
931
932 // a helper that deduces the template argument
937
938 static void* operator new(size_t Size)
939 {
940 if (Size <= SmallTaskSize)
941 {
943 }
944 else
945 {
946 TASKGRAPH_VERBOSE_EVENT_SCOPE(TExecutableTask::LargeAlloc);
947 return FMemory::Malloc(sizeof(TExecutableTask), alignof(TExecutableTask));
948 }
949 }
950
951 static void operator delete(void* Ptr, size_t Size)
952 {
954 }
955 };
956
957 // waiting on named threads that replicates TaskGraph logic
958 // returns true if called on a named thread
959 CORE_API bool TryWaitOnNamedThread(FTaskBase& Task);
960
961 // a special kind of task that is used for signalling or dependency management. It can have prerequisites or be used as a prerequisite for other tasks.
962 // It's optimized for the fact that it doesn't have a task body and so doesn't need to be scheduled and executed
964 {
965 public:
966 static FTaskEventBase* Create(const TCHAR* DebugName)
967 {
968 return new FTaskEventBase(DebugName);
969 }
970
971 static void* operator new(size_t Size);
972 static void operator delete(void* Ptr);
973
974 private:
976 : FTaskBase(/*InitRefCount=*/ 1) // for the initial reference (we don't increment it on passing to `TRefCountPtr`)
977 {
978 TaskTrace::Created(GetTraceId(), sizeof(*this));
979 Init(InDebugName, ETaskPriority::Normal, EExtendedTaskPriority::TaskEvent, ETaskFlags::None);
980 }
981
982 virtual void ExecuteTask() override final
983 {
984 checkNoEntry(); // never executed because it doesn't have a task body
985 }
986 };
987
990
991 inline void* FTaskEventBase::operator new(size_t Size)
992 {
994 }
995
996 inline void FTaskEventBase::operator delete(void* Ptr)
997 {
999 }
1000
1001 // task retraction of multiple tasks, with timeout. The timeout is rounded up to any successful task execution, which means that it can
1002 // time out only in-between individual task retractions.
1003 // WARNING: the function can return `true` even if some tasks are still not completed. The `true` means only that the tasks are executed
1004 // and have no other pending dependencies, but can be still in the process of completion (concurrently). The caller still needs to wait
1005 // for completion.
1006 template<typename TaskCollectionType>
1008 {
1009 bool bResult = true;
1010
1011 for (auto& Task : Tasks)
1012 {
1013 if (Task.IsValid() && !Task.Pimpl->TryRetractAndExecute(Timeout))
1014 {
1015 bResult = false; // do not stop here to let this thread to help in executing tasks as much as possible, as it's waiting for their completion anyway
1016 }
1017
1018 if (Timeout.IsExpired())
1019 {
1020 return false;
1021 }
1022 }
1023
1024 return bResult;
1025 }
1026 }
1027}
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define check(expr)
Definition AssertionMacros.h:314
#define checkNoEntry()
Definition AssertionMacros.h:316
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
#define verify(expr)
Definition AssertionMacros.h:319
#define UE_NONCOPYABLE(TypeName)
Definition CoreMiscDefines.h:457
#define UE_DEPRECATED(Version, Message)
Definition CoreMiscDefines.h:302
FPlatformTypes::int8 int8
An 8-bit signed integer.
Definition Platform.h:1121
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::TCHAR TCHAR
Either ANSICHAR or WIDECHAR, depending on whether the platform supports wide characters or the requir...
Definition Platform.h:1135
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
#define UNLIKELY(x)
Definition Platform.h:857
#define PLATFORM_CACHE_LINE_SIZE
Definition Platform.h:938
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
AUTORTFM_INFER UE_FORCEINLINE_HINT constexpr auto Invoke(FuncType &&Func, ArgTypes &&... Args) -> decltype(((FuncType &&) Func)((ArgTypes &&) Args...))
Definition Invoke.h:44
FORCEINLINE constexpr void DestructItem(ElementType *Element)
Definition MemoryOps.h:56
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
JsonWriter Close()
void Init()
Definition LockFreeList.h:4
auto GetNum(const TStringConversion< Converter, DefaultConversionSize > &Conversion) -> decltype(Conversion.Length())
Definition StringConv.h:808
#define TASKGRAPH_VERBOSE_EVENT_SCOPE(Name)
Definition TaskPrivate.h:49
#define UE_TRACE_CHANNELEXPR_IS_ENABLED(ChannelsExpr)
Definition Trace.h:452
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
FRWLock Lock
Definition UnversionedPropertySerialization.cpp:921
uint32 Size
Definition VulkanMemory.cpp:4034
uint32_t uint32
Definition binka_ue_file_header.h:6
static constexpr uint32 InvalidThreadId
Definition Thread.h:99
Definition Task.h:310
Definition Task.h:153
Definition Array.h:670
Definition EnableIf.h:20
void * Allocate()
Definition LockFreeFixedSizeAllocator.h:51
void Free(void *Item)
Definition LockFreeFixedSizeAllocator.h:100
Definition LockFreeFixedSizeAllocator.h:334
Definition InheritedContext.h:118
Definition InheritedContext.h:54
Definition Mutex.h:18
UE_API void Lock()
Definition RecursiveMutex.cpp:40
UE_API void Unlock()
Definition RecursiveMutex.cpp:115
Definition Timeout.h:21
Definition UniqueLock.h:20
Definition Pipe.h:29
Definition TaskPrivate.h:120
bool IsNamedThreadTask() const
Definition TaskPrivate.h:205
void SetPipe(FPipe &InPipe)
Definition TaskPrivate.h:376
void AddPrerequisites(const PrerequisiteCollectionType &InPrerequisites, bool bLockPrerequisite)
Definition TaskPrivate.h:270
EExtendedTaskPriority GetExtendedPriority() const
Definition TaskPrivate.h:215
void Release()
Definition TaskPrivate.h:136
void AddNested(FTaskBase &Nested)
Definition TaskPrivate.h:444
void Close()
Definition TaskPrivate.h:543
bool AddPrerequisites(const HigherLevelTaskType &Prerequisite)
Definition TaskPrivate.h:252
virtual ~FTaskBase()
Definition TaskPrivate.h:189
virtual void ExecuteTask()=0
bool Trigger(uint64 TaskSize)
Definition TaskPrivate.h:392
bool AddSubsequent(FTaskBase &Subsequent)
Definition TaskPrivate.h:363
bool AddPrerequisites(FTaskBase &Prerequisite)
Definition TaskPrivate.h:223
uint32 GetRefCount(std::memory_order MemoryOrder=std::memory_order_relaxed) const
Definition TaskPrivate.h:147
void AddRef()
Definition TaskPrivate.h:131
bool TryExecuteTask()
Definition TaskPrivate.h:492
TaskTrace::FId GetTraceId() const
Definition TaskPrivate.h:479
bool IsAwaitable() const
Definition TaskPrivate.h:200
FPipe * GetPipe() const
Definition TaskPrivate.h:383
void UnlockPrerequisites()
Definition TaskPrivate.h:825
bool TryLaunch(uint64 TaskSize)
Definition TaskPrivate.h:409
void Init(const TCHAR *InDebugName, ETaskPriority InPriority, EExtendedTaskPriority InExtendedPriority, ETaskFlags Flags)
Definition TaskPrivate.h:166
void ReleaseInternalReference()
Definition TaskPrivate.h:438
FTaskBase(uint32 InitRefCount, bool bUnlockPrerequisites=true)
Definition TaskPrivate.h:157
bool IsCompleted() const
Definition TaskPrivate.h:424
void AddPrerequisites(const PrerequisiteCollectionType &InPrerequisites)
Definition TaskPrivate.h:356
ETaskPriority GetPriority() const
Definition TaskPrivate.h:210
Definition TaskPrivate.h:964
static FTaskEventBase * Create(const TCHAR *DebugName)
Definition TaskPrivate.h:966
TExecutableTaskBase(const TCHAR *InDebugName, TaskBodyType &&TaskBody, ETaskPriority InPriority, EExtendedTaskPriority InExtendedPriority, ETaskFlags Flags)
Definition TaskPrivate.h:905
Definition TaskPrivate.h:863
virtual void ExecuteTask() override final
Definition TaskPrivate.h:867
TExecutableTaskBase(const TCHAR *InDebugName, TaskBodyType &&TaskBody, ETaskPriority InPriority, EExtendedTaskPriority InExtendedPriority, ETaskFlags Flags)
Definition TaskPrivate.h:876
Definition TaskPrivate.h:925
static TExecutableTask * Create(const TCHAR *InDebugName, TaskBodyType &&TaskBody, ETaskPriority InPriority, EExtendedTaskPriority InExtendedPriority, ETaskFlags Flags)
Definition TaskPrivate.h:933
TExecutableTask(const TCHAR *InDebugName, TaskBodyType &&TaskBody, ETaskPriority InPriority, EExtendedTaskPriority InExtendedPriority, ETaskFlags Flags)
Definition TaskPrivate.h:927
Definition TaskPrivate.h:835
ResultType & GetResult()
Definition TaskPrivate.h:849
virtual ~TTaskWithResult() override
Definition TaskPrivate.h:843
TTypeCompatibleBytes< ResultType > ResultStorage
Definition TaskPrivate.h:856
TTaskWithResult(const TCHAR *InDebugName, ETaskPriority InPriority, EExtendedTaskPriority InExtendedPriority, uint32 InitRefCount, ETaskFlags Flags)
Definition TaskPrivate.h:837
Type
Definition TaskGraphInterfaces.h:57
ETaskPriority
Definition Task.h:18
bool ToTaskPriority(const TCHAR *PriorityStr, ETaskPriority &OutPriority)
Definition Task.h:48
ETaskFlags
Definition Task.h:93
const TCHAR * ToString(ETaskPriority Priority)
Definition Task.h:30
UE::FRecursiveMutex Mutex
Definition MeshPaintVirtualTexture.cpp:164
Definition OverriddenPropertySet.cpp:45
void TASK_CORE_API Launched(FId TaskId, const TCHAR *DebugName, bool bTracked, ENamedThreads::Type ThreadToExecuteOn, uint64 TaskSize)
Definition TaskTrace.h:77
const FId InvalidId
Definition TaskTrace.h:39
void TASK_CORE_API Destroyed(FId TaskId)
Definition TaskTrace.h:83
FId TASK_CORE_API GenerateTaskId()
Definition TaskTrace.h:74
void TASK_CORE_API SubsequentAdded(FId TaskId, FId SubsequentId)
Definition TaskTrace.h:79
void TASK_CORE_API Created(FId TaskId, uint64 TaskSize)
Definition TaskTrace.h:76
uint64 FId
Definition TaskTrace.h:37
void TASK_CORE_API Completed(FId TaskId)
Definition TaskTrace.h:82
FTaskEventBaseAllocator TaskEventBaseAllocator
Definition TaskPrivate.cpp:25
ENamedThreads::Type TranslatePriority(EExtendedTaskPriority Priority)
Definition TaskPrivate.cpp:502
constexpr int32 SmallTaskSize
Definition TaskPrivate.h:918
bool IsThreadRetractingTask()
Definition TaskPrivate.cpp:49
FTaskBase * ExchangeCurrentTask(FTaskBase *Task)
Definition TaskPrivate.cpp:293
bool TryRetractAndExecute(const TaskCollectionType &Tasks, FTimeout Timeout)
Definition TaskPrivate.h:1007
FExecutableTaskAllocator SmallTaskAllocator
Definition TaskPrivate.cpp:24
FTaskBase * GetCurrentTask()
Definition TaskPrivate.cpp:288
bool IsCompleted(const HigherLevelTaskType &Prerequisite)
Definition Task.h:351
Definition AnalyticsProviderLog.h:8
TStaticArray< Private::FTaskBase *, sizeof...(TaskTypes)> Prerequisites(TaskTypes &... Tasks)
Definition Task.h:365
ETaskFlags
Definition TaskPrivate.h:89
EExtendedTaskPriority
Definition TaskPrivate.h:60
bool ToExtendedTaskPriority(const TCHAR *ExtendedPriorityStr, EExtendedTaskPriority &OutExtendedPriority)
Definition TaskPrivate.cpp:358
bool Wait(const TaskCollectionType &Tasks, FTimespan InTimeout=FTimespan::MaxValue())
Definition Task.h:381
Definition AdvancedWidgetsModule.cpp:13
TEventCount< uint32 > FEventCount
Definition EventCount.h:214
constexpr struct UE::FAcquireLock AcquireLock
static uint32 GetCurrentThreadId(void)
Definition AndroidPlatformTLS.h:20
static FORCENOINLINE CORE_API void Free(void *Original)
Definition UnrealMemory.cpp:685
Definition NumericLimits.h:41
Definition TypeCompatibleBytes.h:24
ElementType * GetTypedPtr()
Definition TypeCompatibleBytes.h:38
Definition TaskTrace.h:63