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).

PR #5270 <https://github.com/Genymobile/scrcpy/pull/5270>
This commit is contained in:
Romain Vimont 2024-09-06 23:08:08 +02:00
parent e9240f6804
commit 8620d06741
3 changed files with 78 additions and 0 deletions

View File

@ -1,6 +1,7 @@
#include "events.h" #include "events.h"
#include "util/log.h" #include "util/log.h"
#include "util/thread.h"
bool bool
sc_push_event_impl(uint32_t type, const char *name) { 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; 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("Could not post runnable 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);
}

View File

@ -9,6 +9,7 @@
enum { enum {
SC_EVENT_NEW_FRAME = SDL_USEREVENT, SC_EVENT_NEW_FRAME = SDL_USEREVENT,
SC_EVENT_RUN_ON_MAIN_THREAD,
SC_EVENT_DEVICE_DISCONNECTED, SC_EVENT_DEVICE_DISCONNECTED,
SC_EVENT_SERVER_CONNECTION_FAILED, SC_EVENT_SERVER_CONNECTION_FAILED,
SC_EVENT_SERVER_CONNECTED, 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) #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 #endif

View File

@ -174,6 +174,12 @@ event_loop(struct scrcpy *s) {
case SDL_QUIT: case SDL_QUIT:
LOGD("User requested to quit"); LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS; 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: default:
if (!sc_screen_handle_event(&s->screen, &event)) { if (!sc_screen_handle_event(&s->screen, &event)) {
return SCRCPY_EXIT_FAILURE; return SCRCPY_EXIT_FAILURE;
@ -184,6 +190,21 @@ event_loop(struct scrcpy *s) {
return SCRCPY_EXIT_FAILURE; 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 // Return true on success, false on error
static bool static bool
await_for_server(bool *connected) { await_for_server(bool *connected) {
@ -819,6 +840,7 @@ scrcpy(struct scrcpy_options *options) {
} }
ret = event_loop(s); ret = event_loop(s);
terminate_event_loop();
LOGD("quit..."); LOGD("quit...");
if (options->video_playback) { if (options->video_playback) {