Skip to content

Instantly share code, notes, and snippets.

@riptl
Created March 1, 2018 14:02
Show Gist options
  • Save riptl/b527326941927364da03c04a9c3582e8 to your computer and use it in GitHub Desktop.
Save riptl/b527326941927364da03c04a9c3582e8 to your computer and use it in GitHub Desktop.
C Unmasked Ring Buffer
#include <stdlib.h>
#include <string.h>
#include "ringbuf.h"
// Unmasked implementation
inline static size_t unmasked_inc(size_t index, size_t size);
inline static size_t unmasked_inc(size_t index, size_t size)
{
return index != SIZE_MAX
? index + 1
: (SIZE_MAX % size) + 1; // Rewind
}
mdfy_ring_t *mdfy_create_ring(size_t size)
{
mdfy_ring_t tmp = {
calloc(size, sizeof(float)),
size, 0, 0
};
mdfy_ring_t *r = malloc(sizeof(mdfy_ring_t));
memcpy(r, &tmp, sizeof(mdfy_ring_t));
return r;
}
void mdfy_destroy_ring(mdfy_ring_t *r)
{
free(r);
}
_Bool mdfy_ring_empty(mdfy_ring_t *r)
{
return r->head == r->tail;
}
_Bool mdfy_ring_full(mdfy_ring_t *r)
{
return mdfy_ring_used(r) == r->size;
}
size_t mdfy_ring_used(mdfy_ring_t *r)
{
return r->head - r->tail;
}
size_t mdfy_ring_free(mdfy_ring_t *r)
{
return r->size - mdfy_ring_used(r);
}
mdfy_ring_t *mdfy_ring_resize(mdfy_ring_t *r, size_t newSize)
{
if (mdfy_ring_used(r) > newSize)
return NULL;
float *newBuf;
size_t realHead = (r->head % r->size);
size_t realTail = (r->tail % r->size);
size_t newHead, newTail;
if ((realHead > realTail || r->tail == r->head) && newSize > r->size) {
// Easy resize
newBuf = realloc(r->buf, newSize);
newHead = r->head;
newTail = r->tail;
} else {
// Cache current indices
size_t oldHead = r->head;
size_t oldTail = r->tail;
// Copy necessary
newBuf = calloc(newSize, sizeof(float));
newHead = mdfy_ring_read(r, newBuf, newSize);
newTail = 0;
// Revert from read
r->head = oldHead;
r->tail = oldTail;
}
// Allocation failed
if (newBuf == NULL)
return NULL;
mdfy_ring_t tmp = {
newBuf, newSize,
newHead, newTail
};
mdfy_ring_t *newRing = malloc(sizeof(mdfy_ring_t));
memcpy(newRing, &tmp, sizeof(mdfy_ring_t));
return newRing;
}
mdfy_ring_t *mdfy_ring_promise(mdfy_ring_t *r, size_t minSize)
{
if (r->size >= minSize) {
return r;
} else {
size_t stdResize = (3 * r->size) / 8;
// newSize = max(stdResize, minSize)
size_t newSize = minSize > stdResize ? minSize : stdResize;
return mdfy_ring_resize(r, newSize);
}
}
void mdfy_ring_clear(mdfy_ring_t *r)
{
memset(r->buf, 0, r->size * sizeof(float));
r->head = 0;
r->tail = 0;
}
size_t mdfy_ring_read(mdfy_ring_t *r, float *data, size_t len)
{
size_t avl = mdfy_ring_used(r);
if (len > avl)
len = avl;
if (len == 0)
return 0;
size_t realHead = (r->head % r->size);
size_t realTail = (r->tail % r->size);
size_t split = r->size - realTail;
if (realHead > realTail || len < split) {
// One copy needed
// Tail does not wrap around
memcpy(data, r->buf + realTail, len * sizeof(float));
} else {
// Update both halves
// Tail wraps around
memcpy(data, r->buf + realTail, split * sizeof(float));
size_t part2 = len - split;
memcpy(data + split, r->buf, part2 * sizeof(float));
}
size_t newTail = r->tail + len;
if (newTail >= r->tail) {
r->tail = newTail;
} else {
// Overflow
r->tail = realTail + 1;
}
return len;
}
size_t mdfy_ring_write(mdfy_ring_t *r, float *data, size_t len)
{
size_t avl = mdfy_ring_free(r);
if (len > avl)
len = avl;
if (len == 0)
return 0;
size_t realHead = (r->head % r->size);
size_t realTail = (r->tail % r->size);
size_t split = r->size - realHead;
if (realHead < realTail || len <= split) {
// One copy needed
// Head does not wrap around
memcpy(r->buf + realHead, data, len * sizeof(float));
} else {
// Two copies needed
// Head wraps around
memcpy(r->buf + realHead, data, split * sizeof(float));
size_t part2 = len - split;
memcpy(r->buf, data + split, part2 * sizeof(float));
}
size_t newHead = r->head + len;
if (newHead >= r->head) {
r->head = newHead;
} else {
// Overflow
r->head = realHead + 1;
}
return len;
}
void mdfy_ring_push(mdfy_ring_t *r, float v)
{
if (mdfy_ring_full(r))
return;
r->buf[(r->head % r->size)] = v;
r->head = unmasked_inc(r->head, r->size);
}
float mdfy_ring_pop(mdfy_ring_t *r)
{
if (mdfy_ring_empty(r))
return 0.0f / 0.0f; // NaN
float v = r->buf[(r->tail % r->size)];
r->tail = unmasked_inc(r->tail, r->size);
return v;
}
#ifndef LIBMIDIFY_RINGBUF_H
#define LIBMIDIFY_RINGBUF_H
#include <unistd.h>
struct mdfy_ring_s {
float * const buf;
const size_t size;
size_t head, tail;
};
typedef struct mdfy_ring_s mdfy_ring_t;
mdfy_ring_t *mdfy_create_ring (size_t);
mdfy_ring_t *mdfy_ring_resize (mdfy_ring_t *, size_t newSize);
mdfy_ring_t *mdfy_ring_promise (mdfy_ring_t *, size_t minSize);
void mdfy_ring_clear (mdfy_ring_t *);
void mdfy_destroy_ring (mdfy_ring_t *);
_Bool mdfy_ring_empty (mdfy_ring_t *);
_Bool mdfy_ring_full (mdfy_ring_t *);
size_t mdfy_ring_used (mdfy_ring_t *);
size_t mdfy_ring_free (mdfy_ring_t *);
size_t mdfy_ring_write (mdfy_ring_t *, float *, size_t len);
size_t mdfy_ring_read (mdfy_ring_t *, float *, size_t len);
void mdfy_ring_push (mdfy_ring_t *, float);
float mdfy_ring_pop (mdfy_ring_t *);
#endif //LIBMIDIFY_RINGBUF_H
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "ringbuf.h"
#include "test.h"
static void check_alloc(int used);
static void array_access(void);
static mdfy_ring_t *r;
// Could be more efficient
// but looks good in logs
static void check_alloc(int used)
{
if (used == 0) {
assert(mdfy_ring_empty(r));
assert(!mdfy_ring_full(r));
assert(mdfy_ring_used(r) == 0);
assert(mdfy_ring_free(r) == 5);
} else if (used == r->size) {
assert(!mdfy_ring_empty(r));
assert(mdfy_ring_full(r));
assert(mdfy_ring_used(r) == r->size);
assert(mdfy_ring_free(r) == 0);
} else {
assert(!mdfy_ring_empty(r));
assert(!mdfy_ring_full(r));
assert(mdfy_ring_used(r) == used);
assert(mdfy_ring_free(r) == r->size - used);
}
}
static void array_access(void)
{
{
// Write some values
float buf[5 * 4] = { 1, 2, 3, 4, 5 };
size_t count = mdfy_ring_write(r, buf, 5 * 4);
// Ring should only copy available size
assert(count == 5);
check_alloc(5);
}
{
// Read some values
float buf[5 * 4] = {};
float correct[5] = {1, 2, 3, 4, 5};
size_t count = mdfy_ring_read(r, buf, 5 * 4);
// Ring should only copy available size
assert(count == 5);
check_alloc(0);
// Check values
assert(memcmp(buf, correct, 5 * sizeof(float)) == 0);
}
}
int test_ringbuf(void)
{
r = mdfy_create_ring(5);
check_alloc(0);
// Aligned access
array_access();
{
mdfy_ring_push(r, 0x0A);
// Write one value
check_alloc(1);
// Read one value
assert(mdfy_ring_pop(r) == 0x0A);
check_alloc(0);
}
// Misaligned access (wraparound)
array_access();
mdfy_ring_clear(r);
check_alloc(0);
// Promise already fulfilled
{
mdfy_ring_t *newRing = mdfy_ring_promise(r, 5);
assert(newRing == r);
mdfy_ring_push(r, 1);
mdfy_ring_push(r, 2);
mdfy_ring_push(r, 3);
mdfy_ring_push(r, 4);
mdfy_ring_push(r, 5);
}
// Easy ring copy
{
mdfy_ring_t *oldRing = r;
mdfy_ring_t *newRing = mdfy_ring_resize(r, 10);
assert(newRing != r);
r = newRing;
check_alloc(5);
float correct[] = { 1, 2, 3, 4, 5 };
float buf[5];
mdfy_ring_read(r, buf, 5);
// Check values
assert(memcmp(correct, buf, 5 * sizeof(float)) == 0);
r = oldRing;
}
// Wraparound ring copy
{
r->tail = 1;
r->head = 6;
mdfy_ring_t *oldRing = r;
mdfy_ring_t *newRing = mdfy_ring_resize(r, 10);
assert(newRing != r);
r = newRing;
check_alloc(5);
float correct[] = { 2, 3, 4, 5, 1 };
float buf[5];
mdfy_ring_read(r, buf, 5);
assert(memcmp(correct, buf, 5 * sizeof(float)) == 0);
r = oldRing;
}
mdfy_destroy_ring(r);
return 0;
}
#ifndef LIBMIDIFY_TEST_RINGBUF_H
#define LIBMIDIFY_TEST_RINGBUF_H
int test_ringbuf(void);
#endif //LIBMIDIFY_TEST_RINGBUF_H
@riptl
Copy link
Author

riptl commented Mar 1, 2018

Not yet battle tested and probably has some weird bugs in read() and write().
Vanilla C99.

Implemented after the unmasked method in https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/ .
This one should support index overflowing with sizes other than powers of two though.

It uses floats as the main data type but that can be changed easily ofc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment