0.4.1
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
ScrollingBuffer.hpp
Go to the documentation of this file.
1// This file is part of INSTINCT, the INS Toolkit for Integrated
2// Navigation Concepts and Training by the Institute of Navigation of
3// the University of Stuttgart, Germany.
4//
5// This Source Code Form is subject to the terms of the Mozilla Public
6// License, v. 2.0. If a copy of the MPL was not distributed with this
7// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9/// @file ScrollingBuffer.hpp
10/// @brief A buffer which is overwriting itself from the start when full
11/// @author T. Topp (topp@ins.uni-stuttgart.de)
12/// @date 2021-01-09
13
14#pragma once
15
16#include <vector>
17#include <string>
18#include <iostream>
19#include <fmt/ostream.h>
20#include <fmt/ranges.h>
21
22namespace NAV
23{
24
25/// @brief A buffer which is overwriting itself from the start when full
26/// @tparam T Type of data stored in the buffer
27/// @tparam _Padding The padding are empty values at the start of the buffer to prevent overriding the start value in multithreaded applications
28template<class T, size_t _Padding = 0>
30{
31 public:
32 // ###########################################################################################################
33 // Constructors
34 // ###########################################################################################################
35
36 /// @brief Reserves space for the buffer but does not fill the buffer with values
37 /// @param[in] maxSize The maximum size of the scrolling buffer
38 explicit ScrollingBuffer(size_t maxSize = 0)
39 : _infiniteBuffer(maxSize == 0), _maxSize(maxSize + _Padding), _dataStart(_Padding), _dataEnd(_infiniteBuffer ? 0 : _Padding)
40 {
41 _data.reserve(maxSize + _Padding);
42 _data.resize(_Padding);
43 }
44
45 /// @brief Constructs a new container with the contents of the initializer list init.
46 /// @param[in] init initializer list to initialize the elements of the container with
47 ScrollingBuffer(std::initializer_list<T> init)
48 : _infiniteBuffer(init.size() == 0), _maxSize(init.size())
49 {
50 _data.reserve(init.size());
51
52 for (auto& val : init)
53 {
54 push_back(val);
55 }
56 }
57
58 // ###########################################################################################################
59 // Element access
60 // ###########################################################################################################
61
62 /// @brief Returns a reference to the element at specified location pos, with bounds checking.
63 /// If pos is not within the range of the container, an exception of type std::out_of_range is thrown.
64 /// @param[in] pos position of the element to return
65 /// @return Reference to the requested element.
66 T& at(size_t pos)
67 {
68 // Cast the const away and reuse the implementation below
69 return const_cast<T&>(static_cast<const ScrollingBuffer&>(*this).at(pos)); // NOLINT(cppcoreguidelines-pro-type-const-cast)
70 }
71
72 /// @brief Returns a reference to the element at specified location pos, with bounds checking.
73 /// If pos is not within the range of the container, an exception of type std::out_of_range is thrown.
74 /// @param[in] pos position of the element to return
75 /// @return Reference to the requested element.
76 [[nodiscard]] const T& at(size_t pos) const
77 {
78 if (!(pos < size()))
79 {
80 throw std::out_of_range("ScrollingBuffer::at: pos (which is "
81 + std::to_string(pos) + ") >= this->size() (which is " + std::to_string(size()) + ")");
82 }
83
84 // start/end
85 // |
86 // 8 9 3 4 5 6 7
87 // at(0) = 3, at(4) = 7, at(5) = 8
88 if (_dataStart + pos >= _maxSize)
89 {
90 return _data.at(_dataStart + pos - _maxSize);
91 }
92
93 return _data.at(_dataStart + pos);
94 }
95
96 /// @brief Returns a reference to the first element in the container.
97 /// Calling front on an empty container is undefined.
98 /// @return reference to the first element
99 T& front()
100 {
101 // Cast the const away and reuse the implementation below (don't repeat yourself)
102 return const_cast<T&>(static_cast<const ScrollingBuffer&>(*this).front()); // NOLINT(cppcoreguidelines-pro-type-const-cast)
103 }
104
105 /// @brief Returns a reference to the first element in the container.
106 /// Calling front on an empty container is undefined.
107 /// @return Reference to the first element
108 [[nodiscard]] const T& front() const
109 {
110 return _data.at(_dataStart);
111 }
112
113 /// @brief Returns a reference to the last element in the container.
114 /// Calling back on an empty container causes undefined behavior.
115 /// @return Reference to the last element.
116 T& back()
117 {
118 return const_cast<T&>(static_cast<const ScrollingBuffer&>(*this).back()); // NOLINT(cppcoreguidelines-pro-type-const-cast)
119 }
120
121 /// @brief Returns a reference to the last element in the container.
122 /// Calling back on an empty container causes undefined behavior.Reference to the last element.
123 /// @return Reference to the last element.
124 [[nodiscard]] const T& back() const
125 {
126 if (_dataEnd == 0)
127 {
128 return _data.back();
129 }
130
131 return _data.at(_dataEnd - 1);
132 }
133
134 // ###########################################################################################################
135 // Iterators
136 // ###########################################################################################################
137
138 /// @brief Iterator
140 {
141 public:
142 using iterator_category = std::forward_iterator_tag; ///< To categorize the iteration direction
143 using difference_type = std::ptrdiff_t; ///< Signed integer type (usually std::ptrdiff_t)
144 using value_type = T; ///< T
145 using pointer = T*; ///< value_type*
146 using reference = T&; ///< value_type&
147
148 /// @brief Constructor
149 /// @param[in] buffer Mutable reference to the buffer of the iterator
150 /// @param[in] index Iterator index inside the buffer (counted from the start)
151 explicit Iterator(ScrollingBuffer& buffer, size_t index = 0)
152 : buffer(buffer), index(index) {}
153
154 /// @brief Returns a reference to the current element.
155 reference operator*() const { return buffer.at(index); }
156 /// @brief Returns a pointer to the current element.
157 pointer operator->() { return &buffer.at(index); }
158
159 /// @brief Advances the iterator.
161 {
162 if (index == buffer.size()) { return *this; }
163 index++;
164 return *this;
165 }
166 /// @brief Advances the iterator.
168 {
169 Iterator tmp = *this;
170 operator++();
171 return tmp;
172 }
173
174 /// @brief Equality comparison operator
175 /// @param[in] lhs Left-hand side
176 /// @param[in] rhs Right-hand side
177 /// @return True if elements are equal
178 friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return &lhs.buffer == &rhs.buffer && lhs.index == rhs.index; };
179 /// @brief Inequality comparison operator
180 /// @param[in] lhs Left-hand side
181 /// @param[in] rhs Right-hand side
182 /// @return False if elements are equal
183 friend bool operator!=(const Iterator& lhs, const Iterator& rhs) { return !(lhs == rhs); };
184
185 private:
186 ScrollingBuffer& buffer; ///< Reference to the buffer
187 size_t index; ///< Iterator index inside the buffer (counted from the start)
188 };
189
190 /// @brief Returns an iterator to the first element of the vector.
191 ///
192 /// If the buffer is empty, the returned iterator will be equal to end().
193 Iterator begin() { return Iterator(*this, 0); }
194 /// @brief Returns an iterator to the element following the last element of the vector.
195 ///
196 /// This element acts as a placeholder; attempting to access it results in undefined behavior.
197 Iterator end() { return Iterator(*this, size()); }
198
199 /// Const iterator
201 {
202 public:
203 using iterator_category = std::forward_iterator_tag; ///< To categorize the iteration direction
204 using difference_type = std::ptrdiff_t; ///< Signed integer type (usually std::ptrdiff_t)
205 using value_type = T; ///< T
206 using pointer = const T*; ///< value_type*
207 using reference = const T&; ///< value_type&
208
209 /// @brief Constructor
210 /// @param[in] buffer Immutable reference to the buffer of the iterator
211 /// @param[in] index Iterator index inside the buffer (counted from the start)
212 explicit ConstIterator(const ScrollingBuffer& buffer, size_t index = 0)
213 : buffer(buffer), index(index) {}
214
215 /// @brief Returns a reference to the current element.
216 reference operator*() const { return buffer.at(index); }
217 /// @brief Returns a pointer to the current element.
218 pointer operator->() { return &buffer.at(index); }
219
220 /// @brief Advances the iterator.
222 {
223 if (index == buffer.size()) { return *this; }
224 index++;
225 return *this;
226 }
227 /// @brief Advances the iterator.
229 {
230 ConstIterator tmp = *this;
231 operator++();
232 return tmp;
233 }
234
235 /// @brief Equality comparison operator
236 /// @param[in] lhs Left-hand side
237 /// @param[in] rhs Right-hand side
238 /// @return True if elements are equal
239 friend bool operator==(const ConstIterator& lhs, const ConstIterator& rhs) { return &lhs.buffer == &rhs.buffer && lhs.index == rhs.index; };
240 /// @brief Inequality comparison operator
241 /// @param[in] lhs Left-hand side
242 /// @param[in] rhs Right-hand side
243 /// @return False if elements are equal
244 friend bool operator!=(const ConstIterator& lhs, const ConstIterator& rhs) { return !(lhs == rhs); };
245
246 private:
247 const ScrollingBuffer& buffer; ///< Reference to the buffer
248 size_t index; ///< Iterator index inside the buffer (counted from the start)
249 };
250
251 /// @brief Returns an iterator to the first element of the vector.
252 ///
253 /// If the buffer is empty, the returned iterator will be equal to end().
254 [[nodiscard]] ConstIterator begin() const { return ConstIterator(*this, 0); }
255 /// @brief Returns an iterator to the element following the last element of the vector.
256 ///
257 /// This element acts as a placeholder; attempting to access it results in undefined behavior.
258 [[nodiscard]] ConstIterator end() const { return ConstIterator(*this, size()); }
259 /// @brief Returns an iterator to the first element of the vector.
260 ///
261 /// If the buffer is empty, the returned iterator will be equal to end().
262 [[nodiscard]] ConstIterator cbegin() const { return ConstIterator(*this, 0); }
263 /// @brief Returns an iterator to the element following the last element of the vector.
264 ///
265 /// This element acts as a placeholder; attempting to access it results in undefined behavior.
266 [[nodiscard]] ConstIterator cend() const { return ConstIterator(*this, size()); }
267
268 /// Reverse Iterator
270 {
271 public:
272 using iterator_category = std::forward_iterator_tag; ///< To categorize the iteration direction
273 using difference_type = std::ptrdiff_t; ///< Signed integer type (usually std::ptrdiff_t)
274 using value_type = T; ///< T
275 using pointer = T*; ///< value_type*
276 using reference = T&; ///< value_type&
277
278 /// @brief Constructor
279 /// @param[in] buffer Mutable reference to the buffer of the iterator
280 /// @param[in] index Iterator index inside the buffer (counted from the non-reversed start)
282 : buffer(buffer), index(index) {}
283
284 /// @brief Returns a reference to the current element.
285 reference operator*() const { return buffer.at(static_cast<size_t>(index)); }
286 /// @brief Returns a pointer to the current element.
287 pointer operator->() { return &buffer.at(static_cast<size_t>(index)); }
288
289 /// @brief Advances the iterator.
291 {
292 if (index < 0) { return *this; }
293 index--;
294 return *this;
295 }
296 /// @brief Advances the iterator.
298 {
299 ReverseIterator tmp = *this;
300 operator++();
301 return tmp;
302 }
303
304 /// @brief Equality comparison operator
305 /// @param[in] lhs Left-hand side
306 /// @param[in] rhs Right-hand side
307 /// @return True if elements are equal
308 friend bool operator==(const ReverseIterator& lhs, const ReverseIterator& rhs) { return &lhs.buffer == &rhs.buffer && lhs.index == rhs.index; };
309 /// @brief Inequality comparison operator
310 /// @param[in] lhs Left-hand side
311 /// @param[in] rhs Right-hand side
312 /// @return False if elements are equal
313 friend bool operator!=(const ReverseIterator& lhs, const ReverseIterator& rhs) { return !(lhs == rhs); };
314
315 private:
316 ScrollingBuffer& buffer; ///< Reference to the buffer
317 int64_t index; ///< Iterator index inside the buffer (counted from the non-reversed start)
318 };
319
320 /// @brief Returns a reverse iterator to the first element of the reversed vector.
321 ///
322 /// It corresponds to the last element of the non-reversed vector.
323 /// If the vector is empty, the returned iterator is equal to rend().
324 ReverseIterator rbegin() { return ReverseIterator(*this, static_cast<int64_t>(size()) - 1); }
325 /// @brief Returns a reverse iterator to the element following the last element of the reversed vector.
326 ///
327 /// It corresponds to the element preceding the first element of the non-reversed vector.
328 /// This element acts as a placeholder, attempting to access it results in undefined behavior.
329 ReverseIterator rend() { return ReverseIterator(*this, -1); }
330
331 /// Const reverse iterator
333 {
334 public:
335 using iterator_category = std::forward_iterator_tag; ///< To categorize the iteration direction
336 using difference_type = std::ptrdiff_t; ///< Signed integer type (usually std::ptrdiff_t)
337 using value_type = T; ///< T
338 using pointer = const T*; ///< value_type*
339 using reference = const T&; ///< value_type&
340
341 /// @brief Constructor
342 /// @param[in] buffer Immutable reference to the buffer of the iterator
343 /// @param[in] index Iterator index inside the buffer (counted from the non-reversed start)
344 explicit ConstReverseIterator(const ScrollingBuffer& buffer, int64_t index = 0)
345 : buffer(buffer), index(index) {}
346
347 /// @brief Returns a reference to the current element.
348 reference operator*() const { return buffer.at(static_cast<size_t>(index)); }
349 /// @brief Returns a pointer to the current element.
350 pointer operator->() { return &buffer.at(static_cast<size_t>(index)); }
351
352 /// @brief Advances the iterator.
354 {
355 if (index < 0) { return *this; }
356 index--;
357 return *this;
358 }
359 /// @brief Advances the iterator.
361 {
362 ConstReverseIterator tmp = *this;
363 operator++();
364 return tmp;
365 }
366
367 /// @brief Equality comparison operator
368 /// @param[in] lhs Left-hand side
369 /// @param[in] rhs Right-hand side
370 /// @return True if elements are equal
371 friend bool operator==(const ConstReverseIterator& lhs, const ConstReverseIterator& rhs) { return &lhs.buffer == &rhs.buffer && lhs.index == rhs.index; };
372 /// @brief Inequality comparison operator
373 /// @param[in] lhs Left-hand side
374 /// @param[in] rhs Right-hand side
375 /// @return False if elements are equal
376 friend bool operator!=(const ConstReverseIterator& lhs, const ConstReverseIterator& rhs) { return !(lhs == rhs); };
377
378 private:
379 const ScrollingBuffer& buffer; ///< Reference to the buffer
380 int64_t index; ///< Iterator index inside the buffer (counted from the non-reversed start)
381 };
382
383 /// @brief Returns a reverse iterator to the first element of the reversed vector.
384 ///
385 /// It corresponds to the last element of the non-reversed vector.
386 /// If the vector is empty, the returned iterator is equal to rend().
387 [[nodiscard]] ConstReverseIterator rbegin() const { return ConstReverseIterator(*this, static_cast<int64_t>(size()) - 1); }
388 /// @brief Returns a reverse iterator to the element following the last element of the reversed vector.
389 ///
390 /// It corresponds to the element preceding the first element of the non-reversed vector.
391 /// This element acts as a placeholder, attempting to access it results in undefined behavior.
392 [[nodiscard]] ConstReverseIterator rend() const { return ConstReverseIterator(*this, -1); }
393 /// @brief Returns a reverse iterator to the first element of the reversed vector.
394 ///
395 /// It corresponds to the last element of the non-reversed vector.
396 /// If the vector is empty, the returned iterator is equal to rend().
397 [[nodiscard]] ConstReverseIterator crbegin() const { return ConstReverseIterator(*this, static_cast<int64_t>(size()) - 1); }
398 /// @brief Returns a reverse iterator to the element following the last element of the reversed vector.
399 ///
400 /// It corresponds to the element preceding the first element of the non-reversed vector.
401 /// This element acts as a placeholder, attempting to access it results in undefined behavior.
402 [[nodiscard]] ConstReverseIterator crend() const { return ConstReverseIterator(*this, -1); }
403
404 // ###########################################################################################################
405 // Capacity
406 // ###########################################################################################################
407
408 /// @brief Checks if the container has no elements
409 [[nodiscard]] bool empty() const
410 {
411 return size() == 0;
412 }
413
414 /// @brief Checks if the container is full (never full when infinite buffer)
415 [[nodiscard]] bool full() const
416 {
417 return !_infiniteBuffer && _maxSize - _Padding == size();
418 }
419
420 /// @brief Returns the number of elements in the container
421 [[nodiscard]] size_t size() const
422 {
423 if (_dataStart == _Padding && _dataEnd == _Padding) // Buffer empty or full and not scrolled
424 {
425 return _data.size() - _Padding;
426 }
427 if (_dataStart < _dataEnd) // not scrolled buffer
428 {
429 return _dataEnd - _dataStart;
430 }
431
432 // scrolled buffer
433 return _maxSize - (_dataStart - _dataEnd);
434 }
435
436 /// @brief Increase the capacity of the vector to a value that's greater or equal to new_cap.
437 /// @param new_cap New capacity of the vector, in number of elements
438 void reserve(size_t new_cap)
439 {
440 _data.reserve(new_cap);
441 }
442
443 /// @brief Returns the number of elements that can be held in currently allocated storage
444 [[nodiscard]] size_t capacity() const
445 {
446 return _infiniteBuffer ? 0 : (_maxSize - _Padding);
447 }
448
449 // ###########################################################################################################
450 // Modifiers
451 // ###########################################################################################################
452
453 /// @brief Erases all elements from the container. After this call, size() returns zero.
454 void clear()
455 {
456 _data.clear();
457 _dataStart = _Padding;
458 _dataEnd = _infiniteBuffer ? 0 : _Padding;
459
460 for (size_t i = 0; i < _Padding; i++)
461 {
462 _data.push_back({});
463 }
464 }
465
466 /// @brief Appends the given element value to the end of the container.
467 /// @param[in] value the value of the element to append
468 void push_back(const T& value)
469 {
470 if (_infiniteBuffer) // The buffer should grow when adding new values
471 {
472 _data.push_back(value);
473 _maxSize = _data.size();
474 }
475 else if (_data.size() < _maxSize) // The real buffer is smaller than the allowed buffer size
476 {
477 _data.push_back(value);
478 _dataEnd = (_dataEnd + 1) % _maxSize;
479 }
480 else // The real buffer as large as or bigger than the allowed buffer size, so we have to scroll the buffer
481 {
482 _data.at(_dataEnd) = value;
483 if (size() >= capacity())
484 {
485 // "5, 6, _, _, 2, 3, 4"
486 // "5, 6, 7, _, 2, 3, 4"
488 }
489 _dataEnd = (_dataEnd + 1) % _maxSize;
490 }
491 }
492
493 /// @brief Removes the first element of the container
495 {
496 if (empty())
497 {
498 return;
499 }
500 if (size() == 1)
501 {
502 clear();
503 return;
504 }
505
506 if (_infiniteBuffer)
507 {
508 _data.erase(_data.begin());
509 _maxSize = _data.size();
510 }
511 else
512 {
513 // se e s e s s e se
514 // 5, 6, 2, 3, 4 // 5, 6, _, _, 2, 3, 4 // 5, 6, X, X, 2, 3, 4 // X, 6, 7, 8, 9, 10, X // 5, 6, 7, 4
515 // e s e s e s s e s e
516 // 5, 6, _, 3, 4 // 5, 6, _, _, _, 3, 4 // 5, 6, _, X, X, 3, 4 // X, X, 7, 8, 9, 10, _ // 5, 6, 7, _
517
518 front() = {}; // Set to default in case shared_ptr is stored
520 }
521 }
522
523 /// @brief Removes the last element of the container
524 void pop_back()
525 {
526 if (empty())
527 {
528 return;
529 }
530 if (size() == 1)
531 {
532 clear();
533 return;
534 }
535
536 if (_infiniteBuffer)
537 {
538 _data.pop_back();
539 _maxSize = _data.size();
540 }
541 else
542 {
543 back() = {}; // Set to default in case shared_ptr is stored
544 _dataEnd = _dataEnd == 0 ? _maxSize - 1 : (_dataEnd - 1);
545 }
546 }
547
548 /// @brief Resizes the buffer to the specified size
549 /// @param[in] targetSize The new buffer size (0 for infinite buffer)
550 void resize(size_t targetSize)
551 {
552 if (targetSize == 0) // Buffer should grow indefinitely when adding new values
553 {
554 _infiniteBuffer = true;
555
556 if (isScrolled()) // Buffer is scrolled and needs to be sorted in order of insertion
557 {
558 // se e s e s s e
559 // 5, 6, 2, 3, 4 // 5, 6, _, _, 2, 3, 4 // 5, 6, X, X, 2, 3, 4 // X, 6, 7, 8, 9, 10, X
560 // 2, 3, 4, 5, 6 // 2, 3, 4, 5, 6, _, _ // X, X, 2, 3, 4, 5, 6 // X, X, 6, 7, 8, 9, 10
561 std::vector<T> to_vector;
562
563 if (_dataEnd > _dataStart)
564 {
565 std::copy(std::next(_data.begin(), static_cast<int64_t>(_dataEnd)), _data.end(),
566 std::back_inserter(to_vector));
568 }
569
570 std::copy(std::next(_data.begin(), std::max(static_cast<int>(_dataStart - _Padding), 0)),
571 std::next(_data.begin(), static_cast<int64_t>(std::max(_dataEnd, _maxSize))),
572 std::back_inserter(to_vector));
573
574 if (int64_t elementsFront = std::min(static_cast<int64_t>(_dataEnd), static_cast<int64_t>(_dataStart - _Padding));
575 elementsFront > 0)
576 {
577 std::copy(_data.begin(), std::next(_data.begin(), elementsFront),
578 std::back_inserter(to_vector));
579 }
580 _data.swap(to_vector);
581
582 _maxSize = _data.size();
583
584 _dataStart = _Padding;
585 _dataEnd = 0;
586 }
587 }
588 else // Buffer should have scrolling behaviour when adding new values
589 {
590 _infiniteBuffer = false;
591
592 if (_maxSize - _Padding > targetSize) // We make the buffer smaller
593 {
594 if (!isScrolled()) // Buffer is not scrolled, so shrinking removes the values from the front of the buffer
595 {
596 size_t elementsToDelete = _maxSize - _Padding - targetSize;
597
598 if (size_t emptyAtTheBack = _maxSize - _dataEnd;
599 size_t emptyAtTheBackToDelete = std::min(emptyAtTheBack, elementsToDelete))
600 {
601 // 1, 2, 3, _, _, // X, X, 1, 2, 3, _, _,
602 // 1, 2, 3, _, // X, X, 1, 2, 3, _,
603 _maxSize -= emptyAtTheBackToDelete;
604 _dataEnd %= _maxSize; // NOLINT(clang-analyzer-core.DivideZero) // this lint is wrong, even when wrapped by `if(_maxSize != 0)` appearing
605 elementsToDelete -= emptyAtTheBackToDelete;
606 // 1, 2, 3, // X, X, 1, 2, 3,
607 }
608
609 // 1, 2, 3,
610 // 2, 3,
611 if (elementsToDelete)
612 {
613 _data.erase(std::next(_data.begin(), static_cast<int64_t>(_dataStart)),
614 std::next(_data.begin(), static_cast<int64_t>(_dataStart + elementsToDelete)));
615 _maxSize -= elementsToDelete;
616 }
617 }
618 else // Buffer is scrolled, so the correct values have to be erased from the buffer when shrinking
619 {
620 // 5, 6, _, _, 2, 3, 4, // 5, 6, _, _, X, X, 2, 3, 4,
621 // 6, // 6, X, X
622
623 // 6, 7, 3, 4, 5, // 5, 6, X, X, 2, 3, 4
624 // 6, 7, 4, 5, // 5, 6, X, X, 3, 4
625
626 // X, 6, 7, 8, 9, 10, X
627 // X, 9, 10, X
628
629 size_t elementsToDelete = _maxSize - _Padding - targetSize;
630
631 if (size_t emptyInBetween = static_cast<size_t>(std::max(static_cast<int>(_dataStart - _Padding - _dataEnd), 0));
632 size_t emptyInBetweenToDelete = std::min(emptyInBetween, elementsToDelete))
633 {
634 // 5, 6, _, _, 2, 3, 4, // 5, 6, _, _, X, X, 2, 3, 4,
635 _data.erase(std::next(_data.begin(), static_cast<int64_t>(_dataEnd)),
636 std::next(_data.begin(), static_cast<int64_t>(_dataEnd + emptyInBetweenToDelete)));
637 // 5, 6, _, 2, 3, 4, // 5, 6, _, X, X, 2, 3, 4,
638 _dataStart -= emptyInBetweenToDelete;
639 _maxSize -= emptyInBetweenToDelete;
640 elementsToDelete -= emptyInBetweenToDelete;
641 }
642
643 // s e
644 // X , 6, 7 , 8 , 9, 10, X
645 // X(8), 9, 10, X(7)
646 if (size_t paddingAtTheEnd = static_cast<size_t>(std::max(static_cast<int>(_Padding - _dataStart), 0));
647 size_t paddingAtTheEndToDelete = std::min(paddingAtTheEnd, elementsToDelete))
648 {
649 _data.erase(std::next(_data.begin(), static_cast<int64_t>(_dataEnd)),
650 std::next(_data.begin(), static_cast<int64_t>(_dataEnd + paddingAtTheEndToDelete)));
651 _maxSize -= paddingAtTheEndToDelete;
652 _dataStart += paddingAtTheEndToDelete;
654 elementsToDelete -= paddingAtTheEndToDelete;
655 }
656 // e s
657 // X, X, 6, 7 , 8 , 9, 10
658 // X, X, 7 , 8 , 9, 10
659
660 if (size_t elementsAtTheBack = _maxSize - _dataStart - (_dataEnd > _dataStart ? _maxSize - _dataEnd : 0);
661 size_t elementsAtTheBackToDelete = std::min(elementsAtTheBack, elementsToDelete))
662 {
663 // 5, 6, 2, 3, 4, // 5, 6, X, X, 2, 3, 4,
664 _data.erase(std::next(_data.begin(), static_cast<int64_t>(_dataStart - _Padding)),
665 std::next(_data.begin(), static_cast<int64_t>(_dataStart - _Padding + elementsAtTheBackToDelete)));
666 // 5, 6, 4, // 5, 6, X, X, 4,
667 // 5, 6, // 5, 6, X, X,
668 _maxSize -= elementsAtTheBackToDelete;
670 if (_dataEnd > _maxSize)
671 {
672 _dataEnd -= elementsToDelete;
673 }
675 elementsToDelete -= elementsAtTheBackToDelete;
676 }
677 if (elementsToDelete)
678 {
679 // 5, 6, // 5, 6, X, X,
680 _data.erase(std::next(_data.begin(), static_cast<int64_t>(_dataStart)),
681 std::next(_data.begin(), static_cast<int64_t>(std::min(_dataStart + elementsToDelete, _maxSize - _Padding))));
682 // 6, // 6, X, X
683 _maxSize -= elementsToDelete;
684 if (_dataEnd >= elementsToDelete)
685 {
686 _dataEnd -= elementsToDelete;
687 }
688 }
689 }
690 }
691 else if (_maxSize - _Padding < targetSize) // We make the buffer bigger
692 {
693 // 1, 2, 3, _, _, // X, X, 0, 1, 2, 3, _, _
694 // 1, 2, 3, _, _, _, _, // X, X, 0, 1, 2, 3, _, _, _, _
695 if (!isScrolled()) // Buffer not scrolled, so we can simply reserve more space
696 {
697 _maxSize = targetSize + _Padding;
698 _data.reserve(_maxSize);
699 if (_dataEnd == 0) { _dataEnd = _data.size(); }
700 }
701 // se e s s e
702 // 6, 7, 3, 4, 5, // 5, 6, X, X, 2, 3, 4 // X, 6, 7, 8, 9, 10, X
703 // 6, 7, _, 3, 4, 5, // 5, 6, _, X, X, 2, 3, 4 // X, 6, 7, 8, 9, 10, _, _, X
704 else // (_dataStart != 0) // Buffer scrolled, so we need to copy the values to the correct positions
705 {
706 if (_dataEnd == 0) // Buffer actually not scrolled
707 {
708 _maxSize = targetSize + _Padding;
709 _data.reserve(_maxSize);
710 _dataEnd = _data.size();
711 return;
712 }
713
714 _data.resize(targetSize + _Padding);
715
716 auto copy_backward = [](auto first, auto last, auto d_last) { // Needed, as MacOS with C++23 does not support std::copy_backward
717 while (first != last)
718 {
719 *(--d_last) = *(--last);
720 }
721 };
722
723 copy_backward(std::next(_data.begin(), static_cast<int64_t>(_dataEnd)),
724 std::next(_data.begin(), static_cast<int64_t>(_maxSize)),
725 std::next(_data.begin(), static_cast<int64_t>(targetSize + _Padding)));
726
727 auto diff = targetSize + _Padding - _maxSize;
728 if (_dataStart >= _dataEnd)
729 {
730 _dataStart += diff;
731 }
732
733 _maxSize = targetSize + _Padding;
734 }
735 }
736 }
737 }
738
739 // ###########################################################################################################
740 // Other
741 // ###########################################################################################################
742
743 /// @brief Returns the largest value in the buffer
744 [[nodiscard]] T max() const
745 {
746 T currentMax = front();
747 for (size_t i = 0; i < _data.size(); i++)
748 {
749 if (i >= _dataStart || i < _dataEnd)
750 {
751 currentMax = std::max(currentMax, _data.at(i));
752 }
753 }
754 return currentMax;
755 }
756
757 /// @brief Returns the smallest value in the buffer
758 [[nodiscard]] T min() const
759 {
760 T currentMin = front();
761 for (size_t i = 0; i < _data.size(); i++)
762 {
763 if (i >= _dataStart || i < _dataEnd)
764 {
765 currentMin = std::min(currentMin, _data.at(i));
766 }
767 }
768 return currentMin;
769 }
770
771 /// @brief Returns the data index of the first element in the buffer
772 [[nodiscard]] int offset() const
773 {
774 return static_cast<int>(_dataStart);
775 }
776
777 /// @brief Returns a pointer to the raw data array (not in scrolled order)
778 [[nodiscard]] const T* data() const
779 {
780 return _data.data();
781 }
782
783 /// @brief Returns a pointer to the raw data array (not in scrolled order)
784 [[nodiscard]] T* data()
785 {
786 return _data.data();
787 }
788
789 /// @brief Returns whether the buffer is infinite and growing
790 [[nodiscard]] bool isInfiniteBuffer() const
791 {
792 return _infiniteBuffer;
793 }
794
795 /// @brief Converts the raw buffer to a string
796 [[nodiscard]] std::string getRawString() const
797 {
798 // Scrolled
799 // e s
800 // 5, 6, _, _, X, 2, 3, 4
801
802 // se
803 // 5, 6, 2, 3, 4
804
805 // s e
806 // _, 6, _, _, _
807
808 // Not scrolled
809 // s e
810 // X, X, 0, 1, 2, 3, _, _
811
812 // s e
813 // X, 6, 7, 8, 9, 10, _, _, X
814
815 // s e
816 // 6, X, X,
817
818 // s e
819 // 6, _, X, X,
820
821 // se
822 // X, X, _, _, _,
823
824 std::string out;
825
826 for (int i = 0; static_cast<size_t>(i) < _maxSize; i++) // X, 6, 7, 8, 9, 10, _, _, X
827 {
828 if ((i >= static_cast<int>(_dataStart - _Padding) && static_cast<size_t>(i) < _dataStart)
829 || (static_cast<size_t>(i) >= _dataStart + _maxSize - _Padding))
830 {
831 out += "X"; // padding
832 }
833 else if (bool scrolled = isScrolled();
834 static_cast<size_t>(i) >= _data.size()
835 || (scrolled && static_cast<size_t>(i) >= _dataEnd && (static_cast<int>(_dataStart - _Padding) < 0 || i < static_cast<int>(_dataStart - _Padding)))
836 || (scrolled && _dataStart < _dataEnd && (static_cast<size_t>(i) < _dataStart || static_cast<size_t>(i) >= _dataEnd))
837 || (_data.size() > 1 && !scrolled && static_cast<size_t>(i) >= _dataEnd))
838 {
839 out += "_"; // empty
840 }
841 else
842 {
843 out += std::to_string(_data.at(static_cast<size_t>(i)));
844 }
845 if (static_cast<size_t>(i) != _maxSize - 1)
846 {
847 out += ", ";
848 }
849 }
850 return out;
851 }
852
853 /// @brief Prints the buffer to the output stream
854 /// @param[in, out] os The output stream to print to
855 /// @param[in] buffer The buffer to print
856 /// @return The output stream given as parameter
857 friend std::ostream& operator<<(std::ostream& os, const ScrollingBuffer<T, _Padding>& buffer)
858 {
859 os << fmt::format("{}", fmt::join(buffer, ", "));
860 return os;
861 }
862
863 private:
864 /// A Flag whether the buffer is currently growing every time when values are inserted or if it is overwriting itself from the start when full
865 bool _infiniteBuffer{ false };
866 /// The maximum amount of objects to store in the buffer before overwriting itself when full
867 /// When _infiniteBuffer == true, then this corresponds to m_data.size()
868 size_t _maxSize;
869 /// The index of the first element in the scrolling buffer (0 if the buffer is empty)
870 size_t _dataStart{ 0 };
871 /// The index one after the last element (0 if the buffer is empty)
872 size_t _dataEnd{ 0 };
873 /// The data storage object
874 std::vector<T> _data;
875
876 /// @brief Checks if the buffer is scrolled
877 [[nodiscard]] bool isScrolled() const
878 {
879 if (_data.size() == 1) { return false; }
880
881 // e s
882 // 5, 6, _, _, X, 2, 3, 4
883
884 // se
885 if (_dataEnd == 0 && !empty()) // 1, 2, 3, 4
886 {
887 return true;
888 }
889
890 return _dataEnd < _dataStart
891 || (_dataStart != _Padding);
892
893 // se // se
894 // 5, 6, 2, 3, 4 // X, X, _, _, _
895
896 // s e
897 // X, 0, 1, 2, 3, _, _, X // Scrolled
898 // s e
899 // X, X, 0, 1, 2, 3, _, _ // Not scrolled
900 }
901};
902
903} // namespace NAV
904
905#ifndef DOXYGEN_IGNORE
906
907template<class T, size_t _Padding>
908struct fmt::formatter<NAV::ScrollingBuffer<T, _Padding>> : ostream_formatter
909{};
910
911#endif
reference operator*() const
Returns a reference to the current element.
ConstIterator(const ScrollingBuffer &buffer, size_t index=0)
Constructor.
std::ptrdiff_t difference_type
Signed integer type (usually std::ptrdiff_t)
const ScrollingBuffer & buffer
Reference to the buffer.
pointer operator->()
Returns a pointer to the current element.
ConstIterator operator++(int)
Advances the iterator.
size_t index
Iterator index inside the buffer (counted from the start)
friend bool operator!=(const ConstIterator &lhs, const ConstIterator &rhs)
Inequality comparison operator.
std::forward_iterator_tag iterator_category
To categorize the iteration direction.
friend bool operator==(const ConstIterator &lhs, const ConstIterator &rhs)
Equality comparison operator.
const ConstIterator & operator++()
Advances the iterator.
const ConstReverseIterator & operator++()
Advances the iterator.
int64_t index
Iterator index inside the buffer (counted from the non-reversed start)
friend bool operator==(const ConstReverseIterator &lhs, const ConstReverseIterator &rhs)
Equality comparison operator.
std::ptrdiff_t difference_type
Signed integer type (usually std::ptrdiff_t)
friend bool operator!=(const ConstReverseIterator &lhs, const ConstReverseIterator &rhs)
Inequality comparison operator.
std::forward_iterator_tag iterator_category
To categorize the iteration direction.
const ScrollingBuffer & buffer
Reference to the buffer.
reference operator*() const
Returns a reference to the current element.
pointer operator->()
Returns a pointer to the current element.
ConstReverseIterator operator++(int)
Advances the iterator.
ConstReverseIterator(const ScrollingBuffer &buffer, int64_t index=0)
Constructor.
Iterator(ScrollingBuffer &buffer, size_t index=0)
Constructor.
Iterator operator++(int)
Advances the iterator.
reference operator*() const
Returns a reference to the current element.
friend bool operator==(const Iterator &lhs, const Iterator &rhs)
Equality comparison operator.
ScrollingBuffer & buffer
Reference to the buffer.
size_t index
Iterator index inside the buffer (counted from the start)
friend bool operator!=(const Iterator &lhs, const Iterator &rhs)
Inequality comparison operator.
std::forward_iterator_tag iterator_category
To categorize the iteration direction.
Iterator & operator++()
Advances the iterator.
pointer operator->()
Returns a pointer to the current element.
std::ptrdiff_t difference_type
Signed integer type (usually std::ptrdiff_t)
std::forward_iterator_tag iterator_category
To categorize the iteration direction.
friend bool operator!=(const ReverseIterator &lhs, const ReverseIterator &rhs)
Inequality comparison operator.
ReverseIterator operator++(int)
Advances the iterator.
ReverseIterator(ScrollingBuffer &buffer, int64_t index=0)
Constructor.
ScrollingBuffer & buffer
Reference to the buffer.
friend bool operator==(const ReverseIterator &lhs, const ReverseIterator &rhs)
Equality comparison operator.
reference operator*() const
Returns a reference to the current element.
std::ptrdiff_t difference_type
Signed integer type (usually std::ptrdiff_t)
pointer operator->()
Returns a pointer to the current element.
int64_t index
Iterator index inside the buffer (counted from the non-reversed start)
ReverseIterator & operator++()
Advances the iterator.
T * data()
Returns a pointer to the raw data array (not in scrolled order)
bool isInfiniteBuffer() const
Returns whether the buffer is infinite and growing.
T & at(size_t pos)
Returns a reference to the element at specified location pos, with bounds checking....
ConstIterator end() const
Returns an iterator to the element following the last element of the vector.
const T * data() const
Returns a pointer to the raw data array (not in scrolled order)
bool empty() const
Checks if the container has no elements.
void pop_back()
Removes the last element of the container.
void reserve(size_t new_cap)
Increase the capacity of the vector to a value that's greater or equal to new_cap.
ConstReverseIterator crbegin() const
Returns a reverse iterator to the first element of the reversed vector.
ConstReverseIterator rbegin() const
Returns a reverse iterator to the first element of the reversed vector.
Iterator end()
Returns an iterator to the element following the last element of the vector.
int offset() const
Returns the data index of the first element in the buffer.
ReverseIterator rbegin()
Returns a reverse iterator to the first element of the reversed vector.
void push_back(const T &value)
Appends the given element value to the end of the container.
std::vector< T > _data
The data storage object.
const T & front() const
Returns a reference to the first element in the container. Calling front on an empty container is und...
const T & back() const
Returns a reference to the last element in the container. Calling back on an empty container causes u...
size_t _dataEnd
The index one after the last element (0 if the buffer is empty)
bool full() const
Checks if the container is full (never full when infinite buffer)
ConstIterator cend() const
Returns an iterator to the element following the last element of the vector.
ConstReverseIterator rend() const
Returns a reverse iterator to the element following the last element of the reversed vector.
ScrollingBuffer(size_t maxSize=0)
Reserves space for the buffer but does not fill the buffer with values.
size_t size() const
Returns the number of elements in the container.
bool isScrolled() const
Checks if the buffer is scrolled.
ReverseIterator rend()
Returns a reverse iterator to the element following the last element of the reversed vector.
bool _infiniteBuffer
A Flag whether the buffer is currently growing every time when values are inserted or if it is overwr...
size_t capacity() const
Returns the number of elements that can be held in currently allocated storage.
friend std::ostream & operator<<(std::ostream &os, const ScrollingBuffer< T, _Padding > &buffer)
Prints the buffer to the output stream.
ConstReverseIterator crend() const
Returns a reverse iterator to the element following the last element of the reversed vector.
const T & at(size_t pos) const
Returns a reference to the element at specified location pos, with bounds checking....
T min() const
Returns the smallest value in the buffer.
void pop_front()
Removes the first element of the container.
std::string getRawString() const
Converts the raw buffer to a string.
ScrollingBuffer(std::initializer_list< T > init)
Constructs a new container with the contents of the initializer list init.
void clear()
Erases all elements from the container. After this call, size() returns zero.
T & back()
Returns a reference to the last element in the container. Calling back on an empty container causes u...
size_t _dataStart
The index of the first element in the scrolling buffer (0 if the buffer is empty)
void resize(size_t targetSize)
Resizes the buffer to the specified size.
ConstIterator cbegin() const
Returns an iterator to the first element of the vector.
ConstIterator begin() const
Returns an iterator to the first element of the vector.
T max() const
Returns the largest value in the buffer.
Iterator begin()
Returns an iterator to the first element of the vector.
T & front()
Returns a reference to the first element in the container. Calling front on an empty container is und...