Circular Buffer - rFronteddu/general_wiki GitHub Wiki
Imagine your buffer as a ring of fixed-size memory slots. You donβt move data around β you just move two pointers (indexes):
- head_ β where you write new bytes
- tail_ β where you read old bytes
Capacity = 8
[ 0 ][ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ]
β β
tail_ head_
- As you write data, head_ advances.
- As you consume (read) data, tail_ advances.
When either reaches the end, it wraps around to 0.
After writing 3 bytes:
[ A ][ B ][ C ][ ][ ][ ][ ][ ]
β β
tail_ head_
After reading 2 bytes:
[ ][ ][ C ][ ][ ][ ][ ][ ]
β β
tail_ head_
When head_ == tail_, it can mean:
- Buffer is empty (size_ == 0)
- Buffer is full (size_ == capacity)
So we track the current size_ to disambiguate.
#include <stdio>
#include <vector>
#include <span>
class circular_buffer
{
sdt::vector<uint8_t> buffer_;
size_t head_ = 0; // write position
size_t tail_ = 0; // read position
size_t size_ = 0; // bytes currently stored
public:
explicit circular_buffer(size_t capacity) : buffer_(capacity) {}
size_t write(const uint8_t * data, size_t n) {
size_t written = 0;
while (written < n && size_ < buffer_.size()) {
buffer_[head] = data[written++];
head_ = (head_ + 1) % buffer_.size();
size_++;
}
return written;
}
std::span<uint8_t> contiguous_span(void) {
if (tail_ + size_ <= buffer_.size()) {
return {buffer_.data() + tail_, size_};
} else {
return {buffer_.data() + tail_, buffer_.size() - tail_};
}
}
void consume(size_t n) {
assert(n <= size_);
tail_ = (tail_ + n) % buffer_.size();
size_ -= n;
}
size_t available() const { return size_; }
size_t capacity() const { return buffer_.size(); }
}