16 static_assert(std::is_nothrow_swappable_v<T>,
"T must be nothrow swappable");
29 static constexpr uint8_t index_mask = 0x03;
30 static constexpr uint8_t dirty_bit = 0x04;
32 struct alignas(64) slot
39 explicit slot(U&& val)
40 : data(std::forward<U>(val))
45 std::array<slot, 3> m_buffers{};
47 alignas(64) std::atomic<uint8_t> m_mid_state{1};
48 alignas(64) uint8_t m_write_idx{0};
49 alignas(64) uint8_t m_read_idx{2};
51 triple_buffer(
const triple_buffer&) =
delete;
52 triple_buffer& operator=(
const triple_buffer&) =
delete;
53 triple_buffer(triple_buffer&&) =
delete;
54 triple_buffer& operator=(triple_buffer&&) =
delete;
57 explicit triple_buffer(
const T& init)
noexcept(std::is_nothrow_copy_constructible_v<T>)
58 : m_buffers{slot{init}, slot{init}, slot{init}}
62 triple_buffer() noexcept(std::is_nothrow_default_constructible_v<T>)
63 requires std::is_default_constructible_v<T>
67 void produce(T& value)
noexcept
70 swap(m_buffers[m_write_idx].data, value);
72 const uint8_t new_mid =
static_cast<uint8_t
>(m_write_idx | dirty_bit);
73 const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
74 m_write_idx = old_mid & index_mask;
77 void produce(T&& value)
noexcept(std::is_nothrow_move_assignable_v<T>)
79 m_buffers[m_write_idx].data = std::move(value);
81 const uint8_t new_mid =
static_cast<uint8_t
>(m_write_idx | dirty_bit);
82 const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
83 m_write_idx = old_mid & index_mask;
88 bool consume(T& result)
noexcept
90 const uint8_t state = m_mid_state.load(std::memory_order_acquire);
91 if(!(state & dirty_bit))
96 const uint8_t new_mid = m_read_idx;
97 const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
98 m_read_idx = old_mid & index_mask;
101 swap(result, m_buffers[m_read_idx].data);
111 const T& read_buffer() const noexcept
112 requires std::is_copy_assignable_v<T>
114 return m_buffers[m_read_idx].data;
117 bool has_new_data() const noexcept
119 return m_mid_state.load(std::memory_order_acquire) & dirty_bit;
124 requires std::is_trivially_copyable_v<T>
125class triple_buffer<T>
128 static constexpr uint8_t index_mask = 0x03;
129 static constexpr uint8_t dirty_bit = 0x04;
131 struct alignas(64) slot
136 std::array<slot, 3> m_buffers{};
138 alignas(64) std::atomic<uint8_t> m_mid_state{1};
139 alignas(64) uint8_t m_write_idx{0};
140 alignas(64) uint8_t m_read_idx{2};
143 alignas(64) T m_last_read{};
145 triple_buffer(
const triple_buffer&) =
delete;
146 triple_buffer& operator=(
const triple_buffer&) =
delete;
147 triple_buffer(triple_buffer&&) =
delete;
148 triple_buffer& operator=(triple_buffer&&) =
delete;
151 explicit triple_buffer(T init) noexcept
152 : m_buffers{slot{init}, slot{init}, slot{init}}
157 triple_buffer() noexcept = default;
159 void produce(T value) noexcept
161 m_buffers[m_write_idx].data = value;
163 const uint8_t new_mid =
static_cast<uint8_t
>(m_write_idx | dirty_bit);
164 const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
165 m_write_idx = old_mid & index_mask;
170 bool consume(T& result)
noexcept
172 const uint8_t state = m_mid_state.load(std::memory_order_acquire);
173 if(!(state & dirty_bit))
178 const uint8_t new_mid = m_read_idx;
179 const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
180 m_read_idx = old_mid & index_mask;
182 m_last_read = m_buffers[m_read_idx].data;
183 result = m_last_read;
188 T read() const noexcept {
return m_last_read; }
190 bool has_new_data() const noexcept
192 return m_mid_state.load(std::memory_order_acquire) & dirty_bit;
196template <
typename T,
typename Container = std::vector<T>>
197class triple_buffer_raw
200 std::is_nothrow_swappable_v<Container>,
"Container must be nothrow swappable");
203 static constexpr uint8_t index_mask = 0x03;
204 static constexpr uint8_t dirty_bit = 0x04;
206 struct alignas(64) slot
212 template <
typename U>
213 explicit slot(U&& val)
214 : data(std::forward<U>(val))
219 std::array<slot, 3> m_buffers{};
221 alignas(64) std::atomic<uint8_t> m_mid_state{1};
222 alignas(64) uint8_t m_write_idx{0};
223 alignas(64) uint8_t m_read_idx{2};
225 triple_buffer_raw(
const triple_buffer_raw&) =
delete;
226 triple_buffer_raw& operator=(
const triple_buffer_raw&) =
delete;
227 triple_buffer_raw(triple_buffer_raw&&) =
delete;
228 triple_buffer_raw& operator=(triple_buffer_raw&&) =
delete;
231 triple_buffer_raw() noexcept(std::is_nothrow_default_constructible_v<Container>)
235 explicit triple_buffer_raw(std::
size_t initial_capacity)
237 for(
auto& s : m_buffers)
238 s.data.reserve(initial_capacity);
243 Container& write_buffer() noexcept {
return m_buffers[m_write_idx].data; }
247 void publish() noexcept
249 const uint8_t new_mid =
static_cast<uint8_t
>(m_write_idx | dirty_bit);
250 const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
251 m_write_idx = old_mid & index_mask;
255 template <
typename InputIt>
256 void produce(InputIt first, InputIt last)
258 m_buffers[m_write_idx].data.assign(first, last);
263 void produce(std::span<const T> src)
265 m_buffers[m_write_idx].data.assign(src.begin(), src.end());
272 bool consume() noexcept
274 const uint8_t state = m_mid_state.load(std::memory_order_acquire);
275 if(!(state & dirty_bit))
278 const uint8_t new_mid = m_read_idx;
279 const uint8_t old_mid = m_mid_state.exchange(new_mid, std::memory_order_acq_rel);
280 m_read_idx = old_mid & index_mask;
285 std::span<const T> read_span() const noexcept
287 const auto& c = m_buffers[m_read_idx].data;
288 return {c.data(), c.size()};
291 const Container& read_buffer() const noexcept {
return m_buffers[m_read_idx].data; }
293 bool has_new_data() const noexcept
295 return m_mid_state.load(std::memory_order_acquire) & dirty_bit;