|
| 1 | +/* |
| 2 | + * This file is part of AtomVM. |
| 3 | + * |
| 4 | + * Copyright 2023 Paul Guyot <[email protected]> |
| 5 | + * |
| 6 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | + * you may not use this file except in compliance with the License. |
| 8 | + * You may obtain a copy of the License at |
| 9 | + * |
| 10 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | + * |
| 12 | + * Unless required by applicable law or agreed to in writing, software |
| 13 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | + * See the License for the specific language governing permissions and |
| 16 | + * limitations under the License. |
| 17 | + * |
| 18 | + * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later |
| 19 | + */ |
| 20 | + |
| 21 | +/** |
| 22 | + * @file listeners.h |
| 23 | + * @brief Common code for port listeners. |
| 24 | + * |
| 25 | + * @details This header defines convenient common functions to implement |
| 26 | + * listeners, and should be included in platform's `sys.c`. |
| 27 | + * |
| 28 | + * Before including this file, define listener_event_t which represent a |
| 29 | + * selectable event, as well as EventListener, which should have a list head |
| 30 | + * member called `listeners_list_head` and a handler member called `handler`. |
| 31 | + * |
| 32 | + * On a platform using select(3) with file descriptors, this typically is done |
| 33 | + * by creating a `platform_sys.h` header with: |
| 34 | + * ``` |
| 35 | + * #include "sys.h" |
| 36 | + * |
| 37 | + * typedef int listener_event_t; |
| 38 | + * |
| 39 | + * struct EventListener |
| 40 | + * { |
| 41 | + * struct ListHead listeners_list_head; |
| 42 | + * event_handler_t handler; |
| 43 | + * listener_event_t fd; |
| 44 | + * }; |
| 45 | + * ``` |
| 46 | + * |
| 47 | + * and by including `platform_sys.h` header in `sys.c` before `listeners.h`. |
| 48 | + */ |
| 49 | + |
| 50 | +#include <stdbool.h> |
| 51 | + |
| 52 | +/** |
| 53 | + * @brief Add an event listener to the set of polled events. |
| 54 | + * |
| 55 | + * @details This function must be implemented and will typically access the |
| 56 | + * platform data from `glb` and add the event to the set. It is called by |
| 57 | + * `process_listener_handler` when a handler returns a new listener. It can be |
| 58 | + * called by `sys_register_listener`. It may just set a dirty flag. |
| 59 | + * |
| 60 | + * @param listener the listener to add to polling set |
| 61 | + * @param glb the global context |
| 62 | + */ |
| 63 | +static void event_listener_add_to_polling_set(struct EventListener *listener, GlobalContext *glb); |
| 64 | + |
| 65 | +/** |
| 66 | + * @brief Remove an event from the set of polled events. |
| 67 | + * |
| 68 | + * @details This function must be implemented and will typically access the |
| 69 | + * platform data from `glb` and remove the event to the set. It is called by |
| 70 | + * `process_listener_handler` when a handler returns NULL or a new listener. It |
| 71 | + * can be called by `sys_unregister_listener`. It may just set a dirty flag. |
| 72 | + * |
| 73 | + * Compared to `event_listener_add_to_polling_set`, the event listener may no |
| 74 | + * longer exist if it was freed by the handler. |
| 75 | + * |
| 76 | + * @param event the listener event to remove from polling set |
| 77 | + * @param glb the global context |
| 78 | + */ |
| 79 | +static void listener_event_remove_from_polling_set(listener_event_t event, GlobalContext *glb); |
| 80 | + |
| 81 | +/** |
| 82 | + * @brief Determiner if an event is a listener's event. |
| 83 | + * |
| 84 | + * @param listener the listener to test |
| 85 | + * @param event the event to test |
| 86 | + * @return true if event is the listener's event |
| 87 | + */ |
| 88 | +static bool event_listener_is_event(EventListener *listener, listener_event_t event); |
| 89 | + |
| 90 | +/** |
| 91 | + * @brief Process listener handlers, optionally in advancing order, especially useful with poll(2) which returns fd in the provided order. |
| 92 | + * |
| 93 | + * @param glb the global context |
| 94 | + * @param current_event the selected event |
| 95 | + * @param listeners the list of listeners (locked for writing) |
| 96 | + * @param item_ptr the current cursor or NULL to search in items |
| 97 | + * @param previous_ptr the previous cursor (ignored and can be NULL if item_ptr is NULL). |
| 98 | + * @return true if the current_event was found |
| 99 | + */ |
| 100 | +static inline bool process_listener_handler(GlobalContext *glb, listener_event_t current_event, struct ListHead *listeners, struct ListHead **item_ptr, struct ListHead **previous_ptr) |
| 101 | +{ |
| 102 | + bool result = false; |
| 103 | + struct ListHead *item; |
| 104 | + struct ListHead *previous; |
| 105 | + if (item_ptr) { |
| 106 | + item = *item_ptr; |
| 107 | + previous = *previous_ptr; |
| 108 | + } else { |
| 109 | + item = listeners->next; |
| 110 | + previous = listeners; |
| 111 | + } |
| 112 | + |
| 113 | + while (item != listeners) { |
| 114 | + struct ListHead *next = item->next; |
| 115 | + EventListener *listener = GET_LIST_ENTRY(item, EventListener, listeners_list_head); |
| 116 | + if (event_listener_is_event(listener, current_event)) { |
| 117 | + EventListener *new_listener = listener->handler(glb, listener); |
| 118 | + if (new_listener == NULL) { |
| 119 | + listener_event_remove_from_polling_set(current_event, glb); |
| 120 | + previous->next = next; |
| 121 | + next->prev = previous; |
| 122 | + item = next; |
| 123 | + } else if (new_listener != listener) { |
| 124 | + listener_event_remove_from_polling_set(current_event, glb); |
| 125 | + event_listener_add_to_polling_set(new_listener, glb); |
| 126 | + // Replace listener with new_listener in the list |
| 127 | + // listener was freed by handler. |
| 128 | + previous->next = &new_listener->listeners_list_head; |
| 129 | + next->prev = &new_listener->listeners_list_head; |
| 130 | + new_listener->listeners_list_head.prev = previous; |
| 131 | + new_listener->listeners_list_head.next = next; |
| 132 | + item = &new_listener->listeners_list_head; |
| 133 | + } |
| 134 | + result = true; |
| 135 | + break; |
| 136 | + } |
| 137 | + previous = item; |
| 138 | + item = next; |
| 139 | + } |
| 140 | + if (item_ptr) { |
| 141 | + *previous_ptr = previous; |
| 142 | + *item_ptr = item; |
| 143 | + } |
| 144 | + return result; |
| 145 | +} |
| 146 | + |
| 147 | +void sys_listener_destroy(struct ListHead *item) |
| 148 | +{ |
| 149 | + EventListener *listener = GET_LIST_ENTRY(item, EventListener, listeners_list_head); |
| 150 | + free(listener); |
| 151 | +} |
0 commit comments