From 297232d21594b138bb368a42b5b0d085ff9ed6aa Mon Sep 17 00:00:00 2001 From: rtk0c Date: Thu, 19 Oct 2023 22:50:07 -0700 Subject: The great renaming: switch to "module style" --- src/brussel.common/RingBuffer.hpp | 191 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 src/brussel.common/RingBuffer.hpp (limited to 'src/brussel.common/RingBuffer.hpp') diff --git a/src/brussel.common/RingBuffer.hpp b/src/brussel.common/RingBuffer.hpp new file mode 100644 index 0000000..4eaa007 --- /dev/null +++ b/src/brussel.common/RingBuffer.hpp @@ -0,0 +1,191 @@ +#pragma once + +#include +#include +#include +#include + +class RingBufferSentinel {}; + +template +class RingBufferIterator { +public: + using difference_type = TContainer::difference_type; + using value_type = TContainer::value_type; // C++20 relaxed usage requirements of `typename`, now locations where a type is required (like here in a using statement) it's no longer mandatory + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::random_access_iterator_tag; + +public: + TContainer* container; + TContainer::size_type curr; // C++20 relaxed usage requirements of `typename`, same here + bool needsWrapAround; + bool hasWrappedAround = false; + +public: + reference operator*() const { + return container->mRing[curr]; + } + + RingBufferIterator& operator++() { + assert(*this != RingBufferSentinel{}); + ++curr; + if (needsWrapAround && curr == container->mCapacity) { + hasWrappedAround = true; + curr = 0; + } + return *this; + } + + bool operator==(const RingBufferIterator& that) const { + assert(this->container == that.container); + return this->curr == that.curr; + } + + bool operator==(const RingBufferSentinel&) const { + return curr == container->mTailIdx && (!needsWrapAround || hasWrappedAround); + } +}; + +template +class RingBuffer { +public: + using value_type = T; + using reference = T&; + using const_reference = const T&; + friend class RingBufferIterator; + using iterator = RingBufferIterator; + friend class RingBufferIterator; + using const_iterator = RingBufferIterator; + using sentinel = RingBufferSentinel; // Not a part of C++'s Container named requirements, added here for convenience + using difference_type = ptrdiff_t; + using size_type = size_t; + +private: + T* mRing = nullptr; + size_type mHeadIdx = 0; + size_type mTailIdx = 0; + size_type mCapacity = 0; + size_type 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; + } + + [[nodiscard]] iterator begin() { + return { + .container = this, + .curr = mHeadIdx, + .needsWrapAround = mHeadIdx >= mTailIdx, + }; + } + + [[nodiscard]] const_iterator begin() const { return cbegin(); } + + // Same type for both const this and non-const `this` + [[nodiscard]] sentinel end() const { return sentinel{}; } + + [[nodiscard]] const_iterator cbegin() const { + return { + .container = this, + .curr = mHeadIdx, + .needsWrapAround = mHeadIdx >= mTailIdx, + }; + } + + [[nodiscard]] sentinel cend() const { return sentinel{}; } + + [[nodiscard]] T& operator[](size_type i) { return const_cast(const_cast(*this)[i]); } + [[nodiscard]] const T& operator[](size_type i) const { + assert(mRing != nullptr); + size_type idx = mHeadIdx + i; + if (idx >= mCapacity) { + idx -= mCapacity; + } + return mRing[idx]; + } + + void push_back(T t) { + assert(mRing != nullptr); + 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_type capacity() const { + return mCapacity; + } + + [[nodiscard]] size_type size() const { + return mSize; + } + + [[nodiscard]] T* GetBuffer() const { return mRing; } + [[nodiscard]] size_type GetHeadIdx() const { return mHeadIdx; } + [[nodiscard]] size_type GetTailIdx() const { return mTailIdx; } + + void resize(size_type newCapacity) { + auto size = this->size(); + + auto oldRing = mRing; + auto newRing = mRing = new T[newCapacity]; + if (oldRing != nullptr) { + std::rotate_copy(oldRing, oldRing + mHeadIdx, oldRing + mCapacity, newRing); + delete[] oldRing; + } + + mCapacity = newCapacity; + mHeadIdx = 0; + mTailIdx = size; + } +}; -- cgit v1.2.3-70-g09d2