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.");
859 JobAssert(user_thread_id < job_system->num_workers,
"Too many calls to `SetupUserThread`.");
861 worker::WorkerThreadSetup(job_system->
workers + user_thread_id);
866#if IS_SINGLE_THREADED
869 const auto n = std::thread::hardware_concurrency();
870 return n != 0 ? n : 1;
877 GetSystemInfo(&sysinfo);
878 return sysinfo.dwNumberOfProcessors;
880 return sysconf(_SC_NPROCESSORS_ONLN) ;
884 std::size_t len =
sizeof(numCPU);
888 mib[1] = HW_AVAILCPU;
891 sysctl(mib, 2, &numCPU, &len, NULL, 0);
896 sysctl(mib, 2, &numCPU, &len, NULL, 0);
903 return mpctl(MPC_GETNUMSPUS, NULL, NULL);
905 return sysconf(_SC_NPROC_ONLN);
907 NSUInteger a = [[NSProcessInfo processInfo] processorCount];
908 NSUInteger b = [[NSProcessInfo processInfo] activeProcessorCount];
941 static_assert(std::is_trivially_destructible_v<TaskMemoryBlock>,
"TaskMemoryBlock's destructor not called.");
942 static_assert(std::is_trivially_destructible_v<TaskPtr>,
"TaskPtr's destructor not called.");
943 static_assert(std::is_trivially_destructible_v<AtomicTaskPtr>,
"AtomicTaskPtr's destructor not called.");
944 static_assert(std::is_trivially_destructible_v<TaskHandle>,
"TaskHandle's destructor not called.");
950 while (job_system->
is_running.load(std::memory_order_relaxed) !=
true) {}
954 job_system->
is_running.store(
false, std::memory_order_relaxed);
958 system::WakeUpAllWorkers();
960 for (std::uint32_t i = 0; i < num_workers; ++i)
966 worker::ShutdownThread(
worker);
969 worker->~ThreadLocalState();
974 job_system->~JobSystemContext();
980 ::operator
delete[](job_system, job_system->
system_alloc_size, std::align_val_t{job_system->system_alloc_alignment});
986 const WorkerID worker_id = worker::GetCurrentID();
990 if (
worker->num_allocated_tasks == max_tasks_per_worker)
992 worker::GarbageCollectAllocatedTasks(
worker);
994 if (
worker->num_allocated_tasks == max_tasks_per_worker)
997 system::WakeUpAllWorkers();
998 while (
worker->num_allocated_tasks == max_tasks_per_worker)
1000 worker::TryRunTask(
worker);
1001 worker::GarbageCollectAllocatedTasks(
worker);
1006 JobAssert(
worker->num_allocated_tasks < max_tasks_per_worker,
"Too many tasks allocated.");
1008 Task*
const task = task_pool::AllocateTask(&
worker->task_allocator, worker_id,
function, task::PointerToTaskPtr(parent));
1013 parent->num_unfinished_tasks.fetch_add(1u, std::memory_order_release);
1016 worker->allocated_tasks[
worker->num_allocated_tasks++] = task_hdl;
1023 Byte*
const user_storage_start =
static_cast<Byte*
>(AlignPointer(
task->user_data +
task->user_data_start, alignment));
1024 const Byte*
const user_storage_end = std::end(
task->user_data);
1026 if (user_storage_start <= user_storage_end)
1028 const std::size_t user_storage_size = user_storage_end - user_storage_start;
1030 return {user_storage_start, user_storage_size};
1033 return {
nullptr, 0u};
1039 JobAssert(continuation->q_type ==
k_InvalidQueueType,
"A continuation must not have already been submitted to a queue or already added as a continuation.");
1040 JobAssert(continuation->next_continuation.isNull(),
"A continuation must not have already been added to another task.");
1042 const TaskPtr new_head = task::PointerToTaskPtr(continuation);
1043 continuation->q_type = queue;
1044 continuation->next_continuation =
self->first_continuation.load(std::memory_order_relaxed);
1046 while (!std::atomic_compare_exchange_strong(&self->first_continuation, &continuation->next_continuation, new_head))
1053 const auto old_ref_count =
task->ref_count.fetch_add(1, std::memory_order_relaxed);
1055 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.");
1056 (void)old_ref_count;
1061 const auto old_ref_count =
task->ref_count.fetch_sub(1, std::memory_order_relaxed);
1063 JobAssert(old_ref_count >= 0,
"taskDecRef: Called too many times.");
1064 (void)old_ref_count;
1069 return task->num_unfinished_tasks.load(std::memory_order_acquire) == -1;
1085 const TaskPtr task_ptr = task::PointerToTaskPtr(
self);
1087 self->q_type = queue;
1093 task::SubmitQPushHelper(task_ptr,
worker, &
worker->normal_queue);
1106 while (!main_queue->
Push(task_ptr))
1109 worker::TryRunTask(
worker);
1115 task::SubmitQPushHelper(task_ptr,
worker, &
worker->worker_queue);
1119#if defined(__GNUC__)
1120 __builtin_unreachable();
1121#elif defined(_MSC_VER)
1131 if (num_pending_jobs >= num_workers)
1133 system::WakeUpAllWorkers();
1137 system::WakeUpOneWorker();
1147 JobAssert(
task->owning_worker == worker_id,
"You may only call this function with a task created on the current 'Worker'.");
1149 system::WakeUpAllWorkers();
1155 worker::TryRunTask(
worker);
1169 num_unfinished_tasks{1},
1172 first_continuation{
nullptr},
1173 next_continuation{
nullptr},
1181#if defined(_MSC_VER)
1182#define NativePause YieldProcessor
1183#elif defined(__clang__) && defined(__SSE__) || defined(__INTEL_COMPILER)
1184#include <xmmintrin.h>
1185#define NativePause _mm_pause
1186#elif defined(__arm__)
1188#define NativePause __yield
1190#define NativePause() __asm__ __volatile__("yield")
1193#define NativePause std::this_thread::yield
1207 std::this_thread::yield();
1214 return task->q_type;
1219 return AlignPointer(
task->user_data, alignment);
1224 const Byte*
const user_storage_end = std::end(
task->user_data);
1225 Byte*
const requested_storage_start =
static_cast<Byte*
>(AlignPointer(
task->user_data, alignment));
1226 const Byte*
const requested_storage_end = requested_storage_start + num_bytes;
1228 JobAssert(requested_storage_end <= user_storage_end,
"Cannot store object within the task's user storage. ");
1230 task->user_data_start =
static_cast<std::uint8_t
>(requested_storage_end -
task->user_data);
1232 return requested_storage_start;
1242 Task*
const task = task::TaskPtrToPointer(task_ptr);
1243 task::RunTaskFunction(
task);
1252#undef IS_SINGLE_THREADED
1254#if defined(_MSC_VER)
1256#pragma warning(push)
1257#pragma warning(disable : 4244)
1258#pragma warning(disable : 4146)
1262#include "pcg_basic.c"
1264#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.
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...
void 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...
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