Embedded Template Library 1.0
Loading...
Searching...
No Matches
bip_buffer_spsc_atomic.h
Go to the documentation of this file.
1
2
3/******************************************************************************
4The MIT License(MIT)
5
6Embedded Template Library.
7https://github.com/ETLCPP/etl
8https://www.etlcpp.com
9
10Copyright(c) 2021 Benedek Kupper, John Wellbelove
11
12Permission is hereby granted, free of charge, to any person obtaining a copy
13of this software and associated documentation files(the "Software"), to deal
14in the Software without restriction, including without limitation the rights
15to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
16copies of the Software, and to permit persons to whom the Software is
17furnished to do so, subject to the following conditions :
18
19The above copyright notice and this permission notice shall be included in all
20copies or substantial portions of the Software.
21
22THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
25AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28SOFTWARE.
29******************************************************************************/
30
38#ifndef ETL_BIP_BUFFER_SPSC_ATOMIC_INCLUDED
39#define ETL_BIP_BUFFER_SPSC_ATOMIC_INCLUDED
40
41#include "platform.h"
42#include "alignment.h"
43#include "parameter_type.h"
44#include "atomic.h"
45#include "memory.h"
46#include "memory_model.h"
47#include "integral_limits.h"
48#include "utility.h"
49#include "error_handler.h"
50#include "span.h"
51#include "file_error_numbers.h"
52
53#include <stddef.h>
54#include <stdint.h>
55
56#if ETL_HAS_ATOMIC
57
58namespace etl
59{
60 //***************************************************************************
62 //***************************************************************************
63 class bip_buffer_exception: public exception
64 {
65 public:
66
67 bip_buffer_exception(string_type reason_, string_type file_name_, numeric_type line_number_)
68 : exception(reason_, file_name_, line_number_)
69 {
70 }
71 };
72
73 //***************************************************************************
75 //***************************************************************************
76 class bip_buffer_reserve_invalid: public bip_buffer_exception
77 {
78 public:
79
80 bip_buffer_reserve_invalid(string_type file_name_, numeric_type line_number_)
81 : bip_buffer_exception(ETL_ERROR_TEXT("bip_buffer:reserve", ETL_BIP_BUFFER_SPSC_ATOMIC_FILE_ID"A"), file_name_, line_number_)
82 {
83 }
84 };
85
86 //***************************************************************************
88 //***************************************************************************
89 template <size_t Memory_Model = etl::memory_model::MEMORY_MODEL_LARGE>
90 class bip_buffer_spsc_atomic_base
91 {
92 public:
93
95 typedef typename etl::size_type_lookup<Memory_Model>::type size_type;
96
97 //*************************************************************************
99 //*************************************************************************
100 bool empty() const
101 {
102 return size() == 0;
103 }
104
105 //*************************************************************************
107 //*************************************************************************
108 bool full() const
109 {
110 return available() == 0;
111 }
112
113 //*************************************************************************
116 //*************************************************************************
117 size_type size() const
118 {
119 size_type write_index = write.load(etl::memory_order_acquire);
120 size_type read_index = read.load(etl::memory_order_acquire);
121
122 // no wraparound
123 if (write_index >= read_index)
124 {
125 // size is distance between read and write
126 return write_index - read_index;
127 }
128 else
129 {
130 size_type last_index = last.load(etl::memory_order_acquire);
131
132 // size is distance between beginning and write, plus read and last
133 return (write_index - 0) + (last_index - read_index);
134 }
135 }
136
137 //*************************************************************************
139 //*************************************************************************
140 size_type available() const
141 {
142 size_type write_index = write.load(etl::memory_order_acquire);
143 size_type read_index = read.load(etl::memory_order_acquire);
144
145 // no wraparound
146 if (write_index >= read_index)
147 {
148 size_type forward_size = capacity() - write_index;
149
150 // check if there's more space if wrapping around
151 if (read_index > (forward_size + 1))
152 {
153 return read_index - 1;
154 }
155 else
156 {
157 return forward_size;
158 }
159 }
160 else // read_index > write_index
161 {
162 return read_index - write_index - 1;
163 }
164 }
165
166 //*************************************************************************
168 //*************************************************************************
169 size_type capacity() const
170 {
171 return Reserved;
172 }
173
174 //*************************************************************************
176 //*************************************************************************
177 size_type max_size() const
178 {
179 return Reserved;
180 }
181
182 protected:
183
184 //*************************************************************************
186 //*************************************************************************
188 : read(0)
189 , write(0)
190 , last(0)
192 {
193 }
194
195 //*************************************************************************
196 void reset()
197 {
198 read.store(0, etl::memory_order_release);
199 write.store(0, etl::memory_order_release);
200 last.store(0, etl::memory_order_release);
201 }
202
203 //*************************************************************************
204 size_type get_write_reserve(size_type* psize, size_type fallback_size = numeric_limits<size_type>::max())
205 {
206 size_type write_index = write.load(etl::memory_order_relaxed);
207 size_type read_index = read.load(etl::memory_order_acquire);
208
209 // No wraparound
210 if (write_index >= read_index)
211 {
212 size_type forward_size = capacity() - write_index;
213
214 // We still fit in linearly
215 if (*psize <= forward_size)
216 {
217 return write_index;
218 }
219 // There isn't more space even when wrapping around,
220 // or the linear size is good enough as fallback
221 else if ((read_index <= (forward_size + 1)) || (fallback_size <= forward_size))
222 {
224 return write_index;
225 }
226 // Better wrap around now
227 else
228 {
229 // Check if size fits.
230 // When wrapping, the write index cannot reach read index,
231 // then we'd not be able to distinguish wrapped situation from linear.
232 if (*psize >= read_index)
233 {
234 if (read_index > 0)
235 {
236 *psize = read_index - 1;
237 }
238 else
239 {
240 *psize = 0;
241 }
242 }
243
244 return 0;
245 }
246 }
247 else // read_index > write_index
248 {
249 // Doesn't fit
250 if ((write_index + *psize) >= read_index)
251 {
252 *psize = read_index - write_index - 1;
253 }
254
255 return write_index;
256 }
257 }
258
259 //*************************************************************************
260 void apply_write_reserve(size_type windex, size_type wsize)
261 {
262 if (wsize > 0)
263 {
264 size_type write_index = write.load(etl::memory_order_relaxed);
265 size_type read_index = read.load(etl::memory_order_acquire);
266
267 // Wrapped around already
268 if (write_index < read_index)
269 {
270 ETL_ASSERT_OR_RETURN((windex == write_index) && ((wsize + 1) <= read_index), ETL_ERROR(bip_buffer_reserve_invalid));
271 }
272 // No wraparound so far, also not wrapping around with this block
273 else if (windex == write_index)
274 {
275 ETL_ASSERT_OR_RETURN(wsize <= (capacity() - write_index), ETL_ERROR(bip_buffer_reserve_invalid));
276
277 // Move both indexes forward
278 last.store(windex + wsize, etl::memory_order_release);
279 }
280 // Wrapping around now
281 else
282 {
283 ETL_ASSERT_OR_RETURN((windex == 0) && ((wsize + 1) <= read_index), ETL_ERROR(bip_buffer_reserve_invalid));
284 }
285
286 // Always update write index
287 write.store(windex + wsize, etl::memory_order_release);
288 }
289 }
290
291 //*************************************************************************
292 size_type get_read_reserve(size_type* psize)
293 {
294 size_type read_index = read.load(etl::memory_order_relaxed);
295 size_type write_index = write.load(etl::memory_order_acquire);
296
297 if (read_index > write_index)
298 {
299 // Writer has wrapped around
300 size_type last_index = last.load(etl::memory_order_relaxed);
301
302 if (read_index == last_index)
303 {
304 // Reader reached the end, start read from 0
305 read_index = 0;
306 }
307 else // (read_index < last_index)
308 {
309 // Use the remaining buffer at the end
310 write_index = last_index;
311 }
312 }
313 else
314 {
315 // No wraparound, nothing to adjust
316 }
317
318 // Limit to max available size
319 if ((write_index - read_index) < *psize)
320 {
321 *psize = write_index - read_index;
322 }
323
324 return read_index;
325 }
326
327 //*************************************************************************
328 void apply_read_reserve(size_type rindex, size_type rsize)
329 {
330 if (rsize > 0)
331 {
332 size_type rsize_checker = rsize;
333 ETL_ASSERT_OR_RETURN((rindex == get_read_reserve(&rsize_checker)) && (rsize == rsize_checker), ETL_ERROR(bip_buffer_reserve_invalid));
334
335 read.store(rindex + rsize, etl::memory_order_release);
336 }
337 }
338
339 private:
340
344 const size_type Reserved;
345
346#if defined(ETL_POLYMORPHIC_SPSC_BIP_BUFFER_ATOMIC) || defined(ETL_POLYMORPHIC_CONTAINERS)
347 public:
348
350 {
351 }
352#else
353 protected:
354
356 {
357 }
358#endif
359 };
360
361 //***************************************************************************
363 //***************************************************************************
364 template <typename T, const size_t Memory_Model = etl::memory_model::MEMORY_MODEL_LARGE>
365 class ibip_buffer_spsc_atomic : public bip_buffer_spsc_atomic_base<Memory_Model>
366 {
367 private:
368
370 using base_t::reset;
371 using base_t::get_read_reserve;
372 using base_t::apply_read_reserve;
373 using base_t::get_write_reserve;
374 using base_t::apply_write_reserve;
375
376 public:
377
378 typedef T value_type;
379 typedef T& reference;
380 typedef const T& const_reference;
381#if ETL_USING_CPP11
382 typedef T&& rvalue_reference;
383#endif
384 typedef typename base_t::size_type size_type;
385
386 using base_t::max_size;
387
388 //*************************************************************************
389 // Reserves a memory area for reading (up to the max_reserve_size).
390 //*************************************************************************
391 span<T> read_reserve(size_type max_reserve_size = numeric_limits<size_type>::max())
392 {
393 size_type reserve_size = max_reserve_size;
395
396 return span<T>(p_buffer + rindex, reserve_size);
397 }
398
399 //*************************************************************************
400 // Commits the previously reserved read memory area
401 // the reserve can be trimmed at the end before committing.
402 // Throws bip_buffer_reserve_invalid
403 //*************************************************************************
404 void read_commit(const span<T> &reserve)
405 {
406 size_type rindex = etl::distance(p_buffer, reserve.data());
407 apply_read_reserve(rindex, reserve.size());
408 }
409
410 //*************************************************************************
411 // Reserves a memory area for writing up to the max_reserve_size.
412 //*************************************************************************
414 {
415 size_type reserve_size = max_reserve_size;
417
418 return span<T>(p_buffer + windex, reserve_size);
419 }
420
421 //*************************************************************************
422 // Reserves an optimal memory area for writing. The buffer will only wrap
423 // around if the available forward space is less than min_reserve_size.
424 //*************************************************************************
426 {
427 size_type reserve_size = numeric_limits<size_type>::max();
429
430 return span<T>(p_buffer + windex, reserve_size);
431 }
432
433 //*************************************************************************
434 // Commits the previously reserved write memory area
435 // the reserve can be trimmed at the end before committing.
436 // Throws bip_buffer_reserve_invalid
437 //*************************************************************************
438 void write_commit(const span<T> &reserve)
439 {
440 size_type windex = etl::distance(p_buffer, reserve.data());
441 apply_write_reserve(windex, reserve.size());
442 }
443
444 //*************************************************************************
446 //*************************************************************************
447 void clear()
448 {
449 // the buffer might be split into two contiguous blocks
450 for (span<T> reader = read_reserve(); reader.size() > 0; reader = read_reserve())
451 {
452 destroy(reader.begin(), reader.end());
453 read_commit(reader);
454 }
455 // now the buffer is already empty
456 // resetting the buffer here is beneficial to have
457 // the whole buffer available for a single block,
458 // but it requires synchronization between the writer and reader threads
459 reset();
460 }
461
462 protected:
463
464 //*************************************************************************
466 : base_t(reserved_)
467 , p_buffer(p_buffer_)
468 {
469 }
470
471 private:
472
473 // Disable copy construction and assignment.
475 ibip_buffer_spsc_atomic& operator =(const ibip_buffer_spsc_atomic&) ETL_DELETE;
476
477#if ETL_USING_CPP11
479 ibip_buffer_spsc_atomic& operator =(ibip_buffer_spsc_atomic&&) = delete;
480#endif
481
482 T* const p_buffer;
483 };
484
485 //***************************************************************************
491 //***************************************************************************
492 template <typename T, const size_t Size, const size_t Memory_Model = etl::memory_model::MEMORY_MODEL_LARGE>
493 class bip_buffer_spsc_atomic : public ibip_buffer_spsc_atomic<T, Memory_Model>
494 {
495 private:
496
497 typedef typename etl::ibip_buffer_spsc_atomic<T, Memory_Model> base_t;
498
499 public:
500
501 typedef typename base_t::size_type size_type;
502
503 private:
504
505 static ETL_CONSTANT size_type Reserved_Size = size_type(Size);
506
507 public:
508
509 ETL_STATIC_ASSERT((Size <= (etl::integral_limits<size_type>::max)), "Size too large for memory model");
510
511 static ETL_CONSTANT size_type MAX_SIZE = size_type(Size);
512
513 //*************************************************************************
515 //*************************************************************************
517 : base_t(reinterpret_cast<T*>(buffer.raw), Reserved_Size)
518 {
519 }
520
521 //*************************************************************************
523 //*************************************************************************
525 {
526 base_t::clear();
527 }
528
529 private:
530
533 };
534
535 template <typename T, const size_t Size, const size_t Memory_Model>
536 ETL_CONSTANT typename bip_buffer_spsc_atomic<T, Size, Memory_Model>::size_type bip_buffer_spsc_atomic<T, Size, Memory_Model>::Reserved_Size;
537}
538
539#endif /* ETL_HAS_ATOMIC && ETL_USING_CPP11 */
540
541#endif /* ETL_BIP_BUFFER_SPSC_ATOMIC_INCLUDED */
Definition integral_limits.h:516
bitset_ext
Definition absolute.h:38
size_t max_size() const
Returns the maximum number of items in the variant_pool.
Definition variant_pool_generator.h:395
etl::optional< T > read(etl::bit_stream_reader &stream)
Read a checked type from a stream.
Definition bit_stream.h:1377
ETL_CONSTEXPR TContainer::size_type size(const TContainer &container)
Definition iterator.h:1187
void destroy(const T *const p)
Destroys the object.
Definition variant_pool_generator.h:370
bool write(etl::bit_stream_writer &stream, bool value)
Definition bit_stream.h:995
pair holds two objects of arbitrary type
Definition utility.h:164
ETL_CONSTEXPR pair()
Default constructor.
Definition utility.h:176
Definition memory_model.h:50