// SPDX-License-Identifier: BSD-2-Clause // author: Max Kellermann #pragma once #include // for std::move() #include #include #include /** * A circular buffer. * * This class does not manage buffer memory. It will not allocate or * free any memory, it only manages the contents of an existing * buffer given to the constructor. * * Everything between #head and #tail is valid data (may wrap around). * If both are equal, then the buffer is empty. Due to this * implementation detail, the buffer is empty when #size-1 items are * stored; the last buffer cell cannot be used. */ template class CircularBuffer { public: using Range = std::span; using pointer = typename Range::pointer; using size_type = typename Range::size_type; protected: /** * The next index to be read. */ size_type head = 0; /** * The next index to be written to. */ size_type tail = 0; const std::span buffer; public: explicit constexpr CircularBuffer(Range _buffer) noexcept :buffer(_buffer) {} CircularBuffer(const CircularBuffer &other) = delete; CircularBuffer &operator=(const CircularBuffer &other) = delete; protected: constexpr size_type Next(size_type i) const noexcept { return i + 1 == buffer.size() ? 0 : i + 1; } public: constexpr void Clear() noexcept { head = tail = 0; } constexpr size_type GetCapacity() const noexcept { return buffer.size(); } constexpr bool empty() const noexcept { return head == tail; } constexpr bool IsFull() const noexcept { return Next(tail) == head; } /** * Returns the number of elements stored in this buffer. */ constexpr size_type GetSize() const noexcept { return head <= tail ? tail - head : buffer.size() - head + tail; } /** * Returns the number of elements that can be added to this * buffer. */ constexpr size_type GetSpace() const noexcept { /* space = capacity - size - 1 */ return (head <= tail ? buffer.size() - tail + head : head - tail) - 1; } /** * Prepares writing. Returns a buffer range which may be written. * When you are finished, call Append(). */ constexpr Range Write() noexcept { assert(head < buffer.size()); assert(tail < buffer.size()); size_type end = tail < head ? head - 1 /* the "head==0" is there so we don't write the last cell, as this situation cannot be represented by head/tail */ : buffer.size() - (head == 0); return buffer.subspan(tail, end - tail); } /** * Expands the tail of the buffer, after data has been written * to the buffer returned by Write(). */ constexpr void Append(size_type n) noexcept { assert(head < buffer.size()); assert(tail < buffer.size()); assert(n < buffer.size()); assert(tail + n <= buffer.size()); assert(head <= tail || tail + n < head); tail += n; if (tail == buffer.size()) { assert(head > 0); tail = 0; } } /** * Return a buffer range which may be read. The buffer pointer is * writable, to allow modifications while parsing. */ constexpr Range Read() noexcept { assert(head < buffer.size()); assert(tail < buffer.size()); return buffer.subspan(head, (tail < head ? buffer.size() : tail) - head); } /** * Marks a chunk as consumed. */ constexpr void Consume(size_type n) noexcept { assert(head < buffer.size()); assert(tail < buffer.size()); assert(n < buffer.size()); assert(head + n <= buffer.size()); assert(tail < head || head + n <= tail); head += n; if (head == buffer.size()) head = 0; } /** * Move data from the buffer to the destination. This method * considers ring buffer wraparound. * * @return the number of items moved */ constexpr size_type MoveTo(Range dest) noexcept { size_type n = 0; auto a = Read(); if (a.size() > dest.size()) a = a.first(dest.size()); if (!a.empty()) { dest = {std::move(a.begin(), a.end(), dest.begin()), dest.end()}; Consume(a.size()); n += a.size(); if (dest.empty()) return n; if (auto b = Read(); !b.empty()) { if (b.size() > dest.size()) b = b.first(dest.size()); std::move(b.begin(), b.end(), dest.begin()); Consume(b.size()); n += b.size(); } } return n; } };