Skip to content

Commit

Permalink
Core/Movie: Add ability to run code in Host context
Browse files Browse the repository at this point in the history
EndPlayInput runs on the CPU thread so it can't directly call
UpdateWantDeterminism. PlayController also tries to ChangeDisc
from the CPU Thread which is also invalid. It now just pauses
execution and posts a request to the Host to fix it instead.
  • Loading branch information
EmptyChaos committed Apr 26, 2016
1 parent 635b69b commit 05ff4da
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 16 deletions.
28 changes: 28 additions & 0 deletions Source/Android/jni/MainAndroid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <cstdlib>
#include <jni.h>
#include <memory>
#include <mutex>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <EGL/egl.h>
Expand Down Expand Up @@ -65,9 +66,21 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
void Host_NotifyMapLoaded() {}
void Host_RefreshDSPDebuggerWindow() {}

// The Core only supports using a single Host thread.
// If multiple threads want to call host functions then they need to queue
// sequentially for access.
static std::mutex s_host_identity_lock;
Common::Event updateMainFrameEvent;
void Host_Message(int Id)
{
if (Id == WM_USER_JOB_DISPATCH)
{
updateMainFrameEvent.Set();
}
else if (Id == WM_USER_STOP)
{
Core::QueueHostJob(&Core::Stop);
}
}

