diff --git a/common/include/Utilities/Threading.h b/common/include/Utilities/Threading.h index d83f15184e..200fa2a6fc 100644 --- a/common/include/Utilities/Threading.h +++ b/common/include/Utilities/Threading.h @@ -170,8 +170,8 @@ namespace Threading void Post(); void Post( int multiple ); - void WaitRaw(); - bool WaitRaw( const wxTimeSpan& timeout ); + void WaitWithoutYield(); + bool WaitWithoutYield( const wxTimeSpan& timeout ); void WaitNoCancel(); void WaitNoCancel( const wxTimeSpan& timeout ); int Count(); @@ -199,8 +199,8 @@ namespace Threading bool TryAcquire(); void Release(); - void FullBlockingAcquire(); - bool FullBlockingAcquire( const wxTimeSpan& timeout ); + void AcquireWithoutYield(); + bool AcquireWithoutYield( const wxTimeSpan& timeout ); void Wait(); bool Wait( const wxTimeSpan& timeout ); diff --git a/common/include/Utilities/wxGuiTools.h b/common/include/Utilities/wxGuiTools.h index d7c9530175..95a3ed246c 100644 --- a/common/include/Utilities/wxGuiTools.h +++ b/common/include/Utilities/wxGuiTools.h @@ -15,7 +15,11 @@ #pragma once +#if wxUSE_GUI + #include "Dependencies.h" +#include "ScopedPtr.h" +#include // ---------------------------------------------------------------------------- // wxGuiTools.h @@ -122,6 +126,53 @@ protected: } }; +// -------------------------------------------------------------------------------------- +// MoreStockCursors +// -------------------------------------------------------------------------------------- +// Because (inexplicably) the ArrowWait cursor isn't in wxWidgets stock list. +// +class MoreStockCursors +{ +protected: + ScopedPtr m_arrowWait; + +public: + MoreStockCursors() { } + virtual ~MoreStockCursors() throw() { } + const wxCursor& GetArrowWait(); +}; + +enum BusyCursorType +{ + Cursor_NotBusy, + Cursor_KindaBusy, + Cursor_ReallyBusy, +}; + +extern MoreStockCursors StockCursors; + +// -------------------------------------------------------------------------------------- +// ScopedBusyCursor +// -------------------------------------------------------------------------------------- +// ... because wxWidgets wxBusyCursor doesn't really do proper nesting (doesn't let me +// override a partially-busy cursor with a really busy one) + +class ScopedBusyCursor +{ +protected: + static std::stack m_cursorStack; + static BusyCursorType m_defBusyType; + +public: + ScopedBusyCursor( BusyCursorType busytype ); + virtual ~ScopedBusyCursor() throw(); + + static void SetDefault( BusyCursorType busytype ); + static void SetManualBusyCursor( BusyCursorType busytype ); +}; + extern bool pxIsValidWindowPosition( const wxWindow& window, const wxPoint& windowPos ); extern wxRect wxGetDisplayArea(); + +#endif diff --git a/common/src/Utilities/Mutex.cpp b/common/src/Utilities/Mutex.cpp index 4df175f6b2..a9d63d973f 100644 --- a/common/src/Utilities/Mutex.cpp +++ b/common/src/Utilities/Mutex.cpp @@ -18,6 +18,7 @@ #include "Threading.h" #include "wxBaseTools.h" +#include "wxGuiTools.h" #include "ThreadingInternal.h" namespace Threading @@ -112,12 +113,13 @@ bool Threading::Mutex::RecreateIfLocked() // if used from the main GUI thread, since it typically results in an unresponsive program. // Call this method directly only if you know the code in question will be run from threads // other than the main thread. -void Threading::Mutex::FullBlockingAcquire() +void Threading::Mutex::AcquireWithoutYield() { + pxAssertMsg( !wxThread::IsMain(), "Unyielding mutex acquire issued from the main/gui thread. Please use Acquire() instead." ); pthread_mutex_lock( &m_mutex ); } -bool Threading::Mutex::FullBlockingAcquire( const wxTimeSpan& timeout ) +bool Threading::Mutex::AcquireWithoutYield( const wxTimeSpan& timeout ) { wxDateTime megafail( wxDateTime::UNow() + timeout ); const timespec fail = { megafail.GetTicks(), megafail.GetMillisecond() * 1000000 }; @@ -134,7 +136,7 @@ bool Threading::Mutex::TryAcquire() return EBUSY != pthread_mutex_trylock( &m_mutex ); } -// This is a wxApp-safe rendition of FullBlockingAcquire, which makes sure to execute pending app events +// This is a wxApp-safe rendition of AcquireWithoutYield, which makes sure to execute pending app events // and messages *if* the lock is performed from the main GUI thread. // // Exceptions: @@ -145,20 +147,20 @@ void Threading::Mutex::Acquire() #if wxUSE_GUI if( !wxThread::IsMain() || (wxTheApp == NULL) ) { - FullBlockingAcquire(); + pthread_mutex_lock( &m_mutex ); } else if( _WaitGui_RecursionGuard( "Mutex::Acquire" ) ) { - if( !FullBlockingAcquire(def_deadlock_timeout) ) + if( !AcquireWithoutYield(def_deadlock_timeout) ) throw Exception::ThreadTimedOut(); } else { - while( !FullBlockingAcquire(def_yieldgui_interval) ) + while( !AcquireWithoutYield(def_yieldgui_interval) ) wxTheApp->Yield( true ); } #else - FullBlockingAcquire(); + pthread_mutex_lock( &m_mutex ); #endif } @@ -170,23 +172,26 @@ bool Threading::Mutex::Acquire( const wxTimeSpan& timeout ) #if wxUSE_GUI if( !wxThread::IsMain() || (wxTheApp == NULL) ) { - return FullBlockingAcquire(timeout); + return AcquireWithoutYield(timeout); } else if( _WaitGui_RecursionGuard( "Mutex::Acquire(timeout)" ) ) { + ScopedBusyCursor hourglass( Cursor_ReallyBusy ); + if( timeout > def_deadlock_timeout ) { - if( FullBlockingAcquire(def_deadlock_timeout) ) return true; + if( AcquireWithoutYield(def_deadlock_timeout) ) return true; throw Exception::ThreadTimedOut(); } - return FullBlockingAcquire( timeout ); + return AcquireWithoutYield( timeout ); } else { + ScopedBusyCursor hourglass( Cursor_KindaBusy ); wxTimeSpan countdown( (timeout) ); do { - if( FullBlockingAcquire( def_yieldgui_interval ) ) break; + if( AcquireWithoutYield( def_yieldgui_interval ) ) break; wxTheApp->Yield(true); countdown -= def_yieldgui_interval; } while( countdown.GetMilliseconds() > 0 ); @@ -198,7 +203,7 @@ bool Threading::Mutex::Acquire( const wxTimeSpan& timeout ) throw Exception::ThreadTimedOut(); #else - return FullBlockingAcquire(); + return AcquireWithoutYield(); #endif } diff --git a/common/src/Utilities/Semaphore.cpp b/common/src/Utilities/Semaphore.cpp index d25fc86d64..10abb3b09e 100644 --- a/common/src/Utilities/Semaphore.cpp +++ b/common/src/Utilities/Semaphore.cpp @@ -18,6 +18,7 @@ #include "Threading.h" #include "wxBaseTools.h" +#include "wxGuiTools.h" #include "ThreadingInternal.h" // -------------------------------------------------------------------------------------- @@ -59,12 +60,13 @@ void Threading::Semaphore::Post( int multiple ) #endif } -void Threading::Semaphore::WaitRaw() +void Threading::Semaphore::WaitWithoutYield() { + pxAssertMsg( !wxThread::IsMain(), "Unyielding semaphore wait issued from the main/gui thread. Please use Wait() instead." ); sem_wait( &m_sema ); } -bool Threading::Semaphore::WaitRaw( const wxTimeSpan& timeout ) +bool Threading::Semaphore::WaitWithoutYield( const wxTimeSpan& timeout ) { wxDateTime megafail( wxDateTime::UNow() + timeout ); const timespec fail = { megafail.GetTicks(), megafail.GetMillisecond() * 1000000 }; @@ -85,24 +87,26 @@ void Threading::Semaphore::Wait() #if wxUSE_GUI if( !wxThread::IsMain() || (wxTheApp == NULL) ) { - WaitRaw(); + sem_wait( &m_sema ); } else if( _WaitGui_RecursionGuard( "Semaphore::Wait" ) ) { - if( !WaitRaw(def_yieldgui_interval) ) // default is 4 seconds + ScopedBusyCursor hourglass( Cursor_ReallyBusy ); + if( !WaitWithoutYield(def_yieldgui_interval) ) // default is 4 seconds throw Exception::ThreadTimedOut(); } else { - while( !WaitRaw( def_yieldgui_interval ) ) + ScopedBusyCursor hourglass( Cursor_KindaBusy ); + while( !WaitWithoutYield( def_yieldgui_interval ) ) wxTheApp->Yield( true ); } #else - WaitRaw(); + sem_wait( &m_sema ); #endif } -// This is a wxApp-safe implementation of WaitRaw, which makes sure and executes the App's +// This is a wxApp-safe implementation of WaitWithoutYield, which makes sure and executes the App's // pending messages *if* the Wait is performed on the Main/GUI thread. This ensures that // user input continues to be handled and that windows continue to repaint. If the Wait is // called from another thread, no message pumping is performed. @@ -119,23 +123,25 @@ bool Threading::Semaphore::Wait( const wxTimeSpan& timeout ) #if wxUSE_GUI if( !wxThread::IsMain() || (wxTheApp == NULL) ) { - return WaitRaw( timeout ); + return WaitWithoutYield( timeout ); } else if( _WaitGui_RecursionGuard( "Semaphore::Wait(timeout)" ) ) { + ScopedBusyCursor hourglass( Cursor_ReallyBusy ); if( timeout > def_deadlock_timeout ) { - if( WaitRaw(def_deadlock_timeout) ) return true; + if( WaitWithoutYield(def_deadlock_timeout) ) return true; throw Exception::ThreadTimedOut(); } - return WaitRaw( timeout ); + return WaitWithoutYield( timeout ); } else { + ScopedBusyCursor hourglass( Cursor_KindaBusy ); wxTimeSpan countdown( (timeout) ); do { - if( WaitRaw( def_yieldgui_interval ) ) break; + if( WaitWithoutYield( def_yieldgui_interval ) ) break; wxTheApp->Yield(true); countdown -= def_yieldgui_interval; } while( countdown.GetMilliseconds() > 0 ); @@ -143,7 +149,7 @@ bool Threading::Semaphore::Wait( const wxTimeSpan& timeout ) return countdown.GetMilliseconds() > 0; } #else - return WaitRaw( timeout ); + return WaitWithoutYield( timeout ); #endif } @@ -152,14 +158,14 @@ bool Threading::Semaphore::Wait( const wxTimeSpan& timeout ) // the stack and passed to another thread via GUI message or such, avoiding complications where // the thread might be canceled and the stack value becomes invalid. // -// Performance note: this function has quite a bit more overhead compared to Semaphore::WaitRaw(), so -// consider manually specifying the thread as uncancellable and using WaitRaw() instead if you need +// Performance note: this function has quite a bit more overhead compared to Semaphore::WaitWithoutYield(), so +// consider manually specifying the thread as uncancellable and using WaitWithoutYield() instead if you need // to do a lot of no-cancel waits in a tight loop worker thread, for example. void Threading::Semaphore::WaitNoCancel() { int oldstate; pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldstate ); - WaitRaw(); + WaitWithoutYield(); pthread_setcancelstate( oldstate, NULL ); } @@ -167,7 +173,7 @@ void Threading::Semaphore::WaitNoCancel( const wxTimeSpan& timeout ) { int oldstate; pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldstate ); - WaitRaw( timeout ); + WaitWithoutYield( timeout ); pthread_setcancelstate( oldstate, NULL ); } diff --git a/common/src/Utilities/ThreadTools.cpp b/common/src/Utilities/ThreadTools.cpp index ae02a568b7..e1e0af6800 100644 --- a/common/src/Utilities/ThreadTools.cpp +++ b/common/src/Utilities/ThreadTools.cpp @@ -496,7 +496,7 @@ void Threading::BaseTaskThread::WaitForResult() #ifdef wxUSE_GUI m_post_TaskComplete.Wait(); #else - m_post_TaskComplete.WaitRaw(); + m_post_TaskComplete.WaitWithoutYield(); #endif m_post_TaskComplete.Reset(); @@ -507,7 +507,7 @@ void Threading::BaseTaskThread::ExecuteTaskInThread() while( !m_Done ) { // Wait for a job -- or get a pthread_cancel. I'm easy. - m_sem_event.WaitRaw(); + m_sem_event.WaitWithoutYield(); Task(); m_lock_TaskComplete.Acquire(); diff --git a/common/src/Utilities/wxGuiTools.cpp b/common/src/Utilities/wxGuiTools.cpp index 9167f6a1f4..e76360d9ed 100644 --- a/common/src/Utilities/wxGuiTools.cpp +++ b/common/src/Utilities/wxGuiTools.cpp @@ -16,6 +16,7 @@ #include "PrecompiledHeader.h" #include "wxGuiTools.h" +#include #include // Returns FALSE if the window position is considered invalid, which means that it's title @@ -39,3 +40,70 @@ wxRect wxGetDisplayArea() return wxRect( wxPoint(), wxGetDisplaySize() ); } +// -------------------------------------------------------------------------------------- +// ScopedBusyCursor Implementations +// -------------------------------------------------------------------------------------- + +std::stack ScopedBusyCursor::m_cursorStack; +BusyCursorType ScopedBusyCursor::m_defBusyType; + +ScopedBusyCursor::ScopedBusyCursor( BusyCursorType busytype ) +{ + pxAssert( wxTheApp != NULL ); + + BusyCursorType curtype = Cursor_NotBusy; + if( !m_cursorStack.empty() ) + curtype = m_cursorStack.top(); + + if( curtype < busytype ) + SetManualBusyCursor( curtype=busytype ); + + m_cursorStack.push( curtype ); +} + +ScopedBusyCursor::~ScopedBusyCursor() throw() +{ + if( !pxAssert( wxTheApp != NULL ) ) return; + + if( !pxAssert( !m_cursorStack.empty() ) ) + { + SetManualBusyCursor( m_defBusyType ); + return; + } + + BusyCursorType curtype = m_cursorStack.top(); + m_cursorStack.pop(); + + if( m_cursorStack.empty() ) + SetManualBusyCursor( m_defBusyType ); + else if( m_cursorStack.top() != curtype ) + SetManualBusyCursor( m_cursorStack.top() ); +} + +void ScopedBusyCursor::SetDefault( BusyCursorType busytype ) +{ + if( busytype == m_defBusyType ) return; + m_defBusyType = busytype; + + if( m_cursorStack.empty() ) + SetManualBusyCursor( busytype ); +} + +void ScopedBusyCursor::SetManualBusyCursor( BusyCursorType busytype ) +{ + switch( busytype ) + { + case Cursor_NotBusy: wxSetCursor( wxNullCursor ); break; + case Cursor_KindaBusy: wxSetCursor( StockCursors.GetArrowWait() ); break; + case Cursor_ReallyBusy: wxSetCursor( *wxHOURGLASS_CURSOR ); break; + } +} + +const wxCursor& MoreStockCursors::GetArrowWait() +{ + if( !m_arrowWait ) + m_arrowWait = new wxCursor( wxCURSOR_ARROWWAIT ); + return *m_arrowWait; +} + +MoreStockCursors StockCursors; diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index b9ea7690ff..5d1fcac057 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -246,7 +246,7 @@ void mtgsThreadObject::ExecuteTaskInThread() // is very optimized (only 1 instruction test in most cases), so no point in trying // to avoid it. - m_sem_event.WaitRaw(); + m_sem_event.WaitWithoutYield(); StateCheckInThread(); m_RingBufferIsBusy = true; diff --git a/pcsx2/System/SysCoreThread.cpp b/pcsx2/System/SysCoreThread.cpp index f0e2ad70bf..7530b06413 100644 --- a/pcsx2/System/SysCoreThread.cpp +++ b/pcsx2/System/SysCoreThread.cpp @@ -232,7 +232,7 @@ void SysCoreThread::ExecuteTaskInThread() tls_coreThread = this; - m_sem_event.WaitRaw(); + m_sem_event.WaitWithoutYield(); PCSX2_PAGEFAULT_PROTECT { StateCheckInThread(); Cpu->Execute(); diff --git a/pcsx2/System/SysThreadBase.cpp b/pcsx2/System/SysThreadBase.cpp index 85bd69f34a..fc52742ff0 100644 --- a/pcsx2/System/SysThreadBase.cpp +++ b/pcsx2/System/SysThreadBase.cpp @@ -40,7 +40,7 @@ void SysThreadBase::Start() Sleep( 1 ); - if( !m_ResumeEvent.WaitRaw( wxTimeSpan(0, 0, 1, 500) ) ) + if( !m_ResumeEvent.WaitWithoutYield( wxTimeSpan(0, 0, 1, 500) ) ) { RethrowException(); if( pxAssertDev( m_ExecMode == ExecMode_Closing, "Unexpected thread status during SysThread startup." ) ) @@ -103,16 +103,19 @@ bool SysThreadBase::Suspend( bool isBlocking ) { ScopedLock locker( m_ExecModeMutex ); - // Check again -- status could have changed since above. - if( m_ExecMode == ExecMode_Closed ) return false; - - if( m_ExecMode == ExecMode_Pausing || m_ExecMode == ExecMode_Paused ) - throw Exception::CancelEvent( "Another thread is pausing the VM state." ); - - if( m_ExecMode == ExecMode_Opened ) + switch( m_ExecMode ) { - m_ExecMode = ExecMode_Closing; - retval = true; + // Check again -- status could have changed since above. + case ExecMode_Closed: return false; + + case ExecMode_Pausing: + case ExecMode_Paused: + throw Exception::CancelEvent( "Another thread is pausing the VM state." ); + + case ExecMode_Opened: + m_ExecMode = ExecMode_Closing; + retval = true; + break; } pxAssertDev( m_ExecMode == ExecMode_Closing, "ExecMode should be nothing other than Closing..." ); @@ -198,7 +201,7 @@ void SysThreadBase::Resume() // 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. + // right, we should abort; the user may have canceled the action before it even finished. switch( m_ExecMode ) { @@ -206,10 +209,6 @@ void SysThreadBase::Resume() case ExecMode_NoThreadYet: { - /*static int __Guard = 0; - RecursionGuard guard( __Guard ); - if( guard.IsReentrant() ) return;*/ - Start(); if( !m_running || (m_ExecMode == ExecMode_NoThreadYet) ) throw Exception::ThreadCreationError(); @@ -290,7 +289,7 @@ void SysThreadBase::StateCheckInThread() case ExecMode_Paused: while( m_ExecMode == ExecMode_Paused ) - m_ResumeEvent.WaitRaw(); + m_ResumeEvent.WaitWithoutYield(); m_RunningLock.Acquire(); OnResumeInThread( false ); @@ -307,7 +306,7 @@ void SysThreadBase::StateCheckInThread() case ExecMode_Closed: while( m_ExecMode == ExecMode_Closed ) - m_ResumeEvent.WaitRaw(); + m_ResumeEvent.WaitWithoutYield(); m_RunningLock.Acquire(); OnResumeInThread( true ); diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index 2d764b28d1..4c921687ce 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -29,6 +29,8 @@ AppCoreThread::~AppCoreThread() throw() void AppCoreThread::Reset() { + ScopedBusyCursor::SetDefault( Cursor_KindaBusy ); + _parent::Reset(); wxCommandEvent evt( pxEVT_CoreThreadStatus ); @@ -38,6 +40,7 @@ void AppCoreThread::Reset() bool AppCoreThread::Suspend( bool isBlocking ) { + ScopedBusyCursor::SetDefault( Cursor_KindaBusy ); bool retval = _parent::Suspend( isBlocking ); // Clear the sticky key statuses, because hell knows what'll change while the PAD @@ -67,6 +70,7 @@ void AppCoreThread::Resume() return; } + ScopedBusyCursor::SetDefault( Cursor_KindaBusy ); _parent::Resume(); if( m_ExecMode != ExecMode_Opened ) diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index 62c5b6ae41..e74aea286a 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -138,6 +138,7 @@ void Pcsx2App::Ping() const void Pcsx2App::OnCoreThreadStatus( wxCommandEvent& evt ) { m_evtsrc_CoreThreadStatus.Dispatch( evt ); + ScopedBusyCursor::SetDefault( Cursor_NotBusy ); } void Pcsx2App::OnSemaphorePing( wxCommandEvent& evt ) @@ -497,9 +498,11 @@ void Pcsx2App::SysExecute( CDVD_SourceType cdvdsrc, const wxString& elf_override // This message performs actual system execution (as dictated by SysExecute variants). // It is implemented as a message handler so that it can be triggered in response to -// the completion of other dependent activites, namely loading plugins. +// the completion of other dependent activities, namely loading plugins. void Pcsx2App::OnSysExecute( wxCommandEvent& evt ) { + CoreThread.ReleaseResumeLock(); + if( sys_resume_lock > 0 ) { Console.WriteLn( "SysExecute: State is locked, ignoring Execute request!" ); @@ -520,7 +523,6 @@ void Pcsx2App::OnSysExecute( wxCommandEvent& evt ) if( !CoreThread.HasValidState() ) CoreThread.SetElfOverride( _sysexec_elf_override ); - CoreThread.ReleaseResumeLock(); CoreThread.Resume(); } @@ -528,6 +530,7 @@ void Pcsx2App::OnSysExecute( wxCommandEvent& evt ) void Pcsx2App::SysReset() { CoreThread.Reset(); + CoreThread.ReleaseResumeLock(); m_CorePlugins = NULL; } diff --git a/pcsx2/gui/Panels/ConfigurationPanels.h b/pcsx2/gui/Panels/ConfigurationPanels.h index 5ce77ec132..65546d3f9c 100644 --- a/pcsx2/gui/Panels/ConfigurationPanels.h +++ b/pcsx2/gui/Panels/ConfigurationPanels.h @@ -410,8 +410,8 @@ namespace Panels SafeList Results; // array of plugin results. protected: - PluginSelectorPanel& m_master; - + PluginSelectorPanel& m_master; + ScopedBusyCursor m_hourglass; public: virtual ~EnumThread() throw() { diff --git a/pcsx2/gui/Panels/PluginSelectorPanel.cpp b/pcsx2/gui/Panels/PluginSelectorPanel.cpp index 4ba6d950f7..f183c331b7 100644 --- a/pcsx2/gui/Panels/PluginSelectorPanel.cpp +++ b/pcsx2/gui/Panels/PluginSelectorPanel.cpp @@ -545,6 +545,7 @@ Panels::PluginSelectorPanel::EnumThread::EnumThread( PluginSelectorPanel& master PersistentThread() , Results( master.FileCount(), L"PluginSelectorResults" ) , m_master( master ) +, m_hourglass( Cursor_KindaBusy ) { Results.MatchLengthToAllocatedSize(); } diff --git a/pcsx2/gui/Plugins.cpp b/pcsx2/gui/Plugins.cpp index a5f56c775e..9a6a26ac85 100644 --- a/pcsx2/gui/Plugins.cpp +++ b/pcsx2/gui/Plugins.cpp @@ -93,10 +93,10 @@ public: PluginManager* Result; protected: - wxString m_folders[PluginId_Count]; - + wxString m_folders[PluginId_Count]; + ScopedBusyCursor m_hourglass; public: - LoadPluginsTask( const wxString (&folders)[PluginId_Count] ) : Result( NULL ) + LoadPluginsTask( const wxString (&folders)[PluginId_Count] ) : Result( NULL ), m_hourglass( Cursor_KindaBusy ) { for(int i=0; i