diff --git a/app/meson.build b/app/meson.build index 02d24a34..3410c2d3 100644 --- a/app/meson.build +++ b/app/meson.build @@ -150,6 +150,9 @@ tests = [ 'tests/test_device_msg_deserialize.c', 'src/device_msg.c' ]], + ['test_queue', [ + 'tests/test_queue.c', + ]], ['test_strutil', [ 'tests/test_strutil.c', 'src/str_util.c' diff --git a/app/src/queue.h b/app/src/queue.h new file mode 100644 index 00000000..294823da --- /dev/null +++ b/app/src/queue.h @@ -0,0 +1,74 @@ +// generic intrusive FIFO queue +#ifndef QUEUE_H +#define QUEUE_H + +#include +#include + +// To define a queue type of "struct foo": +// struct queue_foo QUEUE(struct foo); +#define QUEUE(TYPE) { \ + TYPE *first; \ + TYPE *last; \ +} + +#define queue_init(PQ) \ + (void) ((PQ)->first = NULL) + +#define queue_is_empty(PQ) \ + !(PQ)->first + +// NEXTFIELD is the field in the ITEM type used for intrusive linked-list +// +// For example: +// struct foo { +// int value; +// struct foo *next; +// }; +// +// // define the type "struct my_queue" +// struct my_queue QUEUE(struct foo); +// +// struct my_queue queue; +// queue_init(&queue); +// +// struct foo v1 = { .value = 42 }; +// struct foo v2 = { .value = 27 }; +// +// queue_push(&queue, next, v1); +// queue_push(&queue, next, v2); +// +// struct foo *foo; +// queue_take(&queue, next, &foo); +// assert(foo->value == 42); +// queue_take(&queue, next, &foo); +// assert(foo->value == 27); +// assert(queue_is_empty(&queue)); +// + +// push a new item into the queue +#define queue_push(PQ, NEXTFIELD, ITEM) \ + (void) ({ \ + (ITEM)->NEXTFIELD = NULL; \ + if (queue_is_empty(PQ)) { \ + (PQ)->first = (PQ)->last = (ITEM); \ + } else { \ + (PQ)->last->NEXTFIELD = (ITEM); \ + (PQ)->last = (ITEM); \ + } \ + }) + +// take the next item and remove it from the queue (the queue must not be empty) +// the result is stored in *(PITEM) +// (without typeof(), we could not store a local variable having the correct +// type so that we can "return" it) +#define queue_take(PQ, NEXTFIELD, PITEM) \ + (void) ({ \ + SDL_assert(!queue_is_empty(PQ)); \ + *(PITEM) = (PQ)->first; \ + (PQ)->first = (PQ)->first->NEXTFIELD; \ + }) + // no need to update (PQ)->last if the queue is left empty: + // (PQ)->last is undefined if !(PQ)->first anyway + +#endif diff --git a/app/tests/test_queue.c b/app/tests/test_queue.c new file mode 100644 index 00000000..bcbced2b --- /dev/null +++ b/app/tests/test_queue.c @@ -0,0 +1,38 @@ +#include + +#include + +struct foo { + int value; + struct foo *next; +}; + +static void test_queue(void) { + struct my_queue QUEUE(struct foo) queue; + queue_init(&queue); + + assert(queue_is_empty(&queue)); + + struct foo v1 = { .value = 42 }; + struct foo v2 = { .value = 27 }; + + queue_push(&queue, next, &v1); + queue_push(&queue, next, &v2); + + struct foo *foo; + + assert(!queue_is_empty(&queue)); + queue_take(&queue, next, &foo); + assert(foo->value == 42); + + assert(!queue_is_empty(&queue)); + queue_take(&queue, next, &foo); + assert(foo->value == 27); + + assert(queue_is_empty(&queue)); +} + +int main(void) { + test_queue(); + return 0; +}