summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStenzek <stenzek@gmail.com>2019-06-29 18:18:24 +1000
committerStenzek <stenzek@gmail.com>2019-07-24 04:08:19 +1000
commitdf45e714a3e7a112e53a79e4b7362c4f8aed84d5 (patch)
treeaac81163b8838289a889c61f4d06f651e1738049
parent29ba53f6c32313e551b4797a84a4edf477a4d54e (diff)
Core: Support asynchronously executing functions on the CPU thread
The CPU thread will be interrupted to execute the callback, and then restored to its old state after execution completes.
-rw-r--r--Source/Core/Core/Core.cpp42
-rw-r--r--Source/Core/Core/Core.h4
-rw-r--r--Source/Core/Core/HW/CPU.cpp31
-rw-r--r--Source/Core/Core/HW/CPU.h5
4 files changed, 80 insertions, 2 deletions
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<HostJob> 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<BootParameters> 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<void()> function)
PauseAndLock(false, was_unpaused);
}
+void RunOnCPUThread(std::function<void()> 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<void()> 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<void()> function, bool wait_for_completion);
+
// for calling back into UI code without introducing a dependency on it in core
using StateChangedCallbackFunc = std::function<void(Core::State)>;
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 <condition_variable>
#include <mutex>
+#include <queue>
#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<std::function<void()>> 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<std::mutex>& 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<std::mutex> 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<void()> function)
+{
+ std::unique_lock<std::mutex> 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 <functional>
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<void()> function);
} // namespace CPU