summaryrefslogtreecommitdiff
path: root/Source/Core
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 /Source/Core
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.
Diffstat (limited to 'Source/Core')
-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