/* 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" wxDEFINE_EVENT(pxEvt_StartIdleEventTimer, wxCommandEvent); wxDEFINE_EVENT(pxEvt_DeleteObject, wxCommandEvent); wxDEFINE_EVENT(pxEvt_DeleteThread, wxCommandEvent); wxDEFINE_EVENT(pxEvt_InvokeAction, pxActionEvent); wxDEFINE_EVENT(pxEvt_SynchronousCommand, pxSynchronousCommandEvent); wxIMPLEMENT_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 // -------------------------------------------------------------------------------------- wxIMPLEMENT_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 // -------------------------------------------------------------------------------------- wxIMPLEMENT_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 { wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(pxRpcEvent); typedef pxActionEvent _parent; protected: void (*m_Method)(); public: virtual ~pxRpcEvent() = default; 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(); } }; wxIMPLEMENT_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). // wxIMPLEMENT_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.empty()) PostEvent(wxCommandEvent(pxEvt_StartIdleEventTimer)); m_IdleEventQueue.push_back(evt.Clone()); } void wxAppWithHelpers::OnStartIdleEventTimer(wxCommandEvent &evt) { ScopedLock lock(m_IdleEventMutex); if (!m_IdleEventQueue.empty()) 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.empty()) 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); } bool wxAppWithHelpers::OnInit() { Bind(pxEvt_SynchronousCommand, &wxAppWithHelpers::OnSynchronousCommand, this); Bind(pxEvt_InvokeAction, &wxAppWithHelpers::OnInvokeAction, this); Bind(pxEvt_StartIdleEventTimer, &wxAppWithHelpers::OnStartIdleEventTimer, this); Bind(pxEvt_DeleteObject, &wxAppWithHelpers::OnDeleteObject, this); Bind(pxEvt_DeleteThread, &wxAppWithHelpers::OnDeleteThread, this); Bind(wxEVT_IDLE, &wxAppWithHelpers::OnIdleEvent, this); Bind(wxEVT_TIMER, &wxAppWithHelpers::OnIdleEventTimeout, this, m_IdleEventTimer.GetId()); 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 thread_local int tls_insurance = 0; tls_insurance = 1; #endif }