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