/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2010 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "PrecompiledHeader.h" #include "wxAppWithHelpers.h" #include "ThreadingInternal.h" #include "PersistentThread.h" DEFINE_EVENT_TYPE( pxEvt_DeleteObject ); DEFINE_EVENT_TYPE( pxEvt_DeleteThread ); DEFINE_EVENT_TYPE( pxEvt_StartIdleEventTimer ); DEFINE_EVENT_TYPE( pxEvt_InvokeAction ); DEFINE_EVENT_TYPE( pxEvt_SynchronousCommand ); IMPLEMENT_DYNAMIC_CLASS( pxSimpleEvent, wxEvent ) ConsoleLogSource_App::ConsoleLogSource_App() { static const TraceLogDescriptor myDesc = { L"AppEvents", L"App Events", pxLt("Includes idle event processing and some other uncommon event usages.") }; m_Descriptor = &myDesc; } ConsoleLogSource_App pxConLog_App; void BaseDeletableObject::DoDeletion() { wxAppWithHelpers* app = wxDynamicCast( wxApp::GetInstance(), wxAppWithHelpers ); pxAssert( app != NULL ); app->DeleteObject( *this ); } // -------------------------------------------------------------------------------------- // SynchronousActionState Implementations // -------------------------------------------------------------------------------------- void SynchronousActionState::SetException( const BaseException& ex ) { m_exception = ScopedExcept(ex.Clone()); } void SynchronousActionState::SetException( BaseException* ex ) { if( !m_posted ) { m_exception = ScopedExcept(ex); } else if( wxTheApp ) { // transport the exception to the main thread, since the message is fully // asynchronous, or has already entered an asynchronous state. 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. pxExceptionEvent ev( ex ); wxTheApp->AddPendingEvent( ev ); } } void SynchronousActionState::RethrowException() const { if( m_exception ) m_exception->Rethrow(); } int SynchronousActionState::WaitForResult() { m_sema.WaitNoCancel(); RethrowException(); return return_value; } int SynchronousActionState::WaitForResult_NoExceptions() { m_sema.WaitNoCancel(); return return_value; } void SynchronousActionState::PostResult( int res ) { return_value = res; PostResult(); } void SynchronousActionState::ClearResult() { m_posted = false; m_exception = NULL; } void SynchronousActionState::PostResult() { if( m_posted ) return; m_posted = true; m_sema.Post(); } // -------------------------------------------------------------------------------------- // pxActionEvent Implementations // -------------------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS( pxActionEvent, wxEvent ) pxActionEvent::pxActionEvent( SynchronousActionState* sema, int msgtype ) : wxEvent( 0, msgtype ) { m_state = sema; } pxActionEvent::pxActionEvent( SynchronousActionState& sema, int msgtype ) : wxEvent( 0, msgtype ) { m_state = &sema; } pxActionEvent::pxActionEvent( const pxActionEvent& src ) : wxEvent( src ) { m_state = src.m_state; } void pxActionEvent::SetException( const BaseException& ex ) { SetException( ex.Clone() ); } void pxActionEvent::SetException( BaseException* ex ) { const wxString& prefix( pxsFmt(L"(%s) ", GetClassInfo()->GetClassName()) ); ex->DiagMsg() = prefix + ex->DiagMsg(); if( !m_state ) { ScopedExcept 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 ) { ScopedExcept exptr( ex ); // auto-delete it after handling. ex->Rethrow(); } m_sync->SetException( ex ); } // -------------------------------------------------------------------------------------- // pxRpcEvent // -------------------------------------------------------------------------------------- // 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 pxRpcEvent : public pxActionEvent { DECLARE_DYNAMIC_CLASS_NO_ASSIGN(pxRpcEvent) typedef pxActionEvent _parent; protected: void (*m_Method)(); public: virtual ~pxRpcEvent() throw() { } virtual pxRpcEvent *Clone() const { return new pxRpcEvent(*this); } explicit pxRpcEvent( void (*method)()=NULL, SynchronousActionState* sema=NULL ) : pxActionEvent( sema ) { m_Method = method; } explicit pxRpcEvent( void (*method)(), SynchronousActionState& sema ) : pxActionEvent( sema ) { m_Method = method; } pxRpcEvent( const pxRpcEvent& src ) : pxActionEvent( src ) { m_Method = src.m_Method; } void SetMethod( void (*method)() ) { m_Method = method; } protected: void InvokeEvent() { if( m_Method ) m_Method(); } }; IMPLEMENT_DYNAMIC_CLASS( pxRpcEvent, pxActionEvent ) // -------------------------------------------------------------------------------------- // pxExceptionEvent implementations // -------------------------------------------------------------------------------------- pxExceptionEvent::pxExceptionEvent( const BaseException& ex ) { m_except = ex.Clone(); } void pxExceptionEvent::InvokeEvent() { ScopedExcept deleteMe( m_except ); if( deleteMe ) deleteMe->Rethrow(); } // -------------------------------------------------------------------------------------- // wxAppWithHelpers Implementation // -------------------------------------------------------------------------------------- // // TODO : Ping dispatch and IdleEvent dispatch can be unified into a single dispatch, which // would mean checking only one list of events per idle event, instead of two. (ie, ping // events can be appended to the idle event list, instead of into their own custom list). // 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( pxRpcEvent( 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 ) { pxRpcEvent evt( method ); AddIdleEvent( evt ); } // Invokes the specified void 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 // AppRpc_TryInvokeAsync. // // This function works something like setjmp/longjmp, in that the return value indicates if the // function actually executed the specified method or not. // // Returns: // FALSE if the method was not invoked (meaning this IS the main thread!) // TRUE if the method was invoked. // bool wxAppWithHelpers::Rpc_TryInvoke( FnType_Void* method ) { if( wxThread::IsMain() ) return false; SynchronousActionState sync; PostEvent( pxRpcEvent( method, sync ) ); sync.WaitForResult(); return true; } // Invokes the specified void method, or posts the method to the main thread if the calling // thread is not Main. Action is non-blocking (asynchronous). For blocking method execution, // use AppRpc_TryInvoke. // // This function works something like setjmp/longjmp, in that the return value indicates if the // function actually executed the specified method or not. // // Returns: // FALSE if the method was not posted to the main thread (meaning this IS the main thread!) // TRUE if the method was posted. // bool wxAppWithHelpers::Rpc_TryInvokeAsync( FnType_Void* method ) { if( wxThread::IsMain() ) return false; PostEvent( pxRpcEvent( method ) ); return true; } void wxAppWithHelpers::ProcessMethod( FnType_Void* method ) { if( wxThread::IsMain() ) { method(); return; } SynchronousActionState sync; PostEvent( pxRpcEvent( 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(); std::unique_ptr deleteMe( evt ); return _parent::ProcessEvent( *deleteMe ); } bool wxAppWithHelpers::ProcessEvent( pxActionEvent& evt ) { if( wxThread::IsMain() ) return _parent::ProcessEvent( evt ); else { SynchronousActionState sync; evt.SetSyncState( sync ); AddPendingEvent( evt ); sync.WaitForResult(); return true; } } bool wxAppWithHelpers::ProcessEvent( pxActionEvent* evt ) { if( wxThread::IsMain() ) { std::unique_ptr deleteMe( evt ); return _parent::ProcessEvent( *deleteMe ); } else { SynchronousActionState sync; evt->SetSyncState( sync ); AddPendingEvent( *evt ); sync.WaitForResult(); return true; } } void wxAppWithHelpers::CleanUp() { // 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(); } // Executes the event with exception handling. If the event throws an exception, the exception // will be neatly packaged and transported back to the thread that posted the event. // This function is virtual, however overloading it is not recommended. Derrived classes // should overload InvokeEvent() instead. void pxActionEvent::_DoInvokeEvent() { AffinityAssert_AllowFrom_MainUI(); try { InvokeEvent(); } catch( BaseException& ex ) { SetException( ex ); } catch( std::runtime_error& ex ) { SetException( new Exception::RuntimeError( ex ) ); } if( m_state ) m_state->PostResult(); } void wxAppWithHelpers::OnSynchronousCommand( pxSynchronousCommandEvent& evt ) { AffinityAssert_AllowFrom_MainUI(); pxAppLog.Write(L"(App) Executing command event synchronously..."); 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 wxChar* action ) { // Recursion is possible thanks to modal dialogs being issued from the idle event handler. // (recursion shouldn't hurt anything anyway, since the node system re-creates the iterator // on each pass) //static int __guard=0; //RecursionGuard guard(__guard); //if( !pxAssertDev(!guard.IsReentrant(), "Re-entrant call to IdleEventdispatcher caught on camera!") ) return; wxEventList postponed; wxEventList::iterator node; ScopedLock lock( m_IdleEventMutex ); while( node = m_IdleEventQueue.begin(), node != m_IdleEventQueue.end() ) { std::unique_ptr deleteMe(*node); m_IdleEventQueue.erase( node ); lock.Release(); if( !Threading::AllowDeletions() && (deleteMe->GetEventType() == pxEvt_DeleteThread) ) { // Threads that have active semaphores or mutexes (other threads are waiting on them) cannot // be deleted because those mutex/sema objects will become invalid and cause the pending // thread to crash. So we disallow deletions when those waits are in action, and continue // to postpone the deletion of the thread until such time that it is safe. pxThreadLog.Write( ((pxThread*)((wxCommandEvent*)deleteMe.get())->GetClientData())->GetName(), L"Deletion postponed due to mutex or semaphore dependency." ); postponed.push_back(deleteMe.release()); } else { pxAppLog.Write( L"(AppIdleQueue%s) Dispatching event '%s'", action, deleteMe->GetClassInfo()->GetClassName() ); ProcessEvent( *deleteMe ); // dereference to prevent auto-deletion by ProcessEvent } lock.Acquire(); } m_IdleEventQueue = postponed; if( m_IdleEventQueue.size() > 0 ) pxAppLog.Write( L"(AppIdleQueue%s) %d events postponed due to dependencies.", action, m_IdleEventQueue.size() ); } void wxAppWithHelpers::OnIdleEvent( wxIdleEvent& evt ) { m_IdleEventTimer.Stop(); IdleEventDispatcher(); } void wxAppWithHelpers::OnIdleEventTimeout( wxTimerEvent& evt ) { IdleEventDispatcher( L"[Timeout]" ); } void wxAppWithHelpers::Ping() { pxThreadLog.Write( pxGetCurrentThreadName().c_str(), L"App Event Ping Requested." ); SynchronousActionState sync; pxActionEvent evt( sync ); AddIdleEvent( evt ); sync.WaitForResult(); } void wxAppWithHelpers::PostCommand( void* clientData, int evtType, int intParam, long longParam, const wxString& stringParam ) { wxCommandEvent evt( evtType ); evt.SetClientData( clientData ); evt.SetInt( intParam ); evt.SetExtraLong( longParam ); evt.SetString( stringParam ); AddPendingEvent( evt ); } void wxAppWithHelpers::PostCommand( int evtType, int intParam, long longParam, const wxString& stringParam ) { PostCommand( NULL, evtType, intParam, longParam, stringParam ); } sptr wxAppWithHelpers::ProcessCommand( void* clientData, int evtType, int intParam, long longParam, const wxString& stringParam ) { 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; } sptr wxAppWithHelpers::ProcessCommand( int evtType, int intParam, long longParam, const wxString& stringParam ) { return ProcessCommand( NULL, evtType, intParam, longParam, stringParam ); } void wxAppWithHelpers::PostAction( const pxActionEvent& evt ) { PostEvent( evt ); } void wxAppWithHelpers::ProcessAction( pxActionEvent& evt ) { if( !wxThread::IsMain() ) { SynchronousActionState sync; evt.SetSyncState( sync ); AddPendingEvent( evt ); sync.WaitForResult(); } else evt._DoInvokeEvent(); } void wxAppWithHelpers::DeleteObject( BaseDeletableObject& obj ) { pxAssert( !obj.IsBeingDeleted() ); wxCommandEvent evt( pxEvt_DeleteObject ); evt.SetClientData( (void*)&obj ); AddIdleEvent( evt ); } void wxAppWithHelpers::DeleteThread( pxThread& obj ) { pxThreadLog.Write(obj.GetName(), L"Scheduling for deletion..."); wxCommandEvent evt( pxEvt_DeleteThread ); evt.SetClientData( (void*)&obj ); AddIdleEvent( evt ); } typedef void (wxEvtHandler::*pxInvokeActionEventFunction)(pxActionEvent&); bool wxAppWithHelpers::OnInit() { #define pxActionEventHandler(func) \ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(pxInvokeActionEventFunction, &func ) Connect( pxEvt_SynchronousCommand, pxSynchronousEventHandler (wxAppWithHelpers::OnSynchronousCommand) ); Connect( pxEvt_InvokeAction, pxActionEventHandler (wxAppWithHelpers::OnInvokeAction) ); Connect( pxEvt_StartIdleEventTimer, wxEventHandler (wxAppWithHelpers::OnStartIdleEventTimer) ); Connect( pxEvt_DeleteObject, wxCommandEventHandler (wxAppWithHelpers::OnDeleteObject) ); Connect( pxEvt_DeleteThread, wxCommandEventHandler (wxAppWithHelpers::OnDeleteThread) ); Connect( wxEVT_IDLE, wxIdleEventHandler (wxAppWithHelpers::OnIdleEvent) ); Connect( m_IdleEventTimer.GetId(), wxEVT_TIMER, wxTimerEventHandler(wxAppWithHelpers::OnIdleEventTimeout) ); return _parent::OnInit(); } void wxAppWithHelpers::OnInvokeAction( pxActionEvent& evt ) { evt._DoInvokeEvent(); // wow this is easy! } void wxAppWithHelpers::OnDeleteObject( wxCommandEvent& evt ) { if( evt.GetClientData() == NULL ) return; delete (BaseDeletableObject*)evt.GetClientData(); } // In theory we create a Pcsx2App object which inherit from wxAppWithHelpers, // so Pcsx2App::CreateTraits must be used instead. // // However it doesn't work this way because wxAppWithHelpers constructor will // be called first. This constructor will build some wx objects (here wxTimer) // that require a trait. In others word, wxAppWithHelpers::CreateTraits will be // called instead wxAppTraits* wxAppWithHelpers::CreateTraits() { return new Pcsx2AppTraits; } // 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 ) { std::unique_ptr thr( (pxThread*)evt.GetClientData() ); if( !thr ) { pxThreadLog.Write( L"null", L"OnDeleteThread: NULL thread object received (and ignored)." ); return; } pxThreadLog.Write(thr->GetName(), wxString(wxString(L"Thread object deleted successfully") + (thr->HasPendingException() ? L" [exception pending!]" : L"")).wc_str() ); thr->RethrowException(); } wxAppWithHelpers::wxAppWithHelpers() : m_IdleEventTimer( this ) { #ifdef __WXMSW__ // This variable assignment ensures that MSVC links in the TLS setup stubs even in // full optimization builds. Without it, DLLs that use TLS won't work because the // FS segment register won't have been initialized by the main exe, due to tls_insurance // being optimized away >_< --air static __threadlocal int tls_insurance = 0; tls_insurance = 1; #endif }