/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2010 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "PrecompiledHeader.h" #include "System.h" #include "SysThreads.h" #include "CDVD/CDVDaccess.h" // -------------------------------------------------------------------------------------- // SysThreadBase *External Thread* Implementations // (Called form outside the context of this thread) // -------------------------------------------------------------------------------------- SysThreadBase::SysThreadBase() : m_ExecMode(ExecMode_NoThreadYet) , m_ExecModeMutex() { } void SysThreadBase::Start() { _parent::Start(); Sleep(1); pxAssertDev((m_ExecMode == ExecMode_Closing) || (m_ExecMode == ExecMode_Closed), "Unexpected thread status during SysThread startup."); m_sem_event.NotifyOfWork(); } void SysThreadBase::OnStart() { if (!pxAssertDev(m_ExecMode == ExecMode_NoThreadYet, "SysSustainableThread:Start(): Invalid execution mode")) return; m_sem_Resume.Reset(); m_sem_ChangingExecMode.Reset(); FrankenMutex(m_ExecModeMutex); FrankenMutex(m_RunningLock); _parent::OnStart(); } // Suspends emulation and closes the emulation state at the next PS2 vsync, // and returns control to the calling thread; or does nothing if the core is already suspended. // // Parameters: // isNonblocking - if set to true then the function will not block for emulation suspension. // Defaults to false if parameter is not specified. Performing non-blocking suspension // is mostly useful for starting certain non-Emu related gui activities (improves gui // responsiveness). // // Returns: // The previous suspension state; true if the thread was running or false if it was // suspended. // // Exceptions: // CancelEvent - thrown if the thread is already in a Paused or Closing state. Because // actions that pause emulation typically rely on subcomponents remaining loaded/active, // Suspension must cancel itself forcefully or risk crashing whatever other action is // in progress. // void SysThreadBase::Suspend(bool isBlocking) { if (!pxAssertDev(!IsSelf(), "Suspend/Resume are not allowed from this thread.")) return; if (!IsRunning()) return; // shortcut ExecMode check to avoid deadlocking on redundant calls to Suspend issued // from Resume or OnResumeReady code. if (m_ExecMode == ExecMode_Closed) return; { ScopedLock locker(m_ExecModeMutex); switch (m_ExecMode.load()) { // Invalid thread state, nothing to suspend case ExecMode_NoThreadYet: // Check again -- status could have changed since above. case ExecMode_Closed: return; case ExecMode_Pausing: case ExecMode_Paused: if (!isBlocking) throw Exception::CancelEvent(L"Cannot suspend in non-blocking fashion: Another thread is pausing the VM state."); m_ExecMode = ExecMode_Closing; m_sem_Resume.Post(); m_sem_ChangingExecMode.Wait(); break; case ExecMode_Opened: m_ExecMode = ExecMode_Closing; break; case ExecMode_Closing: break; } pxAssertDev(m_ExecMode == ExecMode_Closing, "ExecMode should be nothing other than Closing..."); m_sem_event.NotifyOfWork(); } if (isBlocking) m_RunningLock.Wait(); } // Returns: // The previous suspension state; true if the thread was running or false if it was // closed, not running, or paused. // void SysThreadBase::Pause(SystemsMask systemsToTearDown, bool debug) { if (IsSelf() || !IsRunning()) return; // shortcut ExecMode check to avoid deadlocking on redundant calls to Suspend issued // from Resume or OnResumeReady code. if ((m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused)) return; { ScopedLock locker(m_ExecModeMutex); // Check again -- status could have changed since above. if ((m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused)) return; m_SystemsToTearDown.store(systemsToTearDown, std::memory_order_relaxed); if (m_ExecMode == ExecMode_Opened) m_ExecMode = ExecMode_Pausing; pxAssertDev(m_ExecMode == ExecMode_Pausing, "ExecMode should be nothing other than Pausing..."); if (debug) OnPauseDebug(); else OnPause(); m_sem_event.NotifyOfWork(); } m_RunningLock.Wait(); } void SysThreadBase::PauseSelf() { if (!IsSelf() || !IsRunning()) return; { ScopedLock locker(m_ExecModeMutex); if (m_ExecMode == ExecMode_Opened) m_ExecMode = ExecMode_Pausing; OnPause(); m_sem_event.NotifyOfWork(); } } void SysThreadBase::PauseSelfDebug() { if (!IsSelf() || !IsRunning()) return; { ScopedLock locker(m_ExecModeMutex); if (m_ExecMode == ExecMode_Opened) m_ExecMode = ExecMode_Pausing; OnPauseDebug(); m_sem_event.NotifyOfWork(); } } // Resumes the core execution state, or does nothing is the core is already running. If // settings were changed, resets will be performed as needed and emulation state resumed from // memory savestates. // // Note that this is considered a non-blocking action. Most times the state is safely resumed // on return, but in the case of re-entrant or nested message handling the function may return // before the thread has resumed. If you need explicit behavior tied to the completion of the // Resume, you'll need to bind callbacks to either OnResumeReady or OnResumeInThread. // // Exceptions: // ThreadCreationError - Insufficient system resources to create thread. // void SysThreadBase::Resume() { if (IsSelf()) return; if (m_ExecMode == ExecMode_Opened) return; ScopedLock locker(m_ExecModeMutex); // Implementation Note: // The entire state coming out of a Wait is indeterminate because of user input // and pending messages being handled. So after each call we do some seemingly redundant // sanity checks against m_ExecMode/m_Running status, and if something doesn't feel // right, we should abort; the user may have canceled the action before it even finished. switch (m_ExecMode.load()) { case ExecMode_Opened: return; case ExecMode_NoThreadYet: { Start(); if (!m_running || (m_ExecMode == ExecMode_NoThreadYet)) throw Exception::ThreadCreationError(this); if (m_ExecMode == ExecMode_Opened) return; } [[fallthrough]]; case ExecMode_Closing: case ExecMode_Pausing: // we need to make sure and wait for the emuThread to enter a fully suspended // state before continuing... m_RunningLock.Wait(); if (!m_running) return; if ((m_ExecMode != ExecMode_Closed) && (m_ExecMode != ExecMode_Paused)) return; break; case ExecMode_Paused: case ExecMode_Closed: break; } pxAssertDev((m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused), "SysThreadBase is not in a closed/paused state? wtf!"); OnResumeReady(); m_ExecMode = ExecMode_Opened; m_sem_Resume.Post(); } // -------------------------------------------------------------------------------------- // SysThreadBase *Worker* Implementations // (Called from the context of this thread only) // -------------------------------------------------------------------------------------- void SysThreadBase::OnStartInThread() { m_RunningLock.Acquire(); _parent::OnStartInThread(); m_ExecMode = ExecMode_Closing; } void SysThreadBase::OnCleanupInThread() { m_ExecMode = ExecMode_NoThreadYet; _parent::OnCleanupInThread(); m_RunningLock.Release(); } void SysThreadBase::OnSuspendInThread() {} void SysThreadBase::OnResumeInThread(SystemsMask systemsToReinstate) {} // Tests for Pause and Suspend/Close requests. If the thread is trying to be paused or // closed, it will enter a wait/holding pattern here in this method until the managing // thread releases it. Use the return value to detect if changes to the thread's state // may have been changed (based on the rule that other threads are not allowed to modify // this thread's state without pausing or closing it first, to prevent race conditions). // // Return value: // TRUE if the thread was paused or closed; FALSE if the thread // continued execution unimpeded. bool SysThreadBase::StateCheckInThread() { SystemsMask systemsToTearDown {}; // Used for Pausing/Paused switch (m_ExecMode.load()) { #ifdef PCSX2_DEVBUILD // optimize out handlers for these cases in release builds. case ExecMode_NoThreadYet: // threads should never have this state set while the thread is in any way // active or alive. (for obvious reasons!!) pxFailDev("Invalid execution state detected."); return false; #endif case ExecMode_Opened: // Other cases don't need TestCancel() because its built into the various // threading wait/signal actions. TestCancel(); return false; // ------------------------------------- case ExecMode_Pausing: { systemsToTearDown = m_SystemsToTearDown.exchange({}, std::memory_order_relaxed); OnPauseInThread(systemsToTearDown); m_ExecMode = ExecMode_Paused; m_RunningLock.Release(); } [[fallthrough]]; case ExecMode_Paused: while (m_ExecMode == ExecMode_Paused) m_sem_Resume.WaitWithoutYield(); m_RunningLock.Acquire(); if (m_ExecMode != ExecMode_Closing) { // AppCoreThread deals with Reseting CDVD OnResumeInThread(g_CDVDReset ? static_cast(systemsToTearDown & ~(System_CDVD)) : systemsToTearDown); g_CDVDReset = false; break; } m_sem_ChangingExecMode.Post(); [[fallthrough]]; // fallthrough if we're switching to closing state... // ------------------------------------- case ExecMode_Closing: { OnSuspendInThread(); m_ExecMode = ExecMode_Closed; m_RunningLock.Release(); } [[fallthrough]]; case ExecMode_Closed: while (m_ExecMode == ExecMode_Closed) m_sem_Resume.WaitWithoutYield(); m_RunningLock.Acquire(); OnResumeInThread(static_cast(-1)); // All systems g_CDVDReset = false; break; jNO_DEFAULT; } return true; }