#pragma once /* #include "log.hpp" */ #include "util.hpp" #include #include #include #include namespace gz { /** * @brief A fixed size buffer that can store data continuously without needing to move data or reallocate memory * @details * A buffer with size n will store the n newest elements that were inserted. If the number of inserted elements is < n, the buffers size will also be < n. * * The buffer can be @ref RingBuffer::resize() "resized", which potentially leads to a reallocation of memory. * * @subsection ringbuffer_iteration Iteration * The RingBuffer has its own bidirectional iterator. The normal direction is from newest to oldest element. * @code * RingBuffer rb(4); * for (int i = 0; i < 7; i++) { rb.push_back(i); } * for (auto it = rb.begin(); it != rb.end(); it++) { * std::cout << *it << " "; * } * @endcode * will produce @code 6 5 4 3 @endcode * * If the buffer is empty, all iterators point to the separator element (end() == rend() == begin() == rbegin()). * * @subsection ringbuffer_technical_details Technical Details * A buffer with size n will store its objects in a std::vector with size n+1, where the additional element serves as a separator between the newest and the oldest element. * It is technically the real oldest element and could be accessed using end() or rend(), which will always point to this element (meaning end() == rend()), giving you a n+1 sized buffer. * However, this element will be default initialized until n+1 elements have been inserted into the buffer, so it is not advisable to use this extra element. * * The RingBuffer satisfies concept std::ranges::bidirectional_range and RingBuffer::Iterator satisfies std::bidirectional_iterator * * The writeIndex will always point to the element that was last written. * */ template class RingBuffer { public: RingBuffer(size_t size=10); /** * @brief Bidirectonal iterator for the RingBuffer * @todo make a const and non-const version, since const here is all over the place */ struct Iterator { public: Iterator(const RingBuffer& b, size_t index) : b(b), ptr(const_cast(&b.buffer.at(index))) {} Iterator(const Iterator& other) : b(other.b), ptr(other.ptr) {} // Needed for std::input_iterator using value_type = T; T& operator*() const { return *ptr; } Iterator& operator=(const Iterator& other) { b = other.b; ptr = other.ptr; return this; } Iterator& operator++() { if (ptr == &*b.buffer.begin()) { ptr = const_cast(&*b.buffer.rbegin()); } else { ptr--; } return *this; } Iterator operator++(int) { auto copy = *this; if (ptr == &*b.buffer.begin()) { ptr = const_cast(&*b.buffer.rbegin()); } else { ptr--; } return copy; } friend int operator-(Iterator lhs, Iterator rhs) { return lhs.getCurrentIndex() - rhs.getCurrentIndex(); } // Needed for std::forward_iterator /// @warning Default constructor has to be defined for std::forward_iterator but can not be used, since reference to RingBuffer can not be initialized! Your compiler should (hopefully) not allow it. Iterator() : ptr(nullptr) {}; bool operator==(const Iterator& other) const { return this->ptr == other.ptr; } // Needed for std::bidirectional_iterator Iterator& operator--() { if (ptr == &*b.buffer.rbegin()) { ptr = const_cast(&*b.buffer.begin()); } else { ptr++; } return *this; } Iterator operator--(int) { auto copy = *this; if (ptr == &*b.buffer.rbegin()) { ptr = const_cast(&*b.buffer.begin()); } else { ptr++; } return copy; } // Not needed ) /* friend Iterator operator+(Iterator lhs, int i) { */ /* return Iterator(lhs.b, &lhs.b.buffer[lhs.getValidIndex(lhs.getCurrentIndex() + i)]); */ /* } */ /* friend Iterator operator-(Iterator lhs, int i) { */ /* return Iterator(lhs.b, &lhs.b.buffer[lhs.getValidIndex(lhs.getCurrentIndex() - i)]); */ /* } */ /* friend Iterator operator+(int i, Iterator rhs) { */ /* return Iterator(rhs.b, &rhs.b.buffer[rhs.getValidIndex(rhs.getCurrentIndex() + i)]); */ /* } */ /* Iterator& operator+=(int i) { */ /* ptr = &b.buffer[getValidIndex(getCurrentIndex() + i)]; */ /* return this; */ /* } */ /* Iterator& operator-=(int i) { */ /* ptr = &b.buffer[getValidIndex(getCurrentIndex() - i)]; */ /* return this; */ /* } */ /* bool operator!=(const Iterator& other) const { */ /* return this->ptr != other.ptr; */ /* } */ /// Get the index of the vector that ptr points to std::string to_string() const { return "Element: " + std::to_string(*ptr) + ", Index: " + std::to_string(getCurrentIndex()) + ", Pointer: " + std::to_string(reinterpret_cast(ptr)); } private: size_t getCurrentIndex() const { return reinterpret_cast(ptr - &*b.buffer.begin()); } T* ptr; const RingBuffer& b; }; void push_back(T& t); void push_back(T&& t) { push_back(t); }; void emplace_back(T&& t); /** * @brief Return an iterator pointing to the newest object */ const Iterator cbegin() const { return Iterator(*this, writeIndex); } /** * @brief Return an iterator poiting to the element preceeding the oldest element */ const Iterator cend() const { return Iterator(*this, util::getIncrementedIndex(writeIndex, buffer.size())); } /** * @brief Return an iterator pointing to the oldest object */ const Iterator crbegin() const { return Iterator(*this, util::getIncrementedIndex(writeIndex + 1, buffer.size())); } /** * @brief Return an iterator pointing to the element following the newest element */ const Iterator crend() const { return Iterator(*this, util::getIncrementedIndex(writeIndex, buffer.size())); } const Iterator begin() { return Iterator(*this, writeIndex); } const Iterator end() { return Iterator(*this, util::getIncrementedIndex(writeIndex, buffer.size())); } const Iterator rbegin() { return Iterator(*this, util::getIncrementedIndex(writeIndex + 1, buffer.size())); } const Iterator rend() { return Iterator(*this, util::getIncrementedIndex(writeIndex, buffer.size())); } /** * @brief Resize the buffer to contain max size elements * @details * If the current size is greater than size, the buffer is reduced to the newest elements that fit size. \n * If the current size is smaller than size, the buffer size remains but it will be able to grow during element insertion until size is reached. */ void resize(const size_t size); size_t capacity() const { return vectorCapacity - 1; } size_t size() const { return buffer.size() - 1; } private: size_t writeIndex; ///< Points to the element that was last written std::vector buffer; size_t vectorCapacity; }; template RingBuffer::RingBuffer(size_t capacity) { buffer.reserve(capacity + 1); buffer.resize(1); vectorCapacity = capacity + 1; writeIndex = 0; } template void RingBuffer::resize(size_t size) { if (size + 1 > buffer.capacity()) { // when growing // point writeIndex to separator util::incrementIndex(writeIndex, buffer.size()); // separator element becomes first element -> vector grows while inserting elements std::rotate(buffer.begin(), buffer.begin() + writeIndex, buffer.end()); buffer.reserve(size + 1); writeIndex = buffer.size() - 1; } else if (size + 1 < buffer.size()) { // when shrinking // point writeIndex to separator util::incrementIndex(writeIndex, buffer.size()); // separator becomes last element in smaller vector -> resize cuts oldest elements std::rotate(buffer.begin(), buffer.begin() + util::getValidIndex(static_cast(writeIndex - size), buffer.size()), buffer.end()); buffer.resize(size + 1); writeIndex = util::getValidIndex(static_cast(buffer.size() - 2), buffer.size()); } vectorCapacity = size + 1; } template void RingBuffer::push_back(T& t) { util::incrementIndex(writeIndex, vectorCapacity); if (buffer.size() < vectorCapacity) { buffer.push_back(t); } else { buffer[writeIndex] = t; } } template void RingBuffer::emplace_back(T&& t) { util::incrementIndex(writeIndex, vectorCapacity); if (buffer.size() < vectorCapacity) { buffer.emplace_back(std::move(t)); } else { buffer[writeIndex] = std::move(t); } } }