void* Host_GetRenderHandle()
Expand Down Expand Up @@ -388,15 +401,18 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_UnPauseEmulation(JNIEnv *env, jobject obj)
{
std::lock_guard<std::mutex> guard(s_host_identity_lock);
Core::SetState(Core::CORE_RUN);
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulation(JNIEnv *env, jobject obj)
{
std::lock_guard<std::mutex> guard(s_host_identity_lock);
Core::SetState(Core::CORE_PAUSE);
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulation(JNIEnv *env, jobject obj)
{
std::lock_guard<std::mutex> guard(s_host_identity_lock);
Core::SaveScreenShot("thumb");
Renderer::s_screenshotCompleted.WaitFor(std::chrono::seconds(2));
Core::Stop();
Expand Down Expand Up @@ -485,6 +501,7 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Supports

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveScreenShot(JNIEnv *env, jobject obj)
{
std::lock_guard<std::mutex> guard(s_host_identity_lock);
Core::SaveScreenShot();
}

Expand Down Expand Up @@ -529,11 +546,13 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetFilename(

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JNIEnv *env, jobject obj, jint slot)
{
std::lock_guard<std::mutex> guard(s_host_identity_lock);
State::Save(slot);
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv *env, jobject obj, jint slot)
{
std::lock_guard<std::mutex> guard(s_host_identity_lock);
State::Load(slot);
}

Expand Down Expand Up @@ -572,6 +591,7 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetUserDi

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetProfiling(JNIEnv *env, jobject obj, jboolean enable)
{
std::lock_guard<std::mutex> guard(s_host_identity_lock);
Core::SetState(Core::CORE_PAUSE);
JitInterface::ClearCache();
Profiler::g_ProfileBlocks = enable;
Expand Down Expand Up @@ -638,6 +658,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimotes(JNIEnv *env, jobject obj)
{
std::lock_guard<std::mutex> guard(s_host_identity_lock);
WiimoteReal::Refresh();
}

Expand All @@ -651,6 +672,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *

RegisterMsgAlertHandler(&MsgAlert);

std::unique_lock<std::mutex> guard(s_host_identity_lock);
UICommon::SetUserDirectory(g_set_userpath);
UICommon::Init();

Expand All @@ -661,11 +683,17 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *
{
PowerPC::Start();
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
{
guard.unlock();
updateMainFrameEvent.Wait();
guard.lock();
Core::HostDispatchJobs();
}
}

Core::Shutdown();
UICommon::Shutdown();
guard.unlock();

if (surf)
{
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Common/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ enum HOST_COMM
WM_USER_STOP = 10,
WM_USER_CREATE,
WM_USER_SETCURSOR,
WM_USER_JOB_DISPATCH,
};

// Used for notification on emulation state
Expand Down
63 changes: 63 additions & 0 deletions Source/Core/Core/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include <atomic>
#include <cctype>
#include <cstring>
#include <mutex>
#include <queue>
#include <utility>

#ifdef _WIN32
#include <windows.h>
Expand Down Expand Up @@ -102,6 +105,7 @@ void EmuThread();
static bool s_is_stopping = false;
static bool s_hardware_initialized = false;
static bool s_is_started = false;
static std::atomic<bool> s_is_booting{ false };
static void* s_window_handle = nullptr;
static std::string s_state_filename;
static std::thread s_emu_thread;
Expand All @@ -112,6 +116,14 @@ static bool s_request_refresh_info = false;
static int s_pause_and_lock_depth = 0;
static bool s_is_throttler_temp_disabled = false;

struct HostJob
{
std::function<void()> job;
bool run_after_stop;
};
static std::mutex s_host_jobs_lock;
static std::queue<HostJob> s_host_jobs_queue;

#ifdef USE_MEMORYWATCHER
static std::unique_ptr<MemoryWatcher> s_memory_watcher;
#endif
Expand Down Expand Up @@ -229,6 +241,9 @@ bool Init()
s_emu_thread.join();
}

// Drain any left over jobs
HostDispatchJobs();

Core::UpdateWantDeterminism(/*initial*/ true);

INFO_LOG(OSREPORT, "Starting core = %s mode",
Expand Down Expand Up @@ -264,6 +279,9 @@ void Stop() // - Hammertime!

s_is_stopping = true;

// Dump left over jobs
HostDispatchJobs();

Fifo::EmulatorState(false);

INFO_LOG(CONSOLE, "Stop [Main Thread]\t\t---- Shutting down ----");
Expand Down Expand Up @@ -416,6 +434,7 @@ static void FifoPlayerThread()
void EmuThread()
{
const SConfig& core_parameter = SConfig::GetInstance();
s_is_booting.store(true);

Common::SetCurrentThreadName("Emuthread - Starting");

Expand All @@ -434,6 +453,7 @@ void EmuThread()

if (!g_video_backend->Initialize(s_window_handle))
{
s_is_booting.store(false);
PanicAlert("Failed to initialize video backend!");
Host_Message(WM_USER_STOP);
return;
Expand All @@ -450,6 +470,7 @@ void EmuThread()
{
HW::Shutdown();
g_video_backend->Shutdown();
s_is_booting.store(false);
PanicAlert("Failed to initialize DSP emulation!");
Host_Message(WM_USER_STOP);
return;
Expand Down Expand Up @@ -488,6 +509,7 @@ void EmuThread()

// The hardware is initialized.
s_hardware_initialized = true;
s_is_booting.store(false);

// Boot to pause or not
// NOTE: This violates the Host Thread requirement for SetState but we should
Expand Down Expand Up @@ -918,4 +940,45 @@ void UpdateWantDeterminism(bool initial)
}
}

void QueueHostJob(std::function<void()> job, bool run_during_stop)
{
if (!job)
return;

bool send_message = false;
{
std::lock_guard<std::mutex> guard(s_host_jobs_lock);
send_message = s_host_jobs_queue.empty();
s_host_jobs_queue.emplace(HostJob{ std::move(job), run_during_stop });
}
// If the the queue was empty then kick the Host to come and get this job.
if (send_message)
Host_Message(WM_USER_JOB_DISPATCH);
}

void HostDispatchJobs()
{
// WARNING: This should only run on the Host Thread.
// NOTE: This function is potentially re-entrant. If a job calls
// Core::Stop for instance then we'll enter this a second time.
std::unique_lock<std::mutex> guard(s_host_jobs_lock);
while (!s_host_jobs_queue.empty())
{
HostJob job = std::move(s_host_jobs_queue.front());
s_host_jobs_queue.pop();

// NOTE: Memory ordering is important. The booting flag needs to be
// checked first because the state transition is:
// CORE_UNINITIALIZED: s_is_booting -> s_hardware_initialized
// We need to check variables in the same order as the state
// transition, otherwise we race and get transient failures.
if (!job.run_after_stop && !s_is_booting.load() && !IsRunning())
continue;

guard.unlock();
job.job();
guard.lock();
}
}

} // Core
12 changes: 12 additions & 0 deletions Source/Core/Core/Core.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#pragma once

#include <functional>
#include <string>
#include <vector>

Expand Down Expand Up @@ -91,4 +92,15 @@ void SetOnStoppedCallback(StoppedCallbackFunc callback);
// Run on the Host thread when the factors change. [NOT THREADSAFE]
void UpdateWantDeterminism(bool initial = false);

// Queue an arbitrary function to asynchronously run once on the Host thread later.
// Threadsafe. Can be called by any thread, including the Host itself.
// NOTE: Make sure the jobs check the global state instead of assuming
// everything is still the same as when the job was queued.
// NOTE: Jobs that are not set to run during stop will be discarded instead.
void QueueHostJob(std::function<void()> job, bool run_during_stop = false);

// Should be called periodically by the Host to run pending jobs.
// WM_USER_JOB_DISPATCH will be sent when something is added to the queue.
void HostDispatchJobs();

} // namespace
7 changes: 3 additions & 4 deletions Source/Core/Core/HW/DVDInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,8 @@ static void InsertDiscCallback(u64 userdata, s64 cyclesLate)

void ChangeDisc(const std::string& newFileName)
{
bool is_cpu = Core::IsCPUThread();
bool was_unpaused = is_cpu ? false : Core::PauseAndLock(true);
// WARNING: Can only run on Host Thread
bool was_unpaused = Core::PauseAndLock(true);
std::string* _FileName = new std::string(newFileName);
CoreTiming::ScheduleEvent(0, s_eject_disc);
CoreTiming::ScheduleEvent(500000000, s_insert_disc, (u64)_FileName);
Expand All @@ -495,8 +495,7 @@ void ChangeDisc(const std::string& newFileName)
}
Movie::g_discChange = fileName.substr(sizeofpath);
}
if (!is_cpu)
Core::PauseAndLock(false, was_unpaused);
Core::PauseAndLock(false, was_unpaused);
}

void SetLidOpen(bool open)
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/Core/HW/DVDInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ bool VolumeIsValid();
// Disc detection and swapping
void SetDiscInside(bool _DiscInside);
bool IsDiscInside();
void ChangeDisc(const std::string& fileName);
void ChangeDisc(const std::string& fileName); // [NOT THREADSAFE] Host only

// DVD Access Functions
bool ChangePartition(u64 offset);
Expand Down
27 changes: 21 additions & 6 deletions Source/Core/Core/Movie.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,7 @@ void PlayController(GCPadStatus* PadStatus, int controllerID)
{
// This implementation assumes the disc change will only happen once. Trying to change more than that will cause
// it to load the last disc every time. As far as i know though, there are no 3+ disc games, so this should be fine.
Core::SetState(Core::CORE_PAUSE);
CPU::Break();
bool found = false;
std::string path;
for (size_t i = 0; i < SConfig::GetInstance().m_ISOFolder.size(); ++i)
Expand All @@ -1167,8 +1167,16 @@ void PlayController(GCPadStatus* PadStatus, int controllerID)
}
if (found)
{
DVDInterface::ChangeDisc(path + '/' + g_discChange);
Core::SetState(Core::CORE_RUN);
path += '/' + g_discChange;

Core::QueueHostJob([=]
{
if (!Movie::IsPlayingInput())
return;

DVDInterface::ChangeDisc(path);
CPU::EnableStepping(false);
});
}
else
{
Expand Down Expand Up @@ -1236,19 +1244,26 @@ void EndPlayInput(bool cont)
}
else if (s_playMode != MODE_NONE)
{
// We can be called by EmuThread during boot (CPU_POWERDOWN)
bool was_running = Core::IsRunningAndStarted() && !CPU::IsStepping();
if (was_running)
CPU::Break();
s_rerecords = 0;
s_currentByte = 0;
s_playMode = MODE_NONE;
Core::UpdateWantDeterminism();
Core::DisplayMessage("Movie End.", 2000);
s_bRecordingFromSaveState = false;
// we don't clear these things because otherwise we can't resume playback if we load a movie state later
//g_totalFrames = s_totalBytes = 0;
//delete tmpInput;
//tmpInput = nullptr;

if (SConfig::GetInstance().m_PauseMovie)
Core::SetState(Core::CORE_PAUSE);
Core::QueueHostJob([=]
{
Core::UpdateWantDeterminism();
if (was_running && !SConfig::GetInstance().m_PauseMovie)
CPU::EnableStepping(false);
});
}
}

Expand Down
10 changes: 10 additions & 0 deletions Source/Core/DolphinQt2/Host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <QAbstractEventDispatcher>
#include <QApplication>
#include <QMutexLocker>

#include "Common/Common.h"
Expand Down Expand Up @@ -57,7 +59,15 @@ void Host::SetRenderFullscreen(bool fullscreen)
void Host_Message(int id)
{
if (id == WM_USER_STOP)
{
emit Host::GetInstance()->RequestStop();
}
else if (id == WM_USER_JOB_DISPATCH)
{
// Just poke the main thread to get it to wake up, job dispatch
// will happen automatically before it goes back to sleep again.
QAbstractEventDispatcher::instance(qApp->thread())->wakeUp();
}
}

void Host_UpdateTitle(const std::string& title)
Expand Down
Loading

0 comments on commit 05ff4da

Please sign in to comment.