{"id":13812993,"url":"https://github.com/andsmedeiros/uevloop","last_synced_at":"2025-04-22T17:04:14.462Z","repository":{"id":38028523,"uuid":"232035365","full_name":"andsmedeiros/uevloop","owner":"andsmedeiros","description":"A fast and lightweight event loop for embedded platforms.","archived":false,"fork":false,"pushed_at":"2022-06-16T17:02:41.000Z","size":6544,"stargazers_count":102,"open_issues_count":1,"forks_count":21,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-29T17:02:01.278Z","etag":null,"topics":["async","embedded","events","signals"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andsmedeiros.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-06T06:07:42.000Z","updated_at":"2025-02-25T10:05:41.000Z","dependencies_parsed_at":"2022-09-13T07:21:50.484Z","dependency_job_id":null,"html_url":"https://github.com/andsmedeiros/uevloop","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andsmedeiros%2Fuevloop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andsmedeiros%2Fuevloop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andsmedeiros%2Fuevloop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andsmedeiros%2Fuevloop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andsmedeiros","download_url":"https://codeload.github.com/andsmedeiros/uevloop/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250285650,"owners_count":21405296,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","embedded","events","signals"],"created_at":"2024-08-04T04:00:59.407Z","updated_at":"2025-04-22T17:04:14.436Z","avatar_url":"https://github.com/andsmedeiros.png","language":"C","funding_links":[],"categories":["OS","programming framework"],"sub_categories":["Event based scheduler","event"],"readme":"# µEvLoop ![C/C++ CI](https://github.com/andsmedeiros/uevloop/workflows/C/C++%20CI/badge.svg?event=push)\n\nA fast and lightweight event loop aimed at embedded platforms in C99.\n\n\u003c!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 --\u003e\n\n- [About](#about)\n\t- [*DISCLAIMER*](#disclaimer)\n- [Highlights](#highlights)\n- [API documentation](#api-documentation)\n- [Testing](#testing)\n\t- [Test coverage](#test-coverage)\n- [Core data structures](#core-data-structures)\n\t- [Closures](#closures)\n\t\t- [Basic closure usage](#basic-closure-usage)\n\t\t- [A word on (void \\*)](#a-word-on-void-)\n\t- [Circular queues](#circular-queues)\n\t\t- [Basic circular queue usage](#basic-circular-queue-usage)\n\t- [Object pools](#object-pools)\n\t\t- [Basic object pool usage](#basic-object-pool-usage)\n\t- [Linked lists](#linked-lists)\n\t\t- [Basic linked list usage](#basic-linked-list-usage)\n- [Containers](#containers)\n\t- [System pools](#system-pools)\n\t\t- [System pools usage](#system-pools-usage)\n\t- [System queues](#system-queues)\n\t\t- [System queues usage](#system-queues-usage)\n\t- [Application](#application)\n\t\t- [Application registry](#application-registry)\n- [Core components](#core-components)\n\t- [Scheduler](#scheduler)\n\t\t- [Basic scheduler initialisation](#basic-scheduler-initialisation)\n\t\t- [Scheduler operation](#scheduler-operation)\n\t\t- [Timer events](#timer-events)\n\t\t- [Scheduler time resolution](#scheduler-time-resolution)\n\t- [Event loop](#event-loop)\n\t\t- [Basic event loop initialisation](#basic-event-loop-initialisation)\n\t\t- [Event loop usage](#event-loop-usage)\n\t\t- [Observers](#observers)\n\t- [Signal](#signal)\n\t\t- [Signals and relay initialisation](#signals-and-relay-initialisation)\n\t\t- [Signal operation](#signal-operation)\n- [Appendix A: Promises](#appendix-a-promises)\n\t- [Promise stores](#promise-stores)\n\t\t- [Promise store creation](#promise-store-creation)\n\t- [Promises and segments](#promises-and-segments)\n\t\t- [Promise creation](#promise-creation)\n\t\t- [Promise settling](#promise-settling)\n\t\t- [Segments](#segments)\n\t\t- [Segment chains and promise resettling](#segment-chains-and-promise-resettling)\n\t\t- [Nested promises](#nested-promises)\n\t\t- [Promise destroying and promise helpers](#promise-destroying-and-promise-helpers)\n- [Appendix B: Modules](#appendix-b-modules)\n\t- [Module creation](#module-creation)\n\t- [Module registration](#module-registration)\n\t- [Dependency Injection](#dependency-injection)\n\t\t- [Parametrised injection](#parametrised-injection)\n\t\t- [Ad-hoc injection](#ad-hoc-injection)\n- [Appendix C: Useful goodies](#appendix-c-useful-goodies)\n\t- [Iterators](#iterators)\n\t\t- [Array iterators](#array-iterators)\n\t\t- [Linked list iterators](#linked-list-iterators)\n\t\t- [Iterator operation](#iterator-operation)\n\t\t- [Iteration helpers](#iteration-helpers)\n\t\t- [Custom iterators](#custom-iterators)\n\t- [Conditionals](#conditionals)\n\t- [Pipelines](#pipelines)\n\t- [Functional helpers](#functional-helpers)\n\t- [Automatic pools and automatic pointers](#automatic-pools-and-automatic-pointers)\n\t\t- [Automatic pool creation](#automatic-pool-creation)\n\t\t- [Automatic pool operation and automatic pointer destruction](#automatic-pool-operation-and-automatic-pointer-destruction)\n\t\t- [Automatic pool constructors and destructors](#automatic-pool-constructors-and-destructors)\n- [Concurrency model](#concurrency-model)\n\t- [Critical sections](#critical-sections)\n- [Motivation](#motivation)\n- [Roadmap](#roadmap)\n\n\u003c!-- /TOC --\u003e\n\n## About\n\nµEvLoop is a microframework build around a lightweight event loop. It provides the programmer the building blocks to put together async, interrupt-based systems.\n\nµEvLoop is loosely inspired on the Javascript event loop and aims to provide a similar programming model. Many similar concepts, such as events and closures are included.\nIt is aimed at environments with very restricted resources, but can be run on all kinds of platforms.\n\n### *DISCLAIMER*\n\nµEvLoop is in its early days and the API may change at any moment for now. Although it's well tested, use it with caution. Anyway, feedback is most welcome.\n\n## Highlights\n\n* As minimalist and loose-coupled as possible.\n* Does not allocate any dynamic memory on its own. All memory needed is statically allocated either explicitly by the user or implicitly by [containers](#containers).\n* Small memory footprint and runtime latency.\n* Does not try to make assumptions about the underlying system.\n* Extremely portable and conforming to ISO C99.\n* Depends only on a very small subset of the standard libc, mostly for fixed size integers and booleans.\n* Allows for excellent execution predictability and ease of debugging.\n* Can be used baremetal or alongside RTOSes.\n* Well tested and well documented.\n\n## API documentation\n\nThe API documentation is automatically generated by Doxygen. Find it [here](https://andsmedeiros.github.io/uevloop/).\n\n## Testing\n\nTests are written using a simple set of macros. To run them, execute `make test`.\n\nPlease note that the makefile shipped is meant to be run in modern Linux systems. Right now, it makes use of bash commands and utilities as well as expects `libSegFault.so` to be in a hardcoded path.\n\nIf this doesn't fit your needs, edit it as necessary.\n\n### Test coverage\n\nTo generate code coverage reports, run `make coverage`. This requires `gcov`, `lcov` and `genhtml` to be on your `PATH`. After running, the results can be found on `uevloop/coverage/index.html`.\n\n## Core data structures\n\nThese data structures are used across the whole framework. They can also be used by the programmer in userspace as required.\n\n**All core data structures are unsafe. Be sure to wrap access to them in [critical sections](#critical-sections) if you mean to share them amongst contexts asynchronous to each other.**\n\n### Closures\n\nA closure is an object that binds a function to some context. When invoked with arbitrary parameters, the bound function is called with both context and parameters available. With closures, some very powerful programming patterns, as functional composition, become way easier to implement.\n\nClosures are very light and it is often useful to pass them around by value.\n\n#### Basic closure usage\n\n```C\n#include \u003cstdint.h\u003e\n#include \u003cuevloop/utils/closure.h\u003e\n\nstatic void *add(void *context, void *params){\n    uintptr_t value1 = (uintptr_t)context;\n    uintptr_t value2 = (uintptr_t)params;\n\n    return (void *)(value1 + value2);\n}\n\n// ...\n\n// Binds the function `add` to the context (5)\nuel_closure_t add_five = uel_closure_create(\u0026add, (void *)5);\n\n// Invokes the closure with the parameters set to (2)\nuintptr_t result = (uintptr_t)uel_closure_invoke(\u0026add_five, (void *)2);\n// Result is 7\n\n```\n\n#### A word on (void \\*)\n\nClosures take the context and parameters as a void pointers and return the same. This is meant to make possible to pass and return complex objects from them.\n\nAt many times, however, the programmer may find the values passed/returned are small and simple (*i.e.*: smaller than a pointer). If so, it is absolutely valid to cast from/to a `uintptr_t` or other data type known to be at most the size of a pointer. The above example does that to avoid creating unnecessary object pools or allocating dynamic memory.\n\n\n### Circular queues\n\nCircular queues are fast FIFO (*first-in-first-out*) structures that rely on a pair of indices to maintain state. As the indices are moved forward on push/pop operations, the data itself is not moved at all.\n\nThe size of µEvLoop's circular queues are **required** to be powers of two, so it is possible to use fast modulo-2 arithmetic. As such, on queue creation, the size **must** be provided in its log2 form.\n\n***FORGETTING TO SUPPLY THE QUEUE'S SIZE IN LOG2 FORM MAY CAUSE THE STATIC ALLOCATION OF GIANT MEMORY POOLS***\n\n#### Basic circular queue usage\n\n```c\n#include \u003cstdint.h\u003e\n#include \u003cuevloop/utils/circular-queue.h\u003e\n\n#define BUFFER_SIZE_LOG2N   (5)\n#define BUFFER_SIZE         (1\u003c\u003cBUFFER_SIZE_LOG2N)  // 1\u003c\u003c5 == 2**5 == 32\n\n// ...\nuel_cqueue_t queue;\nvoid *buffer[BUFFER_SIZE];\n// Creates a queue with 32 (2**5) slots\nuel_cqueue_init(\u0026queue, buffer, BUFFER_SIZE_LOG2N);\n\n// Push items in the queue\nuel_cqueue_push(\u0026queue, (void *)3)\nuel_cqueue_push(\u0026queue, (void *)2)\nuel_cqueue_push(\u0026queue, (void *)1);\n\n// Pop items from the queue\nuintptr_t value1 = (uintptr_t) uel_cqueue_pop(\u0026queue); // value1 is 3\nuintptr_t value2 = (uintptr_t) uel_cqueue_pop(\u0026queue); // value2 is 2\nuintptr_t value3 = (uintptr_t) uel_cqueue_pop(\u0026queue); // value3 is 1\n```\n\nCircular queues store void pointers. As it is the case with closures, this make possible to store complex objects within the queue, but often typecasting to an smaller value type is more useful.\n\n### Object pools\n\nOn embedded systems, hardware resources such as processing power or RAM memory are often very limited. As a consequence, dynamic memory management can become very expensive in both aspects.\n\nObject pools are statically allocated arrays of objects whose addresses are stored in a queue. Whenever the programmer needs a object in runtime, instead of dynamically allocating memory, it is possible to simply pop an object pointer from the pool and use it away.\n\nBecause object pools are statically allocated and backed by [circular queues](#circular-queues), they are very manageable and fast to operate.\n\n#### Basic object pool usage\n\n```c\n#include \u003cstdint.h\u003e\n#include \u003cuevloop/utils/object-pool.h\u003e\n\ntypedef struct obj obj_t;\nstruct obj {\n    uint32_t num;\n    char str[32];\n    // Whatever\n};\n\n// ...\n\n// The log2 of our pool size.\n#define POOL_SIZE_LOG2N   (5)\nUEL_DECLARE_OBJPOOL_BUFFERS(obj_t, POOL_SIZE_LOG2N, my_pool);\n\nuel_objpool_t my_pool;\nuel_objpool_init(\u0026my_pool, POOL_SIZE_LOG2N, sizeof(obj_t), UEL_OBJPOOL_BUFFERS(my_pool));\n// my_pool now is a pool with 32 (2**5) obj_t\n\n// ...\n\n// Whenever the programmer needs a fresh obj_t\nobj_t *obj = (obj_t *)uel_objpool_acquire(\u0026my_pool);\n\n// When it is no longer needed, return it to the pool\nuel_objpool_release(\u0026my_pool, obj);\n```\n\n### Linked lists\n\nµEvLoop ships a simple linked list implementation that holds void pointers, as usual.\n\n#### Basic linked list usage\n\n```c\n#include \u003cstddef.h\u003e\n#include \u003cstdint.h\u003e\n#include \u003cuevloop/utils/linked-list.h\u003e\n\n// ...\n\nuel_llist_t list;\nuel_llist_init(\u0026list);\n\nuel_llist_node_t nodes[2] = {\n    {(void *)1, NULL},\n    {(void *)2, NULL}\n};\n\n// Push items into the list\nuel_llist_push_head(\u0026list, \u0026nodes[0]);\nuel_llist_push_head(\u0026list, \u0026nodes[1]);\n\n// List now is TAIL-\u003e [1]-\u003e [2]-\u003e NULL. HEAD-\u003e [2]\nuel_llist_node_t *node1 = (uel_llist_node_t *)llist_pop_tail(\u0026list);\nuel_llist_node_t *node2 = (uel_llist_node_t *)llist_pop_tail(\u0026list);\n\n//node1 == nodes[0] and node2 == nodes[1]\n```\n\n## Containers\nContainers are objects that encapsulate declaration, initialisation and manipulation of core data structures used by the framework.\n\nThey also encapsulates manipulation of these data structures inside [critical sections](#critical-sections), ensuring safe access to shared resources across the system.\n\n### System pools\n\nThe `syspools` component is a container for the system internal object pools. It contains pools for events and linked list nodes used by the core components.\n\nThe system pools component is meant to be internally operated only. The only responsibility of the programmer is to allocate, initialise and provide it to other core components.\n\nTo configure the size of each pool created, edit `include/uevloop/config.h`.\n\n#### System pools usage\n\n```c\n#include \u003cuevloop/system/containers/system-pools.h\u003e\n\n// ...\n\nuel_syspools_t pools;\nuel_syspools_init(\u0026pools);\n// This allocates two pools:\n//   1) pools.event_pool\n//   2) pools.llist_node_pool\n```\n\n### System queues\n\nThe `sysqueues` component contains the necessary queues for sharing data amongst the core components. It holds queues for events in differing statuses.\n\nAs is the case with system pools, the `sysqueues` component should not be directly operated by the programmer, except for declaration and initialisation.\n\nConfigure the size of each queue created in `include/uevloop/config.h`.\n\n\n#### System queues usage\n\n```C\n#include \u003cuevloop/system/containers/system-queues.h\u003e\n\n// ...\n\nuel_sysqueues_t queues;\nuel_sysqueues_init(\u0026queues);\n// This allocates two queues:\n//   1) queues.event_queue (events ready to be processed are put here)\n//   2) queues.schedule_queue (events ready to be scheduled are put here)\n```\n\n### Application\n\nThe `application` component is a convenient top-level container for all the internals of an µEvLoop'd app. It is not necessary at all but contains much of the boilerplate in a typical application.\n\nIt also proxies functions to the [`event loop`](#event-loop) and [`scheduler`](#scheduler) components, serving as a single point entry for the system operation.\n\nThe following code is a realistic minimal setup of the framework.\n```c\n#include \u003cuevloop/system/containers/application.h\u003e\n#include \u003cstdint.h\u003e\n\nstatic volatile uint32_t counter = 0;\nstatic uel_application_t my_app;\n\n// 1 kHz timer\nvoid my_timer_isr(){\n    my_timer_isr_flag = 0;\n    uel_app_update_timer(\u0026my_app, ++counter);\n}\n\nint main (int argc, char *argv[]){\n    uel_app_init(\u0026my_app);\n\n    // From here, the programmer can:\n    // - Schedule timers with `uel_app_run_later` or `uel_app_run_at_intervals`\n    // - Enqueue closures with `uel_app_enqueue_closure`\n    // - Set up observers with `uel_app_observe`\n    // - Listen for signals set at other places\n\n    while(1){  \n        uel_app_tick(\u0026my_app);\n    }\n\n    return 0;\n}\n```\n\n#### Application registry\n\nThe `application` component can also keep a registry of modules to manage. See [Appendix A: Modules](#appendix-a-modules) for more information.\n\n## Core components\n\n### Scheduler\n\nThe scheduler is a component that keeps track of current execution time and closures to be run in the future. It provides similar functionality to the `setTimeout` and `setInterval` Javascript functions.\n\nTwo queues lead in and out of it: the inbound schedule_queue is externally fed events that should be scheduled and then accounted for; the outbound event_queue hold events that are due to be collected and processed.\n\nThis component needs access to system's pools and queues.\n\n#### Basic scheduler initialisation\n\n```c\n#include \u003cuevloop/system/containers/system-pools.h\u003e\n#include \u003cuevloop/system/containers/system-queues.h\u003e\n#include \u003cuevloop/system/scheduler.h\u003e\n\n// ...\n\n// Create the system containers\nuel_syspools_t pools;\nuel_syspools_init(\u0026pools);\nuel_sysqueues_t queues;\nuel_sysqueues_init(\u0026queues);\n\n// Create the scheduler\nuel_scheduer_t scheduler;\nuel_sch_init(\u0026scheduler, \u0026pools, \u0026queues);\n```\n\n#### Scheduler operation\n\nThe `scheduler` component accepts input of closures and scheduling info an then turns it into a timer event. This timer is then inserted in a timer list, which is sorted by each timer's due time.\n\n```c\n#include \u003cstdio.h\u003e\n#include \u003cstdint.h\u003e\n#include \u003cuevloop/utils/closure.h\u003e\n\nstatic void *print_coords(void *context, void *params){\n    uintptr_t x = (uintptr_t)context;\n    uintptr_t y = (uintptr_t)params;\n    printf(\"(x: %d, y: %d)\\n\", x, y);\n\n    return NULL;\n}\n\n// ...\n\nuel_closure_t  print_x_one = uel_closure_create(\u0026print_coords, (void *)1);\nuel_closure_t  print_x_two = uel_closure_create(\u0026print_coords, (void *)2);\nuel_closure_t  print_x_three = uel_closure_create(\u0026print_coords, (void *)3);\n\n// Schedules to run 1000ms in the future.\n// Will print (x: 1, y: 4)\nuel_sch_run_later(\u0026scheduler, 1000, print_x_one, (void *)4);\n\n// Schedules to run at intervals of 500ms, runs the first time after 500ms\n// Will print (x: 2, y: 5)\nuel_sch_run_at_intervals(\u0026scheduler, 500, false, print_x_two, (void *)5);\n\n// Schedules to run at intervals of 300ms, runs the first time the next runloop\n// Will print (x: 3, y: 6)\nuel_sch_run_at_intervals(\u0026scheduler, 300, true, print_x_three, (void *)6);\n```\n\nThe `scheduler` must be fed regularly to work. It needs both an update on the running time as an stimulus to process enqueued timers. Ideally, a hardware timer will be consistently incrementing a counter and feeding it at an ISR while in the main loop the scheduler is oriented to process its queue.\n\n```c\n// millisecond counter\nvolatile uint32_t counter = 0;\n\n// 1kHz timer ISR\nvoid my_timer_isr(){\n  \tmy_timer_isr_flag = 0;\n  \tuel_sch_update_timer(\u0026scheduler, ++counter);\n}\n\n// ...\n\n// On the main loop\nuel_sch_manage_timers(\u0026scheduler);\n```\n\nWhen the function `uel_sch_manage_timers` is called, two things happen:\n1. The `schedule_queue` is flushed  and every timer in it is scheduled accordingly;\n2. The scheduler iterates over the scheduled timer list from the beginning and breaks it when it finds a timer scheduled further in the future. It then proceeds to move each timer from the extracted list  to the `event_queue`, where they will be further collected and processed.\n\n#### Timer events\n\nEvents are messages passed amongst the system internals that coordinate what tasks are to be run, when and in which order.\nUsually, the programmer don't have to interact directly with events, being *timer events* and [observers](#observers) the only exceptions to this.\nThe functions `uel_sch_run_later` and `uel_sch_run_at_intervals` return a `uel_event_t *`. With this handle, it is possible to pause and resume or even completely cancel a timer event.\n\n```C\n#include \u003cstddef.h\u003e\n\nuel_event_t *timer = uel_sch_run_at_intervals(\u0026scheduler, 100, false, print_one, NULL);\n\n// The event will be put on a hold queue in the scheduler\nuel_event_timer_pause(timer);  \n\n// The event will be rescheduled on the scheduler\nuel_event_timer_resume(timer);\n\n// The event will be ignored by the scheduler and destroyed at the `event loop`\nuel_event_timer_cancel(timer);\n```\n\nWhen pausing and resuming timer events, be aware of the internal's latencies: paused timers are only sent to the hold queue when their  scheduled time is hit. Also, when resumed, they are scheduled based solely on their period setting, being the elapsed time when they were paused completely ignored. Should a timer both scheduled *and* paused be resumed *before* its elapsed time is hit, it behaves as it was never paused.\nRegarding cancelled timer events, they are equally susceptible to internal latency as they will only be destroyed when processed by the `event loop`. However, cancelled timers are not meant to be reused anyway. As a rule of thumb, **never** use a timer event after it was cancelled.\n\n#### Scheduler time resolution\n\nThere are two distinct factors that will determine the actual time resolution of the scheduler:\n1. the frequency of the feed in timer ISR\n2.  the frequency the function `uel_sch_manage_timers` is called.\n\nThe basic resolution variable is the feed-in timer frequency. Having this update too sporadically will cause events scheduled to differing moments to be indistinguishable regarding their schedule (*e.g.*: most of the time, having the timer increment every 100ms will make any events scheduled to moments with less than 100ms of difference to each other to be run in the same runloop).\n\nA good value for the timer ISR frequency is usually between 1 kHz - 200 Hz, but depending on project requirements and available resources it can be less. Down to around 10 Hz is still valid, but precision will start to deteriorate quickly from here on.\n\nThere is little use having the feed-in timer ISR run at more than 1 kHz, as it is meant to measure milliseconds. Software timers are unlikely to be accurate enough for much greater frequencies anyway.\n\nIf the `uel_sch_manage_timers` function is not called frequently enough, events will start enqueuing and won't be served in time. Just make sure it is called when the counter is updated or when there are events on the schedule queue.\n\n### Event loop\n\nThe central piece of µEvLoop (even its name is a bloody reference to it) is the event loop, a queue of events to be processed sequentially. It is not aware of the execution time and simply process all enqueued events when run. Most heavy work in the system happens here.\n\nThe event loop requires access to system's internal pools and queues.\n\n#### Basic event loop initialisation\n\n```c\n#include \u003cuevloop/system/containers/system-pools.h\u003e\n#include \u003cuevloop/system/containers/system-queues.h\u003e\n#include \u003cuevloop/system/event-loop.h\u003e\n\n// Create system containers\nuel_syspools_t pools;\nuel_syspools_init(\u0026pools);\nuel_sysqueues_t queues;\nuel_sysqueues_init(\u0026queues);\n\n// Create the event loop\nuel_evloop_t loop;\nuel_evloop_init(\u0026loop, \u0026pools, \u0026queues);\n```\n\n#### Event loop usage\n\nThe event loop is mean to behave as a run-to-completion task scheduler. Its `uel_evloop_run` function should be called as often as possible as to minimise execution latency. Each execution of `uel_evloop_run` is called a *runloop* .\n\nThe only way the programmer interacts with it, besides creation / initialisation, is by enqueuing hand-tailored closures directly, but other system components operate on the event loop behind the stage.\n\nAny closure can be enqueued multiple times.\n\n```c\n#include \u003cuevloop/utils/closure.h\u003e\n\nstatic void *add(void *context, void *params){\n    uintptr_t *num = (uintptr_t *)context;\n  \tuintptr_t other = (uintptr_t)params;\n  \t*num += other;\n\n  \treturn NULL;\n}\n\n// ...\n\nuintptr_t value = 0;\nuel_closure_t closure = uel_closure_create(\u0026add, (void *)\u0026value);\n\nuel_evloop_enqueue_closure(\u0026loop, \u0026closure, (void *)1);\n// value is 0\n\nuel_evloop_run(\u0026loop);  \n// value is 1\n\nuel_evloop_enqueue_closure(\u0026loop, \u0026closure, (void *)2);\nuel_evloop_enqueue_closure(\u0026loop, \u0026closure, (void *)3);\nuel_evloop_run(\u0026loop);\n// value is 6\n\n```\n***WARNING!*** `uel_evloop_run` is the single most important function within µEvLoop. Almost every other core component depends on the event loop and if this function is not called, the loop won't work at all. Don't ever let it starve.\n\n#### Observers\n\nThe event loop can be instructed to observe some arbitrary volatile value and react to changes in it.\n\nBecause observers are completely passive, they are ideal for triggering side-effects from ISRs without **any** latency. However, each observer set does incur extra latency during runloops, as the observed value must be continuously polled.\n\n```c\nstatic volatile uintptr_t adc_reading = 0;\n\nvoid my_adc_isr(){\n    adc_reading  = my_adc_buffer;\n    my_adc_isr_flag = 0;\n}\n\nstatic void *process_adc_reading(void *context, void *params){\n    uintptr_t value = (uintptr_t)params;\n    // Do something with `value`\n\n    return NULL;\n}\nuel_closure_t processor =\n    uel_closure_create(process_adc_reading, NULL);\n\n// This ensures each runloop the `adc_reading` variable is polled and, in case\n// of changes to it, the `processor` closure is called with its new value as\n// parameter.\nuel_event_t *observer = uel_evloop_observe(\u0026loop, \u0026adc_reading, \u0026processor);\n\n// When an observer isn't needed anymore, it can be disposed of to release any\n// used system resources.\n// **DON'T** use an observer after it has been cancelled.\nuel_event_observer_cancel(observer).\n```\n\n### Signal\n\nSignals are similar to events in Javascript. It allows the programmer to message distant parts of the system to communicate with each other in a pub/sub fashion.\n\nAt the centre of the signal `system` is the Signal Relay, a structure that bind specific signals to its listeners. When a signal is emitted, the relay will **asynchronously** run each listener registered for that signal. If the listener was not recurring, it will be destroyed upon execution by the event loop.\n\n#### Signals and relay initialisation\n\nTo use signals, the programmer must first define what signals will be available in a particular relay, then create the relay bound to this signals.\n\nTo be initialised, the relay must have access to the system's internal pools and queues. The programmer will also need to supply it a buffer of [linked lists](#linked-lists), where listeners will be stored.\n\n ```c\n#include \u003cuevloop/system/containers/system-pools.h\u003e\n#include \u003cuevloop/system/containers/system-queues.h\u003e\n#include \u003cuevloop/system/signal.h\u003e\n#include \u003cuevloop/utils/linked-list.h\u003e\n\n// Create the system containers\nuel_syspools_t pools;\nuel_syspools_init(\u0026pools);\nuel_sysqueues_t queues;\nuel_sysqueues_init(\u0026queues);\n\n// Define what signals will be available to this relay.\n// Doing so in an enum makes it easy to add new signals in the future.\nenum my_component_signals {\n    SIGNAL_1 = 0,\n    SIGNAL_2,\n\t// New events would go here\n    SIGNAL_COUNT\n};\n\n// Declare the relay buffer. Note this array will be the number of signals large.\nuel_llist_t buffer[SIGNAL_COUNT];\n\n// Create the relay\nuel_signal_relay_t relay;\nuel_signal_relay_init(\u0026relay, \u0026pools, \u0026queues, buffer, SIGNAL_COUNT);\n ```\n\n#### Signal operation\n\n```c\n// This is the listener function.\nstatic void *respond_to_signal(void *context, void *params){\n  uintptr_t num = (uintptr_t)context;\n  // Signals can be emitted with parameters, just like events in JS\n  char c = (char)(uintptr_t)params;\n  printf(\"%d%c\\n\", num, c);\n\n  return NULL;\n}\n\n// Listeners can be persistent. They will fire once each time the signal is emitted\nuel_closure_t respond_to_signal_1 = uel_closure_create(\u0026respond_to_signal, (void *)1);\nuel_signal_listener_t listener_1 = uel_signal_listen(SIGNAL_1, \u0026relay, \u0026respond_to_signal_1);\n\n// Listeners can also be transient, so they fire ust on first emission\nuel_closure_t respond_to_signal_2 = uel_closure_create(\u0026respond_to_signal, (void *)2);\nuel_signal_listener_t listener_2 = uel_signal_listen_once(SIGNAL_2, \u0026relay, \u0026respond_to_signal_2);\n\n// ...\nuel_signal_emit(SIGNAL_1, \u0026relay, (void *)('a')); // prints 1a\nuel_signal_emit(SIGNAL_2, \u0026relay, (void *)('b')); // prints 2b\nuel_signal_emit(SIGNAL_1, \u0026relay, (void *)('c')); // prints 1c\nuel_signal_emit(SIGNAL_2, \u0026relay, (void *)('d')); // doesn't print anything\n```\n\nPlease note the listener function will not be executed immediately, despite what this last snippet can lead to believe. Internally, each closure will be sent to the event loop and only when it runs will the closures be invoked.\n\nYou can also unlisten for events. This will prevent the listener returned by a `uel_signal_listen()` or `uel_signal_listen_once()` operation to have its closure invoked when the [event loop](#event-loop) performs the next runloop.\nAdditionally, said listener will be removed from the signal vector on such opportunity.\n\n```c\nuel_signal_unlisten(listener_1, \u0026relay);\nuel_signal_unlisten(listener_2, \u0026relay);  // This has no effect because the listener\n                                          // for SIGNAL_2 has already been marked as unlistened\n```\n\n## Appendix A: Promises\n\nPromises are data structures that bind an asynchronous operation to the possible execution paths that derive from its result. They are heavily inspired by Javascript promises.\n\nPromises allow for very clean asynchronous code and exceptional error handling.\n\n### Promise stores\n\nAll promises must be created at a store, to where they will come back once destroyed. A promise store encapsulates object pools for promises and segments, the two composing pieces for promise operation.\n\n#### Promise store creation\n\nPromise store need access to two object pools, one for promises and one for segments.\n```C\n#include \u003cuevloop/utils/object-pools.h\u003e\n#include \u003cuevloop/system/promise.h\u003e\n\n#define PROMISE_STORE_SIZE_LOG2N    4   // 16 promises\n#define SEGMENT_STORE_SIZE_LOG2N    6   // 64 segments\n\nuel_objpool_t promise_pool;\nuel_objpool_t segment_pool;\nUEL_DECLARE_OBJPOOL_BUFFERS(uel_promise_t, PROMISE_STORE_SIZE_LOG2N, promise);\nUEL_DECLARE_OBJPOOL_BUFFERS(uel_promise_segment_t, SEGMENT_STORE_SIZE_LOG2N, segment);\nuel_objpool_init(\n    \u0026promise_pool,\n    PROMISE_STORE_SIZE_LOG2N,\n    sizeof(uel_promise_t),\n    UEL_OBJPOOL_BUFFERS(promise)\n);\nuel_objpool_init(\n    \u0026segment_pool,\n    SEGMENT_STORE_SIZE_LOG2N,\n    sizeof(uel_promise_segment_t),\n    UEL_OBJPOOL_BUFFERS(segment)\n);\n\nuel_promise_store_t store = uel_promise_store_create(\u0026promise_pool, \u0026segment_pool);\n```\n\n### Promises and segments\n\nAs mentioned above, promises and segments are the two building blocks for composing asynchronous chains of events. Promises represent the asynchronous operation *per se* and segments are the synchronous processing that occurs when a promise settles.\n\nSettling a promise means transitioning it into either **resolved** or **rejected** states which respectively indicate success or error of the asynchronous operation, optionally assigning a meaningful value to the promise.\n\n#### Promise creation\n\nThere are two necessary pieces for creating a promise: a store and a constructor closure that starts the asynchronous operation.\n\n```C\nvoid *start_some_async(void *context, void *params){\n    uel_promise_t *promise = (uel_promise_t *)params;\n    // ...\n    return NULL; // return value is ignored\n}\n\n// ...\n\nuel_promise_t *promise =\n    uel_promise_create(\u0026store, uel_closure_create(start_some_async, NULL));\n```\n\nWhen the promise is created, `start_some_async` is invoked immediately, taking the promise pointer as parameter.\n\nOn creation, promises are in the **pending** state. This means its asynchronous operation has not been completed yet and the promise does not hold any meaningful value.\n\n#### Promise settling\n\nOnce the operation is completed (and this can also be synchronously done from inside the constructor closure),\nthere are two functions for signalling either success or failure of the asynchronous operation:\n\n```C\nuel_promise_resolve(promise1, (void *)SOME_VALUE); // operation succeeded\nuel_promise_reject(promise2, (void *)SOME_ERROR);  // operation failed\n```\n\nOnce a promise is settled, it holds a value that can be accessed via `promise-\u003evalue`.\n\n#### Segments\n\nSegments represent the synchronous phase that follows a promise settling. They contain two closures, one for handling resolved promises and one for handling rejected promises. Either one is chosen at runtime, depending on the settled state of the promise, and is invoked with the promise as parameter.\n\nDepending on the promise state, attaching segments have different effects. When a promise is pending, attached segments just get enqueued for execution once the promise is settled. Should the promise be already settled, attached segments get processed immediately instead.\n\n```C\n#include \u003cstdio.h\u003e\n#include \u003cstddef.h\u003e\n\nvoid *report_success(void *context, void *params) {\n    char *tag = (char *)context;\n    uel_promise_t *promise = (uel_promise_t *)params;\n    printf(\"promise %s resolved with %d\\n\", tag, promise-\u003evalue);\n    return NULL;\n}\n\nvoid *report_error(void *context, void *params) {\n    char *tag = (char *)context;\n    uel_promise_t *promise = (uel_promise_t *)params;\n    printf(\"promise %s rejected with %d\\n\", tag, promise-\u003evalue);\n    return NULL;\n}\n\n// ...\n\n// Assume p1, p2 and p3 as pending, resolved and rejected promises, respectively\n// p2 is resolved with (uintptr_t)10 and p3 is rejected with (uintptr_t)100\n\nuel_promise_after(\n    p1,\n    uel_closure_create(report_success, (void *)\"p1\"),\n    uel_closure_create(report_error, (void *)\"p1\")\n);\n// Neither closure gets invoked. Instead, a new segment containing them is enqueued.\n\nuel_promise_after(\n    p2,\n    uel_closure_create(report_success, (void *)\"p2\"),  \n    uel_closure_create(report_error, (void *)\"p2\")\n);\n// As the promise is already resolved, instead of enqueueing a segment, the first\n// closure is invoked.\n// \"p2 resolved with 10\" is printed\n\nuel_promise_after(\n    p3,\n    uel_closure_create(report_success, (void *)\"p3\"),  \n    uel_closure_create(report_error, (void *)\"p3\")\n);\n// Similarly, as the promise is already rejected, the second closure gets invoked\n// immediately.\n// \"p3 rejected with 100\" is printed\n\nuel_promise_resolve(p1, (uintptr_t)1);\n// Upon settling, segments are removed from the queue and processed.\n// \"p1 resolved with 1\" is printed\n```\n\nThe `uel_promise_after()` function takes two closures as parameters. This is useful for splitting the execution path in two mutually exclusive routes depending on the settled state of the promise.\n\nThere are three other functions for enqueuing segments. They can be used for attaching segments that only produce effects on specific settled states or attaching the same closure to both states:\n\n```C\nuel_promise_then(my_promise, my_closure);\n// `my_closure` is invoked only when `my_promise` is resolved.\n// The added segment is ignored if the promise is rejected.\n\nuel_promise_catch(my_promise, my_closure);\n// `my_closure` is invoked only when `my_promise` is rejected.\n// The added segment is ignored if the promise is resolved.\n\nuel_promise_always(my_promise, my_closure);\n// `my_closure` is invoked always upon settling, regardless of the settled state\n```\n\n#### Segment chains and promise resettling\n\nAny number of segments can be attached to some promise. They will be either processed immediately, in case the promise is already settled, or enqueued for processing upon settling in the future. Regardless, attached segments are always processed in registration order.\n\nChaining segments is useful because segments have the ability to commute between execution paths through *promise resettling*. To resettle a promise means changing its state and value.\n\n```C\nvoid *store_char(void *context, void *params) {\n    unsigned char *var = (unsigned char *)context;\n    uel_promise_t *promise = (uel_promise_t *)params;\n    if((uintptr_t)promise-\u003evalue \u003c= 255) {\n        *var = (unsigned char)(uintptr_t)promise-\u003evalue;\n    } else {\n        uel_promise_resettle(promise, UEL_PROMISE_REJECTED, (void *)\"Value too big\");\n    }\n    return NULL;\n}\n\nvoid *report_error(void *context, void *params) {\n    uel_promise_t *promise = (uel_promise_t *)params;\n    printf(\"promise was rejected with error '%s'\\n\", (char *)promise-\u003evalue);\n    return NULL;\n}\n\nvoid *done(void *context, void *params) {\n    static char *states[3] = { \"pending\", \"resolved\", \"rejected\" };\n    uel_promise_t *promise = (uel_promise_t *)params;\n    printf(\"operation done with state '%s'\\n\", states[promise-\u003estate]);\n}\n\nunsigned char c1 = 0;\nuel_promise_t *p1 = uel_promise_create(\u0026store, uel_nop()); // Creates a pending promise\nuel_promise_then(p1, uel_closure_create(store_char, (void *)\u0026c1));\nuel_promise_catch(p1, uel_closure_create(report_error, NULL));\nuel_promsie_always(p1, uel_closure_create(done, NULL));\n```\nThis builds a segment chain attached to promise `p1`. Each segment added describes one synchronous step to be executed for each of the two settled states.\n\nSegments are processed sequentially, from first to last, starting with the closure relative to the state the promise was settled as. The following table illustrates this chain:\n\n|   State   | Segment 1  |  Segment 2   |   Segment 3   |\n|:---------:|:----------:|:------------:|:-------------:|\n| resolved  | store_char |      nop     |     done      |\n| rejected  |     nop    | report_error |     done      |\n\nThe outcome of this chain is determined upon settling. For example, given the following resolution:\n\n```C\nuel_promise_resolve(p1, (void *)10);\n```\n\nThe first closure invoked is `store_char`, in segment 1. In the closure function, the test condition `promise-\u003evalue \u003c= 255` is true, so the closure proceeds to store its value in the `c1` variable.\n\nAs the promise remains resolved, it advances to segment 2, where it finds a `nop` (no-operation). This is due to the segment being attached via `uel_promise_catch`.\n\nThe promise then advances to segment 3, where it finds the `done` closure. The process then ends and the promise retains its state and value (`UEL_PROMISE_RESOLVED` and `(void *)10` in this case). By then, `c1` holds `(char)10` and `operation done with state 'resolved'` is printed.\n\nIf instead the promise was resolved as:\n```C\nuel_promise_resolve(p1, (void *)1000);\n```\nThen the test condition `promise-\u003evalue \u003c= 255` would have failed. The `store_char` would then skip storing the value and would rather *resettle* the promise as rejected, with some error message as value. This effectively commutes the execution path to the *rejected* branch.\n\nOnce the `store_char` returns, as the promise is now rejected, the `report_error` closure is invoked and `promise was rejected with error 'Value too big'` is printed. The `done` closure is then invoked and prints `operation done with state 'rejected'`.\n\nSimilarly, if instead the promise had been rejected as:\n```C\nuel_promise_reject(p1, (void *)\"Asynchronous operation failed\");\n```\nSegment 1 would be ignored, `report_error` would be invoked and print `promise was rejected with error 'Asynchronous operation failed'` and, at segment 3, `done` would be invoked and print `operation done with state 'rejected'`.\n\nResettling can also be used for recovering from errors if it commutes back to `resolved` state. This constitutes an excellent exception system that allows errors raised in loose asynchronous operations to be rescued consistently. Even exclusively synchronous processes can benefit from this error rescuing system.\n\nAs a last note, segments can also resettle a promise as `UEL_PROMISE_PENDING`. This halts the synchronous stage immediately, leaving any unprocessed segments in place. This phase can be restarted by either resolving or rejecting the promise again.\n\n#### Nested promises\n\nPromises can be nested into each other, allowing for complex asynchronous operations to compose a single segment chain. This provides superb flow control for related asynchronous operations that would otherwise produce a lot of spaghetti code.\n\nWhenever a promise segment returns any non-null value, it is cast to a promise pointer. The original promise then transitions back to `pending` state and awaits for the returned promise to settle. Once this new promise is settled, the original promise is resumed with whatever state and value the new promise was settled as.\n\nFor instance, suppose the programmer is programming an MCU that possesses an ADC with an arbitrarily long buffer and a DMA channel. The program must start the ADC, which will sample `N` times and store it in its buffer. After `N` samples have been taken, the DMA channel must be instructed to move it out of the ADC buffer into some memory-mapped buffer, where it will be processed.\n\nThis could be easily accomplished with signals or callbacks, but would eventually lead to confusing and discontinuous code. With nested promises, however, it is easy to describe the whole process into one single chain.\n\nSuppose this is the implementation for the DMA and ADC modules:\n\n```C\n// ADC implementation\nstatic uel_promise_t *adc_promise;\n\n// This ISR is called when the ADC finishes sampling\n// the instructed number of samples\nstatic void adc_isr() {\n    uel_promise_resolve(adc_promise, (void *)ADC_BUFFER);\n    adc_promise = NULL;\n    adc_isr_flag = 0;\n}\n\n// Launches the ADC and returns. This effectively starts an asynchronous operation.\nstatic void *start_adc(void *context, void *params) {\n    uintptr_t count = (uintptr_t)context;\n    uel_promise_t *promise = (uel_promise_t *)params;\n    ADC_COUNT = count;\n    ADC_START = 1;\n    adc_promise = promise;\n    return NULL;\n}\n\n// Public interface function. Returns a promise that,\n// when resolved, will contain the address of the buffer where\n// data was sampled\nuel_promise_t *adc_read(uintptr_t count) {\n    return uel_promise_create(\u0026store, uel_closure_create(start_adc, (void *)count));\n}\n```\n\n```c\n// DMA implementation\nstatic uel_promise_ t *dma_promise;\n\n// This ISR is called when the DMA channel has finished\n// moving data\nstatic void dma_isr() {\n    uel_promise_resolve(dma_promise, (void *)DMA_DESTINATION);\n    dma_promise = NULL;\n    dma_isr_flag = 0;\n}\n\n// Auxiliary structure to hold DMA mapping information\nstruct dma_mapping {\n    void *source;\n    void *destination;\n    uintptr_t count;\n};\n\n// Launches the DMA channel, starting an asynchronous operation\nstatic void *start_dma(void *context, void *params) {\n    struct dma_mapping *mapping = (struct dma_mapping *)context;\n    uel_promise_t *promise = (uel_promise_t *)params;\n    DMA_SOURCE = mapping-\u003esource;\n    DMA_DESTINATION = mapping-\u003edestination;\n    DMA_COUNT = mapping-\u003ecount;\n    DMA_START = 1;\n    adc_promise = promise;\n    return NULL;\n}\n\n// Public interface function.\n// Returns a promise that, when resolved, will hold the address\n// of the buffer to where data was moved\nuel_promise_t *dma_move(void *destination, void *source, uintptr_t count) {\n    struct dma_mapping mapping = { source, destination, count };\n    return uel_promise_create(\u0026store, uel_closure_create(start_adc, (void *)\u0026mapping));\n}\n```\n\n\n\nImplementing the project requirements is this simple:\n\n```C\n#define N  10 // Will take 10 samples\n\nstatic void *move_from_buffer(void *context, void *params) {\n    uel_promise_t *promise = (uel_promise_t *)params;\n    void *source = promise-\u003evalue;\n    void *destination = context;\n    return (void *)dma_move(destination, source, N);\n}\n\nstatic void *data_moved(void *context, void *params) {\n    uel_promise_t *promise = (uel_promise_t *)params;\n    printf(\"Data moved to %p\\n\", promise-\u003evalue);\n    return NULL;\n}\n\n// ...\n\nuel_promise_t *promise = adc_read(N);\nunsigned char destination[N];\nuel_promise_then(promise, uel_closure_create(move_from_buffer, (void *)destination));\nuel_promise_then(promise, uel_closure_create(data_moved, NULL));\n\n```\n\nNote that, in the above example, promises are resolved **synchronously** inside the ISR's. This may be not desirable due to performance reasons, but can be easily improved by enqueueing a closure that resolves nested promises into the [event loop](#event-loop).\n\n#### Promise destruction and promise helpers\n\nTo destroy a promise, call `uel_promise_destroy()`. This will release all segments and then the promise itself. Settling a promise after it has been destroyed is undefined behaviour.\n\nBecause settling and destroying promises are so frequent, there are helper functions that emit closures that automate this work:\n\n```C\n// When invoked, destroys the promise\nuel_closure_t destroyer = uel_promise_destroyer(promise);\n\n// When invoked with some value, resolves the promise with that value\nuel_closure_t resolver = uel_promise_resolver(promise);\n\n// When invoked with some value, rejects the promise with that value\nuel_closure_t rejecter = uel_promise_rejecter(promise);\n```\n\n## Appendix B: Modules\n\nModules are independent units of behaviour, self-contained and self-allocated, with clear lifecycle hooks, interface and dependencies. They enforce separation of concerns and isolation by making clear how your code interacts with the rest of the application.\n\nModules are absolutely optional and very thin on the library side. They are basically a convention of how to write code in a fashion that works well with µEvLoop.\n\nModules can serve as a variety of purposes:\n- They can act as bridges to static data, such as SFRs;\n- They can be object factories, meant to distribute and recycle objects to other modules;\n- They can act as services, background processes that interact with other parts of the application in a sattelite-fashion.\n\n### Module creation\n\n```c\n// File: my_module.h\n\n#include \u003cuevloop/utils/module.h\u003e\n#include \u003cuevloop/system/containers/application.h\u003e\n\ntypedef struct my_module my_module_t; // Private implementation\n\nuel_module_t *my_module(uel_application_t *my_app);\n\n// Any functions' signatures to operate the module go here\n```\n```c\n// File: my_module.c\n\n#include \"my_module.h\"\n\nstruct my_module {\n    uel_module_t base; // Inherits base interface\n\n    // Other properties\n};\n\n// The config hook is used to prepare the module.\n// When it is fired by the `application`, all modules are guaranteed to be\n// registered, but may still be in an inconsistent state.\n// Modules are loaded in the order they are supplied, so previous modules will\n// already be configured.\nstatic void config(uel_module_t *mod){\n    // ...\n}\n\n// The launch hook is where the module should effectively start its functions.\n// All modules here are guaranteed to be registered and properly configurated.\nstatic void launch(uel_module_t *mod){\n    // ...\n}\n\n// The constructor function is the only mandatory symbol to be exported.\n// This function should initialise all independent or readly available data.\n// It must not be called more than once per module.\nuel_module_t *my_module(uel_application_t *my_app){\n\tstatic my_module_t module;\n    uel_module_init(\u0026module.base, config, launch, my_app);\n\n    // Initialise other module's properties\n\n    return \u0026module.base;\n}\n```\n\n### Module registration\n\nModules are operated by the `application` component. It is responsible for loading, configuring and launching each module.\n\n```c\n// File: my_app_modules.h\n\n#include \"my_module.h\"\n\n// Each module must be identified by a positive integer, zero-indexed.\n// Storing module IDs in an enum makes it easy to add new modules in the future.\nenum MY_APP_MODULES {\n    MY_MODULE,\n    // other modules...,\n    MY_APP_MODULE_COUNT\n};\n```\n\n```c\n// File: main.c\n\n#include \u003cuevloop/system/containers/application.h\u003e\n#include \"my_app_modules.h\"\n\n// Creates the controlling application\nuel_application_t my_app;\nuel_app_init(\u0026my_app);\n\n// Declares a list of module pointers to be supplied to the application.\nuel_module_t *modules[MY_APP_MODULE_COUNT];\n\n// Individually initialising pointers in the list ensures IDs always\n// match their corresponding module, even if they change during development.\nmodules[MY_MODULE] = my_module(\u0026my_app);\n\n// This loads the modules into the application:\n// - The module list is stored as the application registry;\n// - Each module's configuration hook is sequentially invoked, according to\n//   their position in the registry;\n// - Each module's launch hook is sequentially invoked, equally ordered by\n//   the registry.\nuel_app_load(\u0026my_app, modules, MY_APP_MODULE_COUNT);\n```\n\n### Dependency Injection\n\nThere are two method for injecting a registered module: *parametrised injection* and *ad-hoc injection*. Each is adequate for a different situation:\n\n#### Parametrised injection\n\nParametrised dependencies are dependencies that are supplied during module construction.\n\nGiven the following module header:\n```c\n// File: my_greeter.h\n\n#include \"my_module.h\"\n#include \u003cuevloop/utils/module.h\u003e\n#include \u003cuevloop/system/containers/application.h\u003e\n\ntypedef struct my_greeter my_greeter_t;\nuel_module_t *my_greeter(\n    uel_application_t *my_app,\n    const char *name,         // \u003c-- Parametrised dependency\n    my_module_t *other_module // \u003c-- Dependencies can be other modules\n);\n```\n\nThe application loading procedure would be:\n```c\n// File: my_app_modules.h\n\nenum MY_APP_MODULES {\n    MY_MODULE,\n    MY_GREETER,\n    MY_APP_MODULE_COUNT\n};\n```\n```c\n// File: main.c\n\nuel_module_t *modules[MY_APP_MODULE_COUNT];\n\nmodules[MY_MODULE] = my_module(\u0026my_app);\n\n// Injects parametrised dependencies `name` and `other_module`\nmodules[MY_GREETER] =\n    my_greeter(\u0026my_app, \"King Kong\", (my_module_t *)modules[MY_MODULE]);\n\nuel_app_load(\u0026my_app, modules, MY_APP_MODULE_COUNT)\n```\n\nParametrised injection facilitates reasoning about what each module depends on and in which order they are loaded. This is the preferable way to inject dependencies into modules.\n\n#### Ad-hoc injection\nAd-hoc injections can occur anywhere, including places outside the scope of the application's managed modules.\n\n```c\n// ANYWHERE with access to `my_app`:\n\n#include \u003cuevloop/system/containers/application.h\u003e\n#include \"my_greeter.h\"\n#include \"my_app_modules.h\"\n\nmy_greeter_t *greeter = (my_greeter_t *)uel_app_require(\u0026my_app, MY_GREETER);\n```\n\nWhile Ad-hoc injections seem easier, they make more difficult to know on which modules some particular piece of code depends on. Also, because they require the modules to already be loaded into the registry, they cannot be used during the configuration phase.\n\n## Appendix C: Useful goodies\n\n### Iterators\n\nIterators are abstractions on arbitrary collections of items. They provide a uniform interface for yielding each element in the iterated collection, disregarding implementation details of such collection.\n\nThere are two iterator specialisations shipped with µEvLoop:\n\n#### Array iterators\n\n```c\n#include \u003cuevloop/utils/iterator.h\u003e\n#define NUMS_LENGTH\t5\n\nuintptr_t nums[NUMS_LENGTH] = { 1, 2, 3, 4, 5 };\nuel_iterator_array_t array_iterator =\n        uel_iterator_array_create(nums, NUMS_LENGTH, sizeof(uintptr_t));\n```\n\n#### Linked list iterators\n\n```c\n#include \u003cuevloop/utils/iterator.h\u003e\n#include \u003cuevloop/utils/linked-list.h\u003e\n\nuel_llist_node_t nodes[] = {\n    { (void *)1, NULL },\n    { (void *)2, NULL ),\n    { (void *)3, NULL }\n};\nuel_llist_t list;\nuel_llist_init(\u0026list);\nuel_llist_push_head(\u0026list, \u0026nodes[0]);\nuel_llist_push_head(\u0026list, \u0026nodes[1]);\n\nuel_iterator_llist_t llist_iterator = uel_iterator_llist_create(\u0026list);\n```\n\n#### Iterator operation\n\nIterators live entirely on an embedded function pointer, `next`. It is responsible by yielding a pointer to each element in the collection.\n\n```c\n// cast to generic iterator\nuel_iterator_t *iterator = (uel_iterator_t *)\u0026array_iterator;\n\n// when supplied with `NULL` as parameter to `next`, yields\n// the first element in the collection\nint *current = NULL;\n\nwhile(true){\n    current = (int *)iterator-\u003enext(iterator, (void *)current);\n    if(current != NULL){\n        // do something\n    }else break; // when there are no more elements , yields `NULL`\n}\n```\n\n#### Iteration helpers\n\nBesides manually operating an iterator, there are several iteration helpers that automatise work.\n\n```c\n#include \u003cuevloop/utils/closure.h\u003e\n\nvoid *accumulate(void *context, void *params){\n    uintptr_t *acc = (uintptr_t *)context;\n    uintptr_t num = *(uintptr_t *)params;\n    *acc += num;\n    return (void *)true; // required; returning false is equivalent to a `break`\n}\n\nuintptr_t acc = 0;\nuel_closure_t acumulate_into_acc = uel_closure_create(accumulate, (void *)\u0026acc);\n\nuel_iterator_foreach(iterator, \u0026accumulate_into_acc);\n// if `iterator` is the same array iterator defined previously,\n// acc == 15\n```\n\nThere are many more iteration helpers, check the more details on [the docs](https://andsmedeiros.github.io/uevloop/html/iterator_8h.html).\n\n#### Custom iterators\n\nIterators are meant to be expansible. If you need to enumerate your own type, write an iterator specialisation:\n\n```c\nstruct my_collection_type{\n    // whatever, could be a binary tree, a hash table etc\n};\nstruct my_collection_iterator{\n    uel_iterator_t base; // inherits the base interface\n    // any other state necessary for iteration\n};\nvoid *my_collection_next(my_collection_iterator *iterator, void *last){\n    // implement your iteration logic here.\n    // remember:\n    //   if last == NULL, yield the first element.\n    //   if last == last element in collection, yield NULL.\n}\n\nstruct my_collection_type collection; // initialised as needed\nstruct my_collection_iterator iterator = {\n    { (uel_iterator_next_t)\u0026my_collection_next, (void *)\u0026collection },\n    // initialise any state as necessary\n};\n```\n\nNote that `base` is **required** to be the first member in your custom iterator structure. That way, a pointer to your iterator can be safely cast to `uel_iterator_t *` forth and back.\n\n### Conditionals\n\nConditionals are functional switches in the form of a tuple of closures `\u003ctest, if_true, if_false\u003e`.\n\nWhen applied to some input, this input is submitted to the `test` closure. If it returns `true`, `if_true` closure is invoked, otherwise, `if_false` is invoked. All closures take the same input as arguments.\n\n```c\n#include \u003cuevloop/utils/conditional.h\u003e\n#include \u003cstdio.h\u003e\n\nvoid *is_divisible(void *context, void *params){\n    uintptr_t divisor = (uintptr_t)context;\n    uintptr_t dividend = (uintptr_t)params;\n    return (void *)(uintptr_t)(dividend % divisor != 0);\n}\n\nvoid *qualify_number(void *context, void *params){\n    char *parity = (char *)context;\n    uintptr_t num = (uintptr_t)params;\n    printf(\"%d is %s\\n\", num, parity);\n    return NULL;\n}\n\nuel_closure_t is_even =\n        uel_closure_create(is_divisible, (void *)2);\nuel_closure_t print_even =\n        uel_closure_create(qualify_number, (void *)\"even\");\nuel_closure_t print_odd =\n        uel_closure_create(qualify_number, (void *)\"odd\");\n\nuel_conditional_t numer_parity;\nuel_conditional_init(\u0026number_parity, is_even, print_even, print_odd);\n\nuel_conditional_apply(\u0026number_parity, (void *)1);\n// prints \"1 is odd\"\n\nuel_conditional_apply(\u0026number_parity, (void *)2);\n// prints \"2 is even\"\n```\n\n### Pipelines\n\nPipelines are sequences of closures whose outputs are connected to the next closure's input.\n\nWhen applied to some input, this input is passed along each closure, being transformed along the way. Applying a pipeline returns the value returned by the last closure in it.\n\n```c\n#include \u003cuevloop/utils/pipeline.h\u003e\n\nvoid *exponentiate(void *context, void *params){\n    uintptr_t power = (uintptr_t)context;\n    uintptr_t base = (uintptr_t)params;\n    return (void *)(uintptr_t)pow(base, power);\n}\nvoid *add(void *context, void *params){\n    uintptr_t term1 = (uintptr_t)context;\n    uintptr_t term2 = (uintptr_t)params;\n    return (void *)(uintptr_t)(term1 + term2);\n}\n\nuel_closure_t square = uel_closure_create(exponentiate, (void *)2);\nuel_closure_t increment = uel_closure_create(add, (void *)1);\n\n// creates the math_pipeline, equivalent to the function f(x) = x^2 + 1\nUEL_PIPELINE_DECLARE(math, square, increment);\n\nuintptr_t res = (uintptr_t)uel_pipeline_apply(\u0026math_pipeline, (void *)5);\n// res == f(5) == (5^2 + 1) == 26\n```\n\n### Functional helpers\n\nIterators, conditionals and pipelines are objects associated with synchronous operations.\n\nTo make more suitable to asynchronous contexts, there are numerous helpers that can abstract some of their operational details and export them into portable closures.\n\nPlease read [the docs](https://andsmedeiros.github.io/uevloop/html/functional_8h.html) to find out more about them.\n\n### Automatic pools and automatic pointers\n\nAutomatic pools are wrappers objects that enhance the abilities of object pools. They allow constructors and destructors to be attached and, instead of yielding bare pointers, yield `uel_autoptr_t` automatic pointers.\n\nAutomatic pointers are objects that associate some object to the pool it is from, making it trivial to destroy the object regardless of access to its source. An automatic pointer issued to an object of type `T` can be safely cast to `T**`. Casting to any other pointer type is undefined behaviour.\n\n#### Automatic pool creation\n\nAutomatic pools are created and initialised in very similar ways to object pools:\n\n```C\n#include \u003cuevloop/utils/automatic-pool.h\u003e\n\n#define TUPLE_POOL_SIZE_LOG2N    5    // 32 tuples\nstruct tuple {\n    int a;\n    int b;\n};\nUEL_DECLARE_AUTOPOOL_BUFFERS(struct tuple, TUPLE_POOL_SIZE_LOG2N, tuple);\nuel_autopool_t tuple_pool;\nuel_autopool_init(\n    \u0026tuple_pool,\n    TUPLE_POOL_SIZE_LOG2N,\n    sizeof(struct tuple),\n    UEL_AUTOPOOL_BUFFERS(tuple)\n);\n```\n\n#### Automatic pool operation and automatic pointer destruction\n\nAfter an automatic pool has been created, it can allocate and deallocate objects, just like object pools.\n\n```C\nstruct tuple **t = (struct tuple **)uel_autopool_alloc(\u0026tuple_pool);\nif(t) {\n    (**t).a = 1;\n    (**t).b = 10;\n    uel_autoptr_dealloc((uel_autoptr_t)t);\n}\n```\n\n#### Automatic pool constructors and destructors\n\nIt is possible to attach a constructor and a destructor to some automatic pool. This are closures that will be invoked upon object allocation and deallocation, taking a **bare** pointer to the object being operated on.\n\n```C\nstatic void *default_tuple(void *context, void *params) {\n    struct tuple *value = (struct tuple *)context;\n    struct tuple *t = (struct tuple *)params;\n    *t = *value;\n    return NULL;\n}\n\nstatic void *zero_tuple(void *context, void *params) {\n    struct tuple *t = (struct tuple *)params;\n    t-\u003ea = 0;\n    t-\u003eb = 0;\n    return NULL;\n}\n\n// ...\n\nstatic struct tuple default_value = { 10, 100 };\nuel_autopool_set_contructor(\n    \u0026tuple_pool,\n    uel_closure_create(default_tuple, (void *)\u0026default_value)\n);\nuel_autopool_set_destructor(\n    \u0026tuple_pool,\n    uel_closure_create(zero_tuple, NULL)\n);\n\nstruct tuple **t1 = (struct tuple **)uel_autopool_alloc(\u0026tuple_pool);\n// t1.a == 10, t1.b == 100\n\nuel_autoptr_dealloc((uel_autoptr_t)t1);\n// before t1 is dealloc'ed t1.a == 0, t1.b == 0\n```\n\n## Concurrency model\n\nµEvLoop is meant to run baremetal, primarily in simple single-core MCUs. That said, nothing stops it from being employed as a side library in RTOSes or in full-fledged x86_64 multi-threaded desktop applications.\n\nCommunication between asynchronous contexts, such as ISRs and side threads, is done through some shared data structures defined inside the library's core components. As whenever dealing with non-atomic shared memory, there must be synchronisation between accesses to these structures as to avoid memory corruption.\n\nµEvLoop does not try to implement a universal locking scheme fit for any device. Instead, some generic critical section definition is provided.\n\n### Critical sections\n\nBy default, critical sections in µEvLoop are a no-op. They are provided as a set of macros that can be overriden by the programmer to implement platform specific behaviour.\n\nFor instance, while running baremetal it may be only necessary to disable interrupts to make sure accesses are synchronised. On a RTOS multi-threaded environment, on the other hand, it may be necessary to use a mutex.\n\n\nThere are three macros that define critical section implementation:\n\n1. `UEL_CRITICAL_SECTION_OBJ_TYPE`\n\n  If needed, a global critical section object can be declared. If this macro is defined, this object will be available to any critical section under the symbol `uel_critical_section`.\n\n  The `UEL_CRITICAL_SECTION_OBJ_TYPE` macro defines the **type** of the object. It is the programmer's responsibility to declare, globally allocate and initialise the object.\n\n2. `UEL_CRITICAL_ENTER`\n\n  Enters a new critical section. From this point until the critical section exits, no other thread or ISR may attempt to access the system's shared memory.\n\n3. `UEL_CRITICAL_EXIT`\n\n  Exits the current critical section. After this is called, any shared memory is allowed to be claimed by some party.\n\n## Motivation\n\nI often work with small MCUs (8-16bits) that simply don't have the necessary power to run a RTOS or any fancy scheduling solution. Right now I am working on a new commercial project and felt the need to build something by my own. µEvLoop is my bet on how a modern, interrupt-driven and predictable embedded application should be.\nI am also looking for a new job and needed to improve my portfolio.\n\n## Roadmap\n\n* Better error handling\n* Logging helpers\n* Macro shortcuts for frequently used functions\n* More lifecycle hooks for modules\n* Application teardown/exit\n* Implement [application](#application) signals\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandsmedeiros%2Fuevloop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandsmedeiros%2Fuevloop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandsmedeiros%2Fuevloop/lists"}