From 6d823835df928ef8f02f4034a2aaa76e1fe70c30 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Thu, 31 Oct 2024 05:58:16 +0400 Subject: [PATCH] FurEventLoop: add support for FuriEventFlag, simplify API (#3958) * Core: event_flag, removing duplicate code * event_loop: add support furi_event_flags * Examples: add missing free in event loop examples * Furi: fix event flag * Sync api symbols * Unit_test: evet_loop_event_flags * Fix multiple waiting list elements handling * Unit_test: add event_loop_event_flag test * FURI: event_loop add restrictions * Fix multiple waiting lists items for good * Improve FuriEventLoop unit tests * Abolish callback return value * Remove return value from callback signature * Use bool level value instead of int32_t * Add unit tests for FuriStreamBuffer * Add unit tests for FuriSemaphore * Speed up test execution * Improve docs * Add a stub for furi os-level primitives * Add more checks for edge cases * Allow event loop notification from ISR * Bump api version Co-authored-by: Aleksandr Kutuzov Co-authored-by: Georgii Surkov Co-authored-by: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> --- .../event_loop_blink_test.c | 4 +- .../unit_tests/tests/furi/furi_event_loop.c | 205 -------- .../tests/furi/furi_event_loop_test.c | 490 ++++++++++++++++++ .../tests/furi/furi_primitives_test.c | 103 ++++ .../debug/unit_tests/tests/furi/furi_test.c | 6 + .../example_event_loop/application.fam | 9 + .../example_event_loop_event_flags.c | 173 +++++++ .../example_event_loop_multi.c | 12 +- .../example_event_loop_mutex.c | 4 +- .../example_event_loop_stream_buffer.c | 4 +- applications/services/dolphin/dolphin.c | 4 +- applications/services/gui/view_dispatcher.c | 8 +- applications/services/gui/view_dispatcher_i.h | 4 +- .../services/power/power_service/power.c | 4 +- .../modules/js_event_loop/js_event_loop.c | 4 +- furi/core/event_flag.c | 57 +- furi/core/event_loop.c | 135 +++-- furi/core/event_loop.h | 28 +- furi/core/event_loop_i.h | 1 - furi/core/event_loop_link_i.h | 2 +- furi/core/event_loop_timer.h | 3 + furi/core/message_queue.c | 2 +- furi/core/mutex.c | 4 +- furi/core/semaphore.c | 2 +- furi/core/stream_buffer.c | 2 +- targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 3 +- 27 files changed, 971 insertions(+), 305 deletions(-) delete mode 100644 applications/debug/unit_tests/tests/furi/furi_event_loop.c create mode 100644 applications/debug/unit_tests/tests/furi/furi_event_loop_test.c create mode 100644 applications/debug/unit_tests/tests/furi/furi_primitives_test.c create mode 100644 applications/examples/example_event_loop/example_event_loop_event_flags.c 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 index 7f00e63f2..1cddfa323 100644 --- a/applications/debug/event_loop_blink_test/event_loop_blink_test.c +++ b/applications/debug/event_loop_blink_test/event_loop_blink_test.c @@ -82,7 +82,7 @@ static void view_port_input_callback(InputEvent* input_event, void* context) { furi_message_queue_put(app->input_queue, input_event, 0); } -static bool input_queue_callback(FuriEventLoopObject* object, void* context) { +static void input_queue_callback(FuriEventLoopObject* object, void* context) { FuriMessageQueue* queue = object; EventLoopBlinkTestApp* app = context; @@ -107,8 +107,6 @@ static bool input_queue_callback(FuriEventLoopObject* object, void* context) { furi_event_loop_stop(app->event_loop); } } - - return true; } static void blink_timer_callback(void* context) { diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop.c b/applications/debug/unit_tests/tests/furi/furi_event_loop.c deleted file mode 100644 index 291181c77..000000000 --- a/applications/debug/unit_tests/tests/furi/furi_event_loop.c +++ /dev/null @@ -1,205 +0,0 @@ -#include "../test.h" -#include -#include - -#include -#include - -#define TAG "TestFuriEventLoop" - -#define EVENT_LOOP_EVENT_COUNT (256u) - -typedef struct { - FuriMessageQueue* mq; - - FuriEventLoop* producer_event_loop; - uint32_t producer_counter; - - FuriEventLoop* consumer_event_loop; - uint32_t consumer_counter; -} TestFuriData; - -bool test_furi_event_loop_producer_mq_callback(FuriEventLoopObject* object, void* context) { - furi_check(context); - - TestFuriData* data = context; - furi_check(data->mq == object, "Invalid queue"); - - FURI_LOG_I( - TAG, "producer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter); - - if(data->producer_counter == EVENT_LOOP_EVENT_COUNT / 2) { - furi_event_loop_unsubscribe(data->producer_event_loop, data->mq); - furi_event_loop_subscribe_message_queue( - data->producer_event_loop, - data->mq, - FuriEventLoopEventOut, - test_furi_event_loop_producer_mq_callback, - data); - } - - if(data->producer_counter == EVENT_LOOP_EVENT_COUNT) { - furi_event_loop_stop(data->producer_event_loop); - return false; - } - - data->producer_counter++; - furi_check( - furi_message_queue_put(data->mq, &data->producer_counter, 0) == FuriStatusOk, - "furi_message_queue_put failed"); - furi_delay_us(furi_hal_random_get() % 1000); - - return true; -} - -int32_t test_furi_event_loop_producer(void* p) { - furi_check(p); - - TestFuriData* data = p; - - FURI_LOG_I(TAG, "producer start 1st run"); - - data->producer_event_loop = furi_event_loop_alloc(); - furi_event_loop_subscribe_message_queue( - data->producer_event_loop, - data->mq, - FuriEventLoopEventOut, - test_furi_event_loop_producer_mq_callback, - data); - - furi_event_loop_run(data->producer_event_loop); - - // 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags - xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits); - - furi_event_loop_unsubscribe(data->producer_event_loop, data->mq); - furi_event_loop_free(data->producer_event_loop); - - FURI_LOG_I(TAG, "producer start 2nd run"); - - data->producer_counter = 0; - data->producer_event_loop = furi_event_loop_alloc(); - - furi_event_loop_subscribe_message_queue( - data->producer_event_loop, - data->mq, - FuriEventLoopEventOut, - test_furi_event_loop_producer_mq_callback, - data); - - furi_event_loop_run(data->producer_event_loop); - - furi_event_loop_unsubscribe(data->producer_event_loop, data->mq); - furi_event_loop_free(data->producer_event_loop); - - FURI_LOG_I(TAG, "producer end"); - - return 0; -} - -bool test_furi_event_loop_consumer_mq_callback(FuriEventLoopObject* object, void* context) { - furi_check(context); - - TestFuriData* data = context; - furi_check(data->mq == object); - - furi_delay_us(furi_hal_random_get() % 1000); - furi_check(furi_message_queue_get(data->mq, &data->consumer_counter, 0) == FuriStatusOk); - - FURI_LOG_I( - TAG, "consumer_mq_callback: %lu %lu", data->producer_counter, data->consumer_counter); - - if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT / 2) { - furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq); - furi_event_loop_subscribe_message_queue( - data->consumer_event_loop, - data->mq, - FuriEventLoopEventIn, - test_furi_event_loop_consumer_mq_callback, - data); - } - - if(data->consumer_counter == EVENT_LOOP_EVENT_COUNT) { - furi_event_loop_stop(data->consumer_event_loop); - return false; - } - - return true; -} - -int32_t test_furi_event_loop_consumer(void* p) { - furi_check(p); - - TestFuriData* data = p; - - FURI_LOG_I(TAG, "consumer start 1st run"); - - data->consumer_event_loop = furi_event_loop_alloc(); - furi_event_loop_subscribe_message_queue( - data->consumer_event_loop, - data->mq, - FuriEventLoopEventIn, - test_furi_event_loop_consumer_mq_callback, - data); - - furi_event_loop_run(data->consumer_event_loop); - - // 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags - xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits); - - furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq); - furi_event_loop_free(data->consumer_event_loop); - - FURI_LOG_I(TAG, "consumer start 2nd run"); - - data->consumer_counter = 0; - data->consumer_event_loop = furi_event_loop_alloc(); - furi_event_loop_subscribe_message_queue( - data->consumer_event_loop, - data->mq, - FuriEventLoopEventIn, - test_furi_event_loop_consumer_mq_callback, - data); - - furi_event_loop_run(data->consumer_event_loop); - - furi_event_loop_unsubscribe(data->consumer_event_loop, data->mq); - furi_event_loop_free(data->consumer_event_loop); - - FURI_LOG_I(TAG, "consumer end"); - - return 0; -} - -void test_furi_event_loop(void) { - TestFuriData data = {}; - - data.mq = furi_message_queue_alloc(16, sizeof(uint32_t)); - - FuriThread* producer_thread = furi_thread_alloc(); - furi_thread_set_name(producer_thread, "producer_thread"); - furi_thread_set_stack_size(producer_thread, 1 * 1024); - furi_thread_set_callback(producer_thread, test_furi_event_loop_producer); - furi_thread_set_context(producer_thread, &data); - furi_thread_start(producer_thread); - - FuriThread* consumer_thread = furi_thread_alloc(); - furi_thread_set_name(consumer_thread, "consumer_thread"); - furi_thread_set_stack_size(consumer_thread, 1 * 1024); - furi_thread_set_callback(consumer_thread, test_furi_event_loop_consumer); - furi_thread_set_context(consumer_thread, &data); - furi_thread_start(consumer_thread); - - // Wait for thread to complete their tasks - furi_thread_join(producer_thread); - furi_thread_join(consumer_thread); - - // The test itself - mu_assert_int_eq(data.producer_counter, data.consumer_counter); - mu_assert_int_eq(data.producer_counter, EVENT_LOOP_EVENT_COUNT); - - // Release memory - furi_thread_free(consumer_thread); - furi_thread_free(producer_thread); - furi_message_queue_free(data.mq); -} diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c new file mode 100644 index 000000000..73f38ab77 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c @@ -0,0 +1,490 @@ +#include "../test.h" +#include +#include + +#include +#include + +#define TAG "TestFuriEventLoop" + +#define MESSAGE_COUNT (256UL) +#define EVENT_FLAG_COUNT (23UL) +#define PRIMITIVE_COUNT (4UL) +#define RUN_COUNT (2UL) + +typedef struct { + FuriEventLoop* event_loop; + uint32_t message_queue_count; + uint32_t stream_buffer_count; + uint32_t event_flag_count; + uint32_t semaphore_count; + uint32_t primitives_tested; +} TestFuriEventLoopThread; + +typedef struct { + FuriMessageQueue* message_queue; + FuriStreamBuffer* stream_buffer; + FuriEventFlag* event_flag; + FuriSemaphore* semaphore; + + TestFuriEventLoopThread producer; + TestFuriEventLoopThread consumer; +} TestFuriEventLoopData; + +static void test_furi_event_loop_pending_callback(void* context) { + furi_check(context); + + TestFuriEventLoopThread* test_thread = context; + furi_check(test_thread->primitives_tested < PRIMITIVE_COUNT); + + test_thread->primitives_tested++; + FURI_LOG_I(TAG, "primitives tested: %lu", test_thread->primitives_tested); + + if(test_thread->primitives_tested == PRIMITIVE_COUNT) { + furi_event_loop_stop(test_thread->event_loop); + } +} + +static void test_furi_event_loop_thread_init(TestFuriEventLoopThread* test_thread) { + memset(test_thread, 0, sizeof(TestFuriEventLoopThread)); + test_thread->event_loop = furi_event_loop_alloc(); +} + +static void test_furi_event_loop_thread_run_and_cleanup(TestFuriEventLoopThread* test_thread) { + furi_event_loop_run(test_thread->event_loop); + // 2 EventLoop index, 0xFFFFFFFF - all possible flags, emulate uncleared flags + xTaskNotifyIndexed(xTaskGetCurrentTaskHandle(), 2, 0xFFFFFFFF, eSetBits); + furi_event_loop_free(test_thread->event_loop); +} + +static void test_furi_event_loop_producer_message_queue_callback( + FuriEventLoopObject* object, + void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->message_queue == object); + + FURI_LOG_I( + TAG, + "producer MessageQueue: %lu %lu", + data->producer.message_queue_count, + data->consumer.message_queue_count); + + if(data->producer.message_queue_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue); + furi_event_loop_subscribe_message_queue( + data->producer.event_loop, + data->message_queue, + FuriEventLoopEventOut, + test_furi_event_loop_producer_message_queue_callback, + data); + + } else if(data->producer.message_queue_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(data->producer.event_loop, data->message_queue); + furi_event_loop_pend_callback( + data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer); + return; + } + + data->producer.message_queue_count++; + + furi_check( + furi_message_queue_put(data->message_queue, &data->producer.message_queue_count, 0) == + FuriStatusOk); + + furi_delay_us(furi_hal_random_get() % 100); +} + +static void test_furi_event_loop_producer_stream_buffer_callback( + FuriEventLoopObject* object, + void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->stream_buffer == object); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + FURI_LOG_I( + TAG, + "producer StreamBuffer: %lu %lu", + producer->stream_buffer_count, + consumer->stream_buffer_count); + + if(producer->stream_buffer_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer); + furi_event_loop_subscribe_stream_buffer( + producer->event_loop, + data->stream_buffer, + FuriEventLoopEventOut, + test_furi_event_loop_producer_stream_buffer_callback, + data); + + } else if(producer->stream_buffer_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(producer->event_loop, data->stream_buffer); + furi_event_loop_pend_callback( + producer->event_loop, test_furi_event_loop_pending_callback, producer); + return; + } + + producer->stream_buffer_count++; + + furi_check( + furi_stream_buffer_send( + data->stream_buffer, &producer->stream_buffer_count, sizeof(uint32_t), 0) == + sizeof(uint32_t)); + + furi_delay_us(furi_hal_random_get() % 100); +} + +static void + test_furi_event_loop_producer_event_flag_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->event_flag == object); + + const uint32_t producer_flags = (1UL << data->producer.event_flag_count); + const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count); + + FURI_LOG_I(TAG, "producer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags); + + furi_check(furi_event_flag_set(data->event_flag, producer_flags) & producer_flags); + + if(data->producer.event_flag_count == EVENT_FLAG_COUNT / 2) { + furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag); + furi_event_loop_subscribe_event_flag( + data->producer.event_loop, + data->event_flag, + FuriEventLoopEventOut, + test_furi_event_loop_producer_event_flag_callback, + data); + + } else if(data->producer.event_flag_count == EVENT_FLAG_COUNT) { + furi_event_loop_unsubscribe(data->producer.event_loop, data->event_flag); + furi_event_loop_pend_callback( + data->producer.event_loop, test_furi_event_loop_pending_callback, &data->producer); + return; + } + + data->producer.event_flag_count++; + + furi_delay_us(furi_hal_random_get() % 100); +} + +static void + test_furi_event_loop_producer_semaphore_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->semaphore == object); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + FURI_LOG_I( + TAG, "producer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count); + furi_check(furi_semaphore_release(data->semaphore) == FuriStatusOk); + + if(producer->semaphore_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(producer->event_loop, data->semaphore); + furi_event_loop_subscribe_semaphore( + producer->event_loop, + data->semaphore, + FuriEventLoopEventOut, + test_furi_event_loop_producer_semaphore_callback, + data); + + } else if(producer->semaphore_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(producer->event_loop, data->semaphore); + furi_event_loop_pend_callback( + producer->event_loop, test_furi_event_loop_pending_callback, producer); + return; + } + + data->producer.semaphore_count++; + + furi_delay_us(furi_hal_random_get() % 100); +} + +static int32_t test_furi_event_loop_producer(void* p) { + furi_check(p); + + TestFuriEventLoopData* data = p; + TestFuriEventLoopThread* producer = &data->producer; + + for(uint32_t i = 0; i < RUN_COUNT; ++i) { + FURI_LOG_I(TAG, "producer start run %lu", i); + + test_furi_event_loop_thread_init(producer); + + furi_event_loop_subscribe_message_queue( + producer->event_loop, + data->message_queue, + FuriEventLoopEventOut, + test_furi_event_loop_producer_message_queue_callback, + data); + furi_event_loop_subscribe_stream_buffer( + producer->event_loop, + data->stream_buffer, + FuriEventLoopEventOut, + test_furi_event_loop_producer_stream_buffer_callback, + data); + furi_event_loop_subscribe_event_flag( + producer->event_loop, + data->event_flag, + FuriEventLoopEventOut, + test_furi_event_loop_producer_event_flag_callback, + data); + furi_event_loop_subscribe_semaphore( + producer->event_loop, + data->semaphore, + FuriEventLoopEventOut, + test_furi_event_loop_producer_semaphore_callback, + data); + + test_furi_event_loop_thread_run_and_cleanup(producer); + } + + FURI_LOG_I(TAG, "producer end"); + + return 0; +} + +static void test_furi_event_loop_consumer_message_queue_callback( + FuriEventLoopObject* object, + void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->message_queue == object); + + furi_delay_us(furi_hal_random_get() % 100); + + furi_check( + furi_message_queue_get(data->message_queue, &data->consumer.message_queue_count, 0) == + FuriStatusOk); + + FURI_LOG_I( + TAG, + "consumer MessageQueue: %lu %lu", + data->producer.message_queue_count, + data->consumer.message_queue_count); + + if(data->consumer.message_queue_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue); + furi_event_loop_subscribe_message_queue( + data->consumer.event_loop, + data->message_queue, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_message_queue_callback, + data); + + } else if(data->consumer.message_queue_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->message_queue); + furi_event_loop_pend_callback( + data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer); + } +} + +static void test_furi_event_loop_consumer_stream_buffer_callback( + FuriEventLoopObject* object, + void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->stream_buffer == object); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + furi_delay_us(furi_hal_random_get() % 100); + + furi_check( + furi_stream_buffer_receive( + data->stream_buffer, &consumer->stream_buffer_count, sizeof(uint32_t), 0) == + sizeof(uint32_t)); + + FURI_LOG_I( + TAG, + "consumer StreamBuffer: %lu %lu", + producer->stream_buffer_count, + consumer->stream_buffer_count); + + if(consumer->stream_buffer_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(consumer->event_loop, data->stream_buffer); + furi_event_loop_subscribe_stream_buffer( + consumer->event_loop, + data->stream_buffer, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_stream_buffer_callback, + data); + + } else if(consumer->stream_buffer_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->stream_buffer); + furi_event_loop_pend_callback( + consumer->event_loop, test_furi_event_loop_pending_callback, consumer); + } +} + +static void + test_furi_event_loop_consumer_event_flag_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->event_flag == object); + + furi_delay_us(furi_hal_random_get() % 100); + + const uint32_t producer_flags = (1UL << data->producer.event_flag_count); + const uint32_t consumer_flags = (1UL << data->consumer.event_flag_count); + + furi_check( + furi_event_flag_wait(data->event_flag, consumer_flags, FuriFlagWaitAny, 0) & + consumer_flags); + + FURI_LOG_I(TAG, "consumer EventFlag: 0x%06lX 0x%06lX", producer_flags, consumer_flags); + + if(data->consumer.event_flag_count == EVENT_FLAG_COUNT / 2) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag); + furi_event_loop_subscribe_event_flag( + data->consumer.event_loop, + data->event_flag, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_event_flag_callback, + data); + + } else if(data->consumer.event_flag_count == EVENT_FLAG_COUNT) { + furi_event_loop_unsubscribe(data->consumer.event_loop, data->event_flag); + furi_event_loop_pend_callback( + data->consumer.event_loop, test_furi_event_loop_pending_callback, &data->consumer); + return; + } + + data->consumer.event_flag_count++; +} + +static void + test_furi_event_loop_consumer_semaphore_callback(FuriEventLoopObject* object, void* context) { + furi_check(context); + + TestFuriEventLoopData* data = context; + furi_check(data->semaphore == object); + + furi_delay_us(furi_hal_random_get() % 100); + + TestFuriEventLoopThread* producer = &data->producer; + TestFuriEventLoopThread* consumer = &data->consumer; + + furi_check(furi_semaphore_acquire(data->semaphore, 0) == FuriStatusOk); + + FURI_LOG_I( + TAG, "consumer Semaphore: %lu %lu", producer->semaphore_count, consumer->semaphore_count); + + if(consumer->semaphore_count == MESSAGE_COUNT / 2) { + furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore); + furi_event_loop_subscribe_semaphore( + consumer->event_loop, + data->semaphore, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_semaphore_callback, + data); + + } else if(consumer->semaphore_count == MESSAGE_COUNT) { + furi_event_loop_unsubscribe(consumer->event_loop, data->semaphore); + furi_event_loop_pend_callback( + consumer->event_loop, test_furi_event_loop_pending_callback, consumer); + return; + } + + data->consumer.semaphore_count++; +} + +static int32_t test_furi_event_loop_consumer(void* p) { + furi_check(p); + + TestFuriEventLoopData* data = p; + TestFuriEventLoopThread* consumer = &data->consumer; + + for(uint32_t i = 0; i < RUN_COUNT; ++i) { + FURI_LOG_I(TAG, "consumer start run %lu", i); + + test_furi_event_loop_thread_init(consumer); + + furi_event_loop_subscribe_message_queue( + consumer->event_loop, + data->message_queue, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_message_queue_callback, + data); + furi_event_loop_subscribe_stream_buffer( + consumer->event_loop, + data->stream_buffer, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_stream_buffer_callback, + data); + furi_event_loop_subscribe_event_flag( + consumer->event_loop, + data->event_flag, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_event_flag_callback, + data); + furi_event_loop_subscribe_semaphore( + consumer->event_loop, + data->semaphore, + FuriEventLoopEventIn, + test_furi_event_loop_consumer_semaphore_callback, + data); + + test_furi_event_loop_thread_run_and_cleanup(consumer); + } + + FURI_LOG_I(TAG, "consumer end"); + + return 0; +} + +void test_furi_event_loop(void) { + TestFuriEventLoopData data = {}; + + data.message_queue = furi_message_queue_alloc(16, sizeof(uint32_t)); + data.stream_buffer = furi_stream_buffer_alloc(16, sizeof(uint32_t)); + data.event_flag = furi_event_flag_alloc(); + data.semaphore = furi_semaphore_alloc(8, 0); + + FuriThread* producer_thread = + furi_thread_alloc_ex("producer_thread", 1 * 1024, test_furi_event_loop_producer, &data); + furi_thread_start(producer_thread); + + FuriThread* consumer_thread = + furi_thread_alloc_ex("consumer_thread", 1 * 1024, test_furi_event_loop_consumer, &data); + furi_thread_start(consumer_thread); + + // Wait for thread to complete their tasks + furi_thread_join(producer_thread); + furi_thread_join(consumer_thread); + + TestFuriEventLoopThread* producer = &data.producer; + TestFuriEventLoopThread* consumer = &data.consumer; + + // The test itself + mu_assert_int_eq(producer->message_queue_count, consumer->message_queue_count); + mu_assert_int_eq(producer->message_queue_count, MESSAGE_COUNT); + mu_assert_int_eq(producer->stream_buffer_count, consumer->stream_buffer_count); + mu_assert_int_eq(producer->stream_buffer_count, MESSAGE_COUNT); + mu_assert_int_eq(producer->event_flag_count, consumer->event_flag_count); + mu_assert_int_eq(producer->event_flag_count, EVENT_FLAG_COUNT); + mu_assert_int_eq(producer->semaphore_count, consumer->semaphore_count); + mu_assert_int_eq(producer->semaphore_count, MESSAGE_COUNT); + + // Release memory + furi_thread_free(consumer_thread); + furi_thread_free(producer_thread); + + furi_message_queue_free(data.message_queue); + furi_stream_buffer_free(data.stream_buffer); + furi_event_flag_free(data.event_flag); + furi_semaphore_free(data.semaphore); +} diff --git a/applications/debug/unit_tests/tests/furi/furi_primitives_test.c b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c new file mode 100644 index 000000000..d9ad03039 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_primitives_test.c @@ -0,0 +1,103 @@ +#include +#include "../test.h" // IWYU pragma: keep + +#define MESSAGE_QUEUE_CAPACITY (16U) +#define MESSAGE_QUEUE_ELEMENT_SIZE (sizeof(uint32_t)) + +#define STREAM_BUFFER_SIZE (32U) +#define STREAM_BUFFER_TRG_LEVEL (STREAM_BUFFER_SIZE / 2U) + +typedef struct { + FuriMessageQueue* message_queue; + FuriStreamBuffer* stream_buffer; +} TestFuriPrimitivesData; + +static void test_furi_message_queue(TestFuriPrimitivesData* data) { + FuriMessageQueue* message_queue = data->message_queue; + + mu_assert_int_eq(0, furi_message_queue_get_count(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_capacity(message_queue)); + mu_assert_int_eq( + MESSAGE_QUEUE_ELEMENT_SIZE, furi_message_queue_get_message_size(message_queue)); + + for(uint32_t i = 0;; ++i) { + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_space(message_queue)); + mu_assert_int_eq(i, furi_message_queue_get_count(message_queue)); + + if(furi_message_queue_put(message_queue, &i, 0) != FuriStatusOk) { + break; + } + } + + mu_assert_int_eq(0, furi_message_queue_get_space(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_count(message_queue)); + + for(uint32_t i = 0;; ++i) { + mu_assert_int_eq(i, furi_message_queue_get_space(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY - i, furi_message_queue_get_count(message_queue)); + + uint32_t value; + if(furi_message_queue_get(message_queue, &value, 0) != FuriStatusOk) { + break; + } + + mu_assert_int_eq(i, value); + } + + mu_assert_int_eq(0, furi_message_queue_get_count(message_queue)); + mu_assert_int_eq(MESSAGE_QUEUE_CAPACITY, furi_message_queue_get_space(message_queue)); +} + +static void test_furi_stream_buffer(TestFuriPrimitivesData* data) { + FuriStreamBuffer* stream_buffer = data->stream_buffer; + + mu_assert(furi_stream_buffer_is_empty(stream_buffer), "Must be empty"); + mu_assert(!furi_stream_buffer_is_full(stream_buffer), "Must be not full"); + mu_assert_int_eq(0, furi_stream_buffer_bytes_available(stream_buffer)); + mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_spaces_available(stream_buffer)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq(i, furi_stream_buffer_bytes_available(stream_buffer)); + mu_assert_int_eq( + STREAM_BUFFER_SIZE - i, furi_stream_buffer_spaces_available(stream_buffer)); + + if(furi_stream_buffer_send(stream_buffer, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) { + break; + } + } + + mu_assert(!furi_stream_buffer_is_empty(stream_buffer), "Must be not empty"); + mu_assert(furi_stream_buffer_is_full(stream_buffer), "Must be full"); + mu_assert_int_eq(STREAM_BUFFER_SIZE, furi_stream_buffer_bytes_available(stream_buffer)); + mu_assert_int_eq(0, furi_stream_buffer_spaces_available(stream_buffer)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq( + STREAM_BUFFER_SIZE - i, furi_stream_buffer_bytes_available(stream_buffer)); + mu_assert_int_eq(i, furi_stream_buffer_spaces_available(stream_buffer)); + + uint8_t value; + if(furi_stream_buffer_receive(stream_buffer, &value, sizeof(uint8_t), 0) != + sizeof(uint8_t)) { + break; + } + + mu_assert_int_eq(i, value); + } +} + +// This is a stub that needs expanding +void test_furi_primitives(void) { + TestFuriPrimitivesData data = { + .message_queue = + furi_message_queue_alloc(MESSAGE_QUEUE_CAPACITY, MESSAGE_QUEUE_ELEMENT_SIZE), + .stream_buffer = furi_stream_buffer_alloc(STREAM_BUFFER_SIZE, STREAM_BUFFER_TRG_LEVEL), + }; + + test_furi_message_queue(&data); + test_furi_stream_buffer(&data); + + furi_message_queue_free(data.message_queue); + furi_stream_buffer_free(data.stream_buffer); +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index 2a76d5184..193a8124d 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -9,6 +9,7 @@ void test_furi_pubsub(void); void test_furi_memmgr(void); void test_furi_event_loop(void); void test_errno_saving(void); +void test_furi_primitives(void); static int foo = 0; @@ -47,6 +48,10 @@ MU_TEST(mu_test_errno_saving) { test_errno_saving(); } +MU_TEST(mu_test_furi_primitives) { + test_furi_primitives(); +} + MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_check); @@ -57,6 +62,7 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(mu_test_furi_memmgr); MU_RUN_TEST(mu_test_furi_event_loop); MU_RUN_TEST(mu_test_errno_saving); + MU_RUN_TEST(mu_test_furi_primitives); } int run_minunit_test_furi(void) { diff --git a/applications/examples/example_event_loop/application.fam b/applications/examples/example_event_loop/application.fam index a37ffb1a0..15a7c8837 100644 --- a/applications/examples/example_event_loop/application.fam +++ b/applications/examples/example_event_loop/application.fam @@ -1,3 +1,12 @@ +App( + appid="example_event_loop_event_flags", + name="Example: Event Loop Event Flags", + apptype=FlipperAppType.EXTERNAL, + sources=["example_event_loop_event_flags.c"], + entry_point="example_event_loop_event_flags_app", + fap_category="Examples", +) + App( appid="example_event_loop_timer", name="Example: Event Loop Timer", diff --git a/applications/examples/example_event_loop/example_event_loop_event_flags.c b/applications/examples/example_event_loop/example_event_loop_event_flags.c new file mode 100644 index 000000000..5d0acf7f1 --- /dev/null +++ b/applications/examples/example_event_loop/example_event_loop_event_flags.c @@ -0,0 +1,173 @@ +/** + * @file example_event_loop_event_flags.c + * @brief Example application demonstrating the use of the FuriEventFlag primitive in FuriEventLoop instances. + * + * This application receives keystrokes from the input service and sets the appropriate flags, + * which are subsequently processed in the event loop + */ + +#include +#include +#include + +#include + +#define TAG "ExampleEventLoopEventFlags" + +typedef struct { + Gui* gui; + ViewPort* view_port; + FuriEventLoop* event_loop; + FuriEventFlag* event_flag; +} EventLoopEventFlagsApp; + +typedef enum { + EventLoopEventFlagsOk = (1 << 0), + EventLoopEventFlagsUp = (1 << 1), + EventLoopEventFlagsDown = (1 << 2), + EventLoopEventFlagsLeft = (1 << 3), + EventLoopEventFlagsRight = (1 << 4), + EventLoopEventFlagsBack = (1 << 5), + EventLoopEventFlagsExit = (1 << 6), +} EventLoopEventFlags; + +#define EVENT_LOOP_EVENT_FLAGS_MASK \ + (EventLoopEventFlagsOk | EventLoopEventFlagsUp | EventLoopEventFlagsDown | \ + EventLoopEventFlagsLeft | EventLoopEventFlagsRight | EventLoopEventFlagsBack | \ + EventLoopEventFlagsExit) + +// This function is executed in the GUI context each time an input event occurs (e.g. the user pressed a key) +static void event_loop_event_flags_app_input_callback(InputEvent* event, void* context) { + furi_assert(context); + EventLoopEventFlagsApp* app = context; + UNUSED(app); + + if(event->type == InputTypePress) { + if(event->key == InputKeyOk) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsOk); + } else if(event->key == InputKeyUp) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsUp); + } else if(event->key == InputKeyDown) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsDown); + } else if(event->key == InputKeyLeft) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsLeft); + } else if(event->key == InputKeyRight) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsRight); + } else if(event->key == InputKeyBack) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsBack); + } + } else if(event->type == InputTypeLong) { + if(event->key == InputKeyBack) { + furi_event_flag_set(app->event_flag, EventLoopEventFlagsExit); + } + } +} + +// This function is executed each time a new event flag is inserted in the input event flag. +static void + event_loop_event_flags_app_event_flags_callback(FuriEventLoopObject* object, void* context) { + furi_assert(context); + EventLoopEventFlagsApp* app = context; + + furi_assert(object == app->event_flag); + + EventLoopEventFlags events = + furi_event_flag_wait(app->event_flag, EVENT_LOOP_EVENT_FLAGS_MASK, FuriFlagWaitAny, 0); + furi_check((events) != 0); + + if(events & EventLoopEventFlagsOk) { + FURI_LOG_I(TAG, "Press \"Ok\""); + } + if(events & EventLoopEventFlagsUp) { + FURI_LOG_I(TAG, "Press \"Up\""); + } + if(events & EventLoopEventFlagsDown) { + FURI_LOG_I(TAG, "Press \"Down\""); + } + if(events & EventLoopEventFlagsLeft) { + FURI_LOG_I(TAG, "Press \"Left\""); + } + if(events & EventLoopEventFlagsRight) { + FURI_LOG_I(TAG, "Press \"Right\""); + } + if(events & EventLoopEventFlagsBack) { + FURI_LOG_I(TAG, "Press \"Back\""); + } + if(events & EventLoopEventFlagsExit) { + FURI_LOG_I(TAG, "Exit App"); + furi_event_loop_stop(app->event_loop); + } +} + +static EventLoopEventFlagsApp* event_loop_event_flags_app_alloc(void) { + EventLoopEventFlagsApp* app = malloc(sizeof(EventLoopEventFlagsApp)); + + // Create event loop instances. + app->event_loop = furi_event_loop_alloc(); + // Create event flag instances. + app->event_flag = furi_event_flag_alloc(); + + // Create GUI instance. + app->gui = furi_record_open(RECORD_GUI); + app->view_port = view_port_alloc(); + // Gain exclusive access to the input events + view_port_input_callback_set(app->view_port, event_loop_event_flags_app_input_callback, app); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + + // Notify the event loop about incoming messages in the event flag + furi_event_loop_subscribe_event_flag( + app->event_loop, + app->event_flag, + FuriEventLoopEventIn | FuriEventLoopEventFlagEdge, + event_loop_event_flags_app_event_flags_callback, + app); + + return app; +} + +static void event_loop_event_flags_app_free(EventLoopEventFlagsApp* app) { + gui_remove_view_port(app->gui, app->view_port); + + furi_record_close(RECORD_GUI); + app->gui = NULL; + + // Delete all instances + view_port_free(app->view_port); + app->view_port = NULL; + + // IMPORTANT: The user code MUST unsubscribe from all events before deleting the event loop. + // Failure to do so will result in a crash. + furi_event_loop_unsubscribe(app->event_loop, app->event_flag); + + furi_event_flag_free(app->event_flag); + app->event_flag = NULL; + + furi_event_loop_free(app->event_loop); + app->event_loop = NULL; + + free(app); +} + +static void event_loop_event_flags_app_run(EventLoopEventFlagsApp* app) { + FURI_LOG_I(TAG, "Press keys to see them printed here."); + FURI_LOG_I(TAG, "Quickly press different keys to generate events."); + FURI_LOG_I(TAG, "Long press \"Back\" to exit app."); + + // Run the application event loop. This call will block until the application is about to exit. + furi_event_loop_run(app->event_loop); +} + +/******************************************************************* + * vvv START HERE vvv + * + * The application's entry point - referenced in application.fam + *******************************************************************/ +int32_t example_event_loop_event_flags_app(void* arg) { + UNUSED(arg); + + EventLoopEventFlagsApp* app = event_loop_event_flags_app_alloc(); + event_loop_event_flags_app_run(app); + event_loop_event_flags_app_free(app); + + return 0; +} diff --git a/applications/examples/example_event_loop/example_event_loop_multi.c b/applications/examples/example_event_loop/example_event_loop_multi.c index ebfb00911..ae748da55 100644 --- a/applications/examples/example_event_loop/example_event_loop_multi.c +++ b/applications/examples/example_event_loop/example_event_loop_multi.c @@ -52,7 +52,7 @@ typedef struct { */ // This function is executed each time the data is taken out of the stream buffer. It is used to restart the worker timer. -static bool +static void event_loop_multi_app_stream_buffer_worker_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopMultiAppWorker* worker = context; @@ -62,8 +62,6 @@ static bool FURI_LOG_I(TAG, "Data was removed from buffer"); // Restart the timer to generate another block of random data. furi_event_loop_timer_start(worker->timer, WORKER_DATA_INTERVAL_MS); - - return true; } // This function is executed when the worker timer expires. The timer will NOT restart automatically @@ -152,7 +150,7 @@ static void event_loop_multi_app_input_callback(InputEvent* event, void* context } // This function is executed each time new data is available in the stream buffer. -static bool +static void event_loop_multi_app_stream_buffer_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopMultiApp* app = context; @@ -172,12 +170,10 @@ static bool FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str)); furi_string_free(tmp_str); - - return true; } // This function is executed each time a new message is inserted in the input queue. -static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) { +static void event_loop_multi_app_input_queue_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopMultiApp* app = context; @@ -222,8 +218,6 @@ static bool event_loop_multi_app_input_queue_callback(FuriEventLoopObject* objec // Not a long press, just print the key's name. FURI_LOG_I(TAG, "Short press: %s", input_get_key_name(event.key)); } - - return true; } // This function is executed each time the countdown timer expires. diff --git a/applications/examples/example_event_loop/example_event_loop_mutex.c b/applications/examples/example_event_loop/example_event_loop_mutex.c index d043f3f89..20bf7af4b 100644 --- a/applications/examples/example_event_loop/example_event_loop_mutex.c +++ b/applications/examples/example_event_loop/example_event_loop_mutex.c @@ -59,7 +59,7 @@ static int32_t event_loop_mutex_app_worker_thread(void* context) { } // This function is being run each time when the mutex gets released -static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) { +static void event_loop_mutex_app_event_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopMutexApp* app = context; @@ -82,8 +82,6 @@ static bool event_loop_mutex_app_event_callback(FuriEventLoopObject* object, voi MUTEX_EVENT_AND_FLAGS, event_loop_mutex_app_event_callback, app); - - return true; } static EventLoopMutexApp* event_loop_mutex_app_alloc(void) { diff --git a/applications/examples/example_event_loop/example_event_loop_stream_buffer.c b/applications/examples/example_event_loop/example_event_loop_stream_buffer.c index 65dbd83cf..6f7280973 100644 --- a/applications/examples/example_event_loop/example_event_loop_stream_buffer.c +++ b/applications/examples/example_event_loop/example_event_loop_stream_buffer.c @@ -54,7 +54,7 @@ static int32_t event_loop_stream_buffer_app_worker_thread(void* context) { } // This function is being run each time when the number of bytes in the buffer is above its trigger level. -static bool +static void event_loop_stream_buffer_app_event_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); EventLoopStreamBufferApp* app = context; @@ -76,8 +76,6 @@ static bool FURI_LOG_I(TAG, "Received data: %s", furi_string_get_cstr(tmp_str)); furi_string_free(tmp_str); - - return true; } static EventLoopStreamBufferApp* event_loop_stream_buffer_app_alloc(void) { diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 5d8dc61cb..09feee40f 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -212,7 +212,7 @@ static void dolphin_update_clear_limits_timer_period(void* context) { FURI_LOG_D(TAG, "Daily limits reset in %lu ms", time_to_clear_limits); } -static bool dolphin_process_event(FuriEventLoopObject* object, void* context) { +static void dolphin_process_event(FuriEventLoopObject* object, void* context) { UNUSED(object); Dolphin* dolphin = context; @@ -264,8 +264,6 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) { } dolphin_event_release(&event); - - return true; } static void dolphin_storage_callback(const void* message, void* context) { diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 6db4d8241..e85ff2b20 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -376,7 +376,7 @@ void view_dispatcher_update(View* view, void* context) { } } -bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) { +void view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); ViewDispatcher* instance = context; furi_assert(instance->event_queue == object); @@ -384,11 +384,9 @@ bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* conte uint32_t event; furi_check(furi_message_queue_get(instance->event_queue, &event, 0) == FuriStatusOk); view_dispatcher_handle_custom_event(instance, event); - - return true; } -bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) { +void view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); ViewDispatcher* instance = context; furi_assert(instance->input_queue == object); @@ -396,6 +394,4 @@ bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* conte InputEvent input; furi_check(furi_message_queue_get(instance->input_queue, &input, 0) == FuriStatusOk); view_dispatcher_handle_input(instance, &input); - - return true; } diff --git a/applications/services/gui/view_dispatcher_i.h b/applications/services/gui/view_dispatcher_i.h index 3d84b5499..a5f87d75c 100644 --- a/applications/services/gui/view_dispatcher_i.h +++ b/applications/services/gui/view_dispatcher_i.h @@ -57,7 +57,7 @@ void view_dispatcher_set_current_view(ViewDispatcher* view_dispatcher, View* vie void view_dispatcher_update(View* view, void* context); /** ViewDispatcher run event loop event callback */ -bool view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context); +void view_dispatcher_run_event_callback(FuriEventLoopObject* object, void* context); /** ViewDispatcher run event loop input callback */ -bool view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context); +void view_dispatcher_run_input_callback(FuriEventLoopObject* object, void* context); diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 189bf24da..b73c4a1dd 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -191,7 +191,7 @@ static void power_handle_reboot(PowerBootMode mode) { furi_hal_power_reset(); } -static bool power_message_callback(FuriEventLoopObject* object, void* context) { +static void power_message_callback(FuriEventLoopObject* object, void* context) { furi_assert(context); Power* power = context; @@ -223,8 +223,6 @@ static bool power_message_callback(FuriEventLoopObject* object, void* context) { if(msg.lock) { api_lock_unlock(msg.lock); } - - return true; } static void power_tick_callback(void* context) { diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.c b/applications/system/js_app/modules/js_event_loop/js_event_loop.c index c4f0d1bee..7f45c1a0f 100644 --- a/applications/system/js_app/modules/js_event_loop/js_event_loop.c +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.c @@ -80,7 +80,7 @@ static void js_event_loop_callback_generic(void* param) { /** * @brief Handles non-timer events */ -static bool js_event_loop_callback(void* object, void* param) { +static void js_event_loop_callback(void* object, void* param) { JsEventLoopCallbackContext* context = param; if(context->transformer) { @@ -102,8 +102,6 @@ static bool js_event_loop_callback(void* object, void* param) { } js_event_loop_callback_generic(param); - - return true; } /** diff --git a/furi/core/event_flag.c b/furi/core/event_flag.c index 19b28a500..721f4c2fa 100644 --- a/furi/core/event_flag.c +++ b/furi/core/event_flag.c @@ -5,11 +5,15 @@ #include #include +#include "event_loop_link_i.h" + #define FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS 24U -#define FURI_EVENT_FLAG_INVALID_BITS (~((1UL << FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS) - 1U)) +#define FURI_EVENT_FLAG_VALID_BITS ((1UL << FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS) - 1U) +#define FURI_EVENT_FLAG_INVALID_BITS (~(FURI_EVENT_FLAG_VALID_BITS)) struct FuriEventFlag { StaticEventGroup_t container; + FuriEventLoopLink event_loop_link; }; // IMPORTANT: container MUST be the FIRST struct member @@ -27,6 +31,11 @@ FuriEventFlag* furi_event_flag_alloc(void) { void furi_event_flag_free(FuriEventFlag* instance) { furi_check(!FURI_IS_IRQ_MODE()); + + // Event Loop must be disconnected + furi_check(!instance->event_loop_link.item_in); + furi_check(!instance->event_loop_link.item_out); + vEventGroupDelete((EventGroupHandle_t)instance); free(instance); } @@ -39,6 +48,8 @@ uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags) { uint32_t rflags; BaseType_t yield; + FURI_CRITICAL_ENTER(); + if(FURI_IS_IRQ_MODE()) { yield = pdFALSE; if(xEventGroupSetBitsFromISR(hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) { @@ -48,11 +59,15 @@ uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags) { portYIELD_FROM_ISR(yield); } } else { - vTaskSuspendAll(); rflags = xEventGroupSetBits(hEventGroup, (EventBits_t)flags); - (void)xTaskResumeAll(); } + if(rflags & flags) { + furi_event_loop_link_notify(&instance->event_loop_link, FuriEventLoopEventIn); + } + + FURI_CRITICAL_EXIT(); + /* Return event flags after setting */ return rflags; } @@ -64,6 +79,7 @@ uint32_t furi_event_flag_clear(FuriEventFlag* instance, uint32_t flags) { EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance; uint32_t rflags; + FURI_CRITICAL_ENTER(); if(FURI_IS_IRQ_MODE()) { rflags = xEventGroupGetBitsFromISR(hEventGroup); @@ -79,6 +95,11 @@ uint32_t furi_event_flag_clear(FuriEventFlag* instance, uint32_t flags) { rflags = xEventGroupClearBits(hEventGroup, (EventBits_t)flags); } + if(rflags & flags) { + furi_event_loop_link_notify(&instance->event_loop_link, FuriEventLoopEventOut); + } + FURI_CRITICAL_EXIT(); + /* Return event flags before clearing */ return rflags; } @@ -146,6 +167,36 @@ uint32_t furi_event_flag_wait( } } + if((rflags & FuriFlagError) == 0U) { + furi_event_loop_link_notify(&instance->event_loop_link, FuriEventLoopEventOut); + } + /* Return event flags before clearing */ return rflags; } + +static FuriEventLoopLink* furi_event_flag_event_loop_get_link(FuriEventLoopObject* object) { + FuriEventFlag* instance = object; + furi_assert(instance); + return &instance->event_loop_link; +} + +static bool + furi_event_flag_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) { + FuriEventFlag* instance = object; + furi_assert(instance); + + if(event == FuriEventLoopEventIn) { + return (furi_event_flag_get(instance) & FURI_EVENT_FLAG_VALID_BITS); + } else if(event == FuriEventLoopEventOut) { + return (furi_event_flag_get(instance) & FURI_EVENT_FLAG_VALID_BITS) != + FURI_EVENT_FLAG_VALID_BITS; + } else { + furi_crash(); + } +} + +const FuriEventLoopContract furi_event_flag_event_loop_contract = { + .get_link = furi_event_flag_event_loop_get_link, + .get_level = furi_event_flag_event_loop_get_level, +}; diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index b622aa7a1..c0998ea90 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -101,36 +101,39 @@ void furi_event_loop_free(FuriEventLoop* instance) { } static inline FuriEventLoopProcessStatus - furi_event_loop_poll_process_level_event(FuriEventLoopItem* item) { - if(!item->contract->get_level(item->object, item->event)) { - return FuriEventLoopProcessStatusComplete; - } else if(item->callback(item->object, item->callback_context)) { - return FuriEventLoopProcessStatusIncomplete; - } else { - return FuriEventLoopProcessStatusAgain; - } + furi_event_loop_process_edge_event(FuriEventLoopItem* item) { + FuriEventLoopProcessStatus status = FuriEventLoopProcessStatusComplete; + item->callback(item->object, item->callback_context); + + return status; } static inline FuriEventLoopProcessStatus - furi_event_loop_poll_process_edge_event(FuriEventLoopItem* item) { - if(item->callback(item->object, item->callback_context)) { - return FuriEventLoopProcessStatusComplete; - } else { - return FuriEventLoopProcessStatusAgain; + furi_event_loop_process_level_event(FuriEventLoopItem* item) { + FuriEventLoopProcessStatus status = FuriEventLoopProcessStatusComplete; + if(item->contract->get_level(item->object, item->event)) { + item->callback(item->object, item->callback_context); + + if(item->contract->get_level(item->object, item->event)) { + status = FuriEventLoopProcessStatusIncomplete; + } } + + return status; } static inline FuriEventLoopProcessStatus - furi_event_loop_poll_process_event(FuriEventLoop* instance, FuriEventLoopItem* item) { + furi_event_loop_process_event(FuriEventLoop* instance, FuriEventLoopItem* item) { FuriEventLoopProcessStatus status; + if(item->event & FuriEventLoopEventFlagOnce) { furi_event_loop_unsubscribe(instance, item->object); } if(item->event & FuriEventLoopEventFlagEdge) { - status = furi_event_loop_poll_process_edge_event(item); + status = furi_event_loop_process_edge_event(item); } else { - status = furi_event_loop_poll_process_level_event(item); + status = furi_event_loop_process_level_event(item); } if(item->owner == NULL) { @@ -140,7 +143,7 @@ static inline FuriEventLoopProcessStatus return status; } -static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) { +static inline FuriEventLoopItem* furi_event_loop_get_waiting_item(FuriEventLoop* instance) { FuriEventLoopItem* item = NULL; FURI_CRITICAL_ENTER(); @@ -152,27 +155,42 @@ static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) { FURI_CRITICAL_EXIT(); + return item; +} + +static inline void furi_event_loop_sync_flags(FuriEventLoop* instance) { + FURI_CRITICAL_ENTER(); + + if(!WaitingList_empty_p(instance->waiting_list)) { + xTaskNotifyIndexed( + (TaskHandle_t)instance->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagEvent, + eSetBits); + } + + FURI_CRITICAL_EXIT(); +} + +static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) { + FuriEventLoopItem* item = furi_event_loop_get_waiting_item(instance); if(!item) return; - while(true) { - FuriEventLoopProcessStatus ret = furi_event_loop_poll_process_event(instance, item); + FuriEventLoopProcessStatus status = furi_event_loop_process_event(instance, item); - if(ret == FuriEventLoopProcessStatusComplete) { - // Event processing complete, break from loop - break; - } else if(ret == FuriEventLoopProcessStatusIncomplete) { - // Event processing incomplete more processing needed - } else if(ret == FuriEventLoopProcessStatusAgain) { //-V547 - furi_event_loop_item_notify(item); - break; - // Unsubscribed from inside the callback, delete item - } else if(ret == FuriEventLoopProcessStatusFreeLater) { //-V547 - furi_event_loop_item_free(item); - break; - } else { - furi_crash(); - } + if(status == FuriEventLoopProcessStatusComplete) { + // Event processing complete, do nothing + } else if(status == FuriEventLoopProcessStatusIncomplete) { + // Event processing incomplete, put item back in waiting list + furi_event_loop_item_notify(item); + } else if(status == FuriEventLoopProcessStatusFreeLater) { //-V547 + // Unsubscribed from inside the callback, delete item + furi_event_loop_item_free(item); + } else { + furi_crash(); } + + furi_event_loop_sync_flags(instance); } static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) { @@ -239,14 +257,28 @@ void furi_event_loop_run(FuriEventLoop* instance) { } } +static void furi_event_loop_notify(FuriEventLoop* instance, FuriEventLoopFlag flag) { + if(FURI_IS_IRQ_MODE()) { + BaseType_t yield = pdFALSE; + + (void)xTaskNotifyIndexedFromISR( + (TaskHandle_t)instance->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + flag, + eSetBits, + &yield); + + portYIELD_FROM_ISR(yield); + + } else { + (void)xTaskNotifyIndexed( + (TaskHandle_t)instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, flag, eSetBits); + } +} + void furi_event_loop_stop(FuriEventLoop* instance) { furi_check(instance); - - xTaskNotifyIndexed( - (TaskHandle_t)instance->thread_id, - FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, - FuriEventLoopFlagStop, - eSetBits); + furi_event_loop_notify(instance, FuriEventLoopFlagStop); } /* @@ -268,11 +300,7 @@ void furi_event_loop_pend_callback( PendingQueue_push_front(instance->pending_queue, item); - xTaskNotifyIndexed( - (TaskHandle_t)instance->thread_id, - FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, - FuriEventLoopFlagPending, - eSetBits); + furi_event_loop_notify(instance, FuriEventLoopFlagPending); } /* @@ -328,6 +356,17 @@ static void furi_event_loop_object_subscribe( * Public specialized subscription API */ +void furi_event_loop_subscribe_event_flag( + FuriEventLoop* instance, + FuriEventFlag* event_flag, + FuriEventLoopEvent event, + FuriEventLoopEventCallback callback, + void* context) { + extern const FuriEventLoopContract furi_event_flag_event_loop_contract; + furi_event_loop_object_subscribe( + instance, event_flag, &furi_event_flag_event_loop_contract, event, callback, context); +} + void furi_event_loop_subscribe_message_queue( FuriEventLoop* instance, FuriMessageQueue* message_queue, @@ -491,11 +530,7 @@ static void furi_event_loop_item_notify(FuriEventLoopItem* instance) { FURI_CRITICAL_EXIT(); - xTaskNotifyIndexed( - (TaskHandle_t)owner->thread_id, - FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, - FuriEventLoopFlagEvent, - eSetBits); + furi_event_loop_notify(owner, FuriEventLoopFlagEvent); } static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) { diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index 6c5ba432c..d5e8710a6 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -11,6 +11,9 @@ * provide any compatibility with other event driven APIs. But * programming concepts are the same, except some runtime * limitations from our side. + * + * @warning Only ONE instance of FuriEventLoop per thread is possible. ALL FuriEventLoop + * funcitons MUST be called from the same thread that the instance was created in. */ #pragma once @@ -197,10 +200,29 @@ typedef void FuriEventLoopObject; * * @param object The object that triggered the event * @param context The context that was provided upon subscription - * - * @return true if event was processed, false if we need to delay processing */ -typedef bool (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context); +typedef void (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context); + +/** Opaque event flag type */ +typedef struct FuriEventFlag FuriEventFlag; + +/** Subscribe to event flag events + * + * @warning you can only have one subscription for one event type. + * + * @param instance The Event Loop instance + * @param event_flag The event flag to add + * @param[in] event The Event Loop event to trigger on + * @param[in] callback The callback to call on event + * @param context The context for callback + */ + +void furi_event_loop_subscribe_event_flag( + FuriEventLoop* instance, + FuriEventFlag* event_flag, + FuriEventLoopEvent event, + FuriEventLoopEventCallback callback, + void* context); /** Opaque message queue type */ typedef struct FuriMessageQueue FuriMessageQueue; diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 15efa8f86..7016e1e1b 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -59,7 +59,6 @@ typedef enum { typedef enum { FuriEventLoopProcessStatusComplete, FuriEventLoopProcessStatusIncomplete, - FuriEventLoopProcessStatusAgain, FuriEventLoopProcessStatusFreeLater, } FuriEventLoopProcessStatus; diff --git a/furi/core/event_loop_link_i.h b/furi/core/event_loop_link_i.h index 992ca6555..4b993390f 100644 --- a/furi/core/event_loop_link_i.h +++ b/furi/core/event_loop_link_i.h @@ -21,7 +21,7 @@ void furi_event_loop_link_notify(FuriEventLoopLink* instance, FuriEventLoopEvent typedef FuriEventLoopLink* (*FuriEventLoopContractGetLink)(FuriEventLoopObject* object); -typedef uint32_t ( +typedef bool ( *FuriEventLoopContractGetLevel)(FuriEventLoopObject* object, FuriEventLoopEvent event); typedef struct { diff --git a/furi/core/event_loop_timer.h b/furi/core/event_loop_timer.h index 9034043fa..50fb57389 100644 --- a/furi/core/event_loop_timer.h +++ b/furi/core/event_loop_timer.h @@ -1,6 +1,9 @@ /** * @file event_loop_timer.h * @brief Software timer functionality for FuriEventLoop. + * + * @warning ALL FuriEventLoopTimer functions MUST be called from the + * same thread that the owner FuriEventLoop instance was created in. */ #pragma once diff --git a/furi/core/message_queue.c b/furi/core/message_queue.c index bd0cec021..b0862e501 100644 --- a/furi/core/message_queue.c +++ b/furi/core/message_queue.c @@ -213,7 +213,7 @@ static FuriEventLoopLink* furi_message_queue_event_loop_get_link(FuriEventLoopOb return &instance->event_loop_link; } -static uint32_t +static bool furi_message_queue_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) { FuriMessageQueue* instance = object; furi_assert(instance); diff --git a/furi/core/mutex.c b/furi/core/mutex.c index f9848e1ba..edaba9e00 100644 --- a/furi/core/mutex.c +++ b/furi/core/mutex.c @@ -144,13 +144,13 @@ static FuriEventLoopLink* furi_mutex_event_loop_get_link(FuriEventLoopObject* ob return &instance->event_loop_link; } -static uint32_t +static bool furi_mutex_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) { FuriMutex* instance = object; furi_assert(instance); if(event == FuriEventLoopEventIn || event == FuriEventLoopEventOut) { - return furi_mutex_get_owner(instance) ? 0 : 1; + return !furi_mutex_get_owner(instance); } else { furi_crash(); } diff --git a/furi/core/semaphore.c b/furi/core/semaphore.c index 850169ad6..d05b9bf09 100644 --- a/furi/core/semaphore.c +++ b/furi/core/semaphore.c @@ -165,7 +165,7 @@ static FuriEventLoopLink* furi_semaphore_event_loop_get_link(FuriEventLoopObject return &instance->event_loop_link; } -static uint32_t +static bool furi_semaphore_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) { FuriSemaphore* instance = object; furi_assert(instance); diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index f35abec64..783b2d741 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -157,7 +157,7 @@ static FuriEventLoopLink* furi_stream_buffer_event_loop_get_link(FuriEventLoopOb return &stream_buffer->event_loop_link; } -static uint32_t +static bool furi_stream_buffer_event_loop_get_level(FuriEventLoopObject* object, FuriEventLoopEvent event) { FuriStreamBuffer* stream_buffer = object; furi_assert(stream_buffer); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index be60c6a1c..8b7e0a393 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.3,, +Version,+,78.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1120,6 +1120,7 @@ Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObj 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_subscribe_event_flag,void,"FuriEventLoop*, FuriEventFlag*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5ad8c3be3..2bbbe53b7 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.3,, +Version,+,78.0,, 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,, @@ -1230,6 +1230,7 @@ Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObj 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_subscribe_event_flag,void,"FuriEventLoop*, FuriEventFlag*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*"