parent
24840cdd0c
commit
338a22cf62
@ -0,0 +1,10 @@ |
|||||||
|
Circular buffer (enhanced) |
||||||
|
========================== |
||||||
|
|
||||||
|
This variant of a circular buffer uses static allocation. |
||||||
|
|
||||||
|
This buffer is not meant for producer-consumer synchronization, it's geared towards use cases where the buffer is accessed from a single thread, such as to keep a running average. |
||||||
|
|
||||||
|
The full capacity is utilized thanks to internally storing its length. The buffer has both front and back sets of push/pop/peek functions, as well as a way to iterate and mutate its elements. |
||||||
|
|
||||||
|
See the header for usage details. |
@ -0,0 +1,255 @@ |
|||||||
|
#include <stdint.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#include "circbuf.h" |
||||||
|
|
||||||
|
// --- Circbuf data structure ----
|
||||||
|
|
||||||
|
/** Offset in void* buffer */ |
||||||
|
#define PV_OFFS(pvBuf, elem_size, index) ((uint8_t*)(pvBuf) + ((elem_size)*(index))) |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write data to a CircBuf slot |
||||||
|
* @param cb : circbuf |
||||||
|
* @param index : slot index |
||||||
|
* @param source : data source |
||||||
|
*/ |
||||||
|
static inline void write_buffer(CircBuf *cb, circbuf_size_t index, const void *source) |
||||||
|
{ |
||||||
|
memcpy(PV_OFFS(cb->buf, cb->elem_size, index), source, cb->elem_size); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy data from a CircBuf slot to a buffer |
||||||
|
* @param cb : circbuf |
||||||
|
* @param index : slot index |
||||||
|
* @param dest : destination buffer |
||||||
|
*/ |
||||||
|
static inline void read_buffer(const CircBuf *cb, circbuf_size_t index, void *dest) |
||||||
|
{ |
||||||
|
memcpy(dest, PV_OFFS(cb->buf, cb->elem_size, index), cb->elem_size); |
||||||
|
} |
||||||
|
|
||||||
|
/** Get index of the front position (for write) */ |
||||||
|
static inline circbuf_size_t front_writepos(const CircBuf *cb) { |
||||||
|
return (cb->back + cb->num_used) % cb->cap; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void cbuf_init(CircBuf *cb, void *buf, circbuf_size_t capacity, circbuf_size_t elem_size) |
||||||
|
{ |
||||||
|
// allocate the buffer
|
||||||
|
cb->buf = buf; |
||||||
|
|
||||||
|
// set capacity, clear state
|
||||||
|
cb->elem_size = elem_size; |
||||||
|
cb->cap = capacity; |
||||||
|
|
||||||
|
cbuf_clear(cb); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Check if cbuf is full */ |
||||||
|
bool cbuf_full(const CircBuf *cb) |
||||||
|
{ |
||||||
|
if (cb == NULL) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return cb->num_used == cb->cap; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Check if cbuf is empty */ |
||||||
|
bool cbuf_empty(const CircBuf *cb) |
||||||
|
{ |
||||||
|
if (cb == NULL) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return cb->num_used == 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Get the max capacity of the buffer */ |
||||||
|
circbuf_size_t cbuf_capacity(const CircBuf *cb) |
||||||
|
{ |
||||||
|
return cb->cap; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Get the current number of items in the buffer */ |
||||||
|
circbuf_size_t cbuf_count(const CircBuf *cb) |
||||||
|
{ |
||||||
|
return cb->num_used; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Push one element to the front_writepos */ |
||||||
|
bool cbuf_push(CircBuf *cb, const void *source) |
||||||
|
{ |
||||||
|
if (cb == NULL || source == NULL || cb->num_used == cb->cap) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
write_buffer(cb, front_writepos(cb), source); |
||||||
|
|
||||||
|
// increment
|
||||||
|
cb->num_used++; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Pop one element from the front_writepos */ |
||||||
|
bool cbuf_pop(CircBuf *cb, void *dest) |
||||||
|
{ |
||||||
|
if (cb == NULL || dest == NULL || cb->num_used == 0) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
cb->num_used--; |
||||||
|
|
||||||
|
circbuf_size_t f = front_writepos(cb); |
||||||
|
read_buffer(cb, f, dest); |
||||||
|
|
||||||
|
#ifdef CIRCBUF_ZERO_FREE_SLOTS |
||||||
|
memset(PV_OFFS(cb->buf, cb->elem_size, f), 0, cb->elem_size); |
||||||
|
#endif |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Peek at the front_writepos element */ |
||||||
|
bool cbuf_peek(const CircBuf *cb, void *dest) |
||||||
|
{ |
||||||
|
if (cb == NULL || dest == NULL || cb->num_used == 0) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
circbuf_size_t f = (cb->back + cb->num_used - 1) % cb->cap; |
||||||
|
read_buffer(cb, f, dest); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void * cbuf_ptr(const CircBuf *cb) |
||||||
|
{ |
||||||
|
if (cb == NULL || dest == NULL || cb->num_used == 0) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
circbuf_size_t f = (cb->back + cb->num_used - 1) % cb->cap; |
||||||
|
return PV_OFFS(cb->buf, cb->elem_size, f); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Peek at the nth element (counted from back) */ |
||||||
|
bool cbuf_nth(const CircBuf *cb, circbuf_size_t num, void *dest) |
||||||
|
{ |
||||||
|
if (cb == NULL || dest == NULL || num > cb->num_used) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
circbuf_size_t index = (cb->back + num) % cb->cap; |
||||||
|
read_buffer(cb, index, dest); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void *cbuf_ptr_nth(const CircBuf *cb, circbuf_size_t num) |
||||||
|
{ |
||||||
|
if (cb == NULL || dest == NULL || num > cb->num_used) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
circbuf_size_t index = (cb->back + num) % cb->cap; |
||||||
|
return PV_OFFS(cb->buf, cb->elem_size, index); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Push one element to the back */ |
||||||
|
bool cbuf_push_back(CircBuf *cb, const void *source) |
||||||
|
{ |
||||||
|
if (cb == NULL || source == NULL || cb->num_used == cb->cap) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// move lr back
|
||||||
|
if (cb->back == 0) { |
||||||
|
cb->back = cb->cap - 1; // wrap to the end
|
||||||
|
} else { |
||||||
|
cb->back--; |
||||||
|
} |
||||||
|
cb->num_used++; |
||||||
|
|
||||||
|
write_buffer(cb, cb->back, source); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Pop one element from the back */ |
||||||
|
bool cbuf_pop_back(CircBuf *cb, void *dest) |
||||||
|
{ |
||||||
|
if (cb == NULL || dest == NULL || cb->num_used == 0) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
read_buffer(cb, cb->back, dest); |
||||||
|
|
||||||
|
#ifdef CIRCBUF_ZERO_FREE_SLOTS |
||||||
|
memset(PV_OFFS(cb->buf, cb->elem_size, cb->back), 0, cb->elem_size); |
||||||
|
#endif |
||||||
|
|
||||||
|
// increment
|
||||||
|
cb->back++; |
||||||
|
if (cb->back == cb->cap) { |
||||||
|
cb->back = 0; |
||||||
|
} |
||||||
|
cb->num_used--; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Pop one element from the back */ |
||||||
|
bool cbuf_peek_back(const CircBuf *cb, void *dest) |
||||||
|
{ |
||||||
|
if (cb == NULL || dest == NULL || cb->num_used == 0) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
read_buffer(cb, cb->back, dest); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void* cbuf_ptr_back(const CircBuf *cb) |
||||||
|
{ |
||||||
|
if (cb == NULL || dest == NULL || cb->num_used == 0) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
return PV_OFFS(cb->buf, cb->elem_size, cb->back); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** Clear a cbuf */ |
||||||
|
void cbuf_clear(CircBuf *cb) |
||||||
|
{ |
||||||
|
if (cb == NULL) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
cb->num_used = 0; |
||||||
|
cb->back = 0; |
||||||
|
|
||||||
|
#ifdef CIRCBUF_ZERO_FREE_SLOTS |
||||||
|
memset(PV_OFFS(cb->buf, cb->elem_size, 0), 0, cb->cap * cb->elem_size); |
||||||
|
#endif |
||||||
|
} |
@ -0,0 +1,150 @@ |
|||||||
|
/**
|
||||||
|
* @file circbuf.h |
||||||
|
* @author Ondřej Hruška, 2016,2023 |
||||||
|
* |
||||||
|
* Circular buffer / queue / stack. |
||||||
|
* Slots are pre-allocated, values are copied into the buffer. |
||||||
|
* |
||||||
|
* The buffer may be used as a stack, event queue or a simple buffer. |
||||||
|
* |
||||||
|
* MIT license |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef CIRCBUF_H |
||||||
|
#define CIRCBUF_H |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
// Enable to zero a freed slots after pop, useful for debugging
|
||||||
|
#define CIRCBUF_ZERO_FREE_SLOTS |
||||||
|
|
||||||
|
typedef uint32_t circbuf_size_t; |
||||||
|
|
||||||
|
/** Instance structure - public to allow static allocation, but consider the structure internal matter */ |
||||||
|
struct circbuf_struct { |
||||||
|
void *buf; |
||||||
|
circbuf_size_t num_used; |
||||||
|
circbuf_size_t elem_size; |
||||||
|
circbuf_size_t cap; |
||||||
|
circbuf_size_t back; |
||||||
|
// circbuf_size_t front;
|
||||||
|
}; |
||||||
|
|
||||||
|
typedef struct circbuf_struct CircBuf; |
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize a circular buffer |
||||||
|
* |
||||||
|
* @param[in,out] cb - pointer to the buffer to init, can be statically or dynamically allocated |
||||||
|
* @param buf : backing buffer, can be statically or dynamically allocated |
||||||
|
* @param capacity : buffer capacity |
||||||
|
* @param elem_size : size of one element |
||||||
|
*/ |
||||||
|
void cbuf_init(CircBuf *cb, void *buf, circbuf_size_t capacity, circbuf_size_t elem_size); |
||||||
|
|
||||||
|
|
||||||
|
/** Test for full buffer */ |
||||||
|
bool cbuf_full(const CircBuf *cb); |
||||||
|
|
||||||
|
|
||||||
|
/** Test for empty buffer */ |
||||||
|
bool cbuf_empty(const CircBuf *cb); |
||||||
|
|
||||||
|
|
||||||
|
/** Get the max capacity of the buffer */ |
||||||
|
circbuf_size_t cbuf_capacity(const CircBuf *cb); |
||||||
|
|
||||||
|
|
||||||
|
/** Get the current number of items in the buffer */ |
||||||
|
circbuf_size_t cbuf_count(const CircBuf *cb); |
||||||
|
|
||||||
|
|
||||||
|
/** Peek at the nth element (counted from back) */ |
||||||
|
bool cbuf_nth(const CircBuf *cb, circbuf_size_t num, void *dest); |
||||||
|
|
||||||
|
|
||||||
|
/** Get a mutable reference to nth element (counted from back) */ |
||||||
|
void *cbuf_ptr_nth(const CircBuf *cb, circbuf_size_t num); |
||||||
|
|
||||||
|
|
||||||
|
/** @brief Remove all data from buffer */ |
||||||
|
void cbuf_clear(CircBuf *cb); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append a value to the buffer (FIFO) |
||||||
|
* @param cb : buffer |
||||||
|
* @param source : pointer to a value (will be copied) |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
bool cbuf_push(CircBuf *cb, const void *source); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a value from the buffer, return susccess. |
||||||
|
* |
||||||
|
* @param cb : buffer |
||||||
|
* @param dest : read destination. If NULL, value is discarded. |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
bool cbuf_pop(CircBuf *cb, void *dest); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy the frontmost element without changing the buffer |
||||||
|
* @param cb : buffer |
||||||
|
* @param dest : read destination |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
bool cbuf_peek(const CircBuf *cb, void *dest); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a mutable reference to the front element |
||||||
|
* @param cb : buffer |
||||||
|
* @param dest : read destination |
||||||
|
* @return reference or NULL |
||||||
|
*/ |
||||||
|
void * cbuf_ptr(const CircBuf *cb); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Push a value into the circbuf (LIFO). |
||||||
|
* |
||||||
|
* @param cb : buffer |
||||||
|
* @param source : pointer to a value (will be copied) |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
bool cbuf_push_back(CircBuf *cb, const void *source); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read a value from the buffer, return susccess. |
||||||
|
* |
||||||
|
* @param cb : buffer |
||||||
|
* @param dest : read destination. If NULL, value is discarded. |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
bool cbuf_pop_back(CircBuf *cb, void *dest); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy the backmost element without changing the buffer |
||||||
|
* @param cb : buffer |
||||||
|
* @param dest : read destination |
||||||
|
* @return success |
||||||
|
*/ |
||||||
|
bool cbuf_peek_back(const CircBuf *cb, void *dest); |
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a mutable reference to the backmost element |
||||||
|
* @param cb : buffer |
||||||
|
* @param dest : read destination |
||||||
|
* @return reference or NULL |
||||||
|
*/ |
||||||
|
void* cbuf_ptr_back(const CircBuf *cb); |
||||||
|
|
||||||
|
#endif // CIRCBUF_H
|
Loading…
Reference in new issue