Add mechanism to run any closure on the main thread

This commit is contained in:
Henrik Rydgård 2025-03-03 12:57:27 +01:00
parent 57e114650b
commit d6d1b5bbdc
6 changed files with 37 additions and 19 deletions

View file

@ -100,6 +100,9 @@ enum class SystemRequestType {
RUN_CALLBACK_IN_WNDPROC,
};
// Run a closure on the main thread. Used to safely implement UI that runs on another thread.
void System_RunOnMainThread(std::function<void()> func);
// Implementations are supposed to process the request, and post the response to the g_RequestManager (see Message.h).
// This is not to be used directly by applications, instead use the g_RequestManager to make the requests.
// This can return false if it's known that the platform doesn't support the request, the app is supposed to handle

View file

@ -177,7 +177,7 @@ struct PendingMessage {
std::string value;
};
static std::mutex pendingMutex;
static std::mutex g_pendingMutex;
static std::vector<PendingMessage> pendingMessages;
static Draw::DrawContext *g_draw;
static Draw::Pipeline *colorPipeline;
@ -185,6 +185,7 @@ static Draw::Pipeline *texColorPipeline;
static UIContext *uiContext;
static int g_restartGraphics;
static bool g_windowHidden = false;
std::vector<std::function<void()>> g_pendingClosures;
#ifdef _WIN32
WindowsAudioBackend *winAudioBackend;
@ -345,6 +346,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
setlocale( LC_ALL, "C" );
std::string user_data_path = savegame_dir;
pendingMessages.clear();
g_pendingClosures.clear();
g_requestManager.Clear();
// external_dir has all kinds of meanings depending on platform.
@ -1047,19 +1049,28 @@ void NativeFrame(GraphicsContext *graphicsContext) {
g_screenManager->update();
// Do this after g_screenManager.update() so we can receive setting changes before rendering.
std::vector<PendingMessage> toProcess;
{
std::lock_guard<std::mutex> lock(pendingMutex);
toProcess = std::move(pendingMessages);
pendingMessages.clear();
}
for (const auto &item : toProcess) {
if (HandleGlobalMessage(item.message, item.value)) {
// TODO: Add a to-string thingy.
INFO_LOG(Log::System, "Handled global message: %d / %s", (int)item.message, item.value.c_str());
std::vector<PendingMessage> toProcess;
std::vector<std::function<void()>> toRun;
{
std::lock_guard<std::mutex> lock(g_pendingMutex);
toProcess = std::move(pendingMessages);
toRun = std::move(g_pendingClosures);
pendingMessages.clear();
g_pendingClosures.clear();
}
for (auto &item : toRun) {
item();
}
for (const auto &item : toProcess) {
if (HandleGlobalMessage(item.message, item.value)) {
// TODO: Add a to-string thingy.
INFO_LOG(Log::System, "Handled global message: %d / %s", (int)item.message, item.value.c_str());
}
g_screenManager->sendMessage(item.message, item.value.c_str());
}
g_screenManager->sendMessage(item.message, item.value.c_str());
}
g_requestManager.ProcessRequests();
@ -1404,13 +1415,18 @@ void NativeAccelerometer(float tiltX, float tiltY, float tiltZ) {
}
void System_PostUIMessage(UIMessage message, const std::string &value) {
std::lock_guard<std::mutex> lock(pendingMutex);
std::lock_guard<std::mutex> lock(g_pendingMutex);
PendingMessage pendingMessage;
pendingMessage.message = message;
pendingMessage.value = value;
pendingMessages.push_back(pendingMessage);
}
void System_RunOnMainThread(std::function<void()> func) {
std::lock_guard<std::mutex> lock(g_pendingMutex);
g_pendingClosures.push_back(std::move(func));
}
void NativeResized() {
// NativeResized can come from any thread so we just set a flag, then process it later.
VERBOSE_LOG(Log::G3D, "NativeResized - setting flag");

View file

@ -497,9 +497,7 @@ namespace MainWindow {
break;
case ID_EMULATION_PAUSE:
if (!NetworkWarnUserIfOnlineAndCantSpeed()) {
System_PostUIMessage(UIMessage::REQUEST_GAME_PAUSE);
}
System_PostUIMessage(UIMessage::REQUEST_GAME_PAUSE);
break;
case ID_EMULATION_STOP:

View file

@ -93,6 +93,7 @@ bool System_GetPropertyBool(SystemProperty prop) {
}
void System_Notify(SystemNotification notification) {}
void System_PostUIMessage(UIMessage message, const std::string &param) {}
void System_RunOnMainThread(std::function<void()>) {}
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
switch (type) {
case SystemRequestType::SEND_DEBUG_OUTPUT:

View file

@ -1913,6 +1913,7 @@ void System_Notify(SystemNotification notification) {
}
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) { return false; }
void System_PostUIMessage(UIMessage message, const std::string &param) {}
void System_RunOnMainThread(std::function<void()>) {}
void NativeFrame(GraphicsContext *graphicsContext) {}
void NativeResized() {}

View file

@ -112,6 +112,7 @@ bool System_GetPropertyBool(SystemProperty prop) {
}
void System_Notify(SystemNotification notification) {}
void System_PostUIMessage(UIMessage message, const std::string &param) {}
void System_RunOnMainThread(std::function<void()>) {}
void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\0'; }
void System_AudioClear() {}
void System_AudioPushSamples(const s32 *audio, int numSamples, float volume) {}
@ -119,9 +120,7 @@ void System_AudioPushSamples(const s32 *audio, int numSamples, float volume) {}
// TODO: To avoid having to define these here, these should probably be turned into system "requests".
// To clear the secret entirely, just save an empty string.
bool NativeSaveSecret(std::string_view nameOfSecret, std::string_view data) { return false; }
std::string NativeLoadSecret(std::string_view nameOfSecret) {
return "";
}
std::string NativeLoadSecret(std::string_view nameOfSecret) { return ""; }
#if PPSSPP_PLATFORM(ANDROID)
JNIEnv *getEnv() {