From 201aa659066b41a2cfe89b615213bcb093fcbe79 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 3 Feb 2026 17:29:17 -0600 Subject: [PATCH] Core: Make RunOnCPUThread always non-blocking. --- Source/Core/Core/Core.cpp | 22 +---- Source/Core/Core/Core.h | 3 +- Source/Core/Core/State.cpp | 126 +++++++++++++--------------- Source/Core/DolphinQt/GBAWidget.cpp | 98 ++++++++++------------ 4 files changed, 101 insertions(+), 148 deletions(-) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 95be762ba01..bdae112865e 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -825,8 +825,7 @@ static void RestoreStateAndUnlock(Core::System& system, const bool unpause_on_un system.GetCPU().RestoreStateAndUnlock(unpause_on_unlock); } -void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction function, - bool wait_for_completion) +void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction function) { if (IsCPUThread()) { @@ -834,8 +833,6 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction funct return; } - Common::OneShotEvent cpu_thread_job_finished; - // Pause the CPU (set it to stepping mode). const bool was_running = PauseAndLock(system); @@ -843,15 +840,6 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction funct { // If the core hasn't been started, there is no active CPU thread we can race against. function(); - wait_for_completion = false; - } - else if (wait_for_completion) - { - // Queue the job function followed by triggering the event. - system.GetCPU().AddCPUThreadJob([&function, &cpu_thread_job_finished] { - function(); - cpu_thread_job_finished.Set(); - }); } else { @@ -861,14 +849,6 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction funct // Release the CPU thread, and let it execute the callback. RestoreStateAndUnlock(system, was_running); - - // If we're waiting for completion, block until the event fires. - if (wait_for_completion) - { - // Periodically yield to the UI thread, so we don't deadlock. - while (!cpu_thread_job_finished.WaitFor(std::chrono::milliseconds(10))) - Host_YieldToUI(); - } } // --- Callbacks for backends / engine --- diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index a50fa54937c..7dff8e13d5d 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -158,8 +158,7 @@ void FrameUpdateOnCPUThread(); void OnFrameEnd(Core::System& system); // Run a function on the CPU thread, asynchronously. -void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction function, - bool wait_for_completion); +void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction function); // for calling back into UI code without introducing a dependency on it in core using StateChangedCallbackFunc = std::function; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 36974a9bd59..d9f37b3f487 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -496,11 +496,9 @@ static void SaveAsFromCore(Core::System& system, std::string filename) void SaveAs(Core::System& system, std::string filename) { Core::RunOnCPUThread( - system, - [&system, filename = std::move(filename), lock = GetStateSaveTaskLock()]() mutable { + system, [&system, filename = std::move(filename), lock = GetStateSaveTaskLock()]() mutable { SaveAsFromCore(system, std::move(filename)); - }, - false); + }); } static bool GetVersionFromLZO(StateHeader& header, File::IOFile& f) @@ -873,12 +871,9 @@ void LoadAs(Core::System& system, std::string filename) if (!CheckIfStateLoadIsAllowed(system)) return; - Core::RunOnCPUThread( - system, - [&system, filename = std::move(filename)]() mutable { - LoadAsFromCore(system, std::move(filename)); - }, - false); + Core::RunOnCPUThread(system, [&system, filename = std::move(filename)]() mutable { + LoadAsFromCore(system, std::move(filename)); + }); } void SetOnAfterLoadCallback(AfterLoadCallbackFunc callback) @@ -919,45 +914,39 @@ void LoadLastSaved(Core::System& system, int i) if (!CheckIfStateLoadIsAllowed(system)) return; - Core::RunOnCPUThread( - system, - [&system, i] { - // Data must reach the filesystem for up to date "UsedSlots". - s_compress_and_dump_thread.WaitForCompletion(); + Core::RunOnCPUThread(system, [&system, i] { + // Data must reach the filesystem for up to date "UsedSlots". + s_compress_and_dump_thread.WaitForCompletion(); - std::vector used_slots = GetUsedSlotsWithTimestamp(); - if (std::size_t(i) > used_slots.size()) - { - Core::DisplayMessage("State doesn't exist", 2000); - return; - } + std::vector used_slots = GetUsedSlotsWithTimestamp(); + if (std::size_t(i) > used_slots.size()) + { + Core::DisplayMessage("State doesn't exist", 2000); + return; + } - std::ranges::stable_sort(used_slots, std::ranges::greater{}, &SlotWithTimestamp::timestamp); - LoadAsFromCore(system, MakeStateFilename(used_slots[i].slot)); - }, - false); + std::ranges::stable_sort(used_slots, std::ranges::greater{}, &SlotWithTimestamp::timestamp); + LoadAsFromCore(system, MakeStateFilename(used_slots[i].slot)); + }); } void SaveFirstSaved(Core::System& system) { - Core::RunOnCPUThread( - system, - [&system, lock = GetStateSaveTaskLock()] { - // Data must reach the filesystem for up to date "UsedSlots". - s_compress_and_dump_thread.WaitForCompletion(); + Core::RunOnCPUThread(system, [&system, lock = GetStateSaveTaskLock()] { + // Data must reach the filesystem for up to date "UsedSlots". + s_compress_and_dump_thread.WaitForCompletion(); - std::vector used_slots = GetUsedSlotsWithTimestamp(); - auto slot = GetEmptySlot(used_slots); - if (!slot.has_value()) - { - // overwrite the oldest state - std::ranges::stable_sort(used_slots, {}, &SlotWithTimestamp::timestamp); - slot = used_slots.front().slot; - } + std::vector used_slots = GetUsedSlotsWithTimestamp(); + auto slot = GetEmptySlot(used_slots); + if (!slot.has_value()) + { + // overwrite the oldest state + std::ranges::stable_sort(used_slots, {}, &SlotWithTimestamp::timestamp); + slot = used_slots.front().slot; + } - SaveAsFromCore(system, MakeStateFilename(*slot)); - }, - false); + SaveAsFromCore(system, MakeStateFilename(*slot)); + }); } // Load the last state before loading the state @@ -966,36 +955,33 @@ void UndoLoadState(Core::System& system) if (!CheckIfStateLoadIsAllowed(system)) return; - Core::RunOnCPUThread( - system, - [&system] { - if (s_undo_load_buffer.empty()) - { - PanicAlertFmtT("There is nothing to undo!"); - return; - } + Core::RunOnCPUThread(system, [&system] { + if (s_undo_load_buffer.empty()) + { + PanicAlertFmtT("There is nothing to undo!"); + return; + } - auto& movie = system.GetMovie(); - if (movie.IsMovieActive()) - { - // Note: Only the CPU thread writes to "undo.dtm". - const std::string dtmpath = File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm"; - if (File::Exists(dtmpath)) - { - LoadFromBuffer(system, s_undo_load_buffer); - movie.LoadInput(dtmpath); - } - else - { - PanicAlertFmtT("No undo.dtm found, aborting undo load state to prevent movie desyncs"); - } - } - else - { - LoadFromBuffer(system, s_undo_load_buffer); - } - }, - false); + auto& movie = system.GetMovie(); + if (movie.IsMovieActive()) + { + // Note: Only the CPU thread writes to "undo.dtm". + const std::string dtmpath = File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm"; + if (File::Exists(dtmpath)) + { + LoadFromBuffer(system, s_undo_load_buffer); + movie.LoadInput(dtmpath); + } + else + { + PanicAlertFmtT("No undo.dtm found, aborting undo load state to prevent movie desyncs"); + } + } + else + { + LoadFromBuffer(system, s_undo_load_buffer); + } + }); } // Load the state that the last save state overwritten on diff --git a/Source/Core/DolphinQt/GBAWidget.cpp b/Source/Core/DolphinQt/GBAWidget.cpp index d375b3c8c22..af319b890ea 100644 --- a/Source/Core/DolphinQt/GBAWidget.cpp +++ b/Source/Core/DolphinQt/GBAWidget.cpp @@ -37,34 +37,28 @@ static void RestartCore(const std::weak_ptr& core, std::string_view rom_path = {}) { - Core::RunOnCPUThread( - Core::System::GetInstance(), - [core, rom_path = std::string(rom_path)] { - if (auto core_ptr = core.lock()) - { - auto& info = Config::MAIN_GBA_ROM_PATHS[core_ptr->GetCoreInfo().device_number]; - core_ptr->Stop(); - Config::SetCurrent(info, rom_path); - auto& system = Core::System::GetInstance(); - auto& core_timing = system.GetCoreTiming(); - if (core_ptr->Start(core_timing.GetTicks())) - return; - Config::SetCurrent(info, Config::GetBase(info)); - core_ptr->Start(core_timing.GetTicks()); - } - }, - false); + Core::RunOnCPUThread(Core::System::GetInstance(), [core, rom_path = std::string(rom_path)] { + if (auto core_ptr = core.lock()) + { + auto& info = Config::MAIN_GBA_ROM_PATHS[core_ptr->GetCoreInfo().device_number]; + core_ptr->Stop(); + Config::SetCurrent(info, rom_path); + auto& system = Core::System::GetInstance(); + auto& core_timing = system.GetCoreTiming(); + if (core_ptr->Start(core_timing.GetTicks())) + return; + Config::SetCurrent(info, Config::GetBase(info)); + core_ptr->Start(core_timing.GetTicks()); + } + }); } static void QueueEReaderCard(const std::weak_ptr& core, std::string_view card_path) { - Core::RunOnCPUThread( - Core::System::GetInstance(), - [core, card_path = std::string(card_path)] { - if (auto core_ptr = core.lock()) - core_ptr->EReaderQueueCard(card_path); - }, - false); + Core::RunOnCPUThread(Core::System::GetInstance(), [core, card_path = std::string(card_path)] { + if (auto core_ptr = core.lock()) + core_ptr->EReaderQueueCard(card_path); + }); } GBAWidget::GBAWidget(std::weak_ptr core, const HW::GBA::CoreInfo& info, @@ -161,13 +155,11 @@ void GBAWidget::ToggleDisconnect() m_force_disconnect = !m_force_disconnect; - Core::RunOnCPUThread( - Core::System::GetInstance(), - [core = m_core, force_disconnect = m_force_disconnect] { - if (auto core_ptr = core.lock()) - core_ptr->SetForceDisconnect(force_disconnect); - }, - false); + Core::RunOnCPUThread(Core::System::GetInstance(), + [core = m_core, force_disconnect = m_force_disconnect] { + if (auto core_ptr = core.lock()) + core_ptr->SetForceDisconnect(force_disconnect); + }); } void GBAWidget::LoadROM() @@ -224,18 +216,16 @@ void GBAWidget::DoState(bool export_state) if (state_path.isEmpty()) return; - Core::RunOnCPUThread( - Core::System::GetInstance(), - [export_state, core = m_core, state_path = state_path.toStdString()] { - if (auto core_ptr = core.lock()) - { - if (export_state) - core_ptr->ExportState(state_path); - else - core_ptr->ImportState(state_path); - } - }, - false); + Core::RunOnCPUThread(Core::System::GetInstance(), + [export_state, core = m_core, state_path = state_path.toStdString()] { + if (auto core_ptr = core.lock()) + { + if (export_state) + core_ptr->ExportState(state_path); + else + core_ptr->ImportState(state_path); + } + }); } void GBAWidget::ImportExportSave(bool export_save) @@ -255,18 +245,16 @@ void GBAWidget::ImportExportSave(bool export_save) if (save_path.isEmpty()) return; - Core::RunOnCPUThread( - Core::System::GetInstance(), - [export_save, core = m_core, save_path = save_path.toStdString()] { - if (auto core_ptr = core.lock()) - { - if (export_save) - core_ptr->ExportSave(save_path); - else - core_ptr->ImportSave(save_path); - } - }, - false); + Core::RunOnCPUThread(Core::System::GetInstance(), + [export_save, core = m_core, save_path = save_path.toStdString()] { + if (auto core_ptr = core.lock()) + { + if (export_save) + core_ptr->ExportSave(save_path); + else + core_ptr->ImportSave(save_path); + } + }); } void GBAWidget::Resize(int scale)