aboutsummaryrefslogtreecommitdiff
path: root/source/10-common/RingBuffer.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/10-common/RingBuffer.hpp')
-rw-r--r--source/10-common/RingBuffer.hpp174
1 files changed, 174 insertions, 0 deletions
diff --git a/source/10-common/RingBuffer.hpp b/source/10-common/RingBuffer.hpp
new file mode 100644
index 0000000..c4ff851
--- /dev/null
+++ b/source/10-common/RingBuffer.hpp
@@ -0,0 +1,174 @@
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <iterator>
+
+template <typename T>
+class RingBuffer {
+public:
+ class Sentinel {};
+ class Iterator {
+ public:
+ friend class RingBuffer;
+ using difference_type = ptrdiff_t;
+ using value_type = T;
+ using pointer = T*;
+ using reference = T&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ private:
+ RingBuffer* mContainer;
+ size_t mCurr;
+ bool mNeedsWrapAround;
+ bool mHasWrapped = false;
+
+ public:
+ // NOTE: default constructed Iterator is in an undefined state
+
+ T& operator*() const {
+ return mContainer->mRing[mCurr];
+ }
+
+ Iterator& operator++() {
+ assert(*this != Sentinel{});
+ ++mCurr;
+ if (mNeedsWrapAround && mCurr == mContainer->mCapacity) {
+ mHasWrapped = true;
+ mCurr = 0;
+ }
+ return *this;
+ }
+
+ bool operator==(const Iterator& that) const {
+ assert(this->mContainer == that.mContainer);
+ return this->mCurr == that.mCurr;
+ }
+
+ bool operator==(const Sentinel&) const {
+ return mCurr == mContainer->mTailIdx && (!mNeedsWrapAround || mHasWrapped);
+ }
+ };
+
+ using value_type = T;
+ using reference = T&;
+ using const_reference = const T&;
+ using iterator = Iterator;
+ // using const_iterator = void; // TODO
+ using difference_type = ptrdiff_t;
+ using size_type = size_t;
+
+private:
+ T* mRing = nullptr;
+ size_t mHeadIdx = 0;
+ size_t mTailIdx = 0;
+ size_t mCapacity = 0;
+ size_t mSize = 0;
+
+public:
+ RingBuffer() noexcept = default;
+
+ ~RingBuffer() noexcept {
+ delete mRing;
+ }
+
+ RingBuffer(const RingBuffer&) noexcept = delete;
+ RingBuffer& operator=(const RingBuffer&) noexcept = delete;
+
+ RingBuffer(RingBuffer&& that) noexcept
+ : mRing{ that.mRing }
+ , mHeadIdx{ that.mHeadIdx }
+ , mTailIdx{ that.mTailIdx }
+ , mCapacity{ that.mCapacity }
+ , mSize{ that.mSize } {
+ that.mRing = nullptr;
+ }
+
+ RingBuffer& operator=(RingBuffer&& that) noexcept {
+ if (this != &that) {
+ auto oldThisRing = this->mRing;
+ this->mRing = that.mRing;
+ that.mRing = nullptr;
+ delete oldThisRing;
+
+ this->mHeadIdx = that.mHeadIdx;
+ this->mTailIdx = that.mTailIdx;
+ this->mCapacity = that.mCapacity;
+ this->mSize = that.mSize;
+ }
+
+ return *this;
+ }
+
+ Iterator begin() {
+ Iterator it;
+ it.mContainer = this;
+ it.mCurr = mHeadIdx;
+ it.mNeedsWrapAround = mHeadIdx >= mTailIdx;
+ return it;
+ }
+
+ Sentinel end() {
+ return Sentinel{};
+ }
+
+ const T& operator[](size_t i) const {
+ size_t idx = mHeadIdx + i;
+ if (idx >= mCapacity) {
+ idx -= mCapacity;
+ }
+ return mRing[idx];
+ }
+ T& operator[](size_t i) { return const_cast<T&>(const_cast<const RingBuffer&>(*this)[i]); }
+
+ void push_back(T t) {
+ if (mTailIdx == mCapacity) {
+ // Ring buffer is filled to the right, warp around to the beginning
+ // mHeadIdx > 0 must be true, since we checked that as condition (1) above
+ mRing[0] = std::move(t);
+ mTailIdx = 1;
+ } else {
+ mRing[mTailIdx] = std::move(t);
+ mTailIdx += 1;
+ }
+
+ // Push mHeadIdx backwards if overwrote element in a filled buffer
+ bool bufferFilled = mSize == mCapacity;
+ if (bufferFilled && mTailIdx > mHeadIdx) {
+ mHeadIdx += 1;
+ if (mHeadIdx == mCapacity) {
+ mHeadIdx = 0;
+ }
+ }
+
+ if (!bufferFilled) {
+ ++mSize;
+ }
+ }
+
+ [[nodiscard]] size_t capacity() const {
+ return mCapacity;
+ }
+
+ [[nodiscard]] size_t size() const {
+ return mSize;
+ }
+
+ [[nodiscard]] T* GetBuffer() const { return mRing; }
+ [[nodiscard]] size_t GetHeadIdx() const { return mHeadIdx; }
+ [[nodiscard]] size_t GetTailIdx() const { return mTailIdx; }
+
+ void resize(size_t newCapacity) {
+ auto size = this->size();
+
+ auto oldRing = mRing;
+ auto newRing = mRing = new T[newCapacity];
+ std::rotate_copy(oldRing, oldRing + mHeadIdx, oldRing + mCapacity, newRing);
+ delete oldRing;
+
+ mCapacity = newCapacity;
+ mHeadIdx = 0;
+ mTailIdx = size;
+ }
+};