From 338a22cf6270fd1cd2360d571a88c0c7d211fe2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Thu, 12 Jan 2023 23:49:13 +0100 Subject: [PATCH] improved circbuf --- circbuf/README.md | 46 +------- circbuf/circbuf.c | 210 +++++++++++++++++++-------------- circbuf/circbuf.h | 85 +++++++++----- circbuf_deque/README.md | 10 ++ circbuf_deque/circbuf.c | 255 ++++++++++++++++++++++++++++++++++++++++ circbuf_deque/circbuf.h | 150 +++++++++++++++++++++++ 6 files changed, 595 insertions(+), 161 deletions(-) create mode 100644 circbuf_deque/README.md create mode 100644 circbuf_deque/circbuf.c create mode 100644 circbuf_deque/circbuf.h diff --git a/circbuf/README.md b/circbuf/README.md index e13803a..171b0ef 100644 --- a/circbuf/README.md +++ b/circbuf/README.md @@ -4,48 +4,6 @@ Circular buffer This is a C implementation of a circular buffer, which can also be used as a queue or stack. -Supported operations: `push`, `pop`, `append`. - To achieve thread safety (to some extent) in a producent-consumer situation, -there is no length variable, only write pointers. - -The buffer has a fixed size, values are copied when inserted. The buffer is created using `malloc`. -If your platform does not support `malloc`, you will have to customize the initialization routine. - -Applications ------------- - -- Buffer for USARTs or similar communication interfaces -- Event queue -- Object stack - -Usage ------ - -```c -#include -#include "circbuf.h" - -CircBuf *cb; // buffer instance - -void main() -{ - cb = cbuf_create(32, sizeof(char)); // create a char buffer of size 32. - - // now it's ready for use! - - // write functions return true on success: - cbuf_append(cb, 'A'); // append A (using as a queue) - cbuf_push(cb, 'B'); // push B (using as a stack) - - // read all - char c; - while (cbuf_pop(cb, &c)) { - printf("%c", c); - } - - cbuf_destroy(cb); // free the buffer -} -``` - -For details how to use each function, please read the header file. +there is no length variable, only write pointers. Due to not using a length variable, +one element in the buffer is always left unused. diff --git a/circbuf/circbuf.c b/circbuf/circbuf.c index 2b048bd..c3423e3 100644 --- a/circbuf/circbuf.c +++ b/circbuf/circbuf.c @@ -1,7 +1,6 @@ #include #include #include -#include #include "circbuf.h" @@ -11,25 +10,15 @@ #define PV_OFFS(pvBuf, elem_size, index) ((uint8_t*)(pvBuf) + ((elem_size)*(index))) -// Instance structure -struct circbuf_struct { - void *buf; - size_t elem_size; - size_t cap; - size_t lr; // last read pos - size_t nw; // next write pos -}; - - /** * @brief Write data to a CircBuf slot * @param cb : circbuf * @param index : slot index * @param source : data source */ -static void write_buffer(CircBuf *cb, size_t index, const void *source) +static 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); + memcpy(PV_OFFS(cb->buf, cb->elem_size, index), source, cb->elem_size); } @@ -39,129 +28,170 @@ static void write_buffer(CircBuf *cb, size_t index, const void *source) * @param index : slot index * @param dest : destination buffer */ -static void read_buffer(const CircBuf *cb, size_t index, void *dest) +static 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); + memcpy(dest, PV_OFFS(cb->buf, cb->elem_size, index), cb->elem_size); } -/** Create a cbuf */ -CircBuf *cbuf_create(size_t capacity, size_t elem_size) +void cbuf_init(CircBuf *cb, void *buf, circbuf_size_t capacity, circbuf_size_t elem_size) { - // add one, because one is always unused. - capacity++; - - // Allocate the structure - CircBuf *cb = malloc(sizeof(CircBuf)); - if (cb == NULL) { - return NULL; - } - - // allocate the buffer - cb->buf = malloc(capacity * elem_size); - if (cb->buf == NULL) { - free(cb); // fail - free structure. - return NULL; - } - - // set capacity, clear state - cb->elem_size = elem_size; - cb->cap = capacity; - cbuf_clear(cb); - - return cb; -} - + cb->buf = buf; -/** Release cbuf memory */ -void cbuf_destroy(CircBuf *cb) -{ - if (cb != NULL) { - if (cb->buf != NULL) { - free(cb->buf); - } + // set capacity, clear state + cb->elem_size = elem_size; + cb->cap = capacity; - free(cb); - } + cbuf_clear(cb); } /** Check if cbuf is full */ bool cbuf_full(const CircBuf *cb) { - if (cb == NULL) return false; + if (cb == NULL) { + return false; + } - return (cb->lr == cb->nw); + return (cb->lr == cb->nw); } /** Check if cbuf is empty */ bool cbuf_empty(const CircBuf *cb) { - if (cb == NULL) return true; + if (cb == NULL) { + return true; + } - return ((cb->lr + 1) % cb->cap) == cb->nw; + return ((cb->lr + 1) % cb->cap) == cb->nw; } -/** Write a byte to the buffer, if space left */ -bool cbuf_append(CircBuf *cb, const void *source) +bool cbuf_push(CircBuf *cb, const void *source) { - if (cb == NULL) return false; - if (source == NULL) return false; - if (cbuf_full(cb)) return false; + if (cb == NULL || source == NULL || cbuf_full(cb)) { + return false; + } - write_buffer(cb, cb->nw, source); + write_buffer(cb, cb->nw, source); - // increment - cb->nw++; - if (cb->nw == cb->cap) cb->nw = 0; + // increment + cb->nw++; + if (cb->nw == cb->cap) { cb->nw = 0; } - return true; + return true; } -/** Push value to the end, like a stack. */ -bool cbuf_push(CircBuf *cb, const void *source) +bool cbuf_pop(CircBuf *cb, void *dest) { - if (cb == NULL) return false; - if (source == NULL) return false; - if (cbuf_full(cb)) return false; + if (cb == NULL || dest == NULL || cbuf_empty(cb)) { + return false; + } - write_buffer(cb, cb->lr, source); + // increment + if (cb->nw == 0) { + cb->nw = cb->cap - 1; + } else { + cb->nw--; + } - // move lr back - if (cb->lr == 0) { - cb->lr = cb->cap - 1; // wrap to the end - } else { - cb->lr--; - } + read_buffer(cb, cb->nw, dest); - return true; +#ifdef CIRCBUF_ZERO_FREE_SLOTS + memset(PV_OFFS(cb->buf, cb->elem_size, cb->nw), 0, cb->elem_size); +#endif + + return true; } -/** Read one byte, if not empty. */ -bool cbuf_pop(CircBuf *cb, void *dest) +bool cbuf_peek(const CircBuf *cb, void *dest) { + if (cb == NULL || dest == NULL || cbuf_empty(cb)) { + return false; + } + + circbuf_size_t index = cb->nw; + if (index == 0) { + index = cb->cap - 1; + } else { + index--; + } + + read_buffer(cb, index, dest); + return true; +} + + +bool cbuf_push_back(CircBuf *cb, const void *source) { - if (cb == NULL || dest == NULL) return false; - if (cbuf_empty(cb)) return false; + if (cb == NULL || source == NULL || cbuf_full(cb)) { + return false; + } - // increment - cb->lr++; - if (cb->lr == cb->cap) cb->lr = 0; + write_buffer(cb, cb->lr, source); - read_buffer(cb, cb->lr, dest); + // move lr back + if (cb->lr == 0) { + cb->lr = cb->cap - 1; // wrap to the end + } else { + cb->lr--; + } - return true; + return true; +} + + +bool cbuf_peek_back(const CircBuf *cb, void *dest) { + if (cb == NULL || dest == NULL || cbuf_empty(cb)) { + return false; + } + + circbuf_size_t index = cb->lr; + if (index == cb->cap - 1) { + index = 0; + } else { + index++; + } + + read_buffer(cb, index, dest); + return true; +} + + +bool cbuf_pop_back(CircBuf *cb, void *dest) +{ + if (cb == NULL || dest == NULL || cbuf_empty(cb)) { + return false; + } + + // increment + cb->lr++; + if (cb->lr == cb->cap) { + cb->lr = 0; + } + + read_buffer(cb, cb->lr, dest); + +#ifdef CIRCBUF_ZERO_FREE_SLOTS + memset(PV_OFFS(cb->buf, cb->elem_size, cb->lr), 0, cb->elem_size); +#endif + + return true; } -/** Clear a cbuf */ void cbuf_clear(CircBuf *cb) { - if (cb == NULL) return; + if (cb == NULL) { + return; + } + + cb->lr = cb->cap - 1; + cb->nw = 0; - cb->lr = cb->cap - 1; - cb->nw = 0; +#ifdef CIRCBUF_ZERO_FREE_SLOTS + memset(PV_OFFS(cb->buf, cb->elem_size, 0), 0, cb->cap * cb->elem_size); +#endif } diff --git a/circbuf/circbuf.h b/circbuf/circbuf.h index 4e73f59..bfc0d8f 100644 --- a/circbuf/circbuf.h +++ b/circbuf/circbuf.h @@ -1,9 +1,8 @@ /** * @file circbuf.h - * @author Ondřej Hruška, 2016 + * @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. * @@ -21,35 +20,39 @@ * MIT license */ -#pragma once +#ifndef CIRCBUF_H +#define CIRCBUF_H #include #include #include +// Enable to zero a freed slots after pop, useful for debugging +#define CIRCBUF_ZERO_FREE_SLOTS -typedef struct circbuf_struct CircBuf; +typedef uint32_t circbuf_size_t; -/** - * @brief Initialize a circular buffer. The buffer is malloc'd. - * @param capacity : buffer capacity - * @param elem_size : size of one element - * @return pointer to the buffer instance - */ -CircBuf *cbuf_create(size_t capacity, size_t elem_size); +/** Instance structure - public to allow static allocation, but consider the structure internal matter */ +struct circbuf_struct { + void *buf; + circbuf_size_t elem_size; + circbuf_size_t cap; + circbuf_size_t lr; // last read pos + circbuf_size_t nw; // next write pos +}; +typedef struct circbuf_struct CircBuf; /** - * @brief Destroy a buffer, freeing used memory. - * - * @attention - * If the buffer items have malloc'd members, you have - * to free them manually to avoid a memory leak. + * @brief Initialize a circular buffer * - * @param cb : 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_destroy(CircBuf *cb); +void cbuf_init(CircBuf *cb, void *buf, circbuf_size_t capacity, circbuf_size_t elem_size); /** Test for full buffer */ @@ -61,33 +64,61 @@ bool cbuf_empty(const CircBuf *cb); /** - * @brief Append a value to the buffer (FIFO) + * @brief Push to front * @param cb : buffer * @param source : pointer to a value (will be copied) * @return success */ -bool cbuf_append(CircBuf *cb, const void *source); +bool cbuf_push(CircBuf *cb, const void *source); /** - * @brief Push a value into the circbuf (LIFO). - * + * @brief Pop from front + * @param cb : buffer + * @param dest : read destination. If NULL, value is discarded. + * @return success + */ +bool cbuf_pop(CircBuf *cb, void *dest); + + +/** + * @brief Push to end * @param cb : buffer * @param source : pointer to a value (will be copied) * @return success */ -bool cbuf_push(CircBuf *cb, const void *source); +bool cbuf_push_back(CircBuf *cb, const void *source); /** - * @brief Read a value from the buffer, return susccess. - * + * @brief Pop from end * @param cb : buffer - * @param dest : read destionation. If NULL, value is discarded. + * @param dest : read destination. If NULL, value is discarded. * @return success */ -bool cbuf_pop(CircBuf *cb, void *dest); +bool cbuf_pop_back(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 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 Remove all data from buffer */ void cbuf_clear(CircBuf *cb); + + +#endif // CIRCBUF_H diff --git a/circbuf_deque/README.md b/circbuf_deque/README.md new file mode 100644 index 0000000..452ba52 --- /dev/null +++ b/circbuf_deque/README.md @@ -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. diff --git a/circbuf_deque/circbuf.c b/circbuf_deque/circbuf.c new file mode 100644 index 0000000..68a67cf --- /dev/null +++ b/circbuf_deque/circbuf.c @@ -0,0 +1,255 @@ +#include +#include +#include + +#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 +} diff --git a/circbuf_deque/circbuf.h b/circbuf_deque/circbuf.h new file mode 100644 index 0000000..edfa2ea --- /dev/null +++ b/circbuf_deque/circbuf.h @@ -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 +#include +#include + +// 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