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
37
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 "atomic.h"
44#include "error_handler.h"
45#include "file_error_numbers.h"
46#include "integral_limits.h"
47#include "memory.h"
48#include "memory_model.h"
49#include "parameter_type.h"
50#include "span.h"
51#include "utility.h"
52
53#include <stddef.h>
54
55#if ETL_HAS_ATOMIC
56
57namespace etl
58{
59 //***************************************************************************
61 //***************************************************************************
62 class bip_buffer_exception : public exception
63 {
64 public:
65
66 bip_buffer_exception(string_type reason_, string_type file_name_, numeric_type line_number_)
67 : exception(reason_, file_name_, line_number_)
68 {
69 }
70 };
71
72 //***************************************************************************
74 //***************************************************************************
75 class bip_buffer_reserve_invalid : public bip_buffer_exception
76 {
77 public:
78
79 bip_buffer_reserve_invalid(string_type file_name_, numeric_type line_number_)
80 : bip_buffer_exception(ETL_ERROR_TEXT("bip_buffer:reserve", ETL_BIP_BUFFER_SPSC_ATOMIC_FILE_ID"A"), file_name_, line_number_)
81 {
82 }
83 };
84
85 //***************************************************************************
87 //***************************************************************************
88 template <size_t Memory_Model = etl::memory_model::MEMORY_MODEL_LARGE>
89 class bip_buffer_spsc_atomic_base
90 {
91 public:
92
94 typedef typename etl::size_type_lookup<Memory_Model>::type size_type;
95
96 //*************************************************************************
98 //*************************************************************************
99 bool empty() const
100 {
101 return size() == 0;
102 }
103
104 //*************************************************************************
106 //*************************************************************************
107 bool full() const
108 {
109 return available() == 0;
110 }
111
112 //*************************************************************************
115 //*************************************************************************
116 size_type size() const
117 {
118 size_type write_index = write.load(etl::memory_order_acquire);
119 size_type read_index = read.load(etl::memory_order_acquire);
120
121 // no wraparound
122 if (write_index >= read_index)
123 {
124 // size is distance between read and write
125 return write_index - read_index;
126 }
127 else
128 {
129 size_type last_index = last.load(etl::memory_order_acquire);
130
131 // size is distance between beginning and write, plus read and last
132 return (write_index - 0) + (last_index - read_index);
133 }
134 }
135
136 //*************************************************************************
138 //*************************************************************************
139 size_type available() const
140 {
141 size_type write_index = write.load(etl::memory_order_acquire);
142 size_type read_index = read.load(etl::memory_order_acquire);
143
144 // no wraparound
145 if (write_index >= read_index)
146 {
147 size_type forward_size = capacity() - write_index;
148
149 // check if there's more space if wrapping around
150 if (read_index > (forward_size + 1))
151 {
152 return read_index - 1;
153 }
154 else
155 {
156 return forward_size;
157 }
158 }
159 else // read_index > write_index
160 {
161 return read_index - write_index - 1;
162 }
163 }
164
165 //*************************************************************************
167 //*************************************************************************
168 size_type capacity() const
169 {
170 return Reserved;
171 }
172
173 //*************************************************************************
175 //*************************************************************************
176 size_type max_size() const
177 {
178 return Reserved;
179 }
180
181 protected:
182
183 //*************************************************************************
185 //*************************************************************************
186 bip_buffer_spsc_atomic_base(size_type reserved_)
187 : read(0)
188 , write(0)
189 , last(0)
190 , Reserved(reserved_)
191 {
192 }
193
194 //*************************************************************************
195 void reset()
196 {
197 read.store(0, etl::memory_order_release);
198 write.store(0, etl::memory_order_release);
199 last.store(0, etl::memory_order_release);
200 }
201
202 //*************************************************************************
203 size_type get_write_reserve(size_type* psize, size_type fallback_size = numeric_limits<size_type>::max())
204 {
205 size_type write_index = write.load(etl::memory_order_relaxed);
206 size_type read_index = read.load(etl::memory_order_acquire);
207
208 // No wraparound
209 if (write_index >= read_index)
210 {
211 size_type forward_size = capacity() - write_index;
212
213 // We still fit in linearly
214 if (*psize <= forward_size)
215 {
216 return write_index;
217 }
218 // There isn't more space even when wrapping around,
219 // or the linear size is good enough as fallback
220 else if ((read_index <= (forward_size + 1)) || (fallback_size <= forward_size))
221 {
222 *psize = forward_size;
223 return write_index;
224 }
225 // Better wrap around now
226 else
227 {
228 // Check if size fits.
229 // When wrapping, the write index cannot reach read index,
230 // then we'd not be able to distinguish wrapped situation from linear.
231 if (*psize >= read_index)
232 {
233 if (read_index > 0)
234 {
235 *psize = read_index - 1;
236 }
237 else
238 {
239 *psize = 0;
240 }
241 }
242
243 return 0;
244 }
245 }
246 else // read_index > write_index
247 {
248 // Doesn't fit
249 if (*psize >= read_index - write_index)
250 {
251 *psize = read_index - write_index - 1;
252 }
253
254 return write_index;
255 }
256 }
257
258 //*************************************************************************
259 void apply_write_reserve(size_type windex, size_type wsize)
260 {
261 if (wsize > 0)
262 {
263 size_type write_index = write.load(etl::memory_order_relaxed);
264 size_type read_index = read.load(etl::memory_order_acquire);
265
266 // Wrapped around already
267 if (write_index < read_index)
268 {
269 ETL_ASSERT_OR_RETURN((windex == write_index) && ((wsize + 1) <= read_index), ETL_ERROR(bip_buffer_reserve_invalid));
270 }
271 // No wraparound so far, also not wrapping around with this block
272 else if (windex == write_index)
273 {
274 ETL_ASSERT_OR_RETURN(wsize <= (capacity() - write_index), ETL_ERROR(bip_buffer_reserve_invalid));
275
276 // Move both indexes forward
277 last.store(windex + wsize, etl::memory_order_release);
278 }
279 // Wrapping around now
280 else
281 {
282 ETL_ASSERT_OR_RETURN((windex == 0) && ((wsize + 1) <= read_index), ETL_ERROR(bip_buffer_reserve_invalid));
283
284 // Correct wrapping point
285 last.store(write_index, etl::memory_order_release);
286 }
287
288 // Always update write index
289 write.store(windex + wsize, etl::memory_order_release);
290 }
291 }
292
293 //*************************************************************************
294 size_type get_read_reserve(size_type* psize)
295 {
296 size_type read_index = read.load(etl::memory_order_relaxed);
297 size_type write_index = write.load(etl::memory_order_acquire);
298
299 if (read_index > write_index)
300 {
301 // Writer has wrapped around
302 size_type last_index = last.load(etl::memory_order_relaxed);
303
304 if (read_index == last_index)
305 {
306 // Reader reached the end, start read from 0
307 read_index = 0;
308 }
309 else // (read_index < last_index)
310 {
311 // Use the remaining buffer at the end
312 write_index = last_index;
313 }
314 }
315 else
316 {
317 // No wraparound, nothing to adjust
318 }
319
320 // Limit to max available size
321 if ((write_index - read_index) < *psize)
322 {
323 *psize = write_index - read_index;
324 }
325
326 return read_index;
327 }
328
329 //*************************************************************************
330 void apply_read_reserve(size_type rindex, size_type rsize)
331 {
332 if (rsize > 0)
333 {
334 size_type rsize_checker = rsize;
335 ETL_ASSERT_OR_RETURN((rindex == get_read_reserve(&rsize_checker)) && (rsize == rsize_checker), ETL_ERROR(bip_buffer_reserve_invalid));
336
337 read.store(rindex + rsize, etl::memory_order_release);
338 }
339 }
340
341 private:
342
343 etl::atomic<size_type> read;
344 etl::atomic<size_type> write;
345 etl::atomic<size_type> last;
346 const size_type Reserved;
347
348 #if defined(ETL_POLYMORPHIC_SPSC_BIP_BUFFER_ATOMIC) || defined(ETL_POLYMORPHIC_CONTAINERS)
349
350 public:
351
352 virtual ~bip_buffer_spsc_atomic_base() {}
353 #else
354
355 protected:
356
357 ~bip_buffer_spsc_atomic_base() {}
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
369 typedef typename etl::bip_buffer_spsc_atomic_base<Memory_Model> base_t;
370 using base_t::apply_read_reserve;
371 using base_t::apply_write_reserve;
372 using base_t::get_read_reserve;
373 using base_t::get_write_reserve;
374 using base_t::reset;
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;
394 size_type rindex = get_read_reserve(&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 = static_cast<size_type>(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 //*************************************************************************
413 span<T> write_reserve(size_type max_reserve_size)
414 {
415 size_type reserve_size = max_reserve_size;
416 size_type windex = get_write_reserve(&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 //*************************************************************************
425 span<T> write_reserve_optimal(size_type min_reserve_size = 1U)
426 {
427 size_type reserve_size = numeric_limits<size_type>::max();
428 size_type windex = get_write_reserve(&reserve_size, min_reserve_size);
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 = static_cast<size_type>(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 //*************************************************************************
465 ibip_buffer_spsc_atomic(T* p_buffer_, size_type reserved_)
466 : base_t(reserved_)
467 , p_buffer(p_buffer_)
468 {
469 }
470
471 private:
472
473 // Disable copy construction and assignment.
474 ibip_buffer_spsc_atomic(const ibip_buffer_spsc_atomic&) ETL_DELETE;
475 ibip_buffer_spsc_atomic& operator=(const ibip_buffer_spsc_atomic&) ETL_DELETE;
476
477 #if ETL_USING_CPP11
478 ibip_buffer_spsc_atomic(ibip_buffer_spsc_atomic&&) = delete;
479 ibip_buffer_spsc_atomic& operator=(ibip_buffer_spsc_atomic&&) = delete;
480 #endif
481
482 T* const p_buffer;
483 };
484
485 //***************************************************************************
492 //***************************************************************************
493 template <typename T, const size_t Size, const size_t Memory_Model = etl::memory_model::MEMORY_MODEL_LARGE>
494 class bip_buffer_spsc_atomic : public ibip_buffer_spsc_atomic<T, Memory_Model>
495 {
496 private:
497
498 typedef typename etl::ibip_buffer_spsc_atomic<T, Memory_Model> base_t;
499
500 public:
501
502 typedef typename base_t::size_type size_type;
503
504 private:
505
506 static ETL_CONSTANT size_type Reserved_Size = size_type(Size);
507
508 public:
509
510 ETL_STATIC_ASSERT((Size <= (etl::integral_limits<size_type>::max)), "Size too large for memory model");
511
512 static ETL_CONSTANT size_type MAX_SIZE = size_type(Size);
513
514 //*************************************************************************
516 //*************************************************************************
517 bip_buffer_spsc_atomic()
518 : base_t(reinterpret_cast<T*>(buffer.raw), Reserved_Size)
519 {
520 }
521
522 //*************************************************************************
524 //*************************************************************************
525 ~bip_buffer_spsc_atomic()
526 {
527 base_t::clear();
528 }
529
530 private:
531
533 etl::uninitialized_buffer_of<T, Reserved_Size> buffer;
534 };
535
536 template <typename T, const size_t Size, const size_t Memory_Model>
537 ETL_CONSTANT typename bip_buffer_spsc_atomic<T, Size, Memory_Model>::size_type bip_buffer_spsc_atomic<T, Size, Memory_Model>::Reserved_Size;
538} // namespace etl
539
540#endif /* ETL_HAS_ATOMIC && ETL_USING_CPP11 */
541
542#endif /* ETL_BIP_BUFFER_SPSC_ATOMIC_INCLUDED */
Definition exception.h:59
Definition absolute.h:40
etl::optional< T > read(etl::bit_stream_reader &stream)
Read a checked type from a stream.
Definition bit_stream.h:1430
ETL_CONSTEXPR TContainer::size_type size(const TContainer &container)
Definition iterator.h:1434
bool write(etl::bit_stream_writer &stream, bool value)
Definition bit_stream.h:1035