From 68033aba9710cbb5f8ddd41d08ad5f1d7b652015 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 9 Aug 2016 02:48:31 -0500 Subject: [PATCH 1/2] Updated documentation - core api in equeue.h - porting api in equeue_tick/mutex/sema.h - README documentation - internal code documentation --- README.md | 184 +++++++++++++++++++++++++++++++++++++++++++++---- equeue.c | 63 ++++++++++++----- equeue.h | 124 ++++++++++++++++++++++----------- equeue_mutex.h | 19 +++-- equeue_sema.h | 27 ++++++-- equeue_tick.h | 10 ++- 6 files changed, 342 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index cf52940..18bc12a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## The equeue library ## -The equeue library provides a composable event queue implementation -that acts as a drop in scheduler and event framework. +The equeue library is designed as a simple but powerful library for scheduling +events on composable event queues. ``` c #include "equeue.h" @@ -16,25 +16,185 @@ int main() { equeue_t queue; equeue_create(&queue, 32*EQUEUE_EVENT_SIZE); - // events are simple callbacks + // events can be simple callbacks equeue_call(&queue, print, "called immediately"); equeue_call_in(&queue, 2000, print, "called in 2 seconds"); equeue_call_every(&queue, 1000, print, "called every 1 seconds"); - // events are executed when dispatch is called + // events are executed in equeue_dispatch equeue_dispatch(&queue, 3000); print("called after 3 seconds"); - // dispatch can be called in an infinite loop + equeue_destroy(&queue); +} +``` + +The equeue library can be used as a normal event loop, or it can be +backgrounded on a single hardware timer or even another event loop. +The equeue library is both thread and irq safe, and provides functions +for easily composing multiple queues. + +The equeue library can act as a drop-in scheduler, provide synchronization +between multiple threads, or just act as a mechanism for moving events +out of interrupt contexts. + +## Documentation ## + +Unless it is elaborated, the in-depth documentation of the specific functions +can be found in [equeue.h](equeue.h). + +The core of the equeue library is the `equeue_t` type which represents a +single event queue, and the `equeue_dispath` function which runs the equeue, +providing the context for executing events. + +On top of this, `equeue_call`, `equeue_call_in`, and `equeue_call_every` +provide an easy method of posting events to be executed in the context +of the `equeue_dispatch` function. + +``` c +#include "equeue.h" +#include "game.h" + +equeue_t queue; +struct game game; + +// button_isr may be in interrupt context +void button_isr(void) { + equeue_call(&queue, game_button_update, &game); +} + +// a simple user-interface framework +int main() { + equeue_create(&queue, 4096); + game_create(&game); + + // call game_screen_udpate at 60 Hz + equeue_call_every(&queue, 1000/60, game_screen_update, &game); + + // dispatch forever + equeue_dispatch(&queue, -1); +} +``` + +In addition to simple events, an event can be manually allocated with +`equeue_alloc` and posted with `equeue_post` to allow passing an arbitrary +amount of data to the execution of the event. This memory is allocated out +of the equeue's buffer, and dynamic memory can be completely avoided. + +The equeue allocator is designed to minimize jitter in interrupt contexts as +well as avoid memory fragmentation on small devices. The allocator achieves +both constant-runtime and zero-fragmentation for fixed-size events, however +grows linearly as the quantity of different sized allocations increases. + +``` c +#include "equeue.h" + +equeue_t queue; + +// arbitrary data can be moved to a different context +int enet_callback(void *buffer, int size) { + if (size > 512) { + size = 512; + } + + void *event = equeue_alloc(&queue, 512); + memcpy(event, buffer, size); + equeue_post(&queue, event); + + return size; +} +``` + +Additionally, in-flight events can be cancelled with `equeue_cancel`. Events +are given unique ids on post, allowing safe cancellation of expired events. + +``` c +#include "equeue.h" + +equeue_t queue; +int sonar_value; +int sonar_timeout_id; + +void sonar_isr(int value) { + equeue_cancel(&queue, sonar_timeout_id); + sonar_value = value; +} + +void sonar_timeout(void *) { + sonar_value = -1; +} + +void sonar_read(void) { + sonar_timeout_id = equeue_call_in(&queue, 300, sonar_timeout, 0); + sonar_start(); +} +``` + +From an architectural standpoint, event queues easily align with module +boundaries, where internal state can be implicitly synchronized through +event registration. Multiple modules can easily use event queues running +in separate threads. + +Alternatively, multiple event queues can be easily composed through the +`equeue_chain` function, which allows multiple event queues to share the +context of a single `equeue_dispatch` call. + +``` c +#include "equeue.h" + +// run a simultaneous localization and mapping loop in one queue +struct slam { + equeue_t queue; +}; + +void slam_create(struct slam *s, equeue_t *target) { + equeue_create(&s->queue, 4096); + equeue_chain(&s->queue, target); + equeue_call_every(&s->queue, 100, slam_filter); +} + +// run a sonar with it's own queue +struct sonar { + equeue_t equeue; + struct slam *slam; +}; + +void sonar_create(struct sonar *s, equeue_t *target) { + equeue_create(&s->queue, 64); + equeue_chain(&s->queue, target); + equeue_call_in(&s->queue, 5, sonar_update, s); +} + +// although the above is perfectly synchronized, we can run these +// modules on a single event queue +int main() { + equeue_t queue; + equeue_create(&queue, 1024); + + struct sonar s1, s2, s3; + sonar_create(&s1, &queue); + sonar_create(&s2, &queue); + sonar_create(&s3, &queue); + + struct slam slam; + slam_create(&slam, &queue); + + // dispatches events for all of the modules equeue_dispatch(&queue, -1); } ``` -The equeue library can be used for a normal event loops, however it also -supports composition and multithreaded environments. More information on -the idea behind composable event loops -[here](https://gist.github.com/geky/4969d940f1bd5596bdc10e79093e2553). +## Platform ## + +The equeue library has a minimal porting layer that is flexible depending +on the requirements of the underlying platform. Platform specific declarations +and more information can be found in the following files: + +- [equeue_tick](equeue_tick.h) - millisecond counter +- [equeue_mutex](equeue_mutex.h) - non-recursive mutex +- [equeue_sema](equeue_sema.h) - binary semaphore + ## Tests ## @@ -59,9 +219,3 @@ make prof | tee results.txt cat results.txt | make prof ``` -## Porting ## - -The events library requires a small porting layer: -- [equeue_tick](equeue_tick.h) - monotonic counter -- [equeue_mutex](equeue_mutex.h) - non-recursive mutex -- [equeue_sema](equeue_sema.h) - binary semaphore diff --git a/equeue.c b/equeue.c index 161a627..ab0e32e 100644 --- a/equeue.c +++ b/equeue.c @@ -10,7 +10,31 @@ #include +// calculate the relative-difference between absolute times while +// correctly handling overflow conditions +static inline int equeue_tickdiff(unsigned a, unsigned b) { + return (int)(a - b); +} + +// calculate the relative-difference between absolute times, but +// also clamp to zero, resulting in only non-zero values. +static inline int equeue_clampdiff(unsigned a, unsigned b) { + int diff = equeue_tickdiff(a, b); + return ~(diff >> (8*sizeof(int)-1)) & diff; +} + +// Increment the unique id in an event, hiding the event from cancel +static inline void equeue_incid(equeue_t *q, struct equeue_event *e) { + e->id += 1; + if (e->id >> (8*sizeof(int)-1 - q->npw2)) { + e->id = 1; + } +} + + +// equeue lifetime management int equeue_create(equeue_t *q, size_t size) { + // dynamically allocate the specified buffer void *buffer = malloc(size); if (!buffer) { return -1; @@ -22,6 +46,7 @@ int equeue_create(equeue_t *q, size_t size) { } int equeue_create_inplace(equeue_t *q, size_t size, void *buffer) { + // setup queue around provided buffer q->buffer = buffer; q->allocated = 0; @@ -43,6 +68,7 @@ int equeue_create_inplace(equeue_t *q, size_t size, void *buffer) { q->background.update = 0; q->background.timer = 0; + // initialize platform resources int err; err = equeue_sema_create(&q->eventsema); if (err < 0) { @@ -72,23 +98,28 @@ void equeue_destroy(equeue_t *q) { } } + // notify background timer if (q->background.update) { q->background.update(q->background.timer, -1); } + // clean up platform resources + memory equeue_mutex_destroy(&q->memlock); equeue_mutex_destroy(&q->queuelock); equeue_sema_destroy(&q->eventsema); free(q->allocated); } + // equeue chunk allocation functions static struct equeue_event *equeue_mem_alloc(equeue_t *q, size_t size) { + // add event overhead size += sizeof(struct equeue_event); size = (size + sizeof(void*)-1) & ~(sizeof(void*)-1); equeue_mutex_lock(&q->memlock); + // check if a good chunk is available for (struct equeue_event **p = &q->chunks; *p; p = &(*p)->next) { if ((*p)->size >= size) { struct equeue_event *e = *p; @@ -104,6 +135,7 @@ static struct equeue_event *equeue_mem_alloc(equeue_t *q, size_t size) { } } + // otherwise allocate a new chunk out of the slab if (q->slab.size >= size) { struct equeue_event *e = (struct equeue_event *)q->slab.data; q->slab.data += size; @@ -122,6 +154,7 @@ static struct equeue_event *equeue_mem_alloc(equeue_t *q, size_t size) { static void equeue_mem_dealloc(equeue_t *q, struct equeue_event *e) { equeue_mutex_lock(&q->memlock); + // stick chunk into list of chunks struct equeue_event **p = &q->chunks; while (*p && (*p)->size < e->size) { p = &(*p)->next; @@ -139,7 +172,6 @@ static void equeue_mem_dealloc(equeue_t *q, struct equeue_event *e) { equeue_mutex_unlock(&q->memlock); } -// equeue allocation functions void *equeue_alloc(equeue_t *q, size_t size) { struct equeue_event *e = equeue_mem_alloc(q, size); if (!e) { @@ -163,35 +195,23 @@ void equeue_dealloc(equeue_t *q, void *p) { equeue_mem_dealloc(q, e); } -// equeue scheduling functions -static inline int equeue_tickdiff(unsigned a, unsigned b) { - return (int)(a - b); -} - -static inline int equeue_clampdiff(unsigned a, unsigned b) { - int diff = equeue_tickdiff(a, b); - return ~(diff >> (8*sizeof(int)-1)) & diff; -} - -static inline void equeue_incid(equeue_t *q, struct equeue_event *e) { - e->id += 1; - if (e->id >> (8*sizeof(int)-1 - q->npw2)) { - e->id = 1; - } -} +// equeue scheduling functions static int equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned ms) { + // setup event and hash local id with buffer offset for unique id int id = (e->id << q->npw2) | ((unsigned char *)e - q->buffer); e->target = equeue_tick() + ms; e->generation = q->generation; equeue_mutex_lock(&q->queuelock); + // find the event slot struct equeue_event **p = &q->queue; while (*p && equeue_tickdiff((*p)->target, e->target) < 0) { p = &(*p)->next; } + // insert at head in slot if (*p && (*p)->target == e->target) { e->next = (*p)->next; if (e->next) { @@ -212,6 +232,7 @@ static int equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned ms) { *p = e; e->ref = p; + // notify background timer if ((q->background.update && q->background.active) && (q->queue == e && !e->sibling)) { q->background.update(q->background.timer, ms); @@ -223,6 +244,7 @@ static int equeue_enqueue(equeue_t *q, struct equeue_event *e, unsigned ms) { } static struct equeue_event *equeue_unqueue(equeue_t *q, int id) { + // decode event from unique id and check that the local id matches struct equeue_event *e = (struct equeue_event *) &q->buffer[id & ((1 << q->npw2)-1)]; @@ -232,6 +254,7 @@ static struct equeue_event *equeue_unqueue(equeue_t *q, int id) { return 0; } + // clear the event and check if already in-flight e->cb = 0; e->period = -1; @@ -241,6 +264,7 @@ static struct equeue_event *equeue_unqueue(equeue_t *q, int id) { return 0; } + // disentangle from queue if (e->sibling) { e->sibling->next = e->next; if (e->sibling->next) { @@ -265,6 +289,7 @@ static struct equeue_event *equeue_unqueue(equeue_t *q, int id) { static struct equeue_event *equeue_dequeue(equeue_t *q, unsigned target) { equeue_mutex_lock(&q->queuelock); + // find all expired events and mark a new generation q->generation += 1; if (equeue_tickdiff(q->tick, target) <= 0) { q->tick = target; @@ -285,6 +310,7 @@ static struct equeue_event *equeue_dequeue(equeue_t *q, unsigned target) { equeue_mutex_unlock(&q->queuelock); + // reverse and flatten each slot to match insertion order struct equeue_event **tail = &head; struct equeue_event *ess = head; while (ess) { @@ -407,6 +433,7 @@ void equeue_dispatch(equeue_t *q, int ms) { } } + // event functions void equeue_event_delay(void *p, int ms) { struct equeue_event *e = (struct equeue_event*)p - 1; @@ -423,6 +450,7 @@ void equeue_event_dtor(void *p, void (*dtor)(void *)) { e->dtor = dtor; } + // simple callbacks struct ecallback { void (*cb)(void*); @@ -470,6 +498,7 @@ int equeue_call_every(equeue_t *q, int ms, void (*cb)(void*), void *data) { return equeue_post(q, ecallback_dispatch, e); } + // backgrounding void equeue_background(equeue_t *q, void (*update)(void *timer, int ms), void *timer) { diff --git a/equeue.h b/equeue.h index f3aa41b..1a9d9c7 100644 --- a/equeue.h +++ b/equeue.h @@ -11,7 +11,7 @@ extern "C" { #endif -// System specific files +// Platform specific files #include "equeue_tick.h" #include "equeue_mutex.h" #include "equeue_sema.h" @@ -20,11 +20,11 @@ extern "C" { #include -// Definition of the minimum size of an event -// This size fits the events created in the event_call set of functions. +// The minimum size of an event +// This size is garunteed to fit events created by event_call #define EQUEUE_EVENT_SIZE (sizeof(struct equeue_event) + 2*sizeof(void*)) -// Event/queue structures +// Internal event structure struct equeue_event { unsigned size; uint8_t id; @@ -42,6 +42,7 @@ struct equeue_event { // data follows }; +// Event queue structure typedef struct equeue { struct equeue_event *queue; unsigned tick; @@ -70,84 +71,121 @@ typedef struct equeue { } equeue_t; -// Queue operations +// Queue lifetime operations // -// Creation results in negative value on failure. +// Creates and destroys an event queue. The event queue either allocates a +// buffer of the specified size with malloc or uses a user provided buffer +// if constructed with equeue_create_inplace. +// +// If the event queue creation fails, equeue_create returns a negative, +// platform-specific error code. int equeue_create(equeue_t *queue, size_t size); int equeue_create_inplace(equeue_t *queue, size_t size, void *buffer); void equeue_destroy(equeue_t *queue); // Dispatch events // -// Executes any callbacks enqueued for the specified time in milliseconds, -// or forever if ms is negative +// Executes events until the specified milliseconds have passed. If ms is +// negative, equeue_dispatch will dispatch events indefinitely or until +// equeue_break is called on this queue. +// +// When called with a finite timeout, the equeue_dispatch function is garunteed +// to terminate. When called with a timeout of 0, the equeue_dispatch does not +// wait and is irq safe. void equeue_dispatch(equeue_t *queue, int ms); -// Break a running event loop +// Break out of a running event loop // -// Shuts down an unbounded event loop. Already pending events may finish -// executing, but the queue will not continue looping indefinitely. +// Forces the specified event queue's dispatch loop to terminate. Pending +// events may finish executing, but no new events will be executed. void equeue_break(equeue_t *queue); // Simple event calls // -// Passed callback will be executed in the associated equeue's -// dispatch call with the data pointer passed unmodified +// The specified callback will be executed in the context of the event queue's +// dispatch loop. When the callback is executed depends on the call function. // // equeue_call - Immediately post an event to the queue // equeue_call_in - Post an event after a specified time in milliseconds -// equeue_call_every - Post an event periodically in milliseconds +// equeue_call_every - Post an event periodically every milliseconds // -// These calls will result in 0 if no memory is available, otherwise they -// will result in a unique identifier that can be passed to equeue_cancel. +// All equeue_call functions are irq safe and can act as a mechanism for +// moving events out of irq contexts. +// +// The return value is a unique id that represents the posted event and can +// be passed to equeue_cancel. If there is not enough memory to allocate the +// event, equeue_call returns an id of 0. int equeue_call(equeue_t *queue, void (*cb)(void *), void *data); int equeue_call_in(equeue_t *queue, int ms, void (*cb)(void *), void *data); int equeue_call_every(equeue_t *queue, int ms, void (*cb)(void *), void *data); -// Events with queue handled blocks of memory +// Allocate memory for events +// +// The equeue_alloc function allocates an event that can be manually dispatched +// with equeue_post. The equeue_dealloc function may be used to free an event +// that has not been posted. Once posted, an event's memory is managed by the +// event queue and should not be deallocated. // -// Argument to equeue_post must point to a result of a equeue_alloc call -// and the associated memory is automatically freed after the event -// is dispatched. +// Both equeue_alloc and equeue_dealloc are irq safe. // -// equeue_alloc will result in null if no memory is available -// or the requested size is less than the size passed to equeue_create. +// The equeue allocator is designed to minimize jitter in interrupt contexts as +// well as avoid memory fragmentation on small devices. The allocator achieves +// both constant-runtime and zero-fragmentation for fixed-size events, however +// grows linearly as the quantity of different sized allocations increases. +// +// The equeue_alloc function returns a pointer to the event's allocated memory +// and acts as a handle to the underlying event. If there is not enough memory +// to allocate the event, equeue_alloc returns null. void *equeue_alloc(equeue_t *queue, size_t size); void equeue_dealloc(equeue_t *queue, void *event); // Configure an allocated event -// -// equeue_event_delay - Millisecond delay before posting an event -// equeue_event_period - Millisecond period to repeatedly post an event +// +// equeue_event_delay - Millisecond delay before dispatching an event +// equeue_event_period - Millisecond period for repeating dispatching an event // equeue_event_dtor - Destructor to run when the event is deallocated void equeue_event_delay(void *event, int ms); void equeue_event_period(void *event, int ms); void equeue_event_dtor(void *event, void (*dtor)(void *)); -// Post an allocted event to the event queue +// Post an event onto the event queue +// +// The equeue_post function takes a callback and a pointer to an event +// allocated by equeue_alloc. The specified callback will be executed in the +// context of the event queue's dispatch loop with the allocated event +// as its argument. // -// Argument to equeue_post must point to a result of a equeue_alloc call -// and the associated memory is automatically freed after the event -// is dispatched. +// The equeue_post function is irq safe and can act as a mechanism for +// moving events out of irq contexts. // -// This call results in an unique identifier that can be passed to -// equeue_cancel. +// The return value is a unique id that represents the posted event and can +// be passed to equeue_cancel. int equeue_post(equeue_t *queue, void (*cb)(void *), void *event); -// Cancel events that are in flight +// Cancel an in-flight event // -// Every equeue_call function returns a non-negative identifier on success -// that can be used to cancel an in-flight event. If the event has already -// been dispatched or does not exist, no error occurs. Note, this can not -// stop a currently executing event -void equeue_cancel(equeue_t *queue, int event); +// Attempts to cancel an event referenced by the unique id returned from +// equeue_call or equeue_post. It is safe to call equeue_cancel after an event +// has already been dispatched. +// +// The equeue_cancel function is irq safe. +// +// If called while the event queue's dispatch loop is active, equeue_cancel +// does not garuntee that the event will not not execute after it returns as +// the event may have already begun executing. +void equeue_cancel(equeue_t *queue, int id); // Background an event queue onto a single-shot timer // // The provided update function will be called to indicate when the queue // should be dispatched. A negative timeout will be passed to the update -// function when the timer is no longer needed. A null update function -// will disable the existing timer. +// function when the timer is no longer needed. +// +// Passing a null update function disables the existing timer. +// +// The equeue_background function allows an event queue to take advantage +// of hardware timers or even other event loops, allowing an event queue to +// be effectively backgrounded. void equeue_background(equeue_t *queue, void (*update)(void *timer, int ms), void *timer); @@ -155,8 +193,12 @@ void equeue_background(equeue_t *queue, // // After chaining a queue to a target, calling equeue_dispatch on the // target queue will also dispatch events from this queue. The queues -// will use their own buffers and events are handled independently. -// A null queue as the target will unchain this queue. +// use their own buffers and events must be managed independently. +// +// Passing a null queue as the target will unchain the existing queue. +// +// The equeue_chain function allows multiple equeues to be composed, sharing +// the context of a dispatch loop while still being managed independtly. void equeue_chain(equeue_t *queue, equeue_t *target); diff --git a/equeue_mutex.h b/equeue_mutex.h index 6064baa..cf2e893 100644 --- a/equeue_mutex.h +++ b/equeue_mutex.h @@ -12,11 +12,13 @@ extern "C" { #endif -// Mutex type +// Platform mutex type // -// If this type is safe in interrupt contexts, then -// the associated event queue will also be safe in -// interrupt contexts. +// The equeue library requires at minimum a non-recursive mutex that is +// safe in interrupt contexts. The mutex section is help for a bounded +// amount of time, so simply disabling interrupts is acceptable +// +// If irq safety is not required, a regular blocking mutex can be used. #if defined(__unix__) #include typedef pthread_mutex_t equeue_mutex_t; @@ -25,7 +27,14 @@ typedef unsigned equeue_mutex_t; #endif -// Mutex operations +// Platform mutex operations +// +// The equeue_mutex_create and equeue_mutex_destroy manage the lifetime +// of the mutex. On error, equeue_mutex_create should return a negative +// error code. +// +// The equeue_mutex_lock and equeue_mutex_unlock lock and unlock the +// underlying mutex. int equeue_mutex_create(equeue_mutex_t *mutex); void equeue_mutex_destroy(equeue_mutex_t *mutex); void equeue_mutex_lock(equeue_mutex_t *mutex); diff --git a/equeue_sema.h b/equeue_sema.h index 80c0b60..134a242 100644 --- a/equeue_sema.h +++ b/equeue_sema.h @@ -14,10 +14,17 @@ extern "C" { #include -// Semaphore type +// Platform semaphore type // -// Optimal implementation is a binary semaphore, -// however a regular semaphore is sufficient. +// The equeue library requires a binary semaphore type that can be safely +// signaled from interrupt contexts and from inside a equeue_mutex section. +// +// The equeue_signal_wait is relied upon by the equeue library to sleep the +// processor between events. Spurious wakeups have no negative-effects. +// +// A counting semaphore will also work, however may cause the event queue +// dispatch loop to run unnecessarily. For that matter, equeue_signal_wait +// may even be implemented as a single return statement. #if defined(__unix__) #include typedef sem_t equeue_sema_t; @@ -30,7 +37,19 @@ typedef bool equeue_sema_t; #endif -// Semaphore operations +// Platform semaphore operations +// +// The equeue_sema_create and equeue_sema_destroy manage the lifetime +// of the semaphore. On error, equeue_sema_create should return a negative +// error code. +// +// The equeue_sema_signal marks a semaphore as signalled such that the next +// equeue_sema_wait will return true. +// +// The equeue_sema_wait waits for a semaphore to be signalled or returns +// immediately if equeue_sema_signal had been called since the last +// equeue_sema_wait. The equeue_sema_wait returns true if it detected that +// equeue_sema_signal had been called. int equeue_sema_create(equeue_sema_t *sema); void equeue_sema_destroy(equeue_sema_t *sema); void equeue_sema_signal(equeue_sema_t *sema); diff --git a/equeue_tick.h b/equeue_tick.h index f28705d..914e916 100644 --- a/equeue_tick.h +++ b/equeue_tick.h @@ -12,10 +12,14 @@ extern "C" { #endif -// Monotonic tick +// Platform millisecond counter // -// Returns a tick that is incremented every millisecond, -// must intentionally overflow to 0 after 2^32-1 +// Return a tick that represents the number of milliseconds that have passed +// since an arbitrary point in time. The granularity does not need to be at +// the millisecond level, however the accuracy of the equeue library is +// limited by the accuracy of this tick. +// +// Must intentionally overflow to 0 after 2^32-1 unsigned equeue_tick(void); From fe021da65cedc6e11805c01202b25e53f44e502b Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 9 Aug 2016 19:31:56 -0500 Subject: [PATCH 2/2] Updated more documentation - revisions to the README - misspellings in equeue.h --- README.md | 50 ++++++++++++++++++++++---------------------------- equeue.h | 12 ++++++------ 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 18bc12a..e54a682 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,12 @@ ## The equeue library ## The equeue library is designed as a simple but powerful library for scheduling -events on composable event queues. +events on composable queues. ``` c #include "equeue.h" #include -void print(void *s) { - puts((const char *)s); -} - int main() { // creates a queue with space for 32 basic events equeue_t queue; @@ -31,9 +27,9 @@ int main() { ``` The equeue library can be used as a normal event loop, or it can be -backgrounded on a single hardware timer or even another event loop. -The equeue library is both thread and irq safe, and provides functions -for easily composing multiple queues. +backgrounded on a single hardware timer or even another event loop. It +is both thread and irq safe, and provides functions for easily composing +multiple queues. The equeue library can act as a drop-in scheduler, provide synchronization between multiple threads, or just act as a mechanism for moving events @@ -41,16 +37,16 @@ out of interrupt contexts. ## Documentation ## -Unless it is elaborated, the in-depth documentation of the specific functions -can be found in [equeue.h](equeue.h). +The in-depth documentation on specific functions can be found in +[equeue.h](equeue.h). The core of the equeue library is the `equeue_t` type which represents a -single event queue, and the `equeue_dispath` function which runs the equeue, +single event queue, and the `equeue_dispatch` function which runs the equeue, providing the context for executing events. On top of this, `equeue_call`, `equeue_call_in`, and `equeue_call_every` -provide an easy method of posting events to be executed in the context -of the `equeue_dispatch` function. +provide easy methods for posting events to execute in the context of the +`equeue_dispatch` function. ``` c #include "equeue.h" @@ -77,15 +73,15 @@ int main() { } ``` -In addition to simple events, an event can be manually allocated with +In addition to simple callbacks, an event can be manually allocated with `equeue_alloc` and posted with `equeue_post` to allow passing an arbitrary -amount of data to the execution of the event. This memory is allocated out +amount of context to the execution of the event. This memory is allocated out of the equeue's buffer, and dynamic memory can be completely avoided. The equeue allocator is designed to minimize jitter in interrupt contexts as well as avoid memory fragmentation on small devices. The allocator achieves both constant-runtime and zero-fragmentation for fixed-size events, however -grows linearly as the quantity of different sized allocations increases. +grows linearly as the quantity of differently-sized allocations increases. ``` c #include "equeue.h" @@ -93,14 +89,14 @@ grows linearly as the quantity of different sized allocations increases. equeue_t queue; // arbitrary data can be moved to a different context -int enet_callback(void *buffer, int size) { +int enet_consume(void *buffer, int size) { if (size > 512) { size = 512; } - void *event = equeue_alloc(&queue, 512); - memcpy(event, buffer, size); - equeue_post(&queue, event); + void *data = equeue_alloc(&queue, 512); + memcpy(data, buffer, size); + equeue_post(&queue, handle_data_elsewhere, data); return size; } @@ -133,12 +129,11 @@ void sonar_read(void) { From an architectural standpoint, event queues easily align with module boundaries, where internal state can be implicitly synchronized through -event registration. Multiple modules can easily use event queues running -in separate threads. +event dispatch. -Alternatively, multiple event queues can be easily composed through the -`equeue_chain` function, which allows multiple event queues to share the -context of a single `equeue_dispatch` call. +On platforms where multiple threads are unavailable, multiple modules +can use independent event queues and still be composed through the +`equeue_chain` function. ``` c #include "equeue.h" @@ -166,8 +161,7 @@ void sonar_create(struct sonar *s, equeue_t *target) { equeue_call_in(&s->queue, 5, sonar_update, s); } -// although the above is perfectly synchronized, we can run these -// modules on a single event queue +// all of the above queues can be combined into a single thread of execution int main() { equeue_t queue; equeue_create(&queue, 1024); @@ -180,7 +174,7 @@ int main() { struct slam slam; slam_create(&slam, &queue); - // dispatches events for all of the modules + // dispatches events from all of the modules equeue_dispatch(&queue, -1); } ``` diff --git a/equeue.h b/equeue.h index 1a9d9c7..4834643 100644 --- a/equeue.h +++ b/equeue.h @@ -21,7 +21,7 @@ extern "C" { // The minimum size of an event -// This size is garunteed to fit events created by event_call +// This size is guaranteed to fit events created by event_call #define EQUEUE_EVENT_SIZE (sizeof(struct equeue_event) + 2*sizeof(void*)) // Internal event structure @@ -89,9 +89,9 @@ void equeue_destroy(equeue_t *queue); // negative, equeue_dispatch will dispatch events indefinitely or until // equeue_break is called on this queue. // -// When called with a finite timeout, the equeue_dispatch function is garunteed -// to terminate. When called with a timeout of 0, the equeue_dispatch does not -// wait and is irq safe. +// When called with a finite timeout, the equeue_dispatch function is +// guaranteed to terminate. When called with a timeout of 0, the +// equeue_dispatch does not wait and is irq safe. void equeue_dispatch(equeue_t *queue, int ms); // Break out of a running event loop @@ -171,7 +171,7 @@ int equeue_post(equeue_t *queue, void (*cb)(void *), void *event); // The equeue_cancel function is irq safe. // // If called while the event queue's dispatch loop is active, equeue_cancel -// does not garuntee that the event will not not execute after it returns as +// does not guarantee that the event will not not execute after it returns as // the event may have already begun executing. void equeue_cancel(equeue_t *queue, int id); @@ -198,7 +198,7 @@ void equeue_background(equeue_t *queue, // Passing a null queue as the target will unchain the existing queue. // // The equeue_chain function allows multiple equeues to be composed, sharing -// the context of a dispatch loop while still being managed independtly. +// the context of a dispatch loop while still being managed independently. void equeue_chain(equeue_t *queue, equeue_t *target);