BluFedora Job System v1.0.0
This is a C++ job system library for use in game engines.
job::SPMCDeque< T > Class Template Reference

#include <job_queue.hpp>

Public Types

using size_type = std::int64_t
 
using atomic_size_type = std::atomic< size_type >
 

Public Member Functions

 SPMCDeque ()=default
 
 ~SPMCDeque ()=default
 
void Initialize (AtomicT *const memory_backing, const size_type capacity) noexcept
 
SPMCDequeStatus Push (const T &value)
 
SPMCDequeStatus Pop (T *const out_value)
 
SPMCDequeStatus Steal (T *const out_value)
 

Private Types

using AtomicT = std::atomic< T >
 

Private Member Functions

AtomicTElementAt (const size_type index) const noexcept
 

Private Attributes

atomic_size_type m_ProducerIndex
 
atomic_size_type m_ConsumerIndex
 
unsigned char m_Padding0 [k_FalseSharingPadSize - sizeof(m_ProducerIndex) - sizeof(m_ConsumerIndex)]
 
AtomicTm_Data
 
size_type m_Capacity
 
size_type m_CapacityMask
 

Detailed Description

template<typename T>
class job::SPMCDeque< T >

Definition at line 238 of file job_queue.hpp.

Member Typedef Documentation

◆ AtomicT

template<typename T >
using job::SPMCDeque< T >::AtomicT = std::atomic<T>
private

Definition at line 241 of file job_queue.hpp.

◆ size_type

template<typename T >
using job::SPMCDeque< T >::size_type = std::int64_t

Definition at line 246 of file job_queue.hpp.

◆ atomic_size_type

template<typename T >
using job::SPMCDeque< T >::atomic_size_type = std::atomic<size_type>

Definition at line 247 of file job_queue.hpp.

Constructor & Destructor Documentation

◆ SPMCDeque()

template<typename T >
job::SPMCDeque< T >::SPMCDeque ( )
default

◆ ~SPMCDeque()

template<typename T >
job::SPMCDeque< T >::~SPMCDeque ( )
default

Member Function Documentation

◆ Initialize()

template<typename T >
void job::SPMCDeque< T >::Initialize ( AtomicT *const  memory_backing,
const size_type  capacity 
)
inlinenoexcept

Definition at line 264 of file job_queue.hpp.

265 {
266 m_ProducerIndex = 0;
267 m_ConsumerIndex = 0;
268 m_Data = memory_backing;
269 m_Capacity = capacity;
270 m_CapacityMask = capacity - 1;
271
272 JobAssert((m_Capacity & m_CapacityMask) == 0, "Capacity must be a power of 2.");
273 }
atomic_size_type m_ConsumerIndex
Definition: job_queue.hpp:251
size_type m_CapacityMask
Definition: job_queue.hpp:258
size_type m_Capacity
Definition: job_queue.hpp:257
atomic_size_type m_ProducerIndex
Definition: job_queue.hpp:250
AtomicT * m_Data
Definition: job_queue.hpp:256
#define JobAssert(expr, msg)
Definition: job_api.hpp:32

References JobAssert, job::SPMCDeque< T >::m_Capacity, job::SPMCDeque< T >::m_CapacityMask, job::SPMCDeque< T >::m_ConsumerIndex, job::SPMCDeque< T >::m_Data, and job::SPMCDeque< T >::m_ProducerIndex.

◆ Push()

template<typename T >
SPMCDequeStatus job::SPMCDeque< T >::Push ( const T &  value)
inline

Definition at line 277 of file job_queue.hpp.

278 {
279 const size_type write_index = m_ProducerIndex.load(std::memory_order_relaxed);
280 const size_type read_index = m_ConsumerIndex.load(std::memory_order_acquire);
281 const size_type size = write_index - read_index;
282
283 if (size > m_CapacityMask)
284 {
286 }
287
288 ElementAt(write_index)->store(value, std::memory_order_relaxed);
289
290 m_ProducerIndex.store(write_index + 1, std::memory_order_release);
291
293 }
std::int64_t size_type
Definition: job_queue.hpp:246
AtomicT * ElementAt(const size_type index) const noexcept
Definition: job_queue.hpp:367
@ FAILED_SIZE
Returned from Push, Pop and Steal.
@ SUCCESS
Returned from Push, Pop and Steal.

References job::SPMCDeque< T >::ElementAt(), job::FAILED_SIZE, job::SPMCDeque< T >::m_CapacityMask, job::SPMCDeque< T >::m_ConsumerIndex, job::SPMCDeque< T >::m_ProducerIndex, and job::SUCCESS.

