Skip to content
Permalink
Browse files

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.
  • Loading branch information...
stenzek committed Jun 29, 2019
1 parent 29ba53f commit df45e714a3e7a112e53a79e4b7362c4f8aed84d5
Showing with 80 additions and 2 deletions.
  1. +42 −0 Source/Core/Core/Core.cpp
  2. +4 −0 Source/Core/Core/Core.h
  3. +29 −2 Source/Core/Core/HW/CPU.cpp
  4. +5 −0 Source/Core/Core/HW/CPU.h
@@ -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()
@@ -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);
@@ -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
@@ -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

0 comments on commit df45e71

Please sign in to comment.
You can’t perform that action at this time.