37#define IS_SINGLE_THREADED 0
41#define IS_SINGLE_THREADED 0
42#elif (__ANDROID__ || __linux || __unix || __posix)
45#define IS_SINGLE_THREADED 0
49#define IS_SINGLE_THREADED 1
54#define WIN32_LEAN_AND_MEAN
57#define WINDOWS_EXTRA_LEAN
71#ifdef __cpp_lib_hardware_interference_size
72 static constexpr std::size_t
k_CachelineSize = std::max(std::hardware_constructive_interference_size, std::hardware_destructive_interference_size);
87 using Byte =
unsigned char;
117 static_assert(
sizeof(
TaskPtr) ==
sizeof(std::uint16_t) * 2u,
"Expected to be the size of two uint16's.");
118 static_assert(
sizeof(
AtomicTaskPtr) ==
sizeof(
TaskPtr) && AtomicTaskPtr::is_always_lock_free,
"Expected to be lock-free so no extra data members should have been added.");
133 static_assert(
sizeof(
TaskFnStorage) ==
sizeof(std::uint64_t) &&
alignof(
TaskFnStorage) ==
alignof(std::uint64_t),
"Expected to always be 8 bytes.");
135 struct alignas(k_CachelineSize)
Task
137 static constexpr std::size_t k_SizeOfMembers =
141 sizeof(std::uint8_t) +
159 Byte user_data[k_TaskPaddingDataSize];
165 static_assert(std::is_trivially_destructible_v<Task>,
"Task must be trivially destructible.");
170 unsigned char storage[
sizeof(
Task)];
194 std::mutex init_mutex = {};
195 std::condition_variable init_cv = {};
196 std::atomic_uint32_t num_workers_ready = {};
231#if JOB_SYS_ASSERTIONS
233void Job::detail::assertHandler(
const bool condition,
const char*
const filename,
const int line_number,
const char*
const msg)
237 std::fprintf(stderr,
"JobSystem [%s:%i] Assertion '%s' Failed.\n", filename, line_number, msg);
250 static void WakeUpAllWorkers()
noexcept
255 static void WakeUpOneWorker()
noexcept
260 static void Sleep()
noexcept
264 if (job_system->
is_running.load(std::memory_order_relaxed))
299 for (std::size_t i = 0u; i < capacity_minus_one; ++i)
301 memory[i].
next = &memory[i + 1u];
303 memory[capacity_minus_one].
next =
nullptr;
316 static Task* TaskFromIndex(
const Job::TaskPool& pool,
const std::size_t idx)
noexcept
325 JobAssert(result !=
nullptr,
"Allocation failure.");
342 static Task* TaskPtrToPointer(
const TaskPtr ptr)
noexcept
357 static void TaskOnFinish(
Task*
const self)
noexcept
359 const std::int32_t num_jobs_left =
self->
num_unfinished_tasks.fetch_sub(1, std::memory_order_relaxed) - 1;
361 if (num_jobs_left == 0)
363 Task*
const parent_task = task::TaskPtrToPointer(self->parent);
367 TaskOnFinish(parent_task);
370 std::atomic_signal_fence(std::memory_order_release);
372 self->num_unfinished_tasks.fetch_sub(1, std::memory_order_relaxed);
374 TaskPtr continuation_ptr =
self->first_continuation.load(std::memory_order_relaxed);
376 while (!continuation_ptr.
isNull())
378 Task*
const continuation = task::TaskPtrToPointer(continuation_ptr);
384 continuation_ptr = next_task;
387 self->ref_count.fetch_sub(1, std::memory_order_relaxed);
391 static void RunTaskFunction(
Task*
const self)
noexcept
393 self->fn_storage.fn(
self);
397 static TaskPtr PointerToTaskPtr(
const Task*
const self)
noexcept
402 const TaskHandle self_index = task_pool::TaskToIndex(
worker.task_allocator,
self);
404 return TaskPtr{
self->owning_worker, self_index};
422 while (read_idx != num_tasks)
424 const TaskHandle task_handle = allocated_tasks[read_idx++];
425 Task*
const task_ptr = task_pool::TaskFromIndex(
task_pool, task_handle);
426 const bool task_is_finished = task_ptr->
ref_count.load(std::memory_order_acquire) == 0u;
428 if (task_is_finished)
430 task_pool::DeallocateTask(&
task_pool, task_ptr);
434 allocated_tasks[write_idx++] = task_handle;
438 worker->num_allocated_tasks = write_idx;
444 const std::uint32_t other_worker_id = pcg32_boundedrand_r(&
worker->rng_state, num_workers);
446 return system::GetWorker(
WorkerID(other_worker_id));
459 worker->normal_queue.Pop(&task_ptr);
461 if (task_ptr.
isNull() && !is_main_thread)
463 worker->worker_queue.Pop(&task_ptr);
469 if (other_worker !=
worker)
471 other_worker->normal_queue.Steal(&result);
473 if (result.
isNull() && !is_main_thread)
475 other_worker->worker_queue.Steal(&result);
484 task_ptr = TrySteal(
worker->last_stolen_worker);
491 task_ptr = TrySteal(random_worker);
498 worker->last_stolen_worker = random_worker;
503 Task*
const task = task::TaskPtrToPointer(task_ptr);
504 task::RunTaskFunction(
task);
515 job_system->
is_running.store(
true, std::memory_order_relaxed);
516 init_lock->
init_cv.notify_all();
520 std::unique_lock<std::mutex> lock(init_lock->
init_mutex);
521 init_lock->
init_cv.wait(lock, [init_lock]() ->
bool {
529 std::atomic_thread_fence(std::memory_order_acquire);
534 const HANDLE handle = GetCurrentThread();
539 const DWORD_PTR affinity_mask = 1ull << thread_id;
540 const DWORD_PTR affinity_result = SetThreadAffinityMask(handle, affinity_mask);
542 if (affinity_result > 0)
552 const unsigned int thread_index =
unsigned int(
worker - job_system->
workers);
554 char thread_name[32] = u8
"";
555 wchar_t thread_name_w[
sizeof(thread_name)] = L
"";
557 const char*
const format = thread_index >= job_system->
num_owned_workers ?
"Job::User_%u" :
"Job::Owned_%u";
559 const int c_size = std::snprintf(thread_name,
sizeof(thread_name), format, thread_index);
561 std::mbstowcs(thread_name_w, thread_name, c_size);
563 const HRESULT hr = SetThreadDescription(handle, thread_name_w);
564 JobAssert(SUCCEEDED(hr),
"Failed to set thread name.");
570 WaitForAllThreadsReady(job_system);
580 while (job_system->
is_running.load(std::memory_order_relaxed))
582 if (!worker::TryRunTask(
worker))
596 static WorkerID GetCurrentID()
noexcept
616 system::WakeUpAllWorkers();
620 worker::TryRunTask(
worker);
626 static bool IsPointerAligned(
const void*
const ptr,
const std::size_t alignment)
noexcept
628 return (
reinterpret_cast<std::uintptr_t
>(ptr) & (alignment - 1u)) == 0u;
631 static void* AlignPointer(
const void*
const ptr,
const std::size_t alignment)
noexcept
633 const std::size_t required_alignment_mask = alignment - 1;
635 return reinterpret_cast<void*
>(
reinterpret_cast<std::uintptr_t
>(ptr) + required_alignment_mask & ~required_alignment_mask);
642 std::size_t num_elements;
646 static Span<T> LinearAlloc(
void*& ptr,
const std::size_t num_elements)
noexcept
648 void*
const result = AlignPointer(ptr,
alignof(T));
650 ptr =
static_cast<unsigned char*
>(result) +
sizeof(T) * num_elements;
652 for (std::size_t i = 0; i < num_elements; ++i)
654 new (
static_cast<T*
>(result) + i) T;
657 return Span<T>{
static_cast<T*
>(result), num_elements};
661 static T* SpanAlloc(Span<T>*
const span,
const std::size_t num_elements)
noexcept
663 JobAssert(num_elements <= span->num_elements,
"Out of bounds span alloc.");
665 T*
const result = span->ptr;
667 span->ptr += num_elements;
668 span->num_elements -= num_elements;
673 static std::size_t AlignedSizeUp(
const std::size_t size,
const std::size_t alignment)
noexcept
675 const std::size_t remainder = size % alignment;
677 return remainder != 0 ? size + (alignment - remainder) : size;
683 in_out_reqs->byte_size = AlignedSizeUp(in_out_reqs->byte_size,
alignof(T));
684 in_out_reqs->alignment = in_out_reqs->alignment <
alignof(T) ?
alignof(T) : in_out_reqs->alignment;
686 in_out_reqs->byte_size +=
sizeof(T) * num_elements;
689 static bool IsPowerOf2(
const std::size_t value)
noexcept
691 return (value & (value - 1)) == 0;
705 JobAssert(num_tasks_per_worker <= std::uint16_t(-1),
"Too many task items per worker.");
707 return std::uint16_t(num_tasks_per_worker);
710 static std::uint32_t TotalNumTasks(
const Job::WorkerID num_threads,
const std::uint16_t num_tasks_per_worker)
noexcept
712 return num_tasks_per_worker * num_threads;
725 JobAssert(IsPowerOf2(options.main_queue_size),
"Main queue size must be a power of two.");
726 JobAssert(IsPowerOf2(options.normal_queue_size),
"Normal queue size must be a power of two.");
727 JobAssert(IsPowerOf2(options.worker_queue_size),
"Worker queue size must be a power of two.");
729 const WorkerID num_threads = config::WorkerCount(options);
730 const std::uint16_t num_tasks_per_worker = config::NumTasksPerWorker(options);
731 const std::uint32_t total_num_tasks = config::TotalNumTasks(num_threads, num_tasks_per_worker);
733 MemoryRequirementsPush<JobSystemContext>(
this, 1u);
734 MemoryRequirementsPush<ThreadLocalState>(
this, num_threads);
735 MemoryRequirementsPush<TaskMemoryBlock>(
this, total_num_tasks);
736 MemoryRequirementsPush<TaskPtr>(
this, options.main_queue_size);
737 MemoryRequirementsPush<AtomicTaskPtr>(
this, total_num_tasks);
738 MemoryRequirementsPush<TaskHandle>(
this, total_num_tasks);
745 const bool needs_delete = memory ==
nullptr;
749 memory = ::operator
new[](memory_requirements.byte_size, std::align_val_t{memory_requirements.alignment});
752 JobAssert(memory !=
nullptr,
"memory must be a valid pointer.");
753 JobAssert(IsPointerAligned(memory, memory_requirements.alignment),
"memory must be a aligned to `memory_requirements.alignment`.");
757 const WorkerID num_threads = config::WorkerCount(options);
759 const std::uint16_t num_tasks_per_worker = config::NumTasksPerWorker(options);
760 const std::uint32_t total_num_tasks = config::TotalNumTasks(num_threads, num_tasks_per_worker);
762 void* alloc_ptr = memory;
763 JobSystemContext* job_system = LinearAlloc<JobSystemContext>(alloc_ptr, 1u).ptr;
764 Span<ThreadLocalState> all_workers = LinearAlloc<ThreadLocalState>(alloc_ptr, num_threads);
765 Span<TaskMemoryBlock> all_tasks = LinearAlloc<TaskMemoryBlock>(alloc_ptr, total_num_tasks);
766 Span<TaskPtr> main_tasks_ptrs = LinearAlloc<TaskPtr>(alloc_ptr, options.
main_queue_size);
767 Span<AtomicTaskPtr> worker_task_ptrs = LinearAlloc<AtomicTaskPtr>(alloc_ptr, total_num_tasks);
768 Span<TaskHandle> all_task_handles = LinearAlloc<TaskHandle>(alloc_ptr, total_num_tasks);
771 job_system->
workers = all_workers.ptr;
785 GetSystemInfo(&sysinfo);
787 switch (sysinfo.wProcessorArchitecture)
789 case PROCESSOR_ARCHITECTURE_AMD64:
794 case PROCESSOR_ARCHITECTURE_ARM:
799 case PROCESSOR_ARCHITECTURE_ARM64:
804 case PROCESSOR_ARCHITECTURE_IA64:
809 case PROCESSOR_ARCHITECTURE_INTEL:
814 case PROCESSOR_ARCHITECTURE_UNKNOWN:
825 for (std::uint64_t worker_index = 0; worker_index < num_threads; ++worker_index)
832 worker->allocated_tasks = SpanAlloc(&all_task_handles, num_tasks_per_worker);
833 worker->num_allocated_tasks = 0u;
834 pcg32_srandom_r(&
worker->rng_state, worker_index + rng_seed, worker_index * 2u + 1u + rng_seed);
835 worker->last_stolen_worker = main_thread_worker;
841 std::atomic_thread_fence(std::memory_order_release);
842 for (std::uint64_t worker_index = 1; worker_index < owned_threads; ++worker_index)
844 worker::InitializeThread(job_system->
workers + worker_index);
847 JobAssert(all_workers.num_elements == 0u,
"All elements expected to be allocated out.");
848 JobAssert(all_tasks.num_elements == 0u,
"All elements expected to be allocated out.");
849 JobAssert(main_tasks_ptrs.num_elements == 0u,
"All elements expected to be allocated out.");
850 JobAssert(worker_task_ptrs.num_elements == 0u,
"All elements expected to be allocated out.");
851 JobAssert(all_task_handles.num_elements == 0u,
"All elements expected to be allocated out.");
861 JobAssert(user_thread_id < job_system->num_workers,
"Too many calls to `SetupUserThread`.");
863 worker::WorkerThreadSetup(job_system->
workers + user_thread_id);
868#if IS_SINGLE_THREADED
871 const auto n = std::thread::hardware_concurrency();
872 return n != 0 ? n : 1;
879 GetSystemInfo(&sysinfo);
880 return sysinfo.dwNumberOfProcessors;
882 return sysconf(_SC_NPROCESSORS_ONLN) ;
886 std::size_t len =
sizeof(numCPU);
890 mib[1] = HW_AVAILCPU;
893 sysctl(mib, 2, &numCPU, &len, NULL, 0);
898 sysctl(mib, 2, &numCPU, &len, NULL, 0);
905 return mpctl(MPC_GETNUMSPUS, NULL, NULL);
907 return sysconf(_SC_NPROC_ONLN);
909 NSUInteger a = [[NSProcessInfo processInfo] processorCount];
910 NSUInteger b = [[NSProcessInfo processInfo] activeProcessorCount];
943 static_assert(std::is_trivially_destructible_v<TaskMemoryBlock>,
"TaskMemoryBlock's destructor not called.");
944 static_assert(std::is_trivially_destructible_v<TaskPtr>,
"TaskPtr's destructor not called.");
945 static_assert(std::is_trivially_destructible_v<AtomicTaskPtr>,
"AtomicTaskPtr's destructor not called.");
946 static_assert(std::is_trivially_destructible_v<TaskHandle>,
"TaskHandle's destructor not called.");
952 while (job_system->
is_running.load(std::memory_order_relaxed) !=
true) {}
956 job_system->
is_running.store(
false, std::memory_order_relaxed);
960 system::WakeUpAllWorkers();
962 for (std::uint32_t i = 0; i < num_workers; ++i)
968 worker::ShutdownThread(
worker);
971 worker->~ThreadLocalState();
976 job_system->~JobSystemContext();
982 ::operator
delete[](job_system, job_system->
system_alloc_size, std::align_val_t{job_system->system_alloc_alignment});
988 const WorkerID worker_id = worker::GetCurrentID();
992 if (
worker->num_allocated_tasks == max_tasks_per_worker)
994 worker::GarbageCollectAllocatedTasks(
worker);
996 if (
worker->num_allocated_tasks == max_tasks_per_worker)
999 system::WakeUpAllWorkers();
1000 while (
worker->num_allocated_tasks == max_tasks_per_worker)
1002 worker::TryRunTask(
worker);
1003 worker::GarbageCollectAllocatedTasks(
worker);
1008 JobAssert(
worker->num_allocated_tasks < max_tasks_per_worker,
"Too many tasks allocated.");
1010 Task*
const task = task_pool::AllocateTask(&
worker->task_allocator, worker_id,
function, task::PointerToTaskPtr(parent));
1015 parent->num_unfinished_tasks.fetch_add(1u, std::memory_order_release);
1018 worker->allocated_tasks[
worker->num_allocated_tasks++] = task_hdl;
1025 Byte*
const user_storage_start =
static_cast<Byte*
>(AlignPointer(
task->user_data +
task->user_data_start, alignment));
1026 const Byte*
const user_storage_end = std::end(
task->user_data);
1028 if (user_storage_start <= user_storage_end)
1030 const std::size_t user_storage_size = user_storage_end - user_storage_start;
1032 return {user_storage_start, user_storage_size};
1035 return {
nullptr, 0u};
1041 JobAssert(continuation->q_type ==
k_InvalidQueueType,
"A continuation must not have already been submitted to a queue or already added as a continuation.");
1042 JobAssert(continuation->next_continuation.isNull(),
"A continuation must not have already been added to another task.");
1044 const TaskPtr new_head = task::PointerToTaskPtr(continuation);
1045 continuation->q_type = queue;
1046 continuation->next_continuation =
self->first_continuation.load(std::memory_order_relaxed);
1048 while (!std::atomic_compare_exchange_strong(&self->first_continuation, &continuation->next_continuation, new_head))
1055 const auto old_ref_count =
task->ref_count.fetch_add(1, std::memory_order_relaxed);
1057 JobAssert(old_ref_count >= std::int16_t(1) ||
task->q_type ==
k_InvalidQueueType,
"First call to taskIncRef should not happen after the task has been submitted.");
1058 (void)old_ref_count;
1063 const auto old_ref_count =
task->ref_count.fetch_sub(1, std::memory_order_relaxed);
1065 JobAssert(old_ref_count >= 0,
"taskDecRef: Called too many times.");
1066 (void)old_ref_count;
1071 return task->num_unfinished_tasks.load(std::memory_order_acquire) == -1;
1087 const TaskPtr task_ptr = task::PointerToTaskPtr(
self);
1089 self->q_type = queue;
1095 task::SubmitQPushHelper(task_ptr,
worker, &
worker->normal_queue);
1108 while (!main_queue->
Push(task_ptr))
1111 worker::TryRunTask(
worker);
1117 task::SubmitQPushHelper(task_ptr,
worker, &
worker->worker_queue);
1121#if defined(__GNUC__)
1122 __builtin_unreachable();
1123#elif defined(_MSC_VER)
1133 if (num_pending_jobs >= num_workers)
1135 system::WakeUpAllWorkers();
1139 system::WakeUpOneWorker();
1149 JobAssert(
task->owning_worker == worker_id,
"You may only call this function with a task created on the current 'Worker'.");
1151 system::WakeUpAllWorkers();
1157 worker::TryRunTask(
worker);
1171 num_unfinished_tasks{1},
1174 first_continuation{
nullptr},
1175 next_continuation{
nullptr},
1183#if defined(_MSC_VER)
1184#define NativePause YieldProcessor
1185#elif defined(__clang__) && defined(__SSE__) || defined(__INTEL_COMPILER)
1186#include <xmmintrin.h>
1187#define NativePause _mm_pause
1188#elif defined(__arm__)
1190#define NativePause __yield
1192#define NativePause() __asm__ __volatile__("yield")
1195#define NativePause std::this_thread::yield
1209 std::this_thread::yield();
1216 return task->q_type;
1221 return AlignPointer(
task->user_data, alignment);
1226 const Byte*
const user_storage_end = std::end(
task->user_data);
1227 Byte*
const requested_storage_start =
static_cast<Byte*
>(AlignPointer(
task->user_data, alignment));
1228 const Byte*
const requested_storage_end = requested_storage_start + num_bytes;
1230 JobAssert(requested_storage_end <= user_storage_end,
"Cannot store object within the task's user storage. ");
1232 task->user_data_start =
static_cast<std::uint8_t
>(requested_storage_end -
task->user_data);
1234 return requested_storage_start;
1244 Task*
const task = task::TaskPtrToPointer(task_ptr);
1245 task::RunTaskFunction(
task);
1254#undef IS_SINGLE_THREADED
1256#if defined(_MSC_VER)
1258#pragma warning(push)
1259#pragma warning(disable : 4244)
1260#pragma warning(disable : 4146)
1264#include "pcg_basic.c"
1266#if defined(_MSC_VER)
bool Push(const T &value)
API for a multi-threading job system.
Assertion macro for this library.
#define JobAssert(expr, msg)
Concurrent Queue Implmementations for different situations.
static thread_local Job::ThreadLocalState * g_CurrentWorker
static Job::JobSystemContext * g_JobSystem
void * taskReservePrivateUserData(Task *const task, const std::size_t num_bytes, const std::size_t alignment) noexcept
void * taskGetPrivateUserData(Task *const task, const std::size_t alignment) noexcept
QueueType taskQType(const Task *task) noexcept
bool mainQueueTryRunTask(void) noexcept
void assertHandler(const bool condition, const char *const filename, const int line_number, const char *const msg)
LockedQueue< TaskPtr > main_queue
std::atomic< TaskPtr > AtomicTaskPtr
std::uint64_t job_steal_rng_seed
The RNG for work queue stealing will be seeded with this value.
InitializationLock init_lock
TaskData TaskGetData(Task *const task, const std::size_t alignment) noexcept
Returns you the user-data buffer you way write to get data into your TaskFn.
SPMCDeque< TaskPtr > worker_queue
void TaskSubmitAndWait(Task *const self, const QueueType queue=QueueType::NORMAL) noexcept
Same as calling taskSubmit followed by waitOnTask.
std::condition_variable worker_sleep_cv
void WaitOnTask(const Task *const task) noexcept
Waits until the specified task is done executing. This function will block but do work while being bl...
void(*)(Task *) TaskFn
The signature of the type of function for a single Task.
std::atomic_int32_t AtomicInt32
std::atomic_uint32_t num_user_threads_setup
void TaskAddContinuation(Task *const self, Task *const continuation, const QueueType queue=QueueType::NORMAL) noexcept
A 'continuation' is a task that will be added to a queue after the 'self' Task has finished running.
InitializationToken Initialize(const JobSystemMemoryRequirements &memory_requirements={}, void *const memory=nullptr) noexcept
Sets up the Job system and creates all the worker threads. The thread that calls 'Job::Initialize' is...
TaskMemoryBlock * freelist
static constexpr TaskHandle NullTaskHandle
void YieldTimeSlice() noexcept
Asks the OS to yield this threads execution to another thread on the current cpu core.
std::uint16_t NumWorkers() noexcept
Returns the number of workers created by the system. This function can be called by any thread concur...
ThreadLocalState * last_stolen_worker
std::uint16_t normal_queue_size
Number of tasks in each worker's QueueType::NORMAL queue. (Must be power of two)
QueueType
Determines which threads the task will be allowed to run on.
@ MAIN
Tasks in this queue will only be run by the main thread.
@ NORMAL
Tasks in this queue will run on either the main or worker threads.
@ WORKER
Tasks in this queue will never run on the main thread.
TaskHandleType num_allocated_tasks
Task * TaskMake(const TaskFn function, Task *const parent=nullptr) noexcept
Creates a new Task that should be later submitted by calling 'TaskSubmit'.
pcg_state_setseq_64 rng_state
SPMCDeque< TaskPtr > normal_queue
ThreadLocalState * workers
const char * ProcessorArchitectureName() noexcept
An implementation defined name for the CPU architecture of the device. This function can be called by...
std::uint32_t num_owned_workers
std::uint32_t num_workers
std::uint32_t num_tasks_per_worker
std::atomic_bool is_running
unsigned char storage[sizeof(Task)]
const char * sys_arch_str
void SetupUserThread()
Must be called in the callstack of the thread to be setup.
std::atomic_uint32_t num_available_jobs
void Shutdown() noexcept
This will deallocate any memory used by the system and shutdown any threads created by 'bfJob::initia...
std::uint16_t main_queue_size
Number of tasks in the job system's QueueType::MAIN queue. (Must be power of two)
static constexpr std::size_t k_ExpectedTaskSize
std::atomic_uint32_t num_workers_ready
std::size_t system_alloc_alignment
std::mutex worker_sleep_mutex
WorkerID CurrentWorker() noexcept
The current id of the current thread. This function can be called by any thread concurrently.
void TaskIncRef(Task *const task) noexcept
Increments the task's ref count preventing it from being garbage collected.
static constexpr QueueType k_InvalidQueueType
TaskHandle * allocated_tasks
std::size_t system_alloc_size
std::uint16_t WorkerID
The id type of each worker thread.
std::uint8_t num_user_threads
The number of threads not owned by this system but wants access to the Job API (The thread must call ...
bool IsMainThread() noexcept
Allows for querying if we are currently executing in the main thread.
void PauseProcessor() noexcept
CPU pause instruction to indicate when you are in a spin wait loop.
std::uint16_t worker_queue_size
Number of tasks in each worker's QueueType::WORKER queue. (Must be power of two)
void TaskDecRef(Task *const task) noexcept
Decrements the task's ref count allow it to be garbage collected.
std::size_t NumSystemThreads() noexcept
Makes some system calls to grab the number threads / processors on the device. This function can be c...
std::atomic< TaskHandle > AtomicTaskHandleType
void TaskSubmit(Task *const self, const QueueType queue=QueueType::NORMAL) noexcept
Submits the task to the specified queue.
std::condition_variable init_cv
std::uint8_t num_threads
Use 0 to indicate using the number of cores available on the system.
static constexpr std::size_t k_CachelineSize
@ SUCCESS
Returned from Push, Pop and Steal.
TaskHandle TaskHandleType
bool TaskIsDone(const Task *const task) noexcept
Returns the done status of the task.
The runtime configuration for the Job System.
A buffer for user-data you can write to, maybe large enough to store task data inline.
The memory requirements for a given configuration JobSystemCreateOptions.
JobSystemMemoryRequirements(const JobSystemCreateOptions &options={}) noexcept
TaskFnStorage fn_storage
The function that will be run.
AtomicInt32 ref_count
Keeps the task from being garbage collected.
QueueType q_type
The queue type this task has been submitted to, initialized to k_InvalidQueueType.
TaskPtr next_continuation
Next element in the linked list of continuations.
AtomicInt32 num_unfinished_tasks
The number of children tasks.
WorkerID owning_worker
The worker this task has been created on, needed for Task::toTaskPtr and various assertions.
Task(WorkerID worker, TaskFn fn, TaskPtr parent) noexcept
AtomicTaskPtr first_continuation
Head of linked list of tasks to be added on completion.
TaskPtr parent
The parent task, can be null.
std::uint8_t user_data_start
Offset into padding that can be used for user data.
bool isNull() const noexcept
TaskPtr() noexcept=default
TaskPtr(std::nullptr_t) noexcept
TaskFnStorage(const TaskFn fn) noexcept