59 const std::lock_guard<std::mutex> guard(
m_Lock);
73 bool Pop(T*
const out_value)
75 JobAssert(out_value !=
nullptr,
"`out_value` cannot be a nullptr.");
77 const std::lock_guard<std::mutex> guard(
m_Lock);
103#define Job_CacheAlign alignas(k_FalseSharingPadSize)
138 static_assert(atomic_size_type::is_always_lock_free,
"Expected to be lockfree.");
159 return PushLazy([&value](T*
const destination) { ::new (destination) T(value); });
162 bool Pop(T*
const out_value)
164 JobAssert(out_value !=
nullptr,
"`out_value` cannot be a nullptr.");
166 return PopLazy([out_value](T&& value) { *out_value = std::move(value); });
169 template<
typename CallbackFn>
189 template<
typename CallbackFn>
203 T*
const element =
ElementAt(read_index);
204 callback(std::move(*element));
243 static_assert(AtomicT::is_always_lock_free,
"T Should be a small pointer-like type, expected to be lock-free when atomic.");
281 const size_type size = write_index - read_index;
288 ElementAt(write_index)->store(value, std::memory_order_relaxed);
308 std::atomic_thread_fence(std::memory_order_seq_cst);
312 if (consumer_index <= producer_index)
314 if (consumer_index == producer_index)
316 const bool successful_pop =
m_ConsumerIndex.compare_exchange_strong(consumer_index, consumer_index + 1, std::memory_order_seq_cst, std::memory_order_relaxed);
320 *out_value =
ElementAt(producer_index)->load(std::memory_order_relaxed);
327 *out_value =
ElementAt(producer_index)->load(std::memory_order_relaxed);
343 std::atomic_thread_fence(std::memory_order_seq_cst);
348 if (read_index < write_index)
351 T result =
ElementAt(read_index)->load(std::memory_order_relaxed);
354 if (
m_ConsumerIndex.compare_exchange_strong(read_index, read_index + 1, std::memory_order_seq_cst, std::memory_order_relaxed))
356 *out_value = std::move(result);
417 return PushImpl<true>(elements, num_elements) != 0u;
422 return PushImpl<false>(elements, num_elements);
427 return PopImpl<true>(out_elements, num_elements) != 0u;
432 return PopImpl<false>(out_elements, num_elements);
436 template<
bool allOrNothing>
440 if (RequestWriteRange<allOrNothing>(&range, num_elements))
444 return written_elements;
450 template<
bool allOrNothing>
454 if (RequestPopRange<allOrNothing>(&range, num_elements))
458 return read_elements;
464 template<
bool allOrNothing>
475 if constexpr (allOrNothing)
477 if (capacity_left < num_items)
483 if (capacity_left == 0)
488 const size_type num_element_to_write = capacity_left < num_items ? capacity_left : num_items;
490 new_head = old_head + num_element_to_write;
492 }
while (!
m_ProducerPending.compare_exchange_weak(old_head, new_head, std::memory_order_relaxed, std::memory_order_relaxed));
494 *out_range = {old_head, new_head};
498 template<
bool allOrNothing>
509 size_t capacity_left = (
m_Capacity - distance);
510 if constexpr (allOrNothing)
512 if (capacity_left < num_items)
523 const size_type num_element_to_read = capacity_left < num_items ? capacity_left : num_items;
525 new_tail = old_tail + num_element_to_read;
527 }
while (!
m_ConsumerPending.compare_exchange_weak(old_tail, new_tail, std::memory_order_relaxed, std::memory_order_relaxed));
529 *out_range = {old_tail, new_tail};
538 const size_type num_items_before_split = write_size < capacity_before_split ? write_size : capacity_before_split;
539 const size_type num_items_after_split = write_size - num_items_before_split;
541 std::copy_n(elements + 0u, num_items_before_split,
m_Queue + real_start);
542 std::copy_n(elements + num_items_before_split, num_items_after_split,
m_Queue + 0u);
552 const size_type num_items_before_split = read_size < capacity_before_split ? read_size : capacity_before_split;
553 const size_type num_items_after_split = read_size - num_items_before_split;
555 std::copy_n(std::make_move_iterator(
m_Queue + real_start), num_items_before_split, out_elements + 0u);
556 std::copy_n(std::make_move_iterator(
m_Queue + 0u), num_items_after_split, out_elements + num_items_before_split);
564 while (!commit->compare_exchange_weak(
565 start_copy = range.
start,
567 std::memory_order_release,
568 std::memory_order_relaxed))
576 return (b > a) ? (b - a) :
m_Capacity - a + b;
T * elementAt(const size_type raw_index) const noexcept
bool Push(const T &value)
size_type mask(const size_type raw_index) const noexcept
bool Pop(T *const out_value)
void Initialize(T *const memory_backing, const size_type capacity) noexcept
std::atomic< size_type > atomic_size_type
size_type PushImpl(const value_type *elements, const size_type num_elements)
atomic_size_type m_ConsumerPending
bool PopExact(value_type *out_elements, const size_type num_elements)
bool RequestPopRange(IndexRange *out_range, const size_type num_items)
size_type PopUpTo(value_type *out_elements, const size_type num_elements)
size_type Distance(const size_type a, const size_type b) const
unsigned char m_Padding0[k_FalseSharingPadSize - sizeof(atomic_size_type) *2]
size_type ReadElements(value_type *const out_elements, const IndexRange range) const
bool PushExact(const value_type *elements, const size_type num_elements)
atomic_size_type m_ConsumerCommited
unsigned char m_Padding1[k_FalseSharingPadSize - sizeof(atomic_size_type) *2]
size_type PopImpl(value_type *out_elements, const size_type num_elements)
void Initialize(value_type *const memory_backing, const size_type capacity) noexcept
void Commit(atomic_size_type *commit, const IndexRange range) const
atomic_size_type m_ProducerCommited
atomic_size_type m_ProducerPending
size_type WriteElements(const value_type *const elements, const IndexRange range)
bool RequestWriteRange(IndexRange *out_range, const size_type num_items)
size_type PushUpTo(const value_type *elements, const size_type num_elements)
unsigned char m_Padding2[k_FalseSharingPadSize - sizeof(m_Queue) - sizeof(m_Capacity)]
SPMCDequeStatus Pop(T *const out_value)
void Initialize(AtomicT *const memory_backing, const size_type capacity) noexcept
std::atomic< size_type > atomic_size_type
AtomicT * ElementAt(const size_type index) const noexcept
atomic_size_type m_ConsumerIndex
unsigned char m_Padding0[k_FalseSharingPadSize - sizeof(m_ProducerIndex) - sizeof(m_ConsumerIndex)]
SPMCDequeStatus Push(const T &value)
atomic_size_type m_ProducerIndex
SPMCDequeStatus Steal(T *const out_value)
unsigned char m_Padding1[k_FalseSharingPadSize - sizeof(m_CachedConsumerIndex)]
size_type m_CachedProducerIndex
unsigned char m_Padding3[k_FalseSharingPadSize - sizeof(m_CachedProducerIndex)]
bool Push(const T &value)
bool IsFull(const size_type head, const size_type tail) const noexcept
size_type m_CachedConsumerIndex
bool Pop(T *const out_value)
std::atomic< size_type > atomic_size_type
unsigned char m_Padding0[k_FalseSharingPadSize - sizeof(m_ProducerIndex)]
void Initialize(T *const memory_backing, const size_type capacity) noexcept
atomic_size_type m_ConsumerIndex
unsigned char m_Padding2[k_FalseSharingPadSize - sizeof(m_ConsumerIndex)]
static bool IsEmpty(const size_type head, const size_type tail) noexcept
bool PushLazy(CallbackFn &&callback)
bool PopLazy(CallbackFn &&callback)
atomic_size_type m_ProducerIndex
unsigned char m_Padding4[k_FalseSharingPadSize - sizeof(m_Data) - sizeof(m_Capacity) - sizeof(m_CapacityMask)]
T * ElementAt(const size_type index) const noexcept
API for a multi-threading job system.
#define JobAssert(expr, msg)
static constexpr std::size_t k_FalseSharingPadSize
void PauseProcessor() noexcept
CPU pause instruction to indicate when you are in a spin wait loop.
@ FAILED_RACE
Returned from Pop and Steal.
@ FAILED_SIZE
Returned from Push, Pop and Steal.
@ SUCCESS
Returned from Push, Pop and Steal.