blob: 083d7cf6d10713ba764a7ece69517129c33dc1f0 [file] [log] [blame]
#ifndef AOS_RING_BUFFER_H_
#define AOS_RING_BUFFER_H_
#include <array>
#include <cstddef>
#include <iterator>
#include <new>
#include <type_traits>
namespace aos {
// This is a helper to keep track of some amount of recent data. As you push
// data into the ring buffer, it gets stored. If the buffer becomes full, it
// will start overwriting the oldest data.
template <typename Data, size_t buffer_size>
class RingBuffer {
template <typename ValueType, typename BufferType>
struct generic_iterator {
using iterator_category = ::std::random_access_iterator_tag;
using value_type = ValueType;
using difference_type = ::std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
explicit generic_iterator(BufferType *buffer, size_t index)
: buffer_(buffer), index_(index) {}
generic_iterator &operator++() {
++index_;
return *this;
}
generic_iterator operator++(int) {
const generic_iterator retval = *this;
++(*this);
return retval;
}
generic_iterator &operator--() {
--index_;
return *this;
}
generic_iterator operator--(int) {
const generic_iterator retval = *this;
--(*this);
return retval;
}
generic_iterator &operator+=(ptrdiff_t i) {
index_ += i;
return *this;
}
generic_iterator operator+(ptrdiff_t i) const {
generic_iterator retval = *this;
retval += i;
return retval;
}
generic_iterator &operator-=(ptrdiff_t i) {
index_ -= i;
return *this;
}
generic_iterator operator-(ptrdiff_t i) const {
generic_iterator retval = *this;
retval -= i;
return retval;
}
ptrdiff_t operator-(generic_iterator other) const {
return index_ - other.index_;
}
bool operator==(generic_iterator other) const {
return buffer_ == other.buffer_ && index_ == other.index_;
}
bool operator!=(generic_iterator other) const { return !(*this == other); }
bool operator<(generic_iterator other) const {
return buffer_ == other.buffer_ && index_ < other.index_;
}
bool operator>(generic_iterator other) const {
return buffer_ == other.buffer_ && index_ > other.index_;
}
bool operator<=(generic_iterator other) const {
return buffer_ == other.buffer_ && index_ <= other.index_;
}
bool operator>=(generic_iterator other) const {
return buffer_ == other.buffer_ && index_ >= other.index_;
}
reference operator*() const { return (*buffer_)[index_]; }
pointer operator->() const { return &(*buffer_)[index_]; }
reference operator[](difference_type i) const {
return (*buffer_)[index_ + i];
}
private:
BufferType *buffer_;
size_t index_;
};
public:
using iterator = generic_iterator<Data, RingBuffer>;
using const_iterator = generic_iterator<const Data, const RingBuffer>;
static constexpr size_t kBufferSize = buffer_size;
constexpr RingBuffer() {}
~RingBuffer() { Reset(); }
// Add an item to the RingBuffer, overwriting the oldest element if necessary.
//
// Invalidates the end iterator.
void Push(Data data) {
if (full()) {
(*this)[0] = std::move(data);
oldest_ = (oldest_ + 1) % buffer_size;
} else {
new (&(*this)[size_]) Data(std::move(data));
++size_;
}
}
// Removes the oldest element.
//
// Invalidates all iterators.
void Shift() {
(*this)[0].~Data();
oldest_ = (oldest_ + 1) % buffer_size;
--size_;
}
// Return the value of the index requested, adjusted so that the RingBuffer
// contains the oldest element first and the newest last.
Data &operator[](size_t index) {
#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606
return *std::launder(
reinterpret_cast<Data *>(&data_[(oldest_ + index) % buffer_size]));
#else
// TODO(brian): Remove this when all our compilers are 17 or newer.
return *reinterpret_cast<Data *>(&data_[(oldest_ + index) % buffer_size]);
#endif
}
const Data &operator[](size_t index) const {
#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606
return *std::launder(reinterpret_cast<const Data *>(
&data_[(oldest_ + index) % buffer_size]));
#else
// TODO(brian): Remove this when all our compilers are 17 or newer.
return *reinterpret_cast<const Data *>(
&data_[(oldest_ + index) % buffer_size]);
#endif
}
// Returns the capacity of the RingBuffer
size_t capacity() const { return buffer_size; }
// Returns the number of elements stored in the RingBuffer
size_t size() const { return size_; }
// Is the RingBuffer empty or full?
bool empty() const { return size_ == 0; }
bool full() const { return size_ == buffer_size; }
// Clears all the data out of the buffer.
//
// Invalidates all iterators.
void Reset() {
while (!empty()) {
Shift();
}
}
iterator begin() { return iterator(this, 0); }
iterator end() { return iterator(this, size()); }
const_iterator begin() const { return const_iterator(this, 0); }
const_iterator end() const { return const_iterator(this, size()); }
private:
using DataStorage =
typename std::aligned_storage<sizeof(Data), alignof(Data)>::type;
std::array<DataStorage, buffer_size> data_;
// Oldest contains the oldest item added to the RingBuffer which will be
// the next one to be overwritten
size_t oldest_ = 0;
size_t size_ = 0;
};
} // namespace aos
#endif // AOS_RING_BUFFER_H_