From 1f2daa6459c6d4ee14daf95316730456e8c4e500 Mon Sep 17 00:00:00 2001 From: "Jake.Stine" Date: Fri, 16 Oct 2009 03:58:29 +0000 Subject: [PATCH] User Interface: * Fixed and added better Emulation/System menu updating. Suspend/Resume is more consistent, and Reset grays itself out after being used. * Entering plugin configurations auto-suspends the emulator. * Changing plugins in the Configuration PAnel takes effect now without a restart. * Added preliminary support for an ExtensibleConfirmation Dialog box (contains a sizer you can add content to, and also has an optional "[x] Do not show this again" checkbox). Bugfixes: * Added some mutex protection to cdvdNewDiskCB; "just in case." * Resolved several recursion and deadlock scenarios when (very!) rapidly suspending, resuming, and resetting the emu. Developments / Code Cleanups: * Renamed SysCoreThread ExecutionModes: Suspend/Resume are now Opened/Closed (which more accurately reflects the fact they opena nd close the plugins, and helps avoid ambiguity with the "Paused" state). * Added Exception::ThreadTimedOut, which is now thrown from Semaphore::Wait when recursive wxApp::Yield() calls are detected, and a deadlock occurs (basically cancels the current action which, most of the time, allows for full recovery). * Major Threading namespace cleanups, documentations, etc. * Removed wxScopedArray (scopedarray.h) and replaced it with a better implemeneted ScopedArray class. * Removed toUTF8 class, which I only added a couple weeks ago because I didn't realize wxCharBuffer had an implicit typecast to (char*). * Implemented more Source/Listener events for Pcsx2App. CoreThread events are sourced properly now, and added SettingsApplied and SettingsLoadSave Sources. git-svn-id: http://pcsx2.googlecode.com/svn/trunk@2010 96395faa-99c1-11dd-bbfe-3dabce05a288 --- common/include/Utilities/Exceptions.h | 6 + common/include/Utilities/SafeArray.h | 6 +- common/include/Utilities/ScopedPtr.h | 127 +- common/include/Utilities/StringHelpers.h | 32 - common/include/Utilities/Threading.h | 88 +- common/include/wx/folderdesc.txt | 6 +- common/include/wx/scopedarray.h | 63 - common/src/Utilities/Console.cpp | 2 +- common/src/Utilities/Linux/LnxThreads.cpp | 5 - common/src/Utilities/ThreadTools.cpp | 1167 ++++++++++--------- common/src/Utilities/Windows/WinThreads.cpp | 5 - pcsx2/CDVD/CDVD.cpp | 24 +- pcsx2/CDVD/CDVD.h | 1 - pcsx2/CDVD/CDVDaccess.cpp | 8 +- pcsx2/Counters.cpp | 2 +- pcsx2/GS.cpp | 2 +- pcsx2/GS.h | 6 +- pcsx2/Linux/pcsx2.cbp | 932 +++++++-------- pcsx2/MTGS.cpp | 21 +- pcsx2/PluginManager.cpp | 6 +- pcsx2/R5900.cpp | 4 +- pcsx2/RecoverySystem.cpp | 22 +- pcsx2/System/SysThreads.cpp | 181 +-- pcsx2/System/SysThreads.h | 68 +- pcsx2/Vif1Dma.cpp | 4 +- pcsx2/gui/App.h | 45 +- pcsx2/gui/AppAssert.cpp | 2 +- pcsx2/gui/AppConfig.cpp | 81 +- pcsx2/gui/AppConfig.h | 4 + pcsx2/gui/AppCoreThread.cpp | 40 +- pcsx2/gui/AppInit.cpp | 19 +- pcsx2/gui/AppMain.cpp | 69 +- pcsx2/gui/ConsoleLogger.cpp | 8 +- pcsx2/gui/Dialogs/ConfirmationDialogs.cpp | 223 ++++ pcsx2/gui/Dialogs/ModalPopups.h | 72 ++ pcsx2/gui/MainFrame.cpp | 63 +- pcsx2/gui/MainFrame.h | 18 +- pcsx2/gui/MainMenuClicks.cpp | 37 +- pcsx2/gui/Panels/ConfigurationPanels.h | 7 +- pcsx2/gui/Panels/MiscPanelStuff.cpp | 16 +- pcsx2/gui/Panels/PluginSelectorPanel.cpp | 37 +- pcsx2/windows/VCprojects/pcsx2_2008.vcproj | 8 +- pcsx2_suite_2008.sln | 1 - 43 files changed, 2101 insertions(+), 1437 deletions(-) delete mode 100644 common/include/wx/scopedarray.h create mode 100644 pcsx2/gui/Dialogs/ConfirmationDialogs.cpp diff --git a/common/include/Utilities/Exceptions.h b/common/include/Utilities/Exceptions.h index 0790ea614b..c7aca504fc 100644 --- a/common/include/Utilities/Exceptions.h +++ b/common/include/Utilities/Exceptions.h @@ -294,6 +294,12 @@ namespace Exception DEFINE_RUNTIME_EXCEPTION( ThreadCreationError, wxLt("Thread could not be created.") ); }; + class ThreadTimedOut : public virtual RuntimeError + { + public: + DEFINE_RUNTIME_EXCEPTION( ThreadTimedOut, "Blocking action timed out due to potential deadlock." ); + }; + // --------------------------------------------------------------------------------------- // Streaming (file) Exceptions: // Stream / BadStream / CreateStream / FileNotFound / AccessDenied / EndOfStream diff --git a/common/include/Utilities/SafeArray.h b/common/include/Utilities/SafeArray.h index f8ee8cd6c9..4c874a3041 100644 --- a/common/include/Utilities/SafeArray.h +++ b/common/include/Utilities/SafeArray.h @@ -308,11 +308,11 @@ public: if( m_ptr == NULL ) { throw Exception::OutOfMemory( - // English Diagonstic message: + // English Diagnostic message: wxsFormat( L"Out-of-memory on SafeList block re-allocation.\n" - L"Old size: %d bytes, New size: %d bytes", - m_allocsize, newalloc + L"Name: %s, Old size: %d bytes, New size: %d bytes", + Name, m_allocsize, newalloc ), wxsFormat( _("Out of memory, trying to allocate %d bytes."), newalloc ) diff --git a/common/include/Utilities/ScopedPtr.h b/common/include/Utilities/ScopedPtr.h index 9b9118840f..5a04970d09 100644 --- a/common/include/Utilities/ScopedPtr.h +++ b/common/include/Utilities/ScopedPtr.h @@ -1,7 +1,5 @@ #pragma once -#include - // -------------------------------------------------------------------------------------- // ScopedPtr // -------------------------------------------------------------------------------------- @@ -10,7 +8,7 @@ template< typename T > class ScopedPtr { DeclareNoncopyableObject(ScopedPtr); - + protected: T* m_ptr; @@ -19,7 +17,7 @@ public: wxEXPLICIT ScopedPtr(T * ptr = NULL) : m_ptr(ptr) { } - ~ScopedPtr() + ~ScopedPtr() throw() { Delete(); } ScopedPtr& Reassign(T * ptr = NULL) @@ -32,7 +30,7 @@ public: return *this; } - ScopedPtr& Delete() + ScopedPtr& Delete() throw() { // Thread-safe deletion: Set the pointer to NULL first, and then issue // the deletion. This allows pending Application messages that might be @@ -118,6 +116,125 @@ public: } }; +// -------------------------------------------------------------------------------------- +// ScopedArray - same as ScopedPtr but uses delete[], and has operator[] +// -------------------------------------------------------------------------------------- + +template< typename T > +class ScopedArray +{ + DeclareNoncopyableObject(ScopedArray); + +protected: + T* m_array; + uint m_valid_range; +public: + typedef T element_type; + + wxEXPLICIT ScopedArray(T * ptr = NULL) : + m_array(ptr) + , m_valid_range( 0xffffffff ) + { + } + + wxEXPLICIT ScopedArray( int size ) : + m_array( pxAssertDev( size >= 0, "Invalid negative size specified." ) ? new T[size] : NULL ) + , m_valid_range( (uint)size ) + { + } + + // For breaking the 2gb barrier, lets provision this: + wxEXPLICIT ScopedArray( s64 size ) : + m_array( pxAssertDev( size >= 0 && (size < UINT_MAX), "Invalid negative size specified to ScopedArray." ) ? new T[size] : NULL ) + , m_valid_range( (uint)size ) + { + } + + ~ScopedArray() throw() + { Delete(); } + + ScopedArray& Reassign(T * ptr = NULL) + { + if( ptr != m_array ) + { + Delete(); + m_array = ptr; + } + return *this; + } + + ScopedArray& Delete() throw() + { + // Thread-safe deletion: Set the pointer to NULL first, and then issue + // the deletion. This allows pending Application messages that might be + // dependent on the current object to nullify their actions. + + T* deleteme = m_array; + m_array = NULL; + delete[] deleteme; + + return *this; + } + + // Removes the pointer from scoped management, but does not delete! + T *DetachPtr() + { + T *ptr = m_array; + m_array = NULL; + return ptr; + } + + // Returns the managed pointer. Can return NULL as a valid result if the ScopedPtr + // has no object in management. + T* GetPtr() const + { + return m_array; + } + + void SwapPtr(ScopedArray& other) + { + T * const tmp = other.m_array; + other.m_array = m_array; + m_array = tmp; + } + + // ---------------------------------------------------------------------------- + // ScopedPtr Operators + // ---------------------------------------------------------------------------- + // I've decided to use the ATL's approach to pointer validity tests, opposed to + // the wx/boost approach (which uses some bizarre member method pointer crap, and can't + // allow the T* implicit casting. + + bool operator!() const throw() + { + return m_array == NULL; + } + + // Equality + bool operator==(T* pT) const throw() + { + return m_array == pT; + } + + // Inequality + bool operator!=(T* pT) const throw() + { + return !operator==(pT); + } + + // Convenient assignment operator. ScopedPtr = NULL will issue an automatic deletion + // of the managed pointer. + ScopedArray& operator=( T* src ) + { + return Reassign( src ); + } + + T& operator[]( uint idx ) const + { + pxAssertDev( idx < m_valid_range, "Array index out of bounds on ScopedArray." ); + return m_array[idx]; + } +}; // -------------------------------------------------------------------------------------- // pxObjPtr -- fancified version of wxScopedPtr diff --git a/common/include/Utilities/StringHelpers.h b/common/include/Utilities/StringHelpers.h index 0f43f1e89a..3b0d42a006 100644 --- a/common/include/Utilities/StringHelpers.h +++ b/common/include/Utilities/StringHelpers.h @@ -23,38 +23,6 @@ extern void px_fputs( FILE* fp, const char* src ); -// -------------------------------------------------------------------------------------- -// toUTF8 - shortcut for str.ToUTF8().data() -// -------------------------------------------------------------------------------------- -class toUTF8 -{ - DeclareNoncopyableObject( toUTF8 ); - -protected: - wxCharBuffer m_charbuffer; - -public: - toUTF8( const wxString& str ) : m_charbuffer( str.ToUTF8().data() ) { } - virtual ~toUTF8() throw() {} - - operator const char*() { return m_charbuffer.data(); } -}; - -// This class provided for completeness sake. You probably should use toUTF8 instead. -class toAscii -{ - DeclareNoncopyableObject( toAscii ); - -protected: - wxCharBuffer m_charbuffer; - -public: - toAscii( const wxString& str ) : m_charbuffer( str.ToAscii().data() ) { } - virtual ~toAscii() throw() {} - - operator const char*() { return m_charbuffer.data(); } -}; - extern wxString fromUTF8( const char* src ); extern wxString fromAscii( const char* src ); diff --git a/common/include/Utilities/Threading.h b/common/include/Utilities/Threading.h index e47b842ba3..c35c36bb89 100644 --- a/common/include/Utilities/Threading.h +++ b/common/include/Utilities/Threading.h @@ -25,10 +25,30 @@ #undef Yield // release th burden of windows.h global namespace spam. class wxTimeSpan; +#define AllowFromMainThreadOnly() \ + pxAssertMsg( wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only." ) + namespace Threading { - ////////////////////////////////////////////////////////////////////////////////////////// - // Define some useful object handles - wait events, mutexes. + // -------------------------------------------------------------------------------------- + // Platform Specific External APIs + // -------------------------------------------------------------------------------------- + // The following set of documented functions have Linux/Win32 specific implementations, + // which are found in WinThreads.cpp and LnxThreads.cpp + + + // Returns the number of available logical CPUs (cores plus hyperthreaded cpus) + extern void CountLogicalCores( int LogicalCoresPerPhysicalCPU, int PhysicalCoresPerPhysicalCPU ); + + // Releases a timeslice to other threads. + extern void Timeslice(); + + // For use in spin/wait loops. + extern void SpinWait(); + + // sleeps the current thread for the given number of milliseconds. + extern void Sleep( int ms ); + // pthread Cond is an evil api that is not suited for Pcsx2 needs. // Let's not use it. Use mutexes and semaphores instead to create waits. (Air) @@ -39,7 +59,7 @@ namespace Threading pthread_mutex_t mutex; WaitEvent(); - ~WaitEvent(); + ~WaitEvent() throw(); void Set(); void Wait(); @@ -64,7 +84,7 @@ namespace Threading public: NonblockingMutex() : val( false ) {} - virtual ~NonblockingMutex() throw() {}; + virtual ~NonblockingMutex() throw() {} bool TryLock() throw() { @@ -78,25 +98,31 @@ namespace Threading { val = false; } }; - struct Semaphore + class Semaphore { - sem_t sema; + protected: + sem_t m_sema; + public: Semaphore(); - ~Semaphore(); + virtual ~Semaphore() throw(); void Reset(); void Post(); void Post( int multiple ); -#if wxUSE_GUI - void WaitGui(); - bool WaitGui( const wxTimeSpan& timeout ); -#endif - void Wait(); - bool Wait( const wxTimeSpan& timeout ); + void WaitRaw(); + bool WaitRaw( const wxTimeSpan& timeout ); void WaitNoCancel(); int Count(); + +#if wxUSE_GUI + void Wait(); + bool Wait( const wxTimeSpan& timeout ); + + protected: + bool _WaitGui_RecursionGuard(); +#endif }; class MutexLock @@ -124,19 +150,6 @@ namespace Threading virtual ~MutexLockRecursive() throw(); }; - - // Returns the number of available logical CPUs (cores plus hyperthreaded cpus) - extern void CountLogicalCores( int LogicalCoresPerPhysicalCPU, int PhysicalCoresPerPhysicalCPU ); - - // Releases a timeslice to other threads. - extern void Timeslice(); - - // For use in spin/wait loops. - extern void SpinWait(); - - // sleeps the current thread for the given number of milliseconds. - extern void Sleep( int ms ); - // -------------------------------------------------------------------------------------- // IThread - Interface for the public access to PersistentThread. // -------------------------------------------------------------------------------------- @@ -279,8 +292,8 @@ namespace Threading DeclareNoncopyableObject(ScopedLock); protected: - MutexLock& m_lock; - bool m_IsLocked; + MutexLock& m_lock; + bool m_IsLocked; public: virtual ~ScopedLock() throw() @@ -311,6 +324,25 @@ namespace Threading m_lock.Lock(); m_IsLocked = true; } + + bool IsLocked() const { return m_IsLocked; } + + protected: + // Special constructor used by ScopedTryLock + ScopedLock( MutexLock& locker, bool isTryLock ) : + m_lock( locker ) + , m_IsLocked( isTryLock ? m_lock.TryLock() : false ) + { + } + + }; + + class ScopedTryLock : public ScopedLock + { + public: + ScopedTryLock( MutexLock& locker ) : ScopedLock( locker, true ) { } + virtual ~ScopedTryLock() throw() {} + bool Failed() const { return !m_IsLocked; } }; ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/common/include/wx/folderdesc.txt b/common/include/wx/folderdesc.txt index 2c9133cbc8..e03a2e0cd9 100644 --- a/common/include/wx/folderdesc.txt +++ b/common/include/wx/folderdesc.txt @@ -7,11 +7,9 @@ This folder contains various classes borrowed from wxWidgets 2.9.x / 3.0. /Common/include is a PCSX2 project default include search path, and with the /wx folder prefix, these files can be included the same way as other wxWidgets includes: -#include +#include If/when PCSX2 upgrades to wx2.9/3.0 these files will be removed and the wxWidgets distribution files will automatically be used instead. - -NOTE: Removed wxScopedPtr in favor of our own implementation, which uses a more -sensible API naming convention and also features better operator assignment. \ No newline at end of file +NOTE: Folder is currently empty, by design. Nothing from 2.9/3.0 is useful anymore (for now). \ No newline at end of file diff --git a/common/include/wx/scopedarray.h b/common/include/wx/scopedarray.h deleted file mode 100644 index 2a697fedc6..0000000000 --- a/common/include/wx/scopedarray.h +++ /dev/null @@ -1,63 +0,0 @@ -///////////////////////////////////////////////////////////////////////////// -// Name: wx/scopedarray.h -// Purpose: scoped smart pointer class -// Author: Vadim Zeitlin -// Created: 2009-02-03 -// RCS-ID: $Id: scopedarray.h 58634 2009-02-03 12:01:46Z VZ $ -// Copyright: (c) Jesse Lovelace and original Boost authors (see below) -// (c) 2009 Vadim Zeitlin -// Licence: wxWindows licence -///////////////////////////////////////////////////////////////////////////// - -#pragma once -#include "wx/defs.h" - -// ---------------------------------------------------------------------------- -// wxScopedArray: A scoped array, same as a wxScopedPtr but uses delete[] -// instead of delete. -// ---------------------------------------------------------------------------- - -template -class wxScopedArray -{ -public: - typedef T element_type; - - wxEXPLICIT wxScopedArray(T * array = NULL) : m_array(array) { } - - ~wxScopedArray() { delete [] m_array; } - - // test for pointer validity: defining conversion to unspecified_bool_type - // and not more obvious bool to avoid implicit conversions to integer types - typedef T *(wxScopedArray::*unspecified_bool_type)() const; - operator unspecified_bool_type() const - { - return m_array ? &wxScopedArray::get : NULL; - } - - void reset(T *array = NULL) - { - if ( array != m_array ) - { - delete [] m_array; - m_array = array; - } - } - - T& operator[](size_t n) const { return m_array[n]; } - - T *get() const { return m_array; } - - void swap(wxScopedArray &other) - { - T * const tmp = other.m_array; - other.m_array = m_array; - m_array = tmp; - } - -private: - T *m_array; - - wxScopedArray(const wxScopedArray&); - wxScopedArray& operator=(const wxScopedArray&); -}; diff --git a/common/src/Utilities/Console.cpp b/common/src/Utilities/Console.cpp index 2f1ebd870e..a64c2c1236 100644 --- a/common/src/Utilities/Console.cpp +++ b/common/src/Utilities/Console.cpp @@ -146,7 +146,7 @@ void ConsoleBuffer_Clear() void ConsoleBuffer_FlushToFile( FILE *fp ) { if( fp == NULL || m_buffer.IsEmpty() ) return; - px_fputs( fp, toUTF8(m_buffer) ); + px_fputs( fp, m_buffer.ToUTF8() ); m_buffer.Clear(); } diff --git a/common/src/Utilities/Linux/LnxThreads.cpp b/common/src/Utilities/Linux/LnxThreads.cpp index 1026cb7c01..52a7af97f9 100644 --- a/common/src/Utilities/Linux/LnxThreads.cpp +++ b/common/src/Utilities/Linux/LnxThreads.cpp @@ -45,11 +45,6 @@ namespace Threading } } - __forceinline void Timeslice() - { - usleep(500); - } - __forceinline void Sleep( int ms ) { usleep( 1000*ms ); diff --git a/common/src/Utilities/ThreadTools.cpp b/common/src/Utilities/ThreadTools.cpp index b0f93c36bb..48489bdd31 100644 --- a/common/src/Utilities/ThreadTools.cpp +++ b/common/src/Utilities/ThreadTools.cpp @@ -31,382 +31,409 @@ #include #include - -using namespace Threading; - namespace Threading { - static const wxTimeSpan ts_msec_250( 0, 0, 0, 250 ); + // 100ms interval for waitgui (issued from blocking semaphore waits on the main thread, + // to avoid gui deadlock). + static const wxTimeSpan ts_waitgui_interval( 0, 0, 0, 100 ); - void PersistentThread::_pt_callback_cleanup( void* handle ) + // Four second interval for deadlock protection on waitgui. + static const wxTimeSpan ts_waitgui_deadlock( 0, 0, 4, 0 ); + + static long _attr_refcount = 0; + static pthread_mutexattr_t _attr_recursive; +} + +__forceinline void Threading::Timeslice() +{ + sched_yield(); +} + +void Threading::PersistentThread::_pt_callback_cleanup( void* handle ) +{ + ((PersistentThread*)handle)->_ThreadCleanup(); +} + +Threading::PersistentThread::PersistentThread() : + m_name( L"PersistentThread" ) +, m_thread() +, m_sem_event() +, m_sem_finished() +, m_lock_start() +, m_detached( true ) // start out with m_thread in detached/invalid state +, m_running( false ) +{ +} + +// This destructor performs basic "last chance" cleanup, which is a blocking join +// against the thread. Extending classes should almost always implement their own +// thread closure process, since any PersistentThread will, by design, not terminate +// unless it has been properly canceled. +// +// Thread safety: This class must not be deleted from its own thread. That would be +// like marrying your sister, and then cheating on her with your daughter. +Threading::PersistentThread::~PersistentThread() throw() +{ + try { - ((PersistentThread*)handle)->_ThreadCleanup(); - } - - PersistentThread::PersistentThread() : - m_name( L"PersistentThread" ) - , m_thread() - , m_sem_event() - , m_sem_finished() - , m_lock_start() - , m_detached( true ) // start out with m_thread in detached/invalid state - , m_running( false ) - { - } - - // This destructor performs basic "last chance" cleanup, which is a blocking join - // against the thread. Extending classes should almost always implement their own - // thread closure process, since any PersistentThread will, by design, not terminate - // unless it has been properly canceled. - // - // Thread safetly: This class must not be deleted from its own thread. That would be - // like marrying your sister, and then cheating on her with your daughter. - PersistentThread::~PersistentThread() throw() - { - try - { - Console.WriteLn( L"Thread Log: Executing destructor for " + m_name ); - - if( m_running ) - { - Console.WriteLn( L"\tWaiting for running thread to end..."); - #if wxUSE_GUI - m_sem_finished.WaitGui(); - #else - m_sem_finished.Wait(); - #endif - // Need to lock here so that the thread can finish shutting down before - // it gets destroyed, otherwise th mutex handle would become invalid. - ScopedLock locker( m_lock_start ); - } - Threading::Sleep( 1 ); - Detach(); - } - DESTRUCTOR_CATCHALL - } - - // Main entry point for starting or e-starting a persistent thread. This function performs necessary - // locks and checks for avoiding race conditions, and then calls OnStart() immeediately before - // the actual thread creation. Extending classes should generally not override Start(), and should - // instead override DoPrepStart instead. - // - // This function should not be called from the owner thread. - void PersistentThread::Start() - { - ScopedLock startlock( m_lock_start ); // Prevents sudden parallel startup - if( m_running ) return; - - Detach(); // clean up previous thread handle, if one exists. - m_sem_finished.Reset(); - - OnStart(); - - if( pthread_create( &m_thread, NULL, _internal_callback, this ) != 0 ) - throw Exception::ThreadCreationError(); - - m_detached = false; - } - - // Returns: TRUE if the detachment was performed, or FALSE if the thread was - // already detached or isn't running at all. - // This function should not be called from the owner thread. - bool PersistentThread::Detach() - { - pxAssertMsg( !IsSelf(), "Thread affinity error." ); // not allowed from our own thread. - - if( _InterlockedExchange( &m_detached, true ) ) return false; - pthread_detach( m_thread ); - return true; - } - - // Remarks: - // Provision of non-blocking Cancel() is probably academic, since destroying a PersistentThread - // object performs a blocking Cancel regardless of if you explicitly do a non-blocking Cancel() - // prior, since the ExecuteTaskInThread() method requires a valid object state. If you really need - // fire-and-forget behavior on threads, use pthreads directly for now. - // - // This function should not be called from the owner thread. - // - // Parameters: - // isBlocking - indicates if the Cancel action should block for thread completion or not. - // - void PersistentThread::Cancel( bool isBlocking ) - { - pxAssertMsg( !IsSelf(), "Thread affinity error." ); - - if( !m_running ) return; - - if( m_detached ) - { - Console.Notice( "Threading Warning: Attempted to cancel detached thread; Ignoring..." ); - return; - } - - pthread_cancel( m_thread ); - - if( isBlocking ) - { -#if wxUSE_GUI - m_sem_finished.WaitGui(); -#else - m_sem_finished.Wait(); -#endif - } - } - - // Blocks execution of the calling thread until this thread completes its task. The - // caller should make sure to signal the thread to exit, or else blocking may deadlock the - // calling thread. Classes which extend PersistentThread should override this method - // and signal any necessary thread exit variables prior to blocking. - // - // Returns the return code of the thread. - // This method is roughly the equivalent of pthread_join(). - // - void PersistentThread::Block() - { - pxAssertDev( !IsSelf(), "Thread deadlock detected; Block() should never be called by the owner thread." ); + DevCon.WriteLn( L"(Thread Log) Executing destructor for " + m_name ); if( m_running ) + { + DevCon.WriteLn( L"\tWaiting for running thread to end..."); #if wxUSE_GUI - m_sem_finished.WaitGui(); -#else m_sem_finished.Wait(); +#else + m_sem_finished.WaitRaw(); +#endif + // Need to lock here so that the thread can finish shutting down before + // it gets destroyed, otherwise th mutex handle would become invalid. + ScopedLock locker( m_lock_start ); + } + Threading::Sleep( 1 ); + Detach(); + } + catch( Exception::ThreadTimedOut& ex ) + { + // Windows allows for a thread to be terminated forcefully, but it's not really + // a safe thing to do since typically threads are acquiring and releasing locks + // and semaphores all the time. And terminating threads isn't really cross-platform + // either so let's just not bother. + + // Additionally since this is a destructor most of our derived class info is lost, + // so we can't allow for customized deadlock handlers, least not in any useful + // context. So let's just log the condition and move on. + + Console.Error( wxsFormat(L"\tThread destructor for '%s' timed out with error:\n\t", + m_name.c_str(), ex.FormatDiagnosticMessage().c_str() ) ); + } + DESTRUCTOR_CATCHALL +} + +// Main entry point for starting or e-starting a persistent thread. This function performs necessary +// locks and checks for avoiding race conditions, and then calls OnStart() immeediately before +// the actual thread creation. Extending classes should generally not override Start(), and should +// instead override DoPrepStart instead. +// +// This function should not be called from the owner thread. +void Threading::PersistentThread::Start() +{ + ScopedLock startlock( m_lock_start ); // Prevents sudden parallel startup + if( m_running ) return; + + Detach(); // clean up previous thread handle, if one exists. + m_sem_finished.Reset(); + + OnStart(); + + if( pthread_create( &m_thread, NULL, _internal_callback, this ) != 0 ) + throw Exception::ThreadCreationError(); + + m_detached = false; +} + +// Returns: TRUE if the detachment was performed, or FALSE if the thread was +// already detached or isn't running at all. +// This function should not be called from the owner thread. +bool Threading::PersistentThread::Detach() +{ + pxAssertMsg( !IsSelf(), "Thread affinity error." ); // not allowed from our own thread. + + if( _InterlockedExchange( &m_detached, true ) ) return false; + pthread_detach( m_thread ); + return true; +} + +// Remarks: +// Provision of non-blocking Cancel() is probably academic, since destroying a PersistentThread +// object performs a blocking Cancel regardless of if you explicitly do a non-blocking Cancel() +// prior, since the ExecuteTaskInThread() method requires a valid object state. If you really need +// fire-and-forget behavior on threads, use pthreads directly for now. +// +// This function should not be called from the owner thread. +// +// Parameters: +// isBlocking - indicates if the Cancel action should block for thread completion or not. +// +void Threading::PersistentThread::Cancel( bool isBlocking ) +{ + pxAssertMsg( !IsSelf(), "Thread affinity error." ); + + if( !m_running ) return; + + if( m_detached ) + { + Console.Notice( "(Thread Warning) Ignoring attempted cancelation of detached thread." ); + return; + } + + pthread_cancel( m_thread ); + + if( isBlocking ) + { +#if wxUSE_GUI + m_sem_finished.Wait(); +#else + m_sem_finished.WaitRaw(); #endif } +} - bool PersistentThread::IsSelf() const - { - return pthread_self() == m_thread; +// Blocks execution of the calling thread until this thread completes its task. The +// caller should make sure to signal the thread to exit, or else blocking may deadlock the +// calling thread. Classes which extend PersistentThread should override this method +// and signal any necessary thread exit variables prior to blocking. +// +// Returns the return code of the thread. +// This method is roughly the equivalent of pthread_join(). +// +void Threading::PersistentThread::Block() +{ + pxAssertDev( !IsSelf(), "Thread deadlock detected; Block() should never be called by the owner thread." ); + + if( m_running ) +#if wxUSE_GUI + m_sem_finished.Wait(); +#else + m_sem_finished.WaitRaw(); +#endif +} + +bool Threading::PersistentThread::IsSelf() const +{ + return pthread_self() == m_thread; +} + +bool Threading::PersistentThread::IsRunning() const +{ + return !!m_running; +} + +// Throws an exception if the thread encountered one. Uses the BaseException's Rethrow() method, +// which ensures the exception type remains consistent. Debuggable stacktraces will be lost, since +// the thread will have allowed itself to terminate properly. +void Threading::PersistentThread::RethrowException() const +{ + if( !m_except ) return; + m_except->Rethrow(); +} + +void Threading::PersistentThread::TestCancel() +{ + pxAssert( IsSelf() ); + pthread_testcancel(); +} + +// Executes the virtual member method +void Threading::PersistentThread::_try_virtual_invoke( void (PersistentThread::*method)() ) +{ + try { + (this->*method)(); } - bool PersistentThread::IsRunning() const + // ---------------------------------------------------------------------------- + // Neat repackaging for STL Runtime errors... + // + catch( std::runtime_error& ex ) { - return !!m_running; + m_except = new Exception::RuntimeError( + // Diagnostic message: + wxsFormat( L"(thread: %s) STL Runtime Error: %s\n\t%s", + GetName().c_str(), fromUTF8( ex.what() ).c_str() + ), + + // User Message (not translated, std::exception doesn't have that kind of fancy! + wxsFormat( L"A runtime error occurred in %s:\n\n%s (STL)", + GetName().c_str(), fromUTF8( ex.what() ).c_str() + ) + ); } - // Throws an exception if the thread encountered one. Uses the BaseException's Rethrow() method, - // which ensures the exception type remains consistent. Debuggable stacktraces will be lost, since - // the thread will have allowed itself to terminate properly. - void PersistentThread::RethrowException() const + // ---------------------------------------------------------------------------- + catch( Exception::RuntimeError& ex ) { - if( !m_except ) return; - m_except->Rethrow(); + m_except = ex.Clone(); + m_except->DiagMsg() = wxsFormat( L"(thread:%s) ", GetName().c_str() ) + m_except->DiagMsg(); + } +#ifndef PCSX2_DEVBUILD + // ---------------------------------------------------------------------------- + // Allow logic errors to propagate out of the thread in release builds, so that they might be + // handled in non-fatal ways. On Devbuilds let them loose, so that they produce debug stack + // traces and such. + catch( std::logic_error& ex ) + { + throw Exception::LogicError( wxsFormat( L"(thread: %s) STL Logic Error: %s\n\t%s", + GetName().c_str(), fromUTF8( ex.what() ).c_str() ) + ); + } + catch( Exception::LogicError& ex ) + { + m_except = ex.Clone(); + m_except->DiagMsg() = wxsFormat( L"(thread:%s) ", GetName().c_str() ) + m_except->DiagMsg(); } - void PersistentThread::TestCancel() + // ---------------------------------------------------------------------------- + // BaseException / std::exception -- same deal as LogicErrors. + // + catch( std::exception& ex ) { - pxAssert( IsSelf() ); - pthread_testcancel(); + throw Exception::BaseException( wxsFormat( L"(thread: %s) STL exception: %s\n\t%s", + GetName().c_str(), fromUTF8( ex.what() ).c_str() ) + ); } - - // Executes the virtual member method - void PersistentThread::_try_virtual_invoke( void (PersistentThread::*method)() ) + catch( Exception::BaseException& ex ) { - try { - (this->*method)(); - } - - // ---------------------------------------------------------------------------- - // Neat repackaging for STL Runtime errors... - // - catch( std::runtime_error& ex ) - { - m_except = new Exception::RuntimeError( - // Diagnostic message: - wxsFormat( L"(thread: %s) STL Runtime Error: %s\n\t%s", - GetName().c_str(), fromUTF8( ex.what() ).c_str() - ), - - // User Message (not translated, std::exception doesn't have that kind of fancy! - wxsFormat( L"A runtime error occurred in %s:\n\n%s (STL)", - GetName().c_str(), fromUTF8( ex.what() ).c_str() - ) - ); - } - - // ---------------------------------------------------------------------------- - catch( Exception::RuntimeError& ex ) - { - m_except = ex.Clone(); - m_except->DiagMsg() = wxsFormat( L"(thread:%s) ", GetName().c_str() ) + m_except->DiagMsg(); - } - - // ---------------------------------------------------------------------------- - // Should we let logic errors propagate, or swallow them and let the thread manager - // handle them? Hmm.. - /*catch( std::logic_error& ex ) - { - throw Exception::LogicError( wxsFormat( L"(thread: %s) STL Logic Error: %s\n\t%s", - GetName().c_str(), fromUTF8( ex.what() ).c_str() ) - ); - } - catch( Exception::LogicError& ex ) - { - m_except = ex.Clone(); - m_except->DiagMsg() = wxsFormat( L"(thread:%s) ", GetName().c_str() ) + m_except->DiagMsg(); - }*/ - - // ---------------------------------------------------------------------------- - // BaseException / std::exception -- same deal. Allow propagation or no? - // - /*catch( std::exception& ex ) - { - throw Exception::BaseException( wxsFormat( L"(thread: %s) STL exception: %s\n\t%s", - GetName().c_str(), fromUTF8( ex.what() ).c_str() ) - ); - } - catch( Exception::BaseException& ex ) - { - m_except = ex.Clone(); - m_except->DiagMsg() = wxsFormat( L"(thread:%s) ", GetName().c_str() ) + m_except->DiagMsg(); - }*/ + m_except = ex.Clone(); + m_except->DiagMsg() = wxsFormat( L"(thread:%s) ", GetName().c_str() ) + m_except->DiagMsg(); } +#endif +} - // invoked internally when canceling or exiting the thread. Extending classes should implement - // OnCleanupInThread() to extend cleanup functionality. - void PersistentThread::_ThreadCleanup() +// invoked internally when canceling or exiting the thread. Extending classes should implement +// OnCleanupInThread() to extend cleanup functionality. +void Threading::PersistentThread::_ThreadCleanup() +{ + pxAssertMsg( IsSelf(), "Thread affinity error." ); // only allowed from our own thread, thanks. + + // Typically thread cleanup needs to lock against thread startup, since both + // will perform some measure of variable inits or resets, depending on how the + // derived class is implemented. + ScopedLock startlock( m_lock_start ); + + _try_virtual_invoke( &PersistentThread::OnCleanupInThread ); + + m_running = false; + m_sem_finished.Post(); +} + +wxString Threading::PersistentThread::GetName() const +{ + return m_name; +} + +void Threading::PersistentThread::_internal_execute() +{ + m_running = true; + _DoSetThreadName( m_name ); + _try_virtual_invoke( &PersistentThread::ExecuteTaskInThread ); +} + +void Threading::PersistentThread::OnStart() {} +void Threading::PersistentThread::OnCleanupInThread() {} + +// passed into pthread_create, and is used to dispatch the thread's object oriented +// callback function +void* Threading::PersistentThread::_internal_callback( void* itsme ) +{ + jASSUME( itsme != NULL ); + PersistentThread& owner = *((PersistentThread*)itsme); + + pthread_cleanup_push( _pt_callback_cleanup, itsme ); + owner._internal_execute(); + pthread_cleanup_pop( true ); + return NULL; +} + +void Threading::PersistentThread::_DoSetThreadName( const wxString& name ) +{ + _DoSetThreadName( name.ToUTF8() ); +} + +void Threading::PersistentThread::_DoSetThreadName( __unused const char* name ) +{ + pxAssertMsg( IsSelf(), "Thread affinity error." ); // only allowed from our own thread, thanks. + + // This feature needs Windows headers and MSVC's SEH support: + +#if defined(_WINDOWS_) && defined (_MSC_VER) + + // This code sample was borrowed form some obscure MSDN article. + // In a rare bout of sanity, it's an actual Micrsoft-published hack + // that actually works! + + static const int MS_VC_EXCEPTION = 0x406D1388; + + #pragma pack(push,8) + struct THREADNAME_INFO { - pxAssertMsg( IsSelf(), "Thread affinity error." ); // only allowed from our own thread, thanks. + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + }; + #pragma pack(pop) - // Typically thread cleanup needs to lock against thread startup, since both - // will perform some measure of variable inits or resets, depending on how the - // derived class is implemented. - ScopedLock startlock( m_lock_start ); + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = GetCurrentThreadId(); + info.dwFlags = 0; - _try_virtual_invoke( &PersistentThread::OnCleanupInThread ); - - m_running = false; - m_sem_finished.Post(); - } - - wxString PersistentThread::GetName() const + __try { - return m_name; + RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); } - - void PersistentThread::_internal_execute() + __except(EXCEPTION_EXECUTE_HANDLER) { - m_running = true; - _DoSetThreadName( m_name ); - _try_virtual_invoke( &PersistentThread::ExecuteTaskInThread ); - } - - void PersistentThread::OnStart() {} - void PersistentThread::OnCleanupInThread() {} - - // passed into pthread_create, and is used to dispatch the thread's object oriented - // callback function - void* PersistentThread::_internal_callback( void* itsme ) - { - jASSUME( itsme != NULL ); - PersistentThread& owner = *((PersistentThread*)itsme); - - pthread_cleanup_push( _pt_callback_cleanup, itsme ); - owner._internal_execute(); - pthread_cleanup_pop( true ); - return NULL; - } - - void PersistentThread::_DoSetThreadName( const wxString& name ) - { - _DoSetThreadName( toUTF8(name) ); - } - - void PersistentThread::_DoSetThreadName( __unused const char* name ) - { - pxAssertMsg( IsSelf(), "Thread affinity error." ); // only allowed from our own thread, thanks. - - // This feature needs Windows headers and MSVC's SEH support: - - #if defined(_WINDOWS_) && defined (_MSC_VER) - - // This code sample was borrowed form some obscure MSDN article. - // In a rare bout of sanity, it's an actual Micrsoft-published hack - // that actually works! - - static const int MS_VC_EXCEPTION = 0x406D1388; - - #pragma pack(push,8) - struct THREADNAME_INFO - { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - }; - #pragma pack(pop) - - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = name; - info.dwThreadID = GetCurrentThreadId(); - info.dwFlags = 0; - - __try - { - RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - } - #endif } +#endif +} // -------------------------------------------------------------------------------------- // BaseTaskThread Implementations // -------------------------------------------------------------------------------------- - // Tells the thread to exit and then waits for thread termination. - void BaseTaskThread::Block() +// Tells the thread to exit and then waits for thread termination. +void Threading::BaseTaskThread::Block() +{ + if( !IsRunning() ) return; + m_Done = true; + m_sem_event.Post(); + PersistentThread::Block(); +} + +// Initiates the new task. This should be called after your own StartTask has +// initialized internal variables / preparations for task execution. +void Threading::BaseTaskThread::PostTask() +{ + pxAssert( !m_detached ); + + ScopedLock locker( m_lock_TaskComplete ); + m_TaskPending = true; + m_post_TaskComplete.Reset(); + m_sem_event.Post(); +} + +// Blocks current thread execution pending the completion of the parallel task. +void Threading::BaseTaskThread::WaitForResult() +{ + if( m_detached || !m_running ) return; + if( m_TaskPending ) + #ifdef wxUSE_GUI + m_post_TaskComplete.Wait(); + #else + m_post_TaskComplete.WaitRaw(); + #endif + + m_post_TaskComplete.Reset(); +} + +void Threading::BaseTaskThread::ExecuteTaskInThread() +{ + while( !m_Done ) { - if( !IsRunning() ) return; - m_Done = true; - m_sem_event.Post(); - PersistentThread::Block(); - } + // Wait for a job -- or get a pthread_cancel. I'm easy. + m_sem_event.WaitRaw(); - // Initiates the new task. This should be called after your own StartTask has - // initialized internal variables / preparations for task execution. - void BaseTaskThread::PostTask() - { - pxAssert( !m_detached ); + Task(); + m_lock_TaskComplete.Lock(); + m_TaskPending = false; + m_post_TaskComplete.Post(); + m_lock_TaskComplete.Unlock(); + }; - ScopedLock locker( m_lock_TaskComplete ); - m_TaskPending = true; - m_post_TaskComplete.Reset(); - m_sem_event.Post(); - } - - // Blocks current thread execution pending the completion of the parallel task. - void BaseTaskThread::WaitForResult() - { - if( m_detached || !m_running ) return; - if( m_TaskPending ) - #ifdef wxUSE_GUI - m_post_TaskComplete.WaitGui(); - #else - m_post_TaskComplete.Wait(); - #endif - - m_post_TaskComplete.Reset(); - } - - void BaseTaskThread::ExecuteTaskInThread() - { - while( !m_Done ) - { - // Wait for a job -- or get a pthread_cancel. I'm easy. - m_sem_event.Wait(); - - Task(); - m_lock_TaskComplete.Lock(); - m_TaskPending = false; - m_post_TaskComplete.Post(); - m_lock_TaskComplete.Unlock(); - }; - - return; - } + return; +} // -------------------------------------------------------------------------------------- // pthread Cond is an evil api that is not suited for Pcsx2 needs. @@ -414,259 +441,309 @@ namespace Threading // -------------------------------------------------------------------------------------- #if 0 - WaitEvent::WaitEvent() - { - int err = 0; +Threading::WaitEvent::WaitEvent() +{ + int err = 0; - err = pthread_cond_init(&cond, NULL); - err = pthread_mutex_init(&mutex, NULL); - } + err = pthread_cond_init(&cond, NULL); + err = pthread_mutex_init(&mutex, NULL); +} - WaitEvent::~WaitEvent() - { - pthread_cond_destroy( &cond ); - pthread_mutex_destroy( &mutex ); - } +Threading::WaitEvent::~WaitEvent() throw() +{ + pthread_cond_destroy( &cond ); + pthread_mutex_destroy( &mutex ); +} - void WaitEvent::Set() - { - pthread_mutex_lock( &mutex ); - pthread_cond_signal( &cond ); - pthread_mutex_unlock( &mutex ); - } +void Threading::WaitEvent::Set() +{ + pthread_mutex_lock( &mutex ); + pthread_cond_signal( &cond ); + pthread_mutex_unlock( &mutex ); +} - void WaitEvent::Wait() - { - pthread_mutex_lock( &mutex ); - pthread_cond_wait( &cond, &mutex ); - pthread_mutex_unlock( &mutex ); - } +void Threading::WaitEvent::Wait() +{ + pthread_mutex_lock( &mutex ); + pthread_cond_wait( &cond, &mutex ); + pthread_mutex_unlock( &mutex ); +} #endif // -------------------------------------------------------------------------------------- // Semaphore Implementations // -------------------------------------------------------------------------------------- - Semaphore::Semaphore() - { - sem_init( &sema, false, 0 ); - } +Threading::Semaphore::Semaphore() +{ + sem_init( &m_sema, false, 0 ); +} - Semaphore::~Semaphore() - { - sem_destroy( &sema ); - } +Threading::Semaphore::~Semaphore() throw() +{ + sem_destroy( &m_sema ); +} - void Semaphore::Reset() - { - sem_destroy( &sema ); - sem_init( &sema, false, 0 ); - } +void Threading::Semaphore::Reset() +{ + sem_destroy( &m_sema ); + sem_init( &m_sema, false, 0 ); +} - void Semaphore::Post() - { - sem_post( &sema ); - } +void Threading::Semaphore::Post() +{ + sem_post( &m_sema ); +} - // Valid on Win32 builds only!! Attempts to use it on Linux will result in unresolved - // external linker errors. - void Semaphore::Post( int multiple ) - { +void Threading::Semaphore::Post( int multiple ) +{ #if defined(_MSC_VER) - sem_post_multiple( &sema, multiple ); + sem_post_multiple( &m_sema, multiple ); #else - // Only w32pthreads has the post_multiple, but it's easy enough to fake: - while( multiple > 0 ) - { - multiple--; - sem_post( &sema ); - } -#endif + // Only w32pthreads has the post_multiple, but it's easy enough to fake: + while( multiple > 0 ) + { + multiple--; + sem_post( &m_sema ); } +#endif +} #if wxUSE_GUI - // This is a wxApp-safe implementation of Wait, which makes sure and executes the App's - // pending messages *if* the Wait is performed on the Main/GUI thread. If the Wait is - // called from another thread, no message pumping is performed. - void Semaphore::WaitGui() +// (intended for internal use only) +// Returns true if the Wait is recursive, or false if the Wait is safe and should be +// handled via normal yielding methods. +bool Threading::Semaphore::_WaitGui_RecursionGuard() +{ + // In order to avoid deadlock we need to make sure we cut some time to handle + // messages. But this can result in recursive yield calls, which would crash + // the app. Protect against them here and, if recursion is detected, perform + // a standard blocking wait. + + static int __Guard = 0; + RecursionGuard guard( __Guard ); + + if( guard.Counter > 4 ) { - if( !wxThread::IsMain() || (wxTheApp == NULL) ) - Wait(); - else - { - // In order to avoid deadlock we need to make sure we cut some time - // to handle messages. - - do { - wxTheApp->Yield(); - } while( !Wait( ts_msec_250 ) ); - } + Console.WriteLn( "(Thread Log) Possible yield recursion detected in Semaphore::Wait; performing blocking wait." ); + //while( wxTheApp->Pending() ) wxTheApp->Dispatch(); // ensures console gets updated. + return true; } + return false; +} - bool Semaphore::WaitGui( const wxTimeSpan& timeout ) +// This is a wxApp-safe implementation of Wait, 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 windoes continue to repaint. If the Wait is +// called from another thread, no message pumping is performed. +// +// Exceptions: +// ThreadTimedOut - thrown if a blocking wait was needed due to recursion and the default +// timeout period (usually 4 seconds) was reached, indicating likely deadlock. If +// the method is run from a thread *other* than the MainGui thread, this exception +// cannot occur. +// +void Threading::Semaphore::Wait() +{ + if( !wxThread::IsMain() || (wxTheApp == NULL) ) { - if( !wxThread::IsMain() || (wxTheApp == NULL) ) - { - return Wait( timeout ); - } - else - { - wxTimeSpan countdown( (timeout) ); - - // In order to avoid deadlock we need to make sure we cut some time - // to handle messages. - - do { - wxTheApp->Yield(); - if( Wait( ts_msec_250 ) ) break; - countdown -= ts_msec_250; - } while( countdown.GetMilliseconds() > 0 ); - - return countdown.GetMilliseconds() > 0; - } + WaitRaw(); + } + else if( _WaitGui_RecursionGuard() ) + { + if( !WaitRaw(ts_waitgui_deadlock) ) // default is 4 seconds + throw Exception::ThreadTimedOut(); } + else + { + do { + wxTheApp->Yield( true ); + } while( !WaitRaw( ts_waitgui_interval ) ); + } +} + +// This is a wxApp-safe implementation of Wait, 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. +// +// Returns: +// false if the wait timed out before the semaphore was signaled, or true if the signal was +// reached prior to timeout. +// +// Exceptions: +// ThreadTimedOut - thrown if a blocking wait was needed due to recursion and the default +// timeout period (usually 4 seconds) was reached, indicating likely deadlock. If the +// user-specified timeout is less than four seconds, this exception cannot occur. If +// the method is run from a thread *other* than the MainGui thread, this exception +// cannot occur. +// +bool Threading::Semaphore::Wait( const wxTimeSpan& timeout ) +{ + if( !wxThread::IsMain() || (wxTheApp == NULL) ) + { + return WaitRaw( timeout ); + } + else if( _WaitGui_RecursionGuard() ) + { + if( timeout > ts_waitgui_deadlock ) + { + if( WaitRaw(ts_waitgui_deadlock) ) return true; + throw Exception::ThreadTimedOut(); + } + return WaitRaw( timeout ); + } + else + { + wxTimeSpan countdown( (timeout) ); + + do { + wxTheApp->Yield(); + if( WaitRaw( ts_waitgui_interval ) ) break; + countdown -= ts_waitgui_interval; + } while( countdown.GetMilliseconds() > 0 ); + + return countdown.GetMilliseconds() > 0; + } +} #endif - void Semaphore::Wait() - { - sem_wait( &sema ); - } +void Threading::Semaphore::WaitRaw() +{ + sem_wait( &m_sema ); +} - bool Semaphore::Wait( const wxTimeSpan& timeout ) - { - wxDateTime megafail( wxDateTime::UNow() + timeout ); - const timespec fail = { megafail.GetTicks(), megafail.GetMillisecond() * 1000000 }; - return sem_timedwait( &sema, &fail ) != -1; - } +bool Threading::Semaphore::WaitRaw( const wxTimeSpan& timeout ) +{ + wxDateTime megafail( wxDateTime::UNow() + timeout ); + const timespec fail = { megafail.GetTicks(), megafail.GetMillisecond() * 1000000 }; + return sem_timedwait( &m_sema, &fail ) != -1; +} - // Performs an uncancellable wait on a semaphore; restoring the thread's previous cancel state - // after the wait has completed. Useful for situations where the semaphore itself is stored on - // 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::Wait(), so - // consider manually specifying the thread as uncancellable and using Wait() instead if you need - // to do a lot of no-cancel waits in a tight loop worker thread, for example. - void Semaphore::WaitNoCancel() - { - int oldstate; - pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldstate ); - Wait(); - pthread_setcancelstate( oldstate, NULL ); - } +// Performs an uncancellable wait on a semaphore; restoring the thread's previous cancel state +// after the wait has completed. Useful for situations where the semaphore itself is stored on +// 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 +// 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(); + pthread_setcancelstate( oldstate, NULL ); +} - int Semaphore::Count() - { - int retval; - sem_getvalue( &sema, &retval ); - return retval; - } +int Threading::Semaphore::Count() +{ + int retval; + sem_getvalue( &m_sema, &retval ); + return retval; +} // -------------------------------------------------------------------------------------- // MutexLock Implementations // -------------------------------------------------------------------------------------- - MutexLock::MutexLock() +Threading::MutexLock::MutexLock() +{ + int err = 0; + err = pthread_mutex_init( &mutex, NULL ); +} + +Threading::MutexLock::~MutexLock() throw() +{ + pthread_mutex_destroy( &mutex ); +} + +Threading::MutexLockRecursive::MutexLockRecursive() : MutexLock( false ) +{ + if( _InterlockedIncrement( &_attr_refcount ) == 1 ) { - int err = 0; - err = pthread_mutex_init( &mutex, NULL ); + if( 0 != pthread_mutexattr_init( &_attr_recursive ) ) + throw Exception::OutOfMemory( "Out of memory error initializing the Mutex attributes for recursive mutexing." ); + + pthread_mutexattr_settype( &_attr_recursive, PTHREAD_MUTEX_RECURSIVE ); } - MutexLock::~MutexLock() throw() - { - pthread_mutex_destroy( &mutex ); - } + int err = 0; + err = pthread_mutex_init( &mutex, &_attr_recursive ); +} - static long _attr_refcount = 0; - static pthread_mutexattr_t _attr_recursive; +Threading::MutexLockRecursive::~MutexLockRecursive() throw() +{ + if( _InterlockedDecrement( &_attr_refcount ) == 0 ) + pthread_mutexattr_destroy( &_attr_recursive ); +} - MutexLockRecursive::MutexLockRecursive() : MutexLock( false ) - { - if( _InterlockedIncrement( &_attr_refcount ) == 1 ) - { - if( 0 != pthread_mutexattr_init( &_attr_recursive ) ) - throw Exception::OutOfMemory( "Out of memory error initializing the Mutex attributes for recursive mutexing." ); +void Threading::MutexLock::Lock() +{ + pthread_mutex_lock( &mutex ); +} - pthread_mutexattr_settype( &_attr_recursive, PTHREAD_MUTEX_RECURSIVE ); - } +void Threading::MutexLock::Unlock() +{ + pthread_mutex_unlock( &mutex ); +} - int err = 0; - err = pthread_mutex_init( &mutex, &_attr_recursive ); - } - - MutexLockRecursive::~MutexLockRecursive() throw() - { - if( _InterlockedDecrement( &_attr_refcount ) == 0 ) - pthread_mutexattr_destroy( &_attr_recursive ); - } - - void MutexLock::Lock() - { - pthread_mutex_lock( &mutex ); - } - - void MutexLock::Unlock() - { - pthread_mutex_unlock( &mutex ); - } - - bool MutexLock::TryLock() - { - return EBUSY != pthread_mutex_trylock( &mutex ); - } +bool Threading::MutexLock::TryLock() +{ + return EBUSY != pthread_mutex_trylock( &mutex ); +} // -------------------------------------------------------------------------------------- // InterlockedExchanges / AtomicExchanges (PCSX2's Helper versions) // -------------------------------------------------------------------------------------- // define some overloads for InterlockedExchanges for commonly used types, like u32 and s32. - __forceinline void AtomicExchange( volatile u32& Target, u32 value ) - { - _InterlockedExchange( (volatile long*)&Target, value ); - } - - __forceinline void AtomicExchangeAdd( volatile u32& Target, u32 value ) - { - _InterlockedExchangeAdd( (volatile long*)&Target, value ); - } - - __forceinline void AtomicIncrement( volatile u32& Target ) - { - _InterlockedExchangeAdd( (volatile long*)&Target, 1 ); - } - - __forceinline void AtomicDecrement( volatile u32& Target ) - { - _InterlockedExchangeAdd( (volatile long*)&Target, -1 ); - } - - __forceinline void AtomicExchange( volatile s32& Target, s32 value ) - { - _InterlockedExchange( (volatile long*)&Target, value ); - } - - __forceinline void AtomicExchangeAdd( s32& Target, u32 value ) - { - _InterlockedExchangeAdd( (volatile long*)&Target, value ); - } - - __forceinline void AtomicIncrement( volatile s32& Target ) - { - _InterlockedExchangeAdd( (volatile long*)&Target, 1 ); - } - - __forceinline void AtomicDecrement( volatile s32& Target ) - { - _InterlockedExchangeAdd( (volatile long*)&Target, -1 ); - } - - __forceinline void _AtomicExchangePointer( const void ** target, const void* value ) - { - _InterlockedExchange( (volatile long*)target, (long)value ); - } - - __forceinline void _AtomicCompareExchangePointer( const void ** target, const void* value, const void* comparand ) - { - _InterlockedCompareExchange( (volatile long*)target, (long)value, (long)comparand ); - } +__forceinline void Threading::AtomicExchange( volatile u32& Target, u32 value ) +{ + _InterlockedExchange( (volatile long*)&Target, value ); +} + +__forceinline void Threading::AtomicExchangeAdd( volatile u32& Target, u32 value ) +{ + _InterlockedExchangeAdd( (volatile long*)&Target, value ); +} + +__forceinline void Threading::AtomicIncrement( volatile u32& Target ) +{ + _InterlockedExchangeAdd( (volatile long*)&Target, 1 ); +} + +__forceinline void Threading::AtomicDecrement( volatile u32& Target ) +{ + _InterlockedExchangeAdd( (volatile long*)&Target, -1 ); +} + +__forceinline void Threading::AtomicExchange( volatile s32& Target, s32 value ) +{ + _InterlockedExchange( (volatile long*)&Target, value ); +} + +__forceinline void Threading::AtomicExchangeAdd( volatile s32& Target, u32 value ) +{ + _InterlockedExchangeAdd( (volatile long*)&Target, value ); +} + +__forceinline void Threading::AtomicIncrement( volatile s32& Target ) +{ + _InterlockedExchangeAdd( (volatile long*)&Target, 1 ); +} + +__forceinline void Threading::AtomicDecrement( volatile s32& Target ) +{ + _InterlockedExchangeAdd( (volatile long*)&Target, -1 ); +} + +__forceinline void Threading::_AtomicExchangePointer( const void ** target, const void* value ) +{ + _InterlockedExchange( (volatile long*)target, (long)value ); +} + +__forceinline void Threading::_AtomicCompareExchangePointer( const void ** target, const void* value, const void* comparand ) +{ + _InterlockedCompareExchange( (volatile long*)target, (long)value, (long)comparand ); } diff --git a/common/src/Utilities/Windows/WinThreads.cpp b/common/src/Utilities/Windows/WinThreads.cpp index 91eb0bb84d..e4961999c3 100644 --- a/common/src/Utilities/Windows/WinThreads.cpp +++ b/common/src/Utilities/Windows/WinThreads.cpp @@ -51,11 +51,6 @@ namespace Threading //ptw32_smp_system = ( x86caps.LogicalCores > 1 ) ? TRUE : FALSE; } - __forceinline void Timeslice() - { - ::Sleep(0); - } - __forceinline void Sleep( int ms ) { ::Sleep( ms ); diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp index deeb9736f4..bb50eadb33 100644 --- a/pcsx2/CDVD/CDVD.cpp +++ b/pcsx2/CDVD/CDVD.cpp @@ -81,11 +81,11 @@ FILE *_cdvdOpenMechaVer() const wxCharBuffer file( mecfile.GetFullPath().ToUTF8() ); // if file doesnt exist, create empty one - fd = fopen(file.data(), "r+b"); + fd = fopen(file, "r+b"); if (fd == NULL) { Console.Notice("MEC File Not Found , Creating Blank File"); - fd = fopen(file.data(), "wb"); + fd = fopen(file, "wb"); if (fd == NULL) { Console.Error( "\tMEC File Creation failed!" ); @@ -121,11 +121,11 @@ FILE *_cdvdOpenNVM() const wxCharBuffer file( nvmfile.GetFullPath().ToUTF8() ); // if file doesn't exist, create empty one - fd = fopen(file.data(), "r+b"); + fd = fopen(file, "r+b"); if (fd == NULL) { Console.Notice("NVM File Not Found , Creating Blank File"); - fd = fopen(file.data(), "wb"); + fd = fopen(file, "wb"); if (fd == NULL) { Console.Error( "\tNVM File Creation failed!" ); @@ -301,11 +301,15 @@ s32 cdvdWriteConfig(const u8* config) } } -void reloadElfInfo(const char* str) +static MutexLockRecursive Mutex_NewDiskCB; + +static void reloadElfInfo(const char* str) { - // Now's a good time to reload the ELF info... + // Now's a good time to reload the ELF info... if (ElfCRC == 0) { + ScopedLock locker( Mutex_NewDiskCB ); + ElfCRC = loadElfCRC( str ); ElfApplyPatches(); mtgsThread.SendGameCRC( ElfCRC ); @@ -504,7 +508,7 @@ void SaveStateBase::cdvdFreeze() } } -void cdvdDetectDisk() +static void cdvdDetectDisk() { cdvd.Type = DoCDVDdetectDiskType(); @@ -516,12 +520,14 @@ void cdvdDetectDisk() void cdvdNewDiskCB() { + if( !Mutex_NewDiskCB.TryLock() ) return; DoCDVDresetDiskTypeCache(); - cdvdDetectDisk(); + try { cdvdDetectDisk(); } + catch(...) { Mutex_NewDiskCB.Unlock(); } // ensure mutex gets unlocked. } -void mechaDecryptBytes( u32 madr, int size ) +static void mechaDecryptBytes( u32 madr, int size ) { int shiftAmount = (cdvd.decSet>>4) & 7; int doXor = (cdvd.decSet) & 1; diff --git a/pcsx2/CDVD/CDVD.h b/pcsx2/CDVD/CDVD.h index 90e0d605fb..02716d00bd 100644 --- a/pcsx2/CDVD/CDVD.h +++ b/pcsx2/CDVD/CDVD.h @@ -137,7 +137,6 @@ extern void cdvdActionInterrupt(); extern void cdvdReadInterrupt(); // We really should not have a function with the exact same name as a callback except for case! -extern void cdvdDetectDisk(); extern void cdvdNewDiskCB(); extern u8 cdvdRead(u8 key); extern void cdvdWrite(u8 key, u8 rt); diff --git a/pcsx2/CDVD/CDVDaccess.cpp b/pcsx2/CDVD/CDVDaccess.cpp index 98396ebb3d..fc3756abfc 100644 --- a/pcsx2/CDVD/CDVDaccess.cpp +++ b/pcsx2/CDVD/CDVDaccess.cpp @@ -292,15 +292,17 @@ void CDVDsys_ChangeSource( CDVD_SourceType type ) jNO_DEFAULT; } - - CDVD->newDiskCB( cdvdNewDiskCB ); } bool DoCDVDopen() { CheckNullCDVD(); - int ret = CDVD->open( m_SourceFilename[m_CurrentSourceType].IsEmpty() ? NULL : m_SourceFilename[m_CurrentSourceType].ToUTF8().data() ); + // the new disk callback is set on Init also, but just in case the plugin clears it for + // some reason on close, we re-send here: + CDVD->newDiskCB( cdvdNewDiskCB ); + + int ret = CDVD->open( !m_SourceFilename[m_CurrentSourceType].IsEmpty() ? m_SourceFilename[m_CurrentSourceType].ToUTF8() : (char*)NULL ); if( ret == -1 ) return false; int cdtype = DoCDVDdetectDiskType(); diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index f5437d1daa..759b218a7a 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -439,7 +439,7 @@ __forceinline void rcntUpdate_vSync() { eeRecIsReset = false; mtgsThread.RethrowException(); - SysCoreThread::Get().StateCheck(); + SysCoreThread::Get().StateCheckInThread(); if( eeRecIsReset ) { eeRecIsReset = false; diff --git a/pcsx2/GS.cpp b/pcsx2/GS.cpp index 09fea73c3f..806009f9a0 100644 --- a/pcsx2/GS.cpp +++ b/pcsx2/GS.cpp @@ -527,7 +527,7 @@ void gsDynamicSkipEnable() { if( !m_StrictSkipping ) return; - mtgsWaitGS(); + mtgsThread.WaitGS(); m_iSlowStart = GetCPUTicks(); frameLimitReset(); } diff --git a/pcsx2/GS.h b/pcsx2/GS.h index 6e43c88c6b..63ad972a4a 100644 --- a/pcsx2/GS.h +++ b/pcsx2/GS.h @@ -82,9 +82,9 @@ struct MTGS_FreezeData s32 retval; // value returned from the call, valid only after an mtgsWaitGS() }; -class mtgsThreadObject : public SysSuspendableThread +class mtgsThreadObject : public SysThreadBase { - typedef SysSuspendableThread _parent; + typedef SysThreadBase _parent; protected: // note: when g_pGSRingPos == g_pGSWritePos, the fifo is empty @@ -170,8 +170,6 @@ protected: extern __aligned16 mtgsThreadObject mtgsThread; -void mtgsWaitGS(); - ///////////////////////////////////////////////////////////////////////////// // Generalized GS Functions and Stuff diff --git a/pcsx2/Linux/pcsx2.cbp b/pcsx2/Linux/pcsx2.cbp index 54a3f026d5..494d72a9bc 100644 --- a/pcsx2/Linux/pcsx2.cbp +++ b/pcsx2/Linux/pcsx2.cbp @@ -1,466 +1,466 @@ - - - - - - + + + + + + diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index be5066359d..db746b4550 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -88,7 +88,7 @@ std::list ringposStack; #endif mtgsThreadObject::mtgsThreadObject() : - SysSuspendableThread() + SysThreadBase() , m_RingPos( 0 ) , m_WritePos( 0 ) @@ -244,8 +244,8 @@ void mtgsThreadObject::ExecuteTaskInThread() pthread_cleanup_push( _clean_close_gs, this ); while( true ) { - m_sem_event.Wait(); // ... because this does a cancel test itself.. - StateCheck( false ); // false disables cancel test here! + m_sem_event.WaitRaw(); // ... because this does a cancel test itself.. + StateCheckInThread( false ); // false disables cancel test here! m_RingBufferIsBusy = true; @@ -282,7 +282,7 @@ void mtgsThreadObject::ExecuteTaskInThread() m_lock_RingRestart.Lock(); m_lock_RingRestart.Unlock(); - StateCheck( false ); // disable cancel since the above locks are cancelable already + StateCheckInThread( false ); // disable cancel since the above locks are cancelable already continue; case GS_RINGTYPE_P1: @@ -435,7 +435,8 @@ void mtgsThreadObject::WaitGS() { pxAssertDev( !IsSelf(), "This method is only allowed from threads *not* named MTGS." ); - if( !pxAssertDev( m_ExecMode == ExecMode_Running, "MTGS Warning! WaitGS issued on a suspended/paused thread." ) ) return; + if( m_ExecMode == ExecMode_NoThreadYet || !IsRunning() ) return; + if( !pxAssertDev( IsOpen(), "MTGS Warning! WaitGS issued on a closed thread." ) ) return; // FIXME : Use semaphores instead of spinwaits. SetEvent(); @@ -790,7 +791,7 @@ void mtgsThreadObject::SendGameCRC( u32 crc ) void mtgsThreadObject::WaitForOpen() { if( !gsIsOpened ) - m_sem_OpenDone.WaitGui(); + m_sem_OpenDone.Wait(); m_sem_OpenDone.Reset(); } @@ -806,13 +807,5 @@ void mtgsThreadObject::Freeze( int mode, MTGS_FreezeData& data ) else SendPointerPacket( GS_RINGTYPE_FREEZE, mode, &data ); - mtgsWaitGS(); -} - -// Waits for the GS to empty out the entire ring buffer contents. -// Used primarily for plugin startup/shutdown. -void mtgsWaitGS() -{ mtgsThread.WaitGS(); } - diff --git a/pcsx2/PluginManager.cpp b/pcsx2/PluginManager.cpp index e3215ca3ed..a255714365 100644 --- a/pcsx2/PluginManager.cpp +++ b/pcsx2/PluginManager.cpp @@ -668,7 +668,7 @@ PluginManager::PluginManager( const wxString (&folders)[PluginId_Count] ) { const PluginsEnum_t pid = pi->id; - Console.WriteLn( "\tBinding %s\t: %s ", tbl_PluginInfo[pid].shortname, folders[pid].ToUTF8().data() ); + Console.WriteLn( "\tBinding %s\t: %s ", tbl_PluginInfo[pid].shortname, folders[pid].ToUTF8() ); if( folders[pid].IsEmpty() ) throw Exception::InvalidArgument( "Empty plugin filename." ); @@ -701,6 +701,8 @@ PluginManager::PluginManager( const wxString (&folders)[PluginId_Count] ) // (leave pointer null and do not generate error) } while( ++pi, pi->shortname != NULL ); + CDVDapi_Plugin.newDiskCB( cdvdNewDiskCB ); + // Hack for PAD's stupid parameter passed on Init PADinit = (_PADinit)m_info[PluginId_PAD].CommonBindings.Init; m_info[PluginId_PAD].CommonBindings.Init = _hack_PADinit; @@ -1003,7 +1005,7 @@ void PluginManager::Init() // void PluginManager::Shutdown() { - mtgsThread.Cancel(); // speedier shutdown! + mtgsThread.Cancel(); // cancel it for speedier shutdown! Close(); DbgCon.Status( "Shutting down plugins..." ); diff --git a/pcsx2/R5900.cpp b/pcsx2/R5900.cpp index d624a6b373..fe32764854 100644 --- a/pcsx2/R5900.cpp +++ b/pcsx2/R5900.cpp @@ -58,8 +58,8 @@ R5900Exception::BaseExcept::~BaseExcept() throw (){} void cpuReset() { - if( mtgsThread.IsExecMode_Running() ) - mtgsWaitGS(); // GS better be done processing before we reset the EE, just in case. + if( mtgsThread.IsOpen() ) + mtgsThread.WaitGS(); // GS better be done processing before we reset the EE, just in case. cpuIsInitialized = true; diff --git a/pcsx2/RecoverySystem.cpp b/pcsx2/RecoverySystem.cpp index 327bdf87ba..c601e52fd1 100644 --- a/pcsx2/RecoverySystem.cpp +++ b/pcsx2/RecoverySystem.cpp @@ -29,12 +29,12 @@ static NonblockingMutex state_buffer_lock; // This boolean is to keep the system from resuming emulation until the current state has completely // uploaded or downloaded itself. It is only modified from the main thread, and should only be read -// form the main thread. -bool sys_resume_lock = false; +// from the main thread. +int sys_resume_lock = 0; static FnType_OnThreadComplete* Callback_FreezeFinished = NULL; -static void StateThread_OnAppStatus( void* thr, const enum AppStatusEvent& stat ) +static void StateThread_OnAppStatus( void* thr, const enum AppEventType& stat ) { if( (thr == NULL) || (stat != AppStatus_Exiting) ) return; ((PersistentThread*)thr)->Cancel(); @@ -54,7 +54,7 @@ class _BaseStateThread : public PersistentThread typedef PersistentThread _parent; protected: - EventListenerBinding m_bind_OnExit; + EventListenerBinding m_bind_OnExit; public: virtual ~_BaseStateThread() throw() @@ -64,7 +64,7 @@ public: protected: _BaseStateThread( const char* name, FnType_OnThreadComplete* onFinished ) : - m_bind_OnExit( wxGetApp().Source_AppStatus(), EventListener( this, StateThread_OnAppStatus ) ) + m_bind_OnExit( wxGetApp().Source_AppStatus(), EventListener( this, StateThread_OnAppStatus ) ) { Callback_FreezeFinished = onFinished; m_name = L"StateThread::" + fromUTF8(name); @@ -105,7 +105,7 @@ protected: { _parent::OnStart(); - sys_resume_lock = true; + ++sys_resume_lock; CoreThread.Pause(); } @@ -142,7 +142,7 @@ protected: throw Exception::RuntimeError( "ThawState request made, but no valid state exists!" ); } - sys_resume_lock = true; + ++sys_resume_lock; CoreThread.Pause(); } @@ -186,7 +186,7 @@ protected: void OnStart() { _parent::OnStart(); - m_gzfp = gzopen( toUTF8(m_filename), "wb" ); + m_gzfp = gzopen( m_filename.ToUTF8(), "wb" ); if( m_gzfp == NULL ) throw Exception::CreateStream( m_filename, "Cannot create savestate file for writing." ); } @@ -251,7 +251,7 @@ protected: { _parent::OnStart(); - m_gzfp = gzopen( toUTF8(m_filename), "rb" ); + m_gzfp = gzopen( m_filename.ToUTF8(), "rb" ); if( m_gzfp == NULL ) throw Exception::CreateStream( m_filename, "Cannot open savestate file for reading." ); } @@ -284,7 +284,7 @@ protected: void Pcsx2App::OnFreezeThreadFinished( wxCommandEvent& evt ) { - // clear the OnFreezeFinsihed to NULL now, in case of error. + // clear the OnFreezeFinished to NULL now, in case of error. // (but only actually run it if no errors occur) FnType_OnThreadComplete* fn_tmp = Callback_FreezeFinished; Callback_FreezeFinished = NULL; @@ -293,7 +293,7 @@ void Pcsx2App::OnFreezeThreadFinished( wxCommandEvent& evt ) ScopedPtr thr( (PersistentThread*)evt.GetClientData() ); if( !pxAssertDev( thr != NULL, "NULL thread handle on freeze finished?" ) ) return; state_buffer_lock.Release(); - sys_resume_lock = false; + --sys_resume_lock; thr->RethrowException(); } diff --git a/pcsx2/System/SysThreads.cpp b/pcsx2/System/SysThreads.cpp index 204326fcc8..f4c43879df 100644 --- a/pcsx2/System/SysThreads.cpp +++ b/pcsx2/System/SysThreads.cpp @@ -28,32 +28,32 @@ static __threadlocal SysCoreThread* tls_coreThread = NULL; // -------------------------------------------------------------------------------------- -// SysSuspendableThread *External Thread* Implementations +// SysThreadBase *External Thread* Implementations // (Called form outside the context of this thread) // -------------------------------------------------------------------------------------- -SysSuspendableThread::SysSuspendableThread() : +SysThreadBase::SysThreadBase() : m_ExecMode( ExecMode_NoThreadYet ) -, m_lock_ExecMode() +, m_ExecModeMutex() , m_ResumeEvent() , m_SuspendEvent() -, m_ResumeProtection( false ) +, m_resume_guard( 0 ) { } -SysSuspendableThread::~SysSuspendableThread() throw() +SysThreadBase::~SysThreadBase() throw() { } -void SysSuspendableThread::Start() +void SysThreadBase::Start() { _parent::Start(); - m_ExecMode = ExecMode_Suspending; + m_ExecMode = ExecMode_Closing; m_sem_event.Post(); } -void SysSuspendableThread::OnStart() +void SysThreadBase::OnStart() { if( !pxAssertDev( m_ExecMode == ExecMode_NoThreadYet, "SysSustainableThread:Start(): Invalid execution mode" ) ) return; @@ -63,9 +63,8 @@ void SysSuspendableThread::OnStart() _parent::OnStart(); } -// Pauses the emulation state at the next PS2 vsync, and returns control to the calling -// thread; or does nothing if the core is already suspended. Calling this thread from the -// Core thread will result in deadlock. +// Suspends emulation and closes the emulation state (including plugins) 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. @@ -78,61 +77,78 @@ void SysSuspendableThread::OnStart() // suspended. // // Exceptions: -// CancelEvent - thrown if the thread is already in a Paused state. Because actions that -// pause emulation typically rely on plugins remaining loaded/active, Suspension must -// cansel itself forcefully or risk crashing whatever other action is in progress. +// CancelEvent - thrown if the thread is already in a Paused or Closing state. Because +// actions that pause emulation typically rely on plugins remaining loaded/active, +// Suspension must cansel itself forcefully or risk crashing whatever other action is +// in progress. // -bool SysSuspendableThread::Suspend( bool isBlocking ) +bool SysThreadBase::Suspend( bool isBlocking ) { if( IsSelf() || !IsRunning() ) return false; + + // shortcut ExecMode check to avoid deadlocking on redundant calls to Suspend issued + // from Resume or OnResumeReady code. + if( m_ExecMode == ExecMode_Closed ) return false; bool retval = false; { - ScopedLock locker( m_lock_ExecMode ); + ScopedLock locker( m_ExecModeMutex ); - if( m_ExecMode == ExecMode_Suspended ) - return false; + // 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_Running ) + if( m_ExecMode == ExecMode_Opened ) { - m_ExecMode = ExecMode_Suspending; + m_ExecMode = ExecMode_Closing; retval = true; } - pxAssertDev( m_ExecMode == ExecMode_Suspending, "ExecMode should be nothing other than Suspending..." ); + pxAssertDev( m_ExecMode == ExecMode_Closing, "ExecMode should be nothing other than Closing..." ); + m_SuspendEvent.Reset(); + m_sem_event.Post(); } - m_SuspendEvent.Reset(); - m_sem_event.Post(); - if( isBlocking ) m_SuspendEvent.WaitGui(); + + if( isBlocking ) m_SuspendEvent.Wait(); return retval; } -bool SysSuspendableThread::Pause() +// Returns: +// The previous suspension state; true if the thread was running or false if it was +// closed, not running, or paused. +// +bool SysThreadBase::Pause() { if( IsSelf() || !IsRunning() ) return false; + // 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 false; + bool retval = false; { - ScopedLock locker( m_lock_ExecMode ); + ScopedLock locker( m_ExecModeMutex ); - if( (m_ExecMode == ExecMode_Suspended) || (m_ExecMode == ExecMode_Paused) ) return false; + // Check again -- status could have changed since above. + if( (m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused) ) return false; - if( m_ExecMode == ExecMode_Running ) + if( m_ExecMode == ExecMode_Opened ) { m_ExecMode = ExecMode_Pausing; retval = true; } pxAssertDev( m_ExecMode == ExecMode_Pausing, "ExecMode should be nothing other than Pausing..." ); + + m_SuspendEvent.Reset(); + m_sem_event.Post(); } - m_SuspendEvent.Reset(); - m_sem_event.Post(); - m_SuspendEvent.WaitGui(); + + m_SuspendEvent.Wait(); return retval; } @@ -140,73 +156,93 @@ bool SysSuspendableThread::Pause() // 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 (can occur on first call only): // PluginInitError - thrown if a plugin fails init (init is performed on the current thread // on the first time the thread is resumed from it's initial idle state) // ThreadCreationError - Insufficient system resources to create thread. // -void SysSuspendableThread::Resume() +void SysThreadBase::Resume() { if( IsSelf() ) return; + if( m_ExecMode == ExecMode_Opened ) return; + if( !pxAssert( g_plugins != NULL ) ) return; + + ScopedLock locker( m_ExecModeMutex ); + + // Recursion guard is needed because of the non-blocking Wait if the state + // is Suspending/Closing. Processed events could recurse into Resume, and we'll + // want to silently ignore them. + RecursionGuard guard( m_resume_guard ); + if( guard.IsReentrant() ) return; + + switch( m_ExecMode ) { - ScopedLock locker( m_lock_ExecMode ); + case ExecMode_Opened: return; - switch( m_ExecMode ) - { - case ExecMode_Running: return; + case ExecMode_NoThreadYet: + Start(); + m_ExecMode = ExecMode_Closing; + // fall through... - case ExecMode_NoThreadYet: - Start(); - m_ExecMode = ExecMode_Suspending; - // fall through... + case ExecMode_Closing: + case ExecMode_Pausing: + // we need to make sure and wait for the emuThread to enter a fully suspended + // state before continuing... - case ExecMode_Suspending: - case ExecMode_Pausing: - // we need to make sure and wait for the emuThread to enter a fully suspended - // state before continuing... - - locker.Unlock(); // no deadlocks please, thanks. :) - m_SuspendEvent.WaitGui(); - break; - } + locker.Unlock(); // no deadlocks please, thanks. :) + ++sys_resume_lock; + m_SuspendEvent.Wait(); + --sys_resume_lock; + locker.Lock(); + + // The entire state coming out of a Wait is indeterminate because of user input + // and pending messages being handled. If something doesn't feel right, we should + // abort. + + if( (m_ExecMode != ExecMode_Closed) && (m_ExecMode != ExecMode_Paused) ) return; + if( g_plugins == NULL ) return; + break; } - pxAssertDev( (m_ExecMode == ExecMode_Suspended) || (m_ExecMode == ExecMode_Paused), - "SysSuspendableThread is not in a suspended/paused state? wtf!" ); + pxAssertDev( (m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused), + "SysThreadBase is not in a closed/paused state? wtf!" ); - m_ResumeProtection = true; OnResumeReady(); - m_ResumeProtection = false; - m_ExecMode = ExecMode_Running; + m_ExecMode = ExecMode_Opened; m_ResumeEvent.Post(); } // -------------------------------------------------------------------------------------- -// SysSuspendableThread *Worker* Implementations +// SysThreadBase *Worker* Implementations // (Called from the context of this thread only) // -------------------------------------------------------------------------------------- -void SysSuspendableThread::OnCleanupInThread() +void SysThreadBase::OnCleanupInThread() { - ScopedLock locker( m_lock_ExecMode ); + ScopedLock locker( m_ExecModeMutex ); m_ExecMode = ExecMode_NoThreadYet; _parent::OnCleanupInThread(); } -void SysSuspendableThread::StateCheck( bool isCancelable ) +void SysThreadBase::StateCheckInThread( bool isCancelable ) { // Shortcut for the common case, to avoid unnecessary Mutex locks: - if( m_ExecMode == ExecMode_Running ) + if( m_ExecMode == ExecMode_Opened ) { if( isCancelable ) TestCancel(); return; } // Oh, seems we need a full lock, because something special is happening! - ScopedLock locker( m_lock_ExecMode ); + ScopedLock locker( m_ExecModeMutex ); switch( m_ExecMode ) { @@ -219,7 +255,7 @@ void SysSuspendableThread::StateCheck( bool isCancelable ) break; #endif - case ExecMode_Running: + case ExecMode_Opened: // Yup, need this a second time. Variable state could have changed while we // were trying to acquire the lock above. if( isCancelable ) @@ -236,26 +272,26 @@ void SysSuspendableThread::StateCheck( bool isCancelable ) // fallthrough... case ExecMode_Paused: - m_lock_ExecMode.Unlock(); + m_ExecModeMutex.Unlock(); while( m_ExecMode == ExecMode_Paused ) - m_ResumeEvent.WaitGui(); + m_ResumeEvent.WaitRaw(); OnResumeInThread( false ); break; // ------------------------------------- - case ExecMode_Suspending: + case ExecMode_Closing: { OnSuspendInThread(); - m_ExecMode = ExecMode_Suspended; + m_ExecMode = ExecMode_Closed; m_SuspendEvent.Post(); } // fallthrough... - case ExecMode_Suspended: - m_lock_ExecMode.Unlock(); - while( m_ExecMode == ExecMode_Suspended ) - m_ResumeEvent.WaitGui(); + case ExecMode_Closed: + m_ExecModeMutex.Unlock(); + while( m_ExecMode == ExecMode_Closed ) + m_ResumeEvent.WaitRaw(); OnResumeInThread( true ); break; @@ -330,8 +366,7 @@ void SysCoreThread::ApplySettings( const Pcsx2Config& src ) { if( src == EmuConfig ) return; - // Suspend only if ResumeProtection is false: - const bool resumeWhenDone = !m_ResumeProtection && Suspend(); + const bool resumeWhenDone = Suspend(); m_resetRecompilers = ( src.Cpu != EmuConfig.Cpu ) || ( src.Gamefixes != EmuConfig.Gamefixes ) || ( src.Speedhacks != EmuConfig.Speedhacks ); m_resetProfilers = (src.Profiler != EmuConfig.Profiler ); @@ -416,8 +451,8 @@ void SysCoreThread::ExecuteTaskInThread() { tls_coreThread = this; - m_sem_event.Wait(); - StateCheck(); + m_sem_event.WaitRaw(); + StateCheckInThread(); CpuExecute(); } diff --git a/pcsx2/System/SysThreads.h b/pcsx2/System/SysThreads.h index 6bffaeeadb..92bbd88169 100644 --- a/pcsx2/System/SysThreads.h +++ b/pcsx2/System/SysThreads.h @@ -31,39 +31,71 @@ public: }; -class SysSuspendableThread : public PersistentThread, public virtual ISysThread +class SysThreadBase : public PersistentThread, public virtual ISysThread { typedef PersistentThread _parent; protected: + // Important: The order of these enumerations matters. All "not-open" statuses must + // be listed before ExecMode_Closed, since there are "optimized" tests that rely on the + // assumption that "ExecMode <= ExecMode_Closed" equates to a closed thread status. enum ExecutionMode { + // Thread has not been created yet. Typically this is the same as IsRunning() + // returning FALSE. ExecMode_NoThreadYet, - ExecMode_Running, - ExecMode_Suspending, - ExecMode_Suspended, + + // Close signal has been sent to the thread, but the thread's response is still + // pending (thread is busy/running). + ExecMode_Closing, + + // Thread is safely paused, with plugins in a "closed" state, and waiting for a + // resume/open signal. + ExecMode_Closed, + + // Thread is active and running, with pluigns in an "open" state. + ExecMode_Opened, + + // Pause signal has been sent to the thread, but the thread's response is still + // pending (thread is busy/running). ExecMode_Pausing, + + // Thread is safely paused, with plugins in an "open" state, and waiting for a + // resume/open signal. ExecMode_Paused, }; volatile ExecutionMode m_ExecMode; - MutexLock m_lock_ExecMode; + MutexLock m_ExecModeMutex; Semaphore m_ResumeEvent; Semaphore m_SuspendEvent; - bool m_ResumeProtection; - -public: - explicit SysSuspendableThread(); - virtual ~SysSuspendableThread() throw(); + int m_resume_guard; - bool IsExecMode_Running() const { return m_ExecMode == ExecMode_Running; } +public: + explicit SysThreadBase(); + virtual ~SysThreadBase() throw(); + + // Thread safety for IsOpen / IsClosed: The execution mode can change at any time on + // any thread, so the actual status may have already changed by the time this function + // returns its result. Typically this isn't of major concern. However if you need + // more assured execution mode status, issue a lock against the ExecutionModeMutex() + // first. + bool IsOpen() const + { + return m_ExecMode > ExecMode_Closed; + } + + bool IsClosed() const { return !IsOpen(); } + + ExecutionMode GetExecutionMode() const { return m_ExecMode; } + MutexLock& ExecutionModeMutex() { return m_ExecModeMutex; } virtual bool Suspend( bool isBlocking = true ); virtual void Resume(); virtual bool Pause(); - virtual void StateCheck( bool isCancelable = true ); + virtual void StateCheckInThread( bool isCancelable = true ); virtual void OnCleanupInThread(); // This function is called by Resume immediately prior to releasing the suspension of @@ -79,21 +111,21 @@ protected: virtual void Start(); // Extending classes should implement this, but should not call it. The parent class - // handles invocation by the following guidelines: Called *in thread* from StateCheck() + // handles invocation by the following guidelines: Called *in thread* from StateCheckInThread() // prior to suspending the thread (ie, when Suspend() has been called on a separate // thread, requesting this thread suspend itself temporarily). After this is called, // the thread enters a waiting state on the m_ResumeEvent semaphore. virtual void OnSuspendInThread()=0; // Extending classes should implement this, but should not call it. The parent class - // handles invocation by the following guidelines: Called *in thread* from StateCheck() + // handles invocation by the following guidelines: Called *in thread* from StateCheckInThread() // prior to pausing the thread (ie, when Pause() has been called on a separate thread, // requesting this thread pause itself temporarily). After this is called, the thread // enters a waiting state on the m_ResumeEvent semaphore. virtual void OnPauseInThread()=0; // Extending classes should implement this, but should not call it. The parent class - // handles invocation by the following guidelines: Called from StateCheck() after the + // handles invocation by the following guidelines: Called from StateCheckInThread() after the // thread has been suspended and then subsequently resumed. // Parameter: // isSuspended - set to TRUE if the thread is returning from a suspended state, or @@ -104,9 +136,9 @@ protected: // -------------------------------------------------------------------------------------- // EECoreThread class // -------------------------------------------------------------------------------------- -class SysCoreThread : public SysSuspendableThread +class SysCoreThread : public SysThreadBase { - typedef SysSuspendableThread _parent; + typedef SysThreadBase _parent; protected: bool m_resetRecompilers; @@ -141,3 +173,5 @@ protected: virtual void OnCleanupInThread(); virtual void ExecuteTaskInThread(); }; + +extern int sys_resume_lock; diff --git a/pcsx2/Vif1Dma.cpp b/pcsx2/Vif1Dma.cpp index 19495f01ca..2bd01aa309 100644 --- a/pcsx2/Vif1Dma.cpp +++ b/pcsx2/Vif1Dma.cpp @@ -728,7 +728,7 @@ void vif1TransferFromMemory() { if (size > 1) { - mtgsWaitGS(); + mtgsThread.WaitGS(); GSreadFIFO((u64*)&PS2MEM_HW[0x5000]); } pMem[0] = psHu64(VIF1_FIFO); @@ -738,7 +738,7 @@ void vif1TransferFromMemory() } else { - mtgsWaitGS(); + mtgsThread.WaitGS(); GSreadFIFO2(pMem, vif1ch->qwc); // set incase read diff --git a/pcsx2/gui/App.h b/pcsx2/gui/App.h index e1739b3c8a..ffb1601a65 100644 --- a/pcsx2/gui/App.h +++ b/pcsx2/gui/App.h @@ -22,8 +22,9 @@ #include #include "Utilities/Listeners.h" +#include "IniInterface.h" -class IniInterface; +//class IniInterface; class MainEmuFrame; class GSFrame; class ConsoleLogFrame; @@ -40,9 +41,6 @@ class AppCoreThread; typedef void FnType_OnThreadComplete(const wxCommandEvent& evt); -#define AllowFromMainThreadOnly() \ - pxAssertMsg( wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only." ) - BEGIN_DECLARE_EVENT_TYPES() DECLARE_EVENT_TYPE( pxEVT_SemaphorePing, -1 ) DECLARE_EVENT_TYPE( pxEVT_OpenModalDialog, -1 ) @@ -159,7 +157,7 @@ enum DialogIdentifiers DialogId_About, }; -enum AppStatusEvent +enum AppEventType { // Maybe this will be expanded upon later..? AppStatus_Exiting @@ -309,6 +307,23 @@ struct MsgboxEventResult } }; +// -------------------------------------------------------------------------------------- +// AppIniSaver / AppIniLoader +// -------------------------------------------------------------------------------------- +class AppIniSaver : public IniSaver +{ +public: + AppIniSaver(); + virtual ~AppIniSaver() {} +}; + +class AppIniLoader : public IniLoader +{ +public: + AppIniLoader(); + virtual ~AppIniLoader() {} +}; + // -------------------------------------------------------------------------------------- // Pcsx2App - main wxApp class // -------------------------------------------------------------------------------------- @@ -390,7 +405,7 @@ public: ConsoleLogFrame* GetProgramLog(); void ProgramLog_CountMsg(); void ProgramLog_PostEvent( wxEvent& evt ); - void EnableConsoleLogging() const; + void EnableAllLogging() const; void DisableWindowLogging() const; void DisableDiskLogging() const; void OnProgramLogClosed(); @@ -402,12 +417,16 @@ public: protected: CmdEvt_Source m_evtsrc_CorePluginStatus; CmdEvt_Source m_evtsrc_CoreThreadStatus; - EventSource m_evtsrc_AppStatus; + EventSource m_evtsrc_SettingsApplied; + EventSource m_evtsrc_SettingsLoadSave; + EventSource m_evtsrc_AppStatus; public: CmdEvt_Source& Source_CoreThreadStatus() { return m_evtsrc_CoreThreadStatus; } CmdEvt_Source& Source_CorePluginStatus() { return m_evtsrc_CorePluginStatus; } - EventSource& Source_AppStatus() { return m_evtsrc_AppStatus; } + EventSource& Source_SettingsApplied() { return m_evtsrc_SettingsApplied; } + EventSource& Source_SettingsLoadSave() { return m_evtsrc_SettingsLoadSave; } + EventSource& Source_AppStatus() { return m_evtsrc_AppStatus; } protected: void InitDefaultGlobalAccelerators(); @@ -453,6 +472,7 @@ protected: enum CoreThreadStatus { + CoreStatus_Indeterminate, CoreStatus_Resumed, CoreStatus_Suspended, CoreStatus_Stopped, @@ -471,11 +491,14 @@ public: virtual bool Suspend( bool isBlocking=true ); virtual void Resume(); - virtual void StateCheck( bool isCancelable=true ); + virtual void StateCheckInThread( bool isCancelable=true ); virtual void ApplySettings( const Pcsx2Config& src ); protected: virtual void OnResumeReady(); + + virtual void OnResumeInThread( bool IsSuspended ); + virtual void OnSuspendInThread(); virtual void OnCleanupInThread(); virtual void ExecuteTaskInThread(); }; @@ -514,8 +537,6 @@ DECLARE_APP(Pcsx2App) // External App-related Globals and Shortcuts // -------------------------------------------------------------------------------------- -extern bool sys_resume_lock; - extern int EnumeratePluginsInFolder( const wxDirName& searchPath, wxArrayString* dest ); extern void LoadPluginsPassive( FnType_OnThreadComplete* onComplete ); extern void LoadPluginsImmediate(); @@ -523,7 +544,7 @@ extern void UnloadPlugins(); extern void AppLoadSettings(); extern void AppSaveSettings(); -extern void AppApplySettings( const AppConfig* oldconf=NULL ); +extern void AppApplySettings( const AppConfig* oldconf=NULL, bool saveOnSuccess=false ); extern bool SysHasValidState(); diff --git a/pcsx2/gui/AppAssert.cpp b/pcsx2/gui/AppAssert.cpp index 02a635d6e3..1277d0b7b9 100644 --- a/pcsx2/gui/AppAssert.cpp +++ b/pcsx2/gui/AppAssert.cpp @@ -96,7 +96,7 @@ void Pcsx2App::OnAssertFailure( const wxChar *file, int line, const wxChar *func // make life easier for people using VC++ IDE by using this format, which allows double-click // response times from the Output window... - dbgmsg.Printf( L"%s(%d) : assertion failed%s%s: %s", file, line, + dbgmsg.Printf( L"%s(%d) : assertion failed%s%s: %s\n", file, line, (func==NULL) ? wxEmptyString : L" in ", (func==NULL) ? wxEmptyString : func, message.c_str() diff --git a/pcsx2/gui/AppConfig.cpp b/pcsx2/gui/AppConfig.cpp index 76911c831c..5cc20e5310 100644 --- a/pcsx2/gui/AppConfig.cpp +++ b/pcsx2/gui/AppConfig.cpp @@ -375,8 +375,7 @@ void AppConfig::LoadSaveMemcards( IniInterface& ini ) } } -// ------------------------------------------------------------------------ -void AppConfig::LoadSave( IniInterface& ini ) +void AppConfig::LoadSaveRootItems( IniInterface& ini ) { AppConfig defaults; @@ -388,11 +387,16 @@ void AppConfig::LoadSave( IniInterface& ini ) IniEntry( Listbook_ImageSize ); IniEntry( Toolbar_ImageSize ); IniEntry( Toolbar_ShowLabels ); - + IniEntry( CurrentIso ); ini.EnumEntry( L"CdvdSource", CdvdSource, CDVD_SourceLabels, defaults.CdvdSource ); +} +// ------------------------------------------------------------------------ +void AppConfig::LoadSave( IniInterface& ini ) +{ + LoadSaveRootItems( ini ); LoadSaveMemcards( ini ); // Process various sub-components: @@ -508,6 +512,30 @@ wxFileConfig* OpenFileConfig( const wxString& filename ) return new wxFileConfig( wxEmptyString, wxEmptyString, filename, wxEmptyString, wxCONFIG_USE_RELATIVE_PATH ); } +void RelocateLogfile() +{ + g_Conf->Folders.Logs.Mkdir(); + + wxString newlogname( Path::Combine( g_Conf->Folders.Logs.ToString(), L"emuLog.txt" ) ); + + if( (emuLog != NULL) && (emuLogName != newlogname) ) + { + Console.Status( "\nRelocating Logfile...\n\tFrom: %s\n\tTo : %s\n", emuLogName.c_str(), newlogname.c_str() ); + wxGetApp().DisableDiskLogging(); + + fclose( emuLog ); + emuLog = NULL; + } + + if( emuLog == NULL ) + { + emuLogName = newlogname; + emuLog = fopen( emuLogName.ToUTF8(), "wb" ); + } + + wxGetApp().EnableAllLogging(); +} + // Parameters: // overwrite - this option forces the current settings to overwrite any existing settings that might // be saved to the configured ini/settings folder. @@ -517,35 +545,32 @@ void AppConfig_OnChangedSettingsFolder( bool overwrite ) PathDefs::GetDocuments().Mkdir(); PathDefs::GetSettings().Mkdir(); - // Allow wx to use our config, and enforces auto-cleanup as well + if( overwrite ) + { + if( !wxRemoveFile( GetSettingsFilename() ) ) + throw Exception::AccessDenied( "Failed to overwrite settings; permission to file was denied." ); + } + + //if( GetAppConfig() != NULL ) + // wxGetApp().Source_SettingsChanged().Dispatch( SettingsEvt_IniClosing ); + + // Bind into wxConfigBase to allow wx to use our config internally, and delete whatever + // comes out (cleans up prev config, if one). delete wxConfigBase::Set( OpenFileConfig( GetSettingsFilename() ) ); - wxConfigBase::Get()->SetRecordDefaults(); + GetAppConfig()->SetRecordDefaults(); + + //wxGetApp().Source_SettingsChanged().Dispatch( SettingsEvt_IniOpening ); if( !overwrite ) AppLoadSettings(); AppApplySettings(); - sMainFrame.ReloadRecentLists(); - - g_Conf->Folders.Logs.Mkdir(); - - wxString newlogname( Path::Combine( g_Conf->Folders.Logs.ToString(), L"emuLog.txt" ) ); - - wxGetApp().DisableDiskLogging(); - - if( emuLog != NULL ) - { - if( emuLogName != newlogname ) - { - fclose( emuLog ); - emuLog = NULL; - } - } - - if( emuLog == NULL ) - { - emuLogName = newlogname; - emuLog = fopen( toUTF8(emuLogName), "wb" ); - } - wxGetApp().EnableConsoleLogging(); } + +// Returns the current application configuration file. This is preferred over using +// wxConfigBase::GetAppConfig(), since it defaults to *not* creating a config file +// automatically (which is typically highly undesired behavior in our system) +wxConfigBase* GetAppConfig() +{ + return wxConfigBase::Get( false ); +} \ No newline at end of file diff --git a/pcsx2/gui/AppConfig.h b/pcsx2/gui/AppConfig.h index 543a3eb76a..8be7ea404e 100644 --- a/pcsx2/gui/AppConfig.h +++ b/pcsx2/gui/AppConfig.h @@ -19,6 +19,7 @@ #include "CDVD/CDVDaccess.h" class IniInterface; +class wxConfigBase; class wxFileConfig; extern bool UseAdminMode; // dictates if the program uses /home/user or /cwd for the program data @@ -164,6 +165,7 @@ public: void LoadSaveUserMode( IniInterface& ini, const wxString& cwdhash ); void LoadSave( IniInterface& ini ); + void LoadSaveRootItems( IniInterface& ini ); void LoadSaveMemcards( IniInterface& ini ); }; @@ -176,6 +178,8 @@ struct ConfigOverrides extern ConfigOverrides OverrideOptions; extern wxFileConfig* OpenFileConfig( const wxString& filename ); +extern void RelocateLogfile(); extern void AppConfig_OnChangedSettingsFolder( bool overwrite = false ); +extern wxConfigBase* GetAppConfig(); extern ScopedPtr g_Conf; diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index 5758619c30..5a4f28d1dc 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -31,9 +31,9 @@ bool AppCoreThread::Suspend( bool isBlocking ) { bool retval = _parent::Suspend( isBlocking ); - wxCommandEvent evt( pxEVT_CoreThreadStatus ); + /*wxCommandEvent evt( pxEVT_CoreThreadStatus ); evt.SetInt( CoreStatus_Suspended ); - wxGetApp().AddPendingEvent( evt ); + wxGetApp().AddPendingEvent( evt );*/ // Clear the sticky key statuses, because hell knows what'll change while the PAD // plugin is suspended. @@ -50,12 +50,24 @@ void AppCoreThread::Resume() // Thread control (suspend / resume) should only be performed from the main/gui thread. if( !AllowFromMainThreadOnly() ) return; - if( sys_resume_lock ) + if( sys_resume_lock > 0 ) { Console.WriteLn( "SysResume: State is locked, ignoring Resume request!" ); return; } _parent::Resume(); + + if( m_ExecMode != ExecMode_Opened ) + { + // Resume failed for some reason, so update GUI statuses and post a message to + // try again on the resume. + + wxCommandEvent evt( pxEVT_CoreThreadStatus ); + evt.SetInt( CoreStatus_Suspended ); + wxGetApp().AddPendingEvent( evt ); + + sApp.SysExecute(); + } } void AppCoreThread::OnResumeReady() @@ -65,11 +77,25 @@ void AppCoreThread::OnResumeReady() if( GSopen2 != NULL ) wxGetApp().OpenGsFrame(); + _parent::OnResumeReady(); +} + +void AppCoreThread::OnResumeInThread( bool isSuspended ) +{ + _parent::OnResumeInThread( isSuspended ); + wxCommandEvent evt( pxEVT_CoreThreadStatus ); evt.SetInt( CoreStatus_Resumed ); wxGetApp().AddPendingEvent( evt ); +} - _parent::OnResumeReady(); +void AppCoreThread::OnSuspendInThread() +{ + _parent::OnSuspendInThread(); + + wxCommandEvent evt( pxEVT_CoreThreadStatus ); + evt.SetInt( CoreStatus_Suspended ); + wxGetApp().AddPendingEvent( evt ); } // Called whenever the thread has terminated, for either regular or irregular reasons. @@ -88,9 +114,9 @@ void AppCoreThread::OnCleanupInThread() extern int TranslateGDKtoWXK( u32 keysym ); #endif -void AppCoreThread::StateCheck( bool isCancelable ) +void AppCoreThread::StateCheckInThread( bool isCancelable ) { - _parent::StateCheck( isCancelable ); + _parent::StateCheckInThread( isCancelable ); if( !pxAssert(g_plugins!=NULL) ) return; const keyEvent* ev = PADkeyEvent(); @@ -124,7 +150,7 @@ void AppCoreThread::StateCheck( bool isCancelable ) // suspended. If the thread has mot been suspended, this call will fail *silently*. void AppCoreThread::ApplySettings( const Pcsx2Config& src ) { - if( m_ExecMode != ExecMode_Suspended ) return; + if( m_ExecMode != ExecMode_Closed ) return; if( src == EmuConfig ) return; // Re-entry guard protects against cases where code wants to manually set core settings diff --git a/pcsx2/gui/AppInit.cpp b/pcsx2/gui/AppInit.cpp index fff5325b19..ac2d1e4f82 100644 --- a/pcsx2/gui/AppInit.cpp +++ b/pcsx2/gui/AppInit.cpp @@ -118,6 +118,10 @@ void Pcsx2App::ReadUserModeSettings() AppSaveSettings(); } } + + // force a reset here to unload plugins loaded by the wizard. If we don't do this + // the recompilers might fail to allocate the memory they need to function. + SysReset(); } void Pcsx2App::OnInitCmdLine( wxCmdLineParser& parser ) @@ -203,7 +207,7 @@ typedef void (wxEvtHandler::*pxMessageBoxEventFunction)(pxMessageBoxEvent&); bool Pcsx2App::OnInit() { g_Conf = new AppConfig(); - EnableConsoleLogging(); + EnableAllLogging(); wxInitAllImageHandlers(); if( !wxApp::OnInit() ) return false; @@ -260,7 +264,7 @@ bool Pcsx2App::OnInit() } m_ProgramLogBox = new ConsoleLogFrame( m_MainFrame, L"PCSX2 Program Log", g_Conf->ProgLogBox ); - EnableConsoleLogging(); + EnableAllLogging(); SetTopWindow( m_MainFrame ); // not really needed... SetExitOnFrameDelete( true ); // but being explicit doesn't hurt... @@ -316,6 +320,7 @@ bool Pcsx2App::OnInit() catch( Exception::StartupAborted& ex ) { Console.Notice( ex.FormatDiagnosticMessage() ); + CleanupMess(); return false; } // ---------------------------------------------------------------------------- @@ -347,6 +352,9 @@ void Pcsx2App::CleanupMess() // during the wxApp destructor. -- air // FIXME: performing a wxYield() here may fix that problem. -- air + + while( wxGetLocale() != NULL ) + delete wxGetLocale(); } Pcsx2App::Pcsx2App() : @@ -369,6 +377,9 @@ Pcsx2App::~Pcsx2App() // to happen here in the destructor. CleanupMess(); + + delete wxConfigBase::Set( NULL ); + Console_SetActiveHandler( ConsoleWriter_Null ); if( emuLog != NULL ) @@ -377,6 +388,8 @@ Pcsx2App::~Pcsx2App() emuLog = NULL; } } + + // ------------------------------------------------------------------------------------------ // Using the MSVCRT to track memory leaks: // ------------------------------------------------------------------------------------------ @@ -403,6 +416,6 @@ struct CrtDebugBreak } }; -//CrtDebugBreak breakAt( 157 ); +//CrtDebugBreak breakAt( 20603 ); #endif \ No newline at end of file diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index 6043d45d67..436b20898f 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -293,9 +293,6 @@ int Pcsx2App::OnExit() if( g_Conf ) AppSaveSettings(); - while( wxGetLocale() != NULL ) - delete wxGetLocale(); - return wxApp::OnExit(); } @@ -311,7 +308,7 @@ MainEmuFrame& Pcsx2App::GetMainFrame() const return *m_MainFrame; } -void AppApplySettings( const AppConfig* oldconf ) +void AppApplySettings( const AppConfig* oldconf, bool saveOnSuccess ) { AllowFromMainThreadOnly(); @@ -324,6 +321,8 @@ void AppApplySettings( const AppConfig* oldconf ) g_Conf->EmuOptions.BiosFilename = g_Conf->FullpathToBios(); + RelocateLogfile(); + bool resume = CoreThread.Suspend(); // Update the compression attribute on the Memcards folder. @@ -343,34 +342,44 @@ void AppApplySettings( const AppConfig* oldconf ) } } + sApp.Source_SettingsApplied().Dispatch( 0 ); CoreThread.ApplySettings( g_Conf->EmuOptions ); if( resume ) CoreThread.Resume(); + + if( saveOnSuccess ) + AppSaveSettings(); +} + +static wxFileConfig _dud_config; + +AppIniSaver::AppIniSaver() : + IniSaver( (GetAppConfig() != NULL) ? *GetAppConfig() : _dud_config ) +{ +} + +AppIniLoader::AppIniLoader() : + IniLoader( (GetAppConfig() != NULL) ? *GetAppConfig() : _dud_config ) +{ } void AppLoadSettings() { - wxConfigBase* conf = wxConfigBase::Get( false ); - if( NULL == conf ) return; + if( !AllowFromMainThreadOnly() ) return; - IniLoader loader( *conf ); + AppIniLoader loader; g_Conf->LoadSave( loader ); - - if( HasMainFrame() ) - GetMainFrame().LoadRecentIsoList( *conf ); + wxGetApp().Source_SettingsLoadSave().Dispatch( loader ); } void AppSaveSettings() { - wxConfigBase* conf = wxConfigBase::Get( false ); - if( NULL == conf ) return; + if( !AllowFromMainThreadOnly() ) return; - IniSaver saver( *conf ); + AppIniSaver saver; g_Conf->LoadSave( saver ); - - if( HasMainFrame() ) - GetMainFrame().SaveRecentIsoList( *conf ); + wxGetApp().Source_SettingsLoadSave().Dispatch( saver ); } void Pcsx2App::OpenGsFrame() @@ -413,28 +422,30 @@ void Pcsx2App::OnMainFrameClosed() static int _sysexec_cdvdsrc_type = -1; -static void OnSysExecuteAfterPlugins( const wxCommandEvent& loadevt ) +static void _sendmsg_SysExecute() { - if( !wxGetApp().m_CorePlugins ) return; - wxCommandEvent execevt( pxEVT_SysExecute ); execevt.SetInt( _sysexec_cdvdsrc_type ); wxGetApp().AddPendingEvent( execevt ); } +static void OnSysExecuteAfterPlugins( const wxCommandEvent& loadevt ) +{ + if( (wxTheApp == NULL) || !((Pcsx2App*)wxTheApp)->m_CorePlugins ) return; + _sendmsg_SysExecute(); +} + // Executes the emulator using a saved/existing virtual machine state and currently // configured CDVD source device. void Pcsx2App::SysExecute() { + _sysexec_cdvdsrc_type = -1; if( !m_CorePlugins ) { LoadPluginsPassive( OnSysExecuteAfterPlugins ); return; } - - wxCommandEvent evt( pxEVT_SysExecute ); - evt.SetInt( -1 ); - AddPendingEvent( evt ); + _sendmsg_SysExecute(); } // Executes the specified cdvd source and optional elf file. This command performs a @@ -442,20 +453,18 @@ void Pcsx2App::SysExecute() // sources. void Pcsx2App::SysExecute( CDVD_SourceType cdvdsrc ) { + _sysexec_cdvdsrc_type = (int)cdvdsrc; if( !m_CorePlugins ) { LoadPluginsPassive( OnSysExecuteAfterPlugins ); return; } - - wxCommandEvent evt( pxEVT_SysExecute ); - evt.SetInt( (int)cdvdsrc ); - AddPendingEvent( evt ); + _sendmsg_SysExecute(); } void Pcsx2App::OnSysExecute( wxCommandEvent& evt ) { - if( sys_resume_lock ) + if( sys_resume_lock > 0 ) { Console.WriteLn( "SysExecute: State is locked, ignoring Execute request!" ); return; @@ -465,16 +474,18 @@ void Pcsx2App::OnSysExecute( wxCommandEvent& evt ) // it, because apparently too much stuff is going on and the emulation states are wonky. if( !m_CorePlugins ) return; - if( evt.GetInt() != -1 ) SysReset(); else CoreThread.Suspend(); + if( evt.GetInt() != -1 ) CoreThread.Reset(); else CoreThread.Suspend(); CDVDsys_SetFile( CDVDsrc_Iso, g_Conf->CurrentIso ); if( evt.GetInt() != -1 ) CDVDsys_ChangeSource( (CDVD_SourceType)evt.GetInt() ); CoreThread.Resume(); } +// Full system reset stops the core thread and unloads all core plugins *completely*. void Pcsx2App::SysReset() { CoreThread.Reset(); + m_CorePlugins = NULL; } // Returns true if there is a "valid" virtual machine state from the user's perspective. This diff --git a/pcsx2/gui/ConsoleLogger.cpp b/pcsx2/gui/ConsoleLogger.cpp index 4e00f1cf68..eb277e7159 100644 --- a/pcsx2/gui/ConsoleLogger.cpp +++ b/pcsx2/gui/ConsoleLogger.cpp @@ -521,7 +521,7 @@ void ConsoleLogFrame::CountMessage() wxCommandEvent evt( wxEVT_SemaphoreWait ); GetEventHandler()->AddPendingEvent( evt ); - m_semaphore.Wait(); + m_semaphore.WaitRaw(); } } } @@ -594,12 +594,12 @@ static void __concall ConsoleToFile_Newline() static void __concall ConsoleToFile_DoWrite( const wxString& fmt ) { - _immediate_logger( toUTF8(fmt) ); + _immediate_logger( fmt.ToUTF8() ); } static void __concall ConsoleToFile_DoWriteLn( const wxString& fmt ) { - _immediate_logger( toUTF8(fmt) ); + _immediate_logger( fmt.ToUTF8() ); ConsoleToFile_Newline(); if( emuLog != NULL ) @@ -702,7 +702,7 @@ static const IConsoleWriter ConsoleWriter_WindowAndFile = ConsoleToWindow_ClearColor, }; -void Pcsx2App::EnableConsoleLogging() const +void Pcsx2App::EnableAllLogging() const { if( emuLog ) Console_SetActiveHandler( (m_ProgramLogBox!=NULL) ? (IConsoleWriter&)ConsoleWriter_WindowAndFile : (IConsoleWriter&)ConsoleWriter_File ); diff --git a/pcsx2/gui/Dialogs/ConfirmationDialogs.cpp b/pcsx2/gui/Dialogs/ConfirmationDialogs.cpp new file mode 100644 index 0000000000..fcf4f47a8c --- /dev/null +++ b/pcsx2/gui/Dialogs/ConfirmationDialogs.cpp @@ -0,0 +1,223 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2009 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 "App.h" + +#include "ModalPopups.h" + +using namespace wxHelpers; + +bool ConfButtons::Allows( wxWindowID id ) const +{ + switch( id ) + { + case wxID_OK: return HasOK(); + case wxID_CANCEL: return HasCancel(); + case wxID_APPLY: return HasApply(); + case wxID_YES: return HasYes(); + case wxID_NO: return HasNo(); + case wxID_YESTOALL: return HasYes() && AllowsToAll(); + case wxID_NOTOALL: return HasNo() && AllowsToAll(); + + case wxID_ABORT: return HasAbort(); + case wxID_RETRY: return HasRetry(); + + // [TODO] : maybe add in an Ignore All? + case wxID_IGNORE: return HasIgnore(); + } + return false; +} + +static wxString ResultToString( int result ) +{ + switch( result ) + { + case wxID_OK: return L"ok"; + case wxID_CANCEL: return L"cancel"; + case wxID_APPLY: return L"apply"; + case wxID_YES: return L"yes"; + case wxID_NO: return L"no"; + case wxID_YESTOALL: return L"yestoall"; + case wxID_NOTOALL: return L"notoall"; + + case wxID_ABORT: return L"abort"; + case wxID_RETRY: return L"retry"; + + // [TODO] : maybe add in an Ignore All? + case wxID_IGNORE: return L"ignore"; + } + + return wxEmptyString; +} + +static wxWindowID ParseThatResult( const wxString& src, const ConfButtons& validTypes ) +{ + if( !pxAssert( !src.IsEmpty() ) ) return wxID_ANY; + + static const wxWindowID retvals[] = + { + wxID_OK, wxID_CANCEL, wxID_APPLY, + wxID_YES, wxID_NO, + wxID_YESTOALL, wxID_NOTOALL, + + wxID_ABORT, wxID_RETRY, wxID_IGNORE, + }; + + for( int i=0; iSetPath( L"/PopupDisablers" ); + bool recdef = cfg->IsRecordingDefaults(); + cfg->SetRecordDefaults( false ); + + wxString result = cfg->Read( disablerKey, L"enabled" ); + + cfg->SetRecordDefaults( recdef ); + cfg->SetPath( L"/" ); + + result.LowerCase(); + wxArrayString split; + SplitString( split, result, L"," ); + + // if only one param (no comma!?) then assume the entry is invalid and force a + // re-display of the dialog. + + if( split.Count() > 1 ) + { + result = split[0]; + if( result == L"disabled" || result == L"off" || result == L"no" ) + { + int result = ParseThatResult( split[1], type ); + if( result != wxID_ANY ) return result; + } + } + } + + Dialogs::ExtensibleConfirmation confirmDlg( parent, type, title, msg ); + + if( cfg == NULL ) return confirmDlg.ShowModal(); + + // Add an option that allows the user to disable this popup from showing again. + // (and if the config hasn't been initialized yet, then assume the dialog as non-disablable) + + wxBoxSizer& cboxPad = *new wxBoxSizer( wxHORIZONTAL ); + wxCheckBox& DisablerCtrl( + AddCheckBoxTo( &confirmDlg, cboxPad, _("Do not show this dialog again.")) + ); + + confirmDlg.GetExtensibleSizer().Add( &cboxPad, wxSizerFlags().Centre() ); + + if( type != ConfButtons().OK() ) + DisablerCtrl.SetToolTip(_("Disables this popup and whatever response you select here will be automatically used from now on.")); + else + DisablerCtrl.SetToolTip(_("The popup will not be shown again. This setting can be undone from the settings panels.")); + + confirmDlg.Fit(); + + int modalResult = confirmDlg.ShowModal(); + + wxString cfgResult = ResultToString( modalResult ); + if( DisablerCtrl.IsChecked() && !cfgResult.IsEmpty() ) + { + cfg->SetPath( L"/PopupDisablers" ); + cfg->Write( disablerKey, L"disabled," + cfgResult ); + cfg->SetPath( L"/" ); + } + return modalResult; +} + +Dialogs::ExtensibleConfirmation::ExtensibleConfirmation( wxWindow* parent, const ConfButtons& type, const wxString& title, const wxString& msg ) : + wxDialogWithHelpers( parent, wxID_ANY, title, false ) +, m_ExtensibleSizer( *new wxBoxSizer( wxVERTICAL ) ) +, m_ButtonSizer( *new wxBoxSizer( wxHORIZONTAL ) ) +{ + wxBoxSizer& mainsizer( *new wxBoxSizer(wxVERTICAL) ); + + // Add the message padded some (StdCenter gives us a 5 pt padding). Helps emphasize it a bit. + wxBoxSizer& msgPadSizer( *new wxBoxSizer(wxVERTICAL) ); + AddStaticText( msgPadSizer, msg, wxALIGN_CENTRE, 444 ); + mainsizer.Add( &msgPadSizer, SizerFlags::StdCenter() ); + + mainsizer.Add( &m_ExtensibleSizer, wxSizerFlags().Centre() ); + + // Populate the Button Sizer. + // We prefer this over the built-in wxWidgets ButtonSizer stuff used for other types of + // dialogs because we offer more button types, and we don't want the MSW default behavior + // of right-justified buttons. + + #ifdef __WXGTK__ + // GTK+ / Linux inverts OK/CANCEL order -- cancel / no first, OK / Yes later. >_< + if( type.HasCancel() ) + AddActionButton( wxID_CANCEL ); + + if( type.HasNo() ) + { + AddActionButton( wxID_NO ); + if( type.AllowsToAll() ) AddActionButton( wxID_NOTOALL ); + } + if( type.HasOK() || type.HasYes() ) // Extra space between Affirm and Cancel Actions + m_ButtonSizer.Add(0, 0, 1, wxEXPAND, 0); + #endif + + if( type.HasOK() ) + AddActionButton( wxID_OK ); + + if( type.HasYes() ) + { + AddActionButton( wxID_YES ); + if( type.AllowsToAll() ) + AddActionButton( wxID_YESTOALL ); + } + + #ifndef __WXGTK__ + if( type.HasNo() || type.HasCancel() ) // Extra space between Affirm and Cancel Actions + m_ButtonSizer.Add(0, 0, 1, wxEXPAND, 0); + + if( type.HasNo() ) + { + AddActionButton( wxID_NO ); + if( type.AllowsToAll() ) + AddActionButton( wxID_NOTOALL ); + } + + if( type.HasCancel() ) + AddActionButton( wxID_CANCEL ); + #endif + + mainsizer.Add( &m_ButtonSizer, SizerFlags::StdCenter() ); + + SetSizerAndFit( &mainsizer, true ); + CenterOnScreen(); +} + +void Dialogs::ExtensibleConfirmation::AddActionButton( wxWindowID id ) +{ + m_ButtonSizer.Add( new wxButton( this, id ), SizerFlags::StdButton() )->SetProportion( 6 ); +} + diff --git a/pcsx2/gui/Dialogs/ModalPopups.h b/pcsx2/gui/Dialogs/ModalPopups.h index 347ad986b1..1d09fde662 100644 --- a/pcsx2/gui/Dialogs/ModalPopups.h +++ b/pcsx2/gui/Dialogs/ModalPopups.h @@ -64,6 +64,60 @@ protected: virtual void OnDoubleClicked( wxCommandEvent& evt ); }; +class ConfButtons +{ +protected: + BITFIELD32() + bool + m_OK:1, + m_Cancel:1, + m_Yes:1, + m_No:1, + m_AllowToAll:1, + m_Apply:1, + m_Abort:1, + m_Retry:1, + m_Ignore:1; + BITFIELD_END + +public: + ConfButtons() : bitset( 0 ) { } + + ConfButtons& OK() { m_OK = true; return *this; } + ConfButtons& Cancel() { m_Cancel = true; return *this; } + ConfButtons& Apply() { m_Apply = true; return *this; } + ConfButtons& Yes() { m_Yes = true; return *this; } + ConfButtons& No() { m_No = true; return *this; } + ConfButtons& ToAll() { m_AllowToAll = true; return *this; } + + ConfButtons& Abort() { m_Abort = true; return *this; } + ConfButtons& Retry() { m_Retry = true; return *this; } + ConfButtons& Ignore() { m_Ignore = true; return *this; } + + bool HasOK() const { return m_OK; } + bool HasCancel() const { return m_Cancel; } + bool HasApply() const { return m_Apply; } + bool HasYes() const { return m_Yes; } + bool HasNo() const { return m_No; } + bool AllowsToAll() const{ return m_AllowToAll; } + + bool HasAbort() const { return m_Abort; } + bool HasRetry() const { return m_Retry; } + bool HasIgnore() const { return m_Ignore; } + + bool Allows( wxWindowID id ) const; + + bool operator ==( const ConfButtons& right ) const + { + return OpEqu( bitset ); + } + + bool operator !=( const ConfButtons& right ) const + { + return !OpEqu( bitset ); + } +}; + namespace Dialogs { class AboutBoxDialog: public wxDialogWithHelpers @@ -76,6 +130,7 @@ namespace Dialogs //wxStaticBitmap m_bitmap_logo; wxStaticBitmap m_bitmap_dualshock; }; + class PickUserModeDialog : public wxDialogWithHelpers { @@ -103,5 +158,22 @@ namespace Dialogs void OnOverwrite_Click( wxCommandEvent& evt ); }; + + class ExtensibleConfirmation : public wxDialogWithHelpers + { + protected: + wxBoxSizer& m_ExtensibleSizer; + wxBoxSizer& m_ButtonSizer; + public: + ExtensibleConfirmation( wxWindow* parent, const ConfButtons& type, const wxString& title, const wxString& msg ); + virtual ~ExtensibleConfirmation() throw() {} + + wxBoxSizer& GetExtensibleSizer() const { return m_ExtensibleSizer; } + + protected: + void AddActionButton( wxWindowID id ); + }; + + wxWindowID IssueConfirmation( wxWindow* parent, const wxString& disablerKey, const ConfButtons& type, const wxString& title, const wxString& msg ); } diff --git a/pcsx2/gui/MainFrame.cpp b/pcsx2/gui/MainFrame.cpp index 4692ac2880..b6891427d3 100644 --- a/pcsx2/gui/MainFrame.cpp +++ b/pcsx2/gui/MainFrame.cpp @@ -89,19 +89,14 @@ void MainEmuFrame::UpdateIsoSrcFile() GetMenuBar()->SetLabel( MenuId_Src_Iso, label ); } -void MainEmuFrame::LoadRecentIsoList( wxConfigBase& conf ) +void MainEmuFrame::LoadSaveRecentIsoList( IniInterface& conf ) { - if( m_RecentIsoList ) - m_RecentIsoList->Load( conf ); + if( conf.IsLoading() ) + m_RecentIsoList->Load( conf.GetConfig() ); + else + m_RecentIsoList->Save( conf.GetConfig() ); } -void MainEmuFrame::SaveRecentIsoList( wxConfigBase& conf ) -{ - if( m_RecentIsoList ) - m_RecentIsoList->Load( conf ); -} - - // ------------------------------------------------------------------------ // Video / Audio / Pad "Extensible" Menus // ------------------------------------------------------------------------ @@ -260,13 +255,36 @@ void MainEmuFrame::InitLogBoxPosition( AppConfig::ConsoleLogOptions& conf ) } } -static void OnCoreThreadStatusChanged( void* obj, const wxCommandEvent& evt ) +void MainEmuFrame::OnCoreThreadStatusChanged( void* obj, const wxCommandEvent& evt ) +{ + if( obj == NULL ) return; + MainEmuFrame* mframe = (MainEmuFrame*)obj; + mframe->ApplyCoreStatus(); +} + +void MainEmuFrame::OnCorePluginStatusChanged( void* obj, const wxCommandEvent& evt ) +{ + if( obj == NULL ) return; + MainEmuFrame* mframe = (MainEmuFrame*)obj; + mframe->ApplyCoreStatus(); +} + +void MainEmuFrame::OnSettingsApplied( void* obj, const int& evt ) { if( obj == NULL ) return; MainEmuFrame* mframe = (MainEmuFrame*)obj; mframe->ApplySettings(); } +void MainEmuFrame::OnSettingsLoadSave( void* obj, const IniInterface& evt ) +{ + if( obj == NULL ) return; + MainEmuFrame* mframe = (MainEmuFrame*)obj; + + // FIXME: Evil const cast hack! + mframe->LoadSaveRecentIsoList( const_cast(evt) ); +} + // ------------------------------------------------------------------------ MainEmuFrame::MainEmuFrame(wxWindow* parent, const wxString& title): wxFrame(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE & ~(wxMAXIMIZE_BOX | wxRESIZE_BORDER) ), @@ -295,7 +313,10 @@ MainEmuFrame::MainEmuFrame(wxWindow* parent, const wxString& title): m_MenuItem_Console( *new wxMenuItem( &m_menuMisc, MenuId_Console, L"Show Console", wxEmptyString, wxITEM_CHECK ) ), - m_Listener_CoreThreadStatus( wxGetApp().Source_CoreThreadStatus(), CmdEvt_Listener( this, OnCoreThreadStatusChanged ) ) + m_Listener_CoreThreadStatus( wxGetApp().Source_CoreThreadStatus(), CmdEvt_Listener( this, OnCoreThreadStatusChanged ) ), + m_Listener_CorePluginStatus( wxGetApp().Source_CorePluginStatus(), CmdEvt_Listener( this, OnCorePluginStatusChanged ) ), + m_Listener_SettingsApplied( wxGetApp().Source_SettingsApplied(), EventListener( this, OnSettingsApplied ) ), + m_Listener_SettingsLoadSave( wxGetApp().Source_SettingsLoadSave(), EventListener( this, OnSettingsLoadSave ) ) { // ------------------------------------------------------------------------ // Initial menubar setup. This needs to be done first so that the menu bar's visible size @@ -471,8 +492,8 @@ MainEmuFrame::~MainEmuFrame() throw() { try { - if( m_RecentIsoList ) - m_RecentIsoList->Save( *wxConfigBase::Get( false ) ); + if( m_RecentIsoList && GetAppConfig() ) + m_RecentIsoList->Save( *GetAppConfig() ); } DESTRUCTOR_CATCHALL } @@ -485,7 +506,7 @@ void MainEmuFrame::ReloadRecentLists() // the recent file count has been changed, and it's a helluva lot easier than trying // to make a clone copy of this complex object. ;) - wxConfigBase* cfg = wxConfigBase::Get( false ); + wxConfigBase* cfg = GetAppConfig(); pxAssert( cfg != NULL ); if( m_RecentIsoList ) @@ -495,6 +516,14 @@ void MainEmuFrame::ReloadRecentLists() cfg->Flush(); } +void MainEmuFrame::ApplyCoreStatus() +{ + GetMenuBar()->Enable( MenuId_Sys_SuspendResume, SysHasValidState() ); + GetMenuBar()->Enable( MenuId_Sys_Reset, SysHasValidState() || (g_plugins!=NULL) ); + + GetMenuBar()->SetLabel( MenuId_Sys_SuspendResume, CoreThread.IsOpen() ? _("Suspend") : _("Resume") ); +} + void MainEmuFrame::ApplySettings() { GetMenuBar()->Check( MenuId_SkipBiosToggle, g_Conf->EmuOptions.SkipBiosSplash ); @@ -502,10 +531,6 @@ void MainEmuFrame::ApplySettings() GetMenuBar()->Check( MenuId_Config_Multitap0Toggle, g_Conf->EmuOptions.MultitapPort0_Enabled ); GetMenuBar()->Check( MenuId_Config_Multitap1Toggle, g_Conf->EmuOptions.MultitapPort1_Enabled ); - GetMenuBar()->Enable( MenuId_Sys_SuspendResume, SysHasValidState() ); - - GetMenuBar()->SetLabel( MenuId_Sys_SuspendResume, CoreThread.IsExecMode_Running() ? _("Suspend") :_("Resume") ); - if( m_RecentIsoList ) { if( m_RecentIsoList->GetMaxFiles() != g_Conf->RecentFileCount ) diff --git a/pcsx2/gui/MainFrame.h b/pcsx2/gui/MainFrame.h index 32d4902d8a..dab9672435 100644 --- a/pcsx2/gui/MainFrame.h +++ b/pcsx2/gui/MainFrame.h @@ -64,7 +64,10 @@ protected: wxMenuItem& m_MenuItem_Console; - CmdEvt_ListenerBinding m_Listener_CoreThreadStatus; + CmdEvt_ListenerBinding m_Listener_CoreThreadStatus; + CmdEvt_ListenerBinding m_Listener_CorePluginStatus; + EventListenerBinding m_Listener_SettingsApplied; + EventListenerBinding m_Listener_SettingsLoadSave; // ------------------------------------------------------------------------ // MainEmuFrame Constructors and Member Methods @@ -79,13 +82,18 @@ public: bool IsPaused() const { return GetMenuBar()->IsChecked( MenuId_Sys_SuspendResume ); } void UpdateIsoSrcFile(); void UpdateIsoSrcSelection(); - void ApplySettings(); void ReloadRecentLists(); - void LoadRecentIsoList( wxConfigBase& conf ); - void SaveRecentIsoList( wxConfigBase& conf ); - protected: + static void OnCoreThreadStatusChanged( void* obj, const wxCommandEvent& evt ); + static void OnCorePluginStatusChanged( void* obj, const wxCommandEvent& evt ); + static void OnSettingsApplied( void* obj, const int& evt ); + static void MainEmuFrame::OnSettingsLoadSave( void* obj, const IniInterface& evt ); + + void LoadSaveRecentIsoList( IniInterface& conf ); + void ApplySettings(); + void ApplyCoreStatus(); + void InitLogBoxPosition( AppConfig::ConsoleLogOptions& conf ); void OnCloseWindow(wxCloseEvent& evt); diff --git a/pcsx2/gui/MainMenuClicks.cpp b/pcsx2/gui/MainMenuClicks.cpp index 3381879a7b..c38b298b36 100644 --- a/pcsx2/gui/MainMenuClicks.cpp +++ b/pcsx2/gui/MainMenuClicks.cpp @@ -27,18 +27,12 @@ using namespace Dialogs; void MainEmuFrame::Menu_ConfigSettings_Click(wxCommandEvent &event) { - if( Dialogs::ConfigurationDialog( this ).ShowModal() ) - { - AppSaveSettings(); - } + Dialogs::ConfigurationDialog( this ).ShowModal(); } void MainEmuFrame::Menu_SelectBios_Click(wxCommandEvent &event) { - if( Dialogs::BiosSelectorDialog( this ).ShowModal() ) - { - AppSaveSettings(); - } + Dialogs::BiosSelectorDialog( this ).ShowModal(); } void MainEmuFrame::Menu_CdvdSource_Click( wxCommandEvent &event ) @@ -150,10 +144,11 @@ void MainEmuFrame::Menu_SkipBiosToggle_Click( wxCommandEvent &event ) { g_Conf->EmuOptions.SkipBiosSplash = GetMenuBar()->IsChecked( MenuId_SkipBiosToggle ); - wxConfigBase* conf = wxConfigBase::Get( false ); - if( NULL == conf ) return; - IniSaver saver( *conf ); - g_Conf->EmuOptions.LoadSave( saver ); + if( wxConfigBase* conf = GetAppConfig() ) + { + IniSaver saver( *conf ); + g_Conf->EmuOptions.LoadSave( saver ); + } } void MainEmuFrame::Menu_OpenELF_Click(wxCommandEvent &event) @@ -191,8 +186,17 @@ void MainEmuFrame::Menu_SuspendResume_Click(wxCommandEvent &event) { if( !SysHasValidState() ) return; - if( !CoreThread.Suspend() ) - CoreThread.Resume(); + // Note: We manually update the menu here, even though it'll be updated again + // when the thread "officially" suspends or resumes (via listener callback), because + // the callback is tied to the actual thread status + + if( CoreThread.Suspend() ) + GetMenuBar()->SetLabel( MenuId_Sys_SuspendResume, _("Resume") ); + else + { + sApp.SysExecute(); + GetMenuBar()->SetLabel( MenuId_Sys_SuspendResume, _("Suspend") ); + } } void MainEmuFrame::Menu_SysReset_Click(wxCommandEvent &event) @@ -204,6 +208,8 @@ void MainEmuFrame::Menu_SysReset_Click(wxCommandEvent &event) if( resume ) sApp.SysExecute(); + + GetMenuBar()->Enable( MenuId_Sys_Reset, resume ); } void MainEmuFrame::Menu_ConfigPlugin_Click(wxCommandEvent &event) @@ -229,9 +235,10 @@ void MainEmuFrame::Menu_ConfigPlugin_Click(wxCommandEvent &event) LoadPluginsImmediate(); if( g_plugins == NULL ) return; + bool resume = CoreThread.Suspend(); wxWindowDisabler disabler; - //ScopedWindowDisable disabler( this ); g_plugins->Configure( pid ); + if( resume ) CoreThread.Resume(); } void MainEmuFrame::Menu_Debug_Open_Click(wxCommandEvent &event) diff --git a/pcsx2/gui/Panels/ConfigurationPanels.h b/pcsx2/gui/Panels/ConfigurationPanels.h index e22cd8256f..7a18125f05 100644 --- a/pcsx2/gui/Panels/ConfigurationPanels.h +++ b/pcsx2/gui/Panels/ConfigurationPanels.h @@ -51,22 +51,27 @@ namespace Exception // class CannotApplySettings : public BaseException { + public: + bool IsVerbose; + protected: Panels::BaseApplicableConfigPanel* m_Panel; public: DEFINE_EXCEPTION_COPYTORS( CannotApplySettings ) - explicit CannotApplySettings( Panels::BaseApplicableConfigPanel* thispanel, const char* msg=wxLt("Cannot apply new settings, one of the settings is invalid.") ) + explicit CannotApplySettings( Panels::BaseApplicableConfigPanel* thispanel, const char* msg=wxLt("Cannot apply new settings, one of the settings is invalid."), bool isVerbose = true ) { BaseException::InitBaseEx( msg ); m_Panel = thispanel; + IsVerbose = isVerbose; } explicit CannotApplySettings( Panels::BaseApplicableConfigPanel* thispanel, const wxString& msg_eng, const wxString& msg_xlt ) { BaseException::InitBaseEx( msg_eng, msg_xlt ); m_Panel = thispanel; + IsVerbose = true; } Panels::BaseApplicableConfigPanel* GetPanel() diff --git a/pcsx2/gui/Panels/MiscPanelStuff.cpp b/pcsx2/gui/Panels/MiscPanelStuff.cpp index d09fc90c29..4df00305a8 100644 --- a/pcsx2/gui/Panels/MiscPanelStuff.cpp +++ b/pcsx2/gui/Panels/MiscPanelStuff.cpp @@ -85,8 +85,7 @@ bool Panels::StaticApplyState::ApplyPage( int pageid, bool saveOnSuccess ) // Note: apply first, then save -- in case the apply fails. - AppApplySettings( &confcopy ); - if( saveOnSuccess ) AppSaveSettings(); + AppApplySettings( &confcopy, saveOnSuccess ); } catch( Exception::CannotApplySettings& ex ) { @@ -95,10 +94,13 @@ bool Panels::StaticApplyState::ApplyPage( int pageid, bool saveOnSuccess ) UseDefaultSettingsFolder = oldUseDefSet; *g_Conf = confcopy; - wxMessageBox( ex.FormatDisplayMessage(), _("Cannot apply settings...") ); + if( ex.IsVerbose ) + { + wxMessageBox( ex.FormatDisplayMessage(), _("Cannot apply settings...") ); - if( ex.GetPanel() != NULL ) - ex.GetPanel()->SetFocusToMe(); + if( ex.GetPanel() != NULL ) + ex.GetPanel()->SetFocusToMe(); + } retval = false; } @@ -170,7 +172,7 @@ Panels::LanguageSelectionPanel::LanguageSelectionPanel( wxWindow& parent, int id int size = m_langs.size(); int cursel = 0; - wxString* compiled = new wxString[size]; + ScopedArray compiled( size ); //, L"Compiled Language Names" ); wxString configLangName( wxLocale::GetLanguageName( wxLANGUAGE_DEFAULT ) ); for( int i=0; iSetSelection( cursel ); wxBoxSizer& s_lang = *new wxBoxSizer( wxHORIZONTAL ); diff --git a/pcsx2/gui/Panels/PluginSelectorPanel.cpp b/pcsx2/gui/Panels/PluginSelectorPanel.cpp index 9b35842c83..d506903f4b 100644 --- a/pcsx2/gui/Panels/PluginSelectorPanel.cpp +++ b/pcsx2/gui/Panels/PluginSelectorPanel.cpp @@ -18,6 +18,7 @@ #include "Plugins.h" #include "Utilities/ScopedPtr.h" #include "ConfigurationPanels.h" +#include "Dialogs/ModalPopups.h" #include #include @@ -276,6 +277,8 @@ void Panels::PluginSelectorPanel::Apply() // user never entered plugins panel? Skip application since combo boxes are invalid/uninitialized. if( !m_FileList ) return; + AppConfig curconf( *g_Conf ); + for( int i=0; iFullpathTo( pi->id ) != g_Conf->FullpathTo( pi->id ) ) + if( g_Conf->FullpathTo( pi->id ) != curconf.FullpathTo( pi->id ) ) break; } while( ++pi, pi->shortname != NULL ); @@ -313,6 +316,21 @@ void Panels::PluginSelectorPanel::Apply() if( CoreThread.IsRunning() ) { // [TODO] : Post notice that this shuts down existing emulation, and may not safely recover. + int result = Dialogs::IssueConfirmation( this, L"PluginSelector:ConfirmShutdown", ConfButtons().OK().Cancel(), + + _("Shutdown PS2 virtual machine?"), + + pxE( ".Popup:PluginSelector:ConfirmShutdown", + L"Warning! Changing plugins requires a complete shutdown and reset of the PS2 virtual machine. " + L"PCSX2 will attempt to save and restore the state, but if the newly selected plugins are " + L"incompatible the recovery may fail, and current progress will be lost." + L"\n\n" + L"Are you sure you want to apply settings now?" + ) + ); + + if( result == wxID_CANCEL ) + throw Exception::CannotApplySettings( this, "Cannot apply settings: canceled by user because plugins changed while the emulation state was active.", false ); } sApp.SysReset(); @@ -358,12 +376,17 @@ void Panels::PluginSelectorPanel::DoRefresh() return; } - // Disable all controls until enumeration is complete. + // Disable all controls until enumeration is complete + m_ComponentBoxes.Hide(); + + // (including next button if it's a Wizard) + wxWindow* forwardButton = GetGrandParent()->FindWindow( wxID_FORWARD ); + if( forwardButton != NULL ) + forwardButton->Disable(); + // Show status bar for plugin enumeration. Use a pending event so that // the window's size can get initialized properly before trying to custom- // fit the status panel to it. - - m_ComponentBoxes.Hide(); wxCommandEvent evt( pxEVT_ShowStatusBar ); GetEventHandler()->AddPendingEvent( evt ); @@ -415,8 +438,10 @@ void Panels::PluginSelectorPanel::OnConfigure_Clicked( wxCommandEvent& evt ) wxDynamicLibrary dynlib( (*m_FileList)[(int)m_ComponentBoxes.Get(pid).GetClientData(sel)] ); if( PluginConfigureFnptr configfunc = (PluginConfigureFnptr)dynlib.GetSymbol( tbl_PluginInfo[pid].GetShortname() + L"configure" ) ) { + bool resume = CoreThread.Suspend(); wxWindowDisabler disabler; configfunc(); + if( resume ) CoreThread.Resume(); } } @@ -447,6 +472,10 @@ void Panels::PluginSelectorPanel::OnEnumComplete( wxCommandEvent& evt ) m_ComponentBoxes.Show(); m_StatusPanel.Hide(); m_StatusPanel.Reset(); + + wxWindow* forwardButton = GetGrandParent()->FindWindow( wxID_FORWARD ); + if( forwardButton != NULL ) + forwardButton->Enable(); } diff --git a/pcsx2/windows/VCprojects/pcsx2_2008.vcproj b/pcsx2/windows/VCprojects/pcsx2_2008.vcproj index f6d74a73fd..4dd9cd981a 100644 --- a/pcsx2/windows/VCprojects/pcsx2_2008.vcproj +++ b/pcsx2/windows/VCprojects/pcsx2_2008.vcproj @@ -1816,10 +1816,6 @@ RelativePath="..\..\gui\IniInterface.cpp" > - - @@ -1871,6 +1867,10 @@ RelativePath="..\..\gui\Dialogs\ConfigurationDialog.h" > + + diff --git a/pcsx2_suite_2008.sln b/pcsx2_suite_2008.sln index f95619565c..aa696e6bf0 100644 --- a/pcsx2_suite_2008.sln +++ b/pcsx2_suite_2008.sln @@ -123,7 +123,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "wx", "wx", "{62BF822E-6A12-49A8-BE8C-C55A9BCA24DA}" ProjectSection(SolutionItems) = preProject common\include\wx\folderdesc.txt = common\include\wx\folderdesc.txt - common\include\wx\scopedptr.h = common\include\wx\scopedptr.h EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bin2cpp", "tools\bin2cpp\bin2c.vcproj", "{677B7D11-D5E1-40B3-88B1-9A4DF83D2213}"