Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 156 additions & 2 deletions src/jc/cpp/ringbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
#include <stdlib.h>
#include <assert.h> // disable with NDEBUG

#include <string.h> // memmove

#define JC_SWAP(type, lhs, rhs)\
{\
type tmp = rhs;\
rhs = lhs;\
lhs = tmp;\
}

namespace jc
{

Expand All @@ -34,6 +43,14 @@ class RingBuffer
RingBuffer(uint32_t capacity) : m_Buffer(0), m_Head(0), m_Tail(0), m_Max(0) { SetCapacity(capacity); }
~RingBuffer() { Free(); }

void Swap(RingBuffer<T>& rhs) {
JC_SWAP(T*, m_Buffer, rhs.m_Buffer);
JC_SWAP(uint32_t, m_Head, rhs.m_Head);
JC_SWAP(uint32_t, m_Tail, rhs.m_Tail);
JC_SWAP(uint32_t, m_Max, rhs.m_Max);
JC_SWAP(uint32_t, m_Full, rhs.m_Full);
}

/// Gets the size of the buffer
uint32_t Size() const {
if (m_Full)
Expand All @@ -51,20 +68,29 @@ class RingBuffer
uint32_t Capacity() const { return (uint32_t)m_Max; }
/// Increases or decreases capacity. Invalidates pointers to elements
void SetCapacity(uint32_t capacity);
/// Increases or decreases capacity. Invalidates pointers to elements
void OffsetCapacity(uint32_t grow);

/// Adds one item to the ring buffer. Asserts if the buffer is full
void Push(const T& item);
/// Adds one item to the ring buffer. Does not assert, If full, overwrites the value at the tail.
void PushUnchecked(const T& item);
/// Removes (and returns) the first inserted item from the ring buffer. Asserts if the buffer is empty
T Pop();
/// Removes the element at logical index (0-based) and keeps order
void Erase(uint32_t index);

T& operator[] (size_t i) { assert(i < Size()); return m_Buffer[(m_Tail + i) % m_Max]; }
const T& operator[] (size_t i) const { assert(i < Size()); return m_Buffer[(m_Tail + i) % m_Max]; }

// Useful for sorting
void Flatten(); // in-place linearization so data starts at m_Buffer[0]
void FlattenUnordered(); // compacts contiguously without preserving logical order
T* Buffer() const { return m_Buffer; }

// Mainly for unit tests
uint32_t Head() const { return m_Head; }
uint32_t Tail() const { return m_Tail; }
T* Buffer() const { return m_Buffer; }

private:
T* m_Buffer;
Expand All @@ -78,6 +104,8 @@ class RingBuffer
RingBuffer<T>& operator= (const RingBuffer<T>& rhs);
bool operator== (const RingBuffer<T>& rhs);

void ReverseRange(uint32_t begin, uint32_t end_exclusive);

void Free() {
free(m_Buffer);
m_Buffer = 0;
Expand Down Expand Up @@ -136,6 +164,12 @@ void RingBuffer<T>::SetCapacity(uint32_t capacity)
m_Full = capacity == size ? 1 : 0;
}

template <typename T>
void RingBuffer<T>::OffsetCapacity(uint32_t grow)
{
SetCapacity(Capacity() + grow);
}

template <typename T>
void RingBuffer<T>::Push(const T& item)
{
Expand All @@ -160,13 +194,133 @@ T RingBuffer<T>::Pop()
return item;
}

template <typename T>
void RingBuffer<T>::Erase(uint32_t index)
{
uint32_t size = Size();
assert(index < size);

// Compute physical index of the element to erase
uint32_t pos = (m_Tail + index) % m_Max;

// Shift elements left from pos towards head to preserve order
// Stop when the next index equals m_Head (the logical end)
while (true)
{
uint32_t next = pos + 1;
if (next >= m_Max) next = 0;
if (next == m_Head)
break;
m_Buffer[pos] = m_Buffer[next];
pos = next;
}

// One element removed: move head back by one and clear full flag
if (m_Head == 0)
{
m_Head = m_Max - 1;
}
else
{
--m_Head;
}
m_Full = 0;
}


// some cases: .=free, T=Tail, H=Head, *=used
// 1) [T****H....]
// 2) [...T***H..]
// 3) [*H...T****]

template <typename T>
void RingBuffer<T>::FlattenUnordered()
{
uint32_t capacity = m_Max;
uint32_t size = Size();
if (size == 0 || m_Tail == 0)
return; // already contiguous or empty

if (m_Tail < m_Head)
{
// Single contiguous block at offset; shift entire block to start
memmove(m_Buffer, m_Buffer + m_Tail, sizeof(T) * size);
}
else
{
// Wrapped case: move the rightmost part down directly after the left part.
// This compacts elements into [0..size-1] without preserving order.
uint32_t right_len = capacity - m_Tail; // [m_Tail .. m_Max)
memmove(m_Buffer + m_Head, m_Buffer + m_Tail, sizeof(T) * right_len);
}

m_Tail = 0;
m_Head = size;
}

// In-place linearization. After this call, the logical sequence is contiguous
// at m_Buffer[0..Size()-1]. Does not allocate temporary buffers.
template <typename T>
void RingBuffer<T>::Flatten()
{
uint32_t capacity = m_Max;
uint32_t size = Size();

// Early out when already contiguous (tail at 0) or empty
// No state changes in these cases.
if (size == 0 || m_Tail == 0)
return;

if (m_Tail < m_Head)
{
// Single contiguous block but offset; shift down to index 0
memmove(m_Buffer, m_Buffer + m_Tail, sizeof(T) * size);
}
else
{
// Wrapped case: rotate the whole buffer left by m_Tail positions.
// This makes the logical tail land at index 0. Elements outside
// [0..size-1] are unspecified and may contain moved free slots.
uint32_t n = capacity;
uint32_t k = m_Tail % n;
if (k)
{
ReverseRange(0, k);
ReverseRange(k, n);
ReverseRange(0, n);
}
}

m_Tail = 0;
m_Head = size;
}

template <typename T>
void RingBuffer<T>::ReverseRange(uint32_t begin, uint32_t end_exclusive)
{
while (begin < end_exclusive)
{
--end_exclusive;
if (begin >= end_exclusive)
break;
T tmp = m_Buffer[begin];
m_Buffer[begin] = m_Buffer[end_exclusive];
m_Buffer[end_exclusive] = tmp;
++begin;
}
}


} // namespace

#undef JC_SWAP

/*

VERSION:

1.0 Initial version
1.1 2025-10-08 Added Swap(), Erase() and Flatten*() functions
1.0 Initial version


LICENSE:
Expand Down
Loading