11## The equeue library ##
22
3- The equeue library provides a composable event queue implementation
4- that acts as a drop in scheduler and event framework .
3+ The equeue library is designed as a simple but powerful library for scheduling
4+ events on composable event queues .
55
66``` c
77#include " equeue.h"
@@ -16,25 +16,185 @@ int main() {
1616 equeue_t queue;
1717 equeue_create(&queue, 32* EQUEUE_EVENT_SIZE);
1818
19- // events are simple callbacks
19+ // events can be simple callbacks
2020 equeue_call(&queue, print, "called immediately");
2121 equeue_call_in(&queue, 2000, print, "called in 2 seconds");
2222 equeue_call_every(&queue, 1000, print, "called every 1 seconds");
2323
24- // events are executed when dispatch is called
24+ // events are executed in equeue_dispatch
2525 equeue_dispatch(&queue, 3000);
2626
2727 print("called after 3 seconds");
2828
29- // dispatch can be called in an infinite loop
29+ equeue_destroy(&queue);
30+ }
31+ ```
32+
33+ The equeue library can be used as a normal event loop, or it can be
34+ backgrounded on a single hardware timer or even another event loop.
35+ The equeue library is both thread and irq safe, and provides functions
36+ for easily composing multiple queues.
37+
38+ The equeue library can act as a drop-in scheduler, provide synchronization
39+ between multiple threads, or just act as a mechanism for moving events
40+ out of interrupt contexts.
41+
42+ ## Documentation ##
43+
44+ Unless it is elaborated, the in-depth documentation of the specific functions
45+ can be found in [equeue.h](equeue.h).
46+
47+ The core of the equeue library is the `equeue_t` type which represents a
48+ single event queue, and the `equeue_dispath` function which runs the equeue,
49+ providing the context for executing events.
50+
51+ On top of this, `equeue_call`, `equeue_call_in`, and `equeue_call_every`
52+ provide an easy method of posting events to be executed in the context
53+ of the `equeue_dispatch` function.
54+
55+ ``` c
56+ #include "equeue.h"
57+ #include "game.h"
58+
59+ equeue_t queue;
60+ struct game game;
61+
62+ // button_isr may be in interrupt context
63+ void button_isr(void) {
64+ equeue_call(&queue, game_button_update, &game);
65+ }
66+
67+ // a simple user-interface framework
68+ int main() {
69+ equeue_create(&queue, 4096);
70+ game_create(&game);
71+
72+ // call game_screen_udpate at 60 Hz
73+ equeue_call_every(&queue, 1000/60, game_screen_update, &game);
74+
75+ // dispatch forever
76+ equeue_dispatch(&queue, -1);
77+ }
78+ ```
79+
80+ In addition to simple events, an event can be manually allocated with
81+ ` equeue_alloc ` and posted with ` equeue_post ` to allow passing an arbitrary
82+ amount of data to the execution of the event. This memory is allocated out
83+ of the equeue's buffer, and dynamic memory can be completely avoided.
84+
85+ The equeue allocator is designed to minimize jitter in interrupt contexts as
86+ well as avoid memory fragmentation on small devices. The allocator achieves
87+ both constant-runtime and zero-fragmentation for fixed-size events, however
88+ grows linearly as the quantity of different sized allocations increases.
89+
90+ ``` c
91+ #include " equeue.h"
92+
93+ equeue_t queue;
94+
95+ // arbitrary data can be moved to a different context
96+ int enet_callback (void * buffer, int size) {
97+ if (size > 512) {
98+ size = 512;
99+ }
100+
101+ void *event = equeue_alloc(&queue, 512);
102+ memcpy(event, buffer, size);
103+ equeue_post(&queue, event);
104+
105+ return size;
106+ }
107+ ```
108+
109+ Additionally, in-flight events can be cancelled with `equeue_cancel`. Events
110+ are given unique ids on post, allowing safe cancellation of expired events.
111+
112+ ``` c
113+ #include "equeue.h"
114+
115+ equeue_t queue;
116+ int sonar_value;
117+ int sonar_timeout_id;
118+
119+ void sonar_isr(int value) {
120+ equeue_cancel(&queue, sonar_timeout_id);
121+ sonar_value = value;
122+ }
123+
124+ void sonar_timeout(void *) {
125+ sonar_value = -1;
126+ }
127+
128+ void sonar_read(void) {
129+ sonar_timeout_id = equeue_call_in(&queue, 300, sonar_timeout, 0);
130+ sonar_start();
131+ }
132+ ```
133+
134+ From an architectural standpoint, event queues easily align with module
135+ boundaries, where internal state can be implicitly synchronized through
136+ event registration. Multiple modules can easily use event queues running
137+ in separate threads.
138+
139+ Alternatively, multiple event queues can be easily composed through the
140+ ` equeue_chain ` function, which allows multiple event queues to share the
141+ context of a single ` equeue_dispatch ` call.
142+
143+ ``` c
144+ #include " equeue.h"
145+
146+ // run a simultaneous localization and mapping loop in one queue
147+ struct slam {
148+ equeue_t queue;
149+ };
150+
151+ void slam_create (struct slam * s, equeue_t * target) {
152+ equeue_create(&s->queue, 4096);
153+ equeue_chain(&s->queue, target);
154+ equeue_call_every(&s->queue, 100, slam_filter);
155+ }
156+
157+ // run a sonar with it's own queue
158+ struct sonar {
159+ equeue_t equeue;
160+ struct slam * slam;
161+ };
162+
163+ void sonar_create(struct sonar * s, equeue_t * target) {
164+ equeue_create(&s->queue, 64);
165+ equeue_chain(&s->queue, target);
166+ equeue_call_in(&s->queue, 5, sonar_update, s);
167+ }
168+
169+ // although the above is perfectly synchronized, we can run these
170+ // modules on a single event queue
171+ int main() {
172+ equeue_t queue;
173+ equeue_create(&queue, 1024);
174+
175+ struct sonar s1, s2, s3;
176+ sonar_create(&s1, &queue);
177+ sonar_create(&s2, &queue);
178+ sonar_create(&s3, &queue);
179+
180+ struct slam slam;
181+ slam_create(&slam, &queue);
182+
183+ // dispatches events for all of the modules
30184 equeue_dispatch(&queue, -1);
31185}
32186```
33187
34- The equeue library can be used for a normal event loops, however it also
35- supports composition and multithreaded environments. More information on
36- the idea behind composable event loops
37- [here](https://gist.github.com/geky/4969d940f1bd5596bdc10e79093e2553).
188+ ## Platform ##
189+
190+ The equeue library has a minimal porting layer that is flexible depending
191+ on the requirements of the underlying platform. Platform specific declarations
192+ and more information can be found in the following files:
193+
194+ - [equeue_tick](equeue_tick.h) - millisecond counter
195+ - [equeue_mutex](equeue_mutex.h) - non-recursive mutex
196+ - [equeue_sema](equeue_sema.h) - binary semaphore
197+
38198
39199## Tests ##
40200
@@ -59,9 +219,3 @@ make prof | tee results.txt
59219cat results.txt | make prof
60220```
61221
62- ## Porting ##
63-
64- The events library requires a small porting layer:
65- - [ equeue_tick] ( equeue_tick.h ) - monotonic counter
66- - [ equeue_mutex] ( equeue_mutex.h ) - non-recursive mutex
67- - [ equeue_sema] ( equeue_sema.h ) - binary semaphore
0 commit comments