NcEngine
SpscQueue.h
Go to the documentation of this file.
1
5#pragma once
6
8
9#include <array>
10#include <atomic>
11
12namespace nc
13{
14NC_DISABLE_WARNING_PUSH
15NC_DISABLE_WARNING_MSVC(4324)
16
17
18template <class T, size_t N>
20{
21 public:
22 [[nodiscard]] auto push(const T& v) -> bool
23 {
24 return emplace(v);
25 }
26
27 [[nodiscard]] auto push(T&& v) -> bool
28 {
29 return emplace(std::move(v));
30 }
31
32 [[nodiscard]] auto pop(T& out) -> bool
33 {
34 if (is_empty(m_consumer))
35 {
36 refresh_consumer_cache();
37 if (is_empty(m_consumer))
38 {
39 return false;
40 }
41 }
42
43 out = std::move(m_buffer[index(m_consumer.head)]);
44 advance_consumer();
45 return true;
46 }
47
48 template<class... Args>
49 [[nodiscard]] auto emplace(Args&&... args) -> bool
50 {
51 if (is_full(m_producer))
52 {
53 refresh_producer_cache();
54 if (is_full(m_producer))
55 {
56 return false;
57 }
58 }
59
60 m_buffer[index(m_producer.tail)] = T(std::forward<Args>(args)...);
61 advance_producer();
62 return true;
63 }
64
65 [[nodiscard]] auto empty() const noexcept -> bool
66 {
67 return m_head.load(std::memory_order_acquire) ==
68 m_tail.load(std::memory_order_acquire);
69 }
70
71 private:
72 struct Cache
73 {
74 size_t head = 0;
75 size_t tail = 0;
76 };
77
78 static_assert(N >= 1, "Queue capacity must be >= 1");
79 static_assert(std::atomic<size_t>::is_always_lock_free);
80 static constexpr auto CacheLine = nc::hardware_destructive_interference_size;
81
82 alignas(CacheLine) std::array<T, N> m_buffer = {};
83 alignas(CacheLine) std::atomic<size_t> m_head = 0;
84 alignas(CacheLine) std::atomic<size_t> m_tail = 0;
85 alignas(CacheLine) Cache m_producer;
86 alignas(CacheLine) Cache m_consumer;
87
88 static constexpr auto index(size_t i) -> size_t
89 {
90 if constexpr ((N & (N - 1)) == 0)
91 {
92 return i & (N - 1);
93 }
94 else
95 {
96 return i % N;
97 }
98 }
99
100 static auto is_full(const Cache& cache) -> bool { return cache.tail - cache.head >= N; }
101 static auto is_empty(const Cache& cache) -> bool { return cache.head == cache.tail; }
102 void refresh_producer_cache() { m_producer.head = m_head.load(std::memory_order_acquire); }
103 void refresh_consumer_cache() { m_consumer.tail = m_tail.load(std::memory_order_acquire); }
104 void advance_producer() { m_tail.store(++m_producer.tail, std::memory_order_release); }
105 void advance_consumer() { m_head.store(++m_consumer.head, std::memory_order_release); }
106};
107
108NC_DISABLE_WARNING_POP
109} // namespace nc
Fixed-size single-producer/single-consumer queue.
Definition: SpscQueue.h:20