diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index bdd3fcaf10..f1e3161220 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -21,6 +21,7 @@ #include "Common/CPUDetect.h" #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" +#include "Common/Event.h" #include "Common/FileUtil.h" #include "Common/Flag.h" #include "Common/Logging/LogManager.h" @@ -110,6 +111,7 @@ struct HostJob }; static std::mutex s_host_jobs_lock; static std::queue s_host_jobs_queue; +static Common::Event s_cpu_thread_job_finished; static thread_local bool tls_is_cpu_thread = false; @@ -433,6 +435,7 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi Common::ScopeGuard movie_guard{Movie::Shutdown}; HW::Init(); + Common::ScopeGuard hw_guard{[] { // We must set up this flag before executing HW::Shutdown() s_hardware_initialized = false; @@ -771,6 +774,45 @@ void RunAsCPUThread(std::function function) PauseAndLock(false, was_unpaused); } +void RunOnCPUThread(std::function function, bool wait_for_completion) +{ + // If the CPU thread is not running, assume there is no active CPU thread we can race against. + if (!IsRunning() || IsCPUThread()) + { + function(); + return; + } + + // Pause the CPU (set it to stepping mode). + const bool was_running = PauseAndLock(true, true); + + // Queue the job function. + if (wait_for_completion) + { + // Trigger the event after executing the function. + s_cpu_thread_job_finished.Reset(); + CPU::AddCPUThreadJob([&function]() { + function(); + s_cpu_thread_job_finished.Set(); + }); + } + else + { + CPU::AddCPUThreadJob(std::move(function)); + } + + // Release the CPU thread, and let it execute the callback. + PauseAndLock(false, 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 (!s_cpu_thread_job_finished.WaitFor(std::chrono::milliseconds(10))) + Host_YieldToUI(); + } +} + // Display FPS info // This should only be called from VI void VideoThrottle() diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index fdd30a539a..26e30a2d8a 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -82,6 +82,10 @@ void UpdateTitle(); // This should only be called from the CPU thread or the host thread. void RunAsCPUThread(std::function function); +// Run a function on the CPU thread, asynchronously. +// This is only valid to call from the host thread, since it uses PauseAndLock() internally. +void RunOnCPUThread(std::function function, bool wait_for_completion); + // for calling back into UI code without introducing a dependency on it in core using StateChangedCallbackFunc = std::function; void SetOnStateChangedCallback(StateChangedCallbackFunc callback); diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index a1b21ea7a2..b8243a0452 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "AudioCommon/AudioCommon.h" #include "Common/CommonTypes.h" @@ -44,6 +45,7 @@ static bool s_state_paused_and_locked = false; static bool s_state_system_request_stepping = false; static bool s_state_cpu_step_instruction = false; static Common::Event* s_state_cpu_step_instruction_sync = nullptr; +static std::queue> s_pending_jobs; void Init(PowerPC::CPUCore cpu_core) { @@ -60,6 +62,9 @@ void Shutdown() // Requires holding s_state_change_lock static void FlushStepSyncEventLocked() { + if (!s_state_cpu_step_instruction) + return; + if (s_state_cpu_step_instruction_sync) { s_state_cpu_step_instruction_sync->Set(); @@ -68,12 +73,25 @@ static void FlushStepSyncEventLocked() s_state_cpu_step_instruction = false; } +static void ExecutePendingJobs(std::unique_lock& state_lock) +{ + while (!s_pending_jobs.empty()) + { + auto callback = s_pending_jobs.front(); + s_pending_jobs.pop(); + state_lock.unlock(); + callback(); + state_lock.lock(); + } +} + void Run() { std::unique_lock state_lock(s_state_change_lock); while (s_state != State::PowerDown) { s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; }); + ExecutePendingJobs(state_lock); switch (s_state) { @@ -108,8 +126,10 @@ void Run() case State::Stepping: // Wait for step command. - s_state_cpu_cvar.wait(state_lock, - [] { return s_state_cpu_step_instruction || !IsStepping(); }); + s_state_cpu_cvar.wait(state_lock, [&state_lock] { + ExecutePendingJobs(state_lock); + return s_state_cpu_step_instruction || !IsStepping(); + }); if (!IsStepping()) { // Signal event if the mode changes. @@ -330,4 +350,11 @@ bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent) } return was_unpaused; } + +void AddCPUThreadJob(std::function function) +{ + std::unique_lock state_lock(s_state_change_lock); + s_pending_jobs.push(std::move(function)); +} + } // namespace CPU diff --git a/Source/Core/Core/HW/CPU.h b/Source/Core/Core/HW/CPU.h index 408b82ace2..26ffa6783c 100644 --- a/Source/Core/Core/HW/CPU.h +++ b/Source/Core/Core/HW/CPU.h @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #pragma once +#include namespace Common { @@ -74,4 +75,8 @@ const State* GetStatePtr(); // "control_adjacent" causes PauseAndLock to behave like EnableStepping by modifying the // state of the Audio and FIFO subsystems as well. bool PauseAndLock(bool do_lock, bool unpause_on_unlock = true, bool control_adjacent = false); + +// Adds a job to be executed during on the CPU thread. This should be combined with PauseAndLock(), +// as while the CPU is in the run loop, it won't execute the function. +void AddCPUThreadJob(std::function function); } // namespace CPU