Files
archived-pcsx2/pcsx2/gui/ExecutorThread.cpp
Gregory Hainaut 2ff43f2ed8 core: remove throw specifier on destructor
It is the 'default' on C++11
2017-05-13 10:38:35 +02:00

513 lines
14 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "App.h"
#include <memory>
using namespace pxSizerFlags;
// --------------------------------------------------------------------------------------
// ConsoleLogSource_Event (implementations)
// --------------------------------------------------------------------------------------
bool ConsoleLogSource_Event::Write( const pxEvtQueue* evtHandler, const SysExecEvent* evt, const wxChar* msg ) {
return _parent::Write( pxsFmt(L"(%s:%s) ", WX_STR(evtHandler->GetEventHandlerName()), WX_STR(evt->GetEventName())) + msg );
}
bool ConsoleLogSource_Event::Warn( const pxEvtQueue* evtHandler, const SysExecEvent* evt, const wxChar* msg ) {
return _parent::Write( pxsFmt(L"(%s:%s) ", WX_STR(evtHandler->GetEventHandlerName()), WX_STR(evt->GetEventName())) + msg );
}
bool ConsoleLogSource_Event::Error( const pxEvtQueue* evtHandler, const SysExecEvent* evt, const wxChar* msg ) {
return _parent::Write( pxsFmt(L"(%s:%s) ", WX_STR(evtHandler->GetEventHandlerName()), WX_STR(evt->GetEventName())) + msg );
}
ConsoleLogSource_Event::ConsoleLogSource_Event()
{
static const TraceLogDescriptor myDesc =
{
L"SysEvents", L"S&ysVM Control Events",
pxLt("Logs events as they are passed to the PS2 virtual machine."),
};
m_Descriptor = &myDesc;
}
ConsoleLogSource_Event pxConLog_Event;
// --------------------------------------------------------------------------------------
// SysExecEvent (implementations)
// --------------------------------------------------------------------------------------
wxString SysExecEvent::GetEventName() const
{
pxFail( "Warning: Unnamed SysExecutor Event! Please overload GetEventName() in all SysExecEvent derived classes." );
return wxEmptyString;
}
wxString SysExecEvent::GetEventMessage() const
{
return GetEventName();
}
// This is called from _DoInvokeEvent after various affinity and state checks have verified the
// message as executable. Override this when possible. Only override _DoInvokeEvent if you
// need some kind of additional low-level ability.
void SysExecEvent::InvokeEvent()
{
}
// This is called by _DoInvokeEvent *always* -- even when exceptions occur during InvokeEvent(),
// making this function a bit like a C# 'finally' block (try/catch/finally -- a nice feature lacking
// from C++ prior to the new C++0x standard).
//
// This function calls PostResult by default, and should be invoked by derived classes overriding
// CleanupEvent(), unless you want to change the PostResult behavior.
void SysExecEvent::CleanupEvent()
{
PostResult();
}
// Transports the specified exception to the thread/context that invoked this event.
// Events are run from a try/catch block in the event handler that automatically transports
// exceptions as neeeded, so there shouldn't be much need to use this method directly.
void SysExecEvent::SetException( BaseException* ex )
{
if( !ex ) return;
ex->DiagMsg() += pxsFmt(L"(%s) ", WX_STR(GetEventName()));
//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, 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.
wxGetApp().PostEvent( pxExceptionEvent( ex ) );
}
}
void SysExecEvent::SetException( const BaseException& ex )
{
SetException( ex.Clone() );
}
// This method calls _DoInvoke after performing some setup, exception handling, and
// affinity checks. For implementing behavior of your event, override _DoInvoke
// instead, which is the intended method of implementing derived class invocation.
void SysExecEvent::_DoInvokeEvent()
{
AffinityAssert_AllowFrom_SysExecutor();
try {
InvokeEvent();
}
catch( BaseException& ex )
{
SetException( ex );
}
catch( std::runtime_error& ex )
{
SetException( new Exception::RuntimeError(ex) );
}
// Cleanup Execution -- performed regardless of exception or not above.
try {
CleanupEvent();
}
catch( BaseException& ex )
{
SetException( ex );
}
catch( std::runtime_error& ex )
{
SetException( new Exception::RuntimeError(ex) );
}
}
// Posts an empty result to the invoking context/thread of this message, if one exists.
// If the invoking thread posted the event in non-blocking fashion then no action is
// taken.
void SysExecEvent::PostResult() const
{
if( m_sync ) m_sync->PostResult();
}
// --------------------------------------------------------------------------------------
// pxEvtQueue Implementations
// --------------------------------------------------------------------------------------
pxEvtQueue::pxEvtQueue() :
m_OwnerThreadId(), m_Quitting(false), m_qpc_Start(0)
{
}
// Puts the event queue into Shutdown mode, which does *not* immediately stop nor cancel
// the queue's processing. Instead it marks the queue inaccessible to all new events
// and continues procesing queued events for critical events that should not be ignored.
// (typically these are shutdown events critical to closing the app cleanly). Once
// all such events have been processed, the thread is stopped.
//
void pxEvtQueue::ShutdownQueue()
{
if( m_Quitting ) return;
m_Quitting = true;
m_wakeup.Post();
}
struct ScopedThreadCancelDisable
{
ScopedThreadCancelDisable()
{
int oldstate;
pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &oldstate );
}
~ScopedThreadCancelDisable()
{
int oldstate;
pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &oldstate );
}
};
// isIdle - parameter is useful for logging only (currently)
void pxEvtQueue::ProcessEvents( pxEvtList& list, bool isIdle )
{
ScopedLock synclock( m_mtx_pending );
pxEvtList::iterator node;
while( node = list.begin(), node != list.end() )
{
std::unique_ptr<SysExecEvent> deleteMe(*node);
list.erase( node );
if( !m_Quitting || deleteMe->IsCriticalEvent() )
{
// Some messages can be blocking, so we should release the mutex lock while
// processing, to avoid having cases where the main thread deadlocks simply
// trying to add a message to the queue due to the basic mutex acquire needed.
m_qpc_Start = GetCPUTicks();
synclock.Release();
pxEvtLog.Write( this, deleteMe.get(), wxsFormat(L"Executing... [%s]%s",
deleteMe->AllowCancelOnExit() ? L"Cancelable" : L"Noncancelable", isIdle ? L"(Idle)" : wxEmptyString).wc_str()
);
if( deleteMe->AllowCancelOnExit() )
deleteMe->_DoInvokeEvent();
else
{
ScopedThreadCancelDisable thr_cancel_scope;
deleteMe->_DoInvokeEvent();
}
u64 qpc_end = GetCPUTicks();
pxEvtLog.Write( this, deleteMe.get(), wxsFormat(L"Event completed in %ums",
(u32)(((qpc_end-m_qpc_Start)*1000) / GetTickFrequency())).wc_str()
);
synclock.Acquire();
m_qpc_Start = 0; // lets the main thread know the message completed.
}
else
{
pxEvtLog.Write( this, deleteMe.get(), L"Skipping Event: %s" );
deleteMe->PostResult();
}
}
}
void pxEvtQueue::ProcessIdleEvents()
{
ProcessEvents( m_idleEvents, true );
}
void pxEvtQueue::ProcessPendingEvents()
{
ProcessEvents( m_pendingEvents );
}
// This method is provided for wxWidgets API conformance. I like to use PostEvent instead
// since it's reminiscent of PostMessage in Windows (and behaves rather similarly).
void pxEvtQueue::AddPendingEvent( SysExecEvent& evt )
{
PostEvent( evt );
}
// Adds an event to the event queue in non-blocking fashion. The thread executing this
// event queue will be woken up if it's idle/sleeping.
// IMPORTANT: The pointer version of this function will *DELETE* the event object passed
// to it automatically when the event has been executed. If you are using a scoped event
// you should use the Reference/Handle overload instead!
//
void pxEvtQueue::PostEvent( SysExecEvent* evt )
{
std::unique_ptr<SysExecEvent> sevt(evt);
if( !sevt ) return;
if( m_Quitting )
{
sevt->PostResult();
return;
}
ScopedLock synclock( m_mtx_pending );
pxEvtLog.Write( this, evt, pxsFmt(L"Posting event! (pending=%d, idle=%d)", m_pendingEvents.size(), m_idleEvents.size()) );
m_pendingEvents.push_back( sevt.release() );
if( m_pendingEvents.size() == 1)
m_wakeup.Post();
}
void pxEvtQueue::PostEvent( const SysExecEvent& evt )
{
PostEvent( evt.Clone() );
}
void pxEvtQueue::PostIdleEvent( SysExecEvent* evt )
{
if( !evt ) return;
if( m_Quitting )
{
evt->PostResult();
return;
}
ScopedLock synclock( m_mtx_pending );
pxEvtLog.Write( this, evt, pxsFmt(L"Posting event! (pending=%d, idle=%d) [idle]", m_pendingEvents.size(), m_idleEvents.size()) );
if( m_pendingEvents.empty() )
{
m_pendingEvents.push_back( evt );
m_wakeup.Post();
}
else
m_idleEvents.push_back( evt );
}
void pxEvtQueue::PostIdleEvent( const SysExecEvent& evt )
{
PostIdleEvent( evt.Clone() );
}
void pxEvtQueue::ProcessEvent( SysExecEvent& evt )
{
if( wxThread::GetCurrentId() != m_OwnerThreadId )
{
SynchronousActionState sync;
evt.SetSyncState( sync );
PostEvent( evt );
sync.WaitForResult();
}
else
evt._DoInvokeEvent();
}
void pxEvtQueue::ProcessEvent( SysExecEvent* evt )
{
if( !evt ) return;
if( wxThread::GetCurrentId() != m_OwnerThreadId )
{
SynchronousActionState sync;
evt->SetSyncState( sync );
PostEvent( evt );
sync.WaitForResult();
}
else
{
std::unique_ptr<SysExecEvent> deleteMe(evt);
deleteMe->_DoInvokeEvent();
}
}
bool pxEvtQueue::Rpc_TryInvokeAsync( FnType_Void* method, const wxChar* traceName )
{
if( wxThread::GetCurrentId() != m_OwnerThreadId )
{
PostEvent( new SysExecEvent_MethodVoid(method, traceName) );
return true;
}
return false;
}
bool pxEvtQueue::Rpc_TryInvoke( FnType_Void* method, const wxChar* traceName )
{
if( wxThread::GetCurrentId() != m_OwnerThreadId )
{
SynchronousActionState sync;
SysExecEvent_MethodVoid evt(method);
evt.Critical();
evt.SetSyncState( sync );
PostEvent( evt );
sync.WaitForResult();
return true;
}
return false;
}
// This method invokes the derived class Idle implementations (if any) and then enters
// the sleep state until such time that new messages are received.
//
// Extending: Derived classes should override _DoIdle instead, unless it is necessary
// to implement post-wakeup behavior.
//
void pxEvtQueue::Idle()
{
ProcessIdleEvents();
_DoIdle();
m_wakeup.WaitWithoutYield();
}
void pxEvtQueue::SetActiveThread()
{
m_OwnerThreadId = wxThread::GetCurrentId();
}
// --------------------------------------------------------------------------------------
// ExecutorThread Implementations
// --------------------------------------------------------------------------------------
ExecutorThread::ExecutorThread(pxEvtQueue* evthandler)
: m_EvtHandler(evthandler)
{
}
bool ExecutorThread::IsRunning() const
{
if( !m_EvtHandler ) return false;
return( !m_EvtHandler->IsShuttingDown() );
}
// Exposes the internal pxEvtQueue::ShutdownQueue API. See pxEvtQueue for details.
void ExecutorThread::ShutdownQueue()
{
if( !m_EvtHandler ) return;
if( !m_EvtHandler->IsShuttingDown() )
m_EvtHandler->ShutdownQueue();
Block();
}
// Exposes the internal pxEvtQueue::PostEvent API. See pxEvtQueue for details.
void ExecutorThread::PostEvent( SysExecEvent* evt )
{
if( !pxAssert( m_EvtHandler ) ) { delete evt; return; }
m_EvtHandler->PostEvent( evt );
}
void ExecutorThread::PostEvent( const SysExecEvent& evt )
{
if( !pxAssert( m_EvtHandler ) ) return;
m_EvtHandler->PostEvent( evt );
}
// Exposes the internal pxEvtQueue::PostIdleEvent API. See pxEvtQueue for details.
void ExecutorThread::PostIdleEvent( SysExecEvent* evt )
{
if( !pxAssert( m_EvtHandler ) ) return;
m_EvtHandler->PostIdleEvent( evt );
}
void ExecutorThread::PostIdleEvent( const SysExecEvent& evt )
{
if( !pxAssert( m_EvtHandler ) ) return;
m_EvtHandler->PostIdleEvent( evt );
}
// Exposes the internal pxEvtQueue::ProcessEvent API. See pxEvtQueue for details.
void ExecutorThread::ProcessEvent( SysExecEvent* evt )
{
if( m_EvtHandler )
m_EvtHandler->ProcessEvent( evt );
else
{
std::unique_ptr<SysExecEvent> deleteMe(evt);
deleteMe->_DoInvokeEvent();
}
}
void ExecutorThread::ProcessEvent( SysExecEvent& evt )
{
if( m_EvtHandler )
m_EvtHandler->ProcessEvent( evt );
else
evt._DoInvokeEvent();
}
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 task.
void Pcsx2App::OnSysExecutorTaskTimeout( wxTimerEvent& evt )
{
if( !SysExecutorThread.IsRunning() ) return;
//BaseThreadedInvocation* task = SysExecutorThread.GetTask();
//if( !task ) return;
//task->ShowModalStatus();
}