diff --git a/common/build/Utilities/utilities.vcproj b/common/build/Utilities/utilities.vcproj index 5aba44fe50..3fcfaea469 100644 --- a/common/build/Utilities/utilities.vcproj +++ b/common/build/Utilities/utilities.vcproj @@ -429,6 +429,10 @@ RelativePath="..\..\include\intrin_x86.h" > + + diff --git a/common/include/PluginCallbacks.h b/common/include/PluginCallbacks.h index 7f4a0d24ed..f6e2e85af8 100644 --- a/common/include/PluginCallbacks.h +++ b/common/include/PluginCallbacks.h @@ -191,7 +191,7 @@ typedef struct _PS2E_SessionInfo // Sony's assigned serial number, valid only for CD/CDVD games (ASCII-Z string). // Ex: SLUS-2932 (if the running app is not a sony-registered game, the serial // will be a zero length string). - const char Serial[16]; + char Serial[16]; } PS2E_SessionInfo; diff --git a/common/include/Utilities/Assertions.h b/common/include/Utilities/Assertions.h index 4abe4dd93d..d094396512 100644 --- a/common/include/Utilities/Assertions.h +++ b/common/include/Utilities/Assertions.h @@ -45,7 +45,7 @@ #if defined(PCSX2_DEBUG) # define pxAssertMsg(cond, msg) ( (!!(cond)) || \ - (pxOnAssert(__TFILE__, __LINE__, __WXFUNCTION__, _T(#cond), msg), !!(cond)) ) + (pxOnAssert(__TFILE__, __LINE__, __WXFUNCTION__, _T(#cond), msg), likely(cond)) ) # define pxAssertDev(cond,msg) pxAssertMsg(cond, msg) @@ -57,26 +57,28 @@ // Devel builds use __assume for standard assertions and call pxOnAssertDevel // for AssertDev brand assertions (which typically throws a LogicError exception). -# define pxAssertMsg(cond, msg) (!!(cond)) +# define pxAssertMsg(cond, msg) (__assume(cond), likely(cond)) # define pxAssertDev(cond, msg) ( (!!(cond)) || \ - (pxOnAssert(__TFILE__, __LINE__, __WXFUNCTION__, _T(#cond), msg), !!(cond)) ) + (pxOnAssert(__TFILE__, __LINE__, __WXFUNCTION__, _T(#cond), msg), likely(cond)) ) -# define pxFail(msg) __assume(false) +# define pxFail(msg) (__assume(false), false) # define pxFailDev(msg ) pxAssertDev(false, msg) #else - // Release Builds just use __assume as an optimization, and always return 'true' - // indicating the assertion check succeeded (no actual check is performed). + // Release Builds just use __assume as an optimization, and return the conditional + // as a result (if . -# define pxAssertMsg(cond, msg) (__assume(cond), true) -# define pxAssertDev(cond, msg) (__assume(cond), true) -# define pxFail(msg) (__assume(false), true) -# define pxFailDev(msg) (__assume(false), true) +# define pxAssertMsg(cond, msg) (__assume(cond), likely(cond)) +# define pxAssertDev(cond, msg) (__assume(cond), likely(cond)) +# define pxFail(msg) (__assume(false), false) +# define pxFailDev(msg) (__assume(false), false) #endif +__cold + #define pxAssert(cond) pxAssertMsg(cond, (wxChar*)NULL) extern void pxOnAssert( const wxChar* file, int line, const char* func, const wxChar* cond, const wxChar* msg); diff --git a/common/include/Utilities/Exceptions.h b/common/include/Utilities/Exceptions.h index 1762e1fe93..0790ea614b 100644 --- a/common/include/Utilities/Exceptions.h +++ b/common/include/Utilities/Exceptions.h @@ -16,6 +16,7 @@ #pragma once #include "Dependencies.h" +#include "StringHelpers.h" // -------------------------------------------------------------------------------------- // DESTRUCTOR_CATCHALL - safe destructor helper @@ -133,7 +134,7 @@ namespace Exception // #define DEFINE_EXCEPTION_COPYTORS( classname ) \ virtual ~classname() throw() {} \ - virtual void Rethrow() const { throw classname( *this ); } \ + virtual void Rethrow() const { throw *this; } \ virtual BaseException* Clone() const { return new classname( *this ); } #define DEFINE_RUNTIME_EXCEPTION( classname, defmsg ) \ @@ -149,11 +150,13 @@ namespace Exception explicit classname( const wxString& msg_eng ) { BaseException::InitBaseEx( msg_eng, wxEmptyString ); } // --------------------------------------------------------------------------------------- - // Generalized Exceptions: RuntimeError / LogicError / ObjectIsNull + // RuntimeError / LogicError - Generalized Exceptions // --------------------------------------------------------------------------------------- class RuntimeError : public virtual BaseException { + public: + bool IsSilent; public: DEFINE_RUNTIME_EXCEPTION( RuntimeError, wxLt("An unhandled runtime error has occurred, somewhere in the depths of Pcsx2's cluttered brain-matter.") ) }; @@ -166,7 +169,38 @@ namespace Exception DEFINE_LOGIC_EXCEPTION( LogicError, wxLt("An unhandled logic error has occurred.") ) }; - class ObjectIsNull : public virtual RuntimeError + // -------------------------------------------------------------------------------------- + // CancelAppEvent - Exception for canceling an event in a non-verbose fashion + // -------------------------------------------------------------------------------------- + // Typically the PCSX2 interface issues popup dialogs for runtime errors. This exception + // instead issues a "silent" cancelation that is handled by the app gracefully (generates + // log, and resumes messages queue processing). + // + // I chose to have this exception derive from RuntimeError, since if one is thrown from outside + // an App message loop we'll still want it to be handled in a reasonably graceful manner. + class CancelEvent : public virtual RuntimeError + { + public: + DEFINE_EXCEPTION_COPYTORS( CancelEvent ) + + explicit CancelEvent( const char* logmsg ) + { + m_message_diag = fromUTF8( logmsg ); + // overridden message formatters only use the diagnostic version... + } + + explicit CancelEvent( const wxString& logmsg=L"No reason given." ) + { + m_message_diag = logmsg; + // overridden message formatters only use the diagnostic version... + } + + virtual wxString FormatDisplayMessage() const; + virtual wxString FormatDiagnosticMessage() const; + }; + + // -------------------------------------------------------------------------------------- + class ObjectIsNull : public virtual CancelEvent { public: wxString ObjectName; @@ -175,17 +209,16 @@ namespace Exception explicit ObjectIsNull( const char* objname="unspecified" ) { - m_message_diag = wxString::FromUTF8( objname ); + m_message_diag = fromUTF8( objname ); // overridden message formatters only use the diagnostic version... } - virtual wxString FormatDisplayMessage() const; virtual wxString FormatDiagnosticMessage() const; }; // --------------------------------------------------------------------------------------- - // OutOfMemory / InvalidOperation / InvalidArgument / IndexBoundsFault / ParseError + // OutOfMemory / InvalidOperation / InvalidArgument / IndexBoundsFault / ParseError // --------------------------------------------------------------------------------------- class OutOfMemory : public virtual RuntimeError @@ -282,17 +315,17 @@ namespace Exception explicit classname( const char* objname, const char* msg=defmsg ) \ { \ BaseException::InitBaseEx( msg ); \ - StreamName = wxString::FromUTF8( objname ); \ + StreamName = fromUTF8( objname ); \ } \ explicit classname( const char* objname, const wxString& msg_eng, const wxString& msg_xlt ) \ { \ BaseException::InitBaseEx( msg_eng, msg_xlt ); \ - StreamName = wxString::FromUTF8( objname ); \ + StreamName = fromUTF8( objname ); \ } \ explicit classname( const char* objname, const wxString& msg_eng ) \ { \ BaseException::InitBaseEx( msg_eng, msg_eng ); \ - StreamName = wxString::FromUTF8( objname ); \ + StreamName = fromUTF8( objname ); \ } \ explicit classname( const wxString& objname, const wxString& msg_eng ) \ { \ diff --git a/common/include/Utilities/Listeners.h b/common/include/Utilities/Listeners.h new file mode 100644 index 0000000000..e1ee2b4ed9 --- /dev/null +++ b/common/include/Utilities/Listeners.h @@ -0,0 +1,204 @@ +/* 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 . + */ + +#pragma once + +#include + +template< typename EvtType > +struct EventListener +{ + typedef void FuncType( void* object, const EvtType& evt ); + + void* object; + FuncType* OnEvent; + + EventListener( FuncType* fnptr ) + { + object = NULL; + OnEvent = fnptr; + } + + EventListener( void* objhandle, FuncType* fnptr ) + { + object = objhandle; + OnEvent = fnptr; + } + + bool operator ==( const EventListener& right ) const + { + return (object == right.object) && (OnEvent == right.OnEvent); + } + + bool operator !=( const EventListener& right ) const + { + return this->operator ==(right); + } +}; + +template< typename EvtType > +class EventSource +{ +public: + typedef typename EventListener ListenerType; + typedef typename std::list< ListenerType > ListenerList; + typedef typename ListenerList::const_iterator Handle; + +protected: + ListenerList m_listeners; + +public: + virtual ~EventSource() throw() {} + + virtual void Remove( const ListenerType& listener ) + { + m_listeners.remove( listener ); + } + + virtual void Remove( const Handle& listenerHandle ) + { + m_listeners.erase( listenerHandle ); + } + + virtual Handle AddFast( const ListenerType& listener ) + { + m_listeners.push_front( listener ); + return m_listeners.begin(); + } + + // Checks for duplicates before adding the event. + virtual inline void Add( const ListenerType& listener ); + virtual inline void RemoveObject( const void* object ); + + Handle Add( void* objhandle, typename ListenerType::FuncType* fnptr ) + { + return Add( Handle( objhandle, fnptr ) ); + } + + void Remove( void* objhandle, typename ListenerType::FuncType* fnptr ) + { + Remove( Handle( objhandle, fnptr ) ); + } + + void Dispatch( const wxCommandEvent& evt ) const + { + Handle iter = m_listeners.begin(); + while( iter != m_listeners.end() ) + { + try + { + iter->OnEvent( iter->object, evt ); + } + catch( Exception::RuntimeError& ex ) + { + Console::Error( L"Ignoring runtime error thrown from event listener: " + ex.FormatDiagnosticMessage() ); + } + catch( BaseException& ex ) + { + } + } + } +}; + +// -------------------------------------------------------------------------------------- +// EventListenerBinding +// -------------------------------------------------------------------------------------- +// Encapsulated event listener binding, provides the "benefits" of object unwinding. +// +template< typename EvtType = wxCommandEvent > +class EventListenerBinding +{ +public: + typedef typename EventSource::ListenerType ListenerHandle; + typedef typename EventSource::Handle ConstIterator; + +protected: + EventSource& m_source; + const ListenerHandle m_listener; + ConstIterator m_iter; + bool m_attached; + +public: + EventListenerBinding( EventSource& source, const ListenerHandle& listener, bool autoAttach=true ) : + m_source( source ) + , m_listener( listener ) + , m_attached( false ) + { + // If you want to assert on null pointers, you'll need to do the check yourself. There's + // too many cases where silently ignoring null pointers is the desired behavior. + //if( !pxAssertDev( listener.OnEvent != NULL, "NULL listener callback function." ) ) return; + if( autoAttach ) Attach(); + } + + virtual ~EventListenerBinding() throw() + { + Detach(); + } + + void Detach() + { + if( !m_attached ) return; + m_source.Remove( m_iter ); + m_attached = false; + } + + void Attach() + { + if( m_attached || (m_listener.OnEvent == NULL) ) return; + m_iter = m_source.AddFast( m_listener ); + m_attached = true; + } +}; + +typedef EventSource CmdEvt_Source; +typedef EventListener CmdEvt_Listener; +typedef EventListenerBinding CmdEvt_ListenerBinding; + + +// Checks for duplicates before adding the event. +template< typename EvtType > +void EventSource::Add( const ListenerType& listener ) +{ + if( !pxAssertDev( listener.OnEvent != NULL, "NULL listener callback function." ) ) return; + + Handle iter = m_listeners.begin(); + while( iter != m_listeners.end() ) + { + if( *iter == listener ) return; + ++iter; + } + AddFast( listener ); +} + +// removes all listeners which reference the given object. Use for assuring object deletion. +template< typename EvtType > +void EventSource::RemoveObject( const void* object ) +{ + class PredicatesAreTheThingsOfNightmares + { + protected: + const void* const m_object_match; + + public: + PredicatesAreTheThingsOfNightmares( const void* objmatch ) : m_object_match( objmatch ) { } + + bool operator()( const ListenerType& src ) + { + return src.object == m_object_match; + } + }; + m_listeners.remove_if( PredicatesAreTheThingsOfNightmares( object ) ); +} + diff --git a/common/include/Utilities/RedtapeWindows.h b/common/include/Utilities/RedtapeWindows.h index 2551a1a1cd..aa3cb1b866 100644 --- a/common/include/Utilities/RedtapeWindows.h +++ b/common/include/Utilities/RedtapeWindows.h @@ -15,6 +15,10 @@ #pragma once +#ifdef __WXMSW__ +# include +#else + ////////////////////////////////////////////////////////////////////////////////////////// // Windows Redtape! No windows.h should be included without it! // @@ -40,3 +44,4 @@ #undef max #endif +#endif diff --git a/common/include/Utilities/Threading.h b/common/include/Utilities/Threading.h index 08e3473cdc..59c67314cc 100644 --- a/common/include/Utilities/Threading.h +++ b/common/include/Utilities/Threading.h @@ -15,25 +15,14 @@ #pragma once +#include #include // EBUSY #include -#include #include "Pcsx2Defs.h" #include "ScopedPtr.h" -namespace Exception -{ - ////////////////////////////////////////////////////////////////////////////////////////// - // Thread termination exception, used to quickly terminate threads from anywhere in the - // thread's call stack. This exception is handled by the PCSX2 PersistentThread class. Threads - // not derived from that class will not handle this exception. - // - class ThreadTermination - { - }; -} - +#undef Yield // release th burden of windows.h global namespace spam. class wxTimeSpan; namespace Threading @@ -57,6 +46,38 @@ namespace Threading }; #endif + // -------------------------------------------------------------------------------------- + // NonblockingMutex + // -------------------------------------------------------------------------------------- + // This is a very simple non-blocking mutex, which behaves similarly to pthread_mutex's + // trylock(), but without any of the extra overhead needed to set up a structure capable + // of blocking waits. It basically optimizes to a single InterlockedExchange. + // + // Simple use: if TryLock() returns false, the Bool is already interlocked by another thread. + // If TryLock() returns true, you've locked the object and are *responsible* for unlocking + // it later. + // + class NonblockingMutex + { + protected: + volatile long val; + + public: + NonblockingMutex() : val( false ) {} + virtual ~NonblockingMutex() throw() {}; + + bool TryLock() throw() + { + return !_InterlockedExchange( &val, true ); + } + + bool IsLocked() + { return !!val; } + + void Release() + { val = false; } + }; + struct Semaphore { sem_t sema; @@ -166,7 +187,7 @@ namespace Threading pthread_t m_thread; Semaphore m_sem_event; // general wait event that's needed by most threads. Semaphore m_sem_finished; // used for canceling and closing threads in a deadlock-safe manner - MutexLock m_lock_start; // used to lock the Start() code from starting simutaneous threads accidentally. + MutexLock m_lock_start; // used to lock the Start() code from starting simultaneous threads accidentally. volatile long m_detached; // a boolean value which indicates if the m_thread handle is valid volatile long m_running; // set true by Start(), and set false by Cancel(), Block(), etc. @@ -190,27 +211,49 @@ namespace Threading bool IsSelf() const; wxString GetName() const; - void _ThreadCleanup(); - protected: - // Extending classes should always implement your own OnStart(), which is called by // Start() once necessary locks have been obtained. Do not override Start() directly // unless you're really sure that's what you need to do. ;) virtual void OnStart()=0; virtual void OnThreadCleanup()=0; - void DoSetThreadName( const wxString& name ); - void DoSetThreadName( __unused const char* name ); - void _internal_execute(); - - // Used to dispatch the thread callback function. - // (handles some thread cleanup on Win32, and is basically a typecast - // on linux). - static void* _internal_callback( void* func ); - // Implemented by derived class to handle threading actions! virtual void ExecuteTask()=0; + + // Inserts a thread cancellation point. If the thread has received a cancel request, this + // function will throw an SEH exception designed to exit the thread (so make sure to use C++ + // object encapsulation for anything that could leak resources, to ensure object unwinding + // and cleanup, or use the DoThreadCleanup() override to perform resource cleanup). + void TestCancel(); + + // Yields this thread to other threads and checks for cancellation. A sleeping thread should + // always test for cancellation, however if you really don't want to, you can use Threading::Sleep() + // or better yet, disable cancellation of the thread completely with DisableCancellation(). + // + // Parameters: + // ms - 'minimum' yield time in milliseconds (rough -- typically yields are longer by 1-5ms + // depending on operating system/platform). If ms is 0 or unspecified, then a single + // timeslice is yielded to other contending threads. If no threads are contending for + // time when ms==0, then no yield is done, but cancellation is still tested. + void Yield( int ms = 0 ) + { + pxAssert( IsSelf() ); + Threading::Sleep( ms ); + TestCancel(); + } + + // ---------------------------------------------------------------------------- + // Section of methods for internal use only. + + void _DoSetThreadName( const wxString& name ); + void _DoSetThreadName( __unused const char* name ); + void _internal_execute(); + void _try_virtual_invoke( void (PersistentThread::*method)() ); + void _ThreadCleanup(); + + static void* _internal_callback( void* func ); + static void _pt_callback_cleanup( void* handle ); }; ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/common/include/Utilities/win_memzero.h b/common/include/Utilities/win_memzero.h index 21dcc274a6..3c26316b73 100644 --- a/common/include/Utilities/win_memzero.h +++ b/common/include/Utilities/win_memzero.h @@ -15,6 +15,10 @@ #pragma once +#ifdef _MSC_VER +# pragma warning(disable:4063) // case '1' is not a valid value for switch() +#endif + // These functions are meant for memset operations of constant length only. // For dynamic length clears, use the C-compiler provided memset instead. diff --git a/common/src/Utilities/Console.cpp b/common/src/Utilities/Console.cpp index 7b8c6fd5a6..0a6db799d2 100644 --- a/common/src/Utilities/Console.cpp +++ b/common/src/Utilities/Console.cpp @@ -227,14 +227,14 @@ void IConsoleWriter::_Write( const char* fmt, va_list args ) const { std::string m_format_buffer; vssprintf( m_format_buffer, fmt, args ); - Write( wxString::FromUTF8( m_format_buffer.c_str() ) ); + Write( fromUTF8( m_format_buffer.c_str() ) ); } void IConsoleWriter::_WriteLn( const char* fmt, va_list args ) const { std::string m_format_buffer; vssprintf( m_format_buffer, fmt, args ); - WriteLn( wxString::FromUTF8( m_format_buffer.c_str() ) ); + WriteLn( fromUTF8( m_format_buffer.c_str() ) ); } void IConsoleWriter::_WriteLn( ConsoleColors color, const char* fmt, va_list args ) const diff --git a/common/src/Utilities/Exceptions.cpp b/common/src/Utilities/Exceptions.cpp index c686c3c70f..359097f668 100644 --- a/common/src/Utilities/Exceptions.cpp +++ b/common/src/Utilities/Exceptions.cpp @@ -116,11 +116,23 @@ wxString Exception::BaseException::FormatDiagnosticMessage() const return m_message_diag + L"\n\n" + m_stacktrace; } +// ------------------------------------------------------------------------ +wxString Exception::CancelEvent::FormatDiagnosticMessage() const +{ + // FIXME: This should probably just log a single line from the stacktrace.. ? + return L"Action canceled: " + m_message_diag; +} + +wxString Exception::CancelEvent::FormatDisplayMessage() const +{ + return L"Action canceled: " + m_message_diag; +} + // ------------------------------------------------------------------------ wxString Exception::ObjectIsNull::FormatDiagnosticMessage() const { return wxsFormat( - L"An attempted reference to the %s has failed; the frame does not exist or it's handle is null.", + L"reference to '%s' failed : handle is null.", m_message_diag.c_str() ) + m_stacktrace; } @@ -128,7 +140,7 @@ wxString Exception::ObjectIsNull::FormatDiagnosticMessage() const wxString Exception::ObjectIsNull::FormatDisplayMessage() const { return wxsFormat( - L"An attempted reference to the %s has failed; the frame does not exist or it's handle is null.", + L"reference to '%s' failed : handle is null.", m_message_diag.c_str() ); } diff --git a/common/src/Utilities/ThreadTools.cpp b/common/src/Utilities/ThreadTools.cpp index b6acb7b29f..d2a87f4297 100644 --- a/common/src/Utilities/ThreadTools.cpp +++ b/common/src/Utilities/ThreadTools.cpp @@ -15,24 +15,30 @@ #include "PrecompiledHeader.h" -#include "Threading.h" -#include "wxBaseTools.h" -#include -#include +#ifdef _WIN32 +# include // for thread renaming features +#endif #include #ifdef __LINUX__ # include // for pthread_kill, which is in pthread.h on w32-pthreads #endif +#include "Threading.h" +#include "wxBaseTools.h" + +#include +#include + + using namespace Threading; namespace Threading { static const wxTimeSpan ts_msec_250( 0, 0, 0, 250 ); - static void _pt_callback_cleanup( void* handle ) + void PersistentThread::_pt_callback_cleanup( void* handle ) { ((PersistentThread*)handle)->_ThreadCleanup(); } @@ -59,11 +65,11 @@ namespace Threading { try { - wxString logfix = L"Thread Destructor for " + m_name; + Console.WriteLn( L"Thread Log: Executing destructor for " + m_name ); if( m_running ) { - Console.WriteLn( logfix + L": Waiting for running thread to end."); + Console.WriteLn( L"\tWaiting for running thread to end..."); #if wxUSE_GUI m_sem_finished.WaitGui(); #else @@ -73,9 +79,7 @@ namespace Threading // it gets destroyed, otherwise th mutex handle would become invalid. ScopedLock locker( m_lock_start ); } - else - Console.WriteLn( logfix + L": thread not running."); - Sleep( 1 ); + Threading::Sleep( 1 ); Detach(); } DESTRUCTOR_CATCHALL @@ -189,47 +193,22 @@ namespace Threading m_except->Rethrow(); } - // invoked internally when canceling or exiting the thread. Extending classes should implement - // OnThreadCleanup() to extend clenup functionality. - void PersistentThread::_ThreadCleanup() + void PersistentThread::TestCancel() { - 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 - // derrived class is implemented. - ScopedLock startlock( m_lock_start ); - - OnThreadCleanup(); - - m_running = false; - m_sem_finished.Post(); + pxAssert( IsSelf() ); + pthread_testcancel(); } - wxString PersistentThread::GetName() const + // Executes the virtual member method + void PersistentThread::_try_virtual_invoke( void (PersistentThread::*method)() ) { - return m_name; - } - - void PersistentThread::_internal_execute() - { - m_running = true; - DoSetThreadName( m_name ); - try { - ExecuteTask(); - } - 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->DiagMsg() = wxsFormat( L"(thread:%s) ", GetName().c_str() ) + m_except->DiagMsg(); - ex.Rethrow(); + (this->*method)(); } + + // ---------------------------------------------------------------------------- + // Neat repackaging for STL Runtime errors... + // catch( std::runtime_error& ex ) { m_except = new Exception::RuntimeError( @@ -244,16 +223,79 @@ namespace Threading ) ); } + + // ---------------------------------------------------------------------------- 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(); + }*/ + } + + // invoked internally when canceling or exiting the thread. Extending classes should implement + // OnThreadCleanup() to extend cleanup functionality. + void 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::OnThreadCleanup ); + + m_running = false; + m_sem_finished.Post(); + } + + wxString PersistentThread::GetName() const + { + return m_name; + } + + void PersistentThread::_internal_execute() + { + m_running = true; + _DoSetThreadName( m_name ); + _try_virtual_invoke( &PersistentThread::ExecuteTask ); } void PersistentThread::OnStart() {} void PersistentThread::OnThreadCleanup() {} + // 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 ); @@ -265,12 +307,12 @@ namespace Threading return NULL; } - void PersistentThread::DoSetThreadName( const wxString& name ) + void PersistentThread::_DoSetThreadName( const wxString& name ) { - DoSetThreadName( toUTF8(name) ); + _DoSetThreadName( toUTF8(name) ); } - void PersistentThread::DoSetThreadName( __unused const char* name ) + void PersistentThread::_DoSetThreadName( __unused const char* name ) { pxAssertMsg( IsSelf(), "Thread affinity error." ); // only allowed from our own thread, thanks. diff --git a/common/src/Utilities/Windows/WinThreads.cpp b/common/src/Utilities/Windows/WinThreads.cpp index 7bccb2de49..91eb0bb84d 100644 --- a/common/src/Utilities/Windows/WinThreads.cpp +++ b/common/src/Utilities/Windows/WinThreads.cpp @@ -18,7 +18,6 @@ #include "x86emitter/tools.h" #include "Threading.h" - #ifdef _WIN32 #include "implement.h" // win32 pthreads implementations. #endif diff --git a/common/src/x86emitter/cpudetect.cpp b/common/src/x86emitter/cpudetect.cpp index fc7c58c766..79463297ad 100644 --- a/common/src/x86emitter/cpudetect.cpp +++ b/common/src/x86emitter/cpudetect.cpp @@ -15,11 +15,13 @@ #include "PrecompiledHeader.h" -#include "internal.h" -#include "tools.h" #include "Utilities/RedtapeWindows.h" #include "Utilities/Threading.h" +#include "internal.h" +#include "tools.h" + + using namespace x86Emitter; __aligned16 x86CPU_INFO x86caps; diff --git a/common/vsprops/BaseProperties.vsprops b/common/vsprops/BaseProperties.vsprops index ef8f2c325b..1286c1a2bd 100644 --- a/common/vsprops/BaseProperties.vsprops +++ b/common/vsprops/BaseProperties.vsprops @@ -5,7 +5,7 @@ Name="PcsxBaseProperties" OutputDirectory="$(SolutionDir)\bin\$(PcsxSubsection)" IntermediateDirectory="$(PlatformName)\$(ConfigurationName)" - DeleteExtensionsOnClean="*.bsc;*.idb;*.sbr;*.res;*.pch;*.pdb;*.obj;*.ilk;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;$(TargetPath)" + DeleteExtensionsOnClean="*.bsc;*.idb;*.sbr;*.res;*.pch;*.pdb;*.obj;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;$(TargetPath)" > +# include #endif #include diff --git a/pcsx2/CDVD/IsoFileTools.cpp b/pcsx2/CDVD/IsoFileTools.cpp index c58af81717..ea5df1d960 100644 --- a/pcsx2/CDVD/IsoFileTools.cpp +++ b/pcsx2/CDVD/IsoFileTools.cpp @@ -17,7 +17,8 @@ #include "IsoFileTools.h" #ifdef _WIN32 -#include +# include + void *_openfile(const char *filename, int flags) { diff --git a/pcsx2/GS.h b/pcsx2/GS.h index 83b31f613f..e9f4277a51 100644 --- a/pcsx2/GS.h +++ b/pcsx2/GS.h @@ -102,7 +102,6 @@ protected: int m_CopyCommandTally; int m_CopyDataTally; volatile bool m_RingBufferIsBusy; - volatile bool m_LoadState; // Counts the number of vsync frames queued in the MTGS ringbuffer. This is used to // throttle the number of frames allowed to be rendered ahead of time for games that @@ -153,7 +152,8 @@ public: protected: void OpenPlugin(); void OnSuspendInThread(); - void OnResumeInThread(); + void OnPauseInThread() {} + void OnResumeInThread( bool IsSuspended ); void OnResumeReady(); diff --git a/pcsx2/Gif.h b/pcsx2/Gif.h index 40d7c89c6b..7b0ab724f9 100644 --- a/pcsx2/Gif.h +++ b/pcsx2/Gif.h @@ -206,7 +206,7 @@ struct GIFregisters u32 padding[3]; tGIF_MODE mode; u32 padding1[3]; - tGIF_STAT stat; + tGIF_STAT stat; u32 padding2[7]; tGIF_TAG0 tag0; @@ -218,7 +218,7 @@ struct GIFregisters u32 tag3; u32 padding6[3]; - tGIF_CNT cnt; + tGIF_CNT cnt; u32 padding7[3]; tGIF_P3CNT p3cnt; u32 padding8[3]; diff --git a/pcsx2/HostGui.h b/pcsx2/HostGui.h index eb19e043ff..e663e10c72 100644 --- a/pcsx2/HostGui.h +++ b/pcsx2/HostGui.h @@ -54,7 +54,6 @@ public: extern StartupParams g_Startup; -extern void States_Load( const wxString& file ); extern void States_Save( const wxString& file ); extern bool States_isSlotUsed(int num); diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index 7d941fc560..2666904251 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -99,7 +99,6 @@ mtgsThreadObject::mtgsThreadObject() : , m_CopyCommandTally( 0 ) , m_CopyDataTally( 0 ) , m_RingBufferIsBusy( false ) -, m_LoadState( false ) , m_QueuedFrames( 0 ) , m_lock_FrameQueueCounter() , m_packet_size( 0 ) @@ -127,7 +126,6 @@ void mtgsThreadObject::OnStart() m_WritePos = 0; m_RingBufferIsBusy = false; - m_LoadState = false; m_QueuedFrames = 0; m_packet_size = 0; @@ -435,9 +433,9 @@ void mtgsThreadObject::OnSuspendInThread() _clean_close_gs( NULL ); } -void mtgsThreadObject::OnResumeInThread() +void mtgsThreadObject::OnResumeInThread( bool isSuspended ) { - if( !m_LoadState ) + if( isSuspended ) OpenPlugin(); } @@ -812,7 +810,6 @@ void mtgsThreadObject::Freeze( int mode, MTGS_FreezeData& data ) { AtomicExchange( m_RingPos, m_WritePos ); SendPointerPacket( GS_RINGTYPE_FREEZE, mode, &data ); - m_LoadState = true; SetEvent(); Resume(); } @@ -820,7 +817,6 @@ void mtgsThreadObject::Freeze( int mode, MTGS_FreezeData& data ) SendPointerPacket( GS_RINGTYPE_FREEZE, mode, &data ); mtgsWaitGS(); - m_LoadState = false; } // Waits for the GS to empty out the entire ring buffer contents. diff --git a/pcsx2/PluginManager.cpp b/pcsx2/PluginManager.cpp index 495d26dfc4..f5a355c1f0 100644 --- a/pcsx2/PluginManager.cpp +++ b/pcsx2/PluginManager.cpp @@ -90,7 +90,7 @@ struct LegacyApi_CommonMethod // returns the method name as a wxString, converted from UTF8. wxString GetMethodName( PluginsEnum_t pid ) const { - return tbl_PluginInfo[pid].GetShortname() + wxString::FromUTF8( MethodName ); + return tbl_PluginInfo[pid].GetShortname() + fromUTF8( MethodName ); } }; @@ -107,7 +107,7 @@ struct LegacyApi_ReqMethod // returns the method name as a wxString, converted from UTF8. wxString GetMethodName( ) const { - return wxString::FromUTF8( MethodName ); + return fromUTF8( MethodName ); } }; @@ -118,7 +118,7 @@ struct LegacyApi_OptMethod VoidMethod** Dest; // Target function where the binding is saved. // returns the method name as a wxString, converted from UTF8. - wxString GetMethodName() const { return wxString::FromUTF8( MethodName ); } + wxString GetMethodName() const { return fromUTF8( MethodName ); } }; diff --git a/pcsx2/Plugins.h b/pcsx2/Plugins.h index aa91c8af5d..a2e3722637 100644 --- a/pcsx2/Plugins.h +++ b/pcsx2/Plugins.h @@ -32,10 +32,21 @@ struct PluginInfo wxString GetShortname() const { - return wxString::FromUTF8( shortname ); + return fromUTF8( shortname ); } }; +#ifdef _MSC_VER + +// Disabling C4673: throwing 'Exception::Blah' the following types will not be considered at the catch site +// The warning is bugged, and happens even though we're properly inheriting classes with +// 'virtual' qualifiers. But since the warning is potentially useful elsewhere, I disable +// it only for the scope of these exceptions. + +# pragma warning(push) +# pragma warning(disable:4673) +#endif + // -------------------------------------------------------------------------------------- // Plugin-related Exceptions // -------------------------------------------------------------------------------------- @@ -139,9 +150,12 @@ namespace Exception virtual wxString FormatDiagnosticMessage() const; virtual wxString FormatDisplayMessage() const; }; - }; +#ifdef _MSC_VER +# pragma warning(pop) +#endif + // -------------------------------------------------------------------------------------- // LegacyPluginAPI_Common // -------------------------------------------------------------------------------------- diff --git a/pcsx2/R5900Exceptions.h b/pcsx2/R5900Exceptions.h index 24586fd217..1af6d48def 100644 --- a/pcsx2/R5900Exceptions.h +++ b/pcsx2/R5900Exceptions.h @@ -47,7 +47,7 @@ namespace R5900Exception void Init( const char*msg ) { - m_message = wxString::FromUTF8( msg ); + m_message = fromUTF8( msg ); cpuState = cpuRegs; } }; diff --git a/pcsx2/RecoverySystem.cpp b/pcsx2/RecoverySystem.cpp index 2b73c16ef5..561df04274 100644 --- a/pcsx2/RecoverySystem.cpp +++ b/pcsx2/RecoverySystem.cpp @@ -13,57 +13,98 @@ * If not, see . */ - #include "PrecompiledHeader.h" #include "App.h" #include "HostGui.h" + #include "zlib/zlib.h" + static SafeArray state_buffer; -// Simple lock boolean for the state buffer in use by a thread. This simple solution works because -// we are assured that state save/load actions will only be initiated from the main thread. -static bool state_buffer_lock = false; +// Simple lock boolean for the state buffer being in use by a thread. +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; -// -------------------------------------------------------------------------------------- -// StateThread_Freeze -// -------------------------------------------------------------------------------------- -class StateThread_Freeze : public PersistentThread +static FnType_OnThreadComplete* Callback_FreezeFinished = NULL; + +enum +{ + StateThreadAction_None = 0, + StateThreadAction_Create, + StateThreadAction_Restore, + StateThreadAction_ZipToDisk, + StateThreadAction_UnzipFromDisk, +}; + +class _BaseStateThread : public PersistentThread { typedef PersistentThread _parent; public: - StateThread_Freeze( const wxString& file ) + virtual ~_BaseStateThread() throw() { - m_name = L"SaveState::Freeze"; - - AllowFromMainThreadOnly(); - if( state_buffer_lock ) - throw Exception::RuntimeError( "Cannot save state; a previous save or load action is already in progress." ); - - Start(); - sys_resume_lock = true; + state_buffer_lock.Release(); // just in case; } - + protected: - void OnStart() {} + _BaseStateThread( const char* name, FnType_OnThreadComplete* onFinished ) + { + Callback_FreezeFinished = onFinished; + m_name = L"StateThread::" + fromUTF8(name); + } + + void OnStart() + { + if( !state_buffer_lock.TryLock() ) + throw Exception::CancelEvent( m_name + L"request ignored: state copy buffer is already locked!" ); + } + + void SendFinishEvent( int type ) + { + wxCommandEvent evt( pxEVT_FreezeThreadFinished ); + evt.SetClientData( this ); + evt.SetInt( type ); + wxGetApp().AddPendingEvent( evt ); + } + +}; + +// -------------------------------------------------------------------------------------- +// StateThread_Freeze +// -------------------------------------------------------------------------------------- +class StateThread_Freeze : public _BaseStateThread +{ + typedef _BaseStateThread _parent; + +public: + StateThread_Freeze( FnType_OnThreadComplete* onFinished ) : _BaseStateThread( "Freeze", onFinished ) + { + if( !SysHasValidState() ) + throw Exception::RuntimeError( L"Cannot complete state freeze request; the virtual machine state is reset.", _("You'll need to start a new virtual machine before you can save its state.") ); + } + +protected: + void OnStart() + { + _parent::OnStart(); + sys_resume_lock = true; + sCoreThread.Pause(); + } + void ExecuteTask() { memSavingState( state_buffer ).FreezeAll(); } - + void OnThreadCleanup() { - wxCommandEvent evt( pxEVT_FreezeFinished ); - evt.SetClientData( this ); - wxGetApp().AddPendingEvent( evt ); - + SendFinishEvent( StateThreadAction_Create ); _parent::OnThreadCleanup(); } }; @@ -71,37 +112,36 @@ protected: // -------------------------------------------------------------------------------------- // StateThread_Thaw // -------------------------------------------------------------------------------------- -class StateThread_Thaw : public PersistentThread +class StateThread_Thaw : public _BaseStateThread { - typedef PersistentThread _parent; + typedef _BaseStateThread _parent; public: - StateThread_Thaw( const wxString& file ) - { - m_name = L"SaveState::Thaw"; - - AllowFromMainThreadOnly(); - if( state_buffer_lock ) - throw Exception::RuntimeError( "Cannot sload state; a previous save or load action is already in progress." ); - - Start(); - sys_resume_lock = true; - } + StateThread_Thaw( FnType_OnThreadComplete* onFinished ) : _BaseStateThread( "Thaw", onFinished ) { } protected: - void OnStart() {} + void OnStart() + { + _parent::OnStart(); + + if( state_buffer.IsDisposed() ) + { + state_buffer_lock.Release(); + throw Exception::RuntimeError( "ThawState request made, but no valid state exists!" ); + } + + sys_resume_lock = true; + sCoreThread.Pause(); + } void ExecuteTask() { - memSavingState( state_buffer ).FreezeAll(); + memLoadingState( state_buffer ).FreezeAll(); } void OnThreadCleanup() { - wxCommandEvent evt( pxEVT_FreezeFinished ); - evt.SetClientData( this ); - wxGetApp().AddPendingEvent( evt ); - + SendFinishEvent( StateThreadAction_Restore ); _parent::OnThreadCleanup(); } }; @@ -109,57 +149,46 @@ protected: // -------------------------------------------------------------------------------------- // StateThread_ZipToDisk // -------------------------------------------------------------------------------------- -class StateThread_ZipToDisk : public PersistentThread +class StateThread_ZipToDisk : public _BaseStateThread { - typedef PersistentThread _parent; + typedef _BaseStateThread _parent; protected: - gzFile m_gzfp; + const wxString m_filename; + gzFile m_gzfp; public: - StateThread_ZipToDisk( const wxString& file ) : m_gzfp( NULL ) + StateThread_ZipToDisk( FnType_OnThreadComplete* onFinished, const wxString& file ) : + _BaseStateThread( "ZipToDisk", onFinished ) + , m_filename( file ) + , m_gzfp( NULL ) { - m_name = L"SaveState::ZipToDisk"; - - AllowFromMainThreadOnly(); - if( state_buffer_lock ) - throw Exception::RuntimeError( "Cannot save state; a previous save or load action is already in progress." ); - - m_gzfp = gzopen( file.ToUTF8().data(), "wb" ); - if( m_gzfp == NULL ) - throw Exception::CreateStream( file, "Cannot create savestate file for writing." ); - - try{ Start(); } - catch(...) - { - gzclose( m_gzfp ); m_gzfp = NULL; - throw; - } - sys_resume_lock = true; } ~StateThread_ZipToDisk() throw() { - sys_resume_lock = false; // just in case; if( m_gzfp != NULL ) gzclose( m_gzfp ); } protected: - void OnStart() {} + void OnStart() + { + _parent::OnStart(); + m_gzfp = gzopen( toUTF8(m_filename), "wb" ); + if( m_gzfp == NULL ) + throw Exception::CreateStream( m_filename, "Cannot create savestate file for writing." ); + } void ExecuteTask() { - Sleep( 2 ); + Yield( 2 ); if( gzwrite( (gzFile)m_gzfp, state_buffer.GetPtr(), state_buffer.GetSizeInBytes() ) < state_buffer.GetSizeInBytes() ) throw Exception::BadStream(); } void OnThreadCleanup() { - wxCommandEvent evt( pxEVT_FreezeFinished ); - evt.SetClientData( this ); // tells message to clean us up. - wxGetApp().AddPendingEvent( evt ); - + SendFinishEvent( StateThreadAction_ZipToDisk ); _parent::OnThreadCleanup(); } }; @@ -168,43 +197,41 @@ protected: // -------------------------------------------------------------------------------------- // StateThread_UnzipFromDisk // -------------------------------------------------------------------------------------- -class StateThread_UnzipFromDisk : public PersistentThread +class StateThread_UnzipFromDisk : public _BaseStateThread { - typedef PersistentThread _parent; + typedef _BaseStateThread _parent; protected: - gzFile m_gzfp; + const wxString m_filename; + gzFile m_gzfp; + // set true only once the whole file has finished loading. IF the thread is canceled or + // an error occurs, this will remain false. + bool m_finished; + public: - StateThread_UnzipFromDisk( const wxString& file ) : m_gzfp( NULL ) + StateThread_UnzipFromDisk( FnType_OnThreadComplete* onFinished, const wxString& file ) : + _BaseStateThread( "UnzipFromDisk", onFinished ) + , m_filename( file ) + , m_gzfp( NULL ) + , m_finished( false ) { - m_name = L"SaveState::UnzipFromDisk"; - - AllowFromMainThreadOnly(); - if( state_buffer_lock ) - throw Exception::RuntimeError( "Cannot save state; a previous save or load action is already in progress." ); - - m_gzfp = gzopen( file.ToUTF8().data(), "wb" ); - if( m_gzfp == NULL ) - throw Exception::CreateStream( file, "Cannot create savestate file for writing." ); - - try{ Start(); } - catch(...) - { - gzclose( m_gzfp ); m_gzfp = NULL; - throw; - } - sys_resume_lock = true; } ~StateThread_UnzipFromDisk() throw() { - sys_resume_lock = false; // just in case; if( m_gzfp != NULL ) gzclose( m_gzfp ); } protected: - void OnStart() {} + void OnStart() + { + _parent::OnStart(); + + m_gzfp = gzopen( toUTF8(m_filename), "rb" ); + if( m_gzfp == NULL ) + throw Exception::CreateStream( m_filename, "Cannot open savestate file for reading." ); + } void ExecuteTask() { @@ -217,70 +244,95 @@ protected: state_buffer.ExactAlloc( curidx+BlockSize ); gzread( m_gzfp, state_buffer.GetPtr(curidx), BlockSize ); curidx += BlockSize; + TestCancel(); } while( !gzeof(m_gzfp) ); + + m_finished = true; } void OnThreadCleanup() { - wxCommandEvent evt( pxEVT_ThawFinished ); - evt.SetClientData( this ); // tells message to clean us up. - wxGetApp().AddPendingEvent( evt ); - + SendFinishEvent( StateThreadAction_UnzipFromDisk ); _parent::OnThreadCleanup(); } }; -void Pcsx2App::OnFreezeFinished( wxCommandEvent& evt ) +void Pcsx2App::OnFreezeThreadFinished( wxCommandEvent& evt ) { - state_buffer.Dispose(); - state_buffer_lock = false; + // clear the OnFreezeFinsihed 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; - SysClearExecutionCache(); - sCoreThread.Resume(); + { + ScopedPtr thr( (PersistentThread*)evt.GetClientData() ); + if( !pxAssertDev( thr != NULL, "NULL thread handle on freeze finished?" ) ) return; + state_buffer_lock.Release(); + sys_resume_lock = false; + thr->RethrowException(); + } - delete (PersistentThread*)evt.GetClientData(); + if( fn_tmp != NULL ) fn_tmp( evt ); + + //m_evtsrc_FreezeThreadFinished.Dispatch( evt ); } -void Pcsx2App::OnThawFinished( wxCommandEvent& evt ) +void OnFinished_Resume( const wxCommandEvent& evt ) { - PersistentThread* thr = (PersistentThread*)evt.GetClientData(); - if( thr == NULL ) + if( evt.GetInt() == StateThreadAction_Restore ) { - pxAssert( false ); - return; + // Successfully restored state, so remove the copy. Don't remove it sooner + // because the thread may have failed with some exception/error. + + state_buffer.Dispose(); + SysClearExecutionCache(); } - /*catch( Exception::BadSavedState& ex) - { - // At this point we can return control back to the user, no questions asked. - // StateLoadErrors are only thorwn if the load failed prior to any virtual - // machine memory contents being changed. (usually missing file errors) - - Console.Notice( ex.FormatDiagnosticMessage() ); - sCoreThread.Resume(); - }*/ - - state_buffer.Dispose(); - state_buffer_lock = false; - - SysClearExecutionCache(); sCoreThread.Resume(); - - delete (PersistentThread*)evt.GetClientData(); } +static wxString zip_dest_filename; + +void OnFinished_ZipToDisk( const wxCommandEvent& evt ) +{ + if( !pxAssertDev( evt.GetInt() == StateThreadAction_Create, "Unexpected StateThreadAction value, aborting save." ) ) return; + + if( zip_dest_filename.IsEmpty() ) + { + Console.Notice( "Cannot save state to disk: empty filename specified." ); + return; + } + + // Phase 2: Record to disk!! + (new StateThread_ZipToDisk( OnFinished_Resume, zip_dest_filename ))->Start(); +} + +void OnFinished_Restore( const wxCommandEvent& evt ) +{ + if( !pxAssertDev( evt.GetInt() == StateThreadAction_UnzipFromDisk, "Unexpected StateThreadAction value, aborting restore." ) ) return; + + // Phase 2: Restore over existing VM state!! + (new StateThread_Thaw( OnFinished_Resume ))->Start(); +} + + +// ===================================================================================================== +// StateCopy Public Interface +// ===================================================================================================== + void StateCopy_SaveToFile( const wxString& file ) { - if( state_buffer_lock ) return; - new StateThread_ZipToDisk( file ); + if( state_buffer_lock.IsLocked() ) return; + zip_dest_filename = file; + (new StateThread_Freeze( OnFinished_ZipToDisk ))->Start(); + Console.Status( wxsFormat( L"Saving savestate to file: %s", zip_dest_filename.c_str() ) ); } void StateCopy_LoadFromFile( const wxString& file ) { - if( state_buffer_lock ) return; - - sCoreThread.ShortSuspend(); - new StateThread_UnzipFromDisk( file ); + if( state_buffer_lock.IsLocked() ) return; + sCoreThread.Pause(); + (new StateThread_UnzipFromDisk( OnFinished_Restore, file ))->Start(); } // Saves recovery state info to the given saveslot, or saves the active emulation state @@ -289,8 +341,28 @@ void StateCopy_LoadFromFile( const wxString& file ) // the one in the memory save. :) void StateCopy_SaveToSlot( uint num ) { - if( state_buffer_lock ) return; - StateCopy_SaveToFile( SaveStateBase::GetFilename( num ) ); + zip_dest_filename = SaveStateBase::GetFilename( num ); + (new StateThread_Freeze( OnFinished_ZipToDisk ))->Start(); + Console.Status( "Saving savestate to slot %d...", num ); + Console.Status( wxsFormat(L"\tfilename: %s", zip_dest_filename.c_str()) ); +} + +void StateCopy_LoadFromSlot( uint slot ) +{ + if( state_buffer_lock.IsLocked() ) return; + wxString file( SaveStateBase::GetFilename( slot ) ); + + if( !wxFileExists( file ) ) + { + Console.Notice( "Savestate slot %d is empty.", slot ); + return; + } + + Console.Status( "Loading savestate from slot %d...", slot ); + Console.Status( wxsFormat(L"\tfilename: %s", file.c_str()) ); + + sCoreThread.Pause(); + (new StateThread_UnzipFromDisk( OnFinished_Restore, file ))->Start(); } bool StateCopy_IsValid() @@ -310,46 +382,19 @@ bool StateCopy_HasPartialState() void StateCopy_FreezeToMem() { - if( state_buffer_lock ) return; + if( state_buffer_lock.IsLocked() ) return; + (new StateThread_Freeze( OnFinished_Restore ))->Start(); } void StateCopy_ThawFromMem() { - if( state_buffer_lock ) return; + if( state_buffer_lock.IsLocked() ) return; + new StateThread_Thaw( OnFinished_Restore ); } void StateCopy_Clear() { - if( state_buffer_lock ) return; + if( state_buffer_lock.IsLocked() ) return; state_buffer.Dispose(); } - - -// Creates a full recovery of the entire emulation state (CPU and all plugins). -// If a current recovery state is already present, then nothing is done (the -// existing recovery state takes precedence since if it were out-of-date it'd be -// deleted!). -void MakeFull() -{ - //if( g_RecoveryState ) return; - //if( !SysHasValidState() ) return; - - /* - try - { - g_RecoveryState = new SafeArray( L"Memory Savestate Recovery" ); - memSavingState( *g_RecoveryState ).FreezeAll(); - } - catch( Exception::RuntimeError& ex ) - { - Msgbox::Alert( wxsFormat( // fixme: needs proper translation - L"PCSX2 encountered an error while trying to backup/suspend the PS2 VirtualMachine state. " - L"You may resume emulation without losing any data, however the machine state will not be " - L"able to recover if you make changes to your PCSX2 configuration.\n\n" - L"Details: %s", ex.FormatDisplayMessage().c_str() ) - ); - g_RecoveryState = NULL; - }*/ -} - diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp index 54a06ef309..011167a8b7 100644 --- a/pcsx2/SaveState.cpp +++ b/pcsx2/SaveState.cpp @@ -136,6 +136,9 @@ static const int MainMemorySizeInBytes = void SaveStateBase::FreezeMainMemory() { + if( IsLoading() ) + PreLoadPrep(); + // First Block - Memory Dumps // --------------------------- FreezeMem(PS2MEM_BASE, Ps2MemSize::Base); // 32 MB main memory @@ -149,8 +152,8 @@ void SaveStateBase::FreezeMainMemory() void SaveStateBase::FreezeRegisters() { - //if( IsLoading() ) - // PreLoadPrep(); + if( IsLoading() ) + PreLoadPrep(); // Second Block - Various CPU Registers and States // ----------------------------------------------- diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h index e0b5c062eb..0c5f31e541 100644 --- a/pcsx2/SaveState.h +++ b/pcsx2/SaveState.h @@ -202,5 +202,6 @@ extern void StateCopy_ThawFromMem(); extern void StateCopy_SaveToFile( const wxString& file ); extern void StateCopy_LoadFromFile( const wxString& file ); extern void StateCopy_SaveToSlot( uint num ); +extern void StateCopy_LoadFromSlot( uint slot ); extern void StateCopy_Clear(); diff --git a/pcsx2/System.cpp b/pcsx2/System.cpp index 5c2b73487d..0aa4cad535 100644 --- a/pcsx2/System.cpp +++ b/pcsx2/System.cpp @@ -139,7 +139,7 @@ SysCoreAllocations::SysCoreAllocations() throw Exception::OutOfMemory( wxsFormat( // Diagnostic (english) L"std::bad_alloc caught while trying to allocate memory for the PS2 Virtual Machine.\n" - L"Error Details: " + wxString::FromUTF8( ex.what() ) + L"Error Details: " + fromUTF8( ex.what() ) ), GetMemoryErrorVM() // translated diff --git a/pcsx2/System/SysThreads.cpp b/pcsx2/System/SysThreads.cpp index e05ee901c0..7c977a1cdc 100644 --- a/pcsx2/System/SysThreads.cpp +++ b/pcsx2/System/SysThreads.cpp @@ -55,7 +55,6 @@ 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. @@ -66,6 +65,11 @@ void SysSuspendableThread::OnStart() // is mostly useful for starting certain non-Emu related gui activities (improves gui // responsiveness). // +// 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. +// void SysSuspendableThread::Suspend( bool isBlocking ) { if( IsSelf() || !IsRunning() ) return; @@ -76,11 +80,34 @@ void SysSuspendableThread::Suspend( bool isBlocking ) if( m_ExecMode == ExecMode_Suspended ) return; + if( m_ExecMode == ExecMode_Pausing || m_ExecMode == ExecMode_Paused ) + throw Exception::CancelEvent( "Another thread is pausing the VM state." ); + if( m_ExecMode == ExecMode_Running ) m_ExecMode = ExecMode_Suspending; - pxAssertDev( m_ExecMode == ExecMode_Suspending, "ExecMode should be nothing other than Suspended..." ); + pxAssertDev( m_ExecMode == ExecMode_Suspending, "ExecMode should be nothing other than Suspending..." ); } + m_SuspendEvent.Reset(); + m_sem_event.Post(); + if( isBlocking ) m_SuspendEvent.WaitGui(); +} + +void SysSuspendableThread::Pause() +{ + if( IsSelf() || !IsRunning() ) return; + + { + ScopedLock locker( m_lock_ExecMode ); + + if( (m_ExecMode == ExecMode_Suspended) || (m_ExecMode == ExecMode_Paused) ) return; + + if( m_ExecMode == ExecMode_Running ) + m_ExecMode = ExecMode_Pausing; + + pxAssertDev( m_ExecMode == ExecMode_Pausing, "ExecMode should be nothing other than Pausing..." ); + } + m_SuspendEvent.Reset(); m_sem_event.Post(); m_SuspendEvent.WaitGui(); } @@ -111,6 +138,7 @@ void SysSuspendableThread::Resume() // fall through... case ExecMode_Suspending: + case ExecMode_Pausing: // we need to make sure and wait for the emuThread to enter a fully suspended // state before continuing... @@ -120,8 +148,8 @@ void SysSuspendableThread::Resume() } } - pxAssertDev( m_ExecMode == ExecMode_Suspended, - "SysSuspendableThread is not in a suspended/idle state? wtf!" ); + pxAssertDev( (m_ExecMode == ExecMode_Suspended) || (m_ExecMode == ExecMode_Paused), + "SysSuspendableThread is not in a suspended/paused state? wtf!" ); m_ExecMode = ExecMode_Running; m_ResumeProtection = true; @@ -148,7 +176,7 @@ void SysSuspendableThread::StateCheck( bool isCancelable ) // Shortcut for the common case, to avoid unnecessary Mutex locks: if( m_ExecMode == ExecMode_Running ) { - if( isCancelable ) pthread_testcancel(); + if( isCancelable ) TestCancel(); return; } @@ -170,23 +198,41 @@ void SysSuspendableThread::StateCheck( bool isCancelable ) // Yup, need this a second time. Variable state could have changed while we // were trying to acquire the lock above. if( isCancelable ) - pthread_testcancel(); + TestCancel(); break; + // ------------------------------------- + case ExecMode_Pausing: + { + OnPauseInThread(); + m_ExecMode = ExecMode_Paused; + m_SuspendEvent.Post(); + } + // fallthrough... + + case ExecMode_Paused: + m_lock_ExecMode.Unlock(); + while( m_ExecMode == ExecMode_Paused ) + m_ResumeEvent.WaitGui(); + + OnResumeInThread( false ); + break; + + // ------------------------------------- case ExecMode_Suspending: { OnSuspendInThread(); m_ExecMode = ExecMode_Suspended; m_SuspendEvent.Post(); } - // fall through... - + // fallthrough... + case ExecMode_Suspended: m_lock_ExecMode.Unlock(); while( m_ExecMode == ExecMode_Suspended ) m_ResumeEvent.WaitGui(); - OnResumeInThread(); + OnResumeInThread( true ); break; jNO_DEFAULT; @@ -218,16 +264,6 @@ void SysCoreThread::Start() _parent::Start(); } -// Suspends the system without closing plugins or updating GUI status. -// Should be used for savestates or other actions which happen very quickly. -void SysCoreThread::ShortSuspend() -{ - m_shortSuspend = true; - Suspend(); - m_shortSuspend = false; -} - - // 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. @@ -348,11 +384,6 @@ void SysCoreThread::CpuExecute() PCSX2_MEM_PROTECT_END(); } -static void _cet_callback_cleanup( void* handle ) -{ - ((SysCoreThread*)handle)->OnThreadCleanup(); -} - void SysCoreThread::ExecuteTask() { tls_coreThread = this; @@ -369,9 +400,10 @@ void SysCoreThread::OnSuspendInThread() m_plugins.Close(); } -void SysCoreThread::OnResumeInThread() +void SysCoreThread::OnResumeInThread( bool isSuspended ) { - m_plugins.Open(); + if( isSuspended ) + m_plugins.Open(); } diff --git a/pcsx2/System/SysThreads.h b/pcsx2/System/SysThreads.h index 51204cfc37..241495a7fc 100644 --- a/pcsx2/System/SysThreads.h +++ b/pcsx2/System/SysThreads.h @@ -41,7 +41,9 @@ protected: ExecMode_NoThreadYet, ExecMode_Running, ExecMode_Suspending, - ExecMode_Suspended + ExecMode_Suspended, + ExecMode_Pausing, + ExecMode_Paused, }; volatile ExecutionMode m_ExecMode; @@ -59,6 +61,7 @@ public: virtual void Suspend( bool isBlocking = true ); virtual void Resume(); + virtual void Pause(); virtual void StateCheck( bool isCancelable = true ); virtual void OnThreadCleanup(); @@ -78,11 +81,21 @@ protected: // 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() + // 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 // thread has been suspended and then subsequently resumed. - virtual void OnResumeInThread()=0; + // Parameter: + // isSuspended - set to TRUE if the thread is returning from a suspended state, or + // FALSE if it's returning from a paused state. + virtual void OnResumeInThread( bool isSuspended )=0; }; // -------------------------------------------------------------------------------------- @@ -107,7 +120,6 @@ public: virtual void ApplySettings( const Pcsx2Config& src ); virtual void OnThreadCleanup(); - virtual void ShortSuspend(); virtual void OnResumeReady(); protected: @@ -116,6 +128,7 @@ protected: virtual void Start(); virtual void OnSuspendInThread(); - virtual void OnResumeInThread(); + virtual void OnPauseInThread() {} + virtual void OnResumeInThread( bool IsSuspended ); virtual void ExecuteTask(); }; diff --git a/pcsx2/VUmicroMem.cpp b/pcsx2/VUmicroMem.cpp index a89dd44ca7..558d828130 100644 --- a/pcsx2/VUmicroMem.cpp +++ b/pcsx2/VUmicroMem.cpp @@ -27,12 +27,6 @@ VUmicroCpu CpuVU0; // contains a working copy of the VU0 cpu functions/API VUmicroCpu CpuVU1; // contains a working copy of the VU1 cpu functions/API -static void DummyExecuteVU1Block(void) -{ - VU0.VI[ REG_VPU_STAT ].UL &= ~0x100; - VU1.vifRegs->stat.VEW = 0; // also reset the bit (grandia 3 works) -} - void vuMicroCpuReset() { CpuVU0 = CHECK_VU0REC ? recVU0 : intVU0; diff --git a/pcsx2/gui/App.h b/pcsx2/gui/App.h index 0ccfc9ace4..5553e2cf0f 100644 --- a/pcsx2/gui/App.h +++ b/pcsx2/gui/App.h @@ -21,6 +21,8 @@ #include #include +#include "Utilities/Listeners.h" + class IniInterface; class MainEmuFrame; class GSFrame; @@ -35,6 +37,9 @@ class AppCoreThread; #include "System.h" #include "System/SysThreads.h" + +typedef void FnType_OnThreadComplete(const wxCommandEvent& evt); + #define AllowFromMainThreadOnly() \ pxAssertMsg( wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only." ) @@ -42,10 +47,10 @@ BEGIN_DECLARE_EVENT_TYPES() DECLARE_EVENT_TYPE( pxEVT_SemaphorePing, -1 ) DECLARE_EVENT_TYPE( pxEVT_OpenModalDialog, -1 ) DECLARE_EVENT_TYPE( pxEVT_ReloadPlugins, -1 ) + DECLARE_EVENT_TYPE( pxEVT_SysExecute, -1 ) DECLARE_EVENT_TYPE( pxEVT_LoadPluginsComplete, -1 ) - DECLARE_EVENT_TYPE( pxEVT_AppCoreThread_Terminated, -1 ) - DECLARE_EVENT_TYPE( pxEVT_FreezeFinished, -1 ) - DECLARE_EVENT_TYPE( pxEVT_ThawFinished, -1 ) + DECLARE_EVENT_TYPE( pxEVT_AppCoreThreadFinished, -1 ) + DECLARE_EVENT_TYPE( pxEVT_FreezeThreadFinished, -1 ) END_DECLARE_EVENT_TYPES() // This is used when the GS plugin is handling its own window. Messages from the PAD @@ -335,8 +340,6 @@ public: Pcsx2App(); virtual ~Pcsx2App(); - void ReloadPlugins(); - void PostPadKey( wxKeyEvent& evt ); void PostMenuAction( MenuIdentifiers menu_id ) const; int ThreadedModalDialog( DialogIdentifiers dialogId ); @@ -392,6 +395,18 @@ public: void DisableDiskLogging() const; void OnProgramLogClosed(); + // ---------------------------------------------------------------------------- + // Event Sources! + // ---------------------------------------------------------------------------- + +protected: + CmdEvt_Source m_evtsrc_CorePluginStatus; + CmdEvt_Source m_evtsrc_CoreThreadStatus; + +public: + CmdEvt_Source& Source_CoreThreadStatus() { return m_evtsrc_CoreThreadStatus; } + CmdEvt_Source& Source_CorePluginStatus() { return m_evtsrc_CorePluginStatus; } + protected: void InitDefaultGlobalAccelerators(); void BuildCommandHash(); @@ -402,18 +417,18 @@ protected: void HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEvent& event) const; + void OnSysExecute( wxCommandEvent& evt ); void OnReloadPlugins( wxCommandEvent& evt ); void OnLoadPluginsComplete( wxCommandEvent& evt ); void OnSemaphorePing( wxCommandEvent& evt ); void OnOpenModalDialog( wxCommandEvent& evt ); void OnCoreThreadTerminated( wxCommandEvent& evt ); - void OnFreezeFinished( wxCommandEvent& evt ); - void OnThawFinished( wxCommandEvent& evt ); + void OnFreezeThreadFinished( wxCommandEvent& evt ); void OnMessageBox( pxMessageBoxEvent& evt ); void OnEmuKeyDown( wxKeyEvent& evt ); - + // ---------------------------------------------------------------------------- // Override wx default exception handling behavior // ---------------------------------------------------------------------------- @@ -496,7 +511,7 @@ DECLARE_APP(Pcsx2App) extern bool sys_resume_lock; extern int EnumeratePluginsInFolder( const wxDirName& searchPath, wxArrayString* dest ); -extern void LoadPluginsPassive(); +extern void LoadPluginsPassive( FnType_OnThreadComplete* onComplete ); extern void LoadPluginsImmediate(); extern void UnloadPlugins(); diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index e53b2fdd86..ecac3008da 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -72,7 +72,7 @@ void AppCoreThread::OnResumeReady() // the new (lack of) thread status, so this posts a message to the App to do so. void AppCoreThread::OnThreadCleanup() { - wxCommandEvent evt( pxEVT_AppCoreThread_Terminated ); + wxCommandEvent evt( pxEVT_AppCoreThreadFinished ); wxGetApp().AddPendingEvent( evt ); _parent::OnThreadCleanup(); } diff --git a/pcsx2/gui/AppInit.cpp b/pcsx2/gui/AppInit.cpp index 4da439576e..1e98a3c803 100644 --- a/pcsx2/gui/AppInit.cpp +++ b/pcsx2/gui/AppInit.cpp @@ -220,12 +220,12 @@ bool Pcsx2App::OnInit() Connect( pxEVT_SemaphorePing, wxCommandEventHandler( Pcsx2App::OnSemaphorePing ) ); Connect( pxEVT_OpenModalDialog, wxCommandEventHandler( Pcsx2App::OnOpenModalDialog ) ); Connect( pxEVT_ReloadPlugins, wxCommandEventHandler( Pcsx2App::OnReloadPlugins ) ); + Connect( pxEVT_SysExecute, wxCommandEventHandler( Pcsx2App::OnSysExecute ) ); + Connect( pxEVT_LoadPluginsComplete, wxCommandEventHandler( Pcsx2App::OnLoadPluginsComplete ) ); - Connect( pxEVT_FreezeFinished, wxCommandEventHandler( Pcsx2App::OnCoreThreadTerminated ) ); - - Connect( pxEVT_FreezeFinished, wxCommandEventHandler( Pcsx2App::OnFreezeFinished ) ); - Connect( pxEVT_ThawFinished, wxCommandEventHandler( Pcsx2App::OnThawFinished ) ); + Connect( pxEVT_AppCoreThreadFinished, wxCommandEventHandler( Pcsx2App::OnCoreThreadTerminated ) ); + Connect( pxEVT_FreezeThreadFinished, wxCommandEventHandler( Pcsx2App::OnFreezeThreadFinished ) ); Connect( pxID_PadHandler_Keydown, wxEVT_KEY_DOWN, wxKeyEventHandler( Pcsx2App::OnEmuKeyDown ) ); @@ -310,7 +310,7 @@ bool Pcsx2App::OnInit() return false; } - LoadPluginsPassive(); + LoadPluginsPassive( NULL ); } // ---------------------------------------------------------------------------- catch( Exception::StartupAborted& ex ) diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index 025266bc77..cde9bdcb2e 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -30,10 +30,10 @@ IMPLEMENT_APP(Pcsx2App) DEFINE_EVENT_TYPE( pxEVT_SemaphorePing ); DEFINE_EVENT_TYPE( pxEVT_OpenModalDialog ); DEFINE_EVENT_TYPE( pxEVT_ReloadPlugins ); +DEFINE_EVENT_TYPE( pxEVT_SysExecute ); DEFINE_EVENT_TYPE( pxEVT_LoadPluginsComplete ); -DEFINE_EVENT_TYPE( pxEVT_AppCoreThread_Terminated ); -DEFINE_EVENT_TYPE( pxEVT_FreezeFinished ); -DEFINE_EVENT_TYPE( pxEVT_ThawFinished ); +DEFINE_EVENT_TYPE( pxEVT_AppCoreThreadFinished ); +DEFINE_EVENT_TYPE( pxEVT_FreezeThreadFinished ); bool UseAdminMode = false; wxDirName SettingsFolder; @@ -211,6 +211,18 @@ void Pcsx2App::HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEvent& (handler->*func)(event); } // ---------------------------------------------------------------------------- + catch( Exception::CancelEvent& ex ) + { + Console.Notice( ex.FormatDiagnosticMessage() ); + } + // ---------------------------------------------------------------------------- + catch( Exception::BadSavedState& ex) + { + // Saved state load failed. + Console.Notice( ex.FormatDiagnosticMessage() ); + sCoreThread.Resume(); + } + // ---------------------------------------------------------------------------- catch( Exception::PluginError& ex ) { Console.Error( ex.FormatDiagnosticMessage() ); @@ -376,32 +388,53 @@ void Pcsx2App::OnMainFrameClosed() m_MainFrame = NULL; } - - // -------------------------------------------------------------------------------------- // Sys/Core API and Shortcuts (for wxGetApp()) // -------------------------------------------------------------------------------------- +static int _sysexec_cdvdsrc_type = -1; + +static void OnSysExecuteAfterPlugins( const wxCommandEvent& loadevt ) +{ + if( !wxGetApp().m_CorePlugins ) return; + + wxCommandEvent execevt( pxEVT_SysExecute ); + execevt.SetInt( _sysexec_cdvdsrc_type ); + wxGetApp().AddPendingEvent( execevt ); +} + // Executes the emulator using a saved/existing virtual machine state and currently // configured CDVD source device. void Pcsx2App::SysExecute() { - if( sys_resume_lock ) + if( !m_CorePlugins ) { - Console.WriteLn( "SysExecute: State is locked, ignoring Execute request!" ); + LoadPluginsPassive( OnSysExecuteAfterPlugins ); return; } - SysReset(); - LoadPluginsImmediate(); - m_CoreThread = new AppCoreThread( *m_CorePlugins ); - m_CoreThread->Resume(); + wxCommandEvent evt( pxEVT_SysExecute ); + evt.SetInt( -1 ); + AddPendingEvent( evt ); } // Executes the specified cdvd source and optional elf file. This command performs a // full closure of any existing VM state and starts a fresh VM with the requested // sources. void Pcsx2App::SysExecute( CDVD_SourceType cdvdsrc ) +{ + if( !m_CorePlugins ) + { + LoadPluginsPassive( OnSysExecuteAfterPlugins ); + return; + } + + wxCommandEvent evt( pxEVT_SysExecute ); + evt.SetInt( (int)cdvdsrc ); + AddPendingEvent( evt ); +} + +void Pcsx2App::OnSysExecute( wxCommandEvent& evt ) { if( sys_resume_lock ) { @@ -409,10 +442,13 @@ void Pcsx2App::SysExecute( CDVD_SourceType cdvdsrc ) return; } - SysReset(); - LoadPluginsImmediate(); + // if something unloaded plugins since this messages was queued then it's best to ignore + // it, because apparently too much stuff is going on and the emulation states are wonky. + if( !m_CorePlugins ) return; + + if( evt.GetInt() != -1 ) SysReset(); CDVDsys_SetFile( CDVDsrc_Iso, g_Conf->CurrentIso ); - CDVDsys_ChangeSource( cdvdsrc ); + if( evt.GetInt() != -1 ) CDVDsys_ChangeSource( (CDVD_SourceType)evt.GetInt() ); m_CoreThread = new AppCoreThread( *m_CorePlugins ); m_CoreThread->Resume(); diff --git a/pcsx2/gui/ConsoleLogger.cpp b/pcsx2/gui/ConsoleLogger.cpp index 5836c39f5d..3875056852 100644 --- a/pcsx2/gui/ConsoleLogger.cpp +++ b/pcsx2/gui/ConsoleLogger.cpp @@ -109,7 +109,7 @@ void ConsoleTestThread::ExecuteTask() // worst case scenario (without being entirely unrealistic). Console.WriteLn( wxsFormat( L"This is a threaded logging test. Something bad could happen... %d", ++numtrack ) ); Console.Status( wxsFormat( L"Testing high stress loads %s", L"(multi-color)" ) ); - Sleep( 0 ); + Yield( 0 ); } } diff --git a/pcsx2/gui/Dialogs/AboutBoxDialog.cpp b/pcsx2/gui/Dialogs/AboutBoxDialog.cpp index ab0f695fca..99f98fde86 100644 --- a/pcsx2/gui/Dialogs/AboutBoxDialog.cpp +++ b/pcsx2/gui/Dialogs/AboutBoxDialog.cpp @@ -51,7 +51,7 @@ Dialogs::AboutBoxDialog::AboutBoxDialog( wxWindow* parent, int id ): m_bitmap_dualshock( this, wxID_ANY, wxBitmap( EmbeddedImage().Get() ), wxDefaultPosition, wxDefaultSize, wxBORDER_SUNKEN ) { - static const wxString LabelAuthors = wxString::FromUTF8( + static const wxString LabelAuthors = fromUTF8( "Developers" "\n\n" "v0.9.6+: Arcum42, Refraction," @@ -69,7 +69,7 @@ Dialogs::AboutBoxDialog::AboutBoxDialog( wxWindow* parent, int id ): "Webmasters: CKemu, Falcon4ever" ); - static const wxString LabelGreets = wxString::FromUTF8( + static const wxString LabelGreets = fromUTF8( "Contributors" "\n\n" "Hiryu and Sjeep (libcdvd / iso filesystem), nneeve (fpu and vu)" diff --git a/pcsx2/gui/Dialogs/ConfigurationDialog.cpp b/pcsx2/gui/Dialogs/ConfigurationDialog.cpp index 8f7dfd1a56..1254f81bd8 100644 --- a/pcsx2/gui/Dialogs/ConfigurationDialog.cpp +++ b/pcsx2/gui/Dialogs/ConfigurationDialog.cpp @@ -44,7 +44,7 @@ static const int IdealWidth = 500; template< typename T > void Dialogs::ConfigurationDialog::AddPage( const char* label, int iconid ) { - const wxString labelstr( wxString::FromUTF8( label ) ); + const wxString labelstr( fromUTF8( label ) ); const int curidx = m_labels.Add( labelstr ); g_ApplyState.SetCurrentPage( curidx ); m_listbook.AddPage( new T( m_listbook, IdealWidth ), wxGetTranslation( labelstr ), diff --git a/pcsx2/gui/GlobalCommands.cpp b/pcsx2/gui/GlobalCommands.cpp index ec872aaeb4..cb53f15350 100644 --- a/pcsx2/gui/GlobalCommands.cpp +++ b/pcsx2/gui/GlobalCommands.cpp @@ -263,7 +263,7 @@ void AcceleratorDictionary::Map( const KeyAcceleratorCode& acode, const char *se Console.Notice( wxsFormat( L"Kbd Accelerator '%s' is mapped multiple times.\n" L"\t'Command %s' is being replaced by '%s'", - acode.ToString().c_str(), wxString::FromUTF8( result->Id ).c_str(), searchfor ) + acode.ToString().c_str(), fromUTF8( result->Id ).c_str(), searchfor ) ); } @@ -272,7 +272,7 @@ void AcceleratorDictionary::Map( const KeyAcceleratorCode& acode, const char *se if( result == NULL ) { Console.Notice( wxsFormat( L"Kbd Accelerator '%s' is mapped to unknown command '%s'", - acode.ToString().c_str(), wxString::FromUTF8( searchfor ).c_str() ) + acode.ToString().c_str(), fromUTF8( searchfor ).c_str() ) ); } else diff --git a/pcsx2/gui/MainFrame.cpp b/pcsx2/gui/MainFrame.cpp index dd36f1f5d7..0eba2da51b 100644 --- a/pcsx2/gui/MainFrame.cpp +++ b/pcsx2/gui/MainFrame.cpp @@ -310,7 +310,7 @@ MainEmuFrame::MainEmuFrame(wxWindow* parent, const wxString& title): { // Odd versions: beta / development editions, which feature revision number and compile date. wintitle.Printf( _("PCSX2 %d.%d.%d.%d%s (svn) %s"), PCSX2_VersionHi, PCSX2_VersionMid, PCSX2_VersionLo, - SVN_REV, SVN_MODS ? L"m" : wxEmptyString, wxString::FromUTF8(__DATE__).c_str() ); + SVN_REV, SVN_MODS ? L"m" : wxEmptyString, fromUTF8(__DATE__).c_str() ); } else { diff --git a/pcsx2/gui/Panels/PluginSelectorPanel.cpp b/pcsx2/gui/Panels/PluginSelectorPanel.cpp index f601a3f943..8f4e3a2434 100644 --- a/pcsx2/gui/Panels/PluginSelectorPanel.cpp +++ b/pcsx2/gui/Panels/PluginSelectorPanel.cpp @@ -559,13 +559,13 @@ void Panels::PluginSelectorPanel::EnumThread::ExecuteTask() DevCon.Status( "Plugin Enumeration Thread started..." ); wxGetApp().Ping(); // gives the gui thread some time to refresh - Sleep( 3 ); + Yield( 3 ); for( int curidx=0; curidx < m_master.FileCount(); ++curidx ) { DoNextPlugin( curidx ); if( (curidx & 3) == 3 ) wxGetApp().Ping(); // gives the gui thread some time to refresh - pthread_testcancel(); + TestCancel(); //Sleep(150); // uncomment this to slow down the selector, for debugging threading. } diff --git a/pcsx2/gui/Plugins.cpp b/pcsx2/gui/Plugins.cpp index aa71caaa4f..7241424f11 100644 --- a/pcsx2/gui/Plugins.cpp +++ b/pcsx2/gui/Plugins.cpp @@ -27,6 +27,8 @@ using namespace Threading; +static FnType_OnThreadComplete* Callback_PluginsLoadComplete = NULL; + // -------------------------------------------------------------------------------------- // LoadPluginsTask // -------------------------------------------------------------------------------------- @@ -37,76 +39,52 @@ using namespace Threading; // class LoadPluginsTask : public PersistentThread { + typedef PersistentThread _parent; + public: - Exception::PluginError* Ex_PluginError; - Exception::RuntimeError* Ex_RuntimeError; PluginManager* Result; protected: wxString m_folders[PluginId_Count]; public: - LoadPluginsTask( const wxString (&folders)[PluginId_Count] ) : - Ex_PluginError( NULL ) - , Ex_RuntimeError( NULL ) - , Result( NULL ) + LoadPluginsTask( const wxString (&folders)[PluginId_Count] ) : Result( NULL ) { for(int i=0; i _loadTask; - LoadPluginsTask::~LoadPluginsTask() throw() { - if( _loadTask ) - _loadTask.DetachPtr(); // avoids recursive deletion - PersistentThread::Cancel(); - _loadTask = NULL; } void LoadPluginsTask::ExecuteTask() { wxGetApp().Ping(); - Sleep(3); + Yield(3); + // This is for testing of the error handler... uncomment for fun? + //throw Exception::PluginError( PluginId_PAD, "This one is for testing the error handler!" ); + + Result = PluginManager_Create( m_folders ); +} + +void LoadPluginsTask::OnThreadCleanup() +{ wxCommandEvent evt( pxEVT_LoadPluginsComplete ); evt.SetClientData( this ); - - try - { - // This is for testing of the error handler... uncomment for fun? - //throw Exception::PluginError( PluginId_PAD, "This one is for testing the error handler!" ); - - Result = PluginManager_Create( m_folders ); - } - catch( Exception::PluginError& ex ) - { - Ex_PluginError = new Exception::PluginError( ex ); - } - catch( Exception::RuntimeError& innerEx ) - { - // Runtime errors are typically recoverable, so handle them here - // and prep them for re-throw on the main thread. - Ex_RuntimeError = new Exception::RuntimeError( - L"A runtime error occurred on the LoadPlugins thread.\n" + innerEx.FormatDiagnosticMessage(), - innerEx.FormatDisplayMessage() - ); - } - // anything else leave unhandled so that the debugger catches it! - wxGetApp().AddPendingEvent( evt ); + + _parent::OnThreadCleanup(); } ///////////////////////////////////////////////////////////////////////////////////////// @@ -132,30 +110,23 @@ int EnumeratePluginsInFolder( const wxDirName& searchpath, wxArrayString* dest ) wxDir::GetAllFiles( searchpath.ToString(), realdest, wxsFormat( pattern, wxDynamicLibrary::GetDllExt()), wxDIR_FILES ) : 0; } +void ConvertPluginFilenames( wxString (&passins)[PluginId_Count] ) +{ + const PluginInfo* pi = tbl_PluginInfo; do + { + passins[pi->id] = OverrideOptions.Filenames[pi->id].GetFullPath(); + + if( passins[pi->id].IsEmpty() || !wxFileExists( passins[pi->id] ) ) + passins[pi->id] = g_Conf->FullpathTo( pi->id ); + } while( ++pi, pi->shortname != NULL ); +} + +// boolean lock modified from the main thread only... +static bool plugin_load_lock = false; + void Pcsx2App::OnReloadPlugins( wxCommandEvent& evt ) { - ReloadPlugins(); -} - -void Pcsx2App::OnLoadPluginsComplete( wxCommandEvent& evt ) -{ - // scoped ptr ensures the thread object is cleaned up even on exception: - ScopedPtr killTask( (LoadPluginsTask*)evt.GetClientData() ); - m_CorePlugins = killTask->Result; - - if( !m_CorePlugins ) - { - if( killTask->Ex_PluginError != NULL ) - throw *killTask->Ex_PluginError; - if( killTask->Ex_RuntimeError != NULL ) - throw *killTask->Ex_RuntimeError; // Re-Throws generic threaded errors - } -} - -void Pcsx2App::ReloadPlugins() -{ - if( _loadTask ) return; - + if( plugin_load_lock ) return; m_CoreThread = NULL; m_CorePlugins = NULL; @@ -169,42 +140,79 @@ void Pcsx2App::ReloadPlugins() passins[pi->id] = g_Conf->FullpathTo( pi->id ); } while( ++pi, pi->shortname != NULL ); - _loadTask.Delete() = new LoadPluginsTask( passins ); + (new LoadPluginsTask( passins ))->Start(); // ... and when it finishes it posts up a OnLoadPluginsComplete(). Bye. :) + + plugin_load_lock = true; +} + +// Note: If the ClientData paremeter of wxCommandEvt is NULL, this message simply dispatches +// the plugged in listeners. +void Pcsx2App::OnLoadPluginsComplete( wxCommandEvent& evt ) +{ + plugin_load_lock = false; + + FnType_OnThreadComplete* fn_tmp = Callback_PluginsLoadComplete; + + if( evt.GetClientData() != NULL ) + { + if( !pxAssertDev( !m_CorePlugins, "LoadPlugins thread just finished, but CorePlugins state != NULL (odd!)." ) ) + m_CorePlugins = NULL; + + // scoped ptr ensures the thread object is cleaned up even on exception: + ScopedPtr killTask( (LoadPluginsTask*)evt.GetClientData() ); + killTask->RethrowException(); + m_CorePlugins = killTask->Result; + } + + if( fn_tmp != NULL ) fn_tmp( evt ); + + //m_evtsrc_PluginLoadFinished.Dispatch( evt ); } // Posts a message to the App to reload plugins. Plugins are loaded via a background thread // which is started on a pending event, so don't expect them to be ready "right now." -// If plugins are already loaded then no action is performed. -void LoadPluginsPassive() +// If plugins are already loaded, onComplete is invoked, and the function returns with no +// other actions performed. +void LoadPluginsPassive( FnType_OnThreadComplete* onComplete ) { - if( g_plugins ) return; + // Plugins already loaded? + if( wxGetApp().m_CorePlugins ) + { + if( onComplete ) onComplete( wxCommandEvent( pxEVT_LoadPluginsComplete ) ); + return; + } + + if( onComplete ) + Callback_PluginsLoadComplete = onComplete; wxCommandEvent evt( pxEVT_ReloadPlugins ); wxGetApp().AddPendingEvent( evt ); } -// Blocks until plugins have been successfully loaded, or throws an exception if -// the user cancels the loading procedure after error. If plugins are already loaded -// then no action is performed. +// Performs a blocking load of plugins. If the emulation thread is active, it is shut down +// automatically to prevent race conditions (it depends on plugins). +// +// Exceptions regarding plugin failures will propagate out of this function, so be prepared +// to handle them. +// +// Note that this is not recommended for most situations, but coding improper passive loads +// is probably worse, so if in doubt use this and air will fix it up for you later. :) +// void LoadPluginsImmediate() { - AllowFromMainThreadOnly(); - if( g_plugins ) return; + if( g_plugins != NULL ) return; - static int _reentrant = 0; - RecursionGuard guard( _reentrant ); + wxGetApp().m_CoreThread = NULL; + + wxString passins[PluginId_Count]; + ConvertPluginFilenames( passins ); + wxGetApp().m_CorePlugins = PluginManager_Create( passins ); - pxAssertMsg( !guard.IsReentrant(), "Recrsive calls to this function are prohibited." ); - wxGetApp().ReloadPlugins(); - while( _loadTask ) - { - Sleep( 10 ); - wxGetApp().ProcessPendingEvents(); - } } void UnloadPlugins() { + wxGetApp().m_CoreThread = NULL; wxGetApp().m_CorePlugins = NULL; } diff --git a/pcsx2/gui/Saveslots.cpp b/pcsx2/gui/Saveslots.cpp index 386e374f3d..1a6e729908 100644 --- a/pcsx2/gui/Saveslots.cpp +++ b/pcsx2/gui/Saveslots.cpp @@ -23,59 +23,10 @@ StartupParams g_Startup; -// returns true if the new state was loaded, or false if nothing happened. -void States_Load( const wxString& file ) -{ - sCoreThread.ShortSuspend(); - - try - { - StateCopy_LoadFromFile( file ); - //SysLoadState( file ); - //SysStatus( wxsFormat( _("Loaded State %s"), wxFileName( file ).GetFullName().c_str() ) ); - } - catch( Exception::BaseException& ) - { - // VM state is probably ruined. We'll need to recover from the in-memory backup. - //StateRecovery::Recover(); - } - - sApp.SysExecute(); -} - // Save state save-to-file (or slot) helpers. void States_Save( const wxString& file ) { - if( !SysHasValidState() ) - { - Msgbox::Alert( _("You need to start emulation first before you can save it's state.") ); - return; - } - - try - { - Console.Status( wxsFormat( L"Saving savestate to file: %s", file.c_str() ) ); - StateCopy_SaveToFile( file ); - SysStatus( wxsFormat( _("State saved to file: %s"), wxFileName( file ).GetFullName().c_str() ) ); - } - catch( Exception::BaseException& ex ) - { - // TODO: Implement a "pause the action and issue a popup" thing here. - // *OR* some kind of GS overlay... [for now we use the console] - - // Translation Tip: "Your emulation state has not been saved!" - - /*Msgbox::Alert( - wxsFormat( _("Error saving state to file: %s"), file.c_str() ) + - L"\n\n" + _("Error details:") + ex.DisplayMessage() - );*/ - - Console.Error( wxsFormat( - L"An error occurred while trying to save to file %s\n", file.c_str() ) + - L"Your emulation state has not been saved!\n" - L"\nError: " + ex.FormatDiagnosticMessage() - ); - } + StateCopy_SaveToFile( file ); } // -------------------------------------------------------------------------------------- @@ -95,22 +46,12 @@ bool States_isSlotUsed(int num) void States_FreezeCurrentSlot() { - Console.Status( "Saving savestate to slot %d...", StatesC ); - States_Save( SaveStateBase::GetFilename( StatesC ) ); + StateCopy_SaveToSlot( StatesC ); } void States_DefrostCurrentSlot() { - wxString file( SaveStateBase::GetFilename( StatesC ) ); - - if( !wxFileExists( file ) ) - { - Console.Notice( "Savestate slot %d is empty.", StatesC ); - return; - } - - Console.Status( "Loading savestate from slot %d...", StatesC ); - States_Load( file ); + StateCopy_LoadFromSlot( StatesC ); //SysStatus( wxsFormat( _("Loaded State (slot %d)"), StatesC ) ); } diff --git a/pcsx2/windows/VCprojects/pcsx2_2008.vcproj b/pcsx2/windows/VCprojects/pcsx2_2008.vcproj index 6f11e80973..13561b2b08 100644 --- a/pcsx2/windows/VCprojects/pcsx2_2008.vcproj +++ b/pcsx2/windows/VCprojects/pcsx2_2008.vcproj @@ -1804,6 +1804,10 @@ RelativePath="..\..\gui\IniInterface.cpp" > + + diff --git a/pcsx2/windows/WinConsolePipe.cpp b/pcsx2/windows/WinConsolePipe.cpp index bcadd4fbf5..892a4874a6 100644 --- a/pcsx2/windows/WinConsolePipe.cpp +++ b/pcsx2/windows/WinConsolePipe.cpp @@ -142,6 +142,9 @@ static __forceinline bool ReadPipe(HANDLE h_Pipe, ConsoleColors color ) return true; } +// -------------------------------------------------------------------------------------- +// WinPipeThread +// -------------------------------------------------------------------------------------- class WinPipeThread : public PersistentThread { typedef PersistentThread _parent; @@ -173,8 +176,7 @@ protected: SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL ); while( true ) { - Sleep( 100 ); - pthread_testcancel(); + Yield( 100 ); ReadPipe( m_outpipe, m_color ); } } @@ -189,6 +191,9 @@ protected: void OnThreadCleanup() { } }; +// -------------------------------------------------------------------------------------- +// WinPipeRedirection +// -------------------------------------------------------------------------------------- class WinPipeRedirection : public PipeRedirectionBase { DeclareNoncopyableObject( WinPipeRedirection ); @@ -221,7 +226,8 @@ WinPipeRedirection::WinPipeRedirection( FILE* stdstream ) : DWORD stdhandle = ( stdstream == stderr ) ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE; CreatePipe( m_pipe, m_file ); - SetStdHandle( stdhandle, m_file ); + if( 0 == SetStdHandle( stdhandle, m_file ) ) + throw Exception::Win32Error( "SetStdHandle failed." ); // In some cases GetStdHandle can fail, even when the one we just assigned above is valid. HANDLE newhandle = GetStdHandle(stdhandle); @@ -231,11 +237,11 @@ WinPipeRedirection::WinPipeRedirection( FILE* stdstream ) : if( newhandle == NULL ) throw Exception::RuntimeError( "GetStdHandle returned NULL." ); // not a Win32error (no error code) - m_crtFile = _open_osfhandle( (intptr_t)newhandle, _O_TEXT ); + m_crtFile = _open_osfhandle( (intptr_t)newhandle, _O_TEXT ); if( m_crtFile == -1 ) throw Exception::RuntimeError( "_open_osfhandle returned -1." ); - m_fp = _fdopen( m_crtFile, "w" ); + m_fp = _fdopen( m_crtFile, "w" ); if( m_fp == NULL ) throw Exception::RuntimeError( "_fdopen returned NULL." ); @@ -269,10 +275,22 @@ void WinPipeRedirection::Cleanup() throw() { fclose( m_fp ); m_fp = NULL; + + m_crtFile = -1; // crtFile is closed implicitly when closing m_fp + m_file = INVALID_HANDLE_VALUE; // m_file is closed implicitly when closing crtFile + } + + if( m_crtFile != -1 ) + { + _close( m_crtFile ); + m_crtFile = -1; // m_file is closed implicitly when closing crtFile } - // crtFile is closed implicitly when closing m_fp - // m_file is closed implicitly when closing crtFile + if( m_file != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_pipe ); + m_file = INVALID_HANDLE_VALUE; + } if( m_pipe != INVALID_HANDLE_VALUE ) { diff --git a/pcsx2/x86/iVU1micro.cpp b/pcsx2/x86/iVU1micro.cpp index 99055bf186..f76c2c04fa 100644 --- a/pcsx2/x86/iVU1micro.cpp +++ b/pcsx2/x86/iVU1micro.cpp @@ -30,7 +30,6 @@ int mVUdebugNow = 0; #ifdef DEBUG_COMPARE -#include static int runAmount = 0; void VUtestPause() { @@ -70,8 +69,9 @@ void VUtestPause() { SysPrintf("VU Mem CRC = 0x%08x\n", j); SysPrintf("EndPC = 0x%04x\n", VU1.VI[REG_TPC].UL); + // ... wtf?? --air for (int i = 0; i < 10000000; i++) { - Sleep(1000); + Threading::Sleep(1000); } } #else @@ -84,9 +84,6 @@ extern u32 vudump; #ifdef DEBUG_COMPARE2 -#ifndef DEBUG_COMPARE -#include -#endif __aligned16 u8 backVUregs[sizeof(VURegs)]; __aligned16 u8 cmpVUregs [sizeof(VURegs)]; diff --git a/plugins/spu2-x/src/Mixer.cpp b/plugins/spu2-x/src/Mixer.cpp index 244fa2a47a..f2fcd24fd0 100644 --- a/plugins/spu2-x/src/Mixer.cpp +++ b/plugins/spu2-x/src/Mixer.cpp @@ -238,20 +238,6 @@ static __forceinline s32 __fastcall GetNextDataBuffered( V_Core& thiscore, uint g_counter_cache_misses++; } - s16* sbuffer = cacheLine.Sampledata; - - //if( vc.LoopFlags & XAFLAG_LOOP ) - // vc.Prev1 = vc.Prev2 = 0; - - // saturated decoder - //XA_decode_block( sbuffer, memptr, vc.Prev1, vc.Prev2 ); - - // [Air]: Testing use of a new unsaturated decoder. (benchmark needed) - // Chances are the saturation isn't needed, but for a very few exception games. - // This is definitely faster than the above version, but is it by enough to - // merit possible lower compatibility? Especially now that games that make - // heavy use of the SPU2 via music or sfx will mostly use the cache anyway. - XA_decode_block_unsaturated( vc.SBuffer, memptr, vc.Prev1, vc.Prev2 ); } diff --git a/plugins/spu2-x/src/ReadInput.cpp b/plugins/spu2-x/src/ReadInput.cpp index a69fd0ac9a..09129a772f 100644 --- a/plugins/spu2-x/src/ReadInput.cpp +++ b/plugins/spu2-x/src/ReadInput.cpp @@ -63,8 +63,6 @@ StereoOut32 V_Core::ReadInput_HiFi() AdmaInProgress = 0; if(InputDataLeft >= 0x200) { - u8 k = (InputDataLeft >= InputDataProgress); - #ifdef PCM24_S1_INTERLEAVE AutoDMAReadBuffer(1); #else diff --git a/plugins/spu2-x/src/spu2sys.cpp b/plugins/spu2-x/src/spu2sys.cpp index 581deccda2..c2f24c045b 100644 --- a/plugins/spu2-x/src/spu2sys.cpp +++ b/plugins/spu2-x/src/spu2sys.cpp @@ -837,7 +837,7 @@ static void __fastcall RegWrite_Core( u16 value ) { bool irqe = thiscore.IRQEnable; int bit0 = thiscore.AttrBit0; - int bit4 = thiscore.AttrBit4; + //int bit4 = thiscore.AttrBit4; if( ((value>>15)&1) && (!thiscore.CoreEnabled) && (thiscore.InitDelay==0) ) // on init/reset {