From 87d9d68c0780e6a9c59adf6a18833522d23891f1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 6 Sep 2024 23:08:08 +0200 Subject: [PATCH] Add mechanism to execute code on the main thread This allows to schedule a runnable to be executed on the main thread, until the event loop is explicitly terminated. It is guaranteed that all accepted runnables will be executed (this avoids possible memory leaks if a runnable owns resources). --- app/src/events.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ app/src/events.h | 9 +++++++++ app/src/scrcpy.c | 21 +++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/app/src/events.c b/app/src/events.c index 4e256be2..3ab8a4c8 100644 --- a/app/src/events.c +++ b/app/src/events.c @@ -1,6 +1,7 @@ #include "events.h" #include "util/log.h" +#include "util/thread.h" bool sc_push_event_impl(uint32_t type, const char *name) { @@ -17,3 +18,49 @@ sc_push_event_impl(uint32_t type, const char *name) { return true; } + +bool +sc_post_to_main_thread(sc_runnable_fn run, void *userdata) { + SDL_Event event = { + .user = { + .type = SC_EVENT_RUN_ON_MAIN_THREAD, + .data1 = run, + .data2 = userdata, + }, + }; + int ret = SDL_PushEvent(&event); + // ret < 0: error (queue full) + // ret == 0: event was filtered + // ret == 1: success + if (ret != 1) { + if (ret == 0) { + // if ret == 0, this is expected on exit, log in debug mode + LOGD("Could not post runnable to main thread (filtered)"); + } else { + assert(ret < 0); + LOGW("Coud not post to main thread: %s", SDL_GetError()); + } + return false; + } + + return true; +} + +static int SDLCALL +task_event_filter(void *userdata, SDL_Event *event) { + (void) userdata; + + if (event->type == SC_EVENT_RUN_ON_MAIN_THREAD) { + // Reject this event type from now on + return 0; + } + + return 1; +} + +void +sc_reject_new_runnables(void) { + assert(sc_thread_get_id() == SC_MAIN_THREAD_ID); + + SDL_SetEventFilter(task_event_filter, NULL); +} diff --git a/app/src/events.h b/app/src/events.h index d803fb68..3f15087a 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -9,6 +9,7 @@ enum { SC_EVENT_NEW_FRAME = SDL_USEREVENT, + SC_EVENT_RUN_ON_MAIN_THREAD, SC_EVENT_DEVICE_DISCONNECTED, SC_EVENT_SERVER_CONNECTION_FAILED, SC_EVENT_SERVER_CONNECTED, @@ -25,4 +26,12 @@ sc_push_event_impl(uint32_t type, const char *name); #define sc_push_event(TYPE) sc_push_event_impl(TYPE, # TYPE) +typedef void (*sc_runnable_fn)(void *userdata); + +bool +sc_post_to_main_thread(sc_runnable_fn run, void *userdata); + +void +sc_reject_new_runnables(void); + #endif diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5dfc425d..9e2ed414 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -174,6 +174,11 @@ event_loop(struct scrcpy *s) { case SDL_QUIT: LOGD("User requested to quit"); return SCRCPY_EXIT_SUCCESS; + case SC_EVENT_RUN_ON_MAIN_THREAD: + sc_runnable_fn run = event.user.data1; + void *userdata = event.user.data2; + run(userdata); + break; default: if (!sc_screen_handle_event(&s->screen, &event)) { return SCRCPY_EXIT_FAILURE; @@ -184,6 +189,21 @@ event_loop(struct scrcpy *s) { return SCRCPY_EXIT_FAILURE; } +static void +terminate_event_loop(void) { + sc_reject_new_runnables(); + + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SC_EVENT_RUN_ON_MAIN_THREAD) { + // Make sure all posted runnables are run, to avoid memory leaks + sc_runnable_fn run = event.user.data1; + void *userdata = event.user.data2; + run(userdata); + } + } +} + // Return true on success, false on error static bool await_for_server(bool *connected) { @@ -819,6 +839,7 @@ scrcpy(struct scrcpy_options *options) { } ret = event_loop(s); + terminate_event_loop(); LOGD("quit..."); if (options->video_playback) {