From 139660d206a4a1c6e19095d6457551720a817d42 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:09:50 +0300 Subject: [PATCH] [FL-3846] Event Loop Timers (#3721) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement POC event loop tmers (not all edge cases are handled) * Use a separate ready list to allow for (re)starting and stopping of timers from callback * Improve the test application * Improve timer API and test application * Improve timeout calculation logic * Improve timer API, update documentation * Fix API usage error * Update doxygen comments * Revert the old (correct) check * Improve function naming * Check whether a timer was on the expired list before processing it * Implement tick callback * Add critical sections to improve timer consistency * Simplify event loop timer API * Remove redundant search * Refactor timer logic, use message queue * Simplify FuriEventLoopTimer API * Improve event loop timer logic * Update the f18 target * Remove superfluous clears * Correct f18 api symbols * Fix doxygen comments * Update .pvsconfig * Use a double push list instead of deque * Update .pvsconfig * Add pending callback functionality * Restore unprocessed flags when applicable * Refactor Dolphin app to use FuriEventLoop * Improve naming * Update naming some more * Fix a typo Co-authored-by: Silent * Fix wait time in example * Bump API version * Debug: multiple of 25 timings in event loop blink test * Separate FuriEventLoopTimer to its own set of files * Improve start time calculation for periodic timers * Do not use dynamic allocations for timer requests * Split the tick functionality in separate files, rearrange code * Improve timer queue handling * Properly reset GPIO pins in the test app * Properly initialise GPIO pins in the test app too * Furi: variable naming in event loop * Furi: fix spelling in event loop Co-authored-by: あく Co-authored-by: Silent --- .pvsconfig | 4 +- .../event_loop_blink_test/application.fam | 10 + .../event_loop_blink_test.c | 169 ++++++++++++ applications/services/dolphin/dolphin.c | 254 +++++++++++------- applications/services/dolphin/dolphin.h | 7 +- applications/services/dolphin/dolphin_i.h | 26 +- furi/core/event_loop.c | 170 ++++++------ furi/core/event_loop.h | 34 ++- furi/core/event_loop_i.h | 106 ++++++-- furi/core/event_loop_link_i.h | 35 +++ furi/core/event_loop_tick.c | 69 +++++ furi/core/event_loop_tick_i.h | 16 ++ furi/core/event_loop_timer.c | 215 +++++++++++++++ furi/core/event_loop_timer.h | 118 ++++++++ furi/core/event_loop_timer_i.h | 43 +++ furi/core/message_queue.c | 6 + furi/core/message_queue_i.h | 10 +- furi/furi.h | 1 + targets/f18/api_symbols.csv | 25 +- targets/f7/api_symbols.csv | 25 +- 20 files changed, 1087 insertions(+), 256 deletions(-) create mode 100644 applications/debug/event_loop_blink_test/application.fam create mode 100644 applications/debug/event_loop_blink_test/event_loop_blink_test.c create mode 100644 furi/core/event_loop_link_i.h create mode 100644 furi/core/event_loop_tick.c create mode 100644 furi/core/event_loop_tick_i.h create mode 100644 furi/core/event_loop_timer.c create mode 100644 furi/core/event_loop_timer.h create mode 100644 furi/core/event_loop_timer_i.h diff --git a/.pvsconfig b/.pvsconfig index b6001ca5c..674231d22 100644 --- a/.pvsconfig +++ b/.pvsconfig @@ -3,10 +3,12 @@ //-V:M_EACH:1048,1044 //-V:ARRAY_DEF:760,747,568,776,729,712,654,1103 //-V:LIST_DEF:760,747,568,712,729,654,776,1103 +//-V:LIST_DUAL_PUSH_DEF:524,760,774 //-V:BPTREE_DEF2:779,1086,557,773,512 //-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685,1103 //-V:ALGO_DEF:1048,747,1044 //-V:TUPLE_DEF2:524,590,1001,760 +//-V:DEQUE_DEF:658,747,760 # Non-severe malloc/null pointer deref warnings //-V::522:2,3 @@ -43,4 +45,4 @@ //-V:with_view_model:1044,1048 # Examples -//V_EXCLUDE_PATH applications/examples/ \ No newline at end of file +//V_EXCLUDE_PATH applications/examples/ diff --git a/applications/debug/event_loop_blink_test/application.fam b/applications/debug/event_loop_blink_test/application.fam new file mode 100644 index 000000000..7d42ad339 --- /dev/null +++ b/applications/debug/event_loop_blink_test/application.fam @@ -0,0 +1,10 @@ +App( + appid="event_loop_blink_test", + name="Event Loop Blink Test", + apptype=FlipperAppType.DEBUG, + entry_point="event_loop_blink_test_app", + requires=["input"], + stack_size=1 * 1024, + order=20, + fap_category="Debug", +) diff --git a/applications/debug/event_loop_blink_test/event_loop_blink_test.c b/applications/debug/event_loop_blink_test/event_loop_blink_test.c new file mode 100644 index 000000000..5c7e0ce55 --- /dev/null +++ b/applications/debug/event_loop_blink_test/event_loop_blink_test.c @@ -0,0 +1,169 @@ +#include +#include + +#include +#include +#include + +#include + +#define TAG "EventLoopBlinkTest" + +#define TIMER_COUNT (6U) + +typedef struct { + FuriEventLoop* event_loop; + FuriMessageQueue* input_queue; + FuriEventLoopTimer* timers[TIMER_COUNT]; +} EventLoopBlinkTestApp; + +static const GpioPin* blink_gpio_pins[] = { + &gpio_ext_pa7, + &gpio_ext_pa6, + &gpio_ext_pa4, + &gpio_ext_pb3, + &gpio_ext_pb2, + &gpio_ext_pc3, +}; + +static_assert(COUNT_OF(blink_gpio_pins) == TIMER_COUNT); + +static const uint32_t timer_intervals[] = { + 25, + 50, + 100, + 200, + 400, + 800, +}; + +static_assert(COUNT_OF(timer_intervals) == TIMER_COUNT); + +static void blink_gpio_init(void) { + for(size_t i = 0; i < TIMER_COUNT; ++i) { + furi_hal_gpio_init_simple(blink_gpio_pins[i], GpioModeOutputPushPull); + furi_hal_gpio_write(blink_gpio_pins[i], false); + } + + furi_hal_gpio_init_simple(&gpio_ext_pc0, GpioModeOutputPushPull); + furi_hal_gpio_write(&gpio_ext_pc0, false); +} + +static void blink_gpio_deinit(void) { + for(size_t i = 0; i < TIMER_COUNT; ++i) { + furi_hal_gpio_write(blink_gpio_pins[i], false); + furi_hal_gpio_init_simple(blink_gpio_pins[i], GpioModeAnalog); + } + + furi_hal_gpio_write(&gpio_ext_pc0, false); + furi_hal_gpio_init_simple(&gpio_ext_pc0, GpioModeAnalog); +} + +static void view_port_draw_callback(Canvas* canvas, void* context) { + UNUSED(context); + canvas_clear(canvas); + elements_text_box( + canvas, + 0, + 0, + canvas_width(canvas), + canvas_height(canvas), + AlignCenter, + AlignCenter, + "\e#Event Loop Timers Test\e#\n" + "Press buttons\n" + "to enable or disable timers\n" + "\e#Exit\e# = long press \e#Back\e#", + false); +} + +static void view_port_input_callback(InputEvent* input_event, void* context) { + EventLoopBlinkTestApp* app = context; + furi_message_queue_put(app->input_queue, input_event, 0); +} + +static bool input_queue_callback(FuriMessageQueue* queue, void* context) { + EventLoopBlinkTestApp* app = context; + + InputEvent event; + FuriStatus status = furi_message_queue_get(queue, &event, 0); + furi_assert(status == FuriStatusOk); + + if(event.type == InputTypeShort) { + const size_t timer_idx = event.key; + furi_assert(timer_idx < TIMER_COUNT); + + FuriEventLoopTimer* timer = app->timers[timer_idx]; + + if(furi_event_loop_timer_is_running(timer)) { + furi_event_loop_timer_stop(timer); + } else { + furi_event_loop_timer_restart(timer); + } + + } else if(event.type == InputTypeLong) { + if(event.key == InputKeyBack) { + furi_event_loop_stop(app->event_loop); + } + } + + return true; +} + +static void blink_timer_callback(void* context) { + const GpioPin* gpio = blink_gpio_pins[(size_t)context]; + furi_hal_gpio_write(gpio, !furi_hal_gpio_read(gpio)); +} + +static void event_loop_tick_callback(void* context) { + UNUSED(context); + furi_hal_gpio_write(&gpio_ext_pc0, !furi_hal_gpio_read(&gpio_ext_pc0)); +} + +int32_t event_loop_blink_test_app(void* arg) { + UNUSED(arg); + + blink_gpio_init(); + + EventLoopBlinkTestApp app; + + app.event_loop = furi_event_loop_alloc(); + app.input_queue = furi_message_queue_alloc(3, sizeof(InputEvent)); + + for(size_t i = 0; i < TIMER_COUNT; ++i) { + app.timers[i] = furi_event_loop_timer_alloc( + app.event_loop, blink_timer_callback, FuriEventLoopTimerTypePeriodic, (void*)i); + furi_event_loop_timer_start(app.timers[i], timer_intervals[i]); + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, view_port_draw_callback, &app); + view_port_input_callback_set(view_port, view_port_input_callback, &app); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + furi_event_loop_tick_set(app.event_loop, 500, event_loop_tick_callback, &app); + furi_event_loop_message_queue_subscribe( + app.event_loop, app.input_queue, FuriEventLoopEventIn, input_queue_callback, &app); + + furi_event_loop_run(app.event_loop); + + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + + furi_record_close(RECORD_GUI); + + furi_event_loop_message_queue_unsubscribe(app.event_loop, app.input_queue); + furi_message_queue_free(app.input_queue); + + for(size_t i = 0; i < TIMER_COUNT; ++i) { + furi_event_loop_timer_free(app.timers[i]); + } + + furi_event_loop_free(app.event_loop); + + blink_gpio_deinit(); + + return 0; +} diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 4a75241e6..4b4ac0674 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -1,22 +1,48 @@ -#include "dolphin.h" -#include "helpers/dolphin_state.h" #include "dolphin_i.h" + #include -#include -#include -#define DOLPHIN_LOCK_EVENT_FLAG (0x1) #define TAG "Dolphin" -#define HOURS_IN_TICKS(x) ((x) * 60 * 60 * 1000) -static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin); +#define DOLPHIN_LOCK_EVENT_FLAG (0x1) +#define EVENT_QUEUE_SIZE (8) + +#define SECONDS_IN_TICKS(x) ((x) * 1000UL) +#define MINUTES_IN_TICKS(x) (SECONDS_IN_TICKS(x) * 60UL) +#define HOURS_IN_TICKS(x) (MINUTES_IN_TICKS(x) * 60UL) +#define DATE_IN_TICKS(h, m, s) (HOURS_IN_TICKS(h) + MINUTES_IN_TICKS(m) + SECONDS_IN_TICKS(s)) + +#define FLUSH_TIMEOUT_TICKS (SECONDS_IN_TICKS(30UL)) + +#ifndef DOLPHIN_DEBUG +#define BUTTHURT_INCREASE_PERIOD_TICKS (HOURS_IN_TICKS(48UL)) +#define CLEAR_LIMITS_PERIOD_TICKS (HOURS_IN_TICKS(24UL)) +#define CLEAR_LIMITS_UPDATE_PERIOD_TICKS (HOURS_IN_TICKS(1UL)) +#else +#define BUTTHURT_INCREASE_PERIOD_TICKS (SECONDS_IN_TICKS(30UL)) +#define CLEAR_LIMITS_PERIOD_TICKS (MINUTES_IN_TICKS(1)) +#define CLEAR_LIMITS_UPDATE_PERIOD_TICKS (SECONDS_IN_TICKS(5UL)) +#endif + +#define CLEAR_LIMITS_UPDATE_THRESHOLD_TICKS (MINUTES_IN_TICKS(5UL)) + +#define CLEAR_LIMITS_TIME_HOURS (5UL) +#define CLEAR_LIMITS_TIME_TICKS (HOURS_IN_TICKS(CLEAR_LIMITS_TIME_HOURS)) + +static void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event); +static void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event); + +// Public API void dolphin_deed(DolphinDeed deed) { - Dolphin* dolphin = (Dolphin*)furi_record_open(RECORD_DOLPHIN); + Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); + DolphinEvent event; event.type = DolphinEventTypeDeed; event.deed = deed; + dolphin_event_send_async(dolphin, &event); + furi_record_close(RECORD_DOLPHIN); } @@ -43,52 +69,75 @@ void dolphin_flush(Dolphin* dolphin) { dolphin_event_send_wait(dolphin, &event); } -void dolphin_butthurt_timer_callback(void* context) { - Dolphin* dolphin = context; - furi_assert(dolphin); +void dolphin_upgrade_level(Dolphin* dolphin) { + furi_check(dolphin); DolphinEvent event; - event.type = DolphinEventTypeIncreaseButthurt; + event.type = DolphinEventTypeLevel; + dolphin_event_send_async(dolphin, &event); } -void dolphin_flush_timer_callback(void* context) { +FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) { + furi_check(dolphin); + return dolphin->pubsub; +} + +// Private functions + +static void dolphin_butthurt_timer_callback(void* context) { Dolphin* dolphin = context; furi_assert(dolphin); - DolphinEvent event; - event.type = DolphinEventTypeFlush; - dolphin_event_send_async(dolphin, &event); + FURI_LOG_I(TAG, "Increase butthurt"); + dolphin_state_butthurted(dolphin->state); + dolphin_state_save(dolphin->state); } -void dolphin_clear_limits_timer_callback(void* context) { +static void dolphin_flush_timer_callback(void* context) { Dolphin* dolphin = context; furi_assert(dolphin); - furi_timer_start(dolphin->clear_limits_timer, HOURS_IN_TICKS(24)); - - DolphinEvent event; - event.type = DolphinEventTypeClearLimits; - dolphin_event_send_async(dolphin, &event); + FURI_LOG_I(TAG, "Flush stats"); + dolphin_state_save(dolphin->state); } -Dolphin* dolphin_alloc(void) { +static void dolphin_clear_limits_timer_callback(void* context) { + Dolphin* dolphin = context; + furi_assert(dolphin); + + FURI_LOG_I(TAG, "Clear limits"); + dolphin_state_clear_limits(dolphin->state); + dolphin_state_save(dolphin->state); +} + +static Dolphin* dolphin_alloc(void) { Dolphin* dolphin = malloc(sizeof(Dolphin)); dolphin->state = dolphin_state_alloc(); - dolphin->event_queue = furi_message_queue_alloc(8, sizeof(DolphinEvent)); dolphin->pubsub = furi_pubsub_alloc(); - dolphin->butthurt_timer = - furi_timer_alloc(dolphin_butthurt_timer_callback, FuriTimerTypePeriodic, dolphin); - dolphin->flush_timer = - furi_timer_alloc(dolphin_flush_timer_callback, FuriTimerTypeOnce, dolphin); - dolphin->clear_limits_timer = - furi_timer_alloc(dolphin_clear_limits_timer_callback, FuriTimerTypePeriodic, dolphin); + dolphin->event_queue = furi_message_queue_alloc(EVENT_QUEUE_SIZE, sizeof(DolphinEvent)); + dolphin->event_loop = furi_event_loop_alloc(); + + dolphin->butthurt_timer = furi_event_loop_timer_alloc( + dolphin->event_loop, + dolphin_butthurt_timer_callback, + FuriEventLoopTimerTypePeriodic, + dolphin); + + dolphin->flush_timer = furi_event_loop_timer_alloc( + dolphin->event_loop, dolphin_flush_timer_callback, FuriEventLoopTimerTypeOnce, dolphin); + + dolphin->clear_limits_timer = furi_event_loop_timer_alloc( + dolphin->event_loop, + dolphin_clear_limits_timer_callback, + FuriEventLoopTimerTypePeriodic, + dolphin); return dolphin; } -void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) { +static void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) { furi_assert(dolphin); furi_assert(event); event->flag = NULL; @@ -96,7 +145,7 @@ void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event) { furi_message_queue_put(dolphin->event_queue, event, FuriWaitForever) == FuriStatusOk); } -void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) { +static void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) { furi_assert(dolphin); furi_assert(event); @@ -110,39 +159,81 @@ void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event) { furi_event_flag_free(event->flag); } -void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event) { - UNUSED(dolphin); +static void dolphin_event_release(DolphinEvent* event) { if(event->flag) { furi_event_flag_set(event->flag, DOLPHIN_LOCK_EVENT_FLAG); } } -FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) { - furi_check(dolphin); - return dolphin->pubsub; -} +static void dolphin_update_clear_limits_timer_period(void* context) { + furi_assert(context); + Dolphin* dolphin = context; -static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { - furi_assert(dolphin); - uint32_t now_ticks = furi_get_tick(); - uint32_t timer_expires_at = furi_timer_get_expire_time(dolphin->clear_limits_timer); + uint32_t time_to_clear_limits = + furi_event_loop_timer_get_remaining_time(dolphin->clear_limits_timer); - if((timer_expires_at - now_ticks) > HOURS_IN_TICKS(0.1)) { + if(time_to_clear_limits > CLEAR_LIMITS_UPDATE_THRESHOLD_TICKS) { DateTime date; furi_hal_rtc_get_datetime(&date); - uint32_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000; - uint32_t time_to_clear_limits = 0; - if(date.hour < 5) { - time_to_clear_limits = HOURS_IN_TICKS(5) - now_time_in_ms; + const uint32_t now_time_ticks = DATE_IN_TICKS(date.hour, date.minute, date.second); + + if(date.hour < CLEAR_LIMITS_TIME_HOURS) { + time_to_clear_limits = CLEAR_LIMITS_TIME_TICKS - now_time_ticks; } else { - time_to_clear_limits = HOURS_IN_TICKS(24 + 5) - now_time_in_ms; + time_to_clear_limits = + CLEAR_LIMITS_PERIOD_TICKS + CLEAR_LIMITS_TIME_TICKS - now_time_ticks; } - furi_timer_start(dolphin->clear_limits_timer, time_to_clear_limits); + furi_event_loop_timer_start(dolphin->clear_limits_timer, time_to_clear_limits); } + + FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits); } +static bool dolphin_process_event(FuriMessageQueue* queue, void* context) { + UNUSED(queue); + + Dolphin* dolphin = context; + DolphinEvent event; + + FuriStatus status = furi_message_queue_get(dolphin->event_queue, &event, 0); + furi_check(status == FuriStatusOk); + + if(event.type == DolphinEventTypeDeed) { + dolphin_state_on_deed(dolphin->state, event.deed); + + DolphinPubsubEvent event = DolphinPubsubEventUpdate; + furi_pubsub_publish(dolphin->pubsub, &event); + furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS); + furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS); + + } else if(event.type == DolphinEventTypeStats) { + event.stats->icounter = dolphin->state->data.icounter; + event.stats->butthurt = dolphin->state->data.butthurt; + event.stats->timestamp = dolphin->state->data.timestamp; + event.stats->level = dolphin_get_level(dolphin->state->data.icounter); + event.stats->level_up_is_pending = + !dolphin_state_xp_to_levelup(dolphin->state->data.icounter); + + } else if(event.type == DolphinEventTypeFlush) { + furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS); + + } else if(event.type == DolphinEventTypeLevel) { + dolphin_state_increase_level(dolphin->state); + furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS); + + } else { + furi_crash(); + } + + dolphin_event_release(&event); + + return true; +} + +// Application thread + int32_t dolphin_srv(void* p) { UNUSED(p); @@ -157,54 +248,27 @@ int32_t dolphin_srv(void* p) { furi_record_create(RECORD_DOLPHIN, dolphin); dolphin_state_load(dolphin->state); - furi_timer_restart(dolphin->butthurt_timer, HOURS_IN_TICKS(2 * 24)); - dolphin_update_clear_limits_timer_period(dolphin); - furi_timer_restart(dolphin->clear_limits_timer, HOURS_IN_TICKS(24)); - DolphinEvent event; - while(1) { - if(furi_message_queue_get(dolphin->event_queue, &event, HOURS_IN_TICKS(1)) == - FuriStatusOk) { - if(event.type == DolphinEventTypeDeed) { - dolphin_state_on_deed(dolphin->state, event.deed); - DolphinPubsubEvent event = DolphinPubsubEventUpdate; - furi_pubsub_publish(dolphin->pubsub, &event); - furi_timer_restart(dolphin->butthurt_timer, HOURS_IN_TICKS(2 * 24)); - furi_timer_restart(dolphin->flush_timer, 30 * 1000); - } else if(event.type == DolphinEventTypeStats) { - event.stats->icounter = dolphin->state->data.icounter; - event.stats->butthurt = dolphin->state->data.butthurt; - event.stats->timestamp = dolphin->state->data.timestamp; - event.stats->level = dolphin_get_level(dolphin->state->data.icounter); - event.stats->level_up_is_pending = - !dolphin_state_xp_to_levelup(dolphin->state->data.icounter); - } else if(event.type == DolphinEventTypeFlush) { - FURI_LOG_I(TAG, "Flush stats"); - dolphin_state_save(dolphin->state); - } else if(event.type == DolphinEventTypeClearLimits) { - FURI_LOG_I(TAG, "Clear limits"); - dolphin_state_clear_limits(dolphin->state); - dolphin_state_save(dolphin->state); - } else if(event.type == DolphinEventTypeIncreaseButthurt) { - FURI_LOG_I(TAG, "Increase butthurt"); - dolphin_state_butthurted(dolphin->state); - dolphin_state_save(dolphin->state); - } - dolphin_event_release(dolphin, &event); - } else { - /* once per hour check rtc time is not changed */ - dolphin_update_clear_limits_timer_period(dolphin); - } - } + furi_event_loop_message_queue_subscribe( + dolphin->event_loop, + dolphin->event_queue, + FuriEventLoopEventIn, + dolphin_process_event, + dolphin); - furi_crash("That was unexpected"); + furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS); + furi_event_loop_timer_start(dolphin->clear_limits_timer, CLEAR_LIMITS_PERIOD_TICKS); + + furi_event_loop_tick_set( + dolphin->event_loop, + CLEAR_LIMITS_UPDATE_PERIOD_TICKS, + dolphin_update_clear_limits_timer_period, + dolphin); + + furi_event_loop_pend_callback( + dolphin->event_loop, dolphin_update_clear_limits_timer_period, dolphin); + + furi_event_loop_run(dolphin->event_loop); return 0; } - -void dolphin_upgrade_level(Dolphin* dolphin) { - furi_check(dolphin); - - dolphin_state_increase_level(dolphin->state); - dolphin_flush(dolphin); -} diff --git a/applications/services/dolphin/dolphin.h b/applications/services/dolphin/dolphin.h index 1035247e7..01da7f3f2 100644 --- a/applications/services/dolphin/dolphin.h +++ b/applications/services/dolphin/dolphin.h @@ -1,10 +1,9 @@ #pragma once -#include "helpers/dolphin_deed.h" - -#include -#include #include +#include + +#include "helpers/dolphin_deed.h" #ifdef __cplusplus extern "C" { diff --git a/applications/services/dolphin/dolphin_i.h b/applications/services/dolphin/dolphin_i.h index 666e03942..d4add808a 100644 --- a/applications/services/dolphin/dolphin_i.h +++ b/applications/services/dolphin/dolphin_i.h @@ -1,8 +1,8 @@ #pragma once -#include #include -#include + +#include #include "dolphin.h" #include "helpers/dolphin_state.h" @@ -11,8 +11,7 @@ typedef enum { DolphinEventTypeDeed, DolphinEventTypeStats, DolphinEventTypeFlush, - DolphinEventTypeIncreaseButthurt, - DolphinEventTypeClearLimits, + DolphinEventTypeLevel, } DolphinEventType; typedef struct { @@ -25,20 +24,11 @@ typedef struct { } DolphinEvent; struct Dolphin { - // State DolphinState* state; - // Queue - FuriMessageQueue* event_queue; FuriPubSub* pubsub; - FuriTimer* butthurt_timer; - FuriTimer* flush_timer; - FuriTimer* clear_limits_timer; + FuriMessageQueue* event_queue; + FuriEventLoop* event_loop; + FuriEventLoopTimer* butthurt_timer; + FuriEventLoopTimer* flush_timer; + FuriEventLoopTimer* clear_limits_timer; }; - -Dolphin* dolphin_alloc(void); - -void dolphin_event_send_async(Dolphin* dolphin, DolphinEvent* event); - -void dolphin_event_send_wait(Dolphin* dolphin, DolphinEvent* event); - -void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event); diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index f38a67657..26401c84b 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -5,32 +5,14 @@ #include "check.h" #include "thread.h" -#include -#include - #include #include #define TAG "FuriEventLoop" -struct FuriEventLoopItem { - // Source - FuriEventLoop* owner; - - // Tracking item - const FuriEventLoopContract* contract; - void* object; - FuriEventLoopEvent event; - - // Callback and context - FuriEventLoopMessageQueueCallback callback; - void* callback_context; - - // Waiting list - ILIST_INTERFACE(WaitingList, struct FuriEventLoopItem); -}; - -ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST) +/* + * Private functions + */ static FuriEventLoopItem* furi_event_loop_item_alloc( FuriEventLoop* owner, @@ -47,56 +29,17 @@ static void furi_event_loop_item_set_callback( static void furi_event_loop_item_notify(FuriEventLoopItem* instance); -/* Event Loop RB tree */ -#define FURI_EVENT_LOOP_TREE_RANK (4) +static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { + for(; !PendingQueue_empty_p(instance->pending_queue); + PendingQueue_pop_back(NULL, instance->pending_queue)) { + const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue); + item->callback(item->context); + } +} -BPTREE_DEF2( // NOLINT - FuriEventLoopTree, - FURI_EVENT_LOOP_TREE_RANK, - void*, /* pointer to object we track */ - M_PTR_OPLIST, - FuriEventLoopItem*, /* pointer to the FuriEventLoopItem */ - M_PTR_OPLIST) - -#define M_OPL_FuriEventLoopTree_t() BPTREE_OPLIST(FuriEventLoopTree, M_POD_OPLIST) - -#define FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX (2) - -typedef enum { - FuriEventLoopFlagEvent = (1 << 0), - FuriEventLoopFlagStop = (1 << 1), -} FuriEventLoopFlag; - -#define FuriEventLoopFlagAll (FuriEventLoopFlagEvent | FuriEventLoopFlagStop) - -typedef enum { - FuriEventLoopProcessStatusComplete, - FuriEventLoopProcessStatusIncomplete, - FuriEventLoopProcessStatusAgain, -} FuriEventLoopProcessStatus; - -typedef enum { - FuriEventLoopStateIdle, - FuriEventLoopStateProcessing, -} FuriEventLoopState; - -struct FuriEventLoop { - // Only works if all operations are done from the same thread - FuriThreadId thread_id; - - // Poller state - volatile FuriEventLoopState state; - - // Tree - FuriEventLoopTree_t tree; - // Tree waiting list - WaitingList_t waiting_list; - - // Tick event - uint32_t tick_interval; - FuriEventLoopTickCallback tick_callback; - void* tick_callback_context; -}; +/* + * Main public API + */ FuriEventLoop* furi_event_loop_alloc(void) { FuriEventLoop* instance = malloc(sizeof(FuriEventLoop)); @@ -105,6 +48,9 @@ FuriEventLoop* furi_event_loop_alloc(void) { FuriEventLoopTree_init(instance->tree); WaitingList_init(instance->waiting_list); + TimerList_init(instance->timer_list); + TimerQueue_init(instance->timer_queue); + PendingQueue_init(instance->pending_queue); // Clear notification state and value xTaskNotifyStateClearIndexed(instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX); @@ -117,14 +63,19 @@ FuriEventLoop* furi_event_loop_alloc(void) { void furi_event_loop_free(FuriEventLoop* instance) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); + furi_check(instance->state == FuriEventLoopStateStopped); + + furi_event_loop_process_timer_queue(instance); + furi_check(TimerList_empty_p(instance->timer_list)); FuriEventLoopTree_clear(instance->tree); + PendingQueue_clear(instance->pending_queue); uint32_t flags = 0; BaseType_t ret = xTaskNotifyWaitIndexed( FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, 0); if(ret == pdTRUE) { - FURI_LOG_D(TAG, "Some events was not processed: 0x%lx", flags); + FURI_LOG_D(TAG, "Some events were not processed: 0x%lx", flags); } free(instance); @@ -145,33 +96,51 @@ static FuriEventLoopProcessStatus } } +static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) { + if(flags) { + xTaskNotifyIndexed( + instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, flags, eSetBits); + } +} + void furi_event_loop_run(FuriEventLoop* instance) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); + furi_event_loop_init_tick(instance); + furi_thread_set_signal_callback( instance->thread_id, furi_event_loop_signal_callback, instance); - uint32_t timeout = instance->tick_callback ? instance->tick_interval : FuriWaitForever; - while(true) { + instance->state = FuriEventLoopStateIdle; + + const TickType_t ticks_to_sleep = + MIN(furi_event_loop_get_timer_wait_time(instance), + furi_event_loop_get_tick_wait_time(instance)); + uint32_t flags = 0; BaseType_t ret = xTaskNotifyWaitIndexed( - FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, timeout); + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, ticks_to_sleep); instance->state = FuriEventLoopStateProcessing; + if(ret == pdTRUE) { if(flags & FuriEventLoopFlagStop) { - instance->state = FuriEventLoopStateIdle; + instance->state = FuriEventLoopStateStopped; break; + } else if(flags & FuriEventLoopFlagEvent) { FuriEventLoopItem* item = NULL; FURI_CRITICAL_ENTER(); + if(!WaitingList_empty_p(instance->waiting_list)) { item = WaitingList_pop_front(instance->waiting_list); WaitingList_init_field(item); } + FURI_CRITICAL_EXIT(); + if(item) { while(true) { FuriEventLoopProcessStatus ret = @@ -189,13 +158,23 @@ void furi_event_loop_run(FuriEventLoop* instance) { } } } + + furi_event_loop_restore_flags(instance, flags & ~FuriEventLoopFlagEvent); + + } else if(flags & FuriEventLoopFlagTimer) { + furi_event_loop_process_timer_queue(instance); + furi_event_loop_restore_flags(instance, flags & ~FuriEventLoopFlagTimer); + + } else if(flags & FuriEventLoopFlagPending) { + furi_event_loop_process_pending_callbacks(instance); + + } else { + furi_crash(); } - } else { - if(instance->tick_callback) { - instance->tick_callback(instance->tick_callback_context); - } + + } else if(!furi_event_loop_process_expired_timers(instance)) { + furi_event_loop_process_tick(instance); } - instance->state = FuriEventLoopStateIdle; } furi_thread_set_signal_callback(instance->thread_id, NULL, NULL); @@ -208,20 +187,33 @@ void furi_event_loop_stop(FuriEventLoop* instance) { instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagStop, eSetBits); } -void furi_event_loop_tick_set( +/* + * Public deferred function call API + */ + +void furi_event_loop_pend_callback( FuriEventLoop* instance, - uint32_t interval, - FuriEventLoopTickCallback callback, + FuriEventLoopPendingCallback callback, void* context) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); - furi_check(callback ? interval > 0 : true); + furi_check(callback); - instance->tick_interval = interval; - instance->tick_callback = callback; - instance->tick_callback_context = context; + const FuriEventLoopPendingQueueItem item = { + .callback = callback, + .context = context, + }; + + PendingQueue_push_front(instance->pending_queue, item); + + xTaskNotifyIndexed( + instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagPending, eSetBits); } +/* + * Message queue API + */ + void furi_event_loop_message_queue_subscribe( FuriEventLoop* instance, FuriMessageQueue* message_queue, @@ -230,7 +222,7 @@ void furi_event_loop_message_queue_subscribe( void* context) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); - furi_check(instance->state == FuriEventLoopStateIdle); + furi_check(instance->state == FuriEventLoopStateStopped); furi_check(message_queue); FURI_CRITICAL_ENTER(); @@ -267,7 +259,7 @@ void furi_event_loop_message_queue_unsubscribe( FuriEventLoop* instance, FuriMessageQueue* message_queue) { furi_check(instance); - furi_check(instance->state == FuriEventLoopStateIdle); + furi_check(instance->state == FuriEventLoopStateStopped); furi_check(instance->thread_id == furi_thread_get_current_id()); FURI_CRITICAL_ENTER(); diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index 7221a90bc..9ae9f6c4d 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -34,7 +34,7 @@ typedef struct FuriEventLoop FuriEventLoop; * Couple things to keep in mind: * - You can have 1 event_loop per 1 thread * - You can not use event_loop instance in the other thread - * - Do not use blocking api to query object delegated to Event Loop + * - Do not use blocking API to query object delegated to Event Loop * * @return The Event Loop instance */ @@ -72,8 +72,10 @@ typedef void (*FuriEventLoopTickCallback)(void* context); /** Set Event Loop tick callback * - * Tick callback called after specified inactivity time. It's not periodic. If - * Event Loop is busy then ticks will be skipped. + * Tick callback is called periodically after specified inactivity time. + * It acts like a low-priority timer: it will only fire if there is time + * left after processing the synchronization primitives and the regular timers. + * Therefore, it is not monotonic: ticks will be skipped if the event loop is busy. * * @param instance The Event Loop instance * @param[in] interval The tick interval @@ -86,6 +88,32 @@ void furi_event_loop_tick_set( FuriEventLoopTickCallback callback, void* context); +/* + * Deferred function call API + */ + +/** + * @brief Timer callback type for functions to be called in a deferred manner. + * + * @param[in,out] context pointer to a user-specific object that was provided during + * furi_event_loop_pend_callback() call + */ +typedef void (*FuriEventLoopPendingCallback)(void* context); + +/** + * @brief Call a function when all preceding timer commands are processed + * + * This function may be useful to call another function when the event loop has been started. + * + * @param[in,out] instance pointer to the current FuriEventLoop instance + * @param[in] callback pointer to the callback to be executed when previous commands have been processed + * @param[in,out] context pointer to a user-specific object (will be passed to the callback) + */ +void furi_event_loop_pend_callback( + FuriEventLoop* instance, + FuriEventLoopPendingCallback callback, + void* context); + /* * Message queue related APIs */ diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 5c0b144a1..cd1014867 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -1,35 +1,97 @@ #pragma once #include "event_loop.h" +#include "event_loop_link_i.h" +#include "event_loop_timer_i.h" +#include "event_loop_tick_i.h" -#ifdef __cplusplus -extern "C" { -#endif +#include +#include +#include -typedef struct FuriEventLoopItem FuriEventLoopItem; +#include "thread.h" -/* Link between Event Loop */ +struct FuriEventLoopItem { + // Source + FuriEventLoop* owner; + + // Tracking item + const FuriEventLoopContract* contract; + void* object; + FuriEventLoopEvent event; + + // Callback and context + FuriEventLoopMessageQueueCallback callback; + void* callback_context; + + // Waiting list + ILIST_INTERFACE(WaitingList, struct FuriEventLoopItem); +}; + +ILIST_DEF(WaitingList, FuriEventLoopItem, M_POD_OPLIST) + +/* Event Loop RB tree */ +#define FURI_EVENT_LOOP_TREE_RANK (4) + +BPTREE_DEF2( // NOLINT + FuriEventLoopTree, + FURI_EVENT_LOOP_TREE_RANK, + void*, /* pointer to object we track */ + M_PTR_OPLIST, + FuriEventLoopItem*, /* pointer to the FuriEventLoopItem */ + M_PTR_OPLIST) + +#define M_OPL_FuriEventLoopTree_t() BPTREE_OPLIST(FuriEventLoopTree, M_POD_OPLIST) + +#define FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX (2) + +typedef enum { + FuriEventLoopFlagEvent = (1 << 0), + FuriEventLoopFlagStop = (1 << 1), + FuriEventLoopFlagTimer = (1 << 2), + FuriEventLoopFlagPending = (1 << 3), +} FuriEventLoopFlag; + +#define FuriEventLoopFlagAll \ + (FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \ + FuriEventLoopFlagPending) + +typedef enum { + FuriEventLoopProcessStatusComplete, + FuriEventLoopProcessStatusIncomplete, + FuriEventLoopProcessStatusAgain, +} FuriEventLoopProcessStatus; + +typedef enum { + FuriEventLoopStateStopped, + FuriEventLoopStateIdle, + FuriEventLoopStateProcessing, +} FuriEventLoopState; typedef struct { - FuriEventLoopItem* item_in; - FuriEventLoopItem* item_out; -} FuriEventLoopLink; + FuriEventLoopPendingCallback callback; + void* context; +} FuriEventLoopPendingQueueItem; -void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent event); +LIST_DUAL_PUSH_DEF(PendingQueue, FuriEventLoopPendingQueueItem, M_POD_OPLIST) -/* Contract between event loop and an object */ +struct FuriEventLoop { + // Only works if all operations are done from the same thread + FuriThreadId thread_id; -typedef FuriEventLoopLink* (*FuriEventLoopContractGetLink)(void* object); + // Poller state + volatile FuriEventLoopState state; -typedef uint32_t (*FuriEventLoopContractGetLevel)(void* object, FuriEventLoopEvent event); + // Event handling + FuriEventLoopTree_t tree; + WaitingList_t waiting_list; -typedef struct { - const FuriEventLoopContractGetLink get_link; - const FuriEventLoopContractGetLevel get_level; -} FuriEventLoopContract; - -bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context); - -#ifdef __cplusplus -} -#endif + // Active timer list + TimerList_t timer_list; + // Timer request queue + TimerQueue_t timer_queue; + // Pending callback queue + PendingQueue_t pending_queue; + // Tick event + FuriEventLoopTick tick; +}; diff --git a/furi/core/event_loop_link_i.h b/furi/core/event_loop_link_i.h new file mode 100644 index 000000000..5c0b144a1 --- /dev/null +++ b/furi/core/event_loop_link_i.h @@ -0,0 +1,35 @@ +#pragma once + +#include "event_loop.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FuriEventLoopItem FuriEventLoopItem; + +/* Link between Event Loop */ + +typedef struct { + FuriEventLoopItem* item_in; + FuriEventLoopItem* item_out; +} FuriEventLoopLink; + +void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent event); + +/* Contract between event loop and an object */ + +typedef FuriEventLoopLink* (*FuriEventLoopContractGetLink)(void* object); + +typedef uint32_t (*FuriEventLoopContractGetLevel)(void* object, FuriEventLoopEvent event); + +typedef struct { + const FuriEventLoopContractGetLink get_link; + const FuriEventLoopContractGetLevel get_level; +} FuriEventLoopContract; + +bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/furi/core/event_loop_tick.c b/furi/core/event_loop_tick.c new file mode 100644 index 000000000..b81c48933 --- /dev/null +++ b/furi/core/event_loop_tick.c @@ -0,0 +1,69 @@ +#include "event_loop_i.h" + +#include +#include + +#include + +/** + * Private functions + */ + +static inline uint32_t furi_event_loop_tick_get_elapsed_time(const FuriEventLoop* instance) { + return xTaskGetTickCount() - instance->tick.prev_time; +} + +static inline uint32_t furi_event_loop_tick_get_remaining_time(const FuriEventLoop* instance) { + const uint32_t elapsed_time = furi_event_loop_tick_get_elapsed_time(instance); + return elapsed_time < instance->tick.interval ? instance->tick.interval - elapsed_time : 0; +} + +static inline bool furi_event_loop_tick_is_expired(const FuriEventLoop* instance) { + return furi_event_loop_tick_get_elapsed_time(instance) >= instance->tick.interval; +} + +/* + * Private tick API + */ + +void furi_event_loop_init_tick(FuriEventLoop* instance) { + if(instance->tick.callback) { + instance->tick.prev_time = xTaskGetTickCount(); + } +} + +void furi_event_loop_process_tick(FuriEventLoop* instance) { + if(instance->tick.callback && furi_event_loop_tick_is_expired(instance)) { + instance->tick.prev_time += instance->tick.interval; + instance->tick.callback(instance->tick.callback_context); + } +} + +uint32_t furi_event_loop_get_tick_wait_time(const FuriEventLoop* instance) { + uint32_t wait_time = FuriWaitForever; + + if(instance->tick.callback) { + wait_time = furi_event_loop_tick_get_remaining_time(instance); + } + + return wait_time; +} + +/* + * Public tick API + */ + +void furi_event_loop_tick_set( + FuriEventLoop* instance, + uint32_t interval, + FuriEventLoopTickCallback callback, + void* context) { + furi_check(instance); + furi_check(instance->thread_id == furi_thread_get_current_id()); + furi_check(callback ? interval > 0 : true); + + instance->tick.callback = callback; + instance->tick.callback_context = context; + instance->tick.interval = interval; + instance->tick.prev_time = xTaskGetTickCount(); +} diff --git a/furi/core/event_loop_tick_i.h b/furi/core/event_loop_tick_i.h new file mode 100644 index 000000000..8936e3828 --- /dev/null +++ b/furi/core/event_loop_tick_i.h @@ -0,0 +1,16 @@ +#pragma once + +#include "event_loop.h" + +typedef struct { + uint32_t interval; + uint32_t prev_time; + FuriEventLoopTickCallback callback; + void* callback_context; +} FuriEventLoopTick; + +void furi_event_loop_init_tick(FuriEventLoop* instance); + +void furi_event_loop_process_tick(FuriEventLoop* instance); + +uint32_t furi_event_loop_get_tick_wait_time(const FuriEventLoop* instance); diff --git a/furi/core/event_loop_timer.c b/furi/core/event_loop_timer.c new file mode 100644 index 000000000..03b6c5132 --- /dev/null +++ b/furi/core/event_loop_timer.c @@ -0,0 +1,215 @@ +#include "event_loop_i.h" + +#include +#include + +#include + +/* + * Private functions + */ + +static inline uint32_t furi_event_loop_timer_get_elapsed_time(const FuriEventLoopTimer* timer) { + return xTaskGetTickCount() - timer->start_time; +} + +static inline uint32_t + furi_event_loop_timer_get_remaining_time_private(const FuriEventLoopTimer* timer) { + const uint32_t elapsed_time = furi_event_loop_timer_get_elapsed_time(timer); + return elapsed_time < timer->interval ? timer->interval - elapsed_time : 0; +} + +static inline bool furi_event_loop_timer_is_expired(const FuriEventLoopTimer* timer) { + return furi_event_loop_timer_get_elapsed_time(timer) >= timer->interval; +} + +static void furi_event_loop_schedule_timer(FuriEventLoop* instance, FuriEventLoopTimer* timer) { + FuriEventLoopTimer* timer_pos = NULL; + + FURI_CRITICAL_ENTER(); + + const uint32_t remaining_time = furi_event_loop_timer_get_remaining_time_private(timer); + + TimerList_it_t it; + for(TimerList_it_last(it, instance->timer_list); !TimerList_end_p(it); + TimerList_previous(it)) { + FuriEventLoopTimer* tmp = TimerList_ref(it); + if(remaining_time >= furi_event_loop_timer_get_remaining_time_private(tmp)) { + timer_pos = tmp; + break; + } + } + + FURI_CRITICAL_EXIT(); + + if(timer_pos) { + TimerList_push_after(timer_pos, timer); + } else { + TimerList_push_front(instance->timer_list, timer); + } + // At this point, TimerList_front() points to the first timer to expire +} + +static void furi_event_loop_timer_enqueue_request( + FuriEventLoopTimer* timer, + FuriEventLoopTimerRequest request) { + if(timer->request != FuriEventLoopTimerRequestNone) { + // You cannot change your mind after calling furi_event_loop_timer_free() + furi_check(timer->request != FuriEventLoopTimerRequestFree); + TimerQueue_unlink(timer); + } + + timer->request = request; + + FuriEventLoop* instance = timer->owner; + TimerQueue_push_back(instance->timer_queue, timer); + + xTaskNotifyIndexed( + instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagTimer, eSetBits); +} + +/* + * Private API + */ + +uint32_t furi_event_loop_get_timer_wait_time(const FuriEventLoop* instance) { + uint32_t wait_time = FuriWaitForever; + + if(!TimerList_empty_p(instance->timer_list)) { + FuriEventLoopTimer* timer = TimerList_front(instance->timer_list); + wait_time = furi_event_loop_timer_get_remaining_time_private(timer); + } + + return wait_time; +} + +void furi_event_loop_process_timer_queue(FuriEventLoop* instance) { + while(!TimerQueue_empty_p(instance->timer_queue)) { + FuriEventLoopTimer* timer = TimerQueue_pop_front(instance->timer_queue); + + if(timer->active) { + TimerList_unlink(timer); + } + + if(timer->request == FuriEventLoopTimerRequestStart) { + timer->active = true; + timer->interval = timer->next_interval; + timer->start_time = xTaskGetTickCount(); + timer->request = FuriEventLoopTimerRequestNone; + + furi_event_loop_schedule_timer(instance, timer); + + } else if(timer->request == FuriEventLoopTimerRequestStop) { + timer->active = false; + timer->request = FuriEventLoopTimerRequestNone; + + } else if(timer->request == FuriEventLoopTimerRequestFree) { + free(timer); + + } else { + furi_crash(); + } + } +} + +bool furi_event_loop_process_expired_timers(FuriEventLoop* instance) { + if(TimerList_empty_p(instance->timer_list)) { + return false; + } + // The front() element contains the earliest-expiring timer + FuriEventLoopTimer* timer = TimerList_front(instance->timer_list); + + if(!furi_event_loop_timer_is_expired(timer)) { + return false; + } + + TimerList_unlink(timer); + + if(timer->periodic) { + const uint32_t num_events = + furi_event_loop_timer_get_elapsed_time(timer) / timer->interval; + + timer->start_time += timer->interval * num_events; + furi_event_loop_schedule_timer(instance, timer); + + } else { + timer->active = false; + } + + timer->callback(timer->context); + return true; +} + +/* + * Public timer API + */ + +FuriEventLoopTimer* furi_event_loop_timer_alloc( + FuriEventLoop* instance, + FuriEventLoopTimerCallback callback, + FuriEventLoopTimerType type, + void* context) { + furi_check(instance); + furi_check(instance->thread_id == furi_thread_get_current_id()); + furi_check(callback); + furi_check(type <= FuriEventLoopTimerTypePeriodic); + + FuriEventLoopTimer* timer = malloc(sizeof(FuriEventLoopTimer)); + + timer->owner = instance; + timer->callback = callback; + timer->context = context; + timer->periodic = (type == FuriEventLoopTimerTypePeriodic); + + TimerList_init_field(timer); + TimerQueue_init_field(timer); + + return timer; +} + +void furi_event_loop_timer_free(FuriEventLoopTimer* timer) { + furi_check(timer); + furi_check(timer->owner->thread_id == furi_thread_get_current_id()); + + furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestFree); +} + +void furi_event_loop_timer_start(FuriEventLoopTimer* timer, uint32_t interval) { + furi_check(timer); + furi_check(timer->owner->thread_id == furi_thread_get_current_id()); + + timer->next_interval = interval; + + furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStart); +} + +void furi_event_loop_timer_restart(FuriEventLoopTimer* timer) { + furi_check(timer); + furi_check(timer->owner->thread_id == furi_thread_get_current_id()); + + timer->next_interval = timer->interval; + + furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStart); +} + +void furi_event_loop_timer_stop(FuriEventLoopTimer* timer) { + furi_check(timer); + furi_check(timer->owner->thread_id == furi_thread_get_current_id()); + + furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStop); +} + +uint32_t furi_event_loop_timer_get_remaining_time(const FuriEventLoopTimer* timer) { + furi_check(timer); + return furi_event_loop_timer_get_remaining_time_private(timer); +} + +uint32_t furi_event_loop_timer_get_interval(const FuriEventLoopTimer* timer) { + furi_check(timer); + return timer->interval; +} + +bool furi_event_loop_timer_is_running(const FuriEventLoopTimer* timer) { + furi_check(timer); + return timer->active; +} diff --git a/furi/core/event_loop_timer.h b/furi/core/event_loop_timer.h new file mode 100644 index 000000000..9034043fa --- /dev/null +++ b/furi/core/event_loop_timer.h @@ -0,0 +1,118 @@ +/** + * @file event_loop_timer.h + * @brief Software timer functionality for FuriEventLoop. + */ + +#pragma once + +#include "event_loop.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enumeration of possible timer types. + */ +typedef enum { + FuriEventLoopTimerTypeOnce = 0, /**< One-shot timer. */ + FuriEventLoopTimerTypePeriodic = 1, /**< Repeating timer. */ +} FuriEventLoopTimerType; + +/** + * @brief Timer callback type for functions to be called when a timer expires. + * + * In the timer callback, it is ALLOWED: + * - To start, stop, or restart an existing timer, + * - To create new timers using furi_event_loop_timer_alloc(), + * - To delete timers using furi_event_loop_timer_free(). + * + * @param[in,out] context pointer to a user-specific object that was provided during timer creation + */ +typedef void (*FuriEventLoopTimerCallback)(void* context); + +/** + * @brief Opaque event loop timer type. + */ +typedef struct FuriEventLoopTimer FuriEventLoopTimer; + +/** + * @brief Create a new event loop timer instance. + * + * @param[in,out] instance pointer to the current FuriEventLoop instance + * @param[in] callback pointer to the callback function to be executed upon timer timeout + * @param[in] type timer type value to determine its behavior (single-shot or periodic) + * @param[in,out] context pointer to a user-specific object (will be passed to the callback) + * @returns pointer to the created timer instance + */ +FuriEventLoopTimer* furi_event_loop_timer_alloc( + FuriEventLoop* instance, + FuriEventLoopTimerCallback callback, + FuriEventLoopTimerType type, + void* context); + +/** + * @brief Delete an event loop timer instance. + * + * @warning The user code MUST call furi_event_loop_timer_free() on ALL instances + * associated with the current event loop BEFORE calling furi_event_loop_free(). + * The event loop may EITHER be running OR stopped when the timers are being deleted. + * + * @param[in,out] timer pointer to the timer instance to be deleted + */ +void furi_event_loop_timer_free(FuriEventLoopTimer* timer); + +/** + * @brief Start a timer or restart it with a new interval. + * + * @param[in,out] timer pointer to the timer instance to be (re)started + * @param[in] interval timer interval in ticks + */ +void furi_event_loop_timer_start(FuriEventLoopTimer* timer, uint32_t interval); + +/** + * @brief Restart a timer with the previously set interval. + * + * @param[in,out] timer pointer to the timer instance to be restarted + */ +void furi_event_loop_timer_restart(FuriEventLoopTimer* timer); + +/** + * @brief Stop a timer without firing its callback. + * + * It is safe to call this function on an already stopped timer (it will do nothing). + * + * @param[in,out] timer pointer to the timer instance to be stopped + */ +void furi_event_loop_timer_stop(FuriEventLoopTimer* timer); + +/** + * @brief Get the time remaining before the timer becomes expires. + * + * For stopped or expired timers, this function returns 0. + * + * @param[in] timer pointer to the timer to be queried + * @returns remaining time in ticks + */ +uint32_t furi_event_loop_timer_get_remaining_time(const FuriEventLoopTimer* timer); + +/** + * @brief Get the timer interval. + * + * @param[in] timer pointer to the timer to be queried + * @returns timer interval in ticks + */ +uint32_t furi_event_loop_timer_get_interval(const FuriEventLoopTimer* timer); + +/** + * @brief Check if the timer is currently running. + * + * A timer is considered running if it has not expired yet. + * @param[in] timer pointer to the timer to be queried + * @returns true if the timer is running, false otherwise + */ +bool furi_event_loop_timer_is_running(const FuriEventLoopTimer* timer); + +#ifdef __cplusplus +} +#endif diff --git a/furi/core/event_loop_timer_i.h b/furi/core/event_loop_timer_i.h new file mode 100644 index 000000000..9c6f90cca --- /dev/null +++ b/furi/core/event_loop_timer_i.h @@ -0,0 +1,43 @@ +#pragma once + +#include "event_loop_timer.h" + +#include + +typedef enum { + FuriEventLoopTimerRequestNone, + FuriEventLoopTimerRequestStart, + FuriEventLoopTimerRequestStop, + FuriEventLoopTimerRequestFree, +} FuriEventLoopTimerRequest; + +struct FuriEventLoopTimer { + FuriEventLoop* owner; + + FuriEventLoopTimerCallback callback; + void* context; + + uint32_t interval; + uint32_t start_time; + uint32_t next_interval; + + // Interface for the active timer list + ILIST_INTERFACE(TimerList, FuriEventLoopTimer); + + // Interface for the timer request queue + ILIST_INTERFACE(TimerQueue, FuriEventLoopTimer); + + FuriEventLoopTimerRequest request; + + bool active; + bool periodic; +}; + +ILIST_DEF(TimerList, FuriEventLoopTimer, M_POD_OPLIST) +ILIST_DEF(TimerQueue, FuriEventLoopTimer, M_POD_OPLIST) + +uint32_t furi_event_loop_get_timer_wait_time(const FuriEventLoop* instance); + +void furi_event_loop_process_timer_queue(FuriEventLoop* instance); + +bool furi_event_loop_process_expired_timers(FuriEventLoop* instance); diff --git a/furi/core/message_queue.c b/furi/core/message_queue.c index cda775abe..f07b3bfcb 100644 --- a/furi/core/message_queue.c +++ b/furi/core/message_queue.c @@ -1,5 +1,11 @@ #include "message_queue_i.h" +#include +#include + +#include "kernel.h" +#include "check.h" + // Internal FreeRTOS member names #define uxMessagesWaiting uxDummy4[0] #define uxLength uxDummy4[1] diff --git a/furi/core/message_queue_i.h b/furi/core/message_queue_i.h index aa24cfe54..a88d04131 100644 --- a/furi/core/message_queue_i.h +++ b/furi/core/message_queue_i.h @@ -1,12 +1,6 @@ #pragma once #include "message_queue.h" +#include "event_loop_link_i.h" -#include "kernel.h" -#include "event_loop_i.h" -#include "check.h" - -#include -#include - -extern const FuriEventLoopContract furi_message_queue_event_loop_contract; \ No newline at end of file +extern const FuriEventLoopContract furi_message_queue_event_loop_contract; diff --git a/furi/furi.h b/furi/furi.h index 400cf1d64..80ee30457 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -5,6 +5,7 @@ #include "core/check.h" #include "core/common_defines.h" #include "core/event_loop.h" +#include "core/event_loop_timer.h" #include "core/event_flag.h" #include "core/kernel.h" #include "core/log.h" diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 4aa676131..48c1ab0ab 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,67.1,, +Version,+,67.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1105,13 +1105,22 @@ Function,+,furi_event_flag_free,void,FuriEventFlag* Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" -Function,-,furi_event_loop_alloc,FuriEventLoop*, -Function,-,furi_event_loop_free,void,FuriEventLoop* -Function,-,furi_event_loop_message_queue_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*" -Function,-,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*" -Function,-,furi_event_loop_run,void,FuriEventLoop* -Function,-,furi_event_loop_stop,void,FuriEventLoop* -Function,-,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" +Function,+,furi_event_loop_alloc,FuriEventLoop*, +Function,+,furi_event_loop_free,void,FuriEventLoop* +Function,+,furi_event_loop_message_queue_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*" +Function,+,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*" +Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" +Function,+,furi_event_loop_run,void,FuriEventLoop* +Function,+,furi_event_loop_stop,void,FuriEventLoop* +Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" +Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*" +Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer* +Function,+,furi_event_loop_timer_get_interval,uint32_t,const FuriEventLoopTimer* +Function,+,furi_event_loop_timer_get_remaining_time,uint32_t,const FuriEventLoopTimer* +Function,+,furi_event_loop_timer_is_running,_Bool,const FuriEventLoopTimer* +Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer* +Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t" +Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer* Function,+,furi_get_tick,uint32_t, Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index d91c106c5..d7782ee91 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,67.1,, +Version,+,67.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1210,13 +1210,22 @@ Function,+,furi_event_flag_free,void,FuriEventFlag* Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" -Function,-,furi_event_loop_alloc,FuriEventLoop*, -Function,-,furi_event_loop_free,void,FuriEventLoop* -Function,-,furi_event_loop_message_queue_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*" -Function,-,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*" -Function,-,furi_event_loop_run,void,FuriEventLoop* -Function,-,furi_event_loop_stop,void,FuriEventLoop* -Function,-,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" +Function,+,furi_event_loop_alloc,FuriEventLoop*, +Function,+,furi_event_loop_free,void,FuriEventLoop* +Function,+,furi_event_loop_message_queue_subscribe,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopMessageQueueCallback, void*" +Function,+,furi_event_loop_message_queue_unsubscribe,void,"FuriEventLoop*, FuriMessageQueue*" +Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" +Function,+,furi_event_loop_run,void,FuriEventLoop* +Function,+,furi_event_loop_stop,void,FuriEventLoop* +Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" +Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*" +Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer* +Function,+,furi_event_loop_timer_get_interval,uint32_t,const FuriEventLoopTimer* +Function,+,furi_event_loop_timer_get_remaining_time,uint32_t,const FuriEventLoopTimer* +Function,+,furi_event_loop_timer_is_running,_Bool,const FuriEventLoopTimer* +Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer* +Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t" +Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer* Function,+,furi_get_tick,uint32_t, Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*