mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
circularBuffer.hpp
Go to the documentation of this file.
1/** \file circularBuffer.hpp
2 * \author Jared R. Males
3 * \brief A circular buffer class
4 * \ingroup signal_processing_files
5 *
6 */
7
8//***********************************************************************//
9// Copyright 2020 Jared R. Males (jaredmales@gmail.com)
10//
11// This file is part of mxlib.
12//
13// mxlib is free software: you can redistribute it and/or modify
14// it under the terms of the GNU General Public License as published by
15// the Free Software Foundation, either version 3 of the License, or
16// (at your option) any later version.
17//
18// mxlib is distributed in the hope that it will be useful,
19// but WITHOUT ANY WARRANTY; without even the implied warranty of
20// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21// GNU General Public License for more details.
22//
23// You should have received a copy of the GNU General Public License
24// along with mxlib. If not, see <http://www.gnu.org/licenses/>.
25//***********************************************************************//
26
27#ifndef sigproc_circularBuffer
28#define sigproc_circularBuffer
29
30#include <atomic>
31#include <vector>
32
33namespace mx
34{
35namespace sigproc
36{
37
38/** \defgroup circular_buffer Circular Buffer
39 * \brief A circular buffer class
40 *
41 * Three options for the circular buffer are provided, each inheriting the same underlying interface. The choices
42 * vary the way in which the wrapping is handled at the beginning and end of the storage. These are:
43 * - circularBufferMod: uses the mod operator, this is slowest in all situations and is provided for comparison
44 * - circularBufferBranch: uses an if statement (branch) to test for location in memory. Slower in sequential access,
45 * possibly slightly faster in random access.
46 * - circularBufferIndex: uses a pre-populated index array, which adds some memory overhead. Fastest in sequential
47 * access, possibly slightly slower in random access.
48 *
49 * Benchmarks are clear that circularBufferIndex is fastest for sequential acceess, e.g. one element after the other in
50 * sequential order, by something like 30%. For random access circularBufferBranch is possibly slightly faster, but not
51 * enough tests were performed to be conclusive.
52 *
53 * circularBufferMod is always much slower due to use of the `%` operator. Don't use this for real work.
54 *
55 * The memory overhead of circularBufferIndex is `3*maxEntries*sizeof(indexT)`, where `maxEntries` is the maximum length
56 * of the buffer, and indexT is the type used for indexing and sizes. The factor of 3 enables negative offsets, i.e.
57 * for traversal in reverse.
58 *
59 * \note circularBufferIndex will not properly wrap until it is full. Attempts to access wrapped entries prior
60 * to inserting maxEntries entries will segfault.
61 *
62 * \todo perform circular buffer testing on an isolated core, with one test per executable
63 * \todo implement a circular buffer with fixed power-of-two size to test `&` modulo
64 *
65 * \ingroup signal_processing
66 */
67
68/// CRTP base class for all circular buffers, providing the underlying memory management and accessors.
69/** The CRTP derived classes implement a standard interface based on how they handle wrapping from the end
70 * to the beginning of the buffer.
71 *
72 * \tparam _derivedT the child class type
73 * \tparam _storedT the type stored in teh circular buffer
74 * \tparam _indexT the index type, also used for sizes (must be signed)
75 *
76 * \ingroup circular_buffer
77 */
78template <typename _derivedT, typename _storedT, typename _indexT>
80{
81 public:
82 typedef _derivedT derivedT; ///< The child class
83
84 typedef _storedT storedT; ///< The type stored in the circular buffer
85 typedef _indexT indexT; ///< The index type, also used for sizes
86
87 static_assert( std::is_signed_v<_indexT> == true, "circularBuffer indexT must be signed" );
88
89 protected:
90 std::vector<storedT> m_buffer; ///< The circular buffer storage
91
92 indexT m_maxEntries{ 0 }; ///< The maximum number of entries to allow in the buffer before wrapping
93 indexT m_nextEntry{ 0 }; ///< Index into m_buffer of the next entry. This is the oldest entry in the buffer.
94 indexT m_latest{ 0 }; ///< Index into m_buff of the latest entry. This is the newest entry in the buffer.
95
96 uint64_t m_mono{ 0 }; /**< A monotonic counter, which is incremented for each new entry,
97 and reset on call to maxEntries.*/
98
99 public:
100 /// Default c'tor
102
103 /// Sizing constructor
104 /** Sets the maximum size of the buffer. Note that this will not be the size until
105 * a full set of entries have been added to the buffer.
106 */
107 explicit circularBufferBase( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ );
108
109 /// Set the maximum size of the buffer.
110 /** Note that this will not be the size until
111 * a full set of entries have been added to the buffer.
112 */
113 void maxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ );
114
115 /// Get the maximum size of the buffer.
116 /** Note that this is not the current size of the buffer until
117 * at least maxEntries have been added. Use size() to check current size.
118 *
119 * \returns the maximum size of the buffer, m_maxEntries
120 */
122
123 /// Get the number of entries.
124 /** Note that this is the current size. It will not
125 * be the same as maxEntries() until at least maxEntries
126 * have been added.
127 */
128 indexT size() const;
129
130 /// Add the next entry to the circular buffer
131 /** Adds the entry (will incur a deep copy) and updates
132 * the wrapping system.
133 */
134 void nextEntry( const storedT &newEnt /**< [in] the new entry to add to the buffer*/ );
135
136 /// Move to the next entry in the circular buffer
137 /** If not yet at maxEntries entries, this emplaces a new default constructed entry.
138 * Otherwise it increments counters so that the old latest entry is now the newest entry.
139 */
140 void nextEntry();
141
142 /// Returns the index of the earliest entry
143 /** This is particularly useful for accessing entries with the at() function
144 * over an unchanging sequence if asynchronous updates are possible.
145 */
147
148 /// Returns the index of the latest entry
149 /**
150 */
152
153 uint64_t mono();
154
155 /// Get the entry at a given index
156 /** `idx=0` is the earliest entry in the buffer. `idx=1` is the one after that.
157 * I.e., this counts forward from the oldest data
158 *
159 * \returns a reference to the indicated entry in the circular buffer.
160 */
161 storedT &operator[]( indexT idx /**< [in] the index of the entry to access*/ );
162
163 const storedT &operator[]( indexT idx /**< [in] the index of the entry to access*/ ) const;
164
165 /// Get the entry at a given index relative a fixed reference entry
166 /** `idx=0` is the entry at refEntry. `idx=1` is the one after that.
167 * I.e., this counts forward from the oldest data
168 *
169 * \returns a reference to the indicated entry in the circular buffer.
170 */
171 storedT &at( indexT refEntry, ///< [in] the entry to start counting from
172 indexT idx ///< [in] the index of the entry to access
173 );
174
175 /// Get the entry at a given index relative a fixed start entry, const version
176 /** `idx=0` is the entry at refEntry. `idx=1` is the one after that.
177 * I.e., this counts forward from the oldest data
178 *
179 * \overload
180 *
181 * \returns a const reference to the indicated entry in the circular buffer.
182 */
183 const storedT &at( indexT refEntry, ///< [in] the entry to start counting from
184 indexT idx ///< [in] the index of the entry to access
185 ) const;
186
187 private:
188 derivedT &derived()
189 {
190 return *static_cast<derivedT *>( this );
191 }
192
193 const derivedT &derived() const
194 {
195 return *static_cast<const derivedT *>( this );
196 }
197};
198
199template <class derivedT, typename storedT, typename indexT>
203
204template <class derivedT, typename storedT, typename indexT>
209
210template <class derivedT, typename storedT, typename indexT>
212{
213 m_buffer.clear();
214 m_nextEntry = 0;
215 m_latest = 0;
216
217 m_maxEntries = maxEnt;
218 m_buffer.reserve( m_maxEntries );
219
220 derived().setMaxEntries( maxEnt );
221
222 m_mono = 0;
223}
224
225template <class derivedT, typename storedT, typename indexT>
230
231template <class derivedT, typename storedT, typename indexT>
236
237template <class derivedT, typename storedT, typename indexT>
239{
240 if( m_buffer.size() < m_maxEntries )
241 {
242 m_buffer.push_back( newEnt );
243 m_latest = m_buffer.size() - 1;
244 m_nextEntry = 0;
245 }
246 else
247 {
248 m_buffer[m_nextEntry] = newEnt;
250 ++m_nextEntry;
251 if( m_nextEntry > m_buffer.size() - 1 )
252 {
253 m_nextEntry = 0;
254 }
255 }
256
257 ++m_mono;
258}
259
260template <class derivedT, typename storedT, typename indexT>
262{
263 if( m_buffer.size() < m_maxEntries )
264 {
265 m_buffer.emplace_back();
266 m_latest = m_buffer.size() - 1;
267 m_nextEntry = 0;
268 }
269 else
270 {
272 ++m_nextEntry;
273 if( m_nextEntry > m_buffer.size() - 1 )
274 {
275 m_nextEntry = 0;
276 }
277 }
278
279 ++m_mono;
280}
281
282template <class derivedT, typename storedT, typename indexT>
287
288template <class derivedT, typename storedT, typename indexT>
293
294template <class derivedT, typename storedT, typename indexT>
295uint64_t circularBufferBase<derivedT, storedT, indexT>::mono()
296{
297 return m_mono;
298}
299
300template <class derivedT, typename storedT, typename indexT>
305
306template <class derivedT, typename storedT, typename indexT>
308{
309 return derived().at( m_nextEntry, idx );
310}
311
312template <class derivedT, typename storedT, typename indexT>
314{
315 return derived().at( refEntry, idx );
316}
317
318template <class derivedT, typename storedT, typename indexT>
320{
321 return derived().at( refEntry, idx );
322}
323
324/// Circular buffer which wraps with an if statement (branching) [faster than mod, less memory than index]
325/**
326 * \ingroup circular_buffer
327 */
328template <typename _storedT, typename _indexT>
329class circularBufferBranch : public circularBufferBase<circularBufferBranch<_storedT, _indexT>, _storedT, _indexT>
330{
331 public:
332 typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping
333 typedef _indexT indexT; ///< The index type, also used for sizes
334
335 public:
336 /// Default c'tor
340
341 /// Sizing constructor
342 /** Sets the maximum size of the buffer. Note that this will not be the size until
343 * a full set of entries have been added to the buffer.
344 */
345 explicit circularBufferBranch( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ )
347 {
348 }
349
350 /// Interface implementation for maxEntries.
351 /** Resets the wrap entry to 0.
352 */
353 void setMaxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ )
354 {
355 }
356
357 /// Interface implementation for entry access
358 /** Accesses the idx-th element relative to refEntry, using a branch (if-statement) to wrap
359 *
360 * \returns a reference to the idx-th element
361 */
362 storedT &at( indexT refEntry, ///< [in] the entry to start counting from
363 indexT idx ///< [in] the index of the entry to access
364 )
365 {
366 indexT _idx = refEntry + idx;
367 if( _idx < 0 )
368 {
369 return this->m_buffer[_idx + this->m_buffer.size()];
370 }
371 else if( _idx > this->m_buffer.size() - 1 )
372 {
373 return this->m_buffer[_idx - this->m_buffer.size()];
374 }
375 else
376 {
377 return this->m_buffer[_idx];
378 }
379 }
380
381 /// Interface implementation for entry access, const version
382 /** Accesses the idx-th element relative to refEntry, using a branch (if-statement) to wrap
383 *
384 * \overload
385 *
386 * \returns a const reference to the idx-th element
387 */
388 const storedT &at( indexT refEntry, ///< [in] the entry to start counting from
389 indexT idx ///< [in] the index of the entry to access
390 ) const
391 {
392 indexT _idx = refEntry + idx;
393 if( _idx < 0 )
394 {
395 return this->m_buffer[_idx + this->m_buffer.size()];
396 }
397 else if( _idx > this->m_buffer.size() - 1 )
398 {
399 return this->m_buffer[_idx - this->m_buffer.size()];
400 }
401 else
402 {
403 return this->m_buffer[_idx];
404 }
405 }
406};
407
408/// Circular buffer which wraps with the mod operator [very slow]
409/**
410 * \ingroup circular_buffer
411 */
412template <typename _storedT, typename _indexT>
413class circularBufferMod : public circularBufferBase<circularBufferMod<_storedT, _indexT>, _storedT, _indexT>
414{
415 public:
416 typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping
417 typedef _indexT indexT; ///< The index type, also used for sizes
418
419 /// Default c'tor
423
424 /// Sizing constructor
425 /** Sets the maximum size of the buffer. Note that this will not be the size until
426 * a full set of entries have been added to the buffer.
427 */
428 explicit circularBufferMod( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ )
430 {
431 }
432
433 /// Interface implementation for maxEntries.
434 /** A no-op
435 */
436 void setMaxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ )
437 {
438 }
439
440 /// Interface implementation for entry access
441 /** Accesses the idx-th element relative to refEntry, using the mod operator to wrap
442 *
443 * \returns a reference to the idx-th element
444 */
445 storedT &at( indexT refEntry, ///< [in] the entry to start counting from
446 indexT idx ///< [in] the index of the entry to access
447 )
448 {
449 return this->m_buffer[( refEntry + idx ) % this->m_buffer.size()];
450 }
451
452 /// Interface implementation for entry access, const version
453 /** Accesses the idx-th element relative to refEntry, using the mod operator to wrap
454 *
455 * \overload
456 *
457 * \returns a const reference to the idx-th element
458 */
459 const storedT &at( indexT refEntry, ///< [in] the entry to start counting from
460 indexT idx ///< [in] the index of the entry to access
461 ) const
462 {
463 return this->m_buffer[( refEntry + idx ) % this->m_buffer.size()];
464 }
465};
466
467/// Circular buffer which wraps with a pre-populated indices array [generally fastest]
468/**
469 * \ingroup circular_buffer
470 */
471template <typename _storedT, typename _indexT, bool fixed_size = false>
473
474template <typename _storedT, typename _indexT>
475class circularBufferIndex<_storedT, _indexT, false>
476 : public circularBufferBase<circularBufferIndex<_storedT, _indexT, false>, _storedT, _indexT>
477{
478 public:
479 typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping
480 typedef _indexT indexT; ///< The index type, also used for sizes
481
482 protected:
483 std::vector<size_t> m_indices; ///< Vector of indices for fast indexing into parent's m_buffer
484
485 public:
486 /// Default c'tor
488 {
489 }
490
491 /// Sizing constructor
492 /** Sets the maximum size of the buffer. Note that this will not be the size until
493 * a full set of entries have been added to the buffer.
494 */
495 explicit circularBufferIndex( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ )
496 : circularBufferBase<circularBufferIndex, storedT, indexT>( maxEnt )
497 {
498 }
499
500 /// Interface implementation for maxEntries.
501 /** Resizes and populates the indices array.
502 */
503 void setMaxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ )
504 {
505 m_indices.resize( 3 * this->m_maxEntries );
506 for( size_t i = 0; i < this->m_maxEntries; ++i )
507 {
508 m_indices[i] = i;
509 m_indices[this->m_maxEntries + i] = i;
510 m_indices[2 * this->m_maxEntries + i] = i;
511 }
512 }
513
514 /// Interface implementation for entry access
515 /** Accesses the idx-th element relative to refEntry, using the pre-populated indices to wrap
516 *
517 * \returns a reference to the idx-th element
518 */
519 storedT &at( indexT refEntry, ///< [in] the entry to start counting from
520 indexT idx ///< [in] the index of the entry to access
521 )
522 {
523 return this->m_buffer[m_indices[this->m_maxEntries + refEntry + idx]];
524 }
525
526 /// Interface implementation for entry access, const version
527 /** Accesses the idx-th element relative to refEntry, using the pre-populated indices to wrap
528 *
529 * \overload
530 *
531 * \returns a const reference to the idx-th element
532 */
533 const storedT &at( indexT refEntry, ///< [in] the entry to start counting from
534 indexT idx ///< [in] the index of the entry to access
535 ) const
536 {
537 return this->m_buffer[this->m_maxEntries + m_indices[refEntry + idx]];
538 }
539};
540
541/// Fixed-size circular buffer specialization for single-producer/single-consumer use.
542/**
543 * \ingroup circular_buffer
544 */
545template <typename _storedT, typename _indexT>
546class circularBufferIndex<_storedT, _indexT, true>
547{
548 public:
549 typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping
550 typedef _indexT indexT; ///< The index type, also used for sizes
551
552 static_assert( std::is_signed_v<_indexT> == true, "circularBuffer indexT must be signed" );
553 static_assert( std::is_trivially_copyable_v<_storedT> == true,
554 "fixed_size circularBufferIndex requires trivially copyable storedT" );
555
556 /// Snapshot of the readable state of the buffer.
558 {
559 indexT earliest{ 0 }; ///< Earliest readable slot in the current snapshot.
560 indexT latest{ 0 }; ///< Latest published slot in the current snapshot.
561 indexT validEntries{ 0 }; ///< Number of currently valid entries.
562 indexT maxEntries{ 0 }; ///< Fixed capacity.
563 uint64_t mono{ 0 }; ///< Publication generation for retry validation.
564 bool full{ false }; ///< Whether the buffer has reached fixed capacity.
565 };
566
567 protected:
568 std::vector<storedT> m_buffer; ///< The circular buffer storage.
569 std::vector<size_t> m_indices; ///< Vector of indices for fast indexing into m_buffer.
570
571 std::atomic<indexT> m_nextEntry{ 0 }; ///< The slot that will be written next by the producer.
572 std::atomic<indexT> m_latest{ 0 }; ///< The latest slot published by the producer.
573 std::atomic<indexT> m_validEntries{ 0 }; ///< The number of valid published entries.
574 std::atomic<uint64_t> m_mono{ 0 }; ///< A monotonic publication counter.
575
576 indexT m_maxEntries{ 0 }; ///< Fixed capacity of the buffer.
577
578 public:
579 /// Default c'tor.
581
582 /// Sizing constructor.
583 explicit circularBufferIndex( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ )
584 {
585 maxEntries( maxEnt );
586 }
587
588 /// Set the maximum size of the buffer.
589 void maxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ )
590 {
591 m_buffer.clear();
592 m_indices.clear();
593
594 m_maxEntries = maxEnt;
595
596 if( m_maxEntries > 0 )
597 {
598 m_buffer.resize( m_maxEntries );
599 m_indices.resize( 3 * m_maxEntries );
600
601 for( size_t i = 0; i < static_cast<size_t>( m_maxEntries ); ++i )
602 {
603 m_indices[i] = i;
604 m_indices[m_maxEntries + i] = i;
605 m_indices[2 * m_maxEntries + i] = i;
606 }
607 }
608
609 m_nextEntry.store( 0, std::memory_order_release );
610 m_latest.store( 0, std::memory_order_release );
611 m_validEntries.store( 0, std::memory_order_release );
612 m_mono.store( 0, std::memory_order_release );
613 }
614
615 /// Get the fixed capacity of the buffer.
617 {
618 return m_maxEntries;
619 }
620
621 /// Get the number of valid entries.
622 indexT size() const
623 {
624 return m_validEntries.load( std::memory_order_acquire );
625 }
626
627 /// Add the next entry to the circular buffer.
628 void nextEntry( const storedT &newEnt /**< [in] the new entry to add to the buffer*/ )
629 {
630 if( m_maxEntries <= 0 )
631 {
632 return;
633 }
634
635 indexT next = m_nextEntry.load( std::memory_order_relaxed );
636
637 m_buffer[next] = newEnt;
638
639 indexT latest = next;
640 ++next;
641 if( next >= m_maxEntries )
642 {
643 next = 0;
644 }
645
646 indexT validEntries = m_validEntries.load( std::memory_order_relaxed );
647 if( validEntries < m_maxEntries )
648 {
649 ++validEntries;
650 }
651
652 m_latest.store( latest, std::memory_order_relaxed );
653 m_validEntries.store( validEntries, std::memory_order_relaxed );
654 m_nextEntry.store( next, std::memory_order_relaxed );
655 m_mono.fetch_add( 1, std::memory_order_release );
656 }
657
658 /// Move to the next entry using a default constructed value.
660 {
661 nextEntry( storedT{} );
662 }
663
664 /// Returns the index of the earliest entry.
666 {
667 snapshotT sn = snapshot();
668 return sn.earliest;
669 }
670
671 /// Returns the index of the latest entry.
673 {
674 return m_latest.load( std::memory_order_acquire );
675 }
676
677 /// Returns the publication counter.
678 uint64_t mono() const
679 {
680 return m_mono.load( std::memory_order_acquire );
681 }
682
683 /// Get the entry at a given index.
684 storedT operator[]( indexT idx /**< [in] the index of the entry to access*/ ) const
685 {
686 snapshotT sn = snapshot();
687
688 if( sn.validEntries <= 0 )
689 {
690 return storedT{};
691 }
692
693 return at( sn.earliest, idx );
694 }
695
696 /// Get the entry at a given index relative a fixed reference entry.
697 storedT at( indexT refEntry, ///< [in] the entry to start counting from
698 indexT idx ///< [in] the index of the entry to access
699 ) const
700 {
701 return m_buffer[physicalIndex( refEntry, idx )];
702 }
703
704 /// Return a snapshot of the current readable region.
706 {
707 snapshotT sn;
708
709 sn.mono = m_mono.load( std::memory_order_acquire );
710 sn.latest = m_latest.load( std::memory_order_relaxed );
711 sn.validEntries = m_validEntries.load( std::memory_order_relaxed );
713 sn.full = ( sn.validEntries == sn.maxEntries && sn.maxEntries > 0 );
714
715 if( sn.full )
716 {
717 sn.earliest = m_nextEntry.load( std::memory_order_relaxed );
718 }
719 else
720 {
721 sn.earliest = 0;
722 }
723
724 return sn;
725 }
726
727 /// Determine whether a logical window is readable from the provided snapshot.
728 bool windowReadable( const snapshotT &sn, ///< [in] the snapshot to validate against
729 indexT refEntry, ///< [in] the entry to start counting from
730 indexT count ///< [in] the number of entries requested
731 ) const
732 {
733 if( count <= 0 || refEntry < 0 || count > sn.validEntries )
734 {
735 return false;
736 }
737
738 if( !sn.full )
739 {
740 return refEntry + count <= sn.validEntries;
741 }
742
743 for( indexT n = 0; n < count; ++n )
744 {
745 if( physicalIndex( refEntry, n ) == sn.earliest )
746 {
747 return false;
748 }
749 }
750
751 return true;
752 }
753
754 /// Copy a logical sequence into caller-owned storage, retrying if the producer advances.
755 bool loadSequence( indexT refEntry, ///< [in] the entry to start counting from
756 indexT count, ///< [in] the number of entries requested
757 storedT *dest, ///< [out] destination storage of size count
758 snapshotT &sn, ///< [out] the validated snapshot used for the copy
759 int maxRetries = 3 /**< [in] the maximum number of retries*/
760 ) const
761 {
762 if( dest == nullptr )
763 {
764 return false;
765 }
766
767 for( int retry = 0; retry < maxRetries; ++retry )
768 {
769 sn = snapshot();
770
771 if( !windowReadable( sn, refEntry, count ) )
772 {
773 return false;
774 }
775
776 for( indexT n = 0; n < count; ++n )
777 {
778 dest[n] = m_buffer[physicalIndex( refEntry, n )];
779 }
780
781 if( m_mono.load( std::memory_order_acquire ) == sn.mono )
782 {
783 return true;
784 }
785 }
786
787 return false;
788 }
789
790 /// Copy a logical sequence from a caller-supplied snapshot, validating that the producer has not advanced.
791 bool loadSequence( const snapshotT &sn, ///< [in] the snapshot to validate against
792 indexT refEntry, ///< [in] the entry to start counting from
793 indexT count, ///< [in] the number of entries requested
794 storedT *dest ///< [out] destination storage of size count
795 ) const
796 {
797 if( dest == nullptr || !windowReadable( sn, refEntry, count ) )
798 {
799 return false;
800 }
801
802 for( indexT n = 0; n < count; ++n )
803 {
804 dest[n] = m_buffer[physicalIndex( refEntry, n )];
805 }
806
807 return m_mono.load( std::memory_order_acquire ) == sn.mono;
808 }
809
810 /// Copy the latest logical sequence into caller-owned storage, retrying if the producer advances.
811 bool loadLatestSequence( indexT count, ///< [in] the number of entries requested
812 storedT *dest, ///< [out] destination storage of size count
813 snapshotT &sn, ///< [out] the validated snapshot used for the copy
814 int maxRetries = 3 /**< [in] the maximum number of retries*/
815 ) const
816 {
817 for( int retry = 0; retry < maxRetries; ++retry )
818 {
819 sn = snapshot();
820
821 if( count <= 0 || count > sn.validEntries )
822 {
823 return false;
824 }
825
826 indexT refEntry;
827 if( sn.latest >= count - 1 )
828 {
829 refEntry = sn.latest - ( count - 1 );
830 }
831 else
832 {
833 refEntry = sn.maxEntries + sn.latest - ( count - 1 );
834 }
835
836 if( !windowReadable( sn, refEntry, count ) )
837 {
838 return false;
839 }
840
841 for( indexT n = 0; n < count; ++n )
842 {
843 dest[n] = m_buffer[physicalIndex( refEntry, n )];
844 }
845
846 if( m_mono.load( std::memory_order_acquire ) == sn.mono )
847 {
848 return true;
849 }
850 }
851
852 return false;
853 }
854
855 protected:
856 /// Convert a logical index into a physical slot index.
857 indexT physicalIndex( indexT refEntry, ///< [in] the entry to start counting from
858 indexT idx ///< [in] the index of the entry to access
859 ) const
860 {
861 return static_cast<indexT>( m_indices[m_maxEntries + refEntry + idx] );
862 }
863};
864
865} // namespace sigproc
866} // namespace mx
867
868#endif // sigproc_circularBuffer
CRTP base class for all circular buffers, providing the underlying memory management and accessors.
storedT & at(indexT refEntry, indexT idx)
Get the entry at a given index relative a fixed reference entry.
storedT & operator[](indexT idx)
Get the entry at a given index.
indexT maxEntries()
Get the maximum size of the buffer.
std::vector< storedT > m_buffer
The circular buffer storage.
void maxEntries(indexT maxEnt)
Set the maximum size of the buffer.
indexT size() const
Get the number of entries.
indexT earliest()
Returns the index of the earliest entry.
indexT latest()
Returns the index of the latest entry.
indexT m_maxEntries
The maximum number of entries to allow in the buffer before wrapping.
_derivedT derivedT
The child class.
_storedT storedT
The type stored in the circular buffer.
_indexT indexT
The index type, also used for sizes.
void nextEntry()
Move to the next entry in the circular buffer.
indexT m_latest
Index into m_buff of the latest entry. This is the newest entry in the buffer.
indexT m_nextEntry
Index into m_buffer of the next entry. This is the oldest entry in the buffer.
void nextEntry(const storedT &newEnt)
Add the next entry to the circular buffer.
_storedT storedT
The maximum number of entries to allow in the buffer before wrapping.
void setMaxEntries(indexT maxEnt)
Interface implementation for maxEntries.
const storedT & at(indexT refEntry, indexT idx) const
Interface implementation for entry access, const version.
storedT & at(indexT refEntry, indexT idx)
Interface implementation for entry access.
circularBufferBranch(indexT maxEnt)
Sizing constructor.
_indexT indexT
The index type, also used for sizes.
indexT size() const
Get the number of valid entries.
storedT operator[](indexT idx) const
Get the entry at a given index.
uint64_t mono() const
Returns the publication counter.
bool loadSequence(indexT refEntry, indexT count, storedT *dest, snapshotT &sn, int maxRetries=3) const
Copy a logical sequence into caller-owned storage, retrying if the producer advances.
_indexT indexT
The index type, also used for sizes.
indexT maxEntries()
Get the fixed capacity of the buffer.
std::atomic< uint64_t > m_mono
A monotonic publication counter.
bool loadSequence(const snapshotT &sn, indexT refEntry, indexT count, storedT *dest) const
Copy a logical sequence from a caller-supplied snapshot, validating that the producer has not advance...
indexT latest() const
Returns the index of the latest entry.
std::vector< size_t > m_indices
Vector of indices for fast indexing into m_buffer.
std::atomic< indexT > m_latest
The latest slot published by the producer.
_storedT storedT
The maximum number of entries to allow in the buffer before wrapping.
indexT earliest() const
Returns the index of the earliest entry.
bool loadLatestSequence(indexT count, storedT *dest, snapshotT &sn, int maxRetries=3) const
Copy the latest logical sequence into caller-owned storage, retrying if the producer advances.
std::atomic< indexT > m_nextEntry
The slot that will be written next by the producer.
bool windowReadable(const snapshotT &sn, indexT refEntry, indexT count) const
Determine whether a logical window is readable from the provided snapshot.
snapshotT snapshot() const
Return a snapshot of the current readable region.
std::atomic< indexT > m_validEntries
The number of valid published entries.
void nextEntry(const storedT &newEnt)
Add the next entry to the circular buffer.
storedT at(indexT refEntry, indexT idx) const
Get the entry at a given index relative a fixed reference entry.
indexT physicalIndex(indexT refEntry, indexT idx) const
Convert a logical index into a physical slot index.
void nextEntry()
Move to the next entry using a default constructed value.
void maxEntries(indexT maxEnt)
Set the maximum size of the buffer.
std::vector< storedT > m_buffer
The circular buffer storage.
Circular buffer which wraps with a pre-populated indices array [generally fastest].
_storedT storedT
The maximum number of entries to allow in the buffer before wrapping.
_indexT indexT
The index type, also used for sizes.
const storedT & at(indexT refEntry, indexT idx) const
Interface implementation for entry access, const version.
circularBufferMod(indexT maxEnt)
Sizing constructor.
storedT & at(indexT refEntry, indexT idx)
Interface implementation for entry access.
void setMaxEntries(indexT maxEnt)
Interface implementation for maxEntries.
The mxlib c++ namespace.
Definition mxlib.hpp:37
indexT earliest
Earliest readable slot in the current snapshot.
bool full
Whether the buffer has reached fixed capacity.
uint64_t mono
Publication generation for retry validation.
indexT latest
Latest published slot in the current snapshot.