diff --git a/stepmania/src/arch/Threads/Threads.h b/stepmania/src/arch/Threads/Threads.h index 2eca122795..8a3b566676 100644 --- a/stepmania/src/arch/Threads/Threads.h +++ b/stepmania/src/arch/Threads/Threads.h @@ -42,6 +42,15 @@ public: virtual void Unlock() = 0; }; +class EventImpl +{ +public: + virtual ~EventImpl() { } + virtual void Wait() = 0; + virtual void Signal() = 0; + virtual void Broadcast() = 0; +}; + class SemaImpl { public: @@ -56,6 +65,7 @@ public: ThreadImpl *MakeThread( int (*fn)(void *), void *data, uint64_t *piThreadID ); ThreadImpl *MakeThisThread(); MutexImpl *MakeMutex( RageMutex *pParent ); +EventImpl *MakeEvent( MutexImpl *pMutex ); SemaImpl *MakeSemaphore( int iInitialValue ); uint64_t GetThisThreadId(); diff --git a/stepmania/src/arch/Threads/Threads_Win32.cpp b/stepmania/src/arch/Threads/Threads_Win32.cpp index 1784c12ce3..d6f130d1ee 100644 --- a/stepmania/src/arch/Threads/Threads_Win32.cpp +++ b/stepmania/src/arch/Threads/Threads_Win32.cpp @@ -255,6 +255,137 @@ MutexImpl *MakeMutex( RageMutex *pParent ) return new MutexImpl_Win32( pParent ); } +EventImpl_Win32::EventImpl_Win32( MutexImpl_Win32 *pParent ) +{ + m_pParent = pParent; + m_iNumWaiting = 0; + m_bNeedSignal = false; + m_WakeupSema = CreateSemaphore( NULL, 0, 0x7fffffff, NULL ); + InitializeCriticalSection( &m_iNumWaitingLock ); + m_WaitersDone = CreateEvent( NULL, FALSE, FALSE, NULL ); +} + +EventImpl_Win32::~EventImpl_Win32() +{ + ASSERT_M( m_iNumWaiting == 0, ssprintf("event destroyed while still in use (%i)", m_iNumWaiting) ); + + /* We don't own m_pParent; don't free it. */ + CloseHandle( m_WakeupSema ); + DeleteCriticalSection( &m_iNumWaitingLock ); + CloseHandle( m_WaitersDone ); +} + +/* SignalObjectAndWait is atomic, which leads to more fair event handling. However, + * we don't guarantee or depend upon fair events, and SignalObjectAndWait is only + * available in NT. I also can't find a single function to signal an object like + * SignalObjectAndWait, so we need to know if the object is a mutex or an event. */ +static void PortableSignalObjectAndWait( HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, bool bFirstParamIsMutex ) +{ + static bool bSignalObjectAndWaitUnavailable = false; + if( !bSignalObjectAndWaitUnavailable ) + { + DWORD ret = SignalObjectAndWait( hObjectToSignal, hObjectToWaitOn, INFINITE, false ); + switch( ret ) + { + case WAIT_OBJECT_0: + return; + + case WAIT_ABANDONED: + /* The docs aren't particular about what this does, but it should never happen. */ + FAIL_M( "WAIT_ABANDONED" ); + + case WAIT_FAILED: + if( GetLastError() == ERROR_CALL_NOT_IMPLEMENTED ) + { + /* We're probably on 9x. */ + bSignalObjectAndWaitUnavailable = true; + break; + } + + FAIL_M( werr_ssprintf(GetLastError(), "SignalObjectAndWait") ); + + default: + FAIL_M( "unknown" ); + } + } + + if( bFirstParamIsMutex ) + { + const bool bRet = !!ReleaseMutex( hObjectToSignal ); + if( !bRet ) + sm_crash( werr_ssprintf( GetLastError(), "ReleaseMutex failed" ) ); + } + else + SetEvent( hObjectToSignal ); + + WaitForSingleObject( hObjectToWaitOn, INFINITE ); +} + +/* Event logic from http://www.cs.wustl.edu/~schmidt/win32-cv-1.html. */ +void EventImpl_Win32::Wait() +{ + EnterCriticalSection( &m_iNumWaitingLock ); + ++m_iNumWaiting; + LeaveCriticalSection( &m_iNumWaitingLock ); + + /* Unlock the mutex and wait for a signal. */ + PortableSignalObjectAndWait( m_pParent->mutex, m_WakeupSema, true ); + + EnterCriticalSection( &m_iNumWaitingLock ); + --m_iNumWaiting; + bool bLastWaiting = m_bNeedSignal && m_iNumWaiting == 0; + LeaveCriticalSection( &m_iNumWaitingLock ); + + /* If m_bNeedSignal is set, and we're the last waiter to wake up, signal the + * broadcaster. */ + if( bLastWaiting ) + PortableSignalObjectAndWait( m_WaitersDone, m_pParent->mutex, false ); + else + WaitForSingleObject( m_pParent->mutex, INFINITE ); +} + +void EventImpl_Win32::Signal() +{ + EnterCriticalSection( &m_iNumWaitingLock ); + bool bHaveWaiters = (m_iNumWaiting > 0); + LeaveCriticalSection( &m_iNumWaitingLock ); + + if( bHaveWaiters ) + ReleaseSemaphore( m_WakeupSema, 1, 0 ); +} + +void EventImpl_Win32::Broadcast() +{ + EnterCriticalSection( &m_iNumWaitingLock ); + + if( m_iNumWaiting == 0 ) + { + LeaveCriticalSection( &m_iNumWaitingLock ); + return; + } + + /* Since we're broadcasting, we need to wait for all waiters to wake up and + * start waiting for the mutex before returning. */ + m_bNeedSignal = true; + + ReleaseSemaphore( m_WakeupSema, m_iNumWaiting, 0 ); + + LeaveCriticalSection( &m_iNumWaitingLock ); + + /* We set m_bNeedSignal; the last waiter will touch m_WaitersDone. Note that + * we still hold the parent mutex, so no other thread is currently broadcasting; + * we don't have to worry about m_bNeedSignal being set to false on us. */ + WaitForSingleObject( m_WaitersDone, INFINITE ); + m_bNeedSignal = false; +} + +EventImpl *MakeEvent( MutexImpl *pMutex ) +{ + MutexImpl_Win32 *pWin32Mutex = (MutexImpl_Win32 *) pMutex; + + return new EventImpl_Win32( pWin32Mutex ); +} + SemaImpl_Win32::SemaImpl_Win32( int iInitialValue ) { sem = CreateSemaphore( NULL, iInitialValue, 999999999, NULL ); diff --git a/stepmania/src/arch/Threads/Threads_Win32.h b/stepmania/src/arch/Threads/Threads_Win32.h index 7f095b4531..1d3a9f8706 100644 --- a/stepmania/src/arch/Threads/Threads_Win32.h +++ b/stepmania/src/arch/Threads/Threads_Win32.h @@ -27,6 +27,7 @@ HANDLE Win32ThreadIdToHandle( uint64_t iID ); class MutexImpl_Win32: public MutexImpl { + friend class EventImpl_Win32; public: MutexImpl_Win32( RageMutex *parent ); ~MutexImpl_Win32(); @@ -39,6 +40,26 @@ private: HANDLE mutex; }; +class EventImpl_Win32: public EventImpl +{ +public: + EventImpl_Win32( MutexImpl_Win32 *pParent ); + ~EventImpl_Win32(); + + void Wait(); + void Signal(); + void Broadcast(); + +private: + MutexImpl_Win32 *m_pParent; + + int m_iNumWaiting; + CRITICAL_SECTION m_iNumWaitingLock; + HANDLE m_WakeupSema; + HANDLE m_WaitersDone; + bool m_bNeedSignal; +}; + class SemaImpl_Win32: public SemaImpl { public: