eve.util.ringbuffer

A fixed-capacity ring buffer for byte data.

Part of EVE's Buffer Ownership Redesign (BUFFER-OWNERSHIP-REDESIGN.md §4.1). Provides Solution A: a pre-sized ring buffer that eliminates GC allocation on the send hot path when configured via preallocatedSendBuffer.

Designed for send-queue use in Layer 2 transports: pre-allocated once at connection setup, then zero-allocation in steady state. All methods are @nogc @safe nothrow (except initialize() which is @nogc @trusted nothrow).

Memory Management: Backing storage is allocated via malloc() in initialize() and freed via free() in dispose(). This keeps the buffer outside GC-managed memory, eliminating GC scanning overhead for large buffers and ensuring compliance with ADR-003 (no GC allocation in Layer 2).

Important: The developer is responsible for calling dispose() to free memory. There is no destructor — this is intentional to avoid destructor overhead in performance-critical code paths. Failure to call dispose() will leak memory.

Data is stored in a single contiguous ubyte[] slab. The write position (_head) and read position (_tail) wrap around modulo _capacity. _used tracks the number of live bytes so that the full/empty ambiguity is resolved without wasting a slot.

Layout when partially filled (no wrap):

[....TTTTTTTTTHHHH........]
      ^tail    ^head

Layout when wrapped:

[HHHH........TTTTTTTTTTTT]
    ^head    ^tail

Design decision: separate _used counter instead of the traditional "waste one slot" approach. This allows capacity() bytes of usable storage rather than capacity - 1, which matters for send queues where the buffer is sized to match highWaterMark.

struct RingBuffer

Types 1

Fixed-capacity ring buffer for ubyte data.

Allocation occurs once in initialize() via malloc(). All subsequent operations are @nogc. Call dispose() to release the backing storage via free().

Memory ownership: The developer must call dispose() to free memory. There is no destructor — this design choice avoids destructor overhead for maximum performance. Failure to call dispose() leaks memory.

The struct is non-copyable (@disable this(this)) to prevent accidental duplication of the backing storage pointer. Pass by reference or store as a field in a class.

Buffer Ownership (TERMINOLOGY.md): The backing storage is EVE-Owned after initialize(). append() uses Copy semantics — the caller may reuse or free the source buffer immediately. front() returns a Borrowed slice valid only until the next append() or consume() call.

Fields
private ubyte[] _storage
private size_t _capacity
private size_t _head
private size_t _tail
private size_t _used
Methods
void initialize(size_t capacity) @trusted nothrow @nogcAllocate backing storage for the ring buffer.
void dispose() @trusted nothrow @nogcRelease backing storage immediately via `free()`.
bool append(scope const(ubyte)[] data) @trusted nothrow @nogcAppend data to the buffer.
const(ubyte)[] front() const @trusted nothrow @nogcGet a contiguous view of the front (readable) portion of the buffer.
void consume(size_t n) @safe nothrow @nogcAdvance the read position past `n` consumed bytes.
void clear() @safe nothrow @nogcReset the buffer to empty without releasing storage.
size_t length() const @safe nothrow @nogcNumber of bytes currently stored.
size_t capacity() const @safe nothrow @nogcTotal capacity of the backing storage.
size_t freeSpace() const @safe nothrow @nogcBytes available for future `append()` calls.
bool empty() const @safe nothrow @nogcWhether the buffer contains no data.
bool full() const @safe nothrow @nogcWhether no more data can be appended without consuming first.