From 5d37925617e226a4625ac1c04be39fb5f472cdd1 Mon Sep 17 00:00:00 2001 From: "Jake.Stine" Date: Tue, 27 Apr 2010 13:12:03 +0000 Subject: [PATCH] Major UI renovations! * Added versioning info to savestates! (along with some other bugfixes) * Simplified the Boot and System menus -- removed the old Skip BIOS hack and replaced it with the new BOOT2 injection method (which is considered hack-free at this time). * Removed lots of UI deadlock gotchas. * Some new confirmation dialogs and better error handling. * Implemented an exclusive SysExecutor thread, which serves the purpose of executing system/VM commands and events in uninterrupted order (including suspend, resume, savestates, etc.) * ... and probably broke linux! git-svn-id: http://pcsx2.googlecode.com/svn/trunk@2911 96395faa-99c1-11dd-bbfe-3dabce05a288 --- common/build/Utilities/Utilities.cbp | 3 + common/build/Utilities/utilities.vcproj | 24 +- common/include/Utilities/Console.h | 6 + common/include/Utilities/EventSource.inl | 4 +- common/include/Utilities/Exceptions.h | 16 +- common/include/Utilities/General.h | 33 +- common/include/Utilities/Path.h | 1 + common/include/Utilities/PersistentThread.h | 5 +- common/include/Utilities/RwMutex.h | 90 +++ common/include/Utilities/Threading.h | 107 +--- common/include/Utilities/wxAppWithHelpers.h | 299 ++-------- common/include/Utilities/wxGuiTools.h | 4 + common/src/Utilities/Console.cpp | 17 +- common/src/Utilities/Exceptions.cpp | 19 +- common/src/Utilities/Mutex.cpp | 94 ++- common/src/Utilities/RwMutex.cpp | 112 ++++ common/src/Utilities/Semaphore.cpp | 14 +- common/src/Utilities/StringHelpers.cpp | 11 +- common/src/Utilities/ThreadTools.cpp | 91 +-- common/src/Utilities/ThreadingInternal.h | 1 - common/src/Utilities/wxAppWithHelpers.cpp | 480 +++++++++++++--- common/src/Utilities/wxHelpers.cpp | 17 +- common/vsprops/pthreads.vsprops | 2 +- pcsx2/CDVD/CDVD.cpp | 14 +- pcsx2/CDVD/CDVDaccess.cpp | 2 +- pcsx2/CDVD/IsoFS/IsoFS.cpp | 2 +- pcsx2/Config.h | 4 +- pcsx2/Counters.cpp | 10 +- pcsx2/GSState.cpp | 4 +- pcsx2/Linux/pcsx2.cbp | 12 +- pcsx2/MTGS.cpp | 9 +- pcsx2/PathDefs.h | 2 - pcsx2/Pcsx2Config.cpp | 2 +- pcsx2/PluginManager.cpp | 534 +++++++++++------ pcsx2/Plugins.h | 116 ++-- pcsx2/R5900.cpp | 2 +- pcsx2/RecoverySystem.cpp | 598 -------------------- pcsx2/SaveState.cpp | 44 +- pcsx2/SaveState.h | 11 +- pcsx2/System.cpp | 6 +- pcsx2/System/SysCoreThread.cpp | 99 +--- pcsx2/System/SysThreadBase.cpp | 95 ++-- pcsx2/System/SysThreads.h | 142 ++--- pcsx2/ZipTools/FolderDesc.txt | 10 + pcsx2/ZipTools/ThreadedZipTools.h | 103 ++++ pcsx2/ZipTools/thread_gzip.cpp | 79 +++ pcsx2/ZipTools/thread_lzma.cpp | 17 + pcsx2/gui/App.h | 179 ++---- pcsx2/gui/AppConfig.cpp | 9 +- pcsx2/gui/AppConfig.h | 24 +- pcsx2/gui/AppCorePlugins.cpp | 419 ++++++++++++++ pcsx2/gui/AppCorePlugins.h | 48 ++ pcsx2/gui/AppCoreThread.cpp | 497 ++++++++++++---- pcsx2/gui/AppCoreThread.h | 143 +++++ pcsx2/gui/AppEventListeners.h | 26 + pcsx2/gui/AppEventSources.cpp | 74 ++- pcsx2/gui/AppForwardDefs.h | 6 +- pcsx2/gui/AppInit.cpp | 115 ++-- pcsx2/gui/AppMain.cpp | 586 +++++++++---------- pcsx2/gui/AppSaveStates.h | 60 +- pcsx2/gui/ApplyState.h | 2 + pcsx2/gui/ConsoleLogger.h | 4 +- pcsx2/gui/Dialogs/FirstTimeWizard.cpp | 56 +- pcsx2/gui/Dialogs/ModalPopups.h | 10 +- pcsx2/gui/Dialogs/PickUserModeDialog.cpp | 2 +- pcsx2/gui/ExecutorThread.cpp | 391 +++++++++++++ pcsx2/gui/FrameForGS.cpp | 6 +- pcsx2/gui/GSFrame.h | 120 ++++ pcsx2/gui/GlobalCommands.cpp | 12 +- pcsx2/gui/IsoDropTarget.cpp | 43 +- pcsx2/gui/IsoDropTarget.h | 4 + pcsx2/gui/MSWstuff.cpp | 3 +- pcsx2/gui/MainFrame.cpp | 136 +++-- pcsx2/gui/MainFrame.h | 112 +--- pcsx2/gui/MainMenuClicks.cpp | 281 ++++++--- pcsx2/gui/MessageBoxes.cpp | 108 +--- pcsx2/gui/Panels/ConfigurationPanels.h | 21 +- pcsx2/gui/Panels/DirPickerPanel.cpp | 34 +- pcsx2/gui/Panels/MiscPanelStuff.cpp | 28 +- pcsx2/gui/Panels/PluginSelectorPanel.cpp | 257 +++++++-- pcsx2/gui/Plugins.cpp | 396 ------------- pcsx2/gui/RecentIsoList.cpp | 14 +- pcsx2/gui/SysState.cpp | 310 ++++++++++ pcsx2/gui/UpdateUI.cpp | 80 +++ pcsx2/gui/pxEventThread.h | 188 ++++++ pcsx2/gui/pxLogTextCtrl.cpp | 6 +- pcsx2/windows/VCprojects/pcsx2_2008.vcproj | 66 ++- pcsx2/windows/WinCompressNTFS.cpp | 6 +- pcsx2/windows/WinConsolePipe.cpp | 2 +- 89 files changed, 5095 insertions(+), 3156 deletions(-) create mode 100644 common/include/Utilities/RwMutex.h create mode 100644 common/src/Utilities/RwMutex.cpp delete mode 100644 pcsx2/RecoverySystem.cpp create mode 100644 pcsx2/ZipTools/FolderDesc.txt create mode 100644 pcsx2/ZipTools/ThreadedZipTools.h create mode 100644 pcsx2/ZipTools/thread_gzip.cpp create mode 100644 pcsx2/ZipTools/thread_lzma.cpp create mode 100644 pcsx2/gui/AppCorePlugins.cpp create mode 100644 pcsx2/gui/AppCorePlugins.h create mode 100644 pcsx2/gui/AppCoreThread.h create mode 100644 pcsx2/gui/ExecutorThread.cpp create mode 100644 pcsx2/gui/GSFrame.h delete mode 100644 pcsx2/gui/Plugins.cpp create mode 100644 pcsx2/gui/SysState.cpp create mode 100644 pcsx2/gui/UpdateUI.cpp create mode 100644 pcsx2/gui/pxEventThread.h diff --git a/common/build/Utilities/Utilities.cbp b/common/build/Utilities/Utilities.cbp index 42f4375626..53b55da43b 100644 --- a/common/build/Utilities/Utilities.cbp +++ b/common/build/Utilities/Utilities.cbp @@ -156,12 +156,14 @@ + + @@ -182,6 +184,7 @@ + diff --git a/common/build/Utilities/utilities.vcproj b/common/build/Utilities/utilities.vcproj index a035c6d7a3..8d3bb79d7b 100644 --- a/common/build/Utilities/utilities.vcproj +++ b/common/build/Utilities/utilities.vcproj @@ -207,10 +207,6 @@ RelativePath="..\..\src\Utilities\Console.cpp" > - - @@ -426,6 +422,10 @@ RelativePath="..\..\src\Utilities\Mutex.cpp" > + + @@ -501,10 +501,6 @@ RelativePath="..\..\include\Utilities\Path.h" > - - @@ -513,6 +509,10 @@ RelativePath="..\..\include\Utilities\pxCheckBox.h" > + + @@ -556,6 +556,14 @@ + + + + diff --git a/common/include/Utilities/Console.h b/common/include/Utilities/Console.h index 11855c261c..b820f4ec0d 100644 --- a/common/include/Utilities/Console.h +++ b/common/include/Utilities/Console.h @@ -154,8 +154,14 @@ public: } virtual ~ConsoleIndentScope() throw() + { + if( m_amount != 0 ) Console.SetIndent( -m_amount ); + } + + void EndScope() { Console.SetIndent( -m_amount ); + m_amount = 0; } }; diff --git a/common/include/Utilities/EventSource.inl b/common/include/Utilities/EventSource.inl index 58a46f86fd..f87f19c5c1 100644 --- a/common/include/Utilities/EventSource.inl +++ b/common/include/Utilities/EventSource.inl @@ -15,6 +15,8 @@ #pragma once +#include "Threading.h" + using Threading::ScopedLock; template< typename ListenerType > @@ -77,7 +79,7 @@ __forceinline void EventSource::_DispatchRaw( ListenerIterator ite Console.Error( L"Ignoring runtime error thrown from event listener: " + ex.FormatDiagnosticMessage() ); } } - catch( Exception::BaseException& ex ) + catch( BaseException& ex ) { if( IsDevBuild ) { diff --git a/common/include/Utilities/Exceptions.h b/common/include/Utilities/Exceptions.h index a645438a92..45cd0fa204 100644 --- a/common/include/Utilities/Exceptions.h +++ b/common/include/Utilities/Exceptions.h @@ -27,7 +27,7 @@ // friendly error log in their wake. // #define __DESTRUCTOR_CATCHALL( funcname ) \ - catch( Exception::BaseException& ex ) \ + catch( BaseException& ex ) \ { \ Console.Error( "Unhandled BaseException in %s (ignored!):", funcname ); \ Console.Error( ex.FormatDiagnosticMessage() ); \ @@ -155,6 +155,8 @@ namespace Exception bool IsSilent; public: DEFINE_RUNTIME_EXCEPTION( RuntimeError, wxLt("An unhandled runtime error has occurred, somewhere in the depths of Pcsx2's cluttered brain-matter.") ) + + RuntimeError( const std::runtime_error& ex, const wxString& prefix=wxEmptyString ); }; // -------------------------------------------------------------------------------------- @@ -243,7 +245,7 @@ namespace Exception // --------------------------------------------------------------------------------------- // Streaming (file) Exceptions: - // Stream / BadStream / CreateStream / FileNotFound / AccessDenied / EndOfStream + // Stream / BadStream / CannotCreateStream / FileNotFound / AccessDenied / EndOfStream // --------------------------------------------------------------------------------------- #define DEFINE_STREAM_EXCEPTION( classname, defmsg ) \ @@ -308,22 +310,22 @@ namespace Exception // A generic exception for odd-ball stream creation errors. // - class CreateStream : public virtual Stream + class CannotCreateStream : public virtual Stream { public: - DEFINE_STREAM_EXCEPTION( CreateStream, wxLt("File could not be created or opened.") ) + DEFINE_STREAM_EXCEPTION( CannotCreateStream, wxLt("File could not be created or opened.") ) }; // Exception thrown when an attempt to open a non-existent file is made. // (this exception can also mean file permissions are invalid) // - class FileNotFound : public virtual CreateStream + class FileNotFound : public virtual CannotCreateStream { public: DEFINE_STREAM_EXCEPTION( FileNotFound, wxLt("File not found.") ) }; - class AccessDenied : public virtual CreateStream + class AccessDenied : public virtual CannotCreateStream { public: DEFINE_STREAM_EXCEPTION( AccessDenied, wxLt("Permission denied to file.") ) @@ -358,3 +360,5 @@ namespace Exception }; #endif } + +using Exception::BaseException; diff --git a/common/include/Utilities/General.h b/common/include/Utilities/General.h index c40128dd25..61e3c76025 100644 --- a/common/include/Utilities/General.h +++ b/common/include/Utilities/General.h @@ -53,6 +53,9 @@ public: bool IsReentrant() const { return Counter > 1; } }; +// -------------------------------------------------------------------------------------- +// ICloneable / IActionInvocation / IDeletableObject +// -------------------------------------------------------------------------------------- class IActionInvocation { public: @@ -60,8 +63,30 @@ public: virtual void InvokeAction()=0; }; +class ICloneable +{ +public: + virtual ICloneable* Clone() const=0; +}; + +class IDeletableObject +{ +public: + virtual ~IDeletableObject() throw() {} + + virtual void DeleteSelf()=0; + virtual bool IsBeingDeleted()=0; + +protected: + // This function is GUI implementation dependent! It's implemented by PCSX2's AppHost, + // but if the SysCore is being linked to another front end, you'll need to implement this + // yourself. Most GUIs have built in message pumps. If a platform lacks one then you'll + // need to implement one yourself (yay?). + virtual void DoDeletion()=0; +}; + // -------------------------------------------------------------------------------------- -// IDeletableObject +// BaseDeletableObject // -------------------------------------------------------------------------------------- // Oh the fruits and joys of multithreaded C++ coding conundrums! This class provides a way // to be deleted from arbitraty threads, or to delete themselves (which is considered unsafe @@ -83,14 +108,14 @@ public: // (sigh). And, finally, it requires quite a bit of red tape to implement wxObjects because // of the wx-custom runtime type information. So I made my own. // -class IDeletableObject +class BaseDeletableObject : public virtual IDeletableObject { protected: volatile long m_IsBeingDeleted; public: - IDeletableObject(); - virtual ~IDeletableObject() throw(); + BaseDeletableObject(); + virtual ~BaseDeletableObject() throw(); void DeleteSelf(); bool IsBeingDeleted() { return !!m_IsBeingDeleted; } diff --git a/common/include/Utilities/Path.h b/common/include/Utilities/Path.h index f4a70fde13..02aacd602f 100644 --- a/common/include/Utilities/Path.h +++ b/common/include/Utilities/Path.h @@ -60,6 +60,7 @@ public: bool IsWritable() const { return IsDirWritable(); } bool IsReadable() const { return IsDirReadable(); } bool Exists() const { return DirExists(); } + bool FileExists() const { return wxFileName::FileExists(); } bool IsOk() const { return wxFileName::IsOk(); } bool IsRelative() const { return wxFileName::IsRelative(); } bool IsAbsolute() const { return wxFileName::IsAbsolute(); } diff --git a/common/include/Utilities/PersistentThread.h b/common/include/Utilities/PersistentThread.h index fd72cdd213..e0bc599c64 100644 --- a/common/include/Utilities/PersistentThread.h +++ b/common/include/Utilities/PersistentThread.h @@ -123,14 +123,14 @@ namespace Threading Semaphore m_sem_event; // general wait event that's needed by most threads Semaphore m_sem_startup; // startup sync tool Mutex m_lock_InThread; // used for canceling and closing threads in a deadlock-safe manner - MutexLockRecursive m_lock_start; // used to lock the Start() code from starting simultaneous threads accidentally. + MutexRecursive 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. // exception handle, set non-NULL if the thread terminated with an exception // Use RethrowException() to re-throw the exception using its original exception type. - ScopedPtr m_except; + ScopedPtr m_except; EventSource m_evtsrc_OnDelete; @@ -148,6 +148,7 @@ namespace Threading virtual bool Cancel( const wxTimeSpan& timeout ); virtual bool Detach(); virtual void Block(); + virtual bool Block( const wxTimeSpan& timeout ); virtual void RethrowException() const; void AddListener( EventListener_Thread& evt ); diff --git a/common/include/Utilities/RwMutex.h b/common/include/Utilities/RwMutex.h new file mode 100644 index 0000000000..2ffd3029b5 --- /dev/null +++ b/common/include/Utilities/RwMutex.h @@ -0,0 +1,90 @@ +/* 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 "Threading.h" + +namespace Threading +{ +// -------------------------------------------------------------------------------------- +// RwMutex +// -------------------------------------------------------------------------------------- + class RwMutex + { + DeclareNoncopyableObject(RwMutex); + + protected: + pthread_rwlock_t m_rwlock; + + public: + RwMutex(); + virtual ~RwMutex() throw(); + + virtual void AcquireRead(); + virtual void AcquireWrite(); + virtual bool TryAcquireRead(); + virtual bool TryAcquireWrite(); + + virtual void Release(); + }; + +// -------------------------------------------------------------------------------------- +// BaseScopedReadWriteLock +// -------------------------------------------------------------------------------------- + class BaseScopedReadWriteLock + { + DeclareNoncopyableObject(BaseScopedReadWriteLock); + + protected: + RwMutex& m_lock; + bool m_IsLocked; + + public: + BaseScopedReadWriteLock( RwMutex& locker ) + : m_lock( locker ) + { + } + + virtual ~BaseScopedReadWriteLock() throw(); + + void Release(); + bool IsLocked() const { return m_IsLocked; } + }; + +// -------------------------------------------------------------------------------------- +// ScopedReadLock / ScopedWriteLock +// -------------------------------------------------------------------------------------- + class ScopedReadLock : public BaseScopedReadWriteLock + { + public: + ScopedReadLock( RwMutex& locker ); + virtual ~ScopedReadLock() throw() {} + + void Acquire(); + }; + + class ScopedWriteLock : public BaseScopedReadWriteLock + { + public: + ScopedWriteLock( RwMutex& locker ); + virtual ~ScopedWriteLock() throw() {} + + void Acquire(); + + protected: + ScopedWriteLock( RwMutex& locker, bool isTryLock ); + }; +} diff --git a/common/include/Utilities/Threading.h b/common/include/Utilities/Threading.h index c8f9fc3a58..30398313d5 100644 --- a/common/include/Utilities/Threading.h +++ b/common/include/Utilities/Threading.h @@ -24,7 +24,7 @@ #undef Yield // release the burden of windows.h global namespace spam. -#define AffinityAssert_AllowFromMain() \ +#define AffinityAssert_AllowFrom_MainUI() \ pxAssertMsg( wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only." ) // -------------------------------------------------------------------------------------- @@ -48,7 +48,9 @@ class wxTimeSpan; namespace Threading { class PersistentThread; + class RwMutex; + extern void pxTestCancel(); extern PersistentThread* pxGetCurrentThread(); extern wxString pxGetCurrentThreadName(); extern u64 GetThreadCpuTime(); @@ -110,43 +112,6 @@ namespace Exception BaseException::InitBaseEx( msg_diag, msg_user ); } }; - -#if wxUSE_GUI - -// -------------------------------------------------------------------------------------- -// ThreadDeadlock Exception -// -------------------------------------------------------------------------------------- -// This exception is thrown by Semaphore and Mutex Wait/Acquire functions if a blocking wait is -// needed due to gui Yield recursion, and the timeout period for deadlocking (usually 3 seconds) -// is reached before the lock becomes available. This exception cannot occur in the following -// conditions: -// * If the user-specified timeout is less than the deadlock timeout. -// * If the method is run from a thread *other* than the MainGui thread. -// - class ThreadDeadlock : public virtual BaseThreadError - { - public: - DEFINE_EXCEPTION_COPYTORS( ThreadDeadlock ) - - explicit ThreadDeadlock( Threading::PersistentThread* _thread=NULL, const char* msg="Blocking action timed out waiting for '%s' (potential thread deadlock)." ) - { - m_thread = _thread; - BaseException::InitBaseEx( msg ); - } - - ThreadDeadlock( Threading::PersistentThread& _thread, const char* msg="Blocking action timed out waiting for '%s' (potential thread deadlock)." ) - { - m_thread = &_thread; - BaseException::InitBaseEx( msg ); - } - - ThreadDeadlock( Threading::PersistentThread& _thread, const wxString& msg_diag, const wxString& msg_user ) - { - m_thread = &_thread; - BaseException::InitBaseEx( msg_diag, msg_user ); - } - }; -#endif } @@ -298,17 +263,19 @@ namespace Threading void Wait(); bool Wait( const wxTimeSpan& timeout ); + void WaitWithoutYield(); + bool WaitWithoutYield( const wxTimeSpan& timeout ); protected: // empty constructor used by MutexLockRecursive Mutex( bool ) {} }; - class MutexLockRecursive : public Mutex + class MutexRecursive : public Mutex { public: - MutexLockRecursive(); - virtual ~MutexLockRecursive() throw(); + MutexRecursive(); + virtual ~MutexRecursive() throw(); virtual bool IsRecursive() const { return true; } }; @@ -319,60 +286,46 @@ namespace Threading // generally clean) method of locking code inside a function or conditional block. The lock // will be automatically released on any return or exit from the function. // + // Const qualification note: + // ScopedLock takes const instances of the mutex, even though the mutex is modified + // by locking and unlocking. Two rationales: + // + // 1) when designing classes with accessors (GetString, GetValue, etc) that need mutexes, + // this class needs a const hack to allow those accessors to be const (which is typically + // *very* important). + // + // 2) The state of the Mutex is guaranteed to be unchanged when the calling function or + // scope exits, by any means. Only via manual calls to Release or Acquire does that + // change, and typically those are only used in very special circumstances of their own. + // class ScopedLock { DeclareNoncopyableObject(ScopedLock); protected: - Mutex& m_lock; + Mutex* m_lock; bool m_IsLocked; public: - virtual ~ScopedLock() throw() - { - if( m_IsLocked ) - m_lock.Release(); - } - - ScopedLock( Mutex& locker ) : - m_lock( locker ) - { - m_IsLocked = true; - m_lock.Acquire(); - } - - // Provides manual unlocking of a scoped lock prior to object destruction. - void Release() - { - if( !m_IsLocked ) return; - m_IsLocked = false; - m_lock.Release(); - } - - // provides manual locking of a scoped lock, to re-lock after a manual unlocking. - void Acquire() - { - if( m_IsLocked ) return; - m_lock.Acquire(); - m_IsLocked = true; - } + virtual ~ScopedLock() throw(); + explicit ScopedLock( const Mutex* locker=NULL ); + explicit ScopedLock( const Mutex& locker ); + void AssignAndLock( const Mutex& locker ); + void AssignAndLock( const Mutex* locker ); + void Release(); + void Acquire(); bool IsLocked() const { return m_IsLocked; } protected: // Special constructor used by ScopedTryLock - ScopedLock( Mutex& locker, bool isTryLock ) : - m_lock( locker ) - { - m_IsLocked = isTryLock ? m_lock.TryAcquire() : false; - } - + ScopedLock( const Mutex& locker, bool isTryLock ); }; class ScopedTryLock : public ScopedLock { public: - ScopedTryLock( Mutex& locker ) : ScopedLock( locker, true ) { } + ScopedTryLock( const Mutex& locker ) : ScopedLock( locker, true ) { } virtual ~ScopedTryLock() throw() {} bool Failed() const { return !m_IsLocked; } }; diff --git a/common/include/Utilities/wxAppWithHelpers.h b/common/include/Utilities/wxAppWithHelpers.h index e304089bb9..a9e37a94d5 100644 --- a/common/include/Utilities/wxAppWithHelpers.h +++ b/common/include/Utilities/wxAppWithHelpers.h @@ -19,109 +19,12 @@ #include "Threading.h" #include "wxGuiTools.h" +#include "pxEvents.h" using namespace Threading; -class pxPingEvent; -class pxMessageBoxEvent; +class pxSynchronousCommandEvent; -BEGIN_DECLARE_EVENT_TYPES() - DECLARE_EVENT_TYPE( pxEvt_Ping, -1 ) - DECLARE_EVENT_TYPE( pxEvt_IdleEventQueue, -1 ) - DECLARE_EVENT_TYPE( pxEvt_MessageBox, -1 ) - DECLARE_EVENT_TYPE( pxEvt_DeleteObject, -1 ) - //DECLARE_EVENT_TYPE( pxEvt_Assertion, -1 ) -END_DECLARE_EVENT_TYPES() - -struct MsgboxEventResult -{ - Semaphore WaitForMe; - int result; - - MsgboxEventResult() - { - result = 0; - } -}; - -// -------------------------------------------------------------------------------------- -// MsgButtons -// -------------------------------------------------------------------------------------- -class MsgButtons -{ -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, - m_Reset :1, - m_Close :1; - BITFIELD_END - - wxString m_CustomLabel; - -public: - MsgButtons() { bitset = 0; } - - MsgButtons& OK() { m_OK = true; return *this; } - MsgButtons& Cancel() { m_Cancel = true; return *this; } - MsgButtons& Apply() { m_Apply = true; return *this; } - MsgButtons& Yes() { m_Yes = true; return *this; } - MsgButtons& No() { m_No = true; return *this; } - MsgButtons& ToAll() { m_AllowToAll = true; return *this; } - - MsgButtons& Abort() { m_Abort = true; return *this; } - MsgButtons& Retry() { m_Retry = true; return *this; } - MsgButtons& Ignore() { m_Ignore = true; return *this; } - MsgButtons& Reset() { m_Reset = true; return *this; } - MsgButtons& Close() { m_Close = true; return *this; } - - MsgButtons& Custom( const wxString& label) - { - m_CustomLabel = label; - return *this; - } - - MsgButtons& OKCancel() { m_OK = m_Cancel = true; return *this; } - MsgButtons& YesNo() { m_Yes = m_No = 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 HasReset() const { return m_Reset; } - bool HasClose() const { return m_Close; } - - bool HasCustom() const { return !m_CustomLabel.IsEmpty(); } - const wxString& GetCustomLabel() const { return m_CustomLabel; } - - bool Allows( wxWindowID id ) const; - void SetBestFocus( wxWindow* dialog ) const; - void SetBestFocus( wxWindow& dialog ) const; - - bool operator ==( const MsgButtons& right ) const - { - return OpEqu( bitset ); - } - - bool operator !=( const MsgButtons& right ) const - { - return !OpEqu( bitset ); - } -}; // -------------------------------------------------------------------------------------- // ModalButtonPanel @@ -138,151 +41,7 @@ public: virtual void OnActionButtonClicked( wxCommandEvent& evt ); }; -// -------------------------------------------------------------------------------------- -// BaseMessageBoxEvent -// -------------------------------------------------------------------------------------- -class BaseMessageBoxEvent : public wxEvent -{ - DECLARE_DYNAMIC_CLASS_NO_ASSIGN(BaseMessageBoxEvent) - -protected: - MsgboxEventResult* m_Instdata; - wxString m_Content; - -public: - virtual ~BaseMessageBoxEvent() throw() { } - virtual BaseMessageBoxEvent *Clone() const { return new BaseMessageBoxEvent(*this); } - - explicit BaseMessageBoxEvent( int msgtype=pxEvt_MessageBox, const wxString& content=wxEmptyString ); - BaseMessageBoxEvent( MsgboxEventResult& instdata, const wxString& content ); - BaseMessageBoxEvent( const wxString& content ); - BaseMessageBoxEvent( const BaseMessageBoxEvent& event ); - - BaseMessageBoxEvent& SetInstData( MsgboxEventResult& instdata ); - - virtual void IssueDialog(); - -protected: - virtual int _DoDialog() const; -}; - -// -------------------------------------------------------------------------------------- -// pxMessageBoxEvent -// -------------------------------------------------------------------------------------- -// This event type is used to transfer message boxes to the main UI thread, and return the -// result of the box. It's the only way a message box can be issued from non-main threads -// with complete safety in wx2.8. -// -// For simplicity sake this message box only supports two basic designs. The main design -// is a generic message box with confirmation buttons of your choosing. Additionally you -// can specify a "scrollableContent" text string, which is added into a read-only richtext -// control similar to the console logs and such. -// -// Future consideration: If wxWidgets 3.0 has improved thread safety, then it should probably -// be reasonable for it to work with a more flexable model where the dialog can be created -// on a child thread, passed to the main thread, where ShowModal() is run (keeping the nested -// message pumps on the main thread where they belong). But so far this is not possible, -// because of various subtle issues in wx2.8 design. -// -class pxMessageBoxEvent : public BaseMessageBoxEvent -{ - typedef BaseMessageBoxEvent _parent; - DECLARE_DYNAMIC_CLASS_NO_ASSIGN(pxMessageBoxEvent) - -protected: - wxString m_Title; - MsgButtons m_Buttons; - -public: - virtual ~pxMessageBoxEvent() throw() { } - virtual pxMessageBoxEvent *Clone() const { return new pxMessageBoxEvent(*this); } - - explicit pxMessageBoxEvent( int msgtype=pxEvt_MessageBox ); - - pxMessageBoxEvent( MsgboxEventResult& instdata, const wxString& title, const wxString& content, const MsgButtons& buttons ); - pxMessageBoxEvent( const wxString& title, const wxString& content, const MsgButtons& buttons ); - pxMessageBoxEvent( const pxMessageBoxEvent& event ); - - pxMessageBoxEvent& SetInstData( MsgboxEventResult& instdata ); - -protected: - virtual int _DoDialog() const; -}; - -// -------------------------------------------------------------------------------------- -// pxAssertionEvent -// -------------------------------------------------------------------------------------- -class pxAssertionEvent : public BaseMessageBoxEvent -{ - typedef BaseMessageBoxEvent _parent; - DECLARE_DYNAMIC_CLASS_NO_ASSIGN( pxAssertionEvent ) - -protected: - wxString m_Stacktrace; - -public: - virtual ~pxAssertionEvent() throw() { } - virtual pxAssertionEvent *Clone() const { return new pxAssertionEvent(*this); } - - pxAssertionEvent(); - pxAssertionEvent( MsgboxEventResult& instdata, const wxString& content, const wxString& trace ); - pxAssertionEvent( const wxString& content, const wxString& trace ); - pxAssertionEvent( const pxAssertionEvent& event ); - - pxAssertionEvent& SetInstData( MsgboxEventResult& instdata ); - pxAssertionEvent& SetStacktrace( const wxString& trace ); - -protected: - virtual int _DoDialog() const; - -}; - -// -------------------------------------------------------------------------------------- -// pxStuckThreadEvent -// -------------------------------------------------------------------------------------- -class pxStuckThreadEvent : public BaseMessageBoxEvent -{ - typedef BaseMessageBoxEvent _parent; - DECLARE_DYNAMIC_CLASS_NO_ASSIGN( pxStuckThreadEvent ) - -protected: - Threading::PersistentThread& m_Thread; - -public: - virtual ~pxStuckThreadEvent() throw() { } - virtual pxStuckThreadEvent *Clone() const { return new pxStuckThreadEvent(*this); } - - pxStuckThreadEvent(); - pxStuckThreadEvent( PersistentThread& thr ); - pxStuckThreadEvent( MsgboxEventResult& instdata, PersistentThread& thr ); - pxStuckThreadEvent( const pxStuckThreadEvent& src); - -protected: - virtual int _DoDialog() const; -}; - -// -------------------------------------------------------------------------------------- -// pxPingEvent -// -------------------------------------------------------------------------------------- -class pxPingEvent : public wxEvent -{ - DECLARE_DYNAMIC_CLASS_NO_ASSIGN(pxPingEvent) - -protected: - Semaphore* m_PostBack; - -public: - virtual ~pxPingEvent() throw() { } - virtual pxPingEvent *Clone() const { return new pxPingEvent(*this); } - - explicit pxPingEvent( int msgtype, Semaphore* sema=NULL ); - explicit pxPingEvent( Semaphore* sema=NULL ); - pxPingEvent( const pxPingEvent& src ); - - Semaphore* GetSemaphore() { return m_PostBack; } -}; - -typedef void FnType_VoidMethod(); +typedef void FnType_Void(); // -------------------------------------------------------------------------------------- // wxAppWithHelpers @@ -290,15 +49,12 @@ typedef void FnType_VoidMethod(); class wxAppWithHelpers : public wxApp { typedef wxApp _parent; - + DECLARE_DYNAMIC_CLASS(wxAppWithHelpers) protected: - std::vector m_PingWhenIdle; - std::vector m_DeleteWhenIdle; std::vector m_IdleEventQueue; - Threading::Mutex m_DeleteIdleLock; - wxTimer m_PingTimer; + Threading::Mutex m_IdleEventMutex; wxTimer m_IdleEventTimer; public: @@ -306,34 +62,59 @@ public: virtual ~wxAppWithHelpers() {} void CleanUp(); - - void DeleteObject( IDeletableObject& obj ); - void DeleteObject( IDeletableObject* obj ) + + void DeleteObject( BaseDeletableObject& obj ); + void DeleteObject( BaseDeletableObject* obj ) { if( obj == NULL ) return; DeleteObject( *obj ); } + void DeleteThread( Threading::PersistentThread& obj ); + void DeleteThread( Threading::PersistentThread* obj ) + { + if( obj == NULL ) return; + DeleteThread( *obj ); + } + void PostCommand( void* clientData, int evtType, int intParam=0, long longParam=0, const wxString& stringParam=wxEmptyString ); void PostCommand( int evtType, int intParam=0, long longParam=0, const wxString& stringParam=wxEmptyString ); - void PostMethod( FnType_VoidMethod* method ); + void PostMethod( FnType_Void* method ); + void PostIdleMethod( FnType_Void* method ); + void ProcessMethod( void (*method)() ); + + sptr ProcessCommand( void* clientData, int evtType, int intParam=0, long longParam=0, const wxString& stringParam=wxEmptyString ); + sptr ProcessCommand( int evtType, int intParam=0, long longParam=0, const wxString& stringParam=wxEmptyString ); + + void ProcessAction( pxInvokeActionEvent& evt ); + void PostAction( const pxInvokeActionEvent& evt ); + + bool PostMethodMyself( void (*method)() ); void Ping(); bool OnInit(); //int OnExit(); + void AddIdleEvent( const wxEvent& evt ); + + void PostEvent( const wxEvent& evt ); + bool ProcessEvent( wxEvent& evt ); + bool ProcessEvent( wxEvent* evt ); + + bool ProcessEvent( pxInvokeActionEvent& evt ); + bool ProcessEvent( pxInvokeActionEvent* evt ); + protected: void IdleEventDispatcher( const char* action ); - void PingDispatcher( const char* action ); - void DeletionDispatcher(); void OnIdleEvent( wxIdleEvent& evt ); - void OnPingEvent( pxPingEvent& evt ); - void OnAddEventToIdleQueue( wxEvent& evt ); - void OnPingTimeout( wxTimerEvent& evt ); + void OnStartIdleEventTimer( wxEvent& evt ); void OnIdleEventTimeout( wxTimerEvent& evt ); - void OnMessageBox( BaseMessageBoxEvent& evt ); void OnDeleteObject( wxCommandEvent& evt ); + void OnDeleteThread( wxCommandEvent& evt ); + void OnSynchronousCommand( pxSynchronousCommandEvent& evt ); + void OnInvokeAction( pxInvokeActionEvent& evt ); + }; namespace Msgbox diff --git a/common/include/Utilities/wxGuiTools.h b/common/include/Utilities/wxGuiTools.h index 2ee9535599..7102746eda 100644 --- a/common/include/Utilities/wxGuiTools.h +++ b/common/include/Utilities/wxGuiTools.h @@ -291,6 +291,10 @@ namespace pxSizerFlags extern wxSizerFlags Checkbox(); }; +BEGIN_DECLARE_EVENT_TYPES() + DECLARE_EVENT_TYPE( pxEvt_OnThreadCleanup, -1 ); +END_DECLARE_EVENT_TYPES() + // -------------------------------------------------------------------------------------- // wxDialogWithHelpers // -------------------------------------------------------------------------------------- diff --git a/common/src/Utilities/Console.cpp b/common/src/Utilities/Console.cpp index 281fe51363..889e9a65b8 100644 --- a/common/src/Utilities/Console.cpp +++ b/common/src/Utilities/Console.cpp @@ -292,10 +292,11 @@ class FormatBuffer : public Mutex public: bool& clearbit; SafeArray buffer; + wxMBConvUTF8 ConvUTF8; - FormatBuffer( bool& bit_to_clear_on_destruction ) : - clearbit( bit_to_clear_on_destruction ) - , buffer( 4096, wxsFormat( L"%s Format Buffer", (sizeof(CharType)==1) ? "Ascii" : "Unicode" ) ) + FormatBuffer( bool& bit_to_clear_on_destruction ) + : clearbit( bit_to_clear_on_destruction ) + , buffer( 4096, wxsFormat( L"%s Format Buffer", (sizeof(CharType)==1) ? "Ascii" : "Unicode" ) ) { } @@ -376,12 +377,20 @@ static wxString ascii_format_string(const char* fmt, va_list argptr) { if( ascii_buffer_is_deleted ) { + // This means that the program is shutting down and the C++ destructors are + // running, randomly deallocating static variables from existence. We handle it + // as gracefully as possible by allocating local vars to do our bidding (slow, but + // ultimately necessary!) + SafeArray localbuf( 4096, L"Temporary Ascii Formatting Buffer" ); format_that_ascii_mess( localbuf, fmt, argptr ); return fromUTF8( localbuf.GetPtr() ); } else { + // This is normal operation. The static buffers are available for use, and we use + // them for sake of efficiency (fewer heap allocs, for sure!) + ScopedLock locker( ascii_buffer ); format_that_ascii_mess( ascii_buffer.buffer, fmt, argptr ); return fromUTF8( ascii_buffer.buffer.GetPtr() ); @@ -391,6 +400,8 @@ static wxString ascii_format_string(const char* fmt, va_list argptr) static wxString unicode_format_string(const wxChar* fmt, va_list argptr) { + // See above for the explanation on the _is_deleted flags. + if( unicode_buffer_is_deleted ) { SafeArray localbuf( 4096, L"Temporary Unicode Formatting Buffer" ); diff --git a/common/src/Utilities/Exceptions.cpp b/common/src/Utilities/Exceptions.cpp index 9b48055007..f9a843ea37 100644 --- a/common/src/Utilities/Exceptions.cpp +++ b/common/src/Utilities/Exceptions.cpp @@ -135,9 +135,9 @@ __forceinline void pxOnAssert( const DiagnosticOrigin& origin, const char* msg) // Exception Namespace Implementations (Format message handlers for general exceptions) // -------------------------------------------------------------------------------------- -Exception::BaseException::~BaseException() throw() {} +BaseException::~BaseException() throw() {} -void Exception::BaseException::InitBaseEx( const wxString& msg_eng, const wxString& msg_xlt ) +void BaseException::InitBaseEx( const wxString& msg_eng, const wxString& msg_xlt ) { m_message_diag = msg_eng; m_message_user = msg_xlt.IsEmpty() ? msg_eng : msg_xlt; @@ -155,7 +155,7 @@ void Exception::BaseException::InitBaseEx( const wxString& msg_eng, const wxStri // given message is assumed to be a translation key, and will be stored in translated // and untranslated forms. -void Exception::BaseException::InitBaseEx( const char* msg_eng ) +void BaseException::InitBaseEx( const char* msg_eng ) { m_message_diag = GetEnglish( msg_eng ); m_message_user = GetTranslation( msg_eng ); @@ -166,11 +166,22 @@ void Exception::BaseException::InitBaseEx( const char* msg_eng ) #endif } -wxString Exception::BaseException::FormatDiagnosticMessage() const +wxString BaseException::FormatDiagnosticMessage() const { return m_message_diag + L"\n\n" + m_stacktrace; } +// ------------------------------------------------------------------------ +Exception::RuntimeError::RuntimeError( const std::runtime_error& ex, const wxString& prefix ) +{ + const wxString msg( wxsFormat( L"%sSTL Runtime Error: %s", + (prefix.IsEmpty() ? prefix : wxsFormat(L"(%s) ", prefix)), + fromUTF8( ex.what() ).c_str() + ) ); + + BaseException::InitBaseEx( msg, msg ); +} + // ------------------------------------------------------------------------ wxString Exception::CancelEvent::FormatDiagnosticMessage() const { diff --git a/common/src/Utilities/Mutex.cpp b/common/src/Utilities/Mutex.cpp index dcab07e4a8..9015cb592f 100644 --- a/common/src/Utilities/Mutex.cpp +++ b/common/src/Utilities/Mutex.cpp @@ -36,6 +36,8 @@ Threading::Mutex::Mutex() pthread_mutex_init( &m_mutex, NULL ); } +static wxTimeSpan def_detach_timeout( 0, 0, 6, 0 ); + void Threading::Mutex::Detach() { if( EBUSY != pthread_mutex_destroy(&m_mutex) ) return; @@ -52,7 +54,7 @@ void Threading::Mutex::Detach() if( pxAssertDev( result != EBUSY, "Detachment of a recursively-locked mutex (self-locked!)." ) ) return; } - if( Wait(def_deadlock_timeout) ) + if( Wait(def_detach_timeout) ) pthread_mutex_destroy( &m_mutex ); else Console.Error( "(Thread Log) Mutex cleanup failed due to possible deadlock."); @@ -65,7 +67,7 @@ Threading::Mutex::~Mutex() throw() } DESTRUCTOR_CATCHALL; } -Threading::MutexLockRecursive::MutexLockRecursive() : Mutex( false ) +Threading::MutexRecursive::MutexRecursive() : Mutex( false ) { if( _InterlockedIncrement( &_attr_refcount ) == 1 ) { @@ -79,7 +81,7 @@ Threading::MutexLockRecursive::MutexLockRecursive() : Mutex( false ) err = pthread_mutex_init( &m_mutex, &_attr_recursive ); } -Threading::MutexLockRecursive::~MutexLockRecursive() throw() +Threading::MutexRecursive::~MutexRecursive() throw() { if( _InterlockedDecrement( &_attr_refcount ) == 0 ) pthread_mutexattr_destroy( &_attr_recursive ); @@ -100,7 +102,7 @@ void Threading::Mutex::Recreate() // unlocked. bool Threading::Mutex::RecreateIfLocked() { - if( !Wait(def_deadlock_timeout) ) + if( !Wait(def_detach_timeout) ) { Recreate(); return true; @@ -151,8 +153,7 @@ void Threading::Mutex::Acquire() } else if( _WaitGui_RecursionGuard( "Mutex::Acquire" ) ) { - if( !AcquireWithoutYield(def_deadlock_timeout) ) - throw Exception::ThreadDeadlock(); + AcquireWithoutYield(); } else { @@ -177,12 +178,6 @@ bool Threading::Mutex::Acquire( const wxTimeSpan& timeout ) else if( _WaitGui_RecursionGuard( "Mutex::Acquire(timeout)" ) ) { ScopedBusyCursor hourglass( Cursor_ReallyBusy ); - - if( timeout > def_deadlock_timeout ) - { - if( AcquireWithoutYield(def_deadlock_timeout) ) return true; - throw Exception::ThreadDeadlock(); - } return AcquireWithoutYield( timeout ); } else @@ -199,9 +194,6 @@ bool Threading::Mutex::Acquire( const wxTimeSpan& timeout ) return countdown.GetMilliseconds() > 0; } - // Looks like a potential deadlock; throw an exception! - throw Exception::ThreadDeadlock(); - #else return AcquireWithoutYield(); #endif @@ -223,6 +215,12 @@ void Threading::Mutex::Wait() Release(); } +void Threading::Mutex::WaitWithoutYield() +{ + AcquireWithoutYield(); + Release(); +} + // Performs a wait on a locked mutex, or returns instantly if the mutex is unlocked. // (Implemented internally as a simple Acquire/Release pair.) // @@ -243,3 +241,69 @@ bool Threading::Mutex::Wait( const wxTimeSpan& timeout ) return false; } +bool Threading::Mutex::WaitWithoutYield( const wxTimeSpan& timeout ) +{ + if( AcquireWithoutYield(timeout) ) + { + Release(); + return true; + } + return false; +} + +// -------------------------------------------------------------------------------------- +// ScopedLock Implementations +// -------------------------------------------------------------------------------------- + +Threading::ScopedLock::~ScopedLock() throw() +{ + if( m_IsLocked && m_lock ) + m_lock->Release(); +} + +Threading::ScopedLock::ScopedLock( const Mutex* locker ) +{ + AssignAndLock( locker ); +} + +Threading::ScopedLock::ScopedLock( const Mutex& locker ) +{ + AssignAndLock( locker ); +} + +void Threading::ScopedLock::AssignAndLock( const Mutex& locker ) +{ + AssignAndLock( &locker ); +} + +void Threading::ScopedLock::AssignAndLock( const Mutex* locker ) +{ + m_lock = const_cast(locker); + if( !m_lock ) return; + + m_IsLocked = true; + m_lock->Acquire(); +} + +// Provides manual unlocking of a scoped lock prior to object destruction. +void Threading::ScopedLock::Release() +{ + if( !m_IsLocked ) return; + m_IsLocked = false; + if( m_lock ) m_lock->Release(); +} + +// provides manual locking of a scoped lock, to re-lock after a manual unlocking. +void Threading::ScopedLock::Acquire() +{ + if( m_IsLocked || !m_lock ) return; + m_lock->Acquire(); + m_IsLocked = true; +} + +Threading::ScopedLock::ScopedLock( const Mutex& locker, bool isTryLock ) +{ + m_lock = const_cast(&locker); + if( !m_lock ) return; + m_IsLocked = isTryLock ? m_lock->TryAcquire() : false; +} diff --git a/common/src/Utilities/RwMutex.cpp b/common/src/Utilities/RwMutex.cpp new file mode 100644 index 0000000000..4a5192d0e2 --- /dev/null +++ b/common/src/Utilities/RwMutex.cpp @@ -0,0 +1,112 @@ +/* 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 "RwMutex.h" + +// -------------------------------------------------------------------------------------- +// RwMutex +// -------------------------------------------------------------------------------------- +Threading::RwMutex::RwMutex() +{ + pthread_rwlock_init( &m_rwlock, NULL ); +} + +Threading::RwMutex::~RwMutex() throw() +{ + pthread_rwlock_destroy( &m_rwlock ); +} + +void Threading::RwMutex::AcquireRead() +{ + pthread_rwlock_rdlock( &m_rwlock ); +} + +void Threading::RwMutex::AcquireWrite() +{ + pthread_rwlock_wrlock( &m_rwlock ); +} + +bool Threading::RwMutex::TryAcquireRead() +{ + return pthread_rwlock_tryrdlock( &m_rwlock ) != EBUSY; +} + +bool Threading::RwMutex::TryAcquireWrite() +{ + return pthread_rwlock_trywrlock( &m_rwlock ) != EBUSY; +} + +void Threading::RwMutex::Release() +{ + pthread_rwlock_unlock( &m_rwlock ); +} + +// -------------------------------------------------------------------------------------- +// +// -------------------------------------------------------------------------------------- +Threading::BaseScopedReadWriteLock::~BaseScopedReadWriteLock() throw() +{ + if( m_IsLocked ) + m_lock.Release(); +} + +// Provides manual unlocking of a scoped lock prior to object destruction. +void Threading::BaseScopedReadWriteLock::Release() +{ + if( !m_IsLocked ) return; + m_IsLocked = false; + m_lock.Release(); +} + +// -------------------------------------------------------------------------------------- +// ScopedReadLock / ScopedWriteLock +// -------------------------------------------------------------------------------------- +Threading::ScopedReadLock::ScopedReadLock( RwMutex& locker ) + : BaseScopedReadWriteLock( locker ) +{ + m_IsLocked = true; + m_lock.AcquireRead(); +} + +// provides manual locking of a scoped lock, to re-lock after a manual unlocking. +void Threading::ScopedReadLock::Acquire() +{ + if( m_IsLocked ) return; + m_lock.AcquireRead(); + m_IsLocked = true; +} + +Threading::ScopedWriteLock::ScopedWriteLock( RwMutex& locker ) + : BaseScopedReadWriteLock( locker ) +{ + m_IsLocked = true; + m_lock.AcquireWrite(); +} + +// provides manual locking of a scoped lock, to re-lock after a manual unlocking. +void Threading::ScopedWriteLock::Acquire() +{ + if( m_IsLocked ) return; + m_lock.AcquireWrite(); + m_IsLocked = true; +} + +// Special constructor used by ScopedTryLock +Threading::ScopedWriteLock::ScopedWriteLock( RwMutex& locker, bool isTryLock ) + : BaseScopedReadWriteLock( locker ) +{ + //m_IsLocked = isTryLock ? m_lock.TryAcquireWrite() : false; +} diff --git a/common/src/Utilities/Semaphore.cpp b/common/src/Utilities/Semaphore.cpp index 6b5aed6d13..d0e02777ec 100644 --- a/common/src/Utilities/Semaphore.cpp +++ b/common/src/Utilities/Semaphore.cpp @@ -92,8 +92,7 @@ void Threading::Semaphore::Wait() else if( _WaitGui_RecursionGuard( "Semaphore::Wait" ) ) { ScopedBusyCursor hourglass( Cursor_ReallyBusy ); - if( !WaitWithoutYield(def_yieldgui_interval) ) // default is 4 seconds - throw Exception::ThreadDeadlock(); + WaitWithoutYield(); } else { @@ -128,11 +127,6 @@ bool Threading::Semaphore::Wait( const wxTimeSpan& timeout ) else if( _WaitGui_RecursionGuard( "Semaphore::Wait(timeout)" ) ) { ScopedBusyCursor hourglass( Cursor_ReallyBusy ); - if( timeout > def_deadlock_timeout ) - { - if( WaitWithoutYield(def_deadlock_timeout) ) return true; - throw Exception::ThreadDeadlock(); - } return WaitWithoutYield( timeout ); } else @@ -165,7 +159,8 @@ void Threading::Semaphore::WaitNoCancel() { int oldstate; pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldstate ); - WaitWithoutYield(); + //WaitWithoutYield(); + Wait(); pthread_setcancelstate( oldstate, NULL ); } @@ -173,7 +168,8 @@ void Threading::Semaphore::WaitNoCancel( const wxTimeSpan& timeout ) { int oldstate; pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldstate ); - WaitWithoutYield( timeout ); + //WaitWithoutYield( timeout ); + Wait( timeout ); pthread_setcancelstate( oldstate, NULL ); } diff --git a/common/src/Utilities/StringHelpers.cpp b/common/src/Utilities/StringHelpers.cpp index 86ccbdfa31..6902c560ea 100644 --- a/common/src/Utilities/StringHelpers.cpp +++ b/common/src/Utilities/StringHelpers.cpp @@ -20,7 +20,16 @@ const wxRect wxDefaultRect( wxDefaultCoord, wxDefaultCoord, wxDefaultCoord, wxDe __forceinline wxString fromUTF8( const char* src ) { - return wxString::FromUTF8( src ); + // IMPORTANT: We cannot use wxString::FromUTF8 because it *stupidly* relies on a C++ global instance of + // wxMBConvUTF8(). C++ initializes and destroys these globals at random, so any object constructor or + // destructor that attempts to do logging may crash the app (either during startup or during exit) unless + // we use a LOCAL instance of wxMBConvUTF8(). --air + + // Performance? No worries. wxMBConvUTF8() is virtually free. Initializing a stack copy of the class + // is just as efficient as passing a pointer to a pre-instanced global. (which makes me wonder wh wxWidgets + // uses the stupid globals in the first place!) --air + + return wxString( src, wxMBConvUTF8() ); } __forceinline wxString fromAscii( const char* src ) diff --git a/common/src/Utilities/ThreadTools.cpp b/common/src/Utilities/ThreadTools.cpp index 6d1fe4c2e4..feb3e9bc34 100644 --- a/common/src/Utilities/ThreadTools.cpp +++ b/common/src/Utilities/ThreadTools.cpp @@ -33,15 +33,33 @@ template class EventSource< EventListener_Thread >; // to avoid gui deadlock). const wxTimeSpan Threading::def_yieldgui_interval( 0, 0, 0, 100 ); -// three second interval for deadlock protection on waitgui. -const wxTimeSpan Threading::def_deadlock_timeout( 0, 0, 3, 0 ); +class StaticMutex : public Mutex +{ +protected: + bool& m_DeletedFlag; + +public: + StaticMutex( bool& deletedFlag ) + : m_DeletedFlag( deletedFlag ) + { + } + + virtual ~StaticMutex() throw() + { + m_DeletedFlag = true; + } +}; static pthread_key_t curthread_key = NULL; static s32 total_key_count = 0; -static Mutex total_key_lock; + +static bool tkl_destructed = false; +static StaticMutex total_key_lock( tkl_destructed ); static void make_curthread_key() { + pxAssumeDev( !tkl_destructed, "total_key_lock is destroyed; program is shutting down; cannot create new thread key." ); + ScopedLock lock( total_key_lock ); if( total_key_count++ != 0 ) return; @@ -54,7 +72,10 @@ static void make_curthread_key() static void unmake_curthread_key() { - ScopedLock lock( total_key_lock ); + ScopedLock lock; + if( !tkl_destructed ) + lock.AssignAndLock( total_key_lock ); + if( --total_key_count > 0 ) return; if( curthread_key != NULL ) @@ -63,6 +84,11 @@ static void unmake_curthread_key() curthread_key = NULL; } +void Threading::pxTestCancel() +{ + pthread_testcancel(); +} + // Returns a handle to the current persistent thread. If the current thread does not belong // to the PersistentThread table, NULL is returned. Since the main/ui thread is not created // through PersistentThread it will also return NULL. Callers can use wxThread::IsMain() to @@ -158,20 +184,6 @@ Threading::PersistentThread::~PersistentThread() throw() Threading::Sleep( 1 ); Detach(); } - catch( Exception::ThreadDeadlock& 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( L"(Thread Log) Thread destructor for '%s' timed out with error:\n\t", - m_name.c_str(), ex.FormatDiagnosticMessage().c_str() ); - } DESTRUCTOR_CATCHALL } @@ -265,7 +277,6 @@ bool Threading::PersistentThread::Detach() bool Threading::PersistentThread::_basecancel() { - // Prevent simultaneous startup and cancel: if( !m_running ) return false; if( m_detached ) @@ -339,6 +350,12 @@ void Threading::PersistentThread::Block() WaitOnSelf( m_lock_InThread ); } +bool Threading::PersistentThread::Block( const wxTimeSpan& timeout ) +{ + AffinityAssert_DisallowFromSelf(pxDiagSpot); + return WaitOnSelf( m_lock_InThread, timeout ); +} + bool Threading::PersistentThread::IsSelf() const { // Detached threads may have their pthread handles recycled as newer threads, causing @@ -402,7 +419,7 @@ void Threading::PersistentThread::WaitOnSelf( Semaphore& sem ) const while( true ) { - if( sem.Wait( wxTimeSpan(0, 0, 0, 333) ) ) return; + if( sem.WaitWithoutYield( wxTimeSpan(0, 0, 0, 333) ) ) return; _selfRunningTest( L"semaphore" ); } } @@ -426,7 +443,7 @@ void Threading::PersistentThread::WaitOnSelf( Mutex& mutex ) const while( true ) { - if( mutex.Wait( wxTimeSpan(0, 0, 0, 333) ) ) return; + if( mutex.WaitWithoutYield( wxTimeSpan(0, 0, 0, 333) ) ) return; _selfRunningTest( L"mutex" ); } } @@ -442,7 +459,7 @@ bool Threading::PersistentThread::WaitOnSelf( Semaphore& sem, const wxTimeSpan& while( runningout.GetMilliseconds() > 0 ) { const wxTimeSpan interval( (SelfWaitInterval < runningout) ? SelfWaitInterval : runningout ); - if( sem.Wait( interval ) ) return true; + if( sem.WaitWithoutYield( interval ) ) return true; _selfRunningTest( L"semaphore" ); runningout -= interval; } @@ -458,7 +475,7 @@ bool Threading::PersistentThread::WaitOnSelf( Mutex& mutex, const wxTimeSpan& ti while( runningout.GetMilliseconds() > 0 ) { const wxTimeSpan interval( (SelfWaitInterval < runningout) ? SelfWaitInterval : runningout ); - if( mutex.Wait( interval ) ) return true; + if( mutex.WaitWithoutYield( interval ) ) return true; _selfRunningTest( L"mutex" ); runningout -= interval; } @@ -487,17 +504,7 @@ void Threading::PersistentThread::_try_virtual_invoke( void (PersistentThread::* // catch( std::runtime_error& ex ) { - m_except = new Exception::RuntimeError( - // Diagnostic message: - wxsFormat( L"(thread: %s) STL Runtime Error: %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() - ) - ); + m_except = new Exception::RuntimeError( ex, GetName().c_str() ); } // ---------------------------------------------------------------------------- @@ -513,20 +520,20 @@ void Threading::PersistentThread::_try_virtual_invoke( void (PersistentThread::* // the MSVC debugger (or by silent random annoying fail on debug-less linux). /*catch( std::logic_error& ex ) { - throw Exception::BaseException( wxsFormat( L"(thread: %s) STL Logic Error: %s", + throw BaseException( wxsFormat( L"(thread: %s) STL Logic Error: %s", GetName().c_str(), fromUTF8( ex.what() ).c_str() ) ); } catch( std::exception& ex ) { - throw Exception::BaseException( wxsFormat( L"(thread: %s) STL exception: %s", + throw BaseException( wxsFormat( L"(thread: %s) STL exception: %s", GetName().c_str(), fromUTF8( ex.what() ).c_str() ) ); }*/ // ---------------------------------------------------------------------------- // BaseException -- same deal as LogicErrors. // - catch( Exception::BaseException& ex ) + catch( BaseException& ex ) { m_except = ex.Clone(); m_except->DiagMsg() = wxsFormat( L"(thread:%s) ", GetName().c_str() ) + m_except->DiagMsg(); @@ -541,6 +548,10 @@ void Threading::PersistentThread::_ThreadCleanup() AffinityAssert_AllowFromSelf(pxDiagSpot); _try_virtual_invoke( &PersistentThread::OnCleanupInThread ); m_lock_InThread.Release(); + + // Must set m_running LAST, as thread destructors depend on this value (it is used + // to avoid destruction of the thread until all internal data use has stopped. + m_running = false; } wxString Threading::PersistentThread::GetName() const @@ -590,12 +601,10 @@ void Threading::PersistentThread::OnStart() m_sem_startup.Reset(); } -// Extending classes that override this method shoul always call it last from their +// Extending classes that override this method should always call it last from their // personal implementations. void Threading::PersistentThread::OnCleanupInThread() { - m_running = false; - if( curthread_key != NULL ) pthread_setspecific( curthread_key, NULL ); @@ -605,6 +614,8 @@ void Threading::PersistentThread::OnCleanupInThread() m_native_handle = NULL; m_native_id = 0; + + m_evtsrc_OnDelete.Dispatch( 0 ); } // passed into pthread_create, and is used to dispatch the thread's object oriented diff --git a/common/src/Utilities/ThreadingInternal.h b/common/src/Utilities/ThreadingInternal.h index 9cddd7510c..52b2614c94 100644 --- a/common/src/Utilities/ThreadingInternal.h +++ b/common/src/Utilities/ThreadingInternal.h @@ -22,7 +22,6 @@ namespace Threading { extern const wxTimeSpan def_yieldgui_interval; - extern const wxTimeSpan def_deadlock_timeout; extern bool _WaitGui_RecursionGuard( const char* guardname ); } diff --git a/common/src/Utilities/wxAppWithHelpers.cpp b/common/src/Utilities/wxAppWithHelpers.cpp index db109fd319..0e9a448408 100644 --- a/common/src/Utilities/wxAppWithHelpers.cpp +++ b/common/src/Utilities/wxAppWithHelpers.cpp @@ -16,14 +16,19 @@ #include "PrecompiledHeader.h" #include "wxAppWithHelpers.h" -DEFINE_EVENT_TYPE( pxEvt_Ping ); -DEFINE_EVENT_TYPE( pxEvt_IdleEventQueue ); -DEFINE_EVENT_TYPE( pxEvt_MessageBox ); +#include "PersistentThread.h" + DEFINE_EVENT_TYPE( pxEvt_DeleteObject ); -//DEFINE_EVENT_TYPE( pxEvt_Assertion ); +DEFINE_EVENT_TYPE( pxEvt_DeleteThread ); +DEFINE_EVENT_TYPE( pxEvt_StartIdleEventTimer ); +DEFINE_EVENT_TYPE( pxEvt_InvokeAction ); +DEFINE_EVENT_TYPE( pxEvt_SynchronousCommand ); -void IDeletableObject::DoDeletion() +IMPLEMENT_DYNAMIC_CLASS( pxSimpleEvent, wxEvent ) + + +void BaseDeletableObject::DoDeletion() { wxAppWithHelpers* app = wxDynamicCast( wxApp::GetInstance(), wxAppWithHelpers ); pxAssume( app != NULL ); @@ -31,27 +36,171 @@ void IDeletableObject::DoDeletion() } // -------------------------------------------------------------------------------------- -// pxPingEvent Implementations +// pxInvokeActionEvent Implementations // -------------------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS( pxPingEvent, wxEvent ) +IMPLEMENT_DYNAMIC_CLASS( pxInvokeActionEvent, wxEvent ) -pxPingEvent::pxPingEvent( int msgtype, Semaphore* sema ) +pxInvokeActionEvent::pxInvokeActionEvent( SynchronousActionState* sema, int msgtype ) : wxEvent( 0, msgtype ) { - m_PostBack = sema; + m_state = sema; } -pxPingEvent::pxPingEvent( Semaphore* sema ) - : wxEvent( 0, pxEvt_Ping ) +pxInvokeActionEvent::pxInvokeActionEvent( SynchronousActionState& sema, int msgtype ) + : wxEvent( 0, msgtype ) { - m_PostBack = sema; + m_state = &sema; } -pxPingEvent::pxPingEvent( const pxPingEvent& src ) +pxInvokeActionEvent::pxInvokeActionEvent( const pxInvokeActionEvent& src ) : wxEvent( src ) { - m_PostBack = src.m_PostBack; + m_state = src.m_state; +} + +void pxInvokeActionEvent::SetException( const BaseException& ex ) +{ + SetException( ex.Clone() ); +} + +void pxInvokeActionEvent::SetException( BaseException* ex ) +{ + const wxString& prefix( wxsFormat(L"(%s) ", GetClassInfo()->GetClassName()) ); + ex->DiagMsg() = prefix + ex->DiagMsg(); + + if( !m_state ) + { + ScopedPtr exptr( ex ); // auto-delete it after handling. + ex->Rethrow(); + } + + m_state->SetException( ex ); +} + +// -------------------------------------------------------------------------------------- +// pxSynchronousCommandEvent +// -------------------------------------------------------------------------------------- +IMPLEMENT_DYNAMIC_CLASS( pxSynchronousCommandEvent, wxCommandEvent ) + +pxSynchronousCommandEvent::pxSynchronousCommandEvent(SynchronousActionState* sema, wxEventType commandType, int winid) + : wxCommandEvent( pxEvt_SynchronousCommand, winid ) +{ + m_sync = sema; + m_realEvent = commandType; +} + +pxSynchronousCommandEvent::pxSynchronousCommandEvent(SynchronousActionState& sema, wxEventType commandType, int winid) + : wxCommandEvent( pxEvt_SynchronousCommand ) +{ + m_sync = &sema; + m_realEvent = commandType; +} + +pxSynchronousCommandEvent::pxSynchronousCommandEvent(SynchronousActionState* sema, const wxCommandEvent& evt ) + : wxCommandEvent( evt ) +{ + m_sync = sema; + m_realEvent = evt.GetEventType(); + SetEventType( pxEvt_SynchronousCommand ); +} + +pxSynchronousCommandEvent::pxSynchronousCommandEvent(SynchronousActionState& sema, const wxCommandEvent& evt ) + : wxCommandEvent( evt ) +{ + m_sync = &sema; + m_realEvent = evt.GetEventType(); + SetEventType( pxEvt_SynchronousCommand ); +} + +pxSynchronousCommandEvent::pxSynchronousCommandEvent( const pxSynchronousCommandEvent& src ) + : wxCommandEvent( src ) +{ + m_sync = src.m_sync; + m_realEvent = src.m_realEvent; +} + +void pxSynchronousCommandEvent::SetException( const BaseException& ex ) +{ + if( !m_sync ) ex.Rethrow(); + m_sync->SetException( ex ); +} + +void pxSynchronousCommandEvent::SetException( BaseException* ex ) +{ + if( !m_sync ) + { + ScopedPtr exptr( ex ); // auto-delete it after handling. + ex->Rethrow(); + } + + m_sync->SetException( ex ); +} + +// -------------------------------------------------------------------------------------- +// pxInvokeMethodEvent +// -------------------------------------------------------------------------------------- +// Unlike pxPingEvent, the Semaphore belonging to this event is typically posted when the +// invoked method is completed. If the method can be executed in non-blocking fashion then +// it should leave the semaphore postback NULL. +// +class pxInvokeMethodEvent : public pxInvokeActionEvent +{ + DECLARE_DYNAMIC_CLASS_NO_ASSIGN(pxInvokeMethodEvent) + + typedef pxInvokeActionEvent _parent; + +protected: + void (*m_Method)(); + +public: + virtual ~pxInvokeMethodEvent() throw() { } + virtual pxInvokeMethodEvent *Clone() const { return new pxInvokeMethodEvent(*this); } + + explicit pxInvokeMethodEvent( void (*method)()=NULL, SynchronousActionState* sema=NULL ) + : pxInvokeActionEvent( sema ) + { + m_Method = method; + } + + explicit pxInvokeMethodEvent( void (*method)(), SynchronousActionState& sema ) + : pxInvokeActionEvent( sema ) + { + m_Method = method; + } + + pxInvokeMethodEvent( const pxInvokeMethodEvent& src ) + : pxInvokeActionEvent( src ) + { + m_Method = src.m_Method; + } + + void SetMethod( void (*method)() ) + { + m_Method = method; + } + +protected: + void _DoInvoke() + { + if( m_Method ) m_Method(); + } +}; + +IMPLEMENT_DYNAMIC_CLASS( pxInvokeMethodEvent, pxInvokeActionEvent ) + +// -------------------------------------------------------------------------------------- +// pxExceptionEvent implementations +// -------------------------------------------------------------------------------------- +pxExceptionEvent::pxExceptionEvent( const BaseException& ex ) +{ + m_except = ex.Clone(); +} + +void pxExceptionEvent::_DoInvoke() +{ + ScopedPtr deleteMe( m_except ); + if( deleteMe ) deleteMe->Rethrow(); } // -------------------------------------------------------------------------------------- @@ -64,81 +213,192 @@ pxPingEvent::pxPingEvent( const pxPingEvent& src ) // IMPLEMENT_DYNAMIC_CLASS( wxAppWithHelpers, wxApp ) + +// Posts a method to the main thread; non-blocking. Post occurs even when called from the +// main thread. +void wxAppWithHelpers::PostMethod( FnType_Void* method ) +{ + PostEvent( pxInvokeMethodEvent( method ) ); +} + +// Posts a method to the main thread; non-blocking. Post occurs even when called from the +// main thread. +void wxAppWithHelpers::PostIdleMethod( FnType_Void* method ) +{ + pxInvokeMethodEvent evt( method ); + AddIdleEvent( evt ); +} + +bool wxAppWithHelpers::PostMethodMyself( void (*method)() ) +{ + if( wxThread::IsMain() ) return false; + PostEvent( pxInvokeMethodEvent( method ) ); + return true; +} + +void wxAppWithHelpers::ProcessMethod( void (*method)() ) +{ + if( wxThread::IsMain() ) + { + method(); + return; + } + + SynchronousActionState sync; + PostEvent( pxInvokeMethodEvent( method, sync ) ); + sync.WaitForResult(); +} + +void wxAppWithHelpers::PostEvent( const wxEvent& evt ) +{ + // Const Cast is OK! + // Truth is, AddPendingEvent should be a const-qualified parameter, as + // it makes an immediate clone copy of the event -- but wxWidgets + // fails again in structured C/C++ design design. So I'm forcing it as such + // here. -- air + + _parent::AddPendingEvent( const_cast(evt) ); +} + +bool wxAppWithHelpers::ProcessEvent( wxEvent& evt ) +{ + // Note: We can't do an automatic blocking post of the message here, because wxWidgets + // isn't really designed for it (some events return data to the caller via the event + // struct, and posting the event would require a temporary clone, where changes would + // be lost). + + AffinityAssert_AllowFrom_MainUI(); + return _parent::ProcessEvent( evt ); +} + +bool wxAppWithHelpers::ProcessEvent( wxEvent* evt ) +{ + AffinityAssert_AllowFrom_MainUI(); + ScopedPtr deleteMe( evt ); + return _parent::ProcessEvent( *deleteMe ); +} + +bool wxAppWithHelpers::ProcessEvent( pxInvokeActionEvent& evt ) +{ + if( wxThread::IsMain() ) + return _parent::ProcessEvent( evt ); + else + { + SynchronousActionState sync; + evt.SetSyncState( sync ); + AddPendingEvent( evt ); + sync.WaitForResult(); + return true; + } +} + +bool wxAppWithHelpers::ProcessEvent( pxInvokeActionEvent* evt ) +{ + if( wxThread::IsMain() ) + { + ScopedPtr deleteMe( evt ); + return _parent::ProcessEvent( *deleteMe ); + } + else + { + SynchronousActionState sync; + evt->SetSyncState( sync ); + AddPendingEvent( *evt ); + sync.WaitForResult(); + return true; + } +} + + void wxAppWithHelpers::CleanUp() { - DeletionDispatcher(); + // I'm pretty sure the message pump is dead by now, which means we need to run through + // idle event list by hand and process the pending Deletion messages (all others can be + // ignored -- it's only deletions we want handled, and really we *could* ignore those too + // but I like to be tidy. -- air + + //IdleEventDispatcher( "CleanUp" ); + //DeletionDispatcher(); _parent::CleanUp(); } -void wxAppWithHelpers::OnPingEvent( pxPingEvent& evt ) +void pxInvokeActionEvent::InvokeAction() { - // Ping events are dispatched during the idle event handler, which ensures - // the ping is posted only after all other pending messages behind the ping - // are also processed. + AffinityAssert_AllowFrom_MainUI(); - if( m_PingWhenIdle.size() == 0 ) m_PingTimer.Start( 200, true ); - m_PingWhenIdle.push_back( evt.GetSemaphore() ); + try { + _DoInvoke(); + } + catch( BaseException& ex ) + { + SetException( ex ); + } + catch( std::runtime_error& ex ) + { + SetException( new Exception::RuntimeError( ex ) ); + } + + if( m_state ) m_state->PostResult(); } -void wxAppWithHelpers::OnAddEventToIdleQueue( wxEvent& evt ) +void wxAppWithHelpers::OnSynchronousCommand( pxSynchronousCommandEvent& evt ) { - if( m_IdleEventQueue.size() == 0 ) m_IdleEventTimer.Start( 100, true ); + AffinityAssert_AllowFrom_MainUI(); + + evt.SetEventType( evt.GetRealEventType() ); + + try { + ProcessEvent( evt ); + } + catch( BaseException& ex ) + { + evt.SetException( ex ); + } + catch( std::runtime_error& ex ) + { + evt.SetException( new Exception::RuntimeError( ex, evt.GetClassInfo()->GetClassName() ) ); + } + + if( Semaphore* sema = evt.GetSemaphore() ) sema->Post(); +} + +void wxAppWithHelpers::AddIdleEvent( const wxEvent& evt ) +{ + ScopedLock lock( m_IdleEventMutex ); + if( m_IdleEventQueue.size() == 0 ) + PostEvent( pxSimpleEvent( pxEvt_StartIdleEventTimer ) ); + m_IdleEventQueue.push_back( evt.Clone() ); } +void wxAppWithHelpers::OnStartIdleEventTimer( wxEvent& evt ) +{ + ScopedLock lock( m_IdleEventMutex ); + if( m_IdleEventQueue.size() != 0 ) + m_IdleEventTimer.Start( 100, true ); +} + void wxAppWithHelpers::IdleEventDispatcher( const char* action ) { + ScopedLock lock( m_IdleEventMutex ); + size_t size = m_IdleEventQueue.size(); if( size == 0 ) return; DbgCon.WriteLn( Color_Gray, "App IdleQueue (%s) -> %u events.", action, size ); for( size_t i=0; i %u listeners.", action, size ); - - for( size_t i=0; iPost(); - } - - m_PingWhenIdle.clear(); -} - -void wxAppWithHelpers::DeletionDispatcher() -{ - ScopedLock lock( m_DeleteIdleLock ); - - size_t size = m_DeleteWhenIdle.size(); - if( size == 0 ) return; - - DbgCon.WriteLn( Color_Gray, "App Idle Delete -> %u objects.", size ); -} - void wxAppWithHelpers::OnIdleEvent( wxIdleEvent& evt ) { - m_PingTimer.Stop(); m_IdleEventTimer.Stop(); - PingDispatcher( "Idle" ); IdleEventDispatcher( "Idle" ); } -void wxAppWithHelpers::OnPingTimeout( wxTimerEvent& evt ) -{ - PingDispatcher( "Timeout" ); -} - void wxAppWithHelpers::OnIdleEventTimeout( wxTimerEvent& evt ) { IdleEventDispatcher( "Timeout" ); @@ -148,10 +408,10 @@ void wxAppWithHelpers::Ping() { DbgCon.WriteLn( Color_Gray, L"App Event Ping Requested from %s thread.", pxGetCurrentThreadName().c_str() ); - Semaphore sema; - pxPingEvent evt( &sema ); - AddPendingEvent( evt ); - sema.WaitNoCancel(); + SynchronousActionState sync; + pxInvokeActionEvent evt( sync ); + AddIdleEvent( evt ); + sync.WaitForResult(); } void wxAppWithHelpers::PostCommand( void* clientData, int evtType, int intParam, long longParam, const wxString& stringParam ) @@ -169,52 +429,106 @@ void wxAppWithHelpers::PostCommand( int evtType, int intParam, long longParam, c PostCommand( NULL, evtType, intParam, longParam, stringParam ); } -void wxAppWithHelpers::DeleteObject( IDeletableObject& obj ) +sptr wxAppWithHelpers::ProcessCommand( void* clientData, int evtType, int intParam, long longParam, const wxString& stringParam ) { - pxAssume( obj.IsBeingDeleted() ); - ScopedLock lock( m_DeleteIdleLock ); - m_DeleteWhenIdle.push_back( &obj ); + SynchronousActionState sync; + pxSynchronousCommandEvent evt( sync, evtType ); + + evt.SetClientData( clientData ); + evt.SetInt( intParam ); + evt.SetExtraLong( longParam ); + evt.SetString( stringParam ); + AddPendingEvent( evt ); + sync.WaitForResult(); + + return sync.return_value; } -typedef void (wxEvtHandler::*BaseMessageBoxEventFunction)(BaseMessageBoxEvent&); -typedef void (wxEvtHandler::*pxPingEventFunction)(pxPingEvent&); +sptr wxAppWithHelpers::ProcessCommand( int evtType, int intParam, long longParam, const wxString& stringParam ) +{ + return ProcessCommand( NULL, evtType, intParam, longParam, stringParam ); +} + +void wxAppWithHelpers::PostAction( const pxInvokeActionEvent& evt ) +{ + PostEvent( evt ); +} + +void wxAppWithHelpers::ProcessAction( pxInvokeActionEvent& evt ) +{ + if( !wxThread::IsMain() ) + { + SynchronousActionState sync; + evt.SetSyncState( sync ); + AddPendingEvent( evt ); + sync.WaitForResult(); + } + else + evt.InvokeAction(); +} + + +void wxAppWithHelpers::DeleteObject( BaseDeletableObject& obj ) +{ + pxAssume( !obj.IsBeingDeleted() ); + wxCommandEvent evt( pxEvt_DeleteObject ); + evt.SetClientData( (void*)&obj ); + AddIdleEvent( evt ); +} + +void wxAppWithHelpers::DeleteThread( PersistentThread& obj ) +{ + //pxAssume( obj.IsBeingDeleted() ); + wxCommandEvent evt( pxEvt_DeleteThread ); + evt.SetClientData( (void*)&obj ); + AddIdleEvent( evt ); +} + +typedef void (wxEvtHandler::*pxInvokeActionEventFunction)(pxInvokeActionEvent&); bool wxAppWithHelpers::OnInit() { -#define pxMessageBoxEventThing(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(BaseMessageBoxEventFunction, &func ) +#define pxActionEventHandler(func) \ + (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(pxInvokeActionEventFunction, &func ) -#define pxPingEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(pxPingEventFunction, &func ) + Connect( pxEvt_SynchronousCommand, pxSynchronousEventHandler (wxAppWithHelpers::OnSynchronousCommand) ); + Connect( pxEvt_InvokeAction, pxActionEventHandler (wxAppWithHelpers::OnInvokeAction) ); + Connect( pxEvt_StartIdleEventTimer, wxEventHandler (wxAppWithHelpers::OnStartIdleEventTimer) ); - Connect( pxEvt_MessageBox, pxMessageBoxEventThing (wxAppWithHelpers::OnMessageBox) ); - //Connect( pxEvt_Assertion, pxMessageBoxEventThing (wxAppWithHelpers::OnMessageBox) ); - Connect( pxEvt_Ping, pxPingEventHandler (wxAppWithHelpers::OnPingEvent) ); - Connect( pxEvt_IdleEventQueue, wxEventHandler (wxAppWithHelpers::OnAddEventToIdleQueue) ); - Connect( pxEvt_DeleteObject, wxCommandEventHandler (wxAppWithHelpers::OnDeleteObject) ); - Connect( wxEVT_IDLE, wxIdleEventHandler (wxAppWithHelpers::OnIdleEvent) ); + Connect( pxEvt_DeleteObject, wxCommandEventHandler (wxAppWithHelpers::OnDeleteObject) ); + Connect( pxEvt_DeleteThread, wxCommandEventHandler (wxAppWithHelpers::OnDeleteThread) ); + + Connect( wxEVT_IDLE, wxIdleEventHandler (wxAppWithHelpers::OnIdleEvent) ); - Connect( m_PingTimer.GetId(), wxEVT_TIMER, wxTimerEventHandler(wxAppWithHelpers::OnPingTimeout) ); Connect( m_IdleEventTimer.GetId(), wxEVT_TIMER, wxTimerEventHandler(wxAppWithHelpers::OnIdleEventTimeout) ); return _parent::OnInit(); } -void wxAppWithHelpers::OnMessageBox( BaseMessageBoxEvent& evt ) +void wxAppWithHelpers::OnInvokeAction( pxInvokeActionEvent& evt ) { - evt.IssueDialog(); + evt.InvokeAction(); // wow this is easy! } void wxAppWithHelpers::OnDeleteObject( wxCommandEvent& evt ) { if( evt.GetClientData() == NULL ) return; - delete (IDeletableObject*)evt.GetClientData(); + delete (BaseDeletableObject*)evt.GetClientData(); +} + +// Threads have their own deletion handler that propagates exceptions thrown by the thread to the UI. +// (thus we have a fairly automatic threaded exception system!) +void wxAppWithHelpers::OnDeleteThread( wxCommandEvent& evt ) +{ + ScopedPtr thr( (PersistentThread*)evt.GetClientData() ); + if( !thr ) return; + + thr->RethrowException(); } wxAppWithHelpers::wxAppWithHelpers() - : m_PingTimer( this ) - , m_IdleEventTimer( this ) + : m_IdleEventTimer( this ) { #ifdef __WXMSW__ // This variable assignment ensures that MSVC links in the TLS setup stubs even in diff --git a/common/src/Utilities/wxHelpers.cpp b/common/src/Utilities/wxHelpers.cpp index fb26453980..9fe6a2d228 100644 --- a/common/src/Utilities/wxHelpers.cpp +++ b/common/src/Utilities/wxHelpers.cpp @@ -25,37 +25,39 @@ using namespace pxSizerFlags; +DEFINE_EVENT_TYPE( pxEvt_OnThreadCleanup ); + // -------------------------------------------------------------------------------------- -// IDeletableObject Implementation +// BaseDeletableObject Implementation // -------------------------------------------------------------------------------------- // This code probably deserves a better home. It's general purpose non-GUI code (the single // wxApp/Gui dependency is in wxGuiTools.cpp for now). // -bool IDeletableObject::MarkForDeletion() +bool BaseDeletableObject::MarkForDeletion() { return !_InterlockedExchange( &m_IsBeingDeleted, true ); } -void IDeletableObject::DeleteSelf() +void BaseDeletableObject::DeleteSelf() { if( MarkForDeletion() ) DoDeletion(); } -IDeletableObject::IDeletableObject() +BaseDeletableObject::BaseDeletableObject() { #ifdef _MSC_VER // Bleh, this fails because _CrtIsValidHeapPointer calls HeapValidate on the // pointer, but the pointer is a virtual base class, so it's not a valid block. >_< - //pxAssertDev( _CrtIsValidHeapPointer( this ), "IDeletableObject types cannot be created on the stack or as temporaries!" ); + //pxAssertDev( _CrtIsValidHeapPointer( this ), "BaseDeletableObject types cannot be created on the stack or as temporaries!" ); #endif m_IsBeingDeleted = false; } -IDeletableObject::~IDeletableObject() throw() +BaseDeletableObject::~BaseDeletableObject() throw() { - AffinityAssert_AllowFromMain(); + AffinityAssert_AllowFrom_MainUI(); } @@ -334,7 +336,6 @@ pxStaticHeading* wxPanelWithHelpers::Heading( const wxString& label ) return new pxStaticHeading( this, label ); } - wxPanelWithHelpers::wxPanelWithHelpers( wxWindow* parent, wxOrientation orient, const wxString& staticBoxLabel ) : wxPanel( parent ) { diff --git a/common/vsprops/pthreads.vsprops b/common/vsprops/pthreads.vsprops index c380bec6bf..124fe3b05a 100644 --- a/common/vsprops/pthreads.vsprops +++ b/common/vsprops/pthreads.vsprops @@ -12,6 +12,6 @@ /> diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp index 2f58890e33..0a2ea6f5cd 100644 --- a/pcsx2/CDVD/CDVD.cpp +++ b/pcsx2/CDVD/CDVD.cpp @@ -93,7 +93,7 @@ FILE *_cdvdOpenMechaVer() if (fd == NULL) { Console.Error( "MEC File Creation failed!" ); - throw Exception::CreateStream( file ); + throw Exception::CannotCreateStream( file ); //Msgbox::Alert( "_cdvdOpenMechaVer: Error creating %s", file); //exit(1); } @@ -133,7 +133,7 @@ FILE *_cdvdOpenNVM() if (fd == NULL) { Console.Error( "NVM File Creation failed!" ); - throw Exception::CreateStream( file ); + throw Exception::CannotCreateStream( file ); } for (int i=0; i<1024; i++) fputc(0, fd); } @@ -305,7 +305,7 @@ s32 cdvdWriteConfig(const u8* config) } } -static MutexLockRecursive Mutex_NewDiskCB; +static MutexRecursive Mutex_NewDiskCB; // Sets ElfCRC to the CRC of the game bound to the CDVD plugin. static __forceinline ElfObject *loadElf( const wxString filename ) @@ -566,11 +566,11 @@ static void cdvdDetectDisk() void cdvdNewDiskCB() { - if( !Mutex_NewDiskCB.TryAcquire() ) return; + ScopedTryLock lock( Mutex_NewDiskCB ); + if( lock.Failed() ) return; + DoCDVDresetDiskTypeCache(); - - try { cdvdDetectDisk(); } - catch(...) { Mutex_NewDiskCB.Release(); } // ensure mutex gets unlocked. + cdvdDetectDisk(); } static void mechaDecryptBytes( u32 madr, int size ) diff --git a/pcsx2/CDVD/CDVDaccess.cpp b/pcsx2/CDVD/CDVDaccess.cpp index 448cc12941..889b5a1acc 100644 --- a/pcsx2/CDVD/CDVDaccess.cpp +++ b/pcsx2/CDVD/CDVDaccess.cpp @@ -288,7 +288,7 @@ CDVD_SourceType CDVDsys_GetSourceType() void CDVDsys_ChangeSource( CDVD_SourceType type ) { - GetPluginManager().Close( PluginId_CDVD ); + GetCorePlugins().Close( PluginId_CDVD ); switch( m_CurrentSourceType = type ) { diff --git a/pcsx2/CDVD/IsoFS/IsoFS.cpp b/pcsx2/CDVD/IsoFS/IsoFS.cpp index fd580277f1..3939d787e5 100644 --- a/pcsx2/CDVD/IsoFS/IsoFS.cpp +++ b/pcsx2/CDVD/IsoFS/IsoFS.cpp @@ -94,7 +94,7 @@ IsoDirectory::IsoDirectory(SectorSource& r) } if( !isValid ) - throw Exception::BadStream( "IsoFS", "Root directory not found on ISO image." ); + throw Exception::FileNotFound( "IsoFS", "Root directory not found on ISO image." ); DevCon.WriteLn( L"(IsoFS) Filesystem is " + FStype_ToString() ); Init( rootDirEntry ); diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 5c21dfc55f..9fbeb199b0 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -484,8 +484,8 @@ struct Pcsx2Config CdvdDumpBlocks :1, // enables cdvd block dumping EnablePatches :1, // enables patch detection and application - // when enabled performs bios stub execution, skipping full sony bios + splash screens - SkipBiosSplash :1, + // when enabled uses BOOT2 injection, skipping sony bios splashes + UseBOOT2Injection :1, // enables simulated ejection of memory cards when loading savestates McdEnableEjection :1, diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index e57f046900..0455fe7752 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -222,8 +222,7 @@ static void vSyncInfoCalc( vSyncTimingInfo* info, Fixed100 framesPerSecond, u32 u32 UpdateVSyncRate() { - XMMRegisters::Freeze(); - MMXRegisters::Freeze(); + Registers::Freeze(); // Notice: (and I probably repeat this elsewhere, but it's worth repeating) // The PS2's vsync timer is an *independent* crystal that is fixed to either 59.94 (NTSC) @@ -234,8 +233,8 @@ u32 UpdateVSyncRate() // 1/5 and 4/5 ratios). Fixed100 framerate; - u32 scanlines; - bool isCustom; + u32 scanlines; + bool isCustom; if( gsRegionMode == Region_PAL ) { @@ -278,8 +277,7 @@ u32 UpdateVSyncRate() m_iStart = GetCPUTicks(); - XMMRegisters::Thaw(); - MMXRegisters::Thaw(); + Registers::Thaw(); return (u32)m_iTicks; } diff --git a/pcsx2/GSState.cpp b/pcsx2/GSState.cpp index 66e2b34fa6..6c71c68850 100644 --- a/pcsx2/GSState.cpp +++ b/pcsx2/GSState.cpp @@ -118,8 +118,8 @@ void LoadGSState(const wxString& file) RunGSState( f ); - g_plugins->Close( PluginId_GS ); - g_plugins->Close( PluginId_PAD ); + GetCorePlugins().Close( PluginId_GS ); + GetCorePlugins().Close( PluginId_PAD ); } struct GSStatePacket diff --git a/pcsx2/Linux/pcsx2.cbp b/pcsx2/Linux/pcsx2.cbp index e3b99e1e30..a0e2b8488e 100644 --- a/pcsx2/Linux/pcsx2.cbp +++ b/pcsx2/Linux/pcsx2.cbp @@ -287,7 +287,6 @@ - @@ -338,6 +337,9 @@ + + + @@ -345,6 +347,8 @@ + + @@ -376,6 +380,7 @@ + @@ -408,7 +413,6 @@ - @@ -491,9 +495,13 @@ + + + + diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index ed5c3f6c57..059d804fac 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -399,9 +399,9 @@ void SysMtgsThread::ExecuteTaskInThread() { MTGS_FreezeData* data = (MTGS_FreezeData*)(*(uptr*)&tag.data[1]); int mode = tag.data[0]; - data->retval = GetPluginManager().DoFreeze( PluginId_GS, mode, data->fdata ); - break; + data->retval = GetCorePlugins().DoFreeze( PluginId_GS, mode, data->fdata ); } + break; case GS_RINGTYPE_RECORD: { @@ -488,8 +488,7 @@ void SysMtgsThread::ClosePlugin() { if( !m_PluginOpened ) return; m_PluginOpened = false; - if( g_plugins != NULL ) - g_plugins->m_info[PluginId_GS].CommonBindings.Close(); + GetCorePlugins().Close( PluginId_GS ); } void SysMtgsThread::OnSuspendInThread() @@ -998,7 +997,7 @@ void SysMtgsThread::WaitForOpen() void SysMtgsThread::Freeze( int mode, MTGS_FreezeData& data ) { - GetPluginManager().Open( PluginId_GS ); + GetCorePlugins().Open( PluginId_GS ); SendPointerPacket( GS_RINGTYPE_FREEZE, mode, &data ); Resume(); WaitGS(); diff --git a/pcsx2/PathDefs.h b/pcsx2/PathDefs.h index f4224b3e46..8159ab53fd 100644 --- a/pcsx2/PathDefs.h +++ b/pcsx2/PathDefs.h @@ -42,7 +42,6 @@ namespace PathDefs // complete pathnames are returned by these functions // For 99% of all code, you should use these. - extern wxDirName GetDocuments(); extern wxDirName GetSnapshots(); extern wxDirName GetBios(); extern wxDirName GetThemes(); @@ -51,7 +50,6 @@ namespace PathDefs extern wxDirName GetMemoryCards(); extern wxDirName GetSettings(); extern wxDirName GetLogs(); - extern wxDirName GetThemes(); extern wxDirName Get( FoldersEnum_t folderidx ); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 84763e3641..4b033f4fe9 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -275,7 +275,7 @@ void Pcsx2Config::LoadSave( IniInterface& ini ) IniBitBool( EnablePatches ); IniBitBool( ConsoleToStdio ); - IniBitBool( SkipBiosSplash ); + IniBitBool( UseBOOT2Injection ); IniBitBool( McdEnableEjection ); IniBitBool( MultitapPort0_Enabled ); diff --git a/pcsx2/PluginManager.cpp b/pcsx2/PluginManager.cpp index 614be9fcdf..10bc7f6c99 100644 --- a/pcsx2/PluginManager.cpp +++ b/pcsx2/PluginManager.cpp @@ -708,88 +708,193 @@ static void PS2E_CALLBACK pcsx2_OSD_WriteLn( int icon, const char* msg ) } // --------------------------------------------------------------------------------- -// Plugin Manager Implementation +// PluginStatus_t Implementations // --------------------------------------------------------------------------------- - -PluginManager::PluginManager( const wxString (&folders)[PluginId_Count] ) +PluginManager::PluginStatus_t::PluginStatus_t( PluginsEnum_t _pid, const wxString& srcfile ) + : Filename( srcfile ) { + pid = _pid; + + IsInitialized = false; + IsOpened = false; + + if( Filename.IsEmpty() ) + throw Exception::PluginInitError( pid, "Empty plugin filename." ); + + if( !wxFile::Exists( Filename ) ) + throw Exception::PluginLoadError( pid, srcfile, + wxLt("The configured %s plugin file was not found") + ); + + if( !Lib.Load( Filename ) ) + throw Exception::PluginLoadError( pid, Filename, + wxLt("The configured %s plugin file is not a valid dynamic library") + ); + + + // Try to enumerate the new v2.0 plugin interface first. + // If that fails, fall back on the old style interface. + + //m_libs[i].GetSymbol( L"PS2E_InitAPI" ); // on the TODO list! + + + // 2.0 API Failed; Enumerate the Old Stuff! --> + + _PS2EgetLibName GetLibName = (_PS2EgetLibName) Lib.GetSymbol( L"PS2EgetLibName" ); + _PS2EgetLibVersion2 GetLibVersion2 = (_PS2EgetLibVersion2) Lib.GetSymbol( L"PS2EgetLibVersion2" ); + _PS2EsetEmuVersion SetEmuVersion = (_PS2EsetEmuVersion) Lib.GetSymbol( L"PS2EsetEmuVersion" ); + + if( GetLibName == NULL || GetLibVersion2 == NULL ) + throw Exception::PluginLoadError( pid, Filename, + L"\nMethod binding failure on GetLibName or GetLibVersion2.\n", + _( "Configured plugin is not a PCSX2 plugin, or is for an older unsupported version of PCSX2." ) + ); + + if( SetEmuVersion != NULL ) + SetEmuVersion( "PCSX2", (0ul << 24) | (9ul<<16) | (7ul<<8) | 0 ); + + Name = fromUTF8( GetLibName() ); + int version = GetLibVersion2( tbl_PluginInfo[pid].typemask ); + Version.Printf( L"%d.%d.%d", (version>>8)&0xff, version&0xff, (version>>24)&0xff ); + + + // Bind Required Functions + // (generate critical error if binding fails) + + BindCommon( pid ); + BindRequired( pid ); + BindOptional( pid ); + + // Run Plugin's Functionality Test. + // A lot of plugins don't bother to implement this function and return 0 (success) + // regardless, but some do so let's go ahead and check it. I mean, we're supposed to. :) + + int testres = CommonBindings.Test(); + if( testres != 0 ) + throw Exception::PluginLoadError( pid, Filename, + wxsFormat( L"Plugin Test failure, return code: %d", testres ), + _( "The plugin reports that your hardware or software/drivers are not supported." ) + ); +} + +void PluginManager::PluginStatus_t::BindCommon( PluginsEnum_t pid ) +{ + const LegacyApi_CommonMethod* current = s_MethMessCommon; + VoidMethod** target = (VoidMethod**)&CommonBindings; + wxDoNotLogInThisScope please; - Console.WriteLn( Color_StrongBlue, "Loading plugins..." ); - - const PluginInfo* pi = tbl_PluginInfo; do + while( current->MethodName != NULL ) { - ConsoleIndentScope indent; - const PluginsEnum_t pid = pi->id; + *target = (VoidMethod*)Lib.GetSymbol( current->GetMethodName( pid ) ); - Console.WriteLn( L"Binding %s\t: %s ", tbl_PluginInfo[pid].GetShortname().c_str(), folders[pid].c_str() ); + if( *target == NULL ) + *target = current->Fallback; - if( folders[pid].IsEmpty() ) - throw Exception::PluginInitError( pi->id, "Empty plugin filename." ); - - m_info[pid].Filename = folders[pid]; - - if( !wxFile::Exists( folders[pid] ) ) - throw Exception::PluginLoadError( pid, folders[pid], - wxLt("The configured %s plugin file was not found") - ); - - if( !m_info[pid].Lib.Load( folders[pid] ) ) - throw Exception::PluginLoadError( pid, folders[pid], - wxLt("The configured %s plugin file is not a valid dynamic library") - ); - - // Try to enumerate the new v2.0 plugin interface first. - // If that fails, fall back on the old style interface. - - //m_libs[i].GetSymbol( L"PS2E_InitAPI" ); - - // Fetch plugin name and version information - - _PS2EgetLibName GetLibName = (_PS2EgetLibName) m_info[pid].Lib.GetSymbol( L"PS2EgetLibName" ); - _PS2EgetLibVersion2 GetLibVersion2 = (_PS2EgetLibVersion2) m_info[pid].Lib.GetSymbol( L"PS2EgetLibVersion2" ); - _PS2EsetEmuVersion SetEmuVersion = (_PS2EsetEmuVersion) m_info[pid].Lib.GetSymbol( L"PS2EsetEmuVersion" ); - - if( GetLibName == NULL || GetLibVersion2 == NULL ) - throw Exception::PluginLoadError( pid, m_info[pid].Filename, - L"\nMethod binding failure on GetLibName or GetLibVersion2.\n", + if( *target == NULL ) + { + throw Exception::PluginLoadError( pid, Filename, + wxsFormat( L"\nMethod binding failure on: %s\n", current->GetMethodName( pid ).c_str() ), _( "Configured plugin is not a PCSX2 plugin, or is for an older unsupported version of PCSX2." ) ); + } - if( SetEmuVersion != NULL ) - SetEmuVersion( "PCSX2", (0ul << 24) | (9ul<<16) | (7ul<<8) | 0 ); + target++; + current++; + } +} - m_info[pid].Name = fromUTF8( GetLibName() ); - int version = GetLibVersion2( tbl_PluginInfo[pid].typemask ); - m_info[pid].Version.Printf( L"%d.%d.%d", (version>>8)&0xff, version&0xff, (version>>24)&0xff ); +void PluginManager::PluginStatus_t::BindRequired( PluginsEnum_t pid ) +{ + const LegacyApi_ReqMethod* current = s_MethMessReq[pid]; + const wxDynamicLibrary& lib = Lib; - // Bind Required Functions - // (generate critical error if binding fails) + wxDoNotLogInThisScope please; - BindCommon( pid ); - BindRequired( pid ); - BindOptional( pid ); + while( current->MethodName != NULL ) + { + *(current->Dest) = (VoidMethod*)lib.GetSymbol( current->GetMethodName() ); - // Run Plugin's Functionality Test. - // A lot of plugins don't bother to implement this function and return 0 (success) - // regardless, but some do so let's go ahead and check it. I mean, we're supposed to. :) + if( *(current->Dest) == NULL ) + *(current->Dest) = current->Fallback; - int testres = m_info[pi->id].CommonBindings.Test(); - if( testres != 0 ) - throw Exception::PluginLoadError( pid, m_info[pid].Filename, - wxsFormat( L"Plugin Test failure, return code: %d", testres ), - _( "The plugin reports that your hardware or software/drivers are not supported." ) + if( *(current->Dest) == NULL ) + { + throw Exception::PluginLoadError( pid, Filename, + wxsFormat( L"\nMethod binding failure on: %s\n", current->GetMethodName().c_str() ), + _( "Configured plugin is not a valid PCSX2 plugin, or is for an older unsupported version of PCSX2." ) ); + } + current++; + } +} + +void PluginManager::PluginStatus_t::BindOptional( PluginsEnum_t pid ) +{ + const LegacyApi_OptMethod* current = s_MethMessOpt[pid]; + const wxDynamicLibrary& lib = Lib; + + wxDoNotLogInThisScope please; + + while( current->MethodName != NULL ) + { + *(current->Dest) = (VoidMethod*)lib.GetSymbol( current->GetMethodName() ); + current++; + } +} + +// ===================================================================================== +// PluginManager Implementations +// ===================================================================================== + +PluginManager::PluginManager() +{ +} + +PluginManager::~PluginManager() throw() +{ + try + { + Unload(); + } + DESTRUCTOR_CATCHALL + + // All library unloading done automatically by wx. +} + +void PluginManager::Load( PluginsEnum_t pid, const wxString& srcfile ) +{ + ScopedLock lock( m_mtx_PluginStatus ); + pxAssume( (uint)pid < PluginId_Count ); + Console.WriteLn( L"Binding %s\t: %s ", tbl_PluginInfo[pid].GetShortname().c_str(), srcfile.c_str() ); + m_info[pid] = new PluginStatus_t( pid, srcfile ); +} + +void PluginManager::Load( const wxString (&folders)[PluginId_Count] ) +{ + ScopedLock lock( m_mtx_PluginStatus ); + + if( !NeedsLoad() ) return; + + wxDoNotLogInThisScope please; + + Console.WriteLn( Color_StrongBlue, "Loading plugins..." ); + + ConsoleIndentScope indent; + const PluginInfo* pi = tbl_PluginInfo; do + { + Load( pi->id, folders[pi->id] ); pxYield( 2 ); } while( ++pi, pi->shortname != NULL ); + indent.EndScope(); 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; + PADinit = (_PADinit)m_info[PluginId_PAD]->CommonBindings.Init; + m_info[PluginId_PAD]->CommonBindings.Init = _hack_PADinit; Console.WriteLn( Color_StrongBlue, "Plugins loaded successfully.\n" ); @@ -822,90 +927,34 @@ PluginManager::PluginManager( const wxString (&folders)[PluginId_Count] ) throw Exception::PluginLoadError( PluginId_Mcd, wxEmptyString, "Internal Memorycard Plugin failed to load." ); } - g_plugins = this; - SendSettingsFolder(); } -PluginManager::~PluginManager() throw() +void PluginManager::Unload(PluginsEnum_t pid) { - try - { - Close(); - Shutdown(); - } - DESTRUCTOR_CATCHALL - // All library unloading done automatically. - - if( g_plugins == this ) - g_plugins = NULL; + ScopedLock lock( m_mtx_PluginStatus ); + pxAssume( (uint)pid < PluginId_Count ); + m_info[pid].Delete(); } -void PluginManager::BindCommon( PluginsEnum_t pid ) +void PluginManager::Unload() { - const LegacyApi_CommonMethod* current = s_MethMessCommon; - VoidMethod** target = (VoidMethod**)&m_info[pid].CommonBindings; + ScopedLock lock( m_mtx_PluginStatus ); - wxDoNotLogInThisScope please; + if( NeedsShutdown() ) + Console.Warning( "(SysCorePlugins) Warning: Unloading plugins prior to shutdown!" ); - while( current->MethodName != NULL ) - { - *target = (VoidMethod*)m_info[pid].Lib.GetSymbol( current->GetMethodName( pid ) ); + //Shutdown(); - if( *target == NULL ) - *target = current->Fallback; + if( !NeedsUnload() ) return; - if( *target == NULL ) - { - throw Exception::PluginLoadError( pid, m_info[pid].Filename, - wxsFormat( L"\nMethod binding failure on: %s\n", current->GetMethodName( pid ).c_str() ), - _( "Configured plugin is not a PCSX2 plugin, or is for an older unsupported version of PCSX2." ) - ); - } + DbgCon.WriteLn( Color_StrongBlue, "Unloading plugins..." ); - target++; - current++; - } -} + for( int i=PluginId_Count-1; i>=0; --i ) + Unload( tbl_PluginInfo[i].id ); -void PluginManager::BindRequired( PluginsEnum_t pid ) -{ - const LegacyApi_ReqMethod* current = s_MethMessReq[pid]; - const wxDynamicLibrary& lib = m_info[pid].Lib; + DbgCon.WriteLn( Color_StrongBlue, "Plugins unloaded successfully." ); - wxDoNotLogInThisScope please; - - while( current->MethodName != NULL ) - { - *(current->Dest) = (VoidMethod*)lib.GetSymbol( current->GetMethodName() ); - - if( *(current->Dest) == NULL ) - *(current->Dest) = current->Fallback; - - if( *(current->Dest) == NULL ) - { - throw Exception::PluginLoadError( pid, m_info[pid].Filename, - wxsFormat( L"\nMethod binding failure on: %s\n", current->GetMethodName().c_str() ), - _( "Configured plugin is not a valid PCSX2 plugin, or is for an older unsupported version of PCSX2." ) - ); - } - - current++; - } -} - -void PluginManager::BindOptional( PluginsEnum_t pid ) -{ - const LegacyApi_OptMethod* current = s_MethMessOpt[pid]; - const wxDynamicLibrary& lib = m_info[pid].Lib; - - wxDoNotLogInThisScope please; - - while( current->MethodName != NULL ) - { - *(current->Dest) = (VoidMethod*)lib.GetSymbol( current->GetMethodName() ); - current++; - } } // Exceptions: @@ -978,7 +1027,8 @@ bool PluginManager::OpenPlugin_FW() void PluginManager::Open( PluginsEnum_t pid ) { - if( m_info[pid].IsOpened ) return; + pxAssume( (uint)pid < PluginId_Count ); + if( IsOpen(pid) ) return; Console.Indent().WriteLn( "Opening %s", tbl_PluginInfo[pid].shortname ); @@ -999,29 +1049,14 @@ void PluginManager::Open( PluginsEnum_t pid ) if( !result ) throw Exception::PluginOpenError( pid ); - m_info[pid].IsOpened = true; -} - -bool PluginManager::NeedsOpen() const -{ - const PluginInfo* pi = tbl_PluginInfo; do { - if( !m_info[pi->id].IsOpened ) break; - } while( ++pi, pi->shortname != NULL ); - - return pi->shortname != NULL; -} - -bool PluginManager::NeedsClose() const -{ - const PluginInfo* pi = tbl_PluginInfo; do { - if( m_info[pi->id].IsOpened ) break; - } while( ++pi, pi->shortname != NULL ); - - return pi->shortname != NULL; + ScopedLock lock( m_mtx_PluginStatus ); + if( m_info[pid] ) m_info[pid]->IsOpened = true; } void PluginManager::Open() { + Init(); + if( !NeedsOpen() ) return; // Spam stopper: returns before writing any logs. >_< Console.WriteLn( Color_StrongBlue, "Opening plugins..." ); @@ -1041,12 +1076,23 @@ void PluginManager::Open() Console.WriteLn( Color_StrongBlue, "Plugins opened successfully." ); } +void PluginManager::_generalclose( PluginsEnum_t pid ) +{ + ScopedLock lock( m_mtx_PluginStatus ); + if( m_info[pid] ) m_info[pid]->CommonBindings.Close(); +} + void PluginManager::ClosePlugin_GS() { - // force-close PAD before GS, because the PAD depends on the GS window. + // old-skool: force-close PAD before GS, because the PAD depends on the GS window. - Close( PluginId_PAD ); - GetMTGS().Suspend(); + if( GetMTGS().IsSelf() ) + _generalclose( PluginId_GS ); + else + { + if( !GSopen2 ) Close( PluginId_PAD ); + GetMTGS().Suspend(); + } } void PluginManager::ClosePlugin_CDVD() @@ -1056,33 +1102,35 @@ void PluginManager::ClosePlugin_CDVD() void PluginManager::ClosePlugin_PAD() { - m_info[PluginId_PAD].CommonBindings.Close(); + _generalclose( PluginId_PAD ); } void PluginManager::ClosePlugin_SPU2() { - m_info[PluginId_SPU2].CommonBindings.Close(); + _generalclose( PluginId_SPU2 ); } void PluginManager::ClosePlugin_DEV9() { - m_info[PluginId_DEV9].CommonBindings.Close(); + _generalclose( PluginId_DEV9 ); } void PluginManager::ClosePlugin_USB() { - m_info[PluginId_USB].CommonBindings.Close(); + _generalclose( PluginId_USB ); } void PluginManager::ClosePlugin_FW() { - m_info[PluginId_FW].CommonBindings.Close(); + _generalclose( PluginId_FW ); } void PluginManager::Close( PluginsEnum_t pid ) { - if( !m_info[pid].IsOpened ) return; + pxAssume( (uint)pid < PluginId_Count ); + + if( !IsOpen(pid) ) return; Console.Indent().WriteLn( "Closing %s", tbl_PluginInfo[pid].shortname ); switch( pid ) @@ -1094,11 +1142,12 @@ void PluginManager::Close( PluginsEnum_t pid ) case PluginId_USB: ClosePlugin_USB(); break; case PluginId_FW: ClosePlugin_FW(); break; case PluginId_DEV9: ClosePlugin_DEV9(); break; - + jNO_DEFAULT; } - m_info[pid].IsOpened = false; + ScopedLock lock( m_mtx_PluginStatus ); + if( m_info[pid] ) m_info[pid]->IsOpened = false; } void PluginManager::Close() @@ -1126,22 +1175,26 @@ void PluginManager::Close() // void PluginManager::Init() { + ScopedLock lock( m_mtx_PluginStatus ); + + if( !NeedsInit() ) return; + bool printlog = false; const PluginInfo* pi = tbl_PluginInfo; do { const PluginsEnum_t pid = pi->id; - if( m_info[pid].IsInitialized ) continue; + if( !m_info[pid] || m_info[pid]->IsInitialized ) continue; if( !printlog ) { Console.WriteLn( Color_StrongBlue, "Initializing plugins..." ); printlog = true; } Console.Indent().WriteLn( "Init %s", tbl_PluginInfo[pid].shortname ); - if( 0 != m_info[pid].CommonBindings.Init() ) + if( 0 != m_info[pid]->CommonBindings.Init() ) throw Exception::PluginInitError( pid ); - m_info[pid].IsInitialized = true; + m_info[pid]->IsInitialized = true; } while( ++pi, pi->shortname != NULL ); if( SysPlugins.Mcd == NULL ) @@ -1166,9 +1219,13 @@ void PluginManager::Init() // void PluginManager::Shutdown() { - GetMTGS().Cancel(); // cancel it for speedier shutdown! + ScopedLock lock( m_mtx_PluginStatus ); + if( !NeedsShutdown() ) return; - Close(); + pxAssumeDev( !NeedsClose(), "Cannot shut down plugins prior to Close()" ); + + GetMTGS().Cancel(); // cancel it for speedier shutdown! + DbgCon.WriteLn( Color_StrongGreen, "Shutting down plugins..." ); // Shutdown plugins in reverse order (probably doesn't matter... @@ -1177,10 +1234,10 @@ void PluginManager::Shutdown() for( int i=PluginId_Count-1; i>=0; --i ) { const PluginsEnum_t pid = tbl_PluginInfo[i].id; - if( !m_info[pid].IsInitialized ) continue; + if( !m_info[pid] || !m_info[pid]->IsInitialized ) continue; DevCon.Indent().WriteLn( "Shutdown %s", tbl_PluginInfo[pid].shortname ); - m_info[pid].IsInitialized = false; - m_info[pid].CommonBindings.Shutdown(); + m_info[pid]->IsInitialized = false; + m_info[pid]->CommonBindings.Shutdown(); } // More memorycard hacks!! @@ -1208,7 +1265,8 @@ bool PluginManager::DoFreeze( PluginsEnum_t pid, int mode, freezeData* data ) } else { - return m_info[pid].CommonBindings.Freeze( mode, data ) != -1; + ScopedLock lock( m_mtx_PluginStatus ); + return !m_info[pid] || m_info[pid]->CommonBindings.Freeze( mode, data ) != -1; } } @@ -1219,6 +1277,9 @@ bool PluginManager::DoFreeze( PluginsEnum_t pid, int mode, freezeData* data ) // void PluginManager::Freeze( PluginsEnum_t pid, SaveStateBase& state ) { + // No locking leeded -- DoFreeze locks as needed, and this avoids MTGS deadlock. + //ScopedLock lock( m_mtx_PluginStatus ); + Console.Indent().WriteLn( "%s %s", state.IsSaving() ? "Saving" : "Loading", tbl_PluginInfo[pid].shortname ); @@ -1265,14 +1326,16 @@ void PluginManager::Freeze( PluginsEnum_t pid, SaveStateBase& state ) bool PluginManager::KeyEvent( const keyEvent& evt ) { + ScopedLock lock( m_mtx_PluginStatus ); + // [TODO] : The plan here is to give plugins "first chance" handling of keys. // Handling order will be fixed (GS, SPU2, PAD, etc), and the first plugin to // pick up the key and return "true" (for handled) will cause the loop to break. // The current version of PS2E doesn't support it yet, though. const PluginInfo* pi = tbl_PluginInfo; do { - if( pi->id != PluginId_PAD ) - m_info[pi->id].CommonBindings.KeyEvent( const_cast(&evt) ); + if( pi->id != PluginId_PAD && m_info[pi->id] ) + m_info[pi->id]->CommonBindings.KeyEvent( const_cast(&evt) ); } while( ++pi, pi->shortname != NULL ); return false; @@ -1280,23 +1343,26 @@ bool PluginManager::KeyEvent( const keyEvent& evt ) void PluginManager::SendSettingsFolder() { + ScopedLock lock( m_mtx_PluginStatus ); if( m_SettingsFolder.IsEmpty() ) return; wxCharBuffer utf8buffer( m_SettingsFolder.ToUTF8() ); const PluginInfo* pi = tbl_PluginInfo; do { - m_info[pi->id].CommonBindings.SetSettingsDir( utf8buffer ); + if( m_info[pi->id] ) m_info[pi->id]->CommonBindings.SetSettingsDir( utf8buffer ); } while( ++pi, pi->shortname != NULL ); } void PluginManager::SetSettingsFolder( const wxString& folder ) { + ScopedLock lock( m_mtx_PluginStatus ); + wxString fixedfolder( folder ); if( !fixedfolder.IsEmpty() && (fixedfolder[fixedfolder.length()-1] != wxFileName::GetPathSeparator() ) ) { fixedfolder += wxFileName::GetPathSeparator(); } - + if( m_SettingsFolder == fixedfolder ) return; m_SettingsFolder = fixedfolder; SendSettingsFolder(); @@ -1304,26 +1370,126 @@ void PluginManager::SetSettingsFolder( const wxString& folder ) void PluginManager::Configure( PluginsEnum_t pid ) { - m_info[pid].CommonBindings.Configure(); + ScopedLock lock( m_mtx_PluginStatus ); + if( m_info[pid] ) m_info[pid]->CommonBindings.Configure(); } -PluginManager* PluginManager_Create( const wxChar* (&folders)[PluginId_Count] ) +bool PluginManager::AreLoaded() const { - wxString passins[PluginId_Count]; + ScopedLock lock( m_mtx_PluginStatus ); + for( int i=0; iid] = folders[pi->id]; + if( IsInitialized(pi->id) ) return true; } while( ++pi, pi->shortname != NULL ); - return new PluginManager( passins ); + return false; } -static PluginManagerBase s_pluginman_placebo; - -// retrieves a handle to the current plugin manager. Plugin manager is assumed to be valid, -// and debug-level assertions are performed on the validity of the handle. -PluginManagerBase& GetPluginManager() +bool PluginManager::IsOpen( PluginsEnum_t pid ) const { - if( g_plugins == NULL ) return s_pluginman_placebo; - return *g_plugins; + pxAssume( (uint)pid < PluginId_Count ); + ScopedLock lock( m_mtx_PluginStatus ); + return m_info[pid] && m_info[pid]->IsOpened; +} + +bool PluginManager::IsInitialized( PluginsEnum_t pid ) const +{ + pxAssume( (uint)pid < PluginId_Count ); + ScopedLock lock( m_mtx_PluginStatus ); + return m_info[pid] && m_info[pid]->IsInitialized; +} + +bool PluginManager::IsLoaded( PluginsEnum_t pid ) const +{ + pxAssume( (uint)pid < PluginId_Count ); + return !!m_info[pid]; +} + +bool PluginManager::NeedsLoad() const +{ + const PluginInfo* pi = tbl_PluginInfo; do { + if( !IsLoaded(pi->id) ) return true; + } while( ++pi, pi->shortname != NULL ); + + return false; +} + +bool PluginManager::NeedsUnload() const +{ + const PluginInfo* pi = tbl_PluginInfo; do { + if( IsLoaded(pi->id) ) return true; + } while( ++pi, pi->shortname != NULL ); + + return false; +} + +bool PluginManager::NeedsInit() const +{ + const PluginInfo* pi = tbl_PluginInfo; do { + if( !IsInitialized(pi->id) ) return true; + } while( ++pi, pi->shortname != NULL ); + + return false; +} + +bool PluginManager::NeedsShutdown() const +{ + const PluginInfo* pi = tbl_PluginInfo; do { + if( IsInitialized(pi->id) ) return true; + } while( ++pi, pi->shortname != NULL ); + + return false; +} + +bool PluginManager::NeedsOpen() const +{ + const PluginInfo* pi = tbl_PluginInfo; do { + if( !IsOpen(pi->id) ) return true; + } while( ++pi, pi->shortname != NULL ); + + return false; +} + +bool PluginManager::NeedsClose() const +{ + const PluginInfo* pi = tbl_PluginInfo; do { + if( IsOpen(pi->id) ) return true; + } while( ++pi, pi->shortname != NULL ); + + return false; +} + +const wxString PluginManager::GetName( PluginsEnum_t pid ) const +{ + ScopedLock lock( m_mtx_PluginStatus ); + pxAssume( (uint)pid < PluginId_Count ); + return m_info[pid] ? m_info[pid]->Name : _("Unloaded Plugin"); +} + +const wxString PluginManager::GetVersion( PluginsEnum_t pid ) const +{ + ScopedLock lock( m_mtx_PluginStatus ); + pxAssume( (uint)pid < PluginId_Count ); + return m_info[pid] ? m_info[pid]->Version : _("0.0"); } diff --git a/pcsx2/Plugins.h b/pcsx2/Plugins.h index 9b52609701..e021bdccff 100644 --- a/pcsx2/Plugins.h +++ b/pcsx2/Plugins.h @@ -21,6 +21,8 @@ #include "PS2Edefs.h" #include "PluginCallbacks.h" +#include "Utilities/Threading.h" + #include #ifdef _MSC_VER @@ -125,7 +127,7 @@ namespace Exception PluginId = pid; } }; - + // This exception is thrown when a plugin returns an error while trying to save itself. // Typically this should be a very rare occurance since a plugin typically shoudn't // be doing memory allocations or file access during state saving. @@ -222,57 +224,20 @@ public: extern SysPluginBindings SysPlugins; - -// -------------------------------------------------------------------------------------- -// PluginManagerBase Class -// -------------------------------------------------------------------------------------- -// Provides a basic placebo "do-nothing" interface for plugin management. This is used -// to avoid NULL pointer exceptions/segfaults when referencing the plugin manager global -// handle. -// -// Note: The Init and Freeze methods of this class will cause debug assertions, but Close -// methods fail silently, on the premise that Close and Shutdown are typically run from -// exception handlers or cleanup code, and null pointers should be silently ignored in -// favor of continuing cleanup. -// -class PluginManagerBase -{ - DeclareNoncopyableObject( PluginManagerBase ); - -public: - PluginManagerBase() {} - virtual ~PluginManagerBase() {} - - virtual void Init() { pxFail( "Null PluginManager!" ); } - virtual void Shutdown() {} - virtual void Open() { } - virtual void Open( PluginsEnum_t pid ) { pxFail( "Null PluginManager!" ); } - virtual void Close( PluginsEnum_t pid ) {} - virtual void Close() {} - - virtual bool IsOpen( PluginsEnum_t pid ) const { return false; } - - virtual void Freeze( PluginsEnum_t pid, SaveStateBase& state ) { pxFail( "Null PluginManager!" ); } - virtual bool DoFreeze( PluginsEnum_t pid, int mode, freezeData* data ) - { - pxFail( "Null PluginManager!" ); - return false; - } - - virtual bool KeyEvent( const keyEvent& evt ) { return false; } -}; - // -------------------------------------------------------------------------------------- // PluginManager Class // -------------------------------------------------------------------------------------- // -class PluginManager : public PluginManagerBase +class PluginManager { DeclareNoncopyableObject( PluginManager ); protected: - struct PluginStatus_t + class PluginStatus_t { + public: + PluginsEnum_t pid; + bool IsInitialized; bool IsOpened; @@ -283,23 +248,44 @@ protected: LegacyPluginAPI_Common CommonBindings; wxDynamicLibrary Lib; + public: PluginStatus_t() { IsInitialized = false; IsOpened = false; } + + PluginStatus_t( PluginsEnum_t _pid, const wxString& srcfile ); + virtual ~PluginStatus_t() throw() { } + + protected: + void BindCommon( PluginsEnum_t pid ); + void BindRequired( PluginsEnum_t pid ); + void BindOptional( PluginsEnum_t pid ); }; - const PS2E_LibraryAPI* m_mcdPlugin; - wxString m_SettingsFolder; + const PS2E_LibraryAPI* m_mcdPlugin; + wxString m_SettingsFolder; + Threading::MutexRecursive m_mtx_PluginStatus; public: // hack until we unsuck plugins... - PluginStatus_t m_info[PluginId_Count]; + ScopedPtr m_info[PluginId_Count]; public: - PluginManager( const wxString (&folders)[PluginId_Count] ); + PluginManager(); virtual ~PluginManager() throw(); + void Load( PluginsEnum_t pid, const wxString& srcfile ); + void Load( const wxString (&folders)[PluginId_Count] ); + void Unload(); + void Unload( PluginsEnum_t pid ); + + bool AreLoaded() const; + bool AreAnyLoaded() const; + bool AreAnyInitialized() const; + + Threading::Mutex& GetMutex() { return m_mtx_PluginStatus; } + virtual void Init(); virtual void Shutdown(); virtual void Open(); @@ -307,11 +293,10 @@ public: virtual void Close( PluginsEnum_t pid ); virtual void Close(); - virtual bool IsOpen( PluginsEnum_t pid ) const { return m_info[pid].IsOpened; } - - virtual bool NeedsClose() const; - virtual bool NeedsOpen() const; - + virtual bool IsOpen( PluginsEnum_t pid ) const; + virtual bool IsInitialized( PluginsEnum_t pid ) const; + virtual bool IsLoaded( PluginsEnum_t pid ) const; + virtual void Freeze( PluginsEnum_t pid, SaveStateBase& state ); virtual bool DoFreeze( PluginsEnum_t pid, int mode, freezeData* data ); @@ -320,15 +305,18 @@ public: virtual void SetSettingsFolder( const wxString& folder ); virtual void SendSettingsFolder(); - const wxString& GetName( PluginsEnum_t pid ) const { return m_info[pid].Name; } - const wxString& GetVersion( PluginsEnum_t pid ) const { return m_info[pid].Version; } - - friend PluginManager* PluginManager_Create( const wxChar* (&folders)[PluginId_Count] ); + const wxString GetName( PluginsEnum_t pid ) const; + const wxString GetVersion( PluginsEnum_t pid ) const; protected: - void BindCommon( PluginsEnum_t pid ); - void BindRequired( PluginsEnum_t pid ); - void BindOptional( PluginsEnum_t pid ); + virtual bool NeedsClose() const; + virtual bool NeedsOpen() const; + + virtual bool NeedsShutdown() const; + virtual bool NeedsInit() const; + + virtual bool NeedsLoad() const; + virtual bool NeedsUnload() const; virtual bool OpenPlugin_GS(); virtual bool OpenPlugin_CDVD(); @@ -337,6 +325,8 @@ protected: virtual bool OpenPlugin_DEV9(); virtual bool OpenPlugin_USB(); virtual bool OpenPlugin_FW(); + + void _generalclose( PluginsEnum_t pid ); virtual void ClosePlugin_GS(); virtual void ClosePlugin_CDVD(); @@ -350,11 +340,13 @@ protected: }; extern const PluginInfo tbl_PluginInfo[]; -extern PluginManager* g_plugins; -extern PluginManager* PluginManager_Create( const wxChar* (&folders)[PluginId_Count] ); +// GetPluginManager() is a required external implementation. This function is *NOT* +// provided by the PCSX2 core library. It provides an interface for the linking User +// Interface apps or DLLs to reference their own instance of PluginManager (also allowing +// them to extend the class and override virtual methods). -extern PluginManagerBase& GetPluginManager(); +extern PluginManager& GetCorePlugins(); // Hack to expose internal MemoryCard plugin: diff --git a/pcsx2/R5900.cpp b/pcsx2/R5900.cpp index 2ccd360e71..9da571d590 100644 --- a/pcsx2/R5900.cpp +++ b/pcsx2/R5900.cpp @@ -82,7 +82,7 @@ void cpuReset() psxReset(); g_GameStarted = false; - g_SkipBiosHack = EmuConfig.SkipBiosSplash; + g_SkipBiosHack = EmuConfig.UseBOOT2Injection; ElfCRC = 0; DiscID = L""; diff --git a/pcsx2/RecoverySystem.cpp b/pcsx2/RecoverySystem.cpp deleted file mode 100644 index 2ef1ab7a1a..0000000000 --- a/pcsx2/RecoverySystem.cpp +++ /dev/null @@ -1,598 +0,0 @@ -/* 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 te 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 "zlib/zlib.h" - -#include "App.h" -#include "HostGui.h" -#include "AppSaveStates.h" -#include "Utilities/EventSource.inl" - -class _BaseStateThread; - -template class EventSource; -static EventSource m_evtsrc_SaveState; - -// Used to hold the current state backup (fullcopy of PS2 memory and plugin states). -static SafeArray state_buffer; - -static _BaseStateThread* current_state_thread = NULL; - -// Simple lock boolean for the state buffer being in use by a thread. -static NonblockingMutex state_buffer_lock; - -// This boolean tracks if a savestate is actively saving. When a state is saving we -// typically delay program termination to allow the state time to finish its work. -//static bool state_is_saving = false; - -// 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 -// from the main thread. -int sys_resume_lock = 0; - -static FnType_OnThreadComplete* Callback_FreezeFinished = NULL; - -static bool StateCopy_ForceClear() -{ - sys_resume_lock = 0; - state_buffer_lock.Release(); - state_buffer.Dispose(); -} - -class _BaseStateThread : public PersistentThread, - public virtual EventListener_AppStatus, - public virtual IDeletableObject -{ - typedef PersistentThread _parent; - -protected: - bool m_isStarted; - - // Holds the pause/suspend state of the emulator when the state load/stave chain of action is started, - // so that the proper state can be restored automatically on completion. - bool m_resume_when_done; - -public: - virtual ~_BaseStateThread() throw() - { - if( !m_isStarted ) return; - - // Assertion fails because C++ changes the 'this' pointer to the base class since - // derived classes have been deallocated at this point the destructor! - - //pxAssumeDev( current_state_thread == this, wxCharNull ); - current_state_thread = NULL; - state_buffer_lock.Release(); // just in case; - } - - virtual bool IsFreezing() const=0; - -protected: - _BaseStateThread( const char* name, FnType_OnThreadComplete* onFinished ) - { - Callback_FreezeFinished = onFinished; - m_name = L"StateThread::" + fromUTF8(name); - m_isStarted = false; - m_resume_when_done = false; - } - - void OnStart() - { - if( !state_buffer_lock.TryAcquire() ) - throw Exception::CancelEvent( m_name + L"request ignored: state copy buffer is already locked!" ); - - _parent::OnStart(); - - current_state_thread = this; - m_isStarted = true; - } - - void SendFinishEvent( int type ) - { - wxGetApp().PostCommand( this, pxEvt_FreezeThreadFinished, type, m_resume_when_done ); - } - - void AppStatusEvent_OnExit() - { - Cancel(); - wxGetApp().DeleteObject( this ); - } -}; - -// -------------------------------------------------------------------------------------- -// 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.") ); - } - - bool IsFreezing() const { return true; } - -protected: - void OnStart() - { - try - { - ++sys_resume_lock; - m_resume_when_done = CoreThread.Pause(); - - _parent::OnStart(); - } - catch( ... ) - { - wxGetApp().DeleteObject( this ); - throw; - } - } - - void ExecuteTaskInThread() - { - memSavingState( state_buffer ).FreezeAll(); - } - - void OnCleanupInThread() - { - SendFinishEvent( SaveStateAction_CreateFinished ); - _parent::OnCleanupInThread(); - } -}; - - -static const char SavestateIdentString[] = "PCSX2 Savestate"; -static const uint SavestateIdentLen = sizeof(SavestateIdentString); - -// -------------------------------------------------------------------------------------- -// StateThread_ZipToDisk -// -------------------------------------------------------------------------------------- -class StateThread_ZipToDisk : public _BaseStateThread -{ - typedef _BaseStateThread _parent; - -protected: - const wxString m_filename; - gzFile m_gzfp; - -public: - StateThread_ZipToDisk( FnType_OnThreadComplete* onFinished, bool resume_done, const wxString& file ) - : _BaseStateThread( "ZipToDisk", onFinished ) - , m_filename( file ) - { - m_gzfp = NULL; - m_resume_when_done = resume_done; - } - - virtual ~StateThread_ZipToDisk() throw() - { - if( m_gzfp != NULL ) gzclose( m_gzfp ); - } - - bool IsFreezing() const { return true; } - -protected: - void OnStart() - { - try - { - m_gzfp = gzopen( m_filename.ToUTF8(), "wb" ); - if( m_gzfp == NULL ) - throw Exception::CreateStream( m_filename, "Cannot create savestate file for writing." ); - - _parent::OnStart(); - } - catch( ... ) - { - wxGetApp().DeleteObject( this ); - throw; - } - } - - void ExecuteTaskInThread() - { - Yield( 3 ); - - static const int BlockSize = 0x20000; - int curidx = 0; - - gzsetparams(m_gzfp, Z_BEST_SPEED, Z_FILTERED); // Best speed at good compression - gzbuffer(m_gzfp, 0x100000); // 1mb buffer size for less file fragments - - gzwrite(m_gzfp, SavestateIdentString, sizeof( SavestateIdentString )); - gzwrite(m_gzfp, &g_SaveVersion, sizeof( g_SaveVersion )); - - do - { - int thisBlockSize = std::min( BlockSize, state_buffer.GetSizeInBytes() - curidx ); - if( gzwrite( m_gzfp, state_buffer.GetPtr(curidx), thisBlockSize ) < thisBlockSize ) - throw Exception::BadStream( m_filename ); - curidx += thisBlockSize; - Yield( 1 ); - } while( curidx < state_buffer.GetSizeInBytes() ); - - Console.WriteLn( "State saved to disk without error." ); - } - - void OnCleanupInThread() - { - SendFinishEvent( SaveStateAction_ZipToDiskFinished ); - _parent::OnCleanupInThread(); - } -}; - - -// -------------------------------------------------------------------------------------- -// StateThread_UnzipFromDisk -// -------------------------------------------------------------------------------------- -class StateThread_UnzipFromDisk : public _BaseStateThread -{ - typedef _BaseStateThread _parent; - -protected: - 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( FnType_OnThreadComplete* onFinished, bool resume_done, const wxString& file ) - : _BaseStateThread( "UnzipFromDisk", onFinished ) - , m_filename( file ) - { - m_gzfp = NULL; - m_finished = false; - m_resume_when_done = resume_done; - } - - virtual ~StateThread_UnzipFromDisk() throw() - { - if( m_gzfp != NULL ) gzclose( m_gzfp ); - } - - bool IsFreezing() const { return false; } - -protected: - void OnStart() - { - try - { - m_gzfp = gzopen( m_filename.ToUTF8(), "rb" ); - if( m_gzfp == NULL ) - throw Exception::CreateStream( m_filename, "Cannot open savestate file for reading." ); - - char ident[SavestateIdentLen] = {0}; - - int result = gzread(m_gzfp, ident, SavestateIdentLen); - if( result == -1 ) - throw Exception::SaveStateLoadError( m_filename, "Unable to read any data from the gzip archive." ); - - if( result < SavestateIdentLen ) - throw Exception::SaveStateLoadError( m_filename ); - - if( strcmp(SavestateIdentString, ident) ) - throw Exception::SaveStateLoadError( m_filename, - wxsFormat( L"Unrecognized file signature while loading savestate: %s", ident ), - _("File is not a valid PCSX2 savestate, or is from an older unsupported version of PCSX2.") - ); - - u32 savever; - gzread(m_gzfp, &savever, sizeof(g_SaveVersion)); - - if( (savever >> 16) != (g_SaveVersion >> 16) ) - throw Exception::SaveStateLoadError( m_filename, - wxsFormat( L"Unrecognized file signature while loading savestate: %s", ident ), - _("File is not a valid PCSX2 savestate, or is from an older unsupported version of PCSX2.") - ); - - if( savever > g_SaveVersion ) - throw Exception::SaveStateLoadError( m_filename, - wxsFormat( L"Unrecognized file signature while loading savestate: %s", ident ), - _("File is not a valid PCSX2 savestate, or is from an older unsupported version of PCSX2.") - ); - - _parent::OnStart(); - } - catch( ... ) - { - wxGetApp().DeleteObject( this ); - throw; - } - } - - void ExecuteTaskInThread() - { - // fixme: should start initially with the file size, and then grow from there. - - static const int BlockSize = 0x100000; - state_buffer.MakeRoomFor( 0x800000 ); // start with an 8 meg buffer to avoid frequent reallocation. - gzbuffer(m_gzfp, 0x100000); // 1mb buffer for zlib internal operations - int curidx = 0; - do - { - state_buffer.MakeRoomFor( curidx+BlockSize ); - gzread( m_gzfp, state_buffer.GetPtr(curidx), BlockSize ); - curidx += BlockSize; - TestCancel(); - } while( !gzeof(m_gzfp) ); - - m_finished = true; - } - - void OnCleanupInThread() - { - SendFinishEvent( SaveStateAction_UnzipFromDiskFinished ); - _parent::OnCleanupInThread(); - } -}; - - -void Pcsx2App::OnFreezeThreadFinished( wxCommandEvent& evt ) -{ - // 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; - - { - ScopedPtr thr( (PersistentThread*)evt.GetClientData() ); - if( !pxAssertDev( thr != NULL, "NULL thread handle on freeze finished?" ) ) return; - - current_state_thread = NULL; - state_buffer_lock.Release(); - --sys_resume_lock; - - m_evtsrc_SaveState.Dispatch( (SaveStateActionType)evt.GetInt() ); - - thr->RethrowException(); - } - - if( fn_tmp != NULL ) fn_tmp( evt ); -} - -static void OnFinished_Resume( const wxCommandEvent& evt ) -{ - CoreThread.RecoverState(); - if( evt.GetExtraLong() ) CoreThread.Resume(); -} - -static wxString zip_dest_filename; - -static void OnFinished_ZipToDisk( const wxCommandEvent& evt ) -{ - if( !pxAssertDev( evt.GetInt() == SaveStateAction_CreateFinished, "Unexpected StateThreadAction value, aborting save." ) ) return; - - if( zip_dest_filename.IsEmpty() ) - { - Console.Warning( "Cannot save state to disk: empty filename specified." ); - return; - } - - // Phase 2: Record to disk!! - (new StateThread_ZipToDisk( NULL, !!evt.GetExtraLong(), zip_dest_filename ))->Start(); - - CoreThread.Resume(); -} - -class InvokeAction_WhenSaveComplete : - public IEventListener_SaveStateThread, - public IDeletableObject -{ -protected: - IActionInvocation* m_action; - -public: - InvokeAction_WhenSaveComplete( IActionInvocation* action ) - { - m_action = action; - } - - virtual ~InvokeAction_WhenSaveComplete() throw() {} - - void SaveStateAction_OnZipToDiskFinished() - { - if( m_action ) - { - m_action->InvokeAction(); - safe_delete( m_action ); - } - wxGetApp().DeleteObject( this ); - } -}; - -class InvokeAction_WhenStateCopyComplete : public InvokeAction_WhenSaveComplete -{ -public: - InvokeAction_WhenStateCopyComplete( IActionInvocation* action ) - : InvokeAction_WhenSaveComplete( action ) - { - } - - virtual ~InvokeAction_WhenStateCopyComplete() throw() {} - - void SaveStateAction_OnCreateFinished() - { - SaveStateAction_OnZipToDiskFinished(); - } -}; - -// ===================================================================================================== -// StateCopy Public Interface -// ===================================================================================================== - -bool StateCopy_InvokeOnSaveComplete( IActionInvocation* sst ) -{ - AffinityAssert_AllowFromMain(); - - if( current_state_thread == NULL || !current_state_thread->IsFreezing() ) - { - delete sst; - return false; - } - - m_evtsrc_SaveState.Add( new InvokeAction_WhenSaveComplete( sst ) ); - return true; -} - -bool StateCopy_InvokeOnCopyComplete( IActionInvocation* sst ) -{ - AffinityAssert_AllowFromMain(); - - if( current_state_thread == NULL || !current_state_thread->IsFreezing() ) - { - delete sst; - return false; - } - - m_evtsrc_SaveState.Add( new InvokeAction_WhenStateCopyComplete( sst ) ); - return true; -} - -void StateCopy_SaveToFile( const wxString& file ) -{ - if( state_buffer_lock.IsLocked() ) return; - zip_dest_filename = file; - (new StateThread_Freeze( OnFinished_ZipToDisk ))->Start(); - Console.WriteLn( Color_StrongGreen, L"Saving savestate to file: %s", zip_dest_filename.c_str() ); -} - -void StateCopy_LoadFromFile( const wxString& file ) -{ - if( state_buffer_lock.IsLocked() ) return; - bool resume_when_done = CoreThread.Pause(); - (new StateThread_UnzipFromDisk( OnFinished_Resume, resume_when_done, file ))->Start(); -} - -// Saves recovery state info to the given saveslot, or saves the active emulation state -// (if one exists) and no recovery data was found. This is needed because when a recovery -// state is made, the emulation state is usually reset so the only persisting state is -// the one in the memory save. :) -void StateCopy_SaveToSlot( uint num ) -{ - if( state_buffer_lock.IsLocked() ) return; - - zip_dest_filename = SaveStateBase::GetFilename( num ); - (new StateThread_Freeze( OnFinished_ZipToDisk ))->Start(); - Console.WriteLn( Color_StrongGreen, "Saving savestate to slot %d...", num ); - Console.Indent().WriteLn( Color_StrongGreen, L"filename: %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.Warning( "Savestate slot %d is empty.", slot ); - return; - } - - Console.WriteLn( Color_StrongGreen, "Loading savestate from slot %d...", slot ); - Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", file.c_str() ); - - bool resume_when_done = CoreThread.Pause(); - (new StateThread_UnzipFromDisk( OnFinished_Resume, resume_when_done, file ))->Start(); -} - -bool StateCopy_IsValid() -{ - return !state_buffer.IsDisposed(); -} - -const SafeArray* StateCopy_GetBuffer() -{ - if( state_buffer_lock.IsLocked() || state_buffer.IsDisposed() ) return NULL; - return &state_buffer; -} - -void StateCopy_FreezeToMem() -{ - if( state_buffer_lock.IsLocked() ) return; - (new StateThread_Freeze( OnFinished_Resume ))->Start(); -} - -class Acquire_And_Block -{ -protected: - bool m_DisposeWhenFinished; - bool m_Acquired; - -public: - Acquire_And_Block( bool dispose ) - { - m_DisposeWhenFinished = dispose; - m_Acquired = false; - - /* - // If the state buffer is locked and we're being called from the main thread then we need - // to cancel the current action. This is needed because state_buffer_lock is only updated - // from events handled on the main thread. - - if( wxThread::IsMain() ) - throw Exception::CancelEvent( "Blocking ThawFromMem canceled due to existing state buffer lock." ); - else*/ - - while ( !state_buffer_lock.TryAcquire() ) - { - pxAssume( current_state_thread != NULL ); - current_state_thread->Block(); - wxGetApp().ProcessPendingEvents(); // Trying this for now, may or may not work due to recursive pitfalls (see above) - }; - - m_Acquired = true; - } - - virtual ~Acquire_And_Block() throw() - { - if( m_DisposeWhenFinished ) - state_buffer.Dispose(); - - if( m_Acquired ) - state_buffer_lock.Release(); - } -}; - -void StateCopy_FreezeToMem_Blocking() -{ - Acquire_And_Block blocker( false ); - memSavingState( state_buffer ).FreezeAll(); -} - -// Copies the saved state into the active VM, and automatically free's the saved state data. -void StateCopy_ThawFromMem_Blocking() -{ - Acquire_And_Block blocker( true ); - memLoadingState( state_buffer ).FreezeAll(); -} - -void StateCopy_Clear() -{ - if( state_buffer_lock.IsLocked() ) return; - state_buffer.Dispose(); -} - -bool StateCopy_IsBusy() -{ - return state_buffer_lock.IsLocked(); -} diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp index 57ca5462bc..016def9929 100644 --- a/pcsx2/SaveState.cpp +++ b/pcsx2/SaveState.cpp @@ -47,8 +47,18 @@ wxString SaveStateBase::GetFilename( int slot ) } SaveStateBase::SaveStateBase( SafeArray& memblock ) - : m_memory( memblock ) { + Init( &memblock ); +} + +SaveStateBase::SaveStateBase( SafeArray* memblock ) +{ + Init( memblock ); +} + +void SaveStateBase::Init( SafeArray* memblock ) +{ + m_memory = memblock; m_version = g_SaveVersion; m_idx = 0; m_sectid = FreezeId_Unknown; @@ -58,12 +68,14 @@ SaveStateBase::SaveStateBase( SafeArray& memblock ) void SaveStateBase::PrepBlock( int size ) { + pxAssumeDev( m_memory, "Savestate memory/buffer pointer is null!" ); + const int end = m_idx+size; if( IsSaving() ) - m_memory.MakeRoomFor( end ); + m_memory->MakeRoomFor( end ); else { - if( m_memory.GetSizeInBytes() < end ) + if( m_memory->GetSizeInBytes() < end ) throw Exception::SaveStateLoadError(); } } @@ -203,7 +215,7 @@ void SaveStateBase::WritebackSectionLength( int seekpos, int sectlen, const wxCh if( IsSaving() ) { // write back the section length... - *((u32*)m_memory.GetPtr(seekpos-4)) = realsectsize; + *((u32*)m_memory->GetPtr(seekpos-4)) = realsectsize; } else // IsLoading!! { @@ -304,7 +316,7 @@ bool SaveStateBase::FreezeSection( int seek_section ) if( isSeeking ) m_idx += sectlen; else - g_plugins->Freeze( (PluginsEnum_t)m_pid, *this ); + GetCorePlugins().Freeze( (PluginsEnum_t)m_pid, *this ); WritebackSectionLength( seekpos, sectlen, L"Plugins" ); @@ -364,19 +376,26 @@ memSavingState::memSavingState( SafeArray& save_to ) { } +memSavingState::memSavingState( SafeArray* save_to ) + : SaveStateBase( save_to ) +{ +} + // Saving of state data void memSavingState::FreezeMem( void* data, int size ) { - m_memory.MakeRoomFor( m_idx+size ); - memcpy_fast( m_memory.GetPtr(m_idx), data, size ); + m_memory->MakeRoomFor( m_idx+size ); + memcpy_fast( m_memory->GetPtr(m_idx), data, size ); m_idx += size; } void memSavingState::FreezeAll() { + pxAssumeDev( m_memory, "Savestate memory/buffer pointer is null!" ); + // 90% of all savestates fit in under 45 megs (and require more than 43 megs, so might as well...) - m_memory.ChunkSize = ReallocThreshold; - m_memory.MakeRoomFor( MemoryBaseAllocSize ); + m_memory->ChunkSize = ReallocThreshold; + m_memory->MakeRoomFor( MemoryBaseAllocSize ); _parent::FreezeAll(); } @@ -386,12 +405,17 @@ memLoadingState::memLoadingState( const SafeArray& load_from ) { } +memLoadingState::memLoadingState( const SafeArray* load_from ) + : SaveStateBase( const_cast*>(load_from) ) +{ +} + memLoadingState::~memLoadingState() throw() { } // Loading of state data void memLoadingState::FreezeMem( void* data, int size ) { - const u8* const src = m_memory.GetPtr(m_idx); + const u8* const src = m_memory->GetPtr(m_idx); m_idx += size; memcpy_fast( data, src, size ); } diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h index 29b5fb52f4..3ad12c9ddd 100644 --- a/pcsx2/SaveState.h +++ b/pcsx2/SaveState.h @@ -110,7 +110,7 @@ namespace Exception class SaveStateBase { protected: - SafeArray& m_memory; + SafeArray* m_memory; char m_tagspace[32]; u32 m_version; // version of the savestate being loaded. @@ -123,6 +123,7 @@ protected: public: SaveStateBase( SafeArray& memblock ); + SaveStateBase( SafeArray* memblock ); virtual ~SaveStateBase() { } static wxString GetFilename( int slot ); @@ -159,7 +160,7 @@ public: u8* GetBlockPtr() { - return &m_memory[m_idx]; + return m_memory->GetPtr(m_idx); } void CommitBlock( int size ) @@ -190,6 +191,7 @@ public: void gsFreeze(); protected: + void Init( SafeArray* memblock ); // Load/Save functions for the various components of our glorious emulator! @@ -233,6 +235,7 @@ protected: public: virtual ~memSavingState() throw() { } memSavingState( SafeArray& save_to ); + memSavingState( SafeArray* save_to ); // Saving of state data to a memory buffer void FreezeMem( void* data, int size ); @@ -245,13 +248,15 @@ class memLoadingState : public SaveStateBase { public: virtual ~memLoadingState() throw(); + memLoadingState( const SafeArray& load_from ); + memLoadingState( const SafeArray* load_from ); // Loading of state data from a memory buffer... void FreezeMem( void* data, int size ); bool SeekToSection( PluginsEnum_t pid ); bool IsSaving() const { return false; } - bool IsFinished() const { return m_idx >= m_memory.GetSizeInBytes(); } + bool IsFinished() const { return m_idx >= m_memory->GetSizeInBytes(); } }; diff --git a/pcsx2/System.cpp b/pcsx2/System.cpp index 65f8da2a98..e5bfa0eb13 100644 --- a/pcsx2/System.cpp +++ b/pcsx2/System.cpp @@ -64,21 +64,21 @@ const Pcsx2Config EmuConfig; Pcsx2Config::GSOptions& SetGSConfig() { //DbgCon.WriteLn( "Direct modification of EmuConfig.GS detected" ); - AffinityAssert_AllowFromMain(); + AffinityAssert_AllowFrom_MainUI(); return const_cast(EmuConfig.GS); } ConsoleLogFilters& SetConsoleConfig() { //DbgCon.WriteLn( "Direct modification of EmuConfig.Log detected" ); - AffinityAssert_AllowFromMain(); + AffinityAssert_AllowFrom_MainUI(); return const_cast(EmuConfig.Log); } TraceLogFilters& SetTraceConfig() { //DbgCon.WriteLn( "Direct modification of EmuConfig.TraceLog detected" ); - AffinityAssert_AllowFromMain(); + AffinityAssert_AllowFrom_MainUI(); return const_cast(EmuConfig.Trace); } diff --git a/pcsx2/System/SysCoreThread.cpp b/pcsx2/System/SysCoreThread.cpp index 4f66782268..da7a14219b 100644 --- a/pcsx2/System/SysCoreThread.cpp +++ b/pcsx2/System/SysCoreThread.cpp @@ -57,25 +57,27 @@ void SysCoreThread::Cancel( bool isBlocking ) { m_CoreCancelDamnit = true; _parent::Cancel(); - ReleaseResumeLock(); } bool SysCoreThread::Cancel( const wxTimeSpan& span ) { m_CoreCancelDamnit = true; if( _parent::Cancel( span ) ) - { - ReleaseResumeLock(); return true; - } + return false; } +void SysCoreThread::OnStart() +{ + m_CoreCancelDamnit = false; + _parent::OnStart(); +} + void SysCoreThread::Start() { - if( g_plugins == NULL ) return; - g_plugins->Init(); - m_CoreCancelDamnit = false; // belongs in OnStart actually, but I'm tired :P + if( !GetCorePlugins().AreLoaded() ) return; + GetCorePlugins().Init(); _parent::Start(); } @@ -101,8 +103,8 @@ void SysCoreThread::OnResumeReady() // resumed manually). void SysCoreThread::RecoverState() { - Pause(); - m_resetVirtualMachine = true; + pxAssumeDev( IsPaused(), "Unsafe use of RecoverState function; Corethread is not paused/closed." ); + m_resetRecompilers = true; m_hasValidState = false; } @@ -122,50 +124,6 @@ void SysCoreThread::SetElfOverride( const wxString& elf ) m_elf_override = elf; } -ScopedCoreThreadSuspend::ScopedCoreThreadSuspend() -{ - m_ResumeWhenDone = GetCoreThread().Suspend(); -} - -ScopedCoreThreadSuspend::~ScopedCoreThreadSuspend() throw() -{ - if( m_ResumeWhenDone ) - { - Console.WriteLn( Color_Gray, "Scoped CoreThread suspend was not allowed to resume." ); - } -} - -// Resumes CoreThread execution, but *only* if it was in a running state when this object -// was instanized. Subsequent calls to Resume() will be ignored. -void ScopedCoreThreadSuspend::Resume() -{ - if( m_ResumeWhenDone ) - GetCoreThread().Resume(); - m_ResumeWhenDone = false; -} - -ScopedCoreThreadPause::ScopedCoreThreadPause() -{ - m_ResumeWhenDone = GetCoreThread().Pause(); -} - -ScopedCoreThreadPause::~ScopedCoreThreadPause() throw() -{ - if( m_ResumeWhenDone ) - { - Console.WriteLn( Color_Gray, "Scoped CoreThread pause was not allowed to resume." ); - } -} - -// Resumes CoreThread execution, but *only* if it was in a running state when this object -// was instanized. Subsequent calls to Resume() will be ignored. -void ScopedCoreThreadPause::Resume() -{ - if( m_ResumeWhenDone ) - GetCoreThread().Resume(); - m_ResumeWhenDone = false; -} - // Applies a full suite of new settings, which will automatically facilitate the necessary // resets of the core and components (including plugins, if needed). The scope of resetting @@ -175,27 +133,14 @@ void SysCoreThread::ApplySettings( const Pcsx2Config& src ) { if( src == EmuConfig ) return; - ScopedCoreThreadPause sys_paused; - + if( !pxAssertDev( IsPaused(), "CoreThread is not paused; settings cannot be applied." ) ) return; + m_resetRecompilers = ( src.Cpu != EmuConfig.Cpu ) || ( src.Recompiler != EmuConfig.Recompiler ) || ( src.Gamefixes != EmuConfig.Gamefixes ) || ( src.Speedhacks != EmuConfig.Speedhacks ); m_resetProfilers = ( src.Profiler != EmuConfig.Profiler ); m_resetVsyncTimers = ( src.GS != EmuConfig.GS ); const_cast(EmuConfig) = src; - sys_paused.Resume(); -} - -void SysCoreThread::ChangeCdvdSource( CDVD_SourceType type ) -{ - if( type == CDVDsys_GetSourceType() ) return; - - // Fast change of the CDVD source only -- a Pause will suffice. - - bool resumeWhenDone = Pause(); - GetPluginManager().Close( PluginId_CDVD ); - CDVDsys_ChangeSource( type ); - if( resumeWhenDone ) Resume(); } // -------------------------------------------------------------------------------------- @@ -306,6 +251,13 @@ void SysCoreThread::StateCheckInThread() _reset_stuff_as_needed(); // kinda redundant but could catch unexpected threaded state changes... } +// Allows an override point and solves an SEH "exception-type boundary" problem (can't mix +// SEH and C++ exceptions in the same function). +void SysCoreThread::DoCpuExecute() +{ + Cpu->Execute(); +} + void SysCoreThread::ExecuteTaskInThread() { Threading::EnableHiresScheduler(); @@ -317,21 +269,19 @@ void SysCoreThread::ExecuteTaskInThread() PCSX2_PAGEFAULT_PROTECT { do { StateCheckInThread(); - Cpu->Execute(); + DoCpuExecute(); } while( true ); } PCSX2_PAGEFAULT_EXCEPT; } void SysCoreThread::OnSuspendInThread() { - if( g_plugins != NULL ) - g_plugins->Close(); + GetCorePlugins().Close(); } void SysCoreThread::OnResumeInThread( bool isSuspended ) { - if( g_plugins != NULL ) - g_plugins->Open(); + GetCorePlugins().Open(); CpuInitializeMess(); } @@ -346,8 +296,7 @@ void SysCoreThread::OnCleanupInThread() Threading::DisableHiresScheduler(); - if( g_plugins != NULL ) - g_plugins->Close(); + GetCorePlugins().Close(); tls_coreThread = NULL; _parent::OnCleanupInThread(); diff --git a/pcsx2/System/SysThreadBase.cpp b/pcsx2/System/SysThreadBase.cpp index 5846e94bba..970e73be17 100644 --- a/pcsx2/System/SysThreadBase.cpp +++ b/pcsx2/System/SysThreadBase.cpp @@ -51,34 +51,15 @@ void SysThreadBase::OnStart() { if( !pxAssertDev( m_ExecMode == ExecMode_NoThreadYet, "SysSustainableThread:Start(): Invalid execution mode" ) ) return; - m_ResumeEvent.Reset(); + m_sem_Resume.Reset(); + m_sem_ChangingExecMode.Reset(); + FrankenMutex( m_ExecModeMutex ); FrankenMutex( m_RunningLock ); _parent::OnStart(); } -// (overridable) Timeout period before a thread is considered potentially -// deadlocked. SysThreadBase default is 4 seconds. -// -wxTimeSpan SysThreadBase::GetDeadlockTimeout() const -{ - return wxTimeSpan( 0, 0, 4, 0 ); -} - -void SysThreadBase::DoThreadDeadlocked() -{ - -} - -void SysThreadBase::ThrowDeadlockException() -{ - throw Exception::ThreadDeadlock( *this, - wxsFormat(L"Unhandled deadlock while suspending thread '%s'", m_name.c_str()), - wxsFormat(L"'%s' thread is not responding to suspend requests. It may be deadlocked or just running *really* slow.", m_name.c_str()) - ); -} - // 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. // @@ -95,21 +76,19 @@ void SysThreadBase::ThrowDeadlockException() // Exceptions: // 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 +// Suspension must cancel itself forcefully or risk crashing whatever other action is // in progress. // // ThreadDeadlock - thrown if isBlocking is true and the thread to suspend fails to // respond within the timeout period returned by GetDeadlockTimeout(). // -bool SysThreadBase::Suspend( bool isBlocking ) +void SysThreadBase::Suspend( bool isBlocking ) { - if( IsSelf() || !IsRunning() ) return false; + if( IsSelf() || !IsRunning() ) return; // shortcut ExecMode check to avoid deadlocking on redundant calls to Suspend issued // from Resume or OnResumeReady code. - if( m_ExecMode == ExecMode_Closed ) return false; - - bool retval = false; + if( m_ExecMode == ExecMode_Closed ) return; { ScopedLock locker( m_ExecModeMutex ); @@ -117,16 +96,20 @@ bool SysThreadBase::Suspend( bool isBlocking ) switch( m_ExecMode ) { // Check again -- status could have changed since above. - case ExecMode_Closed: return false; + case ExecMode_Closed: return; case ExecMode_Pausing: case ExecMode_Paused: - if( !isBlocking ) return retval; - throw Exception::CancelEvent( "Another thread is pausing the VM state." ); - + if( !isBlocking ) + throw Exception::CancelEvent( "Cannot suspend in non-blocking fashion: Another thread is pausing the VM state." ); + + m_ExecMode = ExecMode_Closing; + m_sem_Resume.Post(); + m_sem_ChangingExecMode.Wait(); + break; + case ExecMode_Opened: m_ExecMode = ExecMode_Closing; - retval = true; break; } @@ -135,40 +118,29 @@ bool SysThreadBase::Suspend( bool isBlocking ) } if( isBlocking ) - { - if( !m_RunningLock.Wait( GetDeadlockTimeout() ) ) - { - DoThreadDeadlocked(); - } - } - return retval; + m_RunningLock.Wait(); } // Returns: // The previous suspension state; true if the thread was running or false if it was // closed, not running, or paused. // -bool SysThreadBase::Pause() +void SysThreadBase::Pause() { - if( IsSelf() || !IsRunning() ) return false; + if( IsSelf() || !IsRunning() ) return; // shortcut ExecMode check to avoid deadlocking on redundant calls to Suspend issued // from Resume or OnResumeReady code. - if( (m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused) ) return false; - - bool retval = false; + if( (m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused) ) return; { ScopedLock locker( m_ExecModeMutex ); // Check again -- status could have changed since above. - if( (m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused) ) return false; + if( (m_ExecMode == ExecMode_Closed) || (m_ExecMode == ExecMode_Paused) ) return; if( m_ExecMode == ExecMode_Opened ) - { m_ExecMode = ExecMode_Pausing; - retval = true; - } pxAssumeDev( m_ExecMode == ExecMode_Pausing, "ExecMode should be nothing other than Pausing..." ); @@ -176,8 +148,6 @@ bool SysThreadBase::Pause() } m_RunningLock.Wait(); - - return retval; } // Resumes the core execution state, or does nothing is the core is already running. If @@ -199,9 +169,6 @@ void SysThreadBase::Resume() if( IsSelf() ) return; if( m_ExecMode == ExecMode_Opened ) return; - ScopedNonblockingLock resprotect( m_ResumeProtection ); - if( resprotect.Failed() ) return; - ScopedLock locker( m_ExecModeMutex ); // Implementation Note: @@ -231,7 +198,7 @@ void SysThreadBase::Resume() m_RunningLock.Wait(); if( !m_running ) return; if( (m_ExecMode != ExecMode_Closed) && (m_ExecMode != ExecMode_Paused) ) return; - if( g_plugins == NULL ) return; + if( !GetCorePlugins().AreLoaded() ) return; break; } @@ -240,7 +207,7 @@ void SysThreadBase::Resume() OnResumeReady(); m_ExecMode = ExecMode_Opened; - m_ResumeEvent.Post(); + m_sem_Resume.Post(); } @@ -296,11 +263,17 @@ void SysThreadBase::StateCheckInThread() case ExecMode_Paused: while( m_ExecMode == ExecMode_Paused ) - m_ResumeEvent.WaitWithoutYield(); - + m_sem_Resume.WaitWithoutYield(); + m_RunningLock.Acquire(); - OnResumeInThread( false ); - break; + if( m_ExecMode != ExecMode_Closing ) + { + OnResumeInThread( false ); + break; + } + m_sem_ChangingExecMode.Post(); + + // fallthrough if we're switching to closing state... // ------------------------------------- case ExecMode_Closing: @@ -313,7 +286,7 @@ void SysThreadBase::StateCheckInThread() case ExecMode_Closed: while( m_ExecMode == ExecMode_Closed ) - m_ResumeEvent.WaitWithoutYield(); + m_sem_Resume.WaitWithoutYield(); m_RunningLock.Acquire(); OnResumeInThread( true ); diff --git a/pcsx2/System/SysThreads.h b/pcsx2/System/SysThreads.h index e4d6879ae1..4e27871e3d 100644 --- a/pcsx2/System/SysThreads.h +++ b/pcsx2/System/SysThreads.h @@ -16,60 +16,48 @@ #pragma once #include "Utilities/PersistentThread.h" +#include "Utilities/RwMutex.h" #include "x86emitter/tools.h" +#include "CDVD/CDVDaccess.h" + using namespace Threading; -// -------------------------------------------------------------------------------------- -// ISysThread -// -------------------------------------------------------------------------------------- -class ISysThread : public virtual IThread -{ -public: - ISysThread() {} - virtual ~ISysThread() throw() {} - - virtual bool Suspend( bool isBlocking = true ) { return false; } - virtual bool Pause() { return false; } - virtual void Resume() {} -}; - // -------------------------------------------------------------------------------------- // SysThreadBase // -------------------------------------------------------------------------------------- -class SysThreadBase : public PersistentThread, public virtual ISysThread +class SysThreadBase : public PersistentThread { typedef PersistentThread _parent; public: - // 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. + // Important: The order of these enumerations matters! Optimized tests are used for both + // Closed and Paused states. enum ExecutionMode { // Thread has not been created yet. Typically this is the same as IsRunning() // returning FALSE. ExecMode_NoThreadYet, - // 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 safely paused, with plugins in an "open" state, and waiting for a + // resume/open signal. + ExecMode_Paused, + // Thread is active and running, with pluigns in an "open" state. ExecMode_Opened, + // Close signal has been sent to the thread, but the thread's response is still + // pending (thread is busy/running). + ExecMode_Closing, + // 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, }; protected: @@ -77,20 +65,19 @@ protected: // This lock is used to avoid simultaneous requests to Suspend/Resume/Pause from // contending threads. - MutexLockRecursive m_ExecModeMutex; + MutexRecursive m_ExecModeMutex; // Used to wake up the thread from sleeping when it's in a suspended state. - Semaphore m_ResumeEvent; + Semaphore m_sem_Resume; + + // Used to synchronize inline changes from paused to suspended status. + Semaphore m_sem_ChangingExecMode; // Locked whenever the thread is not in a suspended state (either closed or paused). // Issue a Wait against this mutex for performing actions that require the thread // to be suspended. - Mutex m_RunningLock; - - // Protects the thread from re-entrant resume requests while dependent resources are - // being constructed. - NonblockingMutex m_ResumeProtection; - + Mutex m_RunningLock; + public: explicit SysThreadBase(); virtual ~SysThreadBase() throw(); @@ -102,32 +89,27 @@ public: // first. bool IsOpen() const { - return m_ExecMode > ExecMode_Closed; + return IsRunning() && (m_ExecMode > ExecMode_Closed); } + bool IsClosed() const { return !IsOpen(); } + + bool IsPaused() const { return !IsRunning() || (m_ExecMode <= ExecMode_Paused); } + bool HasPendingStateChangeRequest() const { ExecutionMode mode = m_ExecMode; return (mode == ExecMode_Closing) || (mode == ExecMode_Pausing); } - bool IsClosed() const { return !IsOpen(); } - ExecutionMode GetExecutionMode() const { return m_ExecMode; } Mutex& ExecutionModeMutex() { return m_ExecModeMutex; } - virtual bool Suspend( bool isBlocking = true ); + virtual void Suspend( bool isBlocking = true ); virtual void Resume(); - virtual bool Pause(); - - virtual bool AcquireResumeLock() { return m_ResumeProtection.TryAcquire(); } - virtual void ReleaseResumeLock() { m_ResumeProtection.Release(); } - - virtual wxTimeSpan GetDeadlockTimeout() const; - virtual void ThrowDeadlockException(); - + virtual void Pause(); + protected: - virtual void DoThreadDeadlocked(); virtual void OnStart(); // This function is called by Resume immediately prior to releasing the suspension of @@ -146,14 +128,14 @@ protected: // 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. + // the thread enters a waiting state on the m_sem_Resume 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 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. + // enters a waiting state on the m_sem_Resume semaphore. virtual void OnPauseInThread()=0; // Extending classes should implement this, but should not call it. The parent class @@ -165,6 +147,7 @@ protected: virtual void OnResumeInThread( bool isSuspended )=0; }; + // -------------------------------------------------------------------------------------- // SysCoreThread class // -------------------------------------------------------------------------------------- @@ -173,6 +156,8 @@ class SysCoreThread : public SysThreadBase typedef SysThreadBase _parent; protected: + s32 m_CloseTemporary; + bool m_resetRecompilers; bool m_resetProfilers; bool m_resetVsyncTimers; @@ -183,7 +168,7 @@ protected: bool m_CoreCancelDamnit; wxString m_elf_override; - + SSE_MXCSR m_mxcsr_saved; public: @@ -199,7 +184,7 @@ public: virtual void RecoverState(); virtual void Cancel( bool isBlocking=true ); virtual bool Cancel( const wxTimeSpan& timeout ); - + bool HasValidState() { return m_hasValidState; @@ -209,55 +194,52 @@ public: virtual void StateCheckInThread(); virtual void VsyncInThread(); virtual void PostVsyncToUI()=0; - + virtual const wxString& GetElfOverride() const { return m_elf_override; } virtual void SetElfOverride( const wxString& elf ); - virtual void ChangeCdvdSource( CDVD_SourceType type ); - + protected: void _reset_stuff_as_needed(); virtual void CpuInitializeMess(); virtual void Start(); + virtual void OnStart(); virtual void OnSuspendInThread(); virtual void OnPauseInThread() {} virtual void OnResumeInThread( bool IsSuspended ); virtual void OnCleanupInThread(); virtual void ExecuteTaskInThread(); virtual void DoCpuReset(); - + virtual void DoCpuExecute(); + void _StateCheckThrows(); }; -// -------------------------------------------------------------------------------------- -// ScopedCoreThreadSuspend -// -------------------------------------------------------------------------------------- -// This class behaves a bit differently from other scoped classes due to the "standard" -// assumption that we actually do *not* want to resume CoreThread operations when an -// exception occurs. Because of this, the destructor of this class does *not* unroll the -// suspend operation. Instead you must manually instruct the class to resume using a call -// to the provisioned Resume() method. -// -// If the class leaves scope without having been resumed, a log is written to the console. -// This can be useful for troubleshooting, and also allows the log a second line of info -// indicating the status of CoreThread execution at the time of the exception. -// -struct ScopedCoreThreadSuspend -{ - bool m_ResumeWhenDone; - ScopedCoreThreadSuspend(); - virtual ~ScopedCoreThreadSuspend() throw(); - virtual void Resume(); +struct SysStateUnlockedParams +{ + SysStateUnlockedParams() {} }; -struct ScopedCoreThreadPause +// -------------------------------------------------------------------------------------- +// IEventListener_SaveStateThread +// -------------------------------------------------------------------------------------- +class IEventListener_SysState : public IEventDispatcher { - bool m_ResumeWhenDone; +public: + typedef SysStateUnlockedParams EvtParams; - ScopedCoreThreadPause(); - virtual ~ScopedCoreThreadPause() throw(); - virtual void Resume(); +public: + IEventListener_SysState() {} + virtual ~IEventListener_SysState() throw() {} + + virtual void DispatchEvent( const SysStateUnlockedParams& status ) + { + SysStateAction_OnUnlocked(); + } + +protected: + virtual void SysStateAction_OnUnlocked(); }; // GetCoreThread() is a required external implementation. This function is *NOT* @@ -266,5 +248,3 @@ struct ScopedCoreThreadPause // them to extend the class and override virtual methods). // extern SysCoreThread& GetCoreThread(); - -extern int sys_resume_lock; diff --git a/pcsx2/ZipTools/FolderDesc.txt b/pcsx2/ZipTools/FolderDesc.txt new file mode 100644 index 0000000000..37469bfd3f --- /dev/null +++ b/pcsx2/ZipTools/FolderDesc.txt @@ -0,0 +1,10 @@ + +------------------- + ZipTools (folder) +------------------- + +Contains C++ interfaces for zipping to/from various formats +(primarily gzip and 7zip). + +Notice: This folder is intended to be moved to a utility folder +outside the main PCSX2 folders at a later date. \ No newline at end of file diff --git a/pcsx2/ZipTools/ThreadedZipTools.h b/pcsx2/ZipTools/ThreadedZipTools.h new file mode 100644 index 0000000000..e09e92e413 --- /dev/null +++ b/pcsx2/ZipTools/ThreadedZipTools.h @@ -0,0 +1,103 @@ +/* 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 "Utilities/PersistentThread.h" +//#include "zlib/zlib.h" + +using namespace Threading; + +class IStreamWriter +{ +public: + virtual ~IStreamWriter() throw() {} + + virtual void Write( const void* data, size_t size )=0; + virtual wxString GetStreamName() const=0; + + template< typename T > + void Write( const T& data ) + { + Write( &data, sizeof(data) ); + } +}; + +class IStreamReader +{ +public: + virtual ~IStreamReader() throw() {} + + virtual void Read( void* dest, size_t size )=0; + virtual wxString GetStreamName() const=0; + + template< typename T > + void Read( T& dest ) + { + Read( &dest, sizeof(dest) ); + } +}; + +typedef void FnType_WriteCompressedHeader( IStreamWriter& thr ); +typedef void FnType_ReadCompressedHeader( IStreamReader& thr ); + +// -------------------------------------------------------------------------------------- +// BaseCompressThread +// -------------------------------------------------------------------------------------- +class BaseCompressThread + : public PersistentThread + , public IStreamWriter +{ + typedef PersistentThread _parent; + +protected: + FnType_WriteCompressedHeader* m_WriteHeaderInThread; + + const wxString m_filename; + ScopedPtr< SafeArray< u8 > > m_src_buffer; + + BaseCompressThread( const wxString& file, SafeArray* srcdata, FnType_WriteCompressedHeader* writeHeader=NULL) + : m_filename( file ) + , m_src_buffer( srcdata ) + { + m_WriteHeaderInThread = writeHeader; + } + + virtual ~BaseCompressThread() throw() {} + +public: + wxString GetStreamName() const { return m_filename; } +}; + +// -------------------------------------------------------------------------------------- +// CompressThread_gzip +// -------------------------------------------------------------------------------------- +class CompressThread_gzip : public BaseCompressThread +{ + typedef BaseCompressThread _parent; + +protected: + gzFile m_gzfp; + +public: + CompressThread_gzip( const wxString& file, SafeArray* srcdata, FnType_WriteCompressedHeader* writeHeader=NULL ); + CompressThread_gzip( const wxString& file, ScopedPtr>& srcdata, FnType_WriteCompressedHeader* writeHeader=NULL ); + virtual ~CompressThread_gzip() throw(); + +protected: + void Write( const void* data, size_t size ); + void ExecuteTaskInThread(); + void OnCleanupInThread(); +}; diff --git a/pcsx2/ZipTools/thread_gzip.cpp b/pcsx2/ZipTools/thread_gzip.cpp new file mode 100644 index 0000000000..64932a536c --- /dev/null +++ b/pcsx2/ZipTools/thread_gzip.cpp @@ -0,0 +1,79 @@ +/* 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 te 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 "App.h" +#include "SaveState.h" +#include "ThreadedZipTools.h" + + +CompressThread_gzip::CompressThread_gzip( const wxString& file, SafeArray* srcdata, FnType_WriteCompressedHeader* writeheader ) + : BaseCompressThread( file, srcdata, writeheader ) +{ + m_gzfp = NULL; +} + +CompressThread_gzip::CompressThread_gzip( const wxString& file, ScopedPtr>& srcdata, FnType_WriteCompressedHeader* writeheader ) + : BaseCompressThread( file, srcdata.DetachPtr(), writeheader ) +{ + m_gzfp = NULL; +} + +CompressThread_gzip::~CompressThread_gzip() throw() +{ + if( m_gzfp ) gzclose( m_gzfp ); +} + +void CompressThread_gzip::Write( const void* data, size_t size ) +{ + if( gzwrite( m_gzfp, data, size ) == 0 ) + throw Exception::BadStream( m_filename, "Write to zip file failed." ); +} + +void CompressThread_gzip::ExecuteTaskInThread() +{ + if( !m_src_buffer ) return; + + Yield( 3 ); + + if( !(m_gzfp = gzopen(m_filename.ToUTF8(), "wb")) ) + throw Exception::CannotCreateStream( m_filename ); + + gzsetparams(m_gzfp, Z_BEST_SPEED, Z_FILTERED); // Best speed at good compression + gzbuffer(m_gzfp, 0x100000); // 1mb buffer size for less file fragments (Windows/NTFS) + + if( m_WriteHeaderInThread ) + m_WriteHeaderInThread( *this ); + + static const int BlockSize = 0x64000; + int curidx = 0; + + do { + int thisBlockSize = std::min( BlockSize, m_src_buffer->GetSizeInBytes() - curidx ); + if( gzwrite( m_gzfp, m_src_buffer->GetPtr(curidx), thisBlockSize ) < thisBlockSize ) + throw Exception::BadStream( m_filename ); + curidx += thisBlockSize; + Yield( 3 ); + } while( curidx < m_src_buffer->GetSizeInBytes() ); + + Console.WriteLn( "(gzipThread) Data saved to disk without error." ); +} + +void CompressThread_gzip::OnCleanupInThread() +{ + wxGetApp().DeleteThread( this ); +} + diff --git a/pcsx2/ZipTools/thread_lzma.cpp b/pcsx2/ZipTools/thread_lzma.cpp new file mode 100644 index 0000000000..5a8c41b1d0 --- /dev/null +++ b/pcsx2/ZipTools/thread_lzma.cpp @@ -0,0 +1,17 @@ +/* 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 te 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" + diff --git a/pcsx2/gui/App.h b/pcsx2/gui/App.h index 3aa92f091d..9bf0edff8c 100644 --- a/pcsx2/gui/App.h +++ b/pcsx2/gui/App.h @@ -21,7 +21,10 @@ #include #include +#include "pxEventThread.h" + #include "AppCommon.h" +#include "AppCoreThread.h" #include "RecentIsoList.h" #include "System.h" @@ -32,22 +35,12 @@ class Pcsx2App; typedef void FnType_OnThreadComplete(const wxCommandEvent& evt); -typedef void (Pcsx2App::*FnPtr_AppMethod)(); +typedef void (Pcsx2App::*FnPtr_Pcsx2App)(); BEGIN_DECLARE_EVENT_TYPES() - /*DECLARE_EVENT_TYPE( pxEVT_ReloadPlugins, -1 ) - DECLARE_EVENT_TYPE( pxEVT_OpenGsPanel, -1 )*/ - - DECLARE_EVENT_TYPE( pxEvt_FreezeThreadFinished, -1 ) - DECLARE_EVENT_TYPE( pxEvt_CoreThreadStatus, -1 ) DECLARE_EVENT_TYPE( pxEvt_LoadPluginsComplete, -1 ) - DECLARE_EVENT_TYPE( pxEvt_PluginStatus, -1 ) - DECLARE_EVENT_TYPE( pxEvt_SysExecute, -1 ) - DECLARE_EVENT_TYPE( pxEvt_InvokeMethod, -1 ) DECLARE_EVENT_TYPE( pxEvt_LogicalVsync, -1 ) - - DECLARE_EVENT_TYPE( pxEvt_OpenModalDialog, -1 ) - //DECLARE_EVENT_TYPE( pxEvt_StuckThread, -1 ) + DECLARE_EVENT_TYPE( pxEvt_ThreadTaskTimeout_SysExec, -1 ) END_DECLARE_EVENT_TYPES() // This is used when the GS plugin is handling its own window. Messages from the PAD @@ -91,15 +84,14 @@ enum MenuIdentifiers MenuId_Boot_Iso, // Opens submenu with Iso browser, and recent isos. MenuId_IsoSelector, // Contains a submenu of selectable "favorite" isos MenuId_IsoBrowse, // Open dialog, runs selected iso. - MenuId_Boot_CDVD, // opens a submenu filled by CDVD plugin (usually list of drives) + MenuId_Boot_CDVD, + MenuId_Boot_CDVD2, MenuId_Boot_ELF, MenuId_Boot_Recent, // Menu populated with recent source bootings - MenuId_SkipBiosToggle, // enables the Bios Skip speedhack MenuId_Sys_SuspendResume, // suspends/resumes active emulation, retains plugin states - MenuId_Sys_Close, // Closes the emulator (states are preserved) - MenuId_Sys_Reset, // Issues a complete VM reset (wipes preserved states) + MenuId_Sys_Restart, // Issues a complete VM reset (wipes preserved states) MenuId_Sys_Shutdown, // Closes virtual machine, shuts down plugins, wipes states. MenuId_Sys_LoadStates, // Opens load states submenu MenuId_Sys_SaveStates, // Opens save states submenu @@ -284,7 +276,7 @@ struct AppImageIds { Paths = Plugins = Speedhacks = Gamefixes = - Video = Cpu = + Video = Cpu = MemoryCard = -1; } } Config; @@ -384,7 +376,7 @@ protected: public: void AddListener( IEventListener_Plugins& listener ) { - m_evtsrc_CorePluginStatus.Add( listener ); + m_evtsrc_CorePluginStatus.Add( listener ); } void AddListener( IEventListener_CoreThread& listener ) @@ -399,7 +391,7 @@ public: void RemoveListener( IEventListener_Plugins& listener ) { - m_evtsrc_CorePluginStatus.Remove( listener ); + m_evtsrc_CorePluginStatus.Remove( listener ); } void RemoveListener( IEventListener_CoreThread& listener ) @@ -414,7 +406,7 @@ public: void AddListener( IEventListener_Plugins* listener ) { - m_evtsrc_CorePluginStatus.Add( listener ); + m_evtsrc_CorePluginStatus.Add( listener ); } void AddListener( IEventListener_CoreThread* listener ) @@ -429,7 +421,7 @@ public: void RemoveListener( IEventListener_Plugins* listener ) { - m_evtsrc_CorePluginStatus.Remove( listener ); + m_evtsrc_CorePluginStatus.Remove( listener ); } void RemoveListener( IEventListener_CoreThread* listener ) @@ -441,27 +433,17 @@ public: { m_evtsrc_AppStatus.Remove( listener ); } - - void DispatchEvent( PluginEventType evt ) - { - if( !AffinityAssert_AllowFromMain() ) return; - m_evtsrc_CorePluginStatus.Dispatch( evt ); - } - - void DispatchEvent( AppEventType evt ) - { - if( !AffinityAssert_AllowFromMain() ) return; - m_evtsrc_AppStatus.Dispatch( AppEventInfo( evt ) ); - } - - void DispatchEvent( IniInterface& ini ) - { - if( !AffinityAssert_AllowFromMain() ) return; - m_evtsrc_AppStatus.Dispatch( AppSettingsEventInfo( ini ) ); - } + + void DispatchEvent( PluginEventType evt ); + void DispatchEvent( AppEventType evt ); + void DispatchEvent( CoreThreadStatus evt ); + void DispatchEvent( IniInterface& ini ); // ---------------------------------------------------------------------------- - +protected: + int m_PendingSaves; + bool m_ScheduledTermination; + public: FramerateManager FpsManager; CommandDictionary GlobalCommands; @@ -474,9 +456,13 @@ protected: ScopedPtr m_RecentIsoList; ScopedPtr m_Resources; + // Executor Thread for complex VM/System tasks. This thread is used to execute such tasks + // in parallel to the main message pump, to allow the main pump to run without fear of + // blocked threads stalling the GUI. + public: + ExecutorThread SysExecutorThread; ScopedPtr m_CoreAllocs; - ScopedPtr m_CorePlugins; protected: wxWindowID m_id_MainFrame; @@ -489,17 +475,13 @@ public: Pcsx2App(); virtual ~Pcsx2App(); - void PostPluginStatus( PluginEventType pevt ); void PostMenuAction( MenuIdentifiers menu_id ) const; - int IssueDialogAsModal( const wxString& dlgName ); - void PostMethod( FnPtr_AppMethod method ); - void PostIdleMethod( FnPtr_AppMethod method ); - int DoStuckThread( PersistentThread& stuck_thread ); + void PostAppMethod( FnPtr_Pcsx2App method ); + void PostIdleAppMethod( FnPtr_Pcsx2App method ); void SysExecute(); void SysExecute( CDVD_SourceType cdvdsrc, const wxString& elf_override=wxEmptyString ); - void SysReset(); - void ReloadPlugins(); + void SysShutdown(); void LogicalVsync(); GSFrame& GetGsFrame() const; @@ -528,6 +510,8 @@ public: void WipeUserModeSettings(); void ReadUserModeSettings(); + void StartPendingSave(); + void ClearPendingSave(); // -------------------------------------------------------------------------- // App-wide Resources @@ -576,8 +560,8 @@ public: void OnProgramLogClosed( wxWindowID id ); protected: - bool InvokeMethodOnMainThread( FnPtr_AppMethod method ); - bool PostMethodToMainThread( FnPtr_AppMethod method ); + bool InvokeOnMainThread( FnPtr_Pcsx2App method ); + bool PostAppMethodMyself( FnPtr_Pcsx2App method ); void AllocateCoreStuffs(); void InitDefaultGlobalAccelerators(); @@ -586,23 +570,13 @@ protected: void CleanupOnExit(); void OpenWizardConsole(); void PadKeyDispatch( const keyEvent& ev ); - void CancelLoadingPlugins(); - + void HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& event) const; void HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& event); - void OnSysExecute( wxCommandEvent& evt ); - void OnLoadPluginsComplete( wxCommandEvent& evt ); - void OnPluginStatus( wxCommandEvent& evt ); - void OnCoreThreadStatus( wxCommandEvent& evt ); - void OnFreezeThreadFinished( wxCommandEvent& evt ); - - void OnOpenModalDialog( wxCommandEvent& evt ); - void OnOpenDialog_StuckThread( wxCommandEvent& evt ); - void OnEmuKeyDown( wxKeyEvent& evt ); - void OnInvokeMethod( pxInvokeAppMethodEvent& evt ); + void OnSysExecutorTaskTimeout( wxTimerEvent& evt ); // ---------------------------------------------------------------------------- // Override wx default exception handling behavior @@ -620,40 +594,6 @@ protected: }; -// -------------------------------------------------------------------------------------- -// AppCoreThread class -// -------------------------------------------------------------------------------------- -class AppCoreThread : public SysCoreThread -{ - typedef SysCoreThread _parent; - -public: - AppCoreThread(); - virtual ~AppCoreThread() throw(); - - virtual bool Suspend( bool isBlocking=true ); - virtual void Resume(); - virtual void Reset(); - virtual void Cancel( bool isBlocking=true ); - virtual void StateCheckInThread(); - virtual void ApplySettings( const Pcsx2Config& src ); - virtual void ChangeCdvdSource( CDVD_SourceType type ); - -protected: - virtual void OnResumeReady(); - virtual void OnResumeInThread( bool IsSuspended ); - virtual void OnSuspendInThread(); - virtual void OnCleanupInThread(); - //virtual void VsyncInThread(); - virtual void PostVsyncToUI(); - virtual void ExecuteTaskInThread(); - virtual void DoCpuReset(); - - virtual void DoThreadDeadlocked(); - - virtual void CpuInitializeMess(); -}; - DECLARE_APP(Pcsx2App) // -------------------------------------------------------------------------------------- @@ -686,9 +626,6 @@ DECLARE_APP(Pcsx2App) #define sMainFrame \ if( MainEmuFrame* __frame_ = GetMainFramePtr() ) (*__frame_) -#define sGSFrame \ - if( GSFrame* __gsframe_ = wxGetApp().GetGsFramePtr() ) (*__gsframe_) - // Use this within the scope of a wxWindow (wxDialog or wxFrame). If the window has a valid menu // bar, the command will run, otherwise it will be silently ignored. :) #define sMenuBar \ @@ -706,30 +643,6 @@ void AppOpenDialog( wxWindow* parent ) (new DialogType( parent ))->Show(); } -// -------------------------------------------------------------------------------------- -// SaveSinglePluginHelper -// -------------------------------------------------------------------------------------- -// A scoped convenience class for closing a single plugin and saving its state to memory. -// Emulation is suspended as needed, and is restored when the object leaves scope. Within -// the scope of the object, code is free to call plugin re-configurations or even unload -// a plugin entirely and re-load a different plugin in its place. -// -class SaveSinglePluginHelper -{ -protected: - SafeArray m_plugstore; - const SafeArray* m_whereitsat; - - bool m_resume; - bool m_validstate; - PluginsEnum_t m_pid; - -public: - SaveSinglePluginHelper( PluginsEnum_t pid ); - virtual ~SaveSinglePluginHelper() throw(); -}; - - extern pxDoAssertFnType AppDoAssert; // -------------------------------------------------------------------------------------- @@ -737,9 +650,10 @@ extern pxDoAssertFnType AppDoAssert; // -------------------------------------------------------------------------------------- extern int EnumeratePluginsInFolder( const wxDirName& searchPath, wxArrayString* dest ); -extern void LoadPluginsPassive( FnType_OnThreadComplete* onComplete ); +extern void LoadPluginsPassive(); extern void LoadPluginsImmediate(); extern void UnloadPlugins(); +extern void ShutdownPlugins(); extern void AppLoadSettings(); extern void AppSaveSettings(); @@ -755,5 +669,22 @@ extern MainEmuFrame* GetMainFramePtr(); extern __aligned16 AppCoreThread CoreThread; extern __aligned16 SysMtgsThread mtgsThread; +extern __aligned16 AppPluginManager CorePlugins; +extern void UI_UpdateSysControls(); + +extern void UI_DisableSysActions(); +extern void UI_EnableSysActions(); + +extern void UI_DisableSysReset(); +extern void UI_DisableSysShutdown(); + + +#define AffinityAssert_AllowFrom_SysExecutor() \ + pxAssertMsg( wxGetApp().SysExecutorThread.IsSelf(), "Thread affinity violation: Call allowed from SysExecutor thread only." ) + +#define AffinityAssert_DisallowFrom_SysExecutor() \ + pxAssertMsg( !wxGetApp().SysExecutorThread.IsSelf(), "Thread affinity violation: Call is *not* allowed from SysExecutor thread." ) + +extern ExecutorThread& GetSysExecutorThread(); diff --git a/pcsx2/gui/AppConfig.cpp b/pcsx2/gui/AppConfig.cpp index 1fc7399fc0..6a8c890d89 100644 --- a/pcsx2/gui/AppConfig.cpp +++ b/pcsx2/gui/AppConfig.cpp @@ -93,9 +93,9 @@ namespace PathDefs // Fetches the path location for user-consumable documents -- stuff users are likely to want to // share with other programs: screenshots, memory cards, and savestates. - wxDirName GetDocuments() + wxDirName GetDocuments( DocsModeType mode ) { - switch( DocsFolderMode ) + switch( mode ) { case DocsFolder_User: return (wxDirName)Path::Combine( wxStandardPaths::Get().GetDocumentsDir(), wxGetApp().GetAppName() ); case DocsFolder_CWD: return (wxDirName)wxGetCwd(); @@ -106,6 +106,11 @@ namespace PathDefs return wxDirName(); } + + wxDirName GetDocuments() + { + return GetDocuments( DocsFolderMode ); + } wxDirName GetSnapshots() { diff --git a/pcsx2/gui/AppConfig.h b/pcsx2/gui/AppConfig.h index 4dde838970..6b90fc51f2 100644 --- a/pcsx2/gui/AppConfig.h +++ b/pcsx2/gui/AppConfig.h @@ -23,15 +23,25 @@ enum DocsModeType { // uses /home/user or /cwd for the program data DocsFolder_User, - + // uses the current working directory for program data DocsFolder_CWD, - + // uses a custom location for program data DocsFolder_Custom, }; -extern DocsModeType DocsFolderMode; // +namespace PathDefs +{ + // complete pathnames are returned by these functions + // For 99% of all code, you should use these. + + extern wxDirName GetDocuments(); + extern wxDirName GetDocuments( DocsModeType mode ); + extern wxDirName GetThemes(); +} + +extern DocsModeType DocsFolderMode; // extern wxDirName SettingsFolder; // dictates where the settings folder comes from, *if* UseDefaultSettingsFolder is FALSE. extern wxDirName CustomDocumentsFolder; // allows the specification of a custom home folder for PCSX2 documents files. extern bool UseDefaultSettingsFolder; // when TRUE, pcsx2 derives the settings folder from the UseAdminMode @@ -48,7 +58,7 @@ enum AspectRatioType }; // ===================================================================================================== -// Pcsx2 Application Configuration. +// Pcsx2 Application Configuration. // ===================================================================================================== class AppConfig @@ -161,7 +171,7 @@ public: Fixed100 SlomoScalar; FramerateOptions(); - + void LoadSave( IniInterface& conf ); void SanityCheck(); }; @@ -212,7 +222,7 @@ public: FilenameOptions BaseFilenames; GSWindowOptions GSWindow; FramerateOptions Framerate; - + // PCSX2-core emulation options, which are passed to the emu core prior to initiating // an emulation session. Note these are the options saved into the GUI ini file and // which are shown as options in the gui preferences, but *not* necessarily the options @@ -227,7 +237,7 @@ public: wxString FullpathTo( PluginsEnum_t pluginId ) const; bool FullpathMatchTest( PluginsEnum_t pluginId, const wxString& cmpto ) const; - + void LoadSaveUserMode( IniInterface& ini, const wxString& cwdhash ); void LoadSave( IniInterface& ini ); diff --git a/pcsx2/gui/AppCorePlugins.cpp b/pcsx2/gui/AppCorePlugins.cpp new file mode 100644 index 0000000000..a6d2be410a --- /dev/null +++ b/pcsx2/gui/AppCorePlugins.cpp @@ -0,0 +1,419 @@ +/* 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 "App.h" +#include "AppSaveStates.h" +#include "GSFrame.h" + +#include +#include + +#include "Plugins.h" +#include "GS.h" +#include "HostGui.h" +#include "AppConfig.h" + +using namespace Threading; + +// The GS plugin needs to be opened to save/load the state during plugin configuration, but +// the window shouldn't. This blocks it. :) +static bool s_DisableGsWindow = false; + +__aligned16 AppPluginManager CorePlugins; + +PluginManager& GetCorePlugins() +{ + return CorePlugins; +} + + +// -------------------------------------------------------------------------------------- +// CorePluginsEvent +// -------------------------------------------------------------------------------------- +class CorePluginsEvent : public pxInvokeActionEvent +{ + typedef pxInvokeActionEvent _parent; + +protected: + PluginEventType m_evt; + +public: + virtual ~CorePluginsEvent() throw() {} + CorePluginsEvent* Clone() const { return new CorePluginsEvent( *this ); } + + explicit CorePluginsEvent( PluginEventType evt, SynchronousActionState* sema=NULL ) + : pxInvokeActionEvent( sema ) + { + m_evt = evt; + } + + explicit CorePluginsEvent( PluginEventType evt, SynchronousActionState& sema ) + : pxInvokeActionEvent( sema ) + { + m_evt = evt; + } + + CorePluginsEvent( const CorePluginsEvent& src ) + : pxInvokeActionEvent( src ) + { + m_evt = src.m_evt; + } + + void SetEventType( PluginEventType evt ) { m_evt = evt; } + PluginEventType GetEventType() { return m_evt; } + +protected: + void _DoInvoke() + { + sApp.DispatchEvent( m_evt ); + } +}; + +static void PostPluginStatus( PluginEventType pevt ) +{ + sApp.PostAction( CorePluginsEvent( pevt ) ); +} + +static 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 ); +} + +// -------------------------------------------------------------------------------------- +// AppPluginManager +// -------------------------------------------------------------------------------------- +AppPluginManager::AppPluginManager() +{ +} + +AppPluginManager::~AppPluginManager() throw() +{ +} + +void AppPluginManager::Load( const wxString (&folders)[PluginId_Count] ) +{ + if( !pxAssert(!AreLoaded()) ) return; + + SetSettingsFolder( GetSettingsFolder().ToString() ); + _parent::Load( folders ); + PostPluginStatus( CorePlugins_Loaded ); +} + +void AppPluginManager::Unload() +{ + _parent::Unload(); + PostPluginStatus( CorePlugins_Unloaded ); +} + +void AppPluginManager::Init() +{ + SetSettingsFolder( GetSettingsFolder().ToString() ); + _parent::Init(); + PostPluginStatus( CorePlugins_Init ); +} + +void AppPluginManager::Shutdown() +{ + _parent::Shutdown(); + PostPluginStatus( CorePlugins_Shutdown ); +} + +typedef void (AppPluginManager::*FnPtr_AppPluginManager)(); + +class SysExecEvent_AppPluginManager : public SysExecEvent +{ +protected: + FnPtr_AppPluginManager m_method; + +public: + virtual ~SysExecEvent_AppPluginManager() throw() {} + SysExecEvent_AppPluginManager* Clone() const { return new SysExecEvent_AppPluginManager( *this ); } + + SysExecEvent_AppPluginManager( FnPtr_AppPluginManager method ) + { + m_method = method; + } + +protected: + void _DoInvoke() + { + if( m_method ) (CorePlugins.*m_method)(); + } +}; + +void AppPluginManager::Close() +{ + AffinityAssert_AllowFrom_CoreThread(); + + /*if( !GetSysExecutorThread().IsSelf() ) + { + GetSysExecutorThread().ProcessEvent( new SysExecEvent_AppPluginManager( &AppPluginManager::Close ) ); + return; + }*/ + + if( !NeedsClose() ) return; + + PostPluginStatus( CorePlugins_Closing ); + _parent::Close(); + PostPluginStatus( CorePlugins_Closed ); +} + +void AppPluginManager::Open() +{ + AffinityAssert_AllowFrom_CoreThread(); + + /*if( !GetSysExecutorThread().IsSelf() ) + { + GetSysExecutorThread().ProcessEvent( new SysExecEvent_AppPluginManager( &AppPluginManager::Open ) ); + return; + }*/ + + SetSettingsFolder( GetSettingsFolder().ToString() ); + + if( !NeedsOpen() ) return; + + PostPluginStatus( CorePlugins_Opening ); + _parent::Open(); + PostPluginStatus( CorePlugins_Opened ); +} + +// Yay, this plugin is guaranteed to always be opened first and closed last. +bool AppPluginManager::OpenPlugin_GS() +{ + if( GSopen2 && !s_DisableGsWindow ) + { + sApp.OpenGsPanel(); + } + + bool retval = _parent::OpenPlugin_GS(); + + if( g_LimiterMode == Limit_Turbo ) + GSsetVsync( false ); + + return retval; +} + +static int _guard = 0; + +// Yay, this plugin is guaranteed to always be opened first and closed last. +void AppPluginManager::ClosePlugin_GS() +{ + /*if( GSopen2 == NULL || CloseViewportWithPlugins ) + { + // All other plugins must be closed before the GS, because they all rely on + // the GS window handle being valid. The recursion guard will protect this + // function from being called a million times. ;) + + RecursionGuard mess( _guard ); + if( !mess.IsReentrant() ) Close(); + }*/ + + _parent::ClosePlugin_GS(); + if( GetMTGS().IsSelf() && GSopen2 && CloseViewportWithPlugins ) sApp.CloseGsPanel(); +} + + +// -------------------------------------------------------------------------------------- +// LoadCorePluginsEvent +// -------------------------------------------------------------------------------------- +class LoadCorePluginsEvent : public SysExecEvent +{ +protected: + wxString m_folders[PluginId_Count]; + +public: + LoadCorePluginsEvent() + { + ConvertPluginFilenames( m_folders ); + } + + wxString GetEventName() const + { + return L"LoadCorePlugins"; + } + + wxString GetEventMessage() const + { + return _("Loading PS2 system plugins..."); + } + +protected: + void _DoInvoke() + { + CorePlugins.Load( m_folders ); + } +}; + +// -------------------------------------------------------------------------------------- +// Public API / Interface +// -------------------------------------------------------------------------------------- + +int EnumeratePluginsInFolder( const wxDirName& searchpath, wxArrayString* dest ) +{ + ScopedPtr placebo; + wxArrayString* realdest = dest; + if( realdest == NULL ) + placebo = realdest = new wxArrayString(); // placebo is our /dev/null -- gets deleted when done + +#ifdef __WXMSW__ + // Windows pretty well has a strict "must end in .dll" rule. + wxString pattern( L"*%s" ); +#else + // Other platforms seem to like to version their libs after the .so extension: + // blah.so.3.1.fail? + wxString pattern( L"*%s*" ); +#endif + + return searchpath.Exists() ? + wxDir::GetAllFiles( searchpath.ToString(), realdest, wxsFormat( pattern, wxDynamicLibrary::GetDllExt()), wxDIR_FILES ) : 0; +} + +// 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, onComplete is invoked, and the function returns with no +// other actions performed. +void LoadPluginsPassive() +{ + AffinityAssert_AllowFrom_MainUI(); + + // Plugins already loaded? + if( !CorePlugins.AreLoaded() ) + { + wxGetApp().SysExecutorThread.PostEvent( new LoadCorePluginsEvent() ); + } +} + +static void _LoadPluginsImmediate() +{ + if( CorePlugins.AreLoaded() ) return; + + wxString passins[PluginId_Count]; + ConvertPluginFilenames( passins ); + CorePlugins.Load( passins ); +} + +void LoadPluginsImmediate() +{ + AffinityAssert_AllowFrom_SysExecutor(); + _LoadPluginsImmediate(); +} + +// 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 ScopedCoreThreadClose::LoadPlugins() +{ + DbgCon.WriteLn("(ScopedCoreThreadClose) Loading plugins!"); + _LoadPluginsImmediate(); +} + + +class SysExecEvent_UnloadPlugins : public SysExecEvent +{ +public: + virtual ~SysExecEvent_UnloadPlugins() throw() {} + SysExecEvent_UnloadPlugins* Clone() const { return new SysExecEvent_UnloadPlugins(*this); } + + virtual bool AllowCancelOnExit() const { return false; } + virtual bool IsCriticalEvent() const { return true; } + + void _DoInvoke() + { + CoreThread.Cancel(); + CorePlugins.Unload(); + } +}; + +class SysExecEvent_ShutdownPlugins : public SysExecEvent +{ +public: + virtual ~SysExecEvent_ShutdownPlugins() throw() {} + SysExecEvent_ShutdownPlugins* Clone() const { return new SysExecEvent_ShutdownPlugins(*this); } + + virtual bool AllowCancelOnExit() const { return false; } + virtual bool IsCriticalEvent() const { return true; } + + void _DoInvoke() + { + CoreThread.Cancel(); + CorePlugins.Shutdown(); + } +}; + +void UnloadPlugins() +{ + GetSysExecutorThread().PostEvent( new SysExecEvent_UnloadPlugins() ); +} + +void ShutdownPlugins() +{ + GetSysExecutorThread().PostEvent( new SysExecEvent_ShutdownPlugins() ); +} + +// -------------------------------------------------------------------------------------- +// SaveSinglePluginHelper (Implementations) +// -------------------------------------------------------------------------------------- + +SaveSinglePluginHelper::SaveSinglePluginHelper( PluginsEnum_t pid ) + : m_plugstore( L"PluginConf Savestate" ) +{ + s_DisableGsWindow = true; + + m_pid = pid; + m_validstate = SysHasValidState(); + + _LoadPluginsImmediate(); + if( !CorePlugins.AreLoaded() ) return; + + if( !m_validstate ) return; + Console.WriteLn( Color_Green, L"Suspending single plugin: " + tbl_PluginInfo[m_pid].GetShortname() ); + + memSavingState save( m_plugstore ); + GetCorePlugins().Freeze( m_pid, save ); + GetCorePlugins().Close( pid ); +} + +SaveSinglePluginHelper::~SaveSinglePluginHelper() throw() +{ + try + { + if( m_validstate ) + { + Console.WriteLn( Color_Green, L"Recovering single plugin: " + tbl_PluginInfo[m_pid].GetShortname() ); + memLoadingState load( m_plugstore ); + //if( m_plugstore.IsDisposed() ) load.SeekToSection( m_pid ); + GetCorePlugins().Freeze( m_pid, load ); + GetCorePlugins().Close( m_pid ); + } + + s_DisableGsWindow = false; + } + DESTRUCTOR_CATCHALL; + + s_DisableGsWindow = false; +} diff --git a/pcsx2/gui/AppCorePlugins.h b/pcsx2/gui/AppCorePlugins.h new file mode 100644 index 0000000000..dd067004c0 --- /dev/null +++ b/pcsx2/gui/AppCorePlugins.h @@ -0,0 +1,48 @@ +/* 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 "AppCommon.h" + +// -------------------------------------------------------------------------------------- +// AppPluginManager +// -------------------------------------------------------------------------------------- +// This extension of PluginManager provides event listener sources for plugins -- loading, +// unloading, open, close, shutdown, etc. +// +// FIXME : Should this be made part of the PCSX2 core emulation? (integrated into PluginManager) +// I'm undecided on if it makes sense more in that context or in this one (interface). +// +class AppPluginManager : public PluginManager +{ + typedef PluginManager _parent; + +public: + AppPluginManager(); + virtual ~AppPluginManager() throw(); + + void Load( const wxString (&folders)[PluginId_Count] ); + void Unload(); + + void Init(); + void Shutdown(); + void Close(); + void Open(); + +protected: + bool OpenPlugin_GS(); + void ClosePlugin_GS(); +}; diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index b1caf0f36c..9205ad29a8 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -14,78 +14,104 @@ */ #include "PrecompiledHeader.h" -#include "MainFrame.h" -#include "ps2/BiosTools.h" +#include "App.h" +#include "AppSaveStates.h" +#include "ps2/BiosTools.h" #include "GS.h" __aligned16 SysMtgsThread mtgsThread; __aligned16 AppCoreThread CoreThread; +static void PostCoreStatus( CoreThreadStatus pevt ) +{ + sApp.PostAction( CoreThreadStatusEvent( pevt ) ); +} + +// -------------------------------------------------------------------------------------- +// AppCoreThread Implementations +// -------------------------------------------------------------------------------------- AppCoreThread::AppCoreThread() : SysCoreThread() { } AppCoreThread::~AppCoreThread() throw() { - AppCoreThread::Cancel(); + _parent::Cancel(); // use parent's, skips thread affinity check. } void AppCoreThread::Cancel( bool isBlocking ) { - if( !_parent::Cancel( wxTimeSpan( 0, 0, 2, 0 ) ) ) - { - // Possible deadlock! - throw Exception::ThreadDeadlock( this ); - } + AffinityAssert_AllowFrom_SysExecutor(); + _parent::Cancel( wxTimeSpan(0, 0, 2, 0) ); } -void AppCoreThread::Reset() +void AppCoreThread::Shutdown() { - ScopedBusyCursor::SetDefault( Cursor_KindaBusy ); + AffinityAssert_AllowFrom_SysExecutor(); _parent::Reset(); + CorePlugins.Shutdown(); } -void AppCoreThread::DoThreadDeadlocked() +ExecutorThread& GetSysExecutorThread() { - wxGetApp().DoStuckThread( *this ); + return wxGetApp().SysExecutorThread; } -bool AppCoreThread::Suspend( bool isBlocking ) +typedef void (AppCoreThread::*FnPtr_CoreThreadMethod)(); + +// -------------------------------------------------------------------------------------- +// SysExecEvent_InvokeCoreThreadMethod +// -------------------------------------------------------------------------------------- +class SysExecEvent_InvokeCoreThreadMethod : public SysExecEvent { - ScopedBusyCursor::SetDefault( Cursor_KindaBusy ); +protected: + FnPtr_CoreThreadMethod m_method; - bool retval = _parent::Suspend( false ); - - if( !retval || isBlocking ) - ScopedBusyCursor::SetDefault( Cursor_NotBusy ); - - if( g_Conf->GSWindow.CloseOnEsc ) +public: + virtual ~SysExecEvent_InvokeCoreThreadMethod() throw() {} + SysExecEvent_InvokeCoreThreadMethod* Clone() const { return new SysExecEvent_InvokeCoreThreadMethod(*this); } + + SysExecEvent_InvokeCoreThreadMethod( FnPtr_CoreThreadMethod method ) { - sGSFrame.Hide(); + m_method = method; } + +protected: + void _DoInvoke() + { + if( m_method ) (CoreThread.*m_method)(); + } +}; - return retval; +bool ProcessingMethodViaThread( FnPtr_CoreThreadMethod method ) +{ + if( GetSysExecutorThread().IsSelf() ) return false; + SysExecEvent_InvokeCoreThreadMethod evt( method ); + GetSysExecutorThread().ProcessEvent( evt ); + return false; +} + +static void _Suspend() +{ + GetCoreThread().Suspend(true); +} + +void AppCoreThread::Suspend( bool isBlocking ) +{ + if( !GetSysExecutorThread().SelfProcessMethod( _Suspend ) ) + _parent::Suspend(true); } static int resume_tries = 0; void AppCoreThread::Resume() { - // Thread control (suspend / resume) should only be performed from the main/gui thread. - if( !AffinityAssert_AllowFromMain() ) return; - if( m_ExecMode == ExecMode_Opened ) return; - if( m_ResumeProtection.IsLocked() ) return; + if( !AffinityAssert_AllowFrom_SysExecutor() ) return; + if( m_ExecMode == ExecMode_Opened || (m_CloseTemporary > 0) ) return; - if( !pxAssert( g_plugins != NULL ) ) return; + if( !pxAssert( CorePlugins.AreLoaded() ) ) return; - if( sys_resume_lock > 0 ) - { - Console.WriteLn( "SysResume: State is locked, ignoring Resume request!" ); - return; - } - - ScopedBusyCursor::SetDefault( Cursor_KindaBusy ); _parent::Resume(); if( m_ExecMode != ExecMode_Opened ) @@ -93,7 +119,7 @@ void AppCoreThread::Resume() // Resume failed for some reason, so update GUI statuses and post a message to // try again on the resume. - wxGetApp().PostCommand( pxEvt_CoreThreadStatus, CoreThread_Suspended ); + PostCoreStatus( CoreThread_Suspended ); if( (m_ExecMode != ExecMode_Closing) || (m_ExecMode != ExecMode_Pausing) ) { @@ -109,29 +135,30 @@ void AppCoreThread::Resume() resume_tries = 0; } -void AppCoreThread::ChangeCdvdSource( CDVD_SourceType type ) +void AppCoreThread::ChangeCdvdSource() { - g_Conf->CdvdSource = type; - _parent::ChangeCdvdSource( type ); - sMainFrame.UpdateIsoSrcSelection(); + if( !GetSysExecutorThread().IsSelf() ) + { + GetSysExecutorThread().PostEvent( new SysExecEvent_InvokeCoreThreadMethod(&AppCoreThread::ChangeCdvdSource) ); + return; + } + + CDVD_SourceType cdvdsrc( g_Conf->CdvdSource ); + if( cdvdsrc == CDVDsys_GetSourceType() ) return; + + // Fast change of the CDVD source only -- a Pause will suffice. + + ScopedCoreThreadPause paused_core; + GetCorePlugins().Close( PluginId_CDVD ); + CDVDsys_ChangeSource( cdvdsrc ); + paused_core.AllowResume(); // TODO: Add a listener for CDVDsource changes? Or should we bother? } -void AppCoreThread::DoCpuReset() -{ - wxGetApp().PostCommand( pxEvt_CoreThreadStatus, CoreThread_Reset ); - _parent::DoCpuReset(); -} - void AppCoreThread::OnResumeReady() { ApplySettings( g_Conf->EmuOptions ); - - if( !wxFile::Exists( g_Conf->CurrentIso ) ) - g_Conf->CurrentIso.Clear(); - - sApp.GetRecentIsoManager().Add( g_Conf->CurrentIso ); CDVDsys_SetFile( CDVDsrc_Iso, g_Conf->CurrentIso ); AppSaveSettings(); @@ -139,45 +166,8 @@ void AppCoreThread::OnResumeReady() _parent::OnResumeReady(); } -void AppCoreThread::OnResumeInThread( bool isSuspended ) -{ - _parent::OnResumeInThread( isSuspended ); - wxGetApp().PostCommand( pxEvt_CoreThreadStatus, CoreThread_Resumed ); -} - -void AppCoreThread::OnSuspendInThread() -{ - _parent::OnSuspendInThread(); - wxGetApp().PostCommand( pxEvt_CoreThreadStatus, CoreThread_Suspended ); -} - -// Called whenever the thread has terminated, for either regular or irregular reasons. -// Typically the thread handles all its own errors, so there's no need to have error -// handling here. However it's a good idea to update the status of the GUI to reflect -// the new (lack of) thread status, so this posts a message to the App to do so. -void AppCoreThread::OnCleanupInThread() -{ - wxGetApp().PostCommand( pxEvt_CoreThreadStatus, CoreThread_Stopped ); - _parent::OnCleanupInThread(); -} - -void AppCoreThread::PostVsyncToUI() -{ - wxGetApp().LogicalVsync(); -} - -void AppCoreThread::StateCheckInThread() -{ - _parent::StateCheckInThread(); -} - -// To simplify settings application rules and re-entry conditions, the main App's implementation -// of ApplySettings requires that the caller manually ensure that the thread has been properly -// suspended. If the thread has not been suspended, this call will fail *silently*. void AppCoreThread::ApplySettings( const Pcsx2Config& src ) { - //if( m_ExecMode != ExecMode_Closed ) return; - Pcsx2Config fixup( src ); if( !g_Conf->EnableSpeedHacks ) fixup.Speedhacks = Pcsx2Config::SpeedhackOptions(); @@ -192,9 +182,63 @@ void AppCoreThread::ApplySettings( const Pcsx2Config& src ) RecursionGuard guard( localc ); if( guard.IsReentrant() ) return; if( fixup == EmuConfig ) return; - _parent::ApplySettings( fixup ); + + if( m_ExecMode >= ExecMode_Opened ) + { + ScopedCoreThreadPause paused_core; + _parent::ApplySettings( fixup ); + paused_core.AllowResume(); + } + else + { + _parent::ApplySettings( fixup ); + } } +// -------------------------------------------------------------------------------------- +// AppCoreThread *Worker* Implementations +// (Called from the context of this thread only) +// -------------------------------------------------------------------------------------- + +void AppCoreThread::DoCpuReset() +{ + PostCoreStatus( CoreThread_Reset ); + _parent::DoCpuReset(); +} + +void AppCoreThread::OnResumeInThread( bool isSuspended ) +{ + _parent::OnResumeInThread( isSuspended ); + PostCoreStatus( CoreThread_Resumed ); +} + +void AppCoreThread::OnSuspendInThread() +{ + _parent::OnSuspendInThread(); + PostCoreStatus( CoreThread_Suspended ); +} + +// Called whenever the thread has terminated, for either regular or irregular reasons. +// Typically the thread handles all its own errors, so there's no need to have error +// handling here. However it's a good idea to update the status of the GUI to reflect +// the new (lack of) thread status, so this posts a message to the App to do so. +void AppCoreThread::OnCleanupInThread() +{ + PostCoreStatus( CoreThread_Stopped ); + _parent::OnCleanupInThread(); +} + +void AppCoreThread::PostVsyncToUI() +{ + wxGetApp().LogicalVsync(); +} + +void AppCoreThread::StateCheckInThread() +{ + _parent::StateCheckInThread(); +} + +// Thread Affinity: This function is called from the SysCoreThread. :) void AppCoreThread::CpuInitializeMess() { if( m_hasValidState ) return; @@ -205,33 +249,278 @@ void AppCoreThread::CpuInitializeMess() // in order to ensure the plugins are in the proper (loaded/opened) state. SysClearExecutionCache(); - StateCopy_ThawFromMem_Blocking(); + memLoadingState( StateCopy_GetBuffer() ).FreezeAll(); + StateCopy_Clear(); m_hasValidState = true; m_resetVirtualMachine = false; return; } - + _parent::CpuInitializeMess(); } void AppCoreThread::ExecuteTaskInThread() { - wxGetApp().PostCommand( pxEvt_CoreThreadStatus, CoreThread_Started ); + PostCoreStatus( CoreThread_Started ); _parent::ExecuteTaskInThread(); - - // ---------------------------------------------------------------------------- - /*catch( Exception::PluginError& ex ) - { - if( g_plugins != NULL ) g_plugins->Close(); - Console.Error( ex.FormatDiagnosticMessage() ); - Msgbox::Alert( ex.FormatDisplayMessage(), _("Plugin Open Error") ); - - if( HandlePluginError( ex ) ) - { - // fixme: automatically re-try emu startup here... - } - }*/ } +enum +{ + FullStop_BlockingResume +, FullStop_NonblockingResume +, FullStop_SkipResume +}; + +// -------------------------------------------------------------------------------------- +// SysExecEvent_FullStop +// -------------------------------------------------------------------------------------- +class SysExecEvent_FullStop : public SysExecEvent +{ +protected: + SynchronousActionState* m_resume; + Threading::Mutex* m_mtx_resume; + +public: + virtual ~SysExecEvent_FullStop() throw() {} + SysExecEvent_FullStop* Clone() const + { + return new SysExecEvent_FullStop( *this ); + } + + SysExecEvent_FullStop( SynchronousActionState* sync=NULL, SynchronousActionState* resume_sync=NULL, Threading::Mutex* mtx_resume=NULL ) + : SysExecEvent( sync ) + { + m_resume = resume_sync; + m_mtx_resume = mtx_resume; + } + + SysExecEvent_FullStop( SynchronousActionState& sync, SynchronousActionState& resume_sync, Threading::Mutex& mtx_resume ) + : SysExecEvent( sync ) + { + m_resume = &resume_sync; + m_mtx_resume = &mtx_resume; + } + +protected: + void _DoInvoke() + { + ScopedCoreThreadClose closed_core; + PostResult(); + + if( m_resume ) + { + ScopedLock lock( m_mtx_resume ); + + // If the sender of the message requests a non-blocking resume, then we need + // to deallocate the m_sync object, since the sender will likely leave scope and + // invalidate it. + switch( m_resume->WaitForResult() ) + { + case FullStop_SkipResume: return; + + case FullStop_BlockingResume: + if( m_sync ) m_sync->ClearResult(); + break; + + case FullStop_NonblockingResume: + m_sync = NULL; + break; + } + } + + closed_core.AllowResume(); + } +}; + +// -------------------------------------------------------------------------------------- +// SysExecEvent_FullStop +// -------------------------------------------------------------------------------------- +class SysExecEvent_Pause : public SysExecEvent +{ +protected: + SynchronousActionState* m_resume; + Threading::Mutex* m_mtx_resume; + +public: + virtual ~SysExecEvent_Pause() throw() {} + SysExecEvent_Pause* Clone() const + { + return new SysExecEvent_Pause( *this ); + } + + SysExecEvent_Pause( SynchronousActionState* sync=NULL, SynchronousActionState* resume_sync=NULL, Threading::Mutex* mtx_resume=NULL ) + : SysExecEvent( sync ) + { + m_resume = resume_sync; + m_mtx_resume = mtx_resume; + } + + SysExecEvent_Pause( SynchronousActionState& sync, SynchronousActionState& resume_sync, Threading::Mutex& mtx_resume ) + : SysExecEvent( sync ) + { + m_resume = &resume_sync; + m_mtx_resume = &mtx_resume; + } + +protected: + void _DoInvoke() + { + ScopedCoreThreadPause paused_core; + PostResult(); + + if( m_resume ) + { + ScopedLock lock( m_mtx_resume ); + + // If the sender of the message requests a non-blocking resume, then we need + // to deallocate the m_sync object, since the sender will likely leave scope and + // invalidate it. + switch( m_resume->WaitForResult() ) + { + case FullStop_SkipResume: return; + + case FullStop_BlockingResume: + if( m_sync ) m_sync->ClearResult(); + break; + + case FullStop_NonblockingResume: + m_sync = NULL; + break; + } + } + + paused_core.AllowResume(); + } +}; + +// -------------------------------------------------------------------------------------- +// ScopedCoreThreadClose / ScopedCoreThreadPause +// -------------------------------------------------------------------------------------- + +static __threadlocal bool ScopedCore_IsPaused = false; +static __threadlocal bool ScopedCore_IsFullyClosed = false; + +BaseScopedCoreThread::BaseScopedCoreThread() +{ + //AffinityAssert_AllowFrom_MainUI(); + + m_allowResume = false; + m_alreadyStopped = false; + m_alreadyScoped = false; +} + +BaseScopedCoreThread::~BaseScopedCoreThread() throw() +{ +} + +// Allows the object to resume execution upon object destruction. Typically called as the last thing +// in the object's scope. Any code prior to this call that causes exceptions will not resume the emulator, +// which is *typically* the intended behavior when errors occur. +void BaseScopedCoreThread::AllowResume() +{ + m_allowResume = true; +} + +void BaseScopedCoreThread::DisallowResume() +{ + m_allowResume = false; +} + +void BaseScopedCoreThread::DoResume() +{ + if( m_alreadyStopped ) return; + if( !GetSysExecutorThread().IsSelf() ) + { + //DbgCon.WriteLn("(ScopedCoreThreadPause) Threaded Scope Created!"); + m_sync_resume.PostResult( m_allowResume ? FullStop_NonblockingResume : FullStop_SkipResume ); + m_mtx_resume.Wait(); + } + else + CoreThread.Resume(); +} + + +ScopedCoreThreadClose::ScopedCoreThreadClose() +{ + if( ScopedCore_IsFullyClosed ) + { + // tracks if we're already in scope or not. + m_alreadyScoped = true; + return; + } + + if( !GetSysExecutorThread().IsSelf() ) + { + //DbgCon.WriteLn("(ScopedCoreThreadClose) Threaded Scope Created!"); + + GetSysExecutorThread().PostEvent( SysExecEvent_FullStop(m_sync, m_sync_resume, m_mtx_resume) ); + m_sync.WaitForResult(); + m_sync.RethrowException(); + } + else if( !(m_alreadyStopped = CoreThread.IsClosed()) ) + CoreThread.Suspend(); + + ScopedCore_IsFullyClosed = true; +} + +ScopedCoreThreadClose::~ScopedCoreThreadClose() throw() +{ + if( m_alreadyScoped ) return; + _parent::DoResume(); + ScopedCore_IsFullyClosed = false; +} + +ScopedCoreThreadPause::ScopedCoreThreadPause() +{ + if( ScopedCore_IsFullyClosed || ScopedCore_IsPaused ) + { + // tracks if we're already in scope or not. + m_alreadyScoped = true; + return; + } + + if( !GetSysExecutorThread().IsSelf() ) + { + //DbgCon.WriteLn("(ScopedCoreThreadPause) Threaded Scope Created!"); + + GetSysExecutorThread().PostEvent( SysExecEvent_Pause(m_sync, m_sync_resume, m_mtx_resume) ); + m_sync.WaitForResult(); + m_sync.RethrowException(); + } + else if( !(m_alreadyStopped = CoreThread.IsPaused()) ) + CoreThread.Pause(); + + ScopedCore_IsPaused = true; +} + +ScopedCoreThreadPause::~ScopedCoreThreadPause() throw() +{ + if( m_alreadyScoped ) return; + _parent::DoResume(); + ScopedCore_IsPaused = false; +} + +ScopedCoreThreadPopup::ScopedCoreThreadPopup() +{ + // The old style GUI (without GSopen2) must use a full close of the CoreThread, in order to + // ensure that the GS window isn't blocking the popup, and to avoid crashes if the GS window + // is maximized or fullscreen. + + if( !GSopen2 ) + m_scoped_core = new ScopedCoreThreadClose(); + else + m_scoped_core = new ScopedCoreThreadPause(); +}; + +void ScopedCoreThreadPopup::AllowResume() +{ + if( m_scoped_core ) m_scoped_core->AllowResume(); +} + +void ScopedCoreThreadPopup::DisallowResume() +{ + if( m_scoped_core ) m_scoped_core->DisallowResume(); +} diff --git a/pcsx2/gui/AppCoreThread.h b/pcsx2/gui/AppCoreThread.h new file mode 100644 index 0000000000..93c6cd7632 --- /dev/null +++ b/pcsx2/gui/AppCoreThread.h @@ -0,0 +1,143 @@ +/* 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 "SysThreads.h" +#include "AppCommon.h" +#include "AppCorePlugins.h" + +#define AffinityAssert_AllowFrom_CoreThread() \ + pxAssertMsg( GetCoreThread().IsSelf(), "Thread affinity violation: Call allowed from SysCoreThread only." ) + +#define AffinityAssert_DisallowFrom_CoreThread() \ + pxAssertMsg( !GetCoreThread().IsSelf(), "Thread affinity violation: Call is *not* allowed from SysCoreThread." ) + +// -------------------------------------------------------------------------------------- +// AppCoreThread class +// -------------------------------------------------------------------------------------- +class AppCoreThread : public SysCoreThread +{ + typedef SysCoreThread _parent; + +public: + AppCoreThread(); + virtual ~AppCoreThread() throw(); + + virtual void Suspend( bool isBlocking=false ); + virtual void Resume(); + virtual void Shutdown(); + virtual void Cancel( bool isBlocking=true ); + virtual void StateCheckInThread(); + virtual void ApplySettings( const Pcsx2Config& src ); + virtual void ChangeCdvdSource(); + +protected: + virtual void OnResumeReady(); + virtual void OnResumeInThread( bool IsSuspended ); + virtual void OnSuspendInThread(); + virtual void OnCleanupInThread(); + virtual void PostVsyncToUI(); + virtual void ExecuteTaskInThread(); + virtual void DoCpuReset(); + virtual void CpuInitializeMess(); + +}; + +class IScopedCoreThread +{ +protected: + IScopedCoreThread() {} + +public: + virtual ~IScopedCoreThread() throw() {}; + virtual void AllowResume()=0; + virtual void DisallowResume()=0; +}; + + +class BaseScopedCoreThread : public IScopedCoreThread +{ + DeclareNoncopyableObject( BaseScopedCoreThread ); + +protected: + bool m_allowResume; + bool m_alreadyStopped; + bool m_alreadyScoped; + SynchronousActionState m_sync; + SynchronousActionState m_sync_resume; + Threading::Mutex m_mtx_resume; + + BaseScopedCoreThread(); + +public: + virtual ~BaseScopedCoreThread() throw()=0; + virtual void AllowResume(); + virtual void DisallowResume(); + +protected: + void DoResume(); +}; + +// -------------------------------------------------------------------------------------- +// ScopedCoreThreadClose / ScopedCoreThreadPause / ScopedCoreThreadPopup +// -------------------------------------------------------------------------------------- +// This class behaves a bit differently from other scoped classes due to the "standard" +// assumption that we actually do *not* want to resume CoreThread operations when an +// exception occurs. Because of this, the destructor of this class does *not* unroll the +// suspend operation. Instead you must manually instruct the class to resume using a call +// to the provisioned AllowResume() method. +// +// If the class leaves scope without having been resumed, a log is written to the console. +// This can be useful for troubleshooting, and also allows the log a second line of info +// indicating the status of CoreThread execution at the time of the exception. +// +// ScopedCoreThreadPopup is intended for use where message boxes are popped up to the user. +// The old style GUI (without GSopen2) must use a full close of the CoreThread, in order to +// ensure that the GS window isn't blocking the popup, and to avoid crashes if the GS window +// is maximized or fullscreen. +// +class ScopedCoreThreadClose : public BaseScopedCoreThread +{ + typedef BaseScopedCoreThread _parent; + +public: + ScopedCoreThreadClose(); + virtual ~ScopedCoreThreadClose() throw(); + + void LoadPlugins(); +}; + +struct ScopedCoreThreadPause : public BaseScopedCoreThread +{ + typedef BaseScopedCoreThread _parent; + +public: + ScopedCoreThreadPause(); + virtual ~ScopedCoreThreadPause() throw(); +}; + +struct ScopedCoreThreadPopup : public IScopedCoreThread +{ +protected: + ScopedPtr m_scoped_core; + +public: + ScopedCoreThreadPopup(); + virtual ~ScopedCoreThreadPopup() throw() {} + + virtual void AllowResume(); + virtual void DisallowResume(); +}; diff --git a/pcsx2/gui/AppEventListeners.h b/pcsx2/gui/AppEventListeners.h index eacf45019e..485797659a 100644 --- a/pcsx2/gui/AppEventListeners.h +++ b/pcsx2/gui/AppEventListeners.h @@ -16,6 +16,7 @@ #pragma once #include "Utilities/EventSource.h" +#include "Utilities/pxEvents.h" enum CoreThreadStatus { @@ -242,3 +243,28 @@ protected: virtual void AppStatusEvent_OnSettingsApplied() { Owner.AppStatusEvent_OnSettingsApplied(); } virtual void AppStatusEvent_OnExit() { Owner.AppStatusEvent_OnExit(); } }; + + +// -------------------------------------------------------------------------------------- +// CoreThreadStatusEvent +// -------------------------------------------------------------------------------------- +class CoreThreadStatusEvent : public pxInvokeActionEvent +{ + typedef pxInvokeActionEvent _parent; + +protected: + CoreThreadStatus m_evt; + +public: + virtual ~CoreThreadStatusEvent() throw() {} + CoreThreadStatusEvent* Clone() const { return new CoreThreadStatusEvent( *this ); } + + explicit CoreThreadStatusEvent( CoreThreadStatus evt, SynchronousActionState* sema=NULL ); + explicit CoreThreadStatusEvent( CoreThreadStatus evt, SynchronousActionState& sema ); + + void SetEventType( CoreThreadStatus evt ) { m_evt = evt; } + CoreThreadStatus GetEventType() { return m_evt; } + +protected: + void _DoInvoke(); +}; diff --git a/pcsx2/gui/AppEventSources.cpp b/pcsx2/gui/AppEventSources.cpp index 3dbff37c99..32c19f9724 100644 --- a/pcsx2/gui/AppEventSources.cpp +++ b/pcsx2/gui/AppEventSources.cpp @@ -42,12 +42,14 @@ void IEventListener_CoreThread::DispatchEvent( const CoreThreadStatus& status ) { switch( status ) { + case CoreThread_Indeterminate: break; + case CoreThread_Started: CoreThread_OnStarted(); break; case CoreThread_Resumed: CoreThread_OnResumed(); break; case CoreThread_Suspended: CoreThread_OnSuspended(); break; case CoreThread_Reset: CoreThread_OnReset(); break; case CoreThread_Stopped: CoreThread_OnStopped(); break; - + jNO_DEFAULT; } } @@ -74,7 +76,7 @@ void IEventListener_Plugins::DispatchEvent( const PluginEventType& pevt ) case CorePlugins_Closed: CorePlugins_OnClosed(); break; case CorePlugins_Shutdown: CorePlugins_OnShutdown(); break; case CorePlugins_Unloaded: CorePlugins_OnUnloaded(); break; - + jNO_DEFAULT; } } @@ -102,3 +104,71 @@ void IEventListener_AppStatus::DispatchEvent( const AppEventInfo& evtinfo ) case AppStatus_Exiting: AppStatusEvent_OnExit(); break; } } + + +void Pcsx2App::DispatchEvent( PluginEventType evt ) +{ + if( !AffinityAssert_AllowFrom_MainUI() ) return; + m_evtsrc_CorePluginStatus.Dispatch( evt ); +} + +void Pcsx2App::DispatchEvent( AppEventType evt ) +{ + if( !AffinityAssert_AllowFrom_MainUI() ) return; + m_evtsrc_AppStatus.Dispatch( AppEventInfo( evt ) ); +} + +void Pcsx2App::DispatchEvent( CoreThreadStatus evt ) +{ + switch( evt ) + { + case CoreThread_Started: + case CoreThread_Reset: + case CoreThread_Stopped: + FpsManager.Reset(); + break; + + case CoreThread_Resumed: + case CoreThread_Suspended: + FpsManager.Resume(); + break; + } + + // Clear the sticky key statuses, because hell knows what'll change while the PAD + // plugin is suspended. + + m_kevt.m_shiftDown = false; + m_kevt.m_controlDown = false; + m_kevt.m_altDown = false; + + m_evtsrc_CoreThreadStatus.Dispatch( evt ); + ScopedBusyCursor::SetDefault( Cursor_NotBusy ); + CoreThread.RethrowException(); +} + +void Pcsx2App::DispatchEvent( IniInterface& ini ) +{ + if( !AffinityAssert_AllowFrom_MainUI() ) return; + m_evtsrc_AppStatus.Dispatch( AppSettingsEventInfo( ini ) ); +} + + +// -------------------------------------------------------------------------------------- +// CoreThreadStatusEvent Implementations +// -------------------------------------------------------------------------------------- +CoreThreadStatusEvent::CoreThreadStatusEvent( CoreThreadStatus evt, SynchronousActionState* sema ) + : pxInvokeActionEvent( sema ) +{ + m_evt = evt; +} + +CoreThreadStatusEvent::CoreThreadStatusEvent( CoreThreadStatus evt, SynchronousActionState& sema ) + : pxInvokeActionEvent( sema ) +{ + m_evt = evt; +} + +void CoreThreadStatusEvent::_DoInvoke() +{ + sApp.DispatchEvent( m_evt ); +} diff --git a/pcsx2/gui/AppForwardDefs.h b/pcsx2/gui/AppForwardDefs.h index e735e06482..ec65b4f99c 100644 --- a/pcsx2/gui/AppForwardDefs.h +++ b/pcsx2/gui/AppForwardDefs.h @@ -19,9 +19,9 @@ // // Purpose: // This header file is meant to be a dependency-free include that provides a relatively -// full compliment of forward defines for PCSX2/App and wxwidgets types. When +// full compliment of forward defines for PCSX2/App and wxwidgets types. When // forward defined in this way, these types can be used by method and class definitions -// as either pointers or handles without running into complicated header file +// as either pointers or handles without running into complicated header file // inter-dependence. // @@ -30,7 +30,7 @@ class GSFrame; class ConsoleLogFrame; class PipeRedirectionBase; class AppCoreThread; -class pxInvokeAppMethodEvent; +class Pcsx2AppMethodEvent; class IniInterface; // wxWidgets forward declarations diff --git a/pcsx2/gui/AppInit.cpp b/pcsx2/gui/AppInit.cpp index f852594101..46e24bc6e4 100644 --- a/pcsx2/gui/AppInit.cpp +++ b/pcsx2/gui/AppInit.cpp @@ -53,15 +53,6 @@ static void CpuCheckSSE2() g_Conf->EmuOptions.Cpu.Recompiler.EnableVU1 = false; } - -void Pcsx2App::OpenWizardConsole() -{ - if( !IsDebugBuild ) return; - g_Conf->ProgLogBox.Visible = true; - m_id_ProgramLogBox = (new ConsoleLogFrame( NULL, L"PCSX2 Program Log", g_Conf->ProgLogBox ))->GetId(); - EnableAllLogging(); -} - void Pcsx2App::WipeUserModeSettings() { wxDirName usrlocaldir( wxStandardPaths::Get().GetUserLocalDataDir() ); @@ -116,14 +107,14 @@ void Pcsx2App::ReadUserModeSettings() L"It will likely crash on all games, devour your young, and make you an object of shame and disgrace among your family and friends. " L"Do not report any bugs with this version if you received this popup. \n\nYou have been warned. ", wxALIGN_CENTER ); - + hackedVersion += new wxButton( &hackedVersion, wxID_OK ) | pxSizerFlags::StdCenter(); hackedVersion.ShowModal(); } bool hasGroup = conf_usermode->HasGroup( groupname ); bool forceWiz = m_ForceWizard || !hasGroup; - + if( !forceWiz ) { conf_usermode->SetPath( groupname ); @@ -141,18 +132,17 @@ void Pcsx2App::ReadUserModeSettings() preAlpha.SetSizer( new wxBoxSizer( wxVERTICAL ) ); preAlpha += new pxStaticText( &preAlpha, - L"NOTICE!! This is a *PRE-ALPHA* developer build of PCSX2 0.9.7. We are in the middle of major rewrites of the " + L"NOTICE!! This is a *PRE-ALPHA* developer build of PCSX2 0.9.7. We are in the middle of major rewrites of the " L"user interface, and many parts of the program have *NOT* been implemented yet. Options will be missing. " L"Some things may crash or hang without warning. Other things will seem plainly stupid and the product of incompetent " L"programmers. This is normal. We're working on it.\n\nYou have been warned!", wxALIGN_CENTER ); - + preAlpha += new wxButton( &preAlpha, wxID_OK ) | pxSizerFlags::StdCenter(); preAlpha.ShowModal(); } - + // first time startup, so give the user the choice of user mode: - OpenWizardConsole(); FirstTimeWizard wiz( NULL ); if( !wiz.RunWizard( wiz.GetUsermodePage() ) ) throw Exception::StartupAborted( L"Startup aborted: User canceled FirstTime Wizard." ); @@ -178,7 +168,6 @@ void Pcsx2App::ReadUserModeSettings() // user wiped their pcsx2.ini -- needs a reconfiguration via wizard! // (we skip the first page since it's a usermode.ini thing) - OpenWizardConsole(); FirstTimeWizard wiz( NULL ); if( !wiz.RunWizard( wiz.GetPostUsermodePage() ) ) throw Exception::StartupAborted( L"Startup aborted: User canceled Configuration Wizard." ); @@ -190,11 +179,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(); - sys_resume_lock = 0; + + // force unload plugins loaded by the wizard. If we don't do this the recompilers might + // fail to allocate the memory they need to function. + UnloadPlugins(); } void Pcsx2App::DetectCpuAndUserMode() @@ -211,9 +199,9 @@ void Pcsx2App::DetectCpuAndUserMode() ReadUserModeSettings(); AppConfig_OnChangedSettingsFolder(); - PostMethod( &Pcsx2App::OpenMainFrame ); - PostMethod( &Pcsx2App::OpenConsoleLog ); - PostMethod( &Pcsx2App::AllocateCoreStuffs ); + PostAppMethod( &Pcsx2App::OpenMainFrame ); + PostAppMethod( &Pcsx2App::OpenConsoleLog ); + PostAppMethod( &Pcsx2App::AllocateCoreStuffs ); } void Pcsx2App::OpenMainFrame() @@ -222,14 +210,13 @@ void Pcsx2App::OpenMainFrame() MainEmuFrame* mainFrame = new MainEmuFrame( NULL, L"PCSX2" ); m_id_MainFrame = mainFrame->GetId(); - mainFrame->PushEventHandler( &GetRecentIsoManager() ); if( wxWindow* deleteme = GetProgramLog() ) { deleteme->Destroy(); g_Conf->ProgLogBox.Visible = true; m_id_ProgramLogBox = wxID_ANY; - PostIdleMethod( &Pcsx2App::OpenConsoleLog ); + PostIdleAppMethod( &Pcsx2App::OpenConsoleLog ); } SetTopWindow( mainFrame ); // not really needed... @@ -259,7 +246,7 @@ void Pcsx2App::AllocateCoreStuffs() // HadSomeFailures only returns 'true' if an *enabled* cpu type fails to init. If // the user already has all interps configured, for example, then no point in // popping up this dialog. - + wxDialogWithHelpers exconf( NULL, _("PCSX2 Recompiler Error(s)"), wxVERTICAL ); exconf += 12; @@ -273,7 +260,7 @@ void Pcsx2App::AllocateCoreStuffs() ); exconf += scrollableTextArea | pxSizerFlags::StdExpand(); - + if( !m_CoreAllocs->IsRecAvailable_EE() ) { scrollableTextArea->AppendText( L"* R5900 (EE)\n\n" ); @@ -333,7 +320,7 @@ void Pcsx2App::AllocateCoreStuffs() } } - LoadPluginsPassive( NULL ); + LoadPluginsPassive(); } @@ -414,15 +401,13 @@ bool Pcsx2App::OnCmdLineParsed( wxCmdLineParser& parser ) return true; } -typedef void (wxEvtHandler::*pxInvokeMethodEventFunction)(pxInvokeAppMethodEvent&); +typedef void (wxEvtHandler::*pxInvokeAppMethodEventFunction)(Pcsx2AppMethodEvent&); typedef void (wxEvtHandler::*pxStuckThreadEventHandler)(pxMessageBoxEvent&); bool Pcsx2App::OnInit() { -#define pxMethodEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(pxInvokeMethodEventFunction, &func ) - - Connect( pxEvt_OpenModalDialog, wxCommandEventHandler( Pcsx2App::OnOpenModalDialog ) ); +#define pxAppMethodEventHandler(func) \ + (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(pxInvokeAppMethodEventFunction, &func ) pxDoAssert = AppDoAssert; @@ -436,14 +421,7 @@ bool Pcsx2App::OnInit() m_StderrRedirHandle = NewPipeRedir(stderr); wxLocale::AddCatalogLookupPathPrefix( wxGetCwd() ); - Connect( pxEvt_FreezeThreadFinished, wxCommandEventHandler (Pcsx2App::OnFreezeThreadFinished) ); - Connect( pxEvt_CoreThreadStatus, wxCommandEventHandler (Pcsx2App::OnCoreThreadStatus) ); - Connect( pxEvt_LoadPluginsComplete, wxCommandEventHandler (Pcsx2App::OnLoadPluginsComplete) ); - Connect( pxEvt_PluginStatus, wxCommandEventHandler (Pcsx2App::OnPluginStatus) ); - Connect( pxEvt_SysExecute, wxCommandEventHandler (Pcsx2App::OnSysExecute) ); - Connect( pxEvt_InvokeMethod, pxMethodEventHandler (Pcsx2App::OnInvokeMethod) ); - - Connect( pxID_PadHandler_Keydown, wxEVT_KEY_DOWN, wxKeyEventHandler( Pcsx2App::OnEmuKeyDown ) ); + Connect( pxID_PadHandler_Keydown, wxEVT_KEY_DOWN, wxKeyEventHandler (Pcsx2App::OnEmuKeyDown) ); // User/Admin Mode Dual Setup: // PCSX2 now supports two fundamental modes of operation. The default is Classic mode, @@ -467,6 +445,7 @@ bool Pcsx2App::OnInit() pxDwm_Load(); #endif + SysExecutorThread.Start(); DetectCpuAndUserMode(); } // ---------------------------------------------------------------------------- @@ -502,25 +481,17 @@ bool Pcsx2App::OnInit() // OnExit() must use CleanupOnExit instead. void Pcsx2App::CleanupRestartable() { - AffinityAssert_AllowFromMain(); + AffinityAssert_AllowFrom_MainUI(); - // app is shutting down, so don't let the system resume for anything. (sometimes - // there are pending Resume messages in the queue from previous user actions, and - // this will block them from executing). - sys_resume_lock += 10; + ShutdownPlugins(); + SysExecutorThread.ShutdownQueue(); - PingDispatcher( "Cleanup" ); - DeletionDispatcher(); - - CoreThread.Cancel(); - - if( m_CorePlugins ) - m_CorePlugins->Shutdown(); + //PingDispatcher( "Cleanup" ); + //DeletionDispatcher(); + IdleEventDispatcher( "Cleanup" ); if( g_Conf ) AppSaveSettings(); - - sMainFrame.RemoveEventHandler( &GetRecentIsoManager() ); } // This cleanup handler can be called from OnExit (it doesn't need a running message pump), @@ -529,14 +500,13 @@ void Pcsx2App::CleanupRestartable() // to be friendly to the OnExit scenario (no message pump). void Pcsx2App::CleanupOnExit() { - AffinityAssert_AllowFromMain(); + AffinityAssert_AllowFrom_MainUI(); try { CleanupRestartable(); CleanupResources(); } - catch( Exception::ThreadDeadlock& ) { throw; } catch( Exception::CancelEvent& ) { throw; } catch( Exception::RuntimeError& ex ) { @@ -551,13 +521,13 @@ void Pcsx2App::CleanupOnExit() #ifdef __WXMSW__ pxDwm_Unload(); #endif - + // Notice: deleting the plugin manager (unloading plugins) here causes Lilypad to crash, // likely due to some pending message in the queue that references lilypad procs. // We don't need to unload plugins anyway tho -- shutdown is plenty safe enough for // closing out all the windows. So just leave it be and let the plugins get unloaded // during the wxApp destructor. -- air - + // FIXME: performing a wxYield() here may fix that problem. -- air pxDoAssert = pxAssertImpl_LogIt; @@ -581,9 +551,30 @@ int Pcsx2App::OnExit() return wxApp::OnExit(); } - -Pcsx2App::Pcsx2App() +// -------------------------------------------------------------------------------------- +// SysEventHandler +// -------------------------------------------------------------------------------------- +class SysEvtHandler : public pxEvtHandler { +public: + wxString GetEvtHandlerName() const { return L"SysExecutor"; } + +protected: + // When the SysExec message queue is finally empty, we should check the state of + // the menus and make sure they're all consistent to the current emulation states. + void DoIdle() + { + UI_UpdateSysControls(); + } +}; + + +Pcsx2App::Pcsx2App() + : SysExecutorThread( new SysEvtHandler() ) +{ + m_PendingSaves = 0; + m_ScheduledTermination = false; + m_id_MainFrame = wxID_ANY; m_id_GsFrame = wxID_ANY; m_id_ProgramLogBox = wxID_ANY; diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index 96c522b9bb..7283108a76 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -16,6 +16,8 @@ #include "PrecompiledHeader.h" #include "IniInterface.h" #include "MainFrame.h" +#include "GSFrame.h" + #include "Plugins.h" #include "AppSaveStates.h" #include "ps2/BiosTools.h" @@ -32,15 +34,10 @@ IMPLEMENT_APP(Pcsx2App) -DEFINE_EVENT_TYPE( pxEvt_FreezeThreadFinished ); -DEFINE_EVENT_TYPE( pxEvt_CoreThreadStatus ); DEFINE_EVENT_TYPE( pxEvt_LoadPluginsComplete ); -DEFINE_EVENT_TYPE( pxEvt_PluginStatus ); -DEFINE_EVENT_TYPE( pxEvt_SysExecute ); -DEFINE_EVENT_TYPE( pxEvt_InvokeMethod ); DEFINE_EVENT_TYPE( pxEvt_LogicalVsync ); -DEFINE_EVENT_TYPE( pxEvt_OpenModalDialog ); +DEFINE_EVENT_TYPE( pxEvt_ThreadTaskTimeout_SysExec ); DocsModeType DocsFolderMode = DocsFolder_User; wxDirName SettingsFolder; @@ -50,6 +47,77 @@ bool UseDefaultSettingsFolder = true; ScopedPtr g_Conf; ConfigOverrides OverrideOptions; +class NamedDialogBoxEvent : public BaseMessageBoxEvent +{ + typedef BaseMessageBoxEvent _parent; + DECLARE_DYNAMIC_CLASS_NO_ASSIGN(NamedDialogBoxEvent) + +public: + virtual ~NamedDialogBoxEvent() throw() { } + virtual NamedDialogBoxEvent *Clone() const { return new NamedDialogBoxEvent(*this); } + + NamedDialogBoxEvent() {} + NamedDialogBoxEvent( const wxString& name, SynchronousActionState& sync ) + : BaseMessageBoxEvent( name, sync ) {} + NamedDialogBoxEvent( const wxString& name, SynchronousActionState* sync=NULL ) + : BaseMessageBoxEvent( name, sync ) {} + +protected: + int _DoDialog() const + { + const wxString& dlgName( m_Content ); + + if( dlgName.IsEmpty() ) return wxID_CANCEL; + + if( wxWindow* window = wxFindWindowByName( dlgName ) ) + { + if( wxDialog* dialog = wxDynamicCast( window, wxDialog ) ) + { + window->SetFocus(); + + // It's legal to call ShowModal on a non-modal dialog, therefore making + // it modal in nature for the needs of whatever other thread of action wants + // to block against it: + + if( !dialog->IsModal() ) + { + int result = dialog->ShowModal(); + dialog->Destroy(); + return result; + } + } + } + else + { + using namespace Dialogs; + + if( dlgName == SysConfigDialog::GetNameStatic() ) + return SysConfigDialog().ShowModal(); + if( dlgName == AppConfigDialog::GetNameStatic() ) + return AppConfigDialog().ShowModal(); + if( dlgName == BiosSelectorDialog::GetNameStatic() ) + return BiosSelectorDialog().ShowModal(); + if( dlgName == LogOptionsDialog::GetNameStatic() ) + return LogOptionsDialog().ShowModal(); + if( dlgName == AboutBoxDialog::GetNameStatic() ) + return AboutBoxDialog().ShowModal(); + } + + return wxID_CANCEL; + } +}; + +IMPLEMENT_DYNAMIC_CLASS( NamedDialogBoxEvent, BaseMessageBoxEvent ); + +// Opens the specified standard dialog as a modal dialog, or forces the an existing +// instance of the dialog (ie, it's already open) to be modal. This is needed for +// items which are +static int IssueDialogAsModal( const wxString& dlgName ) +{ + BaseMessageBoxEvent tevt( dlgName ); + return Msgbox::ShowModal( tevt ); +} + static bool HandlePluginError( Exception::PluginError& ex ) { if( pxDialogExists( L"CoreSettings" ) ) return true; @@ -63,7 +131,7 @@ static bool HandlePluginError( Exception::PluginError& ex ) g_Conf->SysSettingsTabName = L"Plugins"; // fixme: Send a message to the panel to select the failed plugin. - if( wxGetApp().IssueDialogAsModal( Dialogs::SysConfigDialog::GetNameStatic() ) == wxID_CANCEL ) + if( IssueDialogAsModal( Dialogs::SysConfigDialog::GetNameStatic() ) == wxID_CANCEL ) return false; } return result; @@ -84,66 +152,56 @@ void Pcsx2App::PostMenuAction( MenuIdentifiers menu_id ) const } // -------------------------------------------------------------------------------------- -// pxInvokeAppMethodEvent +// Pcsx2AppMethodEvent // -------------------------------------------------------------------------------------- // Unlike pxPingEvent, the Semaphore belonging to this event is typically posted when the // invoked method is completed. If the method can be executed in non-blocking fashion then // it should leave the semaphore postback NULL. // -class pxInvokeAppMethodEvent : public pxPingEvent +class Pcsx2AppMethodEvent : public pxInvokeActionEvent { - DECLARE_DYNAMIC_CLASS_NO_ASSIGN(pxInvokeAppMethodEvent) + typedef pxInvokeActionEvent _parent; + DECLARE_DYNAMIC_CLASS_NO_ASSIGN(Pcsx2AppMethodEvent) protected: - FnPtr_AppMethod m_Method; + FnPtr_Pcsx2App m_Method; public: - virtual ~pxInvokeAppMethodEvent() throw() { } - virtual pxInvokeAppMethodEvent *Clone() const { return new pxInvokeAppMethodEvent(*this); } + virtual ~Pcsx2AppMethodEvent() throw() { } + virtual Pcsx2AppMethodEvent *Clone() const { return new Pcsx2AppMethodEvent(*this); } - explicit pxInvokeAppMethodEvent( int msgtype, FnPtr_AppMethod method=NULL, Semaphore* sema=NULL ) - : pxPingEvent( msgtype, sema ) + explicit Pcsx2AppMethodEvent( FnPtr_Pcsx2App method=NULL, SynchronousActionState* sema=NULL ) + : pxInvokeActionEvent( sema ) { m_Method = method; } - explicit pxInvokeAppMethodEvent( FnPtr_AppMethod method=NULL, Semaphore* sema=NULL ) - : pxPingEvent( pxEvt_InvokeMethod, sema ) + explicit Pcsx2AppMethodEvent( FnPtr_Pcsx2App method, SynchronousActionState& sema ) + : pxInvokeActionEvent( sema ) { m_Method = method; } - - explicit pxInvokeAppMethodEvent( FnPtr_AppMethod method, Semaphore& sema ) - : pxPingEvent( pxEvt_InvokeMethod, &sema ) - { - m_Method = method; - } - - pxInvokeAppMethodEvent( const pxInvokeAppMethodEvent& src ) - : pxPingEvent( src ) + + Pcsx2AppMethodEvent( const Pcsx2AppMethodEvent& src ) + : pxInvokeActionEvent( src ) { m_Method = src.m_Method; } - - void Invoke() const - { - if( m_Method ) (wxGetApp().*m_Method)(); - if( m_PostBack ) m_PostBack->Post(); - } - - void SetMethod( FnPtr_AppMethod method ) + + void SetMethod( FnPtr_Pcsx2App method ) { m_Method = method; } + +protected: + void _DoInvoke() + { + if( m_Method ) (wxGetApp().*m_Method)(); + } }; -IMPLEMENT_DYNAMIC_CLASS( pxInvokeAppMethodEvent, pxPingEvent ) - -void Pcsx2App::OnInvokeMethod( pxInvokeAppMethodEvent& evt ) -{ - evt.Invoke(); // wow this is easy! -} +IMPLEMENT_DYNAMIC_CLASS( Pcsx2AppMethodEvent, pxInvokeActionEvent ) #ifdef __WXGTK__ extern int TranslateGDKtoWXK( u32 keysym ); @@ -206,7 +264,7 @@ void FramerateManager::Reset() Resume(); } -// +// void FramerateManager::Resume() { } @@ -218,7 +276,7 @@ void FramerateManager::DoFrame() m_fpsqueue_writepos = (m_fpsqueue_writepos + 1) % FramerateQueueDepth; m_fpsqueue[m_fpsqueue_writepos] = GetCPUTicks(); - // intentionally leave 1 on the counter here, since ultimately we want to divide the + // intentionally leave 1 on the counter here, since ultimately we want to divide the // final result (in GetFramerate() by QueueDepth-1. if( m_initpause > 1 ) --m_initpause; } @@ -232,13 +290,13 @@ double FramerateManager::GetFramerate() const } // LogicalVsync - Event received from the AppCoreThread (EEcore) for each vsync, -// roughly 50/60 times a second when frame limiting is enabled, and up to 10,000 +// roughly 50/60 times a second when frame limiting is enabled, and up to 10,000 // times a second if not (ok, not quite, but you get the idea... I hope.) void Pcsx2App::LogicalVsync() { - if( PostMethodToMainThread( &Pcsx2App::LogicalVsync ) ) return; + if( PostAppMethodMyself( &Pcsx2App::LogicalVsync ) ) return; - if( !SysHasValidState() || g_plugins == NULL ) return; + if( !SysHasValidState() ) return; // Update / Calculate framerate! @@ -249,78 +307,24 @@ void Pcsx2App::LogicalVsync() if( (PADupdate != NULL) && (GSopen2 != NULL) && (wxGetApp().GetGsFramePtr() != NULL) ) PADupdate(0); - const keyEvent* ev = PADkeyEvent(); - - if( (ev != NULL) && (ev->key != 0) ) + while( const keyEvent* ev = PADkeyEvent() ) { + if( ev->key == 0 ) break; + // Give plugins first try to handle keys. If none of them handles the key, it will // be passed to the main user interface. - if( !g_plugins->KeyEvent( *ev ) ) + if( !GetCorePlugins().KeyEvent( *ev ) ) PadKeyDispatch( *ev ); } } + // ---------------------------------------------------------------------------- // Pcsx2App Event Handlers // ---------------------------------------------------------------------------- -// Invoked by the AppCoreThread when it's internal status has changed. -// evt.GetInt() reflects the status at the time the message was sent, which may differ -// from the actual status. Typically listeners bound to this will want to use direct -// polling of the CoreThread rather than the belated status. -void Pcsx2App::OnCoreThreadStatus( wxCommandEvent& evt ) -{ - CoreThreadStatus status = (CoreThreadStatus)evt.GetInt(); - - switch( status ) - { - case CoreThread_Started: - case CoreThread_Reset: - case CoreThread_Stopped: - FpsManager.Reset(); - break; - - case CoreThread_Resumed: - case CoreThread_Suspended: - FpsManager.Resume(); - break; - } - - // Clear the sticky key statuses, because hell knows what'll change while the PAD - // plugin is suspended. - - m_kevt.m_shiftDown = false; - m_kevt.m_controlDown = false; - m_kevt.m_altDown = false; - - m_evtsrc_CoreThreadStatus.Dispatch( status ); - ScopedBusyCursor::SetDefault( Cursor_NotBusy ); - CoreThread.RethrowException(); -} - -void Pcsx2App::OnOpenModalDialog( wxCommandEvent& evt ) -{ - pxAssertDev( !evt.GetString().IsEmpty(), wxNullChar ); - - MsgboxEventResult* evtres = (MsgboxEventResult*)evt.GetClientData(); - - wxWindowID result = IssueDialogAsModal( evt.GetString() ); - - if( evtres != NULL ) - { - evtres->result = result; - evtres->WaitForMe.Post(); - } -} - -void Pcsx2App::OnOpenDialog_StuckThread( wxCommandEvent& evt ) -{ - if( !pxAssert( evt.GetClientData() != NULL ) ) return; - DoStuckThread( *(PersistentThread*)evt.GetClientData() ); -} - -int Pcsx2App::DoStuckThread( PersistentThread& stuck_thread ) +/*int Pcsx2App::DoStuckThread( PersistentThread& stuck_thread ) { if( !wxThread::IsMain() ) { @@ -336,59 +340,8 @@ int Pcsx2App::DoStuckThread( PersistentThread& stuck_thread ) pxStuckThreadEvent evt( stuck_thread ); return Msgbox::ShowModal( evt ); -} +}*/ -// Opens the specified standard dialog as a modal dialog, or forces the an existing -// instance of the dialog (ie, it's already open) to be modal. This is needed for -// items which are -int Pcsx2App::IssueDialogAsModal( const wxString& dlgName ) -{ - if( dlgName.IsEmpty() ) return wxID_CANCEL; - - if( !wxThread::IsMain() ) - { - MsgboxEventResult result; - PostCommand( &result, pxEvt_OpenModalDialog, 0, 0, dlgName ); - result.WaitForMe.WaitNoCancel(); - return result.result; - } - - if( wxWindow* window = wxFindWindowByName( dlgName ) ) - { - if( wxDialog* dialog = wxDynamicCast( window, wxDialog ) ) - { - window->SetFocus(); - - // It's legal to call ShowModal on a non-modal dialog, therefore making - // it modal in nature for the needs of whatever other thread of action wants - // to block against it: - - if( !dialog->IsModal() ) - { - int result = dialog->ShowModal(); - dialog->Destroy(); - return result; - } - } - } - else - { - using namespace Dialogs; - - if( dlgName == SysConfigDialog::GetNameStatic() ) - return SysConfigDialog().ShowModal(); - if( dlgName == AppConfigDialog::GetNameStatic() ) - return AppConfigDialog().ShowModal(); - if( dlgName == BiosSelectorDialog::GetNameStatic() ) - return BiosSelectorDialog().ShowModal(); - if( dlgName == LogOptionsDialog::GetNameStatic() ) - return LogOptionsDialog().ShowModal(); - if( dlgName == AboutBoxDialog::GetNameStatic() ) - return AboutBoxDialog().ShowModal(); - } - - return wxID_CANCEL; -} HashTools::HashMap GlobalAccels( 0, 0xffffffff ); @@ -446,7 +399,7 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& wxDialogWithHelpers dialog( NULL, _("PS2 BIOS Error"), wxVERTICAL ); dialog += dialog.Heading( ex.FormatDisplayMessage() + BIOS_GetMsg_Required() + _("\nPress Ok to go to the BIOS Configuration Panel.") ); dialog += new ModalButtonPanel( &dialog, MsgButtons().OKCancel() ); - + if( dialog.ShowModal() == wxID_CANCEL ) Console.Warning( "User denied option to re-configure BIOS." ); @@ -466,7 +419,7 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& } catch( Exception::PluginInitError& ex ) { - if( m_CorePlugins ) m_CorePlugins->Shutdown(); + CorePlugins.Shutdown(); Console.Error( ex.FormatDiagnosticMessage() ); if( !HandlePluginError( ex ) ) @@ -477,7 +430,7 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& } catch( Exception::PluginError& ex ) { - if( m_CorePlugins ) m_CorePlugins->Close(); + CorePlugins.Close(); Console.Error( ex.FormatDiagnosticMessage() ); if( !HandlePluginError( ex ) ) @@ -487,15 +440,16 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& } } // ---------------------------------------------------------------------------- + #if 0 catch( Exception::ThreadDeadlock& ex ) { // [TODO] Bind a listener to the CoreThread status, and automatically close the dialog // if the thread starts responding while we're waiting (not hard in fact, but I'm getting // a little tired, so maybe later!) --air - + Console.Warning( ex.FormatDiagnosticMessage() ); wxDialogWithHelpers dialog( NULL, _("PCSX2 Unresponsive Thread"), wxVERTICAL ); - + dialog += dialog.Heading( ex.FormatDisplayMessage() + L"\n\n" + pxE( ".Popup Error:Thread Deadlock Actions", L"'Ignore' to continue waiting for the thread to respond.\n" @@ -505,7 +459,7 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& ); int result = pxIssueConfirmation( dialog, MsgButtons().Ignore().Cancel().Custom( _("Terminate") ) ); - + if( result == pxID_CUSTOM ) { // fastest way to kill the process! (works in Linux and win32, thanks to windows having very @@ -522,6 +476,7 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& // Ignore does nothing... } + #endif // ---------------------------------------------------------------------------- catch( Exception::CancelEvent& ex ) { @@ -537,6 +492,27 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& } } +void Pcsx2App::StartPendingSave() +{ + if( PostAppMethodMyself(&Pcsx2App::StartPendingSave) ) return; + ++m_PendingSaves; +} + +void Pcsx2App::ClearPendingSave() +{ + if( PostAppMethodMyself(&Pcsx2App::StartPendingSave) ) return; + + --m_PendingSaves; + pxAssumeDev( m_PendingSaves >= 0, "Pending saves count mismatch (pending count is less than 0)" ); + + if( (m_PendingSaves == 0) && m_ScheduledTermination ) + { + Console.WriteLn( "App: All pending saves completed; exiting!" ); + Exit(); + } +} + + // Common exit handler which can be called from any event (though really it should // be called only from CloseWindow handlers since that's the more appropriate way // to handle cancelable window closures) @@ -545,10 +521,18 @@ void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& // the glorious user, whomever (s)he-it might be. void Pcsx2App::PrepForExit() { - CancelLoadingPlugins(); + SysExecutorThread.ShutdownQueue(); + //SysExecutorThread.Cancel(); DispatchEvent( AppStatus_Exiting ); + if( m_PendingSaves != 0 ) + { + Console.WriteLn( "App: Saves are pending; exit postponed..." ); + sApp.SetExitOnFrameDelete( false ); + return; + } + // This should be called by OnExit(), but sometimes wxWidgets fails to call OnExit(), so // do it here just in case (no harm anyway -- OnExit is the next logical step after // CloseWindow returns true from the TopLevel window). @@ -577,10 +561,11 @@ GSFrame& Pcsx2App::GetGsFrame() const return *gsFrame; } - void AppApplySettings( const AppConfig* oldconf ) { - AffinityAssert_AllowFromMain(); + AffinityAssert_AllowFrom_MainUI(); + + ScopedCoreThreadClose suspend_core; g_Conf->Folders.ApplyDefaults(); @@ -593,18 +578,8 @@ void AppApplySettings( const AppConfig* oldconf ) g_Conf->EmuOptions.BiosFilename = g_Conf->FullpathToBios(); - ScopedCoreThreadSuspend suspend_core; - - if( g_plugins != NULL ) - g_plugins->SetSettingsFolder( GetSettingsFolder().ToString() ); - RelocateLogfile(); - // Update the compression attribute on the Memcards folder. - // Memcards generally compress very well via NTFS compression. - - NTFS_CompressFile( g_Conf->Folders.MemoryCards.ToString(), g_Conf->McdEnableNTFS ); - if( (oldconf == NULL) || (oldconf->LanguageId != g_Conf->LanguageId) ) { wxDoNotLogInThisScope please; @@ -616,11 +591,27 @@ void AppApplySettings( const AppConfig* oldconf ) } } } + + CorePlugins.SetSettingsFolder( GetSettingsFolder().ToString() ); + // Update the compression attribute on the Memcards folder. + // Memcards generally compress very well via NTFS compression. + + NTFS_CompressFile( g_Conf->Folders.MemoryCards.ToString(), g_Conf->McdEnableNTFS ); sApp.DispatchEvent( AppStatus_SettingsApplied ); - suspend_core.Resume(); + + suspend_core.AllowResume(); } + +// -------------------------------------------------------------------------------------- +// pxDudConfig +// -------------------------------------------------------------------------------------- +// Used to handle config actions prior to the creation of the ini file (for example, the +// first time wizard). Attempts to save ini settings are simply ignored through this +// class, which allows us to give the user a way to set everything up in the wizard, apply +// settings as usual, and only *save* something once the whole wizard is complete. +// class pxDudConfig : public wxConfigBase { protected: @@ -691,16 +682,25 @@ AppIniLoader::AppIniLoader() void AppLoadSettings() { - if( !AffinityAssert_AllowFromMain() ) return; + if( wxGetApp().PostMethodMyself(AppLoadSettings) ) return; AppIniLoader loader; g_Conf->LoadSave( loader ); + + if( !wxFile::Exists( g_Conf->CurrentIso ) ) + g_Conf->CurrentIso.Clear(); + sApp.DispatchEvent( loader ); } void AppSaveSettings() { - if( !AffinityAssert_AllowFromMain() ) return; + if( wxGetApp().PostMethodMyself(AppSaveSettings) ) return; + + if( !wxFile::Exists( g_Conf->CurrentIso ) ) + g_Conf->CurrentIso.Clear(); + + sApp.GetRecentIsoManager().Add( g_Conf->CurrentIso ); AppIniSaver saver; g_Conf->LoadSave( saver ); @@ -709,7 +709,7 @@ void AppSaveSettings() // Invokes the specified Pcsx2App method, or posts the method to the main thread if the calling // thread is not Main. Action is blocking. For non-blocking method execution, use -// PostMethodToMainThread. +// PostAppMethodMyself. // // This function works something like setjmp/longjmp, in that the return value indicates if the // function actually executed the specified method or not. @@ -718,21 +718,20 @@ void AppSaveSettings() // FALSE if the method was not posted to the main thread (meaning this IS the main thread!) // TRUE if the method was posted. // -bool Pcsx2App::InvokeMethodOnMainThread( FnPtr_AppMethod method ) +bool Pcsx2App::InvokeOnMainThread( FnPtr_Pcsx2App method ) { if( wxThread::IsMain() ) return false; - Semaphore sem; - pxInvokeAppMethodEvent evt( method, sem ); - AddPendingEvent( evt ); - sem.Wait(); + SynchronousActionState sync; + PostEvent( Pcsx2AppMethodEvent( method, sync ) ); + sync.WaitForResult(); return true; } // Invokes the specified Pcsx2App method, or posts the method to the main thread if the calling // thread is not Main. Action is non-blocking. For blocking method execution, use -// InvokeMethodOnMainThread. +// InvokeOnMainThread. // // This function works something like setjmp/longjmp, in that the return value indicates if the // function actually executed the specified method or not. @@ -741,33 +740,31 @@ bool Pcsx2App::InvokeMethodOnMainThread( FnPtr_AppMethod method ) // FALSE if the method was not posted to the main thread (meaning this IS the main thread!) // TRUE if the method was posted. // -bool Pcsx2App::PostMethodToMainThread( FnPtr_AppMethod method ) +bool Pcsx2App::PostAppMethodMyself( FnPtr_Pcsx2App method ) { if( wxThread::IsMain() ) return false; - pxInvokeAppMethodEvent evt( method ); - AddPendingEvent( evt ); + PostEvent( Pcsx2AppMethodEvent( method ) ); return true; } // Posts a method to the main thread; non-blocking. Post occurs even when called from the // main thread. -void Pcsx2App::PostMethod( FnPtr_AppMethod method ) +void Pcsx2App::PostAppMethod( FnPtr_Pcsx2App method ) { - pxInvokeAppMethodEvent evt( method ); - AddPendingEvent( evt ); + PostEvent( Pcsx2AppMethodEvent( method ) ); } // Posts a method to the main thread; non-blocking. Post occurs even when called from the // main thread. -void Pcsx2App::PostIdleMethod( FnPtr_AppMethod method ) +void Pcsx2App::PostIdleAppMethod( FnPtr_Pcsx2App method ) { - pxInvokeAppMethodEvent evt( method ); - OnAddEventToIdleQueue( evt ); + Pcsx2AppMethodEvent evt( method ); + AddIdleEvent( evt ); } void Pcsx2App::OpenGsPanel() { - if( InvokeMethodOnMainThread( &Pcsx2App::OpenGsPanel ) ) return; + if( InvokeOnMainThread( &Pcsx2App::OpenGsPanel ) ) return; GSFrame* gsFrame = GetGsFramePtr(); if( gsFrame == NULL ) @@ -786,7 +783,7 @@ void Pcsx2App::OpenGsPanel() // Doing an immediate hide/show didn't work. So now I'm trying a resize. Because // wxWidgets is "clever" (grr!) it optimizes out just force-setting the same size // over again, so instead I resize it to size-1 and then back to the original size. - + const wxSize oldsize( gsFrame->GetSize() ); wxSize newsize( oldsize ); newsize.DecBy(1); @@ -794,8 +791,8 @@ void Pcsx2App::OpenGsPanel() gsFrame->SetSize( newsize ); gsFrame->SetSize( oldsize ); } - - pxAssumeDev( !GetPluginManager().IsOpen( PluginId_GS ), "GS Plugin must be closed prior to opening a new Gs Panel!" ); + + pxAssumeDev( !GetCorePlugins().IsOpen( PluginId_GS ), "GS Plugin must be closed prior to opening a new Gs Panel!" ); gsFrame->Show(); pDsp = (uptr)gsFrame->GetViewport()->GetHandle(); @@ -806,7 +803,7 @@ void Pcsx2App::OpenGsPanel() void Pcsx2App::CloseGsPanel() { - if( InvokeMethodOnMainThread( &Pcsx2App::CloseGsPanel ) ) return; + if( InvokeOnMainThread( &Pcsx2App::CloseGsPanel ) ) return; GSFrame* gsFrame = GetGsFramePtr(); if( (gsFrame != NULL) && CloseViewportWithPlugins ) @@ -838,45 +835,77 @@ void Pcsx2App::OnMainFrameClosed( wxWindowID id ) } // -------------------------------------------------------------------------------------- -// Sys/Core API and Shortcuts (for wxGetApp()) +// SysExecuteEvent // -------------------------------------------------------------------------------------- - -static int _sysexec_cdvdsrc_type = -1; -static wxString _sysexec_elf_override; - -static void _sendmsg_SysExecute() +class SysExecuteEvent : public SysExecEvent { - if( !CoreThread.AcquireResumeLock() ) +protected: + bool m_UseCDVDsrc; + CDVD_SourceType m_cdvdsrc_type; + wxString m_elf_override; + +public: + virtual ~SysExecuteEvent() throw() {} + SysExecuteEvent* Clone() const { return new SysExecuteEvent(*this); } + + wxString GetEventName() const { - DbgCon.WriteLn( "(SysExecute) another resume lock or message is already pending; no message posted." ); - return; + return L"SysExecute"; } - AppSaveSettings(); - wxGetApp().PostCommand( pxEvt_SysExecute, _sysexec_cdvdsrc_type ); -} + wxString GetEventMessage() const + { + return _("Executing PS2 Virtual Machine..."); + } + + SysExecuteEvent() + { + m_UseCDVDsrc = false; + } -static void OnSysExecuteAfterPlugins( const wxCommandEvent& loadevt ) -{ - if( (wxTheApp == NULL) || !((Pcsx2App*)wxTheApp)->m_CorePlugins ) return; - _sendmsg_SysExecute(); -} + SysExecuteEvent( const wxString& elf_override ) + : m_elf_override( elf_override ) + { + m_UseCDVDsrc = false; + } + + SysExecuteEvent( CDVD_SourceType srctype, const wxString& elf_override ) + : m_elf_override( elf_override ) + { + m_cdvdsrc_type = srctype; + m_UseCDVDsrc = true; + } + +protected: + void _DoInvoke() + { + wxGetApp().ProcessMethod( AppSaveSettings ); + + // 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( !CorePlugins.AreLoaded() ) return; + + DbgCon.WriteLn( Color_Gray, "(SysExecute) received." ); + + if( m_UseCDVDsrc ) CoreThread.Shutdown(); else CoreThread.Suspend(); + CDVDsys_SetFile( CDVDsrc_Iso, g_Conf->CurrentIso ); + if( m_UseCDVDsrc ) + CDVDsys_ChangeSource( m_cdvdsrc_type ); + else if( CDVD == NULL ) + CDVDsys_ChangeSource( CDVDsrc_NoDisc ); + + if( !CoreThread.HasValidState() ) + CoreThread.SetElfOverride( m_elf_override ); + + CoreThread.Resume(); + } +}; // Executes the emulator using a saved/existing virtual machine state and currently // configured CDVD source device. void Pcsx2App::SysExecute() { - _sysexec_cdvdsrc_type = -1; - _sysexec_elf_override = CoreThread.GetElfOverride(); - - if( !m_CorePlugins ) - { - LoadPluginsPassive( OnSysExecuteAfterPlugins ); - return; - } - - DbgCon.WriteLn( Color_Gray, "(SysExecute) Queuing request to re-execute existing VM state." ); - _sendmsg_SysExecute(); + SysExecutorThread.PostEvent( new SysExecuteEvent(CoreThread.GetElfOverride()) ); } // Executes the specified cdvd source and optional elf file. This command performs a @@ -884,59 +913,38 @@ void Pcsx2App::SysExecute() // sources. void Pcsx2App::SysExecute( CDVD_SourceType cdvdsrc, const wxString& elf_override ) { - _sysexec_cdvdsrc_type = (int)cdvdsrc; - _sysexec_elf_override = elf_override; - - if( !m_CorePlugins ) - { - LoadPluginsPassive( OnSysExecuteAfterPlugins ); - return; - } - - DbgCon.WriteLn( Color_Gray, "(SysExecute) Queuing request for new VM state." ); - _sendmsg_SysExecute(); + SysExecutorThread.PostEvent( new SysExecuteEvent(cdvdsrc, CoreThread.GetElfOverride()) ); } -// This message performs actual system execution (as dictated by SysExecute variants). -// It is implemented as a message handler so that it can be triggered in response to -// the completion of other dependent activities, namely loading plugins. -void Pcsx2App::OnSysExecute( wxCommandEvent& evt ) +// -------------------------------------------------------------------------------------- +// SysExecEvent_Shutdown +// -------------------------------------------------------------------------------------- +class SysExecEvent_Shutdown : public SysExecEvent { - CoreThread.ReleaseResumeLock(); - - if( sys_resume_lock > 0 ) +public: + wxString GetEventName() const { - Console.WriteLn( "SysExecute: State is locked, ignoring Execute request!" ); - return; + return L"SysShutdown"; + } + + wxString GetEventMessage() const + { + return _("Resetting PS2 virtual machine..."); } - // 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; +protected: + void _DoInvoke() + { + StateCopy_Clear(); + CoreThread.Shutdown(); + } - DbgCon.WriteLn( Color_Gray, "(MainThread) SysExecute received." ); - - 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() ); - else if( CDVD == NULL ) - CDVDsys_ChangeSource( CDVDsrc_NoDisc ); - - if( !CoreThread.HasValidState() ) - CoreThread.SetElfOverride( _sysexec_elf_override ); - - CoreThread.Resume(); -} +}; // Full system reset stops the core thread and unloads all core plugins *completely*. -void Pcsx2App::SysReset() +void Pcsx2App::SysShutdown() { - StateCopy_Clear(); - CoreThread.Reset(); - CoreThread.Cancel(); - CoreThread.ReleaseResumeLock(); - m_CorePlugins = NULL; + SysExecutorThread.PostEvent( new SysExecEvent_Shutdown() ); } // Returns true if there is a "valid" virtual machine state from the user's perspective. This @@ -1002,39 +1010,3 @@ SysCoreAllocations& GetSysCoreAlloc() { return *wxGetApp().m_CoreAllocs; } - -// -------------------------------------------------------------------------------------- -// pxStuckThreadEvent Implementation -// -------------------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS( pxStuckThreadEvent, BaseMessageBoxEvent ) - -pxStuckThreadEvent::pxStuckThreadEvent() - : BaseMessageBoxEvent() - , m_Thread( *(PersistentThread*)NULL ) -{ -} - -pxStuckThreadEvent::pxStuckThreadEvent( MsgboxEventResult& instdata, PersistentThread& thr ) - : BaseMessageBoxEvent( instdata, wxEmptyString ) - , m_Thread( thr ) -{ -} - -pxStuckThreadEvent::pxStuckThreadEvent( PersistentThread& thr ) - : BaseMessageBoxEvent() - , m_Thread( thr ) -{ -} - -pxStuckThreadEvent::pxStuckThreadEvent( const pxStuckThreadEvent& src ) - : BaseMessageBoxEvent( src ) - , m_Thread( src.m_Thread ) -{ -} - -int pxStuckThreadEvent::_DoDialog() const -{ - return 1; - //return Dialogs::StuckThreadDialog( m_Thread ).ShowModal(); -} - diff --git a/pcsx2/gui/AppSaveStates.h b/pcsx2/gui/AppSaveStates.h index cde64b4764..516442396c 100644 --- a/pcsx2/gui/AppSaveStates.h +++ b/pcsx2/gui/AppSaveStates.h @@ -15,63 +15,39 @@ #pragma once +#include "App.h" #include "SaveState.h" -enum SaveStateActionType -{ - SaveStateAction_CreateFinished, - SaveStateAction_RestoreFinished, - SaveStateAction_ZipToDiskFinished, - SaveStateAction_UnzipFromDiskFinished, -}; - // -------------------------------------------------------------------------------------- -// IEventListener_SaveStateThread +// SaveSinglePluginHelper // -------------------------------------------------------------------------------------- -class IEventListener_SaveStateThread : public IEventDispatcher +// A scoped convenience class for closing a single plugin and saving its state to memory. +// Emulation is suspended as needed, and is restored when the object leaves scope. Within +// the scope of the object, code is free to call plugin re-configurations or even unload +// a plugin entirely and re-load a different plugin in its place. +// +class SaveSinglePluginHelper { -public: - typedef SaveStateActionType EvtParams; - -public: - IEventListener_SaveStateThread() {} - virtual ~IEventListener_SaveStateThread() throw() {} - - virtual void DispatchEvent( const SaveStateActionType& status ) - { - switch( status ) - { - case SaveStateAction_CreateFinished: SaveStateAction_OnCreateFinished(); break; - case SaveStateAction_RestoreFinished: SaveStateAction_OnRestoreFinished(); break; - case SaveStateAction_ZipToDiskFinished: SaveStateAction_OnZipToDiskFinished(); break; - case SaveStateAction_UnzipFromDiskFinished: SaveStateAction_OnUnzipFromDiskFinished(); break; - - jNO_DEFAULT; - } - } - protected: - virtual void SaveStateAction_OnCreateFinished() {} - virtual void SaveStateAction_OnRestoreFinished() {} - virtual void SaveStateAction_OnZipToDiskFinished() {} - virtual void SaveStateAction_OnUnzipFromDiskFinished() {} + SafeArray m_plugstore; + bool m_validstate; + PluginsEnum_t m_pid; + + ScopedCoreThreadPause m_scoped_pause; + +public: + SaveSinglePluginHelper( PluginsEnum_t pid ); + virtual ~SaveSinglePluginHelper() throw(); }; -extern bool StateCopy_InvokeOnSaveComplete( IActionInvocation* sst ); -extern bool StateCopy_InvokeOnCopyComplete( IActionInvocation* sst ); +extern SafeArray& StateCopy_GetBuffer(); extern bool StateCopy_IsValid(); extern void StateCopy_FreezeToMem(); -extern void StateCopy_FreezeToMem_Blocking(); -extern void StateCopy_ThawFromMem_Blocking(); 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(); -extern bool StateCopy_IsBusy(); - - -extern const SafeArray* StateCopy_GetBuffer(); diff --git a/pcsx2/gui/ApplyState.h b/pcsx2/gui/ApplyState.h index 9fa0b5beea..2ba63763b5 100644 --- a/pcsx2/gui/ApplyState.h +++ b/pcsx2/gui/ApplyState.h @@ -179,5 +179,7 @@ public: ); virtual ~ApplicableWizardPage() throw() { m_ApplyState.DoCleanup(); } + + virtual bool PrepForApply(); }; diff --git a/pcsx2/gui/ConsoleLogger.h b/pcsx2/gui/ConsoleLogger.h index b5e216aad4..7e258ebfd0 100644 --- a/pcsx2/gui/ConsoleLogger.h +++ b/pcsx2/gui/ConsoleLogger.h @@ -101,7 +101,7 @@ protected: int m_win32_LinesPerPage; int m_win32_LinesPerScroll; #endif - bool m_IsPaused; + ScopedPtr m_IsPaused; bool m_FreezeWrites; public: @@ -217,7 +217,7 @@ protected: // Lock object for accessing or modifying the following three vars: // m_QueueBuffer, m_QueueColorSelection, m_CurQueuePos - MutexLockRecursive m_QueueLock; + MutexRecursive m_QueueLock; // Describes a series of colored text sections in the m_QueueBuffer. SafeList m_QueueColorSection; diff --git a/pcsx2/gui/Dialogs/FirstTimeWizard.cpp b/pcsx2/gui/Dialogs/FirstTimeWizard.cpp index cbaa075573..e47153b7ad 100644 --- a/pcsx2/gui/Dialogs/FirstTimeWizard.cpp +++ b/pcsx2/gui/Dialogs/FirstTimeWizard.cpp @@ -32,6 +32,15 @@ ApplicableWizardPage::ApplicableWizardPage( wxWizard* parent, wxWizardPage* prev { } +// This is a hack feature substitute for prioritized apply events. This callback is issued prior +// to the apply chain being run, allowing a panel to do some pre-apply prepwork. PAnels implementing +// this function should not modify the g_conf state. +bool ApplicableWizardPage::PrepForApply() +{ + return true; +} + + // ---------------------------------------------------------------------------- Panels::SettingsDirPickerPanel::SettingsDirPickerPanel( wxWindow* parent ) : DirPickerPanel( parent, FolderId_Settings, _("Settings"), _("Select a folder for PCSX2 settings") ) @@ -63,7 +72,7 @@ FirstTimeWizard::UsermodePage::UsermodePage( wxWizard* parent ) : m_dirpick_settings = new SettingsDirPickerPanel( &panel ); m_panel_LangSel = new LanguageSelectionPanel( &panel ); - m_panel_UserSel = new UsermodeSelectionPanel( &panel ); + m_panel_UserSel = new DocsFolderPickerPanel( &panel ); panel += panel.Heading(_("PCSX2 is starting from a new or unknown folder and needs to be configured.")); @@ -93,6 +102,37 @@ void FirstTimeWizard::UsermodePage::OnCustomDirChanged( wxCommandEvent& evt ) OnUsermodeChanged( evt ); } +bool FirstTimeWizard::UsermodePage::PrepForApply() +{ + wxDirName path( PathDefs::GetDocuments(m_panel_UserSel->GetDocsMode()) ); + + if( path.FileExists() ) + { + // FIXME: There's already a file by the same name.. not sure what we should do here. + throw Exception::BadStream( path.ToString(), + L"Targeted documents folder is already occupied by a file.", + pxE( "Error:DocsFolderFileConflict", + L"PCSX2 cannot create a documents folder in the requested location. " + L"The path name matches an existing file. Delete the file or change the documents location, " + L"and then try again." + ) + ); + } + + if( !path.Exists() ) + { + wxDialogWithHelpers dialog( NULL, _("Create folder?"), wxVERTICAL ); + dialog += dialog.Heading( _("PCSX2 will create the following folder for documents. You can change this setting later, at any time.") ); + dialog += 12; + dialog += dialog.Heading( path.ToString() ); + + if( wxID_CANCEL == pxIssueConfirmation( dialog, MsgButtons().Custom(_("Create")).Cancel(), L"CreateNewFolder" ) ) + return false; + } + path.Mkdir(); + return true; +} + // ---------------------------------------------------------------------------- FirstTimeWizard::FirstTimeWizard( wxWindow* parent ) : wxWizard( parent, wxID_ANY, _("PCSX2 First Time Configuration") ) @@ -141,6 +181,18 @@ FirstTimeWizard::~FirstTimeWizard() throw() } +static void _OpenConsole() +{ + g_Conf->ProgLogBox.Visible = true; + wxGetApp().OpenConsoleLog(); +} + +int FirstTimeWizard::ShowModal() +{ + if( IsDebugBuild ) wxGetApp().PostIdleMethod( _OpenConsole ); + return _parent::ShowModal(); +} + void FirstTimeWizard::OnDoubleClicked( wxCommandEvent& evt ) { wxWindow* forwardButton = FindWindow( wxID_FORWARD ); @@ -166,7 +218,7 @@ void FirstTimeWizard::OnPageChanging( wxWizardEvent& evt ) { if( ApplicableWizardPage* page = wxDynamicCast( GetCurrentPage(), ApplicableWizardPage ) ) { - if( !page->GetApplyState().ApplyAll() ) + if( !page->PrepForApply() || !page->GetApplyState().ApplyAll() ) { evt.Veto(); return; diff --git a/pcsx2/gui/Dialogs/ModalPopups.h b/pcsx2/gui/Dialogs/ModalPopups.h index ab4e39243e..1d13e6811f 100644 --- a/pcsx2/gui/Dialogs/ModalPopups.h +++ b/pcsx2/gui/Dialogs/ModalPopups.h @@ -26,16 +26,20 @@ static const wxWindowID pxID_CUSTOM = wxID_LOWEST - 1; class FirstTimeWizard : public wxWizard { + typedef wxWizard _parent; + protected: class UsermodePage : public ApplicableWizardPage { protected: Panels::DirPickerPanel* m_dirpick_settings; Panels::LanguageSelectionPanel* m_panel_LangSel; - Panels::UsermodeSelectionPanel* m_panel_UserSel; + Panels::DocsFolderPickerPanel* m_panel_UserSel; public: UsermodePage( wxWizard* parent ); + virtual ~UsermodePage() throw() { } + bool PrepForApply(); protected: void OnUsermodeChanged( wxCommandEvent& evt ); @@ -61,6 +65,8 @@ public: { m_panel_PluginSel.OnShown(); } + + int ShowModal(); protected: virtual void OnPageChanging( wxWizardEvent& evt ); @@ -88,7 +94,7 @@ namespace Dialogs class PickUserModeDialog : public BaseApplicableDialog { protected: - Panels::UsermodeSelectionPanel* m_panel_usersel; + Panels::DocsFolderPickerPanel* m_panel_usersel; Panels::LanguageSelectionPanel* m_panel_langsel; public: diff --git a/pcsx2/gui/Dialogs/PickUserModeDialog.cpp b/pcsx2/gui/Dialogs/PickUserModeDialog.cpp index 5d13a10b76..30bef6088d 100644 --- a/pcsx2/gui/Dialogs/PickUserModeDialog.cpp +++ b/pcsx2/gui/Dialogs/PickUserModeDialog.cpp @@ -23,7 +23,7 @@ using namespace Panels; Dialogs::PickUserModeDialog::PickUserModeDialog( wxWindow* parent ) : BaseApplicableDialog( parent, _("PCSX2 First Time configuration"), wxVERTICAL ) { - m_panel_usersel = new UsermodeSelectionPanel( this, false ); + m_panel_usersel = new DocsFolderPickerPanel( this, false ); m_panel_langsel = new LanguageSelectionPanel( this ); *this += new pxStaticHeading( this, _("PCSX2 is starting from a new or unknown folder and needs to be configured.") ); diff --git a/pcsx2/gui/ExecutorThread.cpp b/pcsx2/gui/ExecutorThread.cpp new file mode 100644 index 0000000000..2a3a1feeb8 --- /dev/null +++ b/pcsx2/gui/ExecutorThread.cpp @@ -0,0 +1,391 @@ +/* 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 "App.h" + + +// This is called from InvokeAction after various affinity and state checks have verified the +// message as executable. Override this when possible. Only override InvokeAction if you +// need some kind of additional low-level ability. +void SysExecEvent::_DoInvoke() +{ +} + +void SysExecEvent::SetException( BaseException* ex ) +{ + if( !ex ) return; + + const wxString& prefix( wxsFormat(L"(%s) ", GetEventName()) ); + ex->DiagMsg() = prefix + ex->DiagMsg(); + //ex->UserMsg() = prefix + ex->UserMsg(); + + if( m_sync ) + { + // Throws the exception inline with the message handler (this makes the exception + // look like it was thrown quite naturally). + m_sync->SetException( ex ); + } + else + { + // transport the exception to the main thread, since the message is fully + // asynchronous. Message is sent as a non-blocking action since proper handling + // of user errors on async messages is *usually* to log/ignore it (hah), or to + // suspend emulation and issue a dialog box to the user. + + wxGetApp().PostEvent( pxExceptionEvent( ex ) ); + } +} + +void SysExecEvent::SetException( const BaseException& ex ) +{ + SetException( ex.Clone() ); +} + + +// This method calls _DoInvoke after performing some setup and affinity checks. +// Override _DoInvoke instead, which is the intended method of implementing derived class invocation. +void SysExecEvent::InvokeAction() +{ + //pxAssumeDev( !IsBeingDeleted(), "Attempted to process a deleted SysExecutor event." ); + AffinityAssert_AllowFrom_SysExecutor(); + try { + _DoInvoke(); + } + catch( BaseException& ex ) + { + SetException( ex ); + } + catch( std::runtime_error& ex ) + { + SetException( new Exception::RuntimeError(ex) ); + } + + PostResult(); +} + +void SysExecEvent::PostResult() const +{ + if( m_sync ) m_sync->PostResult(); +} + +pxEvtHandler::pxEvtHandler() +{ + AtomicExchange( m_Quitting, false ); +} + +void pxEvtHandler::ShutdownQueue() +{ + if( m_Quitting ) return; + AtomicExchange( m_Quitting, true ); + m_wakeup.Post(); +} + +struct ScopedThreadCancelDisable +{ + ScopedThreadCancelDisable() + { + int oldstate; + pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldstate ); + } + + ~ScopedThreadCancelDisable() throw() + { + int oldstate; + pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldstate ); + } +}; + +void pxEvtHandler::ProcessPendingEvents() +{ + ScopedLock synclock( m_mtx_pending ); + + pxEvtList::iterator node; + while( node = m_pendingEvents.begin(), node != m_pendingEvents.end() ) + { + ScopedPtr deleteMe(wx_static_cast(SysExecEvent *, *node)); + + m_pendingEvents.erase( node ); + if( !m_Quitting || deleteMe->IsCriticalEvent() ) + { + // Some messages can be blocking, so we should release the mutex lock + // to avoid having cases where the main thread deadlocks simply trying + // to add a message to the queue. + + synclock.Release(); + + if( deleteMe->AllowCancelOnExit() ) + deleteMe->InvokeAction(); + else + { + ScopedThreadCancelDisable thr_cancel_scope; + deleteMe->InvokeAction(); + } + + synclock.Acquire(); + } + else + { + Console.WriteLn( L"(pxEvtHandler:Skipping Event) %s", deleteMe->GetEventName().c_str() ); + deleteMe->PostResult(); + } + } +} + +// This method is provided for wxWidgets API conformance. +void pxEvtHandler::AddPendingEvent( SysExecEvent& evt ) +{ + PostEvent( evt ); +} + +void pxEvtHandler::PostEvent( SysExecEvent* evt ) +{ + if( !evt ) return; + + if( m_Quitting ) + { + evt->PostResult(); + return; + } + + ScopedLock synclock( m_mtx_pending ); + + DbgCon.WriteLn( L"(%s) Posting event: %s (queue count=%d)", GetEventHandlerName().c_str(), evt->GetEventName().c_str(), m_pendingEvents.size() ); + + m_pendingEvents.push_back( evt ); + if( m_pendingEvents.size() == 1) + m_wakeup.Post(); +} + +void pxEvtHandler::PostEvent( const SysExecEvent& evt ) +{ + if( m_Quitting ) + { + evt.PostResult(); + return; + } + + ScopedLock synclock( m_mtx_pending ); + m_pendingEvents.push_back( evt.Clone() ); + if( m_pendingEvents.size() == 1) + m_wakeup.Post(); +} + +void pxEvtHandler::ProcessEvent( SysExecEvent& evt ) +{ + if( wxThread::GetCurrentId() != m_OwnerThreadId ) + { + SynchronousActionState sync; + evt.SetSyncState( sync ); + PostEvent( evt ); + sync.WaitForResult(); + } + else + evt.InvokeAction(); +} + +bool pxEvtHandler::SelfProcessMethod( FnType_Void* method ) +{ + if( wxThread::GetCurrentId() != m_OwnerThreadId ) + { + SynchronousActionState sync; + SysExecEvent_Method evt(method); + evt.SetSyncState( sync ); + PostEvent( evt ); + sync.WaitForResult(); + + return true; + } + + return false; +} + +void pxEvtHandler::ProcessEvent( SysExecEvent* evt ) +{ + if( !evt ) return; + + if( wxThread::GetCurrentId() != m_OwnerThreadId ) + { + SynchronousActionState sync; + evt->SetSyncState( sync ); + PostEvent( evt ); + sync.WaitForResult(); + } + else + { + ScopedPtr deleteMe( evt ); + deleteMe->InvokeAction(); + } +} + +void pxEvtHandler::Idle() +{ + DoIdle(); + m_wakeup.WaitWithoutYield(); +} + +void pxEvtHandler::SetActiveThread() +{ + m_OwnerThreadId = wxThread::GetCurrentId(); +} + +// -------------------------------------------------------------------------------------- +// WaitingForThreadedTaskDialog +// -------------------------------------------------------------------------------------- +class WaitingForThreadedTaskDialog + : public wxDialogWithHelpers +{ +private: + typedef wxDialogWithHelpers _parent; + +protected: + PersistentThread* m_thread; + +public: + WaitingForThreadedTaskDialog( PersistentThread* thr, wxWindow* parent, const wxString& title, const wxString& content ); + virtual ~WaitingForThreadedTaskDialog() throw() {} + +protected: + void OnCancel_Clicked( wxCommandEvent& evt ); + void OnTerminateApp_Clicked( wxCommandEvent& evt ); +}; + +// -------------------------------------------------------------------------------------- +// WaitingForThreadedTaskDialog Implementations +// -------------------------------------------------------------------------------------- +WaitingForThreadedTaskDialog::WaitingForThreadedTaskDialog( PersistentThread* thr, wxWindow* parent, const wxString& title, const wxString& content ) + : wxDialogWithHelpers( parent, title, wxVERTICAL ) +{ + m_thread = thr; + m_idealWidth = 500; + + *this += Heading( content ); + *this += 15; + *this += Heading( _("Press Cancel to attempt to cancel the action.") ); + *this += Heading( _("Press Terminate to kill PCSX2 immediately.") ); + + *this += new wxButton( this, wxID_CANCEL ); + *this += new wxButton( this, wxID_ANY, _("Terminate App") ); +} + +void WaitingForThreadedTaskDialog::OnCancel_Clicked( wxCommandEvent& evt ) +{ + evt.Skip(); + if( !m_thread ) return; + m_thread->Cancel( false ); + + if( wxWindow* cancel = FindWindowById( wxID_CANCEL ) ) cancel->Disable(); +} + +void WaitingForThreadedTaskDialog::OnTerminateApp_Clicked( wxCommandEvent& evt ) +{ + // (note: SIGTERM is a "handled" kill that performs shutdown stuff, which typically just crashes anyway) + wxKill( wxGetProcessId(), wxSIGKILL ); +} + +// -------------------------------------------------------------------------------------- +// ExecutorThread Implementations +// -------------------------------------------------------------------------------------- +ExecutorThread::ExecutorThread( pxEvtHandler* evthandler ) +{ + m_EvtHandler = evthandler; +} + +void ExecutorThread::ShutdownQueue() +{ + if( !m_EvtHandler || m_EvtHandler->IsShuttingDown() ) return; + m_EvtHandler->ShutdownQueue(); + Block(); +} + +void ExecutorThread::PostEvent( SysExecEvent* evt ) +{ + if( !pxAssert( m_EvtHandler ) ) return; + m_EvtHandler->PostEvent( evt ); +} + +void ExecutorThread::PostEvent( const SysExecEvent& evt ) +{ + if( !pxAssert( m_EvtHandler ) ) return; + m_EvtHandler->PostEvent( evt ); +} + +void ExecutorThread::ProcessEvent( SysExecEvent* evt ) +{ + if( m_EvtHandler ) + m_EvtHandler->ProcessEvent( evt ); + else + { + ScopedPtr deleteMe( evt ); + deleteMe->InvokeAction(); + } +} + +void ExecutorThread::ProcessEvent( SysExecEvent& evt ) +{ + if( m_EvtHandler ) + m_EvtHandler->ProcessEvent( evt ); + else + evt.InvokeAction(); +} + +void ExecutorThread::OnStart() +{ + //if( !m_ExecutorTimer ) + // m_ExecutorTimer = new wxTimer( wxTheApp, pxEvt_ThreadTaskTimeout_SysExec ); + + m_name = L"SysExecutor"; + _parent::OnStart(); +} + +void ExecutorThread::ExecuteTaskInThread() +{ + if( !pxAssertDev( m_EvtHandler, "Gimme a damn Event Handler first, object whore." ) ) return; + + m_EvtHandler->SetActiveThread(); + + while( true ) + { + if( !pxAssertDev( m_EvtHandler, "Event handler has been deallocated during SysExecutor thread execution." ) ) return; + + m_EvtHandler->Idle(); + m_EvtHandler->ProcessPendingEvents(); + if( m_EvtHandler->IsShuttingDown() ) break; + } +} + +void ExecutorThread::OnCleanupInThread() +{ + //wxGetApp().PostCommand( m_task, pxEvt_ThreadTaskComplete ); + _parent::OnCleanupInThread(); +} + +// -------------------------------------------------------------------------------------- +// Threaded Event Handlers (Pcsx2App) +// -------------------------------------------------------------------------------------- + +// This event is called when the SysExecutorThread's timer triggers, which means the +// VM/system task has taken an oddly long period of time to complete. The task is able +// to invoke a modal dialog from here that will grant the user some options for handling +// the unresponsive thread. +void Pcsx2App::OnSysExecutorTaskTimeout( wxTimerEvent& evt ) +{ + if( !SysExecutorThread.IsRunning() ) return; + + //BaseThreadedInvocation* task = SysExecutorThread.GetTask(); + //if( !task ) return; + + //task->ShowModalStatus(); +} diff --git a/pcsx2/gui/FrameForGS.cpp b/pcsx2/gui/FrameForGS.cpp index f779141368..cf1dec0c02 100644 --- a/pcsx2/gui/FrameForGS.cpp +++ b/pcsx2/gui/FrameForGS.cpp @@ -15,6 +15,8 @@ #include "PrecompiledHeader.h" #include "MainFrame.h" +#include "GSFrame.h" + #include "GS.h" #include "MSWstuff.h" @@ -295,6 +297,8 @@ void GSFrame::CoreThread_OnSuspended() // Could stop the timer outright here, tho no harm in having an occasional // update here or there, just in case some state info changes while emu is suspended. m_timer_UpdateTitle.Start( TitleBarUpdateMs ); + + if( g_Conf->GSWindow.CloseOnEsc ) Hide(); } // overrides base Show behavior. @@ -331,7 +335,7 @@ void GSFrame::AppStatusEvent_OnSettingsApplied() { if( IsBeingDeleted() ) return; ShowFullScreen( g_Conf->GSWindow.DefaultToFullscreen ); - Show( !g_Conf->GSWindow.CloseOnEsc || ((g_plugins==NULL) || !SysHasValidState()) ); + Show( !g_Conf->GSWindow.CloseOnEsc || !CorePlugins.IsOpen(PluginId_GS) || !SysHasValidState() ); if( wxStaticText* label = GetLabel_OutputDisabled() ) label->Show( !EmuConfig.GS.DisableOutput ); diff --git a/pcsx2/gui/GSFrame.h b/pcsx2/gui/GSFrame.h new file mode 100644 index 0000000000..ba7a2e166e --- /dev/null +++ b/pcsx2/gui/GSFrame.h @@ -0,0 +1,120 @@ + +/* 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 "App.h" +#include "CpuUsageProvider.h" + + +enum LimiterModeType +{ + Limit_Nominal, + Limit_Turbo, + Limit_Slomo, +}; + +extern LimiterModeType g_LimiterMode; + +// -------------------------------------------------------------------------------------- +// GSPanel +// -------------------------------------------------------------------------------------- +class GSPanel : public wxWindow, public EventListener_AppStatus +{ + typedef wxWindow _parent; + +protected: + AcceleratorDictionary m_Accels; + wxTimer m_HideMouseTimer; + bool m_CursorShown; + bool m_HasFocus; + +public: + GSPanel( wxWindow* parent ); + virtual ~GSPanel() throw(); + + void DoResize(); + void DoShowMouse(); + +protected: + void AppStatusEvent_OnSettingsApplied(); + +#ifdef __WXMSW__ + virtual WXLRESULT MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam); +#endif + + void InitDefaultAccelerators(); + + void OnCloseWindow( wxCloseEvent& evt ); + void OnResize(wxSizeEvent& event); + void OnShowMouse( wxMouseEvent& evt ); + void OnHideMouseTimeout( wxTimerEvent& evt ); + void OnKeyDown( wxKeyEvent& evt ); + void OnFocus( wxFocusEvent& evt ); + void OnFocusLost( wxFocusEvent& evt ); +}; + + +// -------------------------------------------------------------------------------------- +// GSFrame +// -------------------------------------------------------------------------------------- +class GSFrame : public wxFrame, + public EventListener_AppStatus, + public EventListener_CoreThread +{ + typedef wxFrame _parent; + +protected: + wxTimer m_timer_UpdateTitle; + wxWindowID m_id_gspanel; + wxWindowID m_id_OutputDisabled; + wxStaticText* m_label_Disabled; + wxStatusBar* m_statusbar; + + CpuUsageProvider m_CpuUsage; + +public: + GSFrame(wxWindow* parent, const wxString& title); + virtual ~GSFrame() throw(); + + GSPanel* GetViewport(); + void SetFocus(); + bool Show( bool shown=true ); + wxStaticText* GetLabel_OutputDisabled() const; + +protected: + void OnCloseWindow( wxCloseEvent& evt ); + void OnMove( wxMoveEvent& evt ); + void OnResize( wxSizeEvent& evt ); + void OnActivate( wxActivateEvent& evt ); + void OnUpdateTitle( wxTimerEvent& evt ); + + void AppStatusEvent_OnSettingsApplied(); + void CoreThread_OnResumed(); + void CoreThread_OnSuspended(); +}; + +// -------------------------------------------------------------------------------------- +// s* macros! ['s' stands for 'shortcut'] +// -------------------------------------------------------------------------------------- +// Use these for "silent fail" invocation of PCSX2 Application-related constructs. If the +// construct (albeit wxApp, MainFrame, CoreThread, etc) is null, the requested method will +// not be invoked, and an optional "else" clause can be affixed for handling the end case. +// +// See App.h (sApp) for more details. +// +#define sGSFrame \ + if( GSFrame* __gsframe_ = wxGetApp().GetGsFramePtr() ) (*__gsframe_) diff --git a/pcsx2/gui/GlobalCommands.cpp b/pcsx2/gui/GlobalCommands.cpp index 352ba38eea..e828c67c51 100644 --- a/pcsx2/gui/GlobalCommands.cpp +++ b/pcsx2/gui/GlobalCommands.cpp @@ -15,6 +15,8 @@ #include "PrecompiledHeader.h" #include "MainFrame.h" +#include "GSFrame.h" + #include "HostGui.h" #include "AppSaveStates.h" #include "GS.h" @@ -91,7 +93,7 @@ namespace Implementations g_Conf->EmuOptions.GS.LimitScalar = g_Conf->Framerate.TurboScalar; Console.WriteLn("(FrameLimiter) Turbo ENABLED." ); } - pauser.Resume(); + pauser.AllowResume(); } void Framelimiter_SlomoToggle() @@ -117,7 +119,7 @@ namespace Implementations Console.WriteLn("(FrameLimiter) SlowMotion ENABLED." ); g_Conf->EmuOptions.GS.FrameLimitEnable = true; } - pauser.Resume(); + pauser.AllowResume(); } void Framelimiter_MasterToggle() @@ -126,7 +128,7 @@ namespace Implementations g_Conf->EmuOptions.GS.FrameLimitEnable = !g_Conf->EmuOptions.GS.FrameLimitEnable; GSsetVsync( g_Conf->EmuOptions.GS.FrameLimitEnable && g_Conf->EmuOptions.GS.VsyncEnable ); Console.WriteLn("(FrameLimiter) %s.", g_Conf->EmuOptions.GS.FrameLimitEnable ? "ENABLED" : "DISABLED" ); - pauser.Resume(); + pauser.AllowResume(); } void Sys_Suspend() @@ -142,13 +144,11 @@ namespace Implementations void Sys_TakeSnapshot() { - GSmakeSnapshot( g_Conf->Folders.Snapshots.ToAscii().data() ); + GSmakeSnapshot( g_Conf->Folders.Snapshots.ToAscii() ); } void Sys_RenderToggle() { - if( g_plugins == NULL ) return; - SaveSinglePluginHelper helper( PluginId_GS ); renderswitch = !renderswitch; } diff --git a/pcsx2/gui/IsoDropTarget.cpp b/pcsx2/gui/IsoDropTarget.cpp index b82269d18f..ba775b103a 100644 --- a/pcsx2/gui/IsoDropTarget.cpp +++ b/pcsx2/gui/IsoDropTarget.cpp @@ -15,6 +15,7 @@ #include "PrecompiledHeader.h" #include "App.h" +#include "MainFrame.h" #include "IsoDropTarget.h" #include "Dialogs/ModalPopups.h" @@ -34,7 +35,7 @@ wxString GetMsg_ConfirmSysReset() bool IsoDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) { - ScopedCoreThreadSuspend stopped_core; + ScopedCoreThreadPopup stopped_core; if( filenames.GetCount() > 1 ) { @@ -53,7 +54,7 @@ bool IsoDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filen wxFileInputStream filechk( filenames[0] ); if( !filechk.IsOk() ) - throw Exception::CreateStream( filenames[0] ); + throw Exception::CannotCreateStream( filenames[0] ); u8 ident[16]; filechk.Read( ident, 16 ); @@ -82,8 +83,9 @@ bool IsoDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filen { sApp.SysExecute( g_Conf->CdvdSource, g_Conf->CurrentELF ); } + else + stopped_core.AllowResume(); - stopped_core.Resume(); return true; } } @@ -97,45 +99,18 @@ bool IsoDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filen // hack ;) isoFile iso; - memzero( iso ); - iso.handle = _openfile( filenames[0].ToUTF8(), O_RDONLY); + memzero(iso); + iso.handle = _openfile(filenames[0].ToUTF8(), O_RDONLY); if( iso.handle == NULL ) - throw Exception::CreateStream( filenames[0] ); + throw Exception::CannotCreateStream( filenames[0] ); if (isoDetect(&iso)) { Console.WriteLn( L"(Drag&Drop) Found valid ISO file type!" ); - - wxWindowID result = wxID_RESET; - - if( SysHasValidState() ) - { - wxDialogWithHelpers dialog( m_WindowBound, _("Confirm PS2 Reset"), wxVERTICAL ); - - dialog += dialog.Heading(_("You have dropped the following ISO image into PCSX2:\n\n") + - filenames[0] + L"\n\n" + - _("Do you want to swap discs or boot the new image (via system reset)?") - ); - - result = pxIssueConfirmation( dialog, MsgButtons().Reset().Cancel().Custom(_("Swap Disc")), L"DragDrop:BootIso" ); - } - - if( result != wxID_CANCEL ) - { - SysUpdateIsoSrcFile( filenames[0] ); - if( result != wxID_RESET ) - { - CoreThread.ChangeCdvdSource( CDVDsrc_Iso ); - } - else - { - sApp.SysExecute( CDVDsrc_Iso ); - } - } + SwapOrReset_Iso(m_WindowBound, stopped_core, filenames[0], _("You have dropped the following ISO image into PCSX2:\n\n")); } _closefile( iso.handle ); - stopped_core.Resume(); return true; } diff --git a/pcsx2/gui/IsoDropTarget.h b/pcsx2/gui/IsoDropTarget.h index 64d256ef5f..0fa1cf4636 100644 --- a/pcsx2/gui/IsoDropTarget.h +++ b/pcsx2/gui/IsoDropTarget.h @@ -34,3 +34,7 @@ public: virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames); }; + + +extern wxString GetMsg_ConfirmSysReset(); +extern wxWindowID SwapOrReset_Iso( wxWindow* owner, IScopedCoreThread& core_control, const wxString& isoFilename, const wxString& descpart1 ); diff --git a/pcsx2/gui/MSWstuff.cpp b/pcsx2/gui/MSWstuff.cpp index 419d7d1aa6..36569fe683 100644 --- a/pcsx2/gui/MSWstuff.cpp +++ b/pcsx2/gui/MSWstuff.cpp @@ -14,7 +14,8 @@ */ #include "PrecompiledHeader.h" -#include "MainFrame.h" +#include "GSFrame.h" + #include "MSWstuff.h" #include #include diff --git a/pcsx2/gui/MainFrame.cpp b/pcsx2/gui/MainFrame.cpp index 749c67c37a..cdfda44c95 100644 --- a/pcsx2/gui/MainFrame.cpp +++ b/pcsx2/gui/MainFrame.cpp @@ -92,15 +92,16 @@ void MainEmuFrame::OnCloseWindow(wxCloseEvent& evt) //evt.Veto( true ); - if( StateCopy_InvokeOnSaveComplete( new InvokeAction_MenuCommand( MenuId_Exit ) ) ) return; } - m_menuCDVD.Remove( MenuId_IsoSelector ); - //m_menuCDVD.Delete( MenuId_IsoSelector ); - wxGetApp().PrepForExit(); sApp.OnMainFrameClosed( GetId() ); + if( m_menubar.FindItem(MenuId_IsoSelector) ) + m_menuCDVD.Remove(MenuId_IsoSelector); + + RemoveEventHandler( &wxGetApp().GetRecentIsoManager() ); + evt.Skip(); } @@ -169,14 +170,14 @@ void MainEmuFrame::ConnectMenus() ConnectMenu( MenuId_PluginBase_Settings + (i*PluginMenuId_Interval), Menu_ConfigPlugin_Click); ConnectMenu( MenuId_Boot_CDVD, Menu_BootCdvd_Click ); + ConnectMenu( MenuId_Boot_CDVD2, Menu_BootCdvd2_Click ); ConnectMenu( MenuId_Boot_ELF, Menu_OpenELF_Click ); ConnectMenu( MenuId_IsoBrowse, Menu_IsoBrowse_Click ); - ConnectMenu( MenuId_SkipBiosToggle, Menu_SkipBiosToggle_Click ); ConnectMenu( MenuId_EnablePatches, Menu_EnablePatches_Click ); ConnectMenu( MenuId_Exit, Menu_Exit_Click ); ConnectMenu( MenuId_Sys_SuspendResume, Menu_SuspendResume_Click ); - ConnectMenu( MenuId_Sys_Reset, Menu_SysReset_Click ); + ConnectMenu( MenuId_Sys_Restart, Menu_SysReset_Click ); ConnectMenu( MenuId_Sys_Shutdown, Menu_SysShutdown_Click ); ConnectMenu( MenuId_State_LoadOther, Menu_LoadStateOther_Click ); @@ -229,14 +230,12 @@ void MainEmuFrame::DispatchEvent( const PluginEventType& plugin_evt ) } else if( plugin_evt == CorePlugins_Loaded ) { - if( !pxAssertDev( g_plugins!=NULL, wxNullChar ) ) return; - for( int i=0; iGetName( PluginId_CDVD ).c_str() ) ); + // GetCorePlugins().GetName( PluginId_CDVD ).c_str() ) ); } } @@ -304,9 +303,9 @@ MainEmuFrame::MainEmuFrame(wxWindow* parent, const wxString& title) // Initial menubar setup. This needs to be done first so that the menu bar's visible size // can be factored into the window size (which ends up being background+status+menus) - m_menubar.Append( &m_menuBoot, _("&Boot") ); - m_menubar.Append( &m_menuCDVD, _("CD&VD") ); + //m_menubar.Append( &m_menuBoot, _("&Boot") ); m_menubar.Append( &m_menuSys, _("&System") ); + m_menubar.Append( &m_menuCDVD, _("CD&VD") ); m_menubar.Append( &m_menuConfig, _("&Config") ); m_menubar.Append( &m_menuMisc, _("&Misc") ); #ifdef PCSX2_DEVBUILD @@ -364,37 +363,18 @@ MainEmuFrame::MainEmuFrame(wxWindow* parent, const wxString& title) InitLogBoxPosition( g_Conf->ProgLogBox ); // ------------------------------------------------------------------------ + // Some of the items in the System menu are configured by the UpdateCoreStatus() method. + + m_menuSys.Append(MenuId_Boot_CDVD, _("Initializing...")); - m_menuBoot.Append(MenuId_Boot_CDVD, _("Run CDVD"), - _("For booting DVD discs or Isos, depending on the configured CDVD source.")); + m_menuSys.Append(MenuId_Boot_CDVD2, _("Initializing...")); - m_menuBoot.Append(MenuId_Boot_ELF, _("Run ELF File..."), - _("For running raw binaries directly")); - - m_menuBoot.AppendSeparator(); - m_menuBoot.Append(MenuId_Exit, _("Exit"), - _("Closing PCSX2 may be hazardous to your health")); - - // ------------------------------------------------------------------------ - wxMenu& isoRecents( wxGetApp().GetRecentIsoMenu() ); - - //m_menuCDVD.AppendSeparator(); - m_menuCDVD.Append( MenuId_IsoSelector, _("Iso Selector"), &isoRecents ); - m_menuCDVD.Append( GetPluginMenuId_Settings(PluginId_CDVD), _("Plugin Menu"), m_PluginMenuPacks[PluginId_CDVD] ); - - m_menuCDVD.AppendSeparator(); - m_menuCDVD.Append( MenuId_Src_Iso, _("Iso"), _("Makes the specified ISO image the CDVD source."), wxITEM_RADIO ); - m_menuCDVD.Append( MenuId_Src_Plugin, _("Plugin"), _("Uses an external plugin as the CDVD source."), wxITEM_RADIO ); - m_menuCDVD.Append( MenuId_Src_NoDisc, _("No disc"), _("Use this to boot into your virtual PS2's BIOS configuration."), wxITEM_RADIO ); - - m_menuCDVD.AppendSeparator(); - m_menuCDVD.Append( MenuId_SkipBiosToggle,_("Enable Skip BIOS Hack"), - _("Skips PS2 splash screens when booting from Iso or CDVD media"), wxITEM_CHECK ); - - // ------------------------------------------------------------------------ - m_menuSys.Append(MenuId_Sys_SuspendResume, _("Suspend") )->Enable( SysHasValidState() ); + m_menuSys.Append(MenuId_Boot_ELF, _("Run ELF..."), + _("For running raw PS2 binaries directly")); m_menuSys.AppendSeparator(); + m_menuSys.Append(MenuId_Sys_SuspendResume, _("Initializing...")); + m_menuSys.AppendSeparator(); //m_menuSys.Append(MenuId_Sys_Close, _("Close"), // _("Stops emulation and closes the GS window.")); @@ -408,12 +388,30 @@ MainEmuFrame::MainEmuFrame(wxWindow* parent, const wxString& title) wxEmptyString, wxITEM_CHECK); m_menuSys.AppendSeparator(); - m_menuSys.Append(MenuId_Sys_Reset, _("Reset"), - _("Resets the VM state and re-runs current CDVD image.")); m_menuSys.Append(MenuId_Sys_Shutdown, _("Shutdown"), _("Wipes all internal VM states and shuts down plugins.")); + m_menuSys.Append(MenuId_Exit, _("Exit"), + _("Closing PCSX2 may be hazardous to your health")); + + + // ------------------------------------------------------------------------ + wxMenu& isoRecents( wxGetApp().GetRecentIsoMenu() ); + + //m_menuCDVD.AppendSeparator(); + m_menuCDVD.Append( MenuId_IsoSelector, _("Iso Selector"), &isoRecents ); + m_menuCDVD.Append( GetPluginMenuId_Settings(PluginId_CDVD), _("Plugin Menu"), m_PluginMenuPacks[PluginId_CDVD] ); + + m_menuCDVD.AppendSeparator(); + m_menuCDVD.Append( MenuId_Src_Iso, _("Iso"), _("Makes the specified ISO image the CDVD source."), wxITEM_RADIO ); + m_menuCDVD.Append( MenuId_Src_Plugin, _("Plugin"), _("Uses an external plugin as the CDVD source."), wxITEM_RADIO ); + m_menuCDVD.Append( MenuId_Src_NoDisc, _("No disc"), _("Use this to boot into your virtual PS2's BIOS configuration."), wxITEM_RADIO ); + + //m_menuCDVD.AppendSeparator(); + //m_menuCDVD.Append( MenuId_SkipBiosToggle,_("Enable BOOT2 injection"), + // _("Skips PS2 splash screens when booting from Iso or DVD media"), wxITEM_CHECK ); + // ------------------------------------------------------------------------ m_menuConfig.Append(MenuId_Config_SysSettings, _("Emulation &Settings") ); @@ -478,7 +476,9 @@ MainEmuFrame::MainEmuFrame(wxWindow* parent, const wxString& title) Connect( wxEVT_ACTIVATE, wxActivateEventHandler (MainEmuFrame::OnActivate) ); + PushEventHandler( &wxGetApp().GetRecentIsoManager() ); SetDropTarget( new IsoDropTarget( this ) ); + ApplyCoreStatus(); } MainEmuFrame::~MainEmuFrame() throw() @@ -486,7 +486,7 @@ MainEmuFrame::~MainEmuFrame() throw() if( m_RestartEmuOnDelete ) { sApp.SetExitOnFrameDelete( false ); - sApp.PostMethod( &Pcsx2App::DetectCpuAndUserMode ); + sApp.PostAppMethod( &Pcsx2App::DetectCpuAndUserMode ); sApp.WipeUserModeSettings(); } } @@ -520,38 +520,71 @@ void MainEmuFrame::ApplyCoreStatus() { wxMenuBar& menubar( *GetMenuBar() ); - wxMenuItem& susres( *menubar.FindItem( MenuId_Sys_SuspendResume ) ); - if( !pxAssertMsg( &susres!=NULL, "Suspend/Resume Menubar Item is NULL!" ) ) return; + wxMenuItem& susres (*menubar.FindItem( MenuId_Sys_SuspendResume )); + wxMenuItem& cdvd (*menubar.FindItem( MenuId_Boot_CDVD )); + wxMenuItem& cdvd2 (*menubar.FindItem( MenuId_Boot_CDVD2 )); + + if( !pxAssertMsg( (&susres) && (&cdvd) && (&cdvd2), "Unexpected NULL Menubar Item!" ) ) return; + + wxMenuItem* restart = menubar.FindItem( MenuId_Sys_Restart ); if( SysHasValidState() ) { susres.Enable(); if( CoreThread.IsOpen() ) { - susres.SetHelp( _("Safely pauses emulation and preserves the PS2 state.") ); - susres.SetText( _("Suspend") ); + susres.SetText(_("Suspend")); + susres.SetHelp(_("Safely pauses emulation and preserves the PS2 state.")); } else { - susres.SetHelp( _("Resumes the suspended emulation state.") ); - susres.SetText( _("Resume") ); + susres.SetText(_("Resume")); + susres.SetHelp(_("Resumes the suspended emulation state.")); } + + if( restart ) + { + restart->SetText(_("Restart")); + restart->SetHelp(_("Simulates hardware reset of the PS2 virtual machine.")); + } + + cdvd.SetText(_("Reboot CDVD (full)")); + cdvd.SetHelp(_("Hard reset of the active VM.")); + + cdvd2.SetText(_("Reboot CDVD (fast)")); + cdvd2.SetHelp(_("Reboot using BOOT2 injection (skips splash screens)")); + } else { susres.Enable( false ); + susres.SetText(_("Suspend/Resume")); susres.SetHelp( _("No emulation state is active; cannot suspend or resume.") ); + + if( restart ) + { + restart->Enable( false ); + restart->SetHelp( _("No emulation state is active; boot something first.") ); + } + + cdvd.SetText(_("Boot CDVD (full)")); + cdvd.SetHelp(_("Boot the VM using the current DVD or Iso source media")); + + cdvd2.SetText(_("Boot CDVD (fast)")); + cdvd2.SetHelp(_("Use BOOT2 injection to skip PS2 startup and splash screens")); + + // Old style... + //restart.SetHelp(_("Starts execution of the PS2 virtual machine.")); + //restart.SetText(_("Start")); } - menubar.Enable( MenuId_Sys_Reset, true ); - menubar.Enable( MenuId_Sys_Shutdown, SysHasValidState() || (g_plugins!=NULL) ); + menubar.Enable( MenuId_Sys_Shutdown, SysHasValidState() || CorePlugins.AreAnyInitialized() ); } void MainEmuFrame::ApplySettings() { wxMenuBar& menubar( *GetMenuBar() ); - menubar.Check( MenuId_SkipBiosToggle, g_Conf->EmuOptions.SkipBiosSplash ); menubar.Check( MenuId_EnablePatches, g_Conf->EmuOptions.EnablePatches ); menubar.Check( MenuId_CDVD_Info, g_Conf->EmuOptions.CdvdVerboseReads ); #ifdef __LINUX__ @@ -615,8 +648,9 @@ void PerPluginMenuInfo::OnUnloaded() void PerPluginMenuInfo::OnLoaded() { + if( !CorePlugins.IsLoaded(PluginId) ) return; MyMenu.SetLabel( GetPluginMenuId_Name(PluginId), - g_plugins->GetName( PluginId ) + L" " + g_plugins->GetVersion( PluginId ) + CorePlugins.GetName( PluginId ) + L" " + CorePlugins.GetVersion( PluginId ) ); MyMenu.Enable( GetPluginMenuId_Settings(PluginId), true ); } diff --git a/pcsx2/gui/MainFrame.h b/pcsx2/gui/MainFrame.h index e7350b591c..85b8a4c829 100644 --- a/pcsx2/gui/MainFrame.h +++ b/pcsx2/gui/MainFrame.h @@ -15,99 +15,11 @@ #pragma once -#include -#include -#include - #include "App.h" #include "AppSaveStates.h" -#include "CpuUsageProvider.h" -enum LimiterModeType -{ - Limit_Nominal, - Limit_Turbo, - Limit_Slomo, -}; - -extern LimiterModeType g_LimiterMode; - -// -------------------------------------------------------------------------------------- -// GSPanel -// -------------------------------------------------------------------------------------- -class GSPanel : public wxWindow, public EventListener_AppStatus -{ - typedef wxWindow _parent; - -protected: - AcceleratorDictionary m_Accels; - wxTimer m_HideMouseTimer; - bool m_CursorShown; - bool m_HasFocus; - -public: - GSPanel( wxWindow* parent ); - virtual ~GSPanel() throw(); - - void DoResize(); - void DoShowMouse(); - -protected: - void AppStatusEvent_OnSettingsApplied(); - -#ifdef __WXMSW__ - virtual WXLRESULT MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam); -#endif - - void InitDefaultAccelerators(); - - void OnCloseWindow( wxCloseEvent& evt ); - void OnResize(wxSizeEvent& event); - void OnShowMouse( wxMouseEvent& evt ); - void OnHideMouseTimeout( wxTimerEvent& evt ); - void OnKeyDown( wxKeyEvent& evt ); - void OnFocus( wxFocusEvent& evt ); - void OnFocusLost( wxFocusEvent& evt ); -}; - -// -------------------------------------------------------------------------------------- -// GSFrame -// -------------------------------------------------------------------------------------- -class GSFrame : public wxFrame, - public EventListener_AppStatus, - public EventListener_CoreThread -{ - typedef wxFrame _parent; - -protected: - wxTimer m_timer_UpdateTitle; - wxWindowID m_id_gspanel; - wxWindowID m_id_OutputDisabled; - wxStaticText* m_label_Disabled; - wxStatusBar* m_statusbar; - - CpuUsageProvider m_CpuUsage; - -public: - GSFrame(wxWindow* parent, const wxString& title); - virtual ~GSFrame() throw(); - - GSPanel* GetViewport(); - void SetFocus(); - bool Show( bool shown=true ); - wxStaticText* GetLabel_OutputDisabled() const; - -protected: - void OnCloseWindow( wxCloseEvent& evt ); - void OnMove( wxMoveEvent& evt ); - void OnResize( wxSizeEvent& evt ); - void OnActivate( wxActivateEvent& evt ); - void OnUpdateTitle( wxTimerEvent& evt ); - - void AppStatusEvent_OnSettingsApplied(); - void CoreThread_OnResumed(); - void CoreThread_OnSuspended(); -}; +#include +#include struct PluginMenuAddition { @@ -162,18 +74,25 @@ public: operator const wxMenu*() const { return &MyMenu; } }; -class InvokeAction_MenuCommand : public IActionInvocation +// -------------------------------------------------------------------------------------- +// InvokeMenuCommand_OnSysStateUnlocked +// -------------------------------------------------------------------------------------- +class InvokeMenuCommand_OnSysStateUnlocked + : public IEventListener_SysState + , public BaseDeletableObject { protected: MenuIdentifiers m_menu_cmd; public: - InvokeAction_MenuCommand( MenuIdentifiers menu_command ) + InvokeMenuCommand_OnSysStateUnlocked( MenuIdentifiers menu_command ) { m_menu_cmd = menu_command; } + + virtual ~InvokeMenuCommand_OnSysStateUnlocked() throw() {} - virtual void InvokeAction() + virtual void SaveStateAction_OnCreateFinished() { wxGetApp().PostMenuAction( m_menu_cmd ); } @@ -188,10 +107,6 @@ class MainEmuFrame : public wxFrame, public EventListener_AppStatus { protected: -// EventListenerHelper_Plugins m_listener_plugins; -// EventListenerHelper_CoreThread m_listener_corethread; -// EventListenerHelper_AppStatus m_listener_appstatus; - bool m_RestartEmuOnDelete; wxStatusBar& m_statusbar; @@ -248,10 +163,10 @@ protected: void Menu_ResetAllSettings_Click(wxCommandEvent &event); void Menu_IsoBrowse_Click(wxCommandEvent &event); - void Menu_SkipBiosToggle_Click(wxCommandEvent &event); void Menu_EnablePatches_Click(wxCommandEvent &event); void Menu_BootCdvd_Click(wxCommandEvent &event); + void Menu_BootCdvd2_Click(wxCommandEvent &event); void Menu_OpenELF_Click(wxCommandEvent &event); void Menu_CdvdSource_Click(wxCommandEvent &event); void Menu_LoadStates_Click(wxCommandEvent &event); @@ -277,6 +192,7 @@ protected: void Menu_PrintCDVD_Info(wxCommandEvent &event); void Menu_ShowAboutBox(wxCommandEvent &event); + void _DoBootCdvd(); bool _DoSelectIsoBrowser( wxString& dest ); bool _DoSelectELFBrowser(); diff --git a/pcsx2/gui/MainMenuClicks.cpp b/pcsx2/gui/MainMenuClicks.cpp index 2ab84b9bcb..6990d30360 100644 --- a/pcsx2/gui/MainMenuClicks.cpp +++ b/pcsx2/gui/MainMenuClicks.cpp @@ -25,11 +25,10 @@ #include "Dialogs/LogOptionsDialog.h" #include "IniInterface.h" +#include "IsoDropTarget.h" using namespace Dialogs; -extern wxString GetMsg_ConfirmSysReset(); - void MainEmuFrame::SaveEmuOptions() { if (wxConfigBase* conf = GetAppConfig()) @@ -57,7 +56,6 @@ void MainEmuFrame::Menu_SelectBios_Click(wxCommandEvent &event) static void WipeSettings() { - UnloadPlugins(); wxGetApp().CleanupRestartable(); wxGetApp().CleanupResources(); @@ -80,53 +78,31 @@ void MainEmuFrame::RemoveCdvdMenu() m_menuCDVD.Remove( item ); } - -class RestartEverything_WhenCoreThreadStops : public EventListener_CoreThread, - public virtual IDeletableObject -{ -public: - RestartEverything_WhenCoreThreadStops() {} - virtual ~RestartEverything_WhenCoreThreadStops() throw() {} - -protected: - virtual void CoreThread_OnStopped() - { - wxGetApp().DeleteObject( this ); - WipeSettings(); - } -}; - -class CancelCoreThread_WhenSaveStateDone : - public EventListener_CoreThread, - public IDeletableObject -{ -public: - virtual ~CancelCoreThread_WhenSaveStateDone() throw() {} - - void CoreThread_OnResumed() - { - wxGetApp().DeleteObject( this ); - CoreThread.Cancel(); - } -}; - - void MainEmuFrame::Menu_ResetAllSettings_Click(wxCommandEvent &event) { if( IsBeingDeleted() || m_RestartEmuOnDelete ) return; - ScopedCoreThreadSuspend suspender; - if( !Msgbox::OkCancel( - pxE( ".Popup Warning:DeleteSettings", - L"WARNING!! This option will delete *ALL* settings for PCSX2 and force PCSX2 to restart, losing any current emulation progress. Are you absolutely sure?" - L"\n\n(note: settings for plugins are unaffected)" - ), - _("Reset all settings?") ) ) { - suspender.Resume(); - return; + ScopedCoreThreadPopup suspender; + if( !Msgbox::OkCancel( + pxE( ".Popup Warning:DeleteSettings", + L"This command clears PCSX2 settings and allows you to re-run the First-Time Wizard. You will need to " + L"manually restart PCSX2 after this operation.\n\n" + L"WARNING!! Click OK to delete *ALL* settings for PCSX2 and force PCSX2 to shudown, losing any current emulation progress. Are you absolutely sure?" + L"\n\n(note: settings for plugins are unaffected)" + ), + _("Reset all settings?") ) ) + { + suspender.AllowResume(); + return; + } } + // Old 'auto-restart' method; avoids shutting down the PCSX2 process, but tends to + // be bug prone in odd ways (namely QueryPerformanceFrequency hangs for a number of + // seconds .. wtf?) + + /* m_RestartEmuOnDelete = true; Destroy(); @@ -145,21 +121,93 @@ void MainEmuFrame::Menu_ResetAllSettings_Click(wxCommandEvent &event) { WipeSettings(); } + */ + + WipeSettings(); + wxGetApp().PostMenuAction( MenuId_Exit ); } -void MainEmuFrame::Menu_CdvdSource_Click( wxCommandEvent &event ) +// Return values: +// wxID_CANCEL - User canceled the action outright. +// wxID_RESET - User wants to reset the emu in addition to swap discs +// (anything else) - Standard swap, no reset. (hotswap!) +wxWindowID SwapOrReset_Iso( wxWindow* owner, IScopedCoreThread& core_control, const wxString& isoFilename, const wxString& descpart1 ) { - CDVD_SourceType newSource = CDVDsrc_NoDisc; + wxWindowID result = wxID_RESET; - switch( event.GetId() ) + if( SysHasValidState() ) { - case MenuId_Src_Iso: newSource = CDVDsrc_Iso; break; - case MenuId_Src_Plugin: newSource = CDVDsrc_Plugin; break; - case MenuId_Src_NoDisc: newSource = CDVDsrc_NoDisc; break; + core_control.DisallowResume(); + wxDialogWithHelpers dialog( owner, _("Confirm ISO image change"), wxVERTICAL ); - jNO_DEFAULT + dialog += dialog.Heading(descpart1 + + isoFilename + L"\n\n" + + _("Do you want to swap discs or boot the new image (via system reset)?") + ); + + result = pxIssueConfirmation( dialog, MsgButtons().Reset().Cancel().Custom(_("Swap Disc")), L"DragDrop:BootSwapIso" ); } - CoreThread.ChangeCdvdSource( newSource ); + + if( result != wxID_CANCEL ) + { + SysUpdateIsoSrcFile( isoFilename ); + if( result != wxID_RESET ) + { + Console.Indent().WriteLn( "HotSwapping to new ISO src image!" ); + g_Conf->CdvdSource = CDVDsrc_Iso; + sMainFrame.UpdateIsoSrcSelection(); + CoreThread.ChangeCdvdSource(); + core_control.AllowResume(); + } + else + { + core_control.DisallowResume(); + sApp.SysExecute( CDVDsrc_Iso ); + } + } + + return result; +} + +wxWindowID SwapOrReset_CdvdSrc( wxWindow* owner, CDVD_SourceType newsrc ) +{ + if(newsrc == g_Conf->CdvdSource) return wxID_CANCEL; + wxWindowID result = wxID_CANCEL; + ScopedCoreThreadPopup core; + + if( SysHasValidState() ) + { + wxDialogWithHelpers dialog( owner, _("Confirm CDVD source change"), wxVERTICAL ); + + wxString changeMsg; + changeMsg.Printf(_("You've selected to switch the CDVD source from %s to %s."), + CDVD_SourceLabels[g_Conf->CdvdSource], CDVD_SourceLabels[newsrc] ); + + dialog += dialog.Heading(changeMsg + L"\n\n" + + _("Do you want to swap discs or boot the new image (system reset)?") + ); + + result = pxIssueConfirmation( dialog, MsgButtons().Reset().Cancel().Custom(_("Swap Disc")), L"DragDrop:BootSwapIso" ); + } + + if( result != wxID_CANCEL ) + { + if( result != wxID_RESET ) + { + Console.Indent().WriteLn( L"(CdvdSource) HotSwapping CDVD source types from %s to %s.", CDVD_SourceLabels[g_Conf->CdvdSource], CDVD_SourceLabels[newsrc] ); + g_Conf->CdvdSource = newsrc; + CoreThread.ChangeCdvdSource(); + core.AllowResume(); + } + else + { + core.DisallowResume(); + g_Conf->CdvdSource = newsrc; + sApp.SysExecute( newsrc ); + } + } + + return result; } // Returns FALSE if the user canceled the action. @@ -203,9 +251,9 @@ bool MainEmuFrame::_DoSelectELFBrowser() return false; } -void MainEmuFrame::Menu_BootCdvd_Click( wxCommandEvent &event ) +void MainEmuFrame::_DoBootCdvd() { - ScopedCoreThreadSuspend core; + ScopedCoreThreadPause paused_core; if( g_Conf->CdvdSource == CDVDsrc_Iso ) { @@ -232,7 +280,7 @@ void MainEmuFrame::Menu_BootCdvd_Click( wxCommandEvent &event ) wxString result; if( !_DoSelectIsoBrowser( result ) ) { - core.Resume(); + paused_core.AllowResume(); return; } @@ -248,30 +296,57 @@ void MainEmuFrame::Menu_BootCdvd_Click( wxCommandEvent &event ) if( !confirmed ) { - core.Resume(); + paused_core.AllowResume(); return; } } - sApp.SysReset(); sApp.SysExecute( g_Conf->CdvdSource ); } +void MainEmuFrame::Menu_CdvdSource_Click( wxCommandEvent &event ) +{ + CDVD_SourceType newsrc = CDVDsrc_NoDisc; + + switch( event.GetId() ) + { + case MenuId_Src_Iso: newsrc = CDVDsrc_Iso; break; + case MenuId_Src_Plugin: newsrc = CDVDsrc_Plugin; break; + case MenuId_Src_NoDisc: newsrc = CDVDsrc_NoDisc; break; + jNO_DEFAULT + } + + SwapOrReset_CdvdSrc(this, newsrc); +} + +void MainEmuFrame::Menu_BootCdvd_Click( wxCommandEvent &event ) +{ + g_Conf->EmuOptions.UseBOOT2Injection = false; + _DoBootCdvd(); +} + +void MainEmuFrame::Menu_BootCdvd2_Click( wxCommandEvent &event ) +{ + g_Conf->EmuOptions.UseBOOT2Injection = true; + _DoBootCdvd(); +} + +static wxString GetMsg_IsoImageChanged() +{ + return _("You have selected the following ISO image into PCSX2:\n\n"); +} + void MainEmuFrame::Menu_IsoBrowse_Click( wxCommandEvent &event ) { - ScopedCoreThreadSuspend core; - wxString result; + ScopedCoreThreadPopup core; + wxString isofile; - if( _DoSelectIsoBrowser( result ) ) + if( !_DoSelectIsoBrowser(isofile) || + (wxID_CANCEL == SwapOrReset_Iso(this, core, isofile, GetMsg_IsoImageChanged())) ) { - // This command does an on-the-fly change of CD media without automatic reset. - // (useful for disc swapping) - - SysUpdateIsoSrcFile( result ); - AppSaveSettings(); + core.AllowResume(); + return; } - - core.Resume(); } void MainEmuFrame::Menu_MultitapToggle_Click( wxCommandEvent& ) @@ -284,12 +359,6 @@ void MainEmuFrame::Menu_MultitapToggle_Click( wxCommandEvent& ) //evt.Skip(); } -void MainEmuFrame::Menu_SkipBiosToggle_Click( wxCommandEvent& ) -{ - g_Conf->EmuOptions.SkipBiosSplash = GetMenuBar()->IsChecked( MenuId_SkipBiosToggle ); - SaveEmuOptions(); -} - void MainEmuFrame::Menu_EnablePatches_Click( wxCommandEvent& ) { g_Conf->EmuOptions.EnablePatches = GetMenuBar()->IsChecked( MenuId_EnablePatches ); @@ -298,13 +367,13 @@ void MainEmuFrame::Menu_EnablePatches_Click( wxCommandEvent& ) void MainEmuFrame::Menu_OpenELF_Click(wxCommandEvent&) { - bool resume = CoreThread.Suspend(); + ScopedCoreThreadClose stopped_core; if( _DoSelectELFBrowser() ) { sApp.SysExecute( g_Conf->CdvdSource, g_Conf->CurrentELF ); } - if( resume ) CoreThread.Resume(); + stopped_core.AllowResume(); } void MainEmuFrame::Menu_LoadStates_Click(wxCommandEvent &event) @@ -334,6 +403,43 @@ void MainEmuFrame::Menu_Exit_Click(wxCommandEvent &event) Close(); } +class SysExecEvent_ToggleSuspend : public SysExecEvent +{ +public: + virtual ~SysExecEvent_ToggleSuspend() throw() {} + + wxString GetEventName() const { return L"ToggleSuspendResume"; } + +protected: + void _DoInvoke() + { + if( CoreThread.IsOpen() ) + CoreThread.Suspend(); + else + CoreThread.Resume(); + } +}; + +class SysExecEvent_Restart : public SysExecEvent +{ +public: + virtual ~SysExecEvent_Restart() throw() {} + + wxString GetEventName() const { return L"SysRestart"; } + + wxString GetEventMessage() const + { + return _("Restarting PS2 Virtual Machine..."); + } + +protected: + void _DoInvoke() + { + sApp.SysShutdown(); + sApp.SysExecute(); + } +}; + void MainEmuFrame::Menu_SuspendResume_Click(wxCommandEvent &event) { if( !SysHasValidState() ) return; @@ -343,29 +449,21 @@ void MainEmuFrame::Menu_SuspendResume_Click(wxCommandEvent &event) // engaged successfully). GetMenuBar()->Enable( MenuId_Sys_SuspendResume, false ); - - if( !CoreThread.Suspend() ) - { - sApp.SysExecute(); - } + GetSysExecutorThread().PostEvent( new SysExecEvent_ToggleSuspend() ); } void MainEmuFrame::Menu_SysReset_Click(wxCommandEvent &event) { - if( StateCopy_InvokeOnCopyComplete( new InvokeAction_MenuCommand(MenuId_Sys_Reset) ) ) return; - - sApp.SysReset(); - sApp.SysExecute(); - //GetMenuBar()->Enable( MenuId_Sys_Reset, true ); + UI_DisableSysReset(); + GetSysExecutorThread().PostEvent( new SysExecEvent_Restart() ); } void MainEmuFrame::Menu_SysShutdown_Click(wxCommandEvent &event) { - if( !SysHasValidState() && g_plugins == NULL ) return; - if( StateCopy_InvokeOnCopyComplete( new InvokeAction_MenuCommand(MenuId_Sys_Shutdown) ) ) return; + if( !SysHasValidState() && !CorePlugins.AreAnyInitialized() ) return; - sApp.SysReset(); - GetMenuBar()->Enable( MenuId_Sys_Shutdown, false ); + UI_DisableSysShutdown(); + sApp.SysShutdown(); } void MainEmuFrame::Menu_ConfigPlugin_Click(wxCommandEvent &event) @@ -379,12 +477,13 @@ void MainEmuFrame::Menu_ConfigPlugin_Click(wxCommandEvent &event) if( !pxAssertDev( (eventId >= 0) || (pid < PluginId_Count), "Invalid plugin identifier passed to ConfigPlugin event handler." ) ) return; - LoadPluginsImmediate(); - if( g_plugins == NULL ) return; + // This could probably just load a single plugin as needed now, but for design safety + // I'm leaving it force-load everything until the individual plugin management is + // better tested. wxWindowDisabler disabler; SaveSinglePluginHelper helper( pid ); - g_plugins->Configure( pid ); + GetCorePlugins().Configure( pid ); } void MainEmuFrame::Menu_Debug_Open_Click(wxCommandEvent &event) diff --git a/pcsx2/gui/MessageBoxes.cpp b/pcsx2/gui/MessageBoxes.cpp index 2590596f5e..c5ebf19e73 100644 --- a/pcsx2/gui/MessageBoxes.cpp +++ b/pcsx2/gui/MessageBoxes.cpp @@ -26,7 +26,7 @@ using namespace pxSizerFlags; // static int pxMessageDialog( const wxString& caption, const wxString& content, const MsgButtons& buttons ) { - if( !AffinityAssert_AllowFromMain() ) return wxID_CANCEL; + if( !AffinityAssert_AllowFrom_MainUI() ) return wxID_CANCEL; // fixme: If the emulator is currently active and is running in exclusive mode (forced // fullscreen), then we need to either: @@ -43,54 +43,31 @@ static int pxMessageDialog( const wxString& caption, const wxString& content, co // -------------------------------------------------------------------------------------- // BaseMessageBoxEvent Implementation // -------------------------------------------------------------------------------------- -IMPLEMENT_DYNAMIC_CLASS( BaseMessageBoxEvent, wxEvent ) +IMPLEMENT_DYNAMIC_CLASS( BaseMessageBoxEvent, pxInvokeActionEvent ) -BaseMessageBoxEvent::BaseMessageBoxEvent( int msgtype, const wxString& content ) - : wxEvent( 0, msgtype ) - , m_Content( content ) +BaseMessageBoxEvent::BaseMessageBoxEvent( const wxString& content, SynchronousActionState& instdata ) + : m_Content( content ) { - m_Instdata = NULL; + m_state = &instdata; } -BaseMessageBoxEvent::BaseMessageBoxEvent( MsgboxEventResult& instdata, const wxString& content ) - : wxEvent( 0, pxEvt_MessageBox ) - , m_Instdata( &instdata ) - , m_Content( content ) -{ -} - -BaseMessageBoxEvent::BaseMessageBoxEvent( const wxString& content ) - : wxEvent( 0, pxEvt_MessageBox ) - , m_Instdata( NULL ) - , m_Content( content ) +BaseMessageBoxEvent::BaseMessageBoxEvent( const wxString& content, SynchronousActionState* instdata ) + : m_Content( content ) { + m_state = instdata; } BaseMessageBoxEvent::BaseMessageBoxEvent( const BaseMessageBoxEvent& event ) - : wxEvent( event ) - , m_Instdata( event.m_Instdata ) - , m_Content( event.m_Content ) + : m_Content( event.m_Content ) { -} - -BaseMessageBoxEvent& BaseMessageBoxEvent::SetInstData( MsgboxEventResult& instdata ) -{ - m_Instdata = &instdata; - return *this; + m_state = event.m_state; } // Thread Safety: Must be called from the GUI thread ONLY. -void BaseMessageBoxEvent::IssueDialog() +void BaseMessageBoxEvent::_DoInvoke() { - AffinityAssert_AllowFromMain(); - int result = _DoDialog(); - - if( m_Instdata != NULL ) - { - m_Instdata->result = result; - m_Instdata->WaitForMe.Post(); - } + if( m_state ) m_state->PostResult( result ); } int BaseMessageBoxEvent::_DoDialog() const @@ -104,20 +81,15 @@ int BaseMessageBoxEvent::_DoDialog() const // -------------------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS( pxMessageBoxEvent, BaseMessageBoxEvent ) -pxMessageBoxEvent::pxMessageBoxEvent( int msgtype ) - : BaseMessageBoxEvent( msgtype ) -{ -} - -pxMessageBoxEvent::pxMessageBoxEvent( MsgboxEventResult& instdata, const wxString& title, const wxString& content, const MsgButtons& buttons ) - : BaseMessageBoxEvent( instdata, content ) +pxMessageBoxEvent::pxMessageBoxEvent( const wxString& title, const wxString& content, const MsgButtons& buttons, SynchronousActionState& instdata ) + : BaseMessageBoxEvent( content, instdata ) , m_Title( title ) , m_Buttons( buttons ) { } -pxMessageBoxEvent::pxMessageBoxEvent( const wxString& title, const wxString& content, const MsgButtons& buttons ) - : BaseMessageBoxEvent( content ) +pxMessageBoxEvent::pxMessageBoxEvent( const wxString& title, const wxString& content, const MsgButtons& buttons, SynchronousActionState* instdata ) + : BaseMessageBoxEvent( content, instdata ) , m_Title( title ) , m_Buttons( buttons ) { @@ -130,12 +102,6 @@ pxMessageBoxEvent::pxMessageBoxEvent( const pxMessageBoxEvent& event ) { } -pxMessageBoxEvent& pxMessageBoxEvent::SetInstData( MsgboxEventResult& instdata ) -{ - _parent::SetInstData( instdata ); - return *this; -} - int pxMessageBoxEvent::_DoDialog() const { return pxMessageDialog( m_Content, m_Title, m_Buttons ); @@ -146,19 +112,14 @@ int pxMessageBoxEvent::_DoDialog() const // -------------------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS( pxAssertionEvent, BaseMessageBoxEvent ) -pxAssertionEvent::pxAssertionEvent() - : BaseMessageBoxEvent( ) -{ -} - -pxAssertionEvent::pxAssertionEvent( MsgboxEventResult& instdata, const wxString& content, const wxString& trace ) - : BaseMessageBoxEvent( instdata, content ) +pxAssertionEvent::pxAssertionEvent( const wxString& content, const wxString& trace, SynchronousActionState& instdata ) + : BaseMessageBoxEvent( content, instdata ) , m_Stacktrace( trace ) { } -pxAssertionEvent::pxAssertionEvent( const wxString& content, const wxString& trace ) - : BaseMessageBoxEvent( content ) +pxAssertionEvent::pxAssertionEvent( const wxString& content, const wxString& trace, SynchronousActionState* instdata ) + : BaseMessageBoxEvent( content, instdata ) , m_Stacktrace( trace ) { } @@ -169,12 +130,6 @@ pxAssertionEvent::pxAssertionEvent( const pxAssertionEvent& event ) { } -pxAssertionEvent& pxAssertionEvent::SetInstData( MsgboxEventResult& instdata ) -{ - _parent::SetInstData( instdata ); - return *this; -} - pxAssertionEvent& pxAssertionEvent::SetStacktrace( const wxString& trace ) { m_Stacktrace = trace; @@ -190,33 +145,27 @@ namespace Msgbox { int ShowModal( BaseMessageBoxEvent& evt ) { - MsgboxEventResult instdat; - evt.SetInstData( instdat ); + SynchronousActionState instdat; + evt.SetSyncState( instdat ); if( wxThread::IsMain() ) { // main thread can handle the message immediately. wxGetApp().ProcessEvent( evt ); + return instdat.return_value; } else { // Not on main thread, must post the message there for handling instead: wxGetApp().AddPendingEvent( evt ); - instdat.WaitForMe.WaitNoCancel(); // Important! disable cancellation since we're using local stack vars. + return instdat.WaitForResult(); // Important! disable cancellation since we're using local stack vars. } - return instdat.result; } static int ShowModal( const wxString& title, const wxString& content, const MsgButtons& buttons ) { - // must pass the message to the main gui thread, and then stall this thread, to avoid - // threaded chaos where our thread keeps running while the popup is awaiting input. - - MsgboxEventResult instdat; - pxMessageBoxEvent tevt( instdat, title, content, buttons ); - wxGetApp().AddPendingEvent( tevt ); - instdat.WaitForMe.WaitNoCancel(); // Important! disable cancellation since we're using local stack vars. - return instdat.result; + pxMessageBoxEvent tevt( title, content, buttons ); + return ShowModal( tevt ); } // Pops up an alert Dialog Box with a singular "OK" button. @@ -225,10 +174,7 @@ namespace Msgbox { MsgButtons buttons( MsgButtons().OK() ); - if( wxThread::IsMain() ) - pxMessageDialog( caption, text, buttons ); - else - ShowModal( caption, text, buttons ); + ShowModal( caption, text, buttons ); return false; } diff --git a/pcsx2/gui/Panels/ConfigurationPanels.h b/pcsx2/gui/Panels/ConfigurationPanels.h index 5156e5ecae..99900cece8 100644 --- a/pcsx2/gui/Panels/ConfigurationPanels.h +++ b/pcsx2/gui/Panels/ConfigurationPanels.h @@ -39,6 +39,8 @@ namespace Panels // class DirPickerPanel : public BaseApplicableConfigPanel { + typedef BaseApplicableConfigPanel _parent; + protected: FoldersEnum_t m_FolderId; wxDirPickerCtrl* m_pickerCtrl; @@ -49,9 +51,6 @@ namespace Panels DirPickerPanel( wxWindow* parent, FoldersEnum_t folderid, const wxString& dialogLabel ); virtual ~DirPickerPanel() throw() { } - void Apply(); - void AppStatusEvent_OnSettingsApplied(); - void Reset(); wxDirName GetPath() const; void SetPath( const wxString& src ); @@ -62,6 +61,12 @@ namespace Panels wxWindowID GetId() const; wxWindowID GetPanelId() const { return m_windowId; } + // Overrides! + + void Apply(); + void AppStatusEvent_OnSettingsApplied(); + bool Enable( bool enable=true ); + protected: void Init( FoldersEnum_t folderid, const wxString& dialogLabel, bool isCompact ); @@ -71,20 +76,22 @@ namespace Panels }; // -------------------------------------------------------------------------------------- - // UsermodeSelectionPanel / LanguageSelectionPanel + // DocsFolderPickerPanel / LanguageSelectionPanel // -------------------------------------------------------------------------------------- - class UsermodeSelectionPanel : public BaseApplicableConfigPanel + class DocsFolderPickerPanel : public BaseApplicableConfigPanel { protected: pxRadioPanel* m_radio_UserMode; DirPickerPanel* m_dirpicker_custom; public: - virtual ~UsermodeSelectionPanel() throw() { } - UsermodeSelectionPanel( wxWindow* parent, bool isFirstTime = true ); + virtual ~DocsFolderPickerPanel() throw() { } + DocsFolderPickerPanel( wxWindow* parent, bool isFirstTime = true ); void Apply(); void AppStatusEvent_OnSettingsApplied(); + + DocsModeType GetDocsMode() const; wxWindowID GetDirPickerId() const { return m_dirpicker_custom ? m_dirpicker_custom->GetId() : 0; } protected: diff --git a/pcsx2/gui/Panels/DirPickerPanel.cpp b/pcsx2/gui/Panels/DirPickerPanel.cpp index 63ceff35f9..2727ac769c 100644 --- a/pcsx2/gui/Panels/DirPickerPanel.cpp +++ b/pcsx2/gui/Panels/DirPickerPanel.cpp @@ -70,7 +70,7 @@ void Panels::DirPickerPanel::Explore_Click( wxCommandEvent &evt ) ); if( result == wxID_CANCEL ) return; - wxMkdir( path ); + wxDirName(path).Mkdir(); } pxExplore( path ); @@ -108,8 +108,8 @@ void Panels::DirPickerPanel::Init( FoldersEnum_t folderid, const wxString& dialo // The default path is invalid... What should we do here? hmm.. } - if( !wxDir::Exists( normalized ) ) - wxMkdir( normalized ); + //if( !wxDir::Exists( normalized ) ) + // wxMkdir( normalized ); if( !isCompact ) { @@ -191,11 +191,22 @@ void Panels::DirPickerPanel::Reset() if( m_pickerCtrl ) { - m_pickerCtrl->Enable( m_checkCtrl ? !isDefault : true ); + // Important! The dirpicker panel stuff, due to however it's put together + // needs to check the enable status of this panel before setting the child + // panel's enable status. + + m_pickerCtrl->Enable( IsEnabled() ? ( m_checkCtrl ? !isDefault : true ) : false ); m_pickerCtrl->SetPath( GetNormalizedConfigFolder( m_FolderId ) ); } } +bool Panels::DirPickerPanel::Enable( bool enable ) +{ + m_pickerCtrl->Enable( enable ? (!m_checkCtrl || m_checkCtrl->GetValue()) : false ); + return _parent::Enable( enable ); +} + + void Panels::DirPickerPanel::AppStatusEvent_OnSettingsApplied() { Reset(); @@ -204,6 +215,21 @@ void Panels::DirPickerPanel::AppStatusEvent_OnSettingsApplied() void Panels::DirPickerPanel::Apply() { if( !m_pickerCtrl ) return; + + const wxString path( m_pickerCtrl->GetPath() ); + + if( !wxDir::Exists( path ) ) + { + wxDialogWithHelpers dialog( NULL, _("Create folder?"), wxVERTICAL ); + dialog += dialog.Heading( _("A configured folder does not exist. Should PCSX2 to create it?") ); + dialog += 12; + dialog += dialog.Heading( path ); + + if( wxID_CANCEL == pxIssueConfirmation( dialog, MsgButtons().Custom(_("Create")).Cancel(), L"CreateNewFolder" ) ) + throw Exception::CannotApplySettings( this ); + } + + wxDirName(path).Mkdir(); g_Conf->Folders.Set( m_FolderId, m_pickerCtrl->GetPath(), m_checkCtrl ? m_checkCtrl->GetValue() : false ); } diff --git a/pcsx2/gui/Panels/MiscPanelStuff.cpp b/pcsx2/gui/Panels/MiscPanelStuff.cpp index af2649e5b9..879b917fa0 100644 --- a/pcsx2/gui/Panels/MiscPanelStuff.cpp +++ b/pcsx2/gui/Panels/MiscPanelStuff.cpp @@ -1,6 +1,6 @@ /* 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. @@ -27,9 +27,9 @@ using namespace Dialogs; using namespace pxSizerFlags; // -------------------------------------------------------------------------------------- -// UsermodeSelectionPanel +// DocsFolderPickerPanel // -------------------------------------------------------------------------------------- -Panels::UsermodeSelectionPanel::UsermodeSelectionPanel( wxWindow* parent, bool isFirstTime ) +Panels::DocsFolderPickerPanel::DocsFolderPickerPanel( wxWindow* parent, bool isFirstTime ) : BaseApplicableConfigPanel( parent, wxVERTICAL, _("Usermode Selection") ) { const wxString usermodeExplained( pxE( ".Panels:Usermode:Explained", @@ -44,7 +44,7 @@ Panels::UsermodeSelectionPanel::UsermodeSelectionPanel( wxWindow* parent, bool i L"This option only affects Standard Paths which are set to use the installation default value." ) ); - const RadioPanelItem UsermodeOptions[] = + const RadioPanelItem UsermodeOptions[] = { RadioPanelItem( _("User Documents (recommended)"), @@ -67,7 +67,7 @@ Panels::UsermodeSelectionPanel::UsermodeSelectionPanel( wxWindow* parent, bool i m_radio_UserMode = new pxRadioPanel( this, UsermodeOptions ); m_radio_UserMode->SetPaddingHoriz( m_radio_UserMode->GetPaddingVert() + 4 ); m_radio_UserMode->Realize(); - + m_dirpicker_custom = new DirPickerPanel( this, FolderId_Documents, _("Select a document root for PCSX2") ); *this += Text( isFirstTime ? usermodeExplained : usermodeWarning ); @@ -75,32 +75,34 @@ Panels::UsermodeSelectionPanel::UsermodeSelectionPanel( wxWindow* parent, bool i *this += m_dirpicker_custom | pxExpand.Border( wxLEFT, StdPadding + m_radio_UserMode->GetIndentation() ); *this += 4; - AppStatusEvent_OnSettingsApplied(); - - Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler(UsermodeSelectionPanel::OnRadioChanged) ); + Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler(DocsFolderPickerPanel::OnRadioChanged) ); } -void Panels::UsermodeSelectionPanel::Apply() +DocsModeType Panels::DocsFolderPickerPanel::GetDocsMode() const +{ + return (DocsModeType) m_radio_UserMode->GetSelection(); +} + +void Panels::DocsFolderPickerPanel::Apply() { DocsFolderMode = (DocsModeType) m_radio_UserMode->GetSelection(); CustomDocumentsFolder = m_dirpicker_custom->GetPath(); } -void Panels::UsermodeSelectionPanel::AppStatusEvent_OnSettingsApplied() +void Panels::DocsFolderPickerPanel::AppStatusEvent_OnSettingsApplied() { if( m_radio_UserMode ) m_radio_UserMode->SetSelection( DocsFolderMode ); - if( m_dirpicker_custom ) m_dirpicker_custom->Enable( DocsFolderMode == DocsFolder_Custom ); } -void Panels::UsermodeSelectionPanel::OnRadioChanged( wxCommandEvent& evt ) +void Panels::DocsFolderPickerPanel::OnRadioChanged( wxCommandEvent& evt ) { evt.Skip(); if( !m_radio_UserMode ) return; if( m_dirpicker_custom ) - m_dirpicker_custom->Enable( m_radio_UserMode->GetSelection() == (int)DocsFolder_Custom ); + m_dirpicker_custom->Enable( m_radio_UserMode->GetSelection() == (int)DocsFolder_Custom ); } // -------------------------------------------------------------------------------------- diff --git a/pcsx2/gui/Panels/PluginSelectorPanel.cpp b/pcsx2/gui/Panels/PluginSelectorPanel.cpp index 4f9e35b0df..391a37c752 100644 --- a/pcsx2/gui/Panels/PluginSelectorPanel.cpp +++ b/pcsx2/gui/Panels/PluginSelectorPanel.cpp @@ -39,11 +39,13 @@ BEGIN_DECLARE_EVENT_TYPES() DECLARE_EVENT_TYPE(pxEVT_EnumeratedNext, -1) DECLARE_EVENT_TYPE(pxEVT_EnumerationFinished, -1) DECLARE_EVENT_TYPE(pxEVT_ShowStatusBar, -1) + DECLARE_EVENT_TYPE(pxEvt_SysExecEventComplete, -1) END_DECLARE_EVENT_TYPES() DEFINE_EVENT_TYPE(pxEVT_EnumeratedNext) DEFINE_EVENT_TYPE(pxEVT_EnumerationFinished); DEFINE_EVENT_TYPE(pxEVT_ShowStatusBar); +DEFINE_EVENT_TYPE(pxEvt_SysExecEventComplete) typedef s32 (CALLBACK* TestFnptr)(); typedef void (CALLBACK* ConfigureFnptr)(); @@ -60,7 +62,6 @@ namespace Exception // -------------------------------------------------------------------------------------- // PluginEnumerator class // -------------------------------------------------------------------------------------- - class PluginEnumerator { protected: @@ -134,10 +135,162 @@ public: } }; +// -------------------------------------------------------------------------------------- +// ApplyPluginsDialog +// -------------------------------------------------------------------------------------- +class ApplyPluginsDialog : public wxDialogWithHelpers +{ + DECLARE_DYNAMIC_CLASS_NO_COPY(ApplyPluginsDialog) + + typedef wxDialogWithHelpers _parent; + +protected: + BaseApplicableConfigPanel* m_panel; + ScopedPtr m_Exception; + SynchronousActionState m_sync; + +public: + ApplyPluginsDialog( BaseApplicableConfigPanel* panel=NULL ); + virtual ~ApplyPluginsDialog() throw() {} + + virtual void RethrowException(); + virtual int ShowModal(); + + BaseApplicableConfigPanel* GetApplicableConfigPanel() const { return m_panel; } + +protected: + void OnSysExecComplete( wxCommandEvent& evt ); +}; + + +// -------------------------------------------------------------------------------------- +// ApplyOverValidStateEvent +// -------------------------------------------------------------------------------------- +class ApplyOverValidStateEvent : public pxInvokeActionEvent +{ + //DeclareNoncopyableObject( ApplyOverValidStateEvent ); + typedef pxInvokeActionEvent _parent; + +protected: + ApplyPluginsDialog* m_owner; + +public: + ApplyOverValidStateEvent( ApplyPluginsDialog* owner=NULL ) + { + m_owner = owner; + } + + virtual ~ApplyOverValidStateEvent() throw() { } + virtual ApplyOverValidStateEvent *Clone() const { return new ApplyOverValidStateEvent(*this); } + +protected: + void _DoInvoke(); +}; + + +// -------------------------------------------------------------------------------------- +// SysExecEvent_ApplyPlugins +// -------------------------------------------------------------------------------------- +class SysExecEvent_ApplyPlugins : public SysExecEvent +{ +protected: + ApplyPluginsDialog* m_dialog; + +public: + virtual ~SysExecEvent_ApplyPlugins() throw() {} + SysExecEvent_ApplyPlugins* Clone() const { return new SysExecEvent_ApplyPlugins( *this ); } + + SysExecEvent_ApplyPlugins( ApplyPluginsDialog* parent, SynchronousActionState& sync ) + : SysExecEvent( &sync ) + { + m_dialog = parent; + } + +protected: + void _DoInvoke(); +}; + +IMPLEMENT_DYNAMIC_CLASS(ApplyPluginsDialog, wxDialogWithHelpers) + +ApplyPluginsDialog::ApplyPluginsDialog( BaseApplicableConfigPanel* panel ) + : wxDialogWithHelpers( NULL, _("Applying settings..."), wxVERTICAL ) +{ + Connect( pxEvt_SysExecEventComplete, wxCommandEventHandler(ApplyPluginsDialog::OnSysExecComplete) ); + GetSysExecutorThread().PostEvent( new SysExecEvent_ApplyPlugins( this, m_sync ) ); +} + +void ApplyOverValidStateEvent::_DoInvoke() +{ + wxDialogWithHelpers dialog( m_owner, _("Shutdown PS2 virtual machine?"), wxVERTICAL ); + + dialog += dialog.Heading( 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?" + ) ); + + int result = pxIssueConfirmation( dialog, MsgButtons().OK().Cancel(), L"PluginSelector:ConfirmShutdown" ); + + if( result == wxID_CANCEL ) + throw Exception::CannotApplySettings( m_owner->GetApplicableConfigPanel(), "Cannot apply settings: canceled by user because plugins changed while the emulation state was active.", false ); +} + +void SysExecEvent_ApplyPlugins::_DoInvoke() +{ + ScopedCoreThreadPause paused_core; + + if( SysHasValidState() ) + { + paused_core.AllowResume(); + wxGetApp().ProcessEvent( ApplyOverValidStateEvent( m_dialog ) ); + paused_core.DisallowResume(); + + // FIXME : We only actually have to save plugins here, except the recovery code + // in SysCoreThread isn't quite set up yet to handle that (I think...) --air + + memSavingState( StateCopy_GetBuffer() ).FreezeAll(); + } + + ScopedCoreThreadClose closed_core; + + CorePlugins.Shutdown(); + CorePlugins.Unload(); + LoadPluginsImmediate(); + + wxCommandEvent tevt( pxEvt_SysExecEventComplete ); + m_dialog->GetEventHandler()->AddPendingEvent( tevt ); + + closed_core.AllowResume(); + paused_core.AllowResume(); +} + + +void ApplyPluginsDialog::OnSysExecComplete( wxCommandEvent& evt ) +{ + evt.Skip(); + m_sync.WaitForResult(); + EndModal( wxID_OK ); +} + +void ApplyPluginsDialog::RethrowException() +{ + if( m_Exception ) m_Exception->Rethrow(); +} + +int ApplyPluginsDialog::ShowModal() +{ + int result = _parent::ShowModal(); + RethrowException(); + return result; +} + + static const wxString failed_separator( L"-------- Unsupported Plugins --------" ); // -------------------------------------------------------------------------------------- -// PluginSelectorPanel implementations +// PluginSelectorPanel::StatusPanel implementations // -------------------------------------------------------------------------------------- Panels::PluginSelectorPanel::StatusPanel::StatusPanel( wxWindow* parent ) @@ -176,9 +329,9 @@ void Panels::PluginSelectorPanel::StatusPanel::Reset() // Id for all Configure buttons (any non-negative arbitrary integer will do) static const int ButtonId_Configure = 51; -// ===================================================================================================== -// PluginSelectorPanel::ComboBoxPanel -// ===================================================================================================== +// -------------------------------------------------------------------------------------- +// PluginSelectorPanel::ComboBoxPanel implementations +// -------------------------------------------------------------------------------------- Panels::PluginSelectorPanel::ComboBoxPanel::ComboBoxPanel( PluginSelectorPanel* parent ) : wxPanelWithHelpers( parent, wxVERTICAL ) , m_FolderPicker( *new DirPickerPanel( this, FolderId_Plugins, @@ -224,9 +377,6 @@ void Panels::PluginSelectorPanel::ComboBoxPanel::Reset() } } -// ===================================================================================================== -// PluginSelectorPanel -// ===================================================================================================== void Panels::PluginSelectorPanel::DispatchEvent( const PluginEventType& evt ) { if( (evt != CorePlugins_Loaded) && (evt != CorePlugins_Unloaded) ) return; // everything else we don't care about @@ -337,64 +487,47 @@ void Panels::PluginSelectorPanel::Apply() break; } while( ++pi, pi->shortname != NULL ); - bool isSuspended = false; + if( pi->shortname == NULL ) return; // no plugins changed? nothing left to do! - if( pi->shortname != NULL ) + // ---------------------------------------------------------------------------- + // Plugin names are not up-to-date -- RELOAD! + + ApplyPluginsDialog applyDlg( this ); + + wxBoxSizer& paddedMsg( *new wxBoxSizer( wxHORIZONTAL ) ); + paddedMsg += 24; + paddedMsg += applyDlg.Heading(_("Applying Settings...")); + paddedMsg += 24; + + applyDlg += 12; + applyDlg += paddedMsg; + applyDlg += 12; + applyDlg += new wxButton( &applyDlg, wxID_CANCEL ) | pxCenter; + applyDlg += 6; + + //applyDlg.GetEventHandler()->AddPendingEvent( ); + + try { - if( CoreThread.IsRunning() ) - { - // [TODO] : Post notice that this shuts down existing emulation, and may not safely recover. - wxDialogWithHelpers dialog( this, _("Shutdown PS2 virtual machine?"), wxVERTICAL ); - - dialog += dialog.Heading( 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?" - ) ); - - int result = pxIssueConfirmation( dialog, MsgButtons().OK().Cancel(), L"PluginSelector:ConfirmShutdown" ); - - if( result == wxID_CANCEL ) - throw Exception::CannotApplySettings( this, "Cannot apply settings: canceled by user because plugins changed while the emulation state was active.", false ); - - // FIXME : We only actually have to save plugins here, except the recovery code - // in SysCoreThread isn't quite set up yet to handle that (I think...) --air - - isSuspended = CoreThread.Suspend(); - StateCopy_FreezeToMem_Blocking(); - } - - // Don't use SysShutdown, it clears the StateCopy. - CoreThread.Cancel(); - wxGetApp().m_CorePlugins = NULL; + if( wxID_CANCEL == applyDlg.ShowModal() ) + throw Exception::CannotApplySettings( this, "User canceled plugin load process.", false ); } - - if( !wxGetApp().m_CorePlugins ) + catch( Exception::PluginError& ex ) { - try { - LoadPluginsImmediate(); - } - catch( Exception::PluginError& ex ) - { - // Rethrow PluginLoadErrors as a failure to Apply... + // Rethrow PluginLoadErrors as a failure to Apply... - wxString plugname( tbl_PluginInfo[ex.PluginId].GetShortname() ); + wxString plugname( tbl_PluginInfo[ex.PluginId].GetShortname() ); - throw Exception::CannotApplySettings( this, - // Diagnostic - ex.FormatDiagnosticMessage(), + throw Exception::CannotApplySettings( this, + // Diagnostic + ex.FormatDiagnosticMessage(), - // Translated - wxsFormat( _("The selected %s plugin failed to load.\n\nReason: %s\n\n"), - plugname.c_str(), ex.FormatDisplayMessage().c_str() - ) + GetApplyFailedMsg() - ); - } + // Translated + wxsFormat( _("The selected %s plugin failed to load.\n\nReason: %s\n\n"), + plugname.c_str(), ex.FormatDisplayMessage().c_str() + ) + GetApplyFailedMsg() + ); } - - if( isSuspended ) CoreThread.Resume(); } void Panels::PluginSelectorPanel::CancelRefresh() @@ -480,9 +613,9 @@ void Panels::PluginSelectorPanel::OnPluginSelected( wxCommandEvent& evt ) // (a) plugins aren't even loaded yet. // (b) current selection matches exactly the currently configured/loaded plugin. - bool isSame = (g_plugins==NULL) || g_Conf->FullpathMatchTest( pi->id, (*m_FileList)[(int)box.GetClientData(box.GetSelection())] ); + bool isSame = (!CorePlugins.AreLoaded()) || g_Conf->FullpathMatchTest( pi->id, (*m_FileList)[(int)box.GetClientData(box.GetSelection())] ); m_ComponentBoxes->GetConfigButton( pi->id ).Enable( isSame ); - + if( !isSame ) evt.Skip(); // enabled Apply button! :D return; } @@ -504,7 +637,7 @@ void Panels::PluginSelectorPanel::OnConfigure_Clicked( wxCommandEvent& evt ) const wxString filename( (*m_FileList)[(int)m_ComponentBoxes->Get(pid).GetClientData(sel)] ); - if( g_plugins != NULL && !g_Conf->FullpathMatchTest( pid, filename ) ) + if( CorePlugins.AreLoaded() && !g_Conf->FullpathMatchTest( pid, filename ) ) { Console.Warning( "(PluginSelector) Plugin name mismatch, configuration request ignored." ); return; @@ -544,7 +677,7 @@ void Panels::PluginSelectorPanel::OnEnumComplete( wxCommandEvent& evt ) else if( m_ComponentBoxes->Get(pid).GetSelection() == wxNOT_FOUND ) { m_ComponentBoxes->Get(pid).SetSelection( 0 ); - m_ComponentBoxes->GetConfigButton(pid).Enable( g_plugins == NULL ); + m_ComponentBoxes->GetConfigButton(pid).Enable( CorePlugins.AreLoaded() ); } } while( ++pi, pi->shortname != NULL ); diff --git a/pcsx2/gui/Plugins.cpp b/pcsx2/gui/Plugins.cpp deleted file mode 100644 index 36d9b318f7..0000000000 --- a/pcsx2/gui/Plugins.cpp +++ /dev/null @@ -1,396 +0,0 @@ -/* 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 "App.h" -#include "MainFrame.h" - -#include -#include - -#include "Plugins.h" -#include "GS.h" -#include "HostGui.h" -#include "AppConfig.h" - -using namespace Threading; - -static FnType_OnThreadComplete* Callback_PluginsLoadComplete = NULL; - -// The GS plugin needs to be opened to save/load the state during plugin configuration, but -// the window shouldn't. This blocks it. :) -static bool s_DisableGsWindow = false; - -// -------------------------------------------------------------------------------------- -// AppPluginManager -// -------------------------------------------------------------------------------------- -// This extension of PluginManager provides event listener sources for plugins -- loading, -// unloading, open, close, shutdown, etc. -// -// FIXME : Should this be made part of the PCSX2 core emulation? (integrated into PluginManager) -// I'm undecided on if it makes sense more in that context or in this one (interface). -// -class AppPluginManager : public PluginManager -{ - typedef PluginManager _parent; - -public: - AppPluginManager( const wxString (&folders)[PluginId_Count] ) - : PluginManager( folders ) - { - SetSettingsFolder( GetSettingsFolder().ToString() ); - wxGetApp().PostPluginStatus( CorePlugins_Loaded ); - } - - virtual ~AppPluginManager() throw() - { - sApp.PostPluginStatus( CorePlugins_Unloaded ); - } - - void Init() - { - SetSettingsFolder( GetSettingsFolder().ToString() ); - _parent::Init(); - sApp.PostPluginStatus( CorePlugins_Init ); - } - - void Shutdown() - { - _parent::Shutdown(); - sApp.PostPluginStatus( CorePlugins_Shutdown ); - } - - void Close() - { - if( !NeedsClose() ) return; - - sApp.PostPluginStatus( CorePlugins_Closing ); - _parent::Close(); - sApp.PostPluginStatus( CorePlugins_Closed ); - } - - void Open() - { - SetSettingsFolder( GetSettingsFolder().ToString() ); - - if( !NeedsOpen() ) return; - - sApp.PostPluginStatus( CorePlugins_Opening ); - _parent::Open(); - sApp.PostPluginStatus( CorePlugins_Opened ); - } - - // Yay, this plugin is guaranteed to always be opened first and closed last. - bool OpenPlugin_GS() - { - if( GSopen2 != NULL && !s_DisableGsWindow ) - { - sApp.OpenGsPanel(); - } - - bool retval = _parent::OpenPlugin_GS(); - - if( g_LimiterMode == Limit_Turbo ) - GSsetVsync( false ); - - return retval; - } - - // Yay, this plugin is guaranteed to always be opened first and closed last. - void ClosePlugin_GS() - { - if( GSopen2 == NULL || CloseViewportWithPlugins ) - { - // All other plugins must be closed before the GS, because they all rely on - // the GS window handle being valid. The recursion guard will protect this - // function from being called a million times. ;) - - static int _guard; - RecursionGuard mess( _guard ); - if( !mess.IsReentrant() ) Close(); - } - - _parent::ClosePlugin_GS(); - sApp.CloseGsPanel(); - } - - - /*void Open( PluginsEnum_t pid ) - { - _parent::Open( pid ); - } - - void Close( PluginsEnum_t pid ) - { - _parent::Close( pid ); - }*/ -}; - -// -------------------------------------------------------------------------------------- -// LoadPluginsTask -// -------------------------------------------------------------------------------------- -// On completion the thread sends a pxEvt_LoadPluginsComplete message, which contains a -// handle to this thread object. If the load is successful, the Result var is set to -// non-NULL. If NULL, an error occurred and the thread loads the exception into either -// Ex_PluginError or Ex_RuntimeError. -// -class LoadPluginsTask : public PersistentThread -{ - typedef PersistentThread _parent; - -public: - PluginManager* Result; - -protected: - wxString m_folders[PluginId_Count]; - ScopedBusyCursor m_hourglass; -public: - LoadPluginsTask( const wxString (&folders)[PluginId_Count] ) : Result( NULL ), m_hourglass( Cursor_KindaBusy ) - { - for(int i=0; iFreeze( m_pid, save ); - } - - g_plugins->Close( pid ); -} - -SaveSinglePluginHelper::~SaveSinglePluginHelper() throw() -{ - - try - { - if( m_validstate ) - { - Console.WriteLn( Color_Green, L"Recovering single plugin: " + tbl_PluginInfo[m_pid].GetShortname() ); - memLoadingState load( *m_whereitsat ); - if( m_plugstore.IsDisposed() ) load.SeekToSection( m_pid ); - g_plugins->Freeze( m_pid, load ); - g_plugins->Close( m_pid ); - } - - s_DisableGsWindow = false; - if( m_resume ) CoreThread.Resume(); - } - DESTRUCTOR_CATCHALL; - - s_DisableGsWindow = false; -} - -///////////////////////////////////////////////////////////////////////////////////////// - - -int EnumeratePluginsInFolder( const wxDirName& searchpath, wxArrayString* dest ) -{ - ScopedPtr placebo; - wxArrayString* realdest = dest; - if( realdest == NULL ) - placebo = realdest = new wxArrayString(); // placebo is our /dev/null -- gets deleted when done - -#ifdef __WXMSW__ - // Windows pretty well has a strict "must end in .dll" rule. - wxString pattern( L"*%s" ); -#else - // Other platforms seem to like to version their libs after the .so extension: - // blah.so.3.1.fail? - wxString pattern( L"*%s*" ); -#endif - - return searchpath.Exists() ? - 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 LoadPluginsTask* plugin_load_lock = NULL; - -void Pcsx2App::ReloadPlugins() -{ - if( InvokeMethodOnMainThread( &Pcsx2App::ReloadPlugins ) ) return; - - if( plugin_load_lock ) return; - UnloadPlugins(); - - 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 ); - - plugin_load_lock = new LoadPluginsTask(passins); - plugin_load_lock->Start(); - - // ... and when it finishes it posts up a OnLoadPluginsComplete(). Bye. :) -} - -// Note: If the ClientData parameter of wxCommandEvt is NULL, this message simply dispatches -// the plugged in listeners. -void Pcsx2App::OnLoadPluginsComplete( wxCommandEvent& evt ) -{ - FnType_OnThreadComplete* fn_tmp = Callback_PluginsLoadComplete; - - if( LoadPluginsTask* pluginthread = (LoadPluginsTask*)evt.GetClientData() ) - { - // scoped ptr ensures the thread object is cleaned up even on exception: - ScopedPtr killTask( pluginthread ); - - pxAssume( plugin_load_lock == pluginthread ); - plugin_load_lock = NULL; - - if( !pxAssertDev( !m_CorePlugins, "LoadPlugins thread just finished, but CorePlugins state != NULL (odd!)." ) ) - m_CorePlugins = NULL; - - killTask->RethrowException(); - m_CorePlugins = killTask->Result; - } - - if( fn_tmp != NULL ) fn_tmp( evt ); -} - -void Pcsx2App::CancelLoadingPlugins() -{ - if( plugin_load_lock ) - plugin_load_lock->Cancel(); -} - -void Pcsx2App::PostPluginStatus( PluginEventType pevt ) -{ - if( !wxThread::IsMain() ) - { - PostCommand( pxEvt_PluginStatus, pevt ); - } - else - { - sApp.DispatchEvent( pevt ); - } -} - -void Pcsx2App::OnPluginStatus( wxCommandEvent& evt ) -{ - PostPluginStatus( (PluginEventType)evt.GetInt() ); -} - -// 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, onComplete is invoked, and the function returns with no -// other actions performed. -void LoadPluginsPassive( FnType_OnThreadComplete* onComplete ) -{ - // Plugins already loaded? - if( wxGetApp().m_CorePlugins ) - { - if( onComplete ) onComplete( wxCommandEvent( pxEvt_LoadPluginsComplete ) ); - return; - } - - if( onComplete ) - Callback_PluginsLoadComplete = onComplete; - - wxGetApp().ReloadPlugins(); -} - -// 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() -{ - if( g_plugins != NULL ) return; - - CoreThread.Cancel(); - - wxString passins[PluginId_Count]; - ConvertPluginFilenames( passins ); - wxGetApp().m_CorePlugins = new AppPluginManager( passins ); -} - -void UnloadPlugins() -{ - CoreThread.Cancel(); - sApp.m_CorePlugins = NULL; -} diff --git a/pcsx2/gui/RecentIsoList.cpp b/pcsx2/gui/RecentIsoList.cpp index 6ef638356b..c7dd1cbfec 100644 --- a/pcsx2/gui/RecentIsoList.cpp +++ b/pcsx2/gui/RecentIsoList.cpp @@ -46,7 +46,7 @@ void RecentIsoManager::OnChangedSelection( wxCommandEvent& evt ) { if( (m_Items[i].ItemPtr != NULL) && (m_Items[i].ItemPtr->GetId() == evt.GetId()) ) break; } - + if( i >= m_Items.size() ) { evt.Skip(); @@ -55,9 +55,11 @@ void RecentIsoManager::OnChangedSelection( wxCommandEvent& evt ) m_cursel = i; - bool resume = CoreThread.Suspend(); + // TODO: Dialog asking for hotswap or reset!!!! + + ScopedCoreThreadClose stopped_core; SysUpdateIsoSrcFile( m_Items[i].Filename ); - if( resume ) CoreThread.Resume(); + stopped_core.AllowResume(); } void RecentIsoManager::RemoveAllFromMenu() @@ -72,7 +74,7 @@ void RecentIsoManager::RemoveAllFromMenu() m_Menu->Destroy( curitem.ItemPtr ); curitem.ItemPtr = NULL; } - + if( m_Separator != NULL ) { m_Menu->Destroy( m_Separator ); @@ -93,7 +95,7 @@ void RecentIsoManager::Repopulate() if( cnt <= 0 ) return; m_Separator = m_Menu->AppendSeparator(); - + for( int i=0; iRecentIsoCount; IniScopedGroup groupie( ini, L"RecentIso" ); for( uint i=0; i. + */ + +#include "PrecompiledHeader.h" +#include "App.h" + +#include "SysThreads.h" +#include "SaveState.h" + +#include "ZipTools\ThreadedZipTools.h" + +// Used to hold the current state backup (fullcopy of PS2 memory and plugin states). +static SafeArray state_buffer( L"Public Savestate Buffer" ); + +static const char SavestateIdentString[] = "PCSX2 Savestate"; +static const uint SavestateIdentLen = sizeof(SavestateIdentString); + +static void SaveStateFile_WriteHeader( IStreamWriter& thr ) +{ + thr.Write( SavestateIdentString ); + thr.Write( g_SaveVersion ); +} + +static void SaveStateFile_ReadHeader( IStreamReader& thr ) +{ + char ident[SavestateIdentLen] = {0}; + + thr.Read( ident ); + + if( strcmp(SavestateIdentString, ident) ) + throw Exception::SaveStateLoadError( thr.GetStreamName(), + wxsFormat( L"Unrecognized file signature while loading savestate: %s", ident ), + _("File is not a valid PCSX2 savestate, or is from an older unsupported version of PCSX2.") + ); + + u32 savever; + thr.Read( savever ); + + if( (savever >> 16) != (g_SaveVersion >> 16) ) + throw Exception::SaveStateLoadError( thr.GetStreamName(), + wxsFormat( L"Unrecognized file signature while loading savestate: %s", ident ), + _("File is not a valid PCSX2 savestate, or is from an older unsupported version of PCSX2.") + ); + + if( savever > g_SaveVersion ) + throw Exception::SaveStateLoadError( thr.GetStreamName(), + wxsFormat( L"Unrecognized file signature while loading savestate: %s", ident ), + _("File is not a valid PCSX2 savestate, or is from an older unsupported version of PCSX2.") + ); +}; + +// -------------------------------------------------------------------------------------- +// gzipReader +// -------------------------------------------------------------------------------------- +// Interface for reading data from a gzip stream. +// +class gzipReader : public IStreamReader +{ +protected: + wxString m_filename; + gzFile m_gzfp; + +public: + gzipReader( const wxString& filename ) + : m_filename( filename ) + { + if( NULL == (m_gzfp = gzopen( m_filename.ToUTF8(), "rb" )) ) + throw Exception::CannotCreateStream( m_filename, "Cannot open file for reading." ); + + gzbuffer(m_gzfp, 0x100000); // 1mb buffer for zlib internal operations + } + + virtual ~gzipReader() throw () + { + if( m_gzfp ) gzclose( m_gzfp ); + } + + wxString GetStreamName() const { return m_filename; } + + void Read( void* dest, size_t size ) + { + int result = gzread( m_gzfp, dest, size ); + if( result == -1) + throw Exception::BadStream( m_filename, "Data read failed: Invalid or corrupted gzip archive." ); + + if( (size_t)result < size ) + throw Exception::EndOfStream( m_filename ); + } +}; + +// -------------------------------------------------------------------------------------- +// SysExecEvent_DownloadState +// -------------------------------------------------------------------------------------- +// Pauses core emulation and downloads the savestate into the state_buffer. +// +class SysExecEvent_DownloadState : public SysExecEvent +{ +protected: + SafeArray* m_dest_buffer; + +public: + virtual ~SysExecEvent_DownloadState() throw() {} + SysExecEvent_DownloadState* Clone() const { return new SysExecEvent_DownloadState( *this ); } + SysExecEvent_DownloadState( SafeArray* dest=&state_buffer ) + { + m_dest_buffer = dest; + } + + bool AllowCancelOnExit() const { return false; } + +protected: + void _DoInvoke() + { + ScopedCoreThreadPause paused_core; + + 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.") ); + + memSavingState(m_dest_buffer).FreezeAll(); + + paused_core.AllowResume(); + } +}; + +// -------------------------------------------------------------------------------------- +// SysExecEvent_ZipToDisk +// -------------------------------------------------------------------------------------- +class SysExecEvent_ZipToDisk : public SysExecEvent +{ +protected: + SafeArray* m_src_buffer; + wxString m_filename; + +public: + wxString GetEventName() const { return L"SysState_ZipToDisk"; } + + virtual ~SysExecEvent_ZipToDisk() throw() + { + delete m_src_buffer; + } + + SysExecEvent_ZipToDisk* Clone() const { return new SysExecEvent_ZipToDisk( *this ); } + + SysExecEvent_ZipToDisk( ScopedPtr>& src, const wxString& filename ) + : m_filename( filename ) + { + m_src_buffer = src.DetachPtr(); + } + + SysExecEvent_ZipToDisk( SafeArray* src, const wxString& filename ) + : m_filename( filename ) + { + m_src_buffer = src; + } + + bool IsCriticalEvent() const { return true; } + bool AllowCancelOnExit() const { return false; } + +protected: + void _DoInvoke() + { + (new CompressThread_gzip( m_filename, m_src_buffer, SaveStateFile_WriteHeader ))->Start(); + m_src_buffer = NULL; + } +}; + +// -------------------------------------------------------------------------------------- +// SysExecEvent_UnzipFromDisk +// -------------------------------------------------------------------------------------- +// Note: Unzipping always goes directly into the state_buffer, and is always a blocking +// action on the SysExecutor thread (the system cannot execute other commands while states +// are unzipping or uplading into the system). +// +class SysExecEvent_UnzipFromDisk : public SysExecEvent +{ +protected: + wxString m_filename; + gzipReader m_gzreader; + +public: + wxString GetEventName() const { return L"SysState_UnzipFromDisk"; } + + virtual ~SysExecEvent_UnzipFromDisk() throw() {} + SysExecEvent_UnzipFromDisk* Clone() const { return new SysExecEvent_UnzipFromDisk( *this ); } + SysExecEvent_UnzipFromDisk( const wxString& filename ) + : m_filename( filename ) + , m_gzreader( filename ) + { + SaveStateFile_ReadHeader( m_gzreader ); + } + + wxString GetStreamName() const { return m_filename; } + +protected: + void _DoInvoke() + { + // We use direct Suspend/Resume control here, since it's desirable that emulation + // *ALWAYS* start execution after the new savestate is loaded. + + GetCoreThread().Pause(); + + // fixme: should start initially with the file size, and then grow from there. + + static const int BlockSize = 0x100000; + state_buffer.MakeRoomFor( 0x800000 ); // start with an 8 meg buffer to avoid frequent reallocation. + int curidx = 0; + + try { + while(true) { + state_buffer.MakeRoomFor( curidx+BlockSize ); + m_gzreader.Read( state_buffer.GetPtr(curidx), BlockSize ); + curidx += BlockSize; + Threading::pxTestCancel(); + } + } + catch( Exception::EndOfStream& ) + { + // This exception actually means success! Any others we let get sent + // to the main event handler/thread for handling. + } + + // Optional shutdown of plugins when loading states? I'm not implementing it yet because some + // things, like the SPU2-recovery trick, rely on not resetting the plugins prior to loading + // the new savestate data. + + //if( ShutdownOnStateLoad ) GetCoreThread().Cancel(); + GetCoreThread().RecoverState(); + GetCoreThread().Resume(); // force resume regardless of emulation state earlier. + } +}; + +// ===================================================================================================== +// StateCopy Public Interface +// ===================================================================================================== + +SafeArray& StateCopy_GetBuffer() +{ + return state_buffer; +} + +bool StateCopy_IsValid() +{ + return !state_buffer.IsDisposed(); +} + +void StateCopy_FreezeToMem() +{ + GetSysExecutorThread().PostEvent( new SysExecEvent_DownloadState() ); +} + +void StateCopy_SaveToFile( const wxString& file ) +{ + ScopedPtr> zipbuf(new SafeArray( L"Zippable Savestate" )); + GetSysExecutorThread().PostEvent(new SysExecEvent_DownloadState( zipbuf )); + GetSysExecutorThread().PostEvent(new SysExecEvent_ZipToDisk( zipbuf, file )); +} + +void StateCopy_LoadFromFile( const wxString& file ) +{ + UI_DisableSysActions(); + GetSysExecutorThread().PostEvent(new SysExecEvent_UnzipFromDisk( file )); +} + +// Saves recovery state info to the given saveslot, or saves the active emulation state +// (if one exists) and no recovery data was found. This is needed because when a recovery +// state is made, the emulation state is usually reset so the only persisting state is +// the one in the memory save. :) +void StateCopy_SaveToSlot( uint num ) +{ + const wxString file( SaveStateBase::GetFilename( num ) ); + + Console.WriteLn( Color_StrongGreen, "Saving savestate to slot %d...", num ); + Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", file.c_str() ); + + StateCopy_SaveToFile( file ); +} + +void StateCopy_LoadFromSlot( uint slot ) +{ + wxString file( SaveStateBase::GetFilename( slot ) ); + + if( !wxFileExists( file ) ) + { + Console.Warning( "Savestate slot %d is empty.", slot ); + return; + } + + Console.WriteLn( Color_StrongGreen, "Loading savestate from slot %d...", slot ); + Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", file.c_str() ); + + StateCopy_LoadFromFile( file ); +} + +void StateCopy_Clear() +{ + state_buffer.Dispose(); +} + diff --git a/pcsx2/gui/UpdateUI.cpp b/pcsx2/gui/UpdateUI.cpp new file mode 100644 index 0000000000..5ba1d5dd86 --- /dev/null +++ b/pcsx2/gui/UpdateUI.cpp @@ -0,0 +1,80 @@ +/* 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 "Mainframe.h" +#include "GSFrame.h" + + +static void _SaveLoadStuff( bool enabled ) +{ + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_LoadStates, enabled ); + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_SaveStates, enabled ); +} + +// Updates the enable/disable status of all System related controls: menus, toolbars, +// etc. Typically called by SysEvtHandler whenever the message pump becomes idle. +void UI_UpdateSysControls() +{ + if( wxGetApp().PostMethodMyself( &UI_UpdateSysControls ) ) return; + + sApp.PostAction( CoreThreadStatusEvent( CoreThread_Indeterminate ) ); + + _SaveLoadStuff( true ); +} + + +void UI_DisableSysReset() +{ + if( wxGetApp().PostMethodMyself( UI_DisableSysReset ) ) return; + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Restart, false ); + + _SaveLoadStuff( false ); +} + +void UI_DisableSysShutdown() +{ + if( wxGetApp().PostMethodMyself( &UI_DisableSysShutdown ) ) return; + + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Restart, false ); + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Shutdown, false ); +} + +void UI_EnableSysShutdown() +{ + if( wxGetApp().PostMethodMyself( &UI_EnableSysShutdown ) ) return; + + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Restart, true ); + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Shutdown, true ); +} + + +void UI_DisableSysActions() +{ + if( wxGetApp().PostMethodMyself( &UI_DisableSysShutdown ) ) return; + + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Restart, false ); + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Shutdown, false ); +} + +void UI_EnableSysActions() +{ + if( wxGetApp().PostMethodMyself( &UI_EnableSysShutdown ) ) return; + + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Restart, true ); + sMainFrame.GetMenuBar()->Enable( MenuId_Sys_Shutdown, true ); +} + + diff --git a/pcsx2/gui/pxEventThread.h b/pcsx2/gui/pxEventThread.h new file mode 100644 index 0000000000..71353a630e --- /dev/null +++ b/pcsx2/gui/pxEventThread.h @@ -0,0 +1,188 @@ +/* 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 "Utilities/PersistentThread.h" +#include "Utilities/pxEvents.h" + +// TODO!! Make this system a bit more generic, and then move it to the Utilities library. + +// -------------------------------------------------------------------------------------- +// SysExecEvent +// -------------------------------------------------------------------------------------- +class SysExecEvent + : public IActionInvocation + , public ICloneable +{ +protected: + SynchronousActionState* m_sync; + +public: + virtual ~SysExecEvent() throw() {} + SysExecEvent* Clone() const { return new SysExecEvent( *this ); } + + SysExecEvent( SynchronousActionState* sync=NULL ) + { + m_sync = sync; + } + + SysExecEvent( SynchronousActionState& sync ) + { + m_sync = &sync; + } + + const SynchronousActionState* GetSyncState() const { return m_sync; } + SynchronousActionState* GetSyncState() { return m_sync; } + + void SetSyncState( SynchronousActionState* obj ) { m_sync = obj; } + void SetSyncState( SynchronousActionState& obj ) { m_sync = &obj; } + + // Tells the Event Handler whether or not this event can be skipped when the system + // is being quit or reset. Typically set this to true for events which shut down the + // system, since program crashes can occur if the program tries to exit while threads + // are running. + virtual bool IsCriticalEvent() const { return false; } + + // Tells the Event Handler whether or not this event can be canceled. Typically events + // should not prohibit cancellation, since it expedites program termination and helps + // avoid permanent deadlock. Some actions like saving states and shutdown procedures + // should not allow cancellation since they could result in program crashes or corrupted + // data. + virtual bool AllowCancelOnExit() const { return true; } + + virtual void InvokeAction(); + virtual void PostResult() const; + + virtual wxString GetEventName() const { return wxEmptyString; } + virtual wxString GetEventMessage() const { return wxEmptyString; } + + virtual int GetResult() + { + if( !pxAssertDev( m_sync != NULL, "SysEvent: Expecting return value, but no sync object provided." ) ) return 0; + return m_sync->return_value; + } + + virtual void SetException( BaseException* ex ); + + void SetException( const BaseException& ex ); + +protected: + virtual void _DoInvoke(); +}; + +// -------------------------------------------------------------------------------------- +// SysExecEvent_Method +// -------------------------------------------------------------------------------------- +class SysExecEvent_Method : public SysExecEvent +{ +protected: + FnType_Void* m_method; + +public: + virtual ~SysExecEvent_Method() throw() {} + SysExecEvent_Method* Clone() const { return new SysExecEvent_Method( *this ); } + + explicit SysExecEvent_Method( FnType_Void* method = NULL ) + { + m_method = method; + } + +protected: + void _DoInvoke() + { + if( m_method ) m_method(); + } +}; + + +typedef std::list pxEvtList; + +// -------------------------------------------------------------------------------------- +// pxEvtHandler +// -------------------------------------------------------------------------------------- +// wxWidgets Event Queue (wxEvtHandler) isn't thread-safe (uses static vars and checks/modifies wxApp globals +// while processing), so it's useless to us. Have to roll our own. -_- +// +class pxEvtHandler +{ +protected: + pxEvtList m_pendingEvents; + Threading::MutexRecursive m_mtx_pending; + Threading::Semaphore m_wakeup; + wxThreadIdType m_OwnerThreadId; + volatile u32 m_Quitting; + +public: + pxEvtHandler(); + virtual ~pxEvtHandler() throw() {} + + virtual wxString GetEventHandlerName() const { return L"pxEvtHandler"; } + + virtual void ShutdownQueue(); + bool IsShuttingDown() const { return !!m_Quitting; } + + void ProcessPendingEvents(); + void AddPendingEvent( SysExecEvent& evt ); + void PostEvent( SysExecEvent* evt ); + void PostEvent( const SysExecEvent& evt ); + + void ProcessEvent( SysExecEvent* evt ); + void ProcessEvent( SysExecEvent& evt ); + + bool SelfProcessMethod( FnType_Void* method ); + + void Idle(); + + void SetActiveThread(); + +protected: + virtual void DoIdle() {} +}; + +// -------------------------------------------------------------------------------------- +// ExecutorThread +// -------------------------------------------------------------------------------------- +class ExecutorThread : public Threading::PersistentThread +{ + typedef Threading::PersistentThread _parent; + +protected: + ScopedPtr m_ExecutorTimer; + ScopedPtr m_EvtHandler; + +public: + ExecutorThread( pxEvtHandler* evtandler = NULL ); + virtual ~ExecutorThread() throw() { } + + virtual void ShutdownQueue(); + + void PostEvent( SysExecEvent* evt ); + void PostEvent( const SysExecEvent& evt ); + + void ProcessEvent( SysExecEvent* evt ); + void ProcessEvent( SysExecEvent& evt ); + + bool SelfProcessMethod( void (*evt)() ) + { + return m_EvtHandler ? m_EvtHandler->SelfProcessMethod( evt ) : false; + } + +protected: + void OnStart(); + void ExecuteTaskInThread(); + void OnCleanupInThread(); +}; + diff --git a/pcsx2/gui/pxLogTextCtrl.cpp b/pcsx2/gui/pxLogTextCtrl.cpp index 29c91a1cdd..24b3f65129 100644 --- a/pcsx2/gui/pxLogTextCtrl.cpp +++ b/pcsx2/gui/pxLogTextCtrl.cpp @@ -83,7 +83,7 @@ void pxLogTextCtrl::OnThumbTrack(wxScrollWinEvent& evt) //Console.Warning( "Thumb Tracking!!!" ); m_FreezeWrites = true; if( !m_IsPaused ) - m_IsPaused = CoreThread.Pause(); + m_IsPaused = new ScopedCoreThreadPause(); evt.Skip(); } @@ -94,8 +94,8 @@ void pxLogTextCtrl::OnThumbRelease(wxScrollWinEvent& evt) m_FreezeWrites = false; if( m_IsPaused ) { - CoreThread.Resume(); - m_IsPaused = false; + m_IsPaused->AllowResume(); + m_IsPaused.Delete(); } evt.Skip(); } diff --git a/pcsx2/windows/VCprojects/pcsx2_2008.vcproj b/pcsx2/windows/VCprojects/pcsx2_2008.vcproj index ca6f59d9db..d468ae717e 100644 --- a/pcsx2/windows/VCprojects/pcsx2_2008.vcproj +++ b/pcsx2/windows/VCprojects/pcsx2_2008.vcproj @@ -1880,6 +1880,10 @@ RelativePath="..\..\gui\AppConfig.cpp" > + + @@ -1940,6 +1944,10 @@ RelativePath="..\..\gui\CpuUsageProviderMSW.cpp" > + + @@ -1980,10 +1988,6 @@ RelativePath="..\..\gui\MSWstuff.cpp" > - - @@ -1993,11 +1997,7 @@ > - - + + + + @@ -2681,6 +2689,10 @@ RelativePath="..\..\gui\Resources\EmbeddedImage.h" > + + @@ -2697,6 +2709,10 @@ RelativePath="..\..\gui\MainFrame.h" > + + @@ -2822,6 +2838,38 @@ > + + + + + + + + + + + + + + + + diff --git a/pcsx2/windows/WinCompressNTFS.cpp b/pcsx2/windows/WinCompressNTFS.cpp index e831aa2666..0b5fecaf64 100644 --- a/pcsx2/windows/WinCompressNTFS.cpp +++ b/pcsx2/windows/WinCompressNTFS.cpp @@ -32,10 +32,10 @@ void StreamException_ThrowFromErrno( const wxString& streamname, errno_t errcode throw Exception::AccessDenied( streamname ); case EMFILE: // Too many open files! - throw Exception::CreateStream( streamname, "Too many open files" ); // File handle allocation failure + throw Exception::CannotCreateStream( streamname, "Too many open files" ); // File handle allocation failure case EEXIST: - throw Exception::CreateStream( streamname, "File already exists" ); + throw Exception::CannotCreateStream( streamname, "File already exists" ); case ENOENT: // File not found! throw Exception::FileNotFound( streamname ); @@ -70,7 +70,7 @@ void StreamException_ThrowLastError( const wxString& streamname, HANDLE result ) throw Exception::FileNotFound( streamname ); case ERROR_TOO_MANY_OPEN_FILES: - throw Exception::CreateStream( streamname, "Too many open files" ); + throw Exception::CannotCreateStream( streamname, "Too many open files" ); case ERROR_ACCESS_DENIED: throw Exception::AccessDenied( streamname ); diff --git a/pcsx2/windows/WinConsolePipe.cpp b/pcsx2/windows/WinConsolePipe.cpp index c55b8f5a92..d34ffbd248 100644 --- a/pcsx2/windows/WinConsolePipe.cpp +++ b/pcsx2/windows/WinConsolePipe.cpp @@ -226,7 +226,7 @@ WinPipeRedirection::WinPipeRedirection( FILE* stdstream ) Cleanup(); throw Exception::RuntimeError( ex.FormatDiagnosticMessage(), ex.FormatDisplayMessage() ); } - catch( Exception::BaseException& ex ) + catch( BaseException& ex ) { Cleanup(); ex.DiagMsg() = (wxString)((stdstream==stdout) ? L"STDOUT" : L"STDERR") + L" Redirection Init failed: " + ex.DiagMsg();