From fdfaeccdd29c0c2b4c236eca794b1d366fae9ce3 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sat, 13 Jan 2024 22:41:19 +0100 Subject: [PATCH 1/9] Platform: Core: add a basic message queue to the emulator thread --- src/nba/include/nba/integer.hpp | 2 ++ .../core/include/platform/emulator_thread.hpp | 21 +++++++++++- src/platform/core/src/emulator_thread.cpp | 34 ++++++++++++++++++- src/platform/qt/src/widget/main_window.cpp | 8 +---- 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/nba/include/nba/integer.hpp b/src/nba/include/nba/integer.hpp index 6b6188c1f..0a2a73441 100644 --- a/src/nba/include/nba/integer.hpp +++ b/src/nba/include/nba/integer.hpp @@ -23,6 +23,8 @@ using u64 = std::uint64_t; using s64 = std::int64_t; using uint = unsigned int; +using u8bool = u8; + #ifdef NBA_NO_EXPORT_INT_TYPES } // namespace nba #endif diff --git a/src/platform/core/include/platform/emulator_thread.hpp b/src/platform/core/include/platform/emulator_thread.hpp index 34f1ca438..b548250c5 100644 --- a/src/platform/core/include/platform/emulator_thread.hpp +++ b/src/platform/core/include/platform/emulator_thread.hpp @@ -10,8 +10,11 @@ #include #include #include +#include #include -#include +#include +#include +#include namespace nba { @@ -29,7 +32,23 @@ struct EmulatorThread { void Start(); void Stop(); + void Reset(); + private: + enum class MessageType : u8 { + Reset + }; + + struct Message { + MessageType type; + }; + + void PushMessage(const Message& message); + void ProcessMessages(); + + std::queue msg_queue; + std::mutex msg_queue_mutex; + std::unique_ptr& core; FrameLimiter frame_limiter; std::thread thread; diff --git a/src/platform/core/src/emulator_thread.cpp b/src/platform/core/src/emulator_thread.cpp index 4ba9de1df..87de106eb 100644 --- a/src/platform/core/src/emulator_thread.cpp +++ b/src/platform/core/src/emulator_thread.cpp @@ -5,6 +5,7 @@ * Refer to the included LICENSE file. */ +#include #include namespace nba { @@ -16,7 +17,7 @@ EmulatorThread::EmulatorThread( } EmulatorThread::~EmulatorThread() { - if(IsRunning()) Stop(); + Stop(); } bool EmulatorThread::IsRunning() const { @@ -54,6 +55,8 @@ void EmulatorThread::Start() { frame_limiter.Reset(); while(running.load()) { + ProcessMessages(); + frame_limiter.Run([this]() { if(!paused) { per_frame_cb(); @@ -77,4 +80,33 @@ void EmulatorThread::Stop() { } } +void EmulatorThread::Reset() { + PushMessage({.type = MessageType::Reset}); +} + +void EmulatorThread::PushMessage(const Message& message) { + std::lock_guard lock_guard{msg_queue_mutex}; + msg_queue.push(message); // @todo: maybe use emplace. +} + +void EmulatorThread::ProcessMessages() { + // potential optimization: use a separate std::atomic_int to track the number of messages in the queue + // use atomic_int to do early bail out without acquiring the mutex. + std::lock_guard lock_guard{msg_queue_mutex}; + + while(!msg_queue.empty()) { + const Message& message = msg_queue.front(); + + switch(message.type) { + case MessageType::Reset: { + core->Reset(); + break; + } + default: Assert(false, "unhandled message type: {}", (int)message.type); + } + + msg_queue.pop(); + } +} + } // namespace nba diff --git a/src/platform/qt/src/widget/main_window.cpp b/src/platform/qt/src/widget/main_window.cpp index 472c972a2..25e133764 100644 --- a/src/platform/qt/src/widget/main_window.cpp +++ b/src/platform/qt/src/widget/main_window.cpp @@ -666,13 +666,7 @@ void MainWindow::FileOpen() { } void MainWindow::Reset() { - bool was_running = emu_thread->IsRunning(); - - emu_thread->Stop(); - core->Reset(); - if(was_running) { - emu_thread->Start(); - } + emu_thread->Reset(); } void MainWindow::SetPause(bool paused) { From 01fd36ff939d69c3edfe15b13634bc42cddff447 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sun, 14 Jan 2024 00:41:46 +0100 Subject: [PATCH 2/9] Core: replace InputDevice API with something simpler Also moves input changes on the emulator thread message queue. --- src/nba/CMakeLists.txt | 1 - src/nba/include/nba/config.hpp | 2 - src/nba/include/nba/core.hpp | 15 ++++ src/nba/include/nba/device/input_device.hpp | 62 -------------- src/nba/include/nba/scheduler.hpp | 2 - src/nba/src/core.cpp | 6 +- src/nba/src/core.hpp | 1 + src/nba/src/hw/keypad/keypad.cpp | 82 +++---------------- src/nba/src/hw/keypad/keypad.hpp | 29 +------ .../core/include/platform/emulator_thread.hpp | 17 +++- src/platform/core/src/emulator_thread.cpp | 21 ++++- src/platform/qt/rc/config.toml | 22 ++--- src/platform/qt/src/config.hpp | 15 ++-- .../qt/src/widget/controller_manager.cpp | 8 +- .../qt/src/widget/controller_manager.hpp | 2 +- src/platform/qt/src/widget/input_window.hpp | 4 +- src/platform/qt/src/widget/main_window.cpp | 12 ++- src/platform/qt/src/widget/main_window.hpp | 5 +- 18 files changed, 100 insertions(+), 206 deletions(-) delete mode 100644 src/nba/include/nba/device/input_device.hpp diff --git a/src/nba/CMakeLists.txt b/src/nba/CMakeLists.txt index ce145f93f..b6d044848 100644 --- a/src/nba/CMakeLists.txt +++ b/src/nba/CMakeLists.txt @@ -98,7 +98,6 @@ set(HEADERS_PUBLIC include/nba/common/punning.hpp include/nba/common/scope_exit.hpp include/nba/device/audio_device.hpp - include/nba/device/input_device.hpp include/nba/device/video_device.hpp include/nba/rom/backup/backup.hpp include/nba/rom/backup/backup_file.hpp diff --git a/src/nba/include/nba/config.hpp b/src/nba/include/nba/config.hpp index 0bf838c0e..54029d802 100644 --- a/src/nba/include/nba/config.hpp +++ b/src/nba/include/nba/config.hpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -47,7 +46,6 @@ struct Config { } audio; std::shared_ptr audio_dev = std::make_shared(); - std::shared_ptr input_dev = std::make_shared(); std::shared_ptr video_dev = std::make_shared(); }; diff --git a/src/nba/include/nba/core.hpp b/src/nba/include/nba/core.hpp index 88f688606..853821050 100644 --- a/src/nba/include/nba/core.hpp +++ b/src/nba/include/nba/core.hpp @@ -19,6 +19,20 @@ namespace nba { +enum class Key : u8 { + A = 0, + B = 1, + Select = 2, + Start = 3, + Right = 4, + Left = 5, + Up = 6, + Down = 7, + L = 8, + R = 9, + Count = 10 +}; + struct CoreBase { static constexpr int kCyclesPerFrame = 280896; @@ -32,6 +46,7 @@ struct CoreBase { virtual auto CreateSolarSensor() -> std::unique_ptr = 0; virtual void LoadState(SaveState const& state) = 0; virtual void CopyState(SaveState& state) = 0; + virtual void SetKeyStatus(Key key, bool pressed) = 0; virtual void Run(int cycles) = 0; virtual auto GetROM() -> ROM& = 0; diff --git a/src/nba/include/nba/device/input_device.hpp b/src/nba/include/nba/device/input_device.hpp deleted file mode 100644 index 6dfdd63e6..000000000 --- a/src/nba/include/nba/device/input_device.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2023 fleroviux - * - * Licensed under GPLv3 or any later version. - * Refer to the included LICENSE file. - */ - -#pragma once - -#include - -namespace nba { - -struct InputDevice { - virtual ~InputDevice() = default; - - enum class Key { - Up, - Down, - Left, - Right, - Start, - Select, - A, - B, - L, - R - }; - - static constexpr int kKeyCount = 10; - - virtual auto Poll(Key key) -> bool = 0; - virtual void SetOnChangeCallback(std::function callback) = 0; -}; - -struct NullInputDevice : InputDevice { - auto Poll(Key key) -> bool final { - return false; - } - - void SetOnChangeCallback(std::function callback) final {} -}; - -struct BasicInputDevice : InputDevice { - void SetKeyStatus(Key key, bool pressed) { - key_status[static_cast(key)] = pressed; - keypress_callback(); - } - - auto Poll(Key key) -> bool final { - return key_status[static_cast(key)]; - } - - void SetOnChangeCallback(std::function callback) final { - keypress_callback = callback; - } -private: - std::function keypress_callback; - bool key_status[kKeyCount]; -}; - -} // namespace nba diff --git a/src/nba/include/nba/scheduler.hpp b/src/nba/include/nba/scheduler.hpp index d09dfa195..092d8bec1 100644 --- a/src/nba/include/nba/scheduler.hpp +++ b/src/nba/include/nba/scheduler.hpp @@ -67,8 +67,6 @@ struct Scheduler { SIO_transfer_done, - KeyPad_Poll, - EndOfQueue, Count }; diff --git a/src/nba/src/core.cpp b/src/nba/src/core.cpp index 1368143a6..8db307ebb 100644 --- a/src/nba/src/core.cpp +++ b/src/nba/src/core.cpp @@ -23,7 +23,7 @@ Core::Core(std::shared_ptr config) , apu(scheduler, dma, bus, config) , ppu(scheduler, irq, dma, config) , timer(scheduler, irq, apu) - , keypad(scheduler, irq, config) + , keypad(scheduler, irq) , bus(scheduler, {cpu, irq, dma, apu, ppu, timer, keypad}) { Reset(); } @@ -71,6 +71,10 @@ auto Core::CreateSolarSensor() -> std::unique_ptr { return std::make_unique(); } +void Core::SetKeyStatus(Key key, bool pressed) { + keypad.SetKeyStatus(key, pressed); +} + void Core::Run(int cycles) { using HaltControl = Bus::Hardware::HaltControl; diff --git a/src/nba/src/core.hpp b/src/nba/src/core.hpp index 27a1f64bf..32630f9f4 100644 --- a/src/nba/src/core.hpp +++ b/src/nba/src/core.hpp @@ -30,6 +30,7 @@ struct Core final : CoreBase { auto CreateSolarSensor() -> std::unique_ptr override; void LoadState(SaveState const& state) override; void CopyState(SaveState& state) override; + void SetKeyStatus(Key key, bool pressed) override; void Run(int cycles) override; auto GetROM() -> ROM& override; diff --git a/src/nba/src/hw/keypad/keypad.cpp b/src/nba/src/hw/keypad/keypad.cpp index 8376c641d..48ffe804e 100644 --- a/src/nba/src/hw/keypad/keypad.cpp +++ b/src/nba/src/hw/keypad/keypad.cpp @@ -11,12 +11,9 @@ namespace nba::core { -KeyPad::KeyPad(Scheduler& scheduler, IRQ& irq, std::shared_ptr config) +KeyPad::KeyPad(Scheduler& scheduler, IRQ& irq) : scheduler(scheduler) - , irq(irq) - , config(config) { - scheduler.Register(Scheduler::EventClass::KeyPad_Poll, this, &KeyPad::Poll); - + , irq(irq) { Reset(); } @@ -25,29 +22,18 @@ void KeyPad::Reset() { input.keypad = this; control = {}; control.keypad = this; - config->input_dev->SetOnChangeCallback(std::bind(&KeyPad::UpdateInput, this)); - - input_queue.Reset(); - scheduler.Add(k_poll_interval, Scheduler::EventClass::KeyPad_Poll); } -void KeyPad::UpdateInput() { - auto& input_device = config->input_dev; +void KeyPad::SetKeyStatus(Key key, bool pressed) { + const u16 bit = 1 << (int)key; - u16 input = 0; - - if(!input_device->Poll(Key::A)) input |= 1; - if(!input_device->Poll(Key::B)) input |= 2; - if(!input_device->Poll(Key::Select)) input |= 4; - if(!input_device->Poll(Key::Start)) input |= 8; - if(!input_device->Poll(Key::Right)) input |= 16; - if(!input_device->Poll(Key::Left)) input |= 32; - if(!input_device->Poll(Key::Up)) input |= 64; - if(!input_device->Poll(Key::Down)) input |= 128; - if(!input_device->Poll(Key::R)) input |= 256; - if(!input_device->Poll(Key::L)) input |= 512; + if(pressed) { + input.value &= ~bit; + } else { + input.value |= bit; + } - input_queue.Enqueue(input); + UpdateIRQ(); } void KeyPad::UpdateIRQ() { @@ -64,21 +50,7 @@ void KeyPad::UpdateIRQ() { } } -void KeyPad::Poll() { - PollInternal(); - scheduler.Add(k_poll_interval, Scheduler::EventClass::KeyPad_Poll); -} - -void KeyPad::PollInternal() { - while(input_queue.DataAvailable()) { - input.value = input_queue.Dequeue(); - UpdateIRQ(); - } -} - auto KeyPad::KeyInput::ReadByte(uint offset) -> u8 { - keypad->PollInternal(); - switch(offset) { case 0: return u8(value); @@ -133,38 +105,4 @@ void KeyPad::KeyControl::WriteHalf(u16 value) { keypad->UpdateIRQ(); } -void KeyPad::InputQueue::Reset() { - rd_ptr = 0; - wr_ptr = 0; - size = 0; -} - -bool KeyPad::InputQueue::DataAvailable() { - return size > 0; -} - -void KeyPad::InputQueue::Enqueue(u16 input) { - data[wr_ptr] = input; - wr_ptr = (wr_ptr + 1) % k_buffer_size; - - std::size_t current_size = size.load(); - - while(!size.compare_exchange_weak(current_size, std::min(current_size + 1u, k_buffer_size))) { - current_size = size.load(); - } -} - -auto KeyPad::InputQueue::Dequeue() -> u16 { - const u16 value = data[rd_ptr]; - rd_ptr = (rd_ptr + 1) % k_buffer_size; - - std::size_t current_size = size.load(); - - while(!size.compare_exchange_weak(current_size, current_size - 1)) { - current_size = size.load(); - } - - return value; -} - } // namespace nba::core diff --git a/src/nba/src/hw/keypad/keypad.hpp b/src/nba/src/hw/keypad/keypad.hpp index 19bb7514d..3d2d08130 100644 --- a/src/nba/src/hw/keypad/keypad.hpp +++ b/src/nba/src/hw/keypad/keypad.hpp @@ -7,8 +7,7 @@ #pragma once -#include -#include +#include #include #include #include @@ -18,9 +17,10 @@ namespace nba::core { struct KeyPad { - KeyPad(Scheduler& scheduler, IRQ& irq, std::shared_ptr config); + KeyPad(Scheduler& scheduler, IRQ& irq); void Reset(); + void SetKeyStatus(Key key, bool pressed); struct KeyInput { u16 value = 0x3FF; @@ -50,33 +50,10 @@ struct KeyPad { void CopyState(SaveState& state); private: - static constexpr int k_poll_interval = 16384; - - using Key = InputDevice::Key; - - void UpdateInput(); void UpdateIRQ(); - void Poll(); - void PollInternal(); - Scheduler& scheduler; IRQ& irq; - std::shared_ptr config; - - struct InputQueue { - static constexpr std::size_t k_buffer_size = 16; - - std::atomic data[k_buffer_size]; - std::atomic size = 0; - std::size_t rd_ptr = 0; - std::size_t wr_ptr = 0; - - void Reset(); - bool DataAvailable(); - void Enqueue(u16 input); - auto Dequeue() -> u16; - } input_queue{}; }; } // namespace nba::core diff --git a/src/platform/core/include/platform/emulator_thread.hpp b/src/platform/core/include/platform/emulator_thread.hpp index b548250c5..5f9acc65a 100644 --- a/src/platform/core/include/platform/emulator_thread.hpp +++ b/src/platform/core/include/platform/emulator_thread.hpp @@ -33,19 +33,34 @@ struct EmulatorThread { void Stop(); void Reset(); + void SetKeyStatus(Key key, bool pressed); private: enum class MessageType : u8 { - Reset + Reset, + SetKeyStatus }; struct Message { MessageType type; + union { + struct { + Key key; + u8bool pressed; + } set_key_status; + }; }; void PushMessage(const Message& message); void ProcessMessages(); + static constexpr int k_input_subsample_count = 4; + static constexpr int k_cycles_per_second = 16777216; + static constexpr int k_cycles_per_frame = 280896; + static constexpr int k_cycles_per_subsample = k_cycles_per_frame / k_input_subsample_count; + + static_assert(k_cycles_per_frame % k_input_subsample_count == 0); + std::queue msg_queue; std::mutex msg_queue_mutex; diff --git a/src/platform/core/src/emulator_thread.cpp b/src/platform/core/src/emulator_thread.cpp index 87de106eb..d09a420a1 100644 --- a/src/platform/core/src/emulator_thread.cpp +++ b/src/platform/core/src/emulator_thread.cpp @@ -13,7 +13,7 @@ namespace nba { EmulatorThread::EmulatorThread( std::unique_ptr& core ) : core(core) { - frame_limiter.Reset(59.7275); + frame_limiter.Reset(k_cycles_per_second / (float)k_cycles_per_subsample); } EmulatorThread::~EmulatorThread() { @@ -59,14 +59,16 @@ void EmulatorThread::Start() { frame_limiter.Run([this]() { if(!paused) { + // @todo: decide what to do with the per_frame_cb(). per_frame_cb(); - core->RunForOneFrame(); + core->Run(k_cycles_per_subsample); } }, [this](float fps) { + float real_fps = fps / k_input_subsample_count; if(paused) { - fps = 0; + real_fps = 0; } - frame_rate_cb(fps); + frame_rate_cb(real_fps); }); } }}; @@ -84,6 +86,13 @@ void EmulatorThread::Reset() { PushMessage({.type = MessageType::Reset}); } +void EmulatorThread::SetKeyStatus(Key key, bool pressed) { + PushMessage({ + .type = MessageType::SetKeyStatus, + .set_key_status = {.key = key, .pressed = (u8bool)pressed} + }); +} + void EmulatorThread::PushMessage(const Message& message) { std::lock_guard lock_guard{msg_queue_mutex}; msg_queue.push(message); // @todo: maybe use emplace. @@ -102,6 +111,10 @@ void EmulatorThread::ProcessMessages() { core->Reset(); break; } + case MessageType::SetKeyStatus: { + core->SetKeyStatus(message.set_key_status.key, message.set_key_status.pressed); + break; + } default: Assert(false, "unhandled message type: {}", (int)message.type); } diff --git a/src/platform/qt/rc/config.toml b/src/platform/qt/rc/config.toml index 89d0bf168..79f5c3206 100644 --- a/src/platform/qt/rc/config.toml +++ b/src/platform/qt/rc/config.toml @@ -30,20 +30,20 @@ mp2k_hle_cubic = true mp2k_hle_force_reverb = true [input] -fast_forward = [32, -1, -1, -1, 0] hold_fast_forward = true +fast_forward = [32, -1, -1, -1, 0] controller_guid = "" [input.gba] -b = [83, -1, -1, -1, 0] -a = [65, -1, -1, -1, 0] -select = [16777219, -1, -1, -1, 0] -right = [16777236, -1, -1, -1, 0] -r = [70, -1, -1, -1, 0] -l = [68, -1, -1, -1, 0] -left = [16777234, -1, -1, -1, 0] -start = [16777220, -1, -1, -1, 0] -down = [16777237, -1, -1, -1, 0] -up = [16777235, -1, -1, -1, 0] +b = [16777237,-1,-1,-1,0] +a = [16777235,-1,-1,-1,0] +select = [16777234,-1,-1,-1,0] +right = [16777220-1,-1,-1,0] +r = [70,-1,-1,-1,0] +l = [68,-1,-1,-1,0] +left = [16777219,-1,-1,-1,0] +start = [16777236,-1,-1,-1,0] +down = [83,-1,-1,-1,0] +up = [65,-1,-1,-1,0] [window] fullscreen = false diff --git a/src/platform/qt/src/config.hpp b/src/platform/qt/src/config.hpp index bec62472d..99b8a95a0 100644 --- a/src/platform/qt/src/config.hpp +++ b/src/platform/qt/src/config.hpp @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -49,15 +50,15 @@ struct QtConfig final : nba::PlatformConfig { } }; - Map gba[nba::InputDevice::kKeyCount] { - {Qt::Key_Up}, - {Qt::Key_Down}, - {Qt::Key_Left}, - {Qt::Key_Right}, - {Qt::Key_Return}, - {Qt::Key_Backspace}, + Map gba[(int)nba::Key::Count] { {Qt::Key_A}, {Qt::Key_S}, + {Qt::Key_Backspace}, + {Qt::Key_Return}, + {Qt::Key_Right}, + {Qt::Key_Left}, + {Qt::Key_Up}, + {Qt::Key_Down}, {Qt::Key_D}, {Qt::Key_F} }; diff --git a/src/platform/qt/src/widget/controller_manager.cpp b/src/platform/qt/src/widget/controller_manager.cpp index eb8077b27..6ecd1be0b 100644 --- a/src/platform/qt/src/widget/controller_manager.cpp +++ b/src/platform/qt/src/widget/controller_manager.cpp @@ -119,7 +119,7 @@ void ControllerManager::Open(std::string const& guid) { } void ControllerManager::Close() { - using Key = nba::InputDevice::Key; + using Key = nba::Key; // Unset all keys in case any key is currently pressed. main_window->SetKeyStatus(1, Key::Up, false); @@ -211,7 +211,7 @@ void ControllerManager::ProcessEvents() { } void ControllerManager::UpdateKeyState() { - using Key = nba::InputDevice::Key; + using Key = nba::Key; #if defined(__APPLE__) std::lock_guard guard{lock}; @@ -253,9 +253,9 @@ void ControllerManager::UpdateKeyState() { return pressed; }; - for(int i = 0; i < nba::InputDevice::kKeyCount; i++) { + for(int i = 0; i < (int)nba::Key::Count; i++) { main_window->SetKeyStatus( - 1, static_cast(i), evaluate(input.gba[i])); + 1, static_cast(i), evaluate(input.gba[i])); } bool fast_forward_button = evaluate(input.fast_forward); diff --git a/src/platform/qt/src/widget/controller_manager.hpp b/src/platform/qt/src/widget/controller_manager.hpp index 547e1fc12..39f9a8af8 100644 --- a/src/platform/qt/src/widget/controller_manager.hpp +++ b/src/platform/qt/src/widget/controller_manager.hpp @@ -7,8 +7,8 @@ #pragma once +#include #include -#include #include #include #include diff --git a/src/platform/qt/src/widget/input_window.hpp b/src/platform/qt/src/widget/input_window.hpp index d69c5866e..2f96a99a9 100644 --- a/src/platform/qt/src/widget/input_window.hpp +++ b/src/platform/qt/src/widget/input_window.hpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -34,7 +34,7 @@ inline auto GetJoystickGUIDStringFromIndex(int device_index) -> std::string { } struct InputWindow : QDialog { - using Key = nba::InputDevice::Key; + using Key = nba::Key; InputWindow( QApplication* app, diff --git a/src/platform/qt/src/widget/main_window.cpp b/src/platform/qt/src/widget/main_window.cpp index 25e133764..1a25955c5 100644 --- a/src/platform/qt/src/widget/main_window.cpp +++ b/src/platform/qt/src/widget/main_window.cpp @@ -55,7 +55,6 @@ MainWindow::MainWindow( config->video_dev = screen; config->audio_dev = std::make_shared(); - config->input_dev = input_device; core = nba::CreateCore(config); emu_thread = std::make_unique(core); @@ -611,9 +610,9 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) { auto pressed = type == QEvent::KeyPress; auto const& input = config->input; - for(int i = 0; i < nba::InputDevice::kKeyCount; i++) { + for(int i = 0; i < (int)nba::Key::Count; i++) { if(input.gba[i].keyboard == key) { - SetKeyStatus(0, static_cast(i), pressed); + SetKeyStatus(0, static_cast(i), pressed); } } @@ -896,11 +895,10 @@ auto MainWindow::GetSavePath(fs::path const& rom_path, fs::path const& extension return fs::path{rom_path}.replace_extension(extension); } -void MainWindow::SetKeyStatus(int channel, nba::InputDevice::Key key, bool pressed) { - key_input[channel][int(key)] = pressed; +void MainWindow::SetKeyStatus(int channel, nba::Key key, bool pressed) { + key_input[channel][(int)key] = pressed; - input_device->SetKeyStatus(key, - key_input[0][int(key)] || key_input[1][int(key)]); + emu_thread->SetKeyStatus(key, key_input[0][(int)key] || key_input[1][(int)key]); } void MainWindow::SetFastForward(int channel, bool pressed) { diff --git a/src/platform/qt/src/widget/main_window.hpp b/src/platform/qt/src/widget/main_window.hpp index 1a3c66739..a0e0fdaf7 100644 --- a/src/platform/qt/src/widget/main_window.hpp +++ b/src/platform/qt/src/widget/main_window.hpp @@ -117,7 +117,7 @@ private slots: void UpdateMenuBarVisibility(); void UpdateMainWindowActionList(); - void SetKeyStatus(int channel, nba::InputDevice::Key key, bool pressed); + void SetKeyStatus(int channel, nba::Key key, bool pressed); void SetFastForward(int channel, bool pressed); void UpdateWindowSize(); void SetFullscreen(bool value); @@ -130,11 +130,10 @@ private slots: auto GetSavePath(fs::path const& rom_path, fs::path const& extension) -> fs::path; std::shared_ptr screen; - std::shared_ptr input_device = std::make_shared(); std::shared_ptr config = std::make_shared(); std::unique_ptr core; std::unique_ptr emu_thread; - bool key_input[2][nba::InputDevice::kKeyCount] {false}; + bool key_input[2][(int)nba::Key::Count] {false}; bool fast_forward[2] {false}; ControllerManager* controller_manager; From d63eb127610983453adad619159cc39aca221b7f Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sun, 14 Jan 2024 01:00:36 +0100 Subject: [PATCH 3/9] Qt: fix a typo in the default config.toml --- src/platform/qt/rc/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/rc/config.toml b/src/platform/qt/rc/config.toml index 79f5c3206..81ff2bd4d 100644 --- a/src/platform/qt/rc/config.toml +++ b/src/platform/qt/rc/config.toml @@ -37,7 +37,7 @@ controller_guid = "" b = [16777237,-1,-1,-1,0] a = [16777235,-1,-1,-1,0] select = [16777234,-1,-1,-1,0] -right = [16777220-1,-1,-1,0] +right = [16777220,-1,-1,-1,0] r = [70,-1,-1,-1,0] l = [68,-1,-1,-1,0] left = [16777219,-1,-1,-1,0] From 934e88fc80ff643d734c9325e416f2844b4cb475 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sun, 14 Jan 2024 01:08:19 +0100 Subject: [PATCH 4/9] Platform: Core: exclude access to the core from outside the emulator by design When the emulator thread is started, it now takes ownership of the core, which means that short of keeping around raw pointers (which we doing at the moment, but plan to get rid of), the the UI thread cannot access the core while the emulator thread is running. --- .../core/include/platform/emulator_thread.hpp | 11 +++++----- src/platform/core/src/emulator_thread.cpp | 14 +++++++------ src/platform/qt/src/widget/main_window.cpp | 21 ++++++++++--------- src/platform/qt/src/widget/main_window.hpp | 4 ++++ 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/platform/core/include/platform/emulator_thread.hpp b/src/platform/core/include/platform/emulator_thread.hpp index 5f9acc65a..37819e9fe 100644 --- a/src/platform/core/include/platform/emulator_thread.hpp +++ b/src/platform/core/include/platform/emulator_thread.hpp @@ -19,7 +19,7 @@ namespace nba { struct EmulatorThread { - EmulatorThread(std::unique_ptr& core); + EmulatorThread(); ~EmulatorThread(); bool IsRunning() const; @@ -29,8 +29,9 @@ struct EmulatorThread { void SetFastForward(bool enabled); void SetFrameRateCallback(std::function callback); void SetPerFrameCallback(std::function callback); - void Start(); - void Stop(); + + void Start(std::unique_ptr core); + std::unique_ptr Stop(); void Reset(); void SetKeyStatus(Key key, bool pressed); @@ -58,13 +59,13 @@ struct EmulatorThread { static constexpr int k_cycles_per_second = 16777216; static constexpr int k_cycles_per_frame = 280896; static constexpr int k_cycles_per_subsample = k_cycles_per_frame / k_input_subsample_count; - + static_assert(k_cycles_per_frame % k_input_subsample_count == 0); std::queue msg_queue; std::mutex msg_queue_mutex; - std::unique_ptr& core; + std::unique_ptr core; FrameLimiter frame_limiter; std::thread thread; std::atomic_bool running = false; diff --git a/src/platform/core/src/emulator_thread.cpp b/src/platform/core/src/emulator_thread.cpp index d09a420a1..e03afc8ce 100644 --- a/src/platform/core/src/emulator_thread.cpp +++ b/src/platform/core/src/emulator_thread.cpp @@ -10,9 +10,7 @@ namespace nba { -EmulatorThread::EmulatorThread( - std::unique_ptr& core -) : core(core) { +EmulatorThread::EmulatorThread() { frame_limiter.Reset(k_cycles_per_second / (float)k_cycles_per_subsample); } @@ -48,7 +46,9 @@ void EmulatorThread::SetPerFrameCallback(std::function callback) { per_frame_cb = callback; } -void EmulatorThread::Start() { +void EmulatorThread::Start(std::unique_ptr core) { + this->core = std::move(core); + if(!running) { running = true; thread = std::thread{[this]() { @@ -61,7 +61,7 @@ void EmulatorThread::Start() { if(!paused) { // @todo: decide what to do with the per_frame_cb(). per_frame_cb(); - core->Run(k_cycles_per_subsample); + this->core->Run(k_cycles_per_subsample); } }, [this](float fps) { float real_fps = fps / k_input_subsample_count; @@ -75,11 +75,13 @@ void EmulatorThread::Start() { } } -void EmulatorThread::Stop() { +std::unique_ptr EmulatorThread::Stop() { if(IsRunning()) { running = false; thread.join(); } + + return std::move(core); } void EmulatorThread::Reset() { diff --git a/src/platform/qt/src/widget/main_window.cpp b/src/platform/qt/src/widget/main_window.cpp index 1a25955c5..fbe8ffc4d 100644 --- a/src/platform/qt/src/widget/main_window.cpp +++ b/src/platform/qt/src/widget/main_window.cpp @@ -56,7 +56,8 @@ MainWindow::MainWindow( config->video_dev = screen; config->audio_dev = std::make_shared(); core = nba::CreateCore(config); - emu_thread = std::make_unique(core); + core_not_thread_safe = core.get(); + emu_thread = std::make_unique(); app->installEventFilter(this); @@ -383,7 +384,7 @@ void MainWindow::CreateToolsMenu() { connect(tools_menu->addAction(tr("Palette Viewer")), &QAction::triggered, [this]() { if(!palette_viewer_window) { - palette_viewer_window = new PaletteViewerWindow{core.get(), this}; + palette_viewer_window = new PaletteViewerWindow{core_not_thread_safe, this}; connect(screen.get(), &Screen::RequestDraw, palette_viewer_window, &PaletteViewerWindow::Update); } @@ -392,7 +393,7 @@ void MainWindow::CreateToolsMenu() { connect(tools_menu->addAction(tr("Background Viewer")), &QAction::triggered, [this]() { if(!background_viewer_window) { - background_viewer_window = new BackgroundViewerWindow{core.get(), this}; + background_viewer_window = new BackgroundViewerWindow{core_not_thread_safe, this}; connect(screen.get(), &Screen::RequestDraw, background_viewer_window, &BackgroundViewerWindow::Update); } @@ -400,7 +401,7 @@ void MainWindow::CreateToolsMenu() { }); connect(tools_menu->addAction(tr("Sprite Viewer")), &QAction::triggered, [this]() { - const auto sprite_viewer_window = new SpriteViewerWindow{core.get(), this}; + const auto sprite_viewer_window = new SpriteViewerWindow{core_not_thread_safe, this}; connect(screen.get(), &Screen::RequestDraw, sprite_viewer_window, &SpriteViewerWindow::Update); sprite_viewer_window->show(); }); @@ -680,7 +681,7 @@ void MainWindow::SetPause(bool paused) { void MainWindow::Stop() { if(emu_thread->IsRunning()) { - emu_thread->Stop(); + core = emu_thread->Stop(); config->audio_dev->Close(); screen->SetForceClear(true); @@ -806,7 +807,7 @@ void MainWindow::LoadROM(std::u16string const& path) { // Reset the core and start the emulation thread. // If the emulator is currently paused force-clear the screen. core->Reset(); - emu_thread->Start(); + emu_thread->Start(std::move(core)); /** * If the the emulator is paused we force-clear the screen to avoid @@ -821,7 +822,7 @@ void MainWindow::LoadROM(std::u16string const& path) { auto MainWindow::LoadState(std::u16string const& path) -> nba::SaveStateLoader::Result { bool was_running = emu_thread->IsRunning(); - emu_thread->Stop(); + core = emu_thread->Stop(); auto result = nba::SaveStateLoader::Load(core, path); @@ -854,7 +855,7 @@ auto MainWindow::LoadState(std::u16string const& path) -> nba::SaveStateLoader:: } if(was_running) { - emu_thread->Start(); + emu_thread->Start(std::move(core)); } return result; @@ -862,7 +863,7 @@ auto MainWindow::LoadState(std::u16string const& path) -> nba::SaveStateLoader:: auto MainWindow::SaveState(std::u16string const& path) -> nba::SaveStateWriter::Result { bool was_running = emu_thread->IsRunning(); - emu_thread->Stop(); + core = emu_thread->Stop(); auto result = nba::SaveStateWriter::Write(core, path); @@ -875,7 +876,7 @@ auto MainWindow::SaveState(std::u16string const& path) -> nba::SaveStateWriter:: } if(was_running) { - emu_thread->Start(); + emu_thread->Start(std::move(core)); } return result; diff --git a/src/platform/qt/src/widget/main_window.hpp b/src/platform/qt/src/widget/main_window.hpp index a0e0fdaf7..a6db68fe5 100644 --- a/src/platform/qt/src/widget/main_window.hpp +++ b/src/platform/qt/src/widget/main_window.hpp @@ -137,6 +137,10 @@ private slots: bool fast_forward[2] {false}; ControllerManager* controller_manager; + // The PPU debuggers do not access the core in a thread-safe way yet. + // So until that is fixed we have to keep a raw pointer around... + nba::CoreBase* core_not_thread_safe; + QAction* pause_action; InputWindow* input_window; QMenu* recent_menu; From db6802f7f1c3f616230d8820eb2d160b88078a7e Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sun, 14 Jan 2024 01:42:13 +0100 Subject: [PATCH 5/9] Platform: Core: assert when attempting to start an already running thread. --- src/platform/core/src/emulator_thread.cpp | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/platform/core/src/emulator_thread.cpp b/src/platform/core/src/emulator_thread.cpp index e03afc8ce..be2db4fbd 100644 --- a/src/platform/core/src/emulator_thread.cpp +++ b/src/platform/core/src/emulator_thread.cpp @@ -47,32 +47,32 @@ void EmulatorThread::SetPerFrameCallback(std::function callback) { } void EmulatorThread::Start(std::unique_ptr core) { - this->core = std::move(core); + Assert(!running, "Started an emulator thread which was already running"); - if(!running) { - running = true; - thread = std::thread{[this]() { - frame_limiter.Reset(); - - while(running.load()) { - ProcessMessages(); - - frame_limiter.Run([this]() { - if(!paused) { - // @todo: decide what to do with the per_frame_cb(). - per_frame_cb(); - this->core->Run(k_cycles_per_subsample); - } - }, [this](float fps) { - float real_fps = fps / k_input_subsample_count; - if(paused) { - real_fps = 0; - } - frame_rate_cb(real_fps); - }); - } - }}; - } + this->core = std::move(core); + running = true; + + thread = std::thread{[this]() { + frame_limiter.Reset(); + + while(running.load()) { + ProcessMessages(); + + frame_limiter.Run([this]() { + if(!paused) { + // @todo: decide what to do with the per_frame_cb(). + per_frame_cb(); + this->core->Run(k_cycles_per_subsample); + } + }, [this](float fps) { + float real_fps = fps / k_input_subsample_count; + if(paused) { + real_fps = 0; + } + frame_rate_cb(real_fps); + }); + } + }}; } std::unique_ptr EmulatorThread::Stop() { From ff39a47159be8039a1d5ab2bbf5f8f534e6cd51c Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sun, 14 Jan 2024 01:53:08 +0100 Subject: [PATCH 6/9] Platform: Core: ignore emulator thread messages sent while the thread isn't running --- src/platform/core/src/emulator_thread.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/platform/core/src/emulator_thread.cpp b/src/platform/core/src/emulator_thread.cpp index be2db4fbd..497872f64 100644 --- a/src/platform/core/src/emulator_thread.cpp +++ b/src/platform/core/src/emulator_thread.cpp @@ -72,6 +72,9 @@ void EmulatorThread::Start(std::unique_ptr core) { frame_rate_cb(real_fps); }); } + + // Make sure all messages are handled before exiting + ProcessMessages(); }}; } @@ -96,6 +99,12 @@ void EmulatorThread::SetKeyStatus(Key key, bool pressed) { } void EmulatorThread::PushMessage(const Message& message) { + // @todo: think of the best way to transparently handle messages + // sent while the emulator thread isn't running. + if(!IsRunning()) { + return; + } + std::lock_guard lock_guard{msg_queue_mutex}; msg_queue.push(message); // @todo: maybe use emplace. } From 3f7a202e31759dd65aa63a5b35af28e5a21c9089 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Sun, 14 Jan 2024 22:03:37 +0100 Subject: [PATCH 7/9] Qt: fix incorrect saving and loading of the input mapping config. --- src/platform/qt/rc/config.toml | 18 +++++++++--------- src/platform/qt/src/config.cpp | 32 ++++++++++++++++---------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/platform/qt/rc/config.toml b/src/platform/qt/rc/config.toml index 81ff2bd4d..76f3b8973 100644 --- a/src/platform/qt/rc/config.toml +++ b/src/platform/qt/rc/config.toml @@ -34,16 +34,16 @@ hold_fast_forward = true fast_forward = [32, -1, -1, -1, 0] controller_guid = "" [input.gba] -b = [16777237,-1,-1,-1,0] -a = [16777235,-1,-1,-1,0] -select = [16777234,-1,-1,-1,0] -right = [16777220,-1,-1,-1,0] -r = [70,-1,-1,-1,0] +a = [65,-1,-1,-1,0] +b = [83,-1,-1,-1,0] +select = [16777219,-1,-1,-1,0] +start = [16777220,-1,-1,-1,0] +right = [16777236,-1,-1,-1,0] +left = [16777234,-1,-1,-1,0] +up = [16777235,-1,-1,-1,0] +down = [16777237,-1,-1,-1,0] l = [68,-1,-1,-1,0] -left = [16777219,-1,-1,-1,0] -start = [16777236,-1,-1,-1,0] -down = [83,-1,-1,-1,0] -up = [65,-1,-1,-1,0] +r = [70,-1,-1,-1,0] [window] fullscreen = false diff --git a/src/platform/qt/src/config.cpp b/src/platform/qt/src/config.cpp index 1d7371870..e4954dc5b 100644 --- a/src/platform/qt/src/config.cpp +++ b/src/platform/qt/src/config.cpp @@ -31,14 +31,14 @@ void QtConfig::LoadCustomData(toml::value const& data) { if(gba_result.is_ok()) { auto gba = gba_result.unwrap(); - input.gba[0] = get_map(gba, "up"); - input.gba[1] = get_map(gba, "down"); - input.gba[2] = get_map(gba, "left"); - input.gba[3] = get_map(gba, "right"); - input.gba[4] = get_map(gba, "start"); - input.gba[5] = get_map(gba, "select"); - input.gba[6] = get_map(gba, "a"); - input.gba[7] = get_map(gba, "b"); + input.gba[0] = get_map(gba, "a"); + input.gba[1] = get_map(gba, "b"); + input.gba[2] = get_map(gba, "select"); + input.gba[3] = get_map(gba, "start"); + input.gba[4] = get_map(gba, "right"); + input.gba[5] = get_map(gba, "left"); + input.gba[6] = get_map(gba, "up"); + input.gba[7] = get_map(gba, "down"); input.gba[8] = get_map(gba, "l"); input.gba[9] = get_map(gba, "r"); } @@ -72,14 +72,14 @@ void QtConfig::SaveCustomData( data["input"]["fast_forward"] = input.fast_forward.Array(); data["input"]["hold_fast_forward"] = input.hold_fast_forward; - data["input"]["gba"]["up"] = input.gba[0].Array(); - data["input"]["gba"]["down"] = input.gba[1].Array(); - data["input"]["gba"]["left"] = input.gba[2].Array(); - data["input"]["gba"]["right"] = input.gba[3].Array(); - data["input"]["gba"]["start"] = input.gba[4].Array(); - data["input"]["gba"]["select"] = input.gba[5].Array(); - data["input"]["gba"]["a"] = input.gba[6].Array(); - data["input"]["gba"]["b"] = input.gba[7].Array(); + data["input"]["gba"]["a"] = input.gba[0].Array(); + data["input"]["gba"]["b"] = input.gba[1].Array(); + data["input"]["gba"]["select"] = input.gba[2].Array(); + data["input"]["gba"]["start"] = input.gba[3].Array(); + data["input"]["gba"]["right"] = input.gba[4].Array(); + data["input"]["gba"]["left"] = input.gba[5].Array(); + data["input"]["gba"]["up"] = input.gba[6].Array(); + data["input"]["gba"]["down"] = input.gba[7].Array(); data["input"]["gba"]["l"] = input.gba[8].Array(); data["input"]["gba"]["r"] = input.gba[9].Array(); From d9e87f9509cd93d6ce5cedbe4fa5037c95262398 Mon Sep 17 00:00:00 2001 From: fleroviux Date: Thu, 18 Jan 2024 21:35:05 +0100 Subject: [PATCH 8/9] Platform: Core: reduce latency on handling messages produced on the emulator thread --- .../core/include/platform/emulator_thread.hpp | 1 + src/platform/core/src/emulator_thread.cpp | 40 +++++++++++-------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/platform/core/include/platform/emulator_thread.hpp b/src/platform/core/include/platform/emulator_thread.hpp index 37819e9fe..90a9017ae 100644 --- a/src/platform/core/include/platform/emulator_thread.hpp +++ b/src/platform/core/include/platform/emulator_thread.hpp @@ -54,6 +54,7 @@ struct EmulatorThread { void PushMessage(const Message& message); void ProcessMessages(); + void ProcessMessage(const Message& message); static constexpr int k_input_subsample_count = 4; static constexpr int k_cycles_per_second = 16777216; diff --git a/src/platform/core/src/emulator_thread.cpp b/src/platform/core/src/emulator_thread.cpp index 497872f64..1a4bdd66b 100644 --- a/src/platform/core/src/emulator_thread.cpp +++ b/src/platform/core/src/emulator_thread.cpp @@ -105,8 +105,15 @@ void EmulatorThread::PushMessage(const Message& message) { return; } - std::lock_guard lock_guard{msg_queue_mutex}; - msg_queue.push(message); // @todo: maybe use emplace. + if(std::this_thread::get_id() == thread.get_id()) { + // Messages sent on the emulator thread (i.e. from a callback) do + // not need to be pushed to the message queue. + // Process them right away instead to reduce latency. + ProcessMessage(message); + } else { + std::lock_guard lock_guard{msg_queue_mutex}; + msg_queue.push(message); // @todo: maybe use emplace. + } } void EmulatorThread::ProcessMessages() { @@ -115,22 +122,23 @@ void EmulatorThread::ProcessMessages() { std::lock_guard lock_guard{msg_queue_mutex}; while(!msg_queue.empty()) { - const Message& message = msg_queue.front(); - - switch(message.type) { - case MessageType::Reset: { - core->Reset(); - break; - } - case MessageType::SetKeyStatus: { - core->SetKeyStatus(message.set_key_status.key, message.set_key_status.pressed); - break; - } - default: Assert(false, "unhandled message type: {}", (int)message.type); - } - + ProcessMessage(msg_queue.front()); msg_queue.pop(); } } +void EmulatorThread::ProcessMessage(const Message& message) { + switch(message.type) { + case MessageType::Reset: { + core->Reset(); + break; + } + case MessageType::SetKeyStatus: { + core->SetKeyStatus(message.set_key_status.key, message.set_key_status.pressed); + break; + } + default: Assert(false, "unhandled message type: {}", (int)message.type); + } +} + } // namespace nba From 27dc1cf346eeca2ed16de61f867a0ae700f0b08e Mon Sep 17 00:00:00 2001 From: fleroviux Date: Thu, 18 Jan 2024 22:03:25 +0100 Subject: [PATCH 9/9] Platform: Core: improve naming regarding input subsampling --- src/platform/core/include/platform/emulator_thread.hpp | 6 +++--- src/platform/core/src/emulator_thread.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/core/include/platform/emulator_thread.hpp b/src/platform/core/include/platform/emulator_thread.hpp index 90a9017ae..8327d6627 100644 --- a/src/platform/core/include/platform/emulator_thread.hpp +++ b/src/platform/core/include/platform/emulator_thread.hpp @@ -56,12 +56,12 @@ struct EmulatorThread { void ProcessMessages(); void ProcessMessage(const Message& message); - static constexpr int k_input_subsample_count = 4; + static constexpr int k_number_of_input_subframes = 4; static constexpr int k_cycles_per_second = 16777216; static constexpr int k_cycles_per_frame = 280896; - static constexpr int k_cycles_per_subsample = k_cycles_per_frame / k_input_subsample_count; + static constexpr int k_cycles_per_subframe = k_cycles_per_frame / k_number_of_input_subframes; - static_assert(k_cycles_per_frame % k_input_subsample_count == 0); + static_assert(k_cycles_per_frame % k_number_of_input_subframes == 0); std::queue msg_queue; std::mutex msg_queue_mutex; diff --git a/src/platform/core/src/emulator_thread.cpp b/src/platform/core/src/emulator_thread.cpp index 1a4bdd66b..aaf0f4029 100644 --- a/src/platform/core/src/emulator_thread.cpp +++ b/src/platform/core/src/emulator_thread.cpp @@ -11,7 +11,7 @@ namespace nba { EmulatorThread::EmulatorThread() { - frame_limiter.Reset(k_cycles_per_second / (float)k_cycles_per_subsample); + frame_limiter.Reset(k_cycles_per_second / (float)k_cycles_per_subframe); } EmulatorThread::~EmulatorThread() { @@ -62,10 +62,10 @@ void EmulatorThread::Start(std::unique_ptr core) { if(!paused) { // @todo: decide what to do with the per_frame_cb(). per_frame_cb(); - this->core->Run(k_cycles_per_subsample); + this->core->Run(k_cycles_per_subframe); } }, [this](float fps) { - float real_fps = fps / k_input_subsample_count; + float real_fps = fps / k_number_of_input_subframes; if(paused) { real_fps = 0; }