diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index b5b93d56..0187b424 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -9,9 +9,11 @@ #include #include #include +#include #include "emulator.hpp" #include "panda_qt/screen.hpp" +#include "services/hid.hpp" class MainWindow : public QMainWindow { Q_OBJECT @@ -23,15 +25,34 @@ class MainWindow : public QMainWindow { Dark = 2, }; + // Types of messages we might send from the GUI thread to the emulator thread + enum class MessageType { + LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey + }; + + // Tagged union representing our message queue messages + struct EmulatorMessage { + MessageType type; + + union { + struct { + std::filesystem::path* p; + } path; + + struct { + u32 key; + } key; + }; + }; + // This would normally be an std::unique_ptr but it's shared between threads so definitely not Emulator* emu = nullptr; std::thread emuThread; std::atomic appRunning = true; // Is the application itself running? - std::mutex messageQueueMutex; // Used for synchronizing messages between the emulator and UI - std::filesystem::path romToLoad = ""; - - bool needToLoadROM = false; + // Used for synchronizing messages between the emulator and UI + std::mutex messageQueueMutex; + std::vector messageQueue; ScreenWidget screen; QComboBox* themeSelect = nullptr; @@ -43,6 +64,8 @@ class MainWindow : public QMainWindow { void emuThreadMainLoop(); void selectROM(); void dumpRomFS(); + void sendMessage(const EmulatorMessage& message); + void dispatchMessage(const EmulatorMessage& message); // Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer bool usingGL = false; diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 2c2cc64f..8ffaf7b6 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -73,13 +73,13 @@ void MainWindow::emuThreadMainLoop() { { std::unique_lock lock(messageQueueMutex); - if (needToLoadROM) { - needToLoadROM = false; - - bool success = emu->loadROM(romToLoad); - if (!success) { - printf("Failed to load ROM"); + // Dispatch all messages in the message queue + if (!messageQueue.empty()) { + for (const auto& msg : messageQueue) { + dispatchMessage(msg); } + + messageQueue.clear(); } } @@ -102,23 +102,15 @@ void MainWindow::swapEmuBuffer() { } void MainWindow::selectROM() { - // Are we already waiting for a ROM to be loaded? Then complain about it! - { - std::unique_lock lock(messageQueueMutex); - if (needToLoadROM) { - QMessageBox::warning(this, tr("Already loading ROM"), tr("Panda3DS is already busy loading a ROM, please wait")); - return; - } - } - auto path = QFileDialog::getOpenFileName(this, tr("Select 3DS ROM to load"), "", tr("Nintendo 3DS ROMs (*.3ds *.cci *.cxi *.app *.3dsx *.elf *.axf)")); if (!path.isEmpty()) { - std::unique_lock lock(messageQueueMutex); + std::filesystem::path* p = new std::filesystem::path(path.toStdU16String()); - romToLoad = path.toStdU16String(); - needToLoadROM = true; + EmulatorMessage message{.type = MessageType::LoadROM}; + message.path.p = p; + sendMessage(message); } } @@ -135,6 +127,12 @@ MainWindow::~MainWindow() { delete themeSelect; } +// Send a message to the emulator thread. Lock the mutex and just push back to the vector. +void MainWindow::sendMessage(const EmulatorMessage& message) { + std::unique_lock lock(messageQueueMutex); + messageQueue.push_back(message); +} + void MainWindow::setTheme(Theme theme) { currentTheme = theme; @@ -225,4 +223,21 @@ void MainWindow::dumpRomFS() { QMessageBox::warning(this, tr("No RomFS found"), tr("No RomFS partition was found in the loaded app")); break; } +} + +void MainWindow::dispatchMessage(const EmulatorMessage& message) { + switch (message.type) { + case MessageType::LoadROM: + emu->loadROM(*message.path.p); + // Clean up the allocated path + delete message.path.p; + break; + + case MessageType::Pause: emu->pause(); break; + case MessageType::Resume: emu->resume(); break; + case MessageType::TogglePause: emu->togglePause(); break; + case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break; + case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break; + case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; + } } \ No newline at end of file