std1.0.0-beta-7 SafeC docs

Source collections/ringbuffer.sc

View in the repository

// SafeC Standard Library — Ring Buffer Implementation
#pragma once
#include <std/collections/ringbuffer.h>

namespace std {

// RingBuffer's '& self.mask' indexing (length()/write()/etc. below) only
// gives correct results when cap is a power of two; nothing previously
// checked that. Rounds DOWN (never up) since 'buf' is caller-provided and
// sized for the caller's original 'cap' — this may only ever use less of
// it, never more.
static unsigned long ringbuf_floor_pow2_(unsigned long n) {
    if (n == (unsigned long)0) { return (unsigned long)1; }
    unsigned long p = (unsigned long)1;
    while (p * (unsigned long)2 <= n) { p = p * (unsigned long)2; }
    return p;
}

inline struct RingBuffer RingBuffer::over([]unsigned char buf) {
    struct RingBuffer rb;
    unsigned long roundedCap = buf.len == 0UL ? 0UL : ringbuf_floor_pow2_(buf.len);
    rb.buf  = buf;
    rb.cap  = roundedCap;
    rb.mask = roundedCap == 0UL ? 0UL : roundedCap - (unsigned long)1;
    rb.head = (unsigned long)0;
    rb.tail = (unsigned long)0;
    return rb;
}

inline unsigned long RingBuffer::length() const {
    return self.head - self.tail;
}

inline unsigned long RingBuffer::writable() const {
    return self.cap - (self.head - self.tail);
}

inline int RingBuffer::is_empty() const {
    if (self.head == self.tail) { return 1; }
    return 0;
}

inline int RingBuffer::is_full() const {
    if ((self.head - self.tail) >= self.cap) { return 1; }
    return 0;
}

// write()/read()/peek() copy in at most two contiguous runs (the part up to
// the physical end of 'buf', then the wrapped remainder from its start)
// instead of one loop that recomputes '(pos + i) & mask' every iteration.
// Both do exactly the same bytes in the same order and stay allocation-free
// and dependency-free (no memcpy call, so this file's "Freestanding-safe"
// guarantee for real-time audio/DSP use is unaffected) — the only change is
// that each run is now a straight, unwrapped index sequence, which the
// auto-vectorizer can turn into real SIMD loads/stores; the masked version
// couldn't be vectorized as well with a data-dependent wraparound computed
// fresh every byte.
inline unsigned long RingBuffer::write([]const unsigned char data) {
    unsigned long len = data.len;
    unsigned long space = self.writable();
    if (len > space) { len = space; }
    unsigned long head = self.head;
    unsafe {
        unsigned long start = head & self.mask;
        unsigned long firstChunk = self.cap - start;
        if (firstChunk > len) { firstChunk = len; }
        unsigned long i = (unsigned long)0;
        while (i < firstChunk) {
            self.buf[start + i] = data[i];
            i = i + (unsigned long)1;
        }
        unsigned long remaining = len - firstChunk;
        i = (unsigned long)0;
        while (i < remaining) {
            self.buf[i] = data[firstChunk + i];
            i = i + (unsigned long)1;
        }
    }
    // Release store: ensure writes visible before head update.
    unsafe { asm volatile ("" : : : "memory"); }
    self.head = head + len;
    return len;
}

inline unsigned long RingBuffer::read([]unsigned char out) {
    unsigned long len = out.len;
    unsigned long avail = self.length();
    if (len > avail) { len = avail; }
    unsigned long tail = self.tail;
    unsafe {
        unsigned long start = tail & self.mask;
        unsigned long firstChunk = self.cap - start;
        if (firstChunk > len) { firstChunk = len; }
        unsigned long i = (unsigned long)0;
        while (i < firstChunk) {
            out[i] = self.buf[start + i];
            i = i + (unsigned long)1;
        }
        unsigned long remaining = len - firstChunk;
        i = (unsigned long)0;
        while (i < remaining) {
            out[firstChunk + i] = self.buf[i];
            i = i + (unsigned long)1;
        }
    }
    unsafe { asm volatile ("" : : : "memory"); }
    self.tail = tail + len;
    return len;
}

inline unsigned long RingBuffer::peek([]unsigned char out) const {
    unsigned long len = out.len;
    unsigned long avail = self.length();
    if (len > avail) { len = avail; }
    unsigned long tail = self.tail;
    unsafe {
        unsigned long start = tail & self.mask;
        unsigned long firstChunk = self.cap - start;
        if (firstChunk > len) { firstChunk = len; }
        unsigned long i = (unsigned long)0;
        while (i < firstChunk) {
            out[i] = self.buf[start + i];
            i = i + (unsigned long)1;
        }
        unsigned long remaining = len - firstChunk;
        i = (unsigned long)0;
        while (i < remaining) {
            out[firstChunk + i] = self.buf[i];
            i = i + (unsigned long)1;
        }
    }
    return len;
}

inline unsigned long RingBuffer::discard(unsigned long len) {
    unsigned long avail = self.length();
    if (len > avail) { len = avail; }
    self.tail = self.tail + len;
    return len;
}

inline int RingBuffer::enqueue(unsigned char b) {
    unsigned char one[1];
    one[0] = b;
    return self.write(one[0..1]) == (unsigned long)1 ? 1 : 0;
}

inline ?unsigned char RingBuffer::dequeue() {
    unsigned char one[1];
    one[0] = (unsigned char)0;
    if (self.read(one[0..1]) == (unsigned long)0) { return null; }
    return one[0];
}

inline void RingBuffer::clear() {
    self.head = (unsigned long)0;
    self.tail = (unsigned long)0;
}

} // namespace std