◆ Pop()

template<typename T >
SPMCDequeStatus job::SPMCDeque< T >::Pop ( T *const  out_value)
inline

Definition at line 295 of file job_queue.hpp.

296 {
297 const size_type producer_index = m_ProducerIndex.load(std::memory_order_relaxed) - 1;
298
299 // Reserve the slot at the producer end.
300 m_ProducerIndex.store(producer_index, std::memory_order_relaxed);
301
302 // The above store needs to happen before this next read
303 // to have consistent view of the buffer.
304 //
305 // `m_ProducerIndex` can only be written to by this thread
306 // so first reserve a slot then we read what the other threads have to say.
307 //
308 std::atomic_thread_fence(std::memory_order_seq_cst);
309
310 size_type consumer_index = m_ConsumerIndex.load(std::memory_order_relaxed);
311
312 if (consumer_index <= producer_index)
313 {
314 if (consumer_index == producer_index) // Only one item in queue
315 {
316 const bool successful_pop = m_ConsumerIndex.compare_exchange_strong(consumer_index, consumer_index + 1, std::memory_order_seq_cst, std::memory_order_relaxed);
317
318 if (successful_pop)
319 {
320 *out_value = ElementAt(producer_index)->load(std::memory_order_relaxed);
321 }
322
323 m_ProducerIndex.store(producer_index + 1, std::memory_order_relaxed);
325 }
326
327 *out_value = ElementAt(producer_index)->load(std::memory_order_relaxed);
329 }
330
331 // Empty Queue, so restore to canonical empty.
332 m_ProducerIndex.store(producer_index + 1, std::memory_order_seq_cst);
334 }
@ FAILED_RACE
Returned from Pop and Steal.

References job::SPMCDeque< T >::ElementAt(), job::FAILED_RACE, job::FAILED_SIZE, job::SPMCDeque< T >::m_ConsumerIndex, job::SPMCDeque< T >::m_ProducerIndex, and job::SUCCESS.

◆ Steal()

template<typename T >
SPMCDequeStatus job::SPMCDeque< T >::Steal ( T *const  out_value)
inline

Definition at line 338 of file job_queue.hpp.

339 {
340 size_type read_index = m_ConsumerIndex.load(std::memory_order_acquire);
341
342 // Must fully read `m_ConsumerIndex` before we read the producer owned `m_ProducerIndex`.
343 std::atomic_thread_fence(std::memory_order_seq_cst);
344
345 const size_type write_index = m_ProducerIndex.load(std::memory_order_acquire);
346
347 // if (next_read_index <= write_index)
348 if (read_index < write_index)
349 {
350 // Must load result before the CAS, since a push can happen concurrently right after the CAS.
351 T result = ElementAt(read_index)->load(std::memory_order_relaxed);
352
353 // Need strong memory ordering to read the element before the cas.
354 if (m_ConsumerIndex.compare_exchange_strong(read_index, read_index + 1, std::memory_order_seq_cst, std::memory_order_relaxed))
355 {
356 *out_value = std::move(result);
358 }
359
361 }
362
364 }

References job::SPMCDeque< T >::ElementAt(), job::FAILED_RACE, job::FAILED_SIZE, job::SPMCDeque< T >::m_ConsumerIndex, job::SPMCDeque< T >::m_ProducerIndex, and job::SUCCESS.

◆ ElementAt()

template<typename T >
AtomicT * job::SPMCDeque< T >::ElementAt ( const size_type  index) const
inlineprivatenoexcept

Member Data Documentation

◆ m_ProducerIndex

template<typename T >
atomic_size_type job::SPMCDeque< T >::m_ProducerIndex
private

◆ m_ConsumerIndex

template<typename T >
atomic_size_type job::SPMCDeque< T >::m_ConsumerIndex
private

◆ m_Padding0

template<typename T >
unsigned char job::SPMCDeque< T >::m_Padding0[k_FalseSharingPadSize - sizeof(m_ProducerIndex) - sizeof(m_ConsumerIndex)]
private

Definition at line 252 of file job_queue.hpp.

◆ m_Data

template<typename T >
AtomicT* job::SPMCDeque< T >::m_Data
private

◆ m_Capacity

template<typename T >
size_type job::SPMCDeque< T >::m_Capacity
private

Definition at line 257 of file job_queue.hpp.

Referenced by job::SPMCDeque< T >::Initialize().

◆ m_CapacityMask

template<typename T >
size_type job::SPMCDeque< T >::m_CapacityMask
private

The documentation for this class was generated from the following file: