You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
4.2 KiB
C++

// SPDX-License-Identifier: BSD-2-Clause
// author: Max Kellermann <max.kellermann@gmail.com>
#pragma once
#include <algorithm> // for std::move()
#include <cassert>
#include <cstddef>
#include <span>
/**
* 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<typename T>
class CircularBuffer {
public:
using Range = std::span<T>;
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<T> 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;
}
};