//========== Copyright 2005, Valve Corporation, All rights reserved. ======== // // Purpose: // //============================================================================= #include "tier0/platform.h" #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #define _WIN32_WINNT 0x0403 #include #endif #ifdef _WIN32 #include #include #pragma comment(lib, "winmm.lib") #include "tier0/vcrmode.h" #elif POSIX #include #include #include #include #include #include #define GetLastError() errno typedef void *LPVOID; #if !defined(OSX) #if defined(ANDROID) #include #include #else #include #include #endif #define sem_unlink( arg ) #define OS_TO_PTHREAD(x) (x) #else #define pthread_yield pthread_yield_np #include #include #define OS_TO_PTHREAD(x) pthread_from_mach_thread_np( x ) #endif // !OSX #endif #ifndef _PS3 #include #endif #include "tier0/minidump.h" #include "tier0/threadtools.h" #include "tier0/dynfunction.h" #ifdef _X360 #include "xbox/xbox_win32stubs.h" #endif #include // Must be last header... #include "tier0/memdbgon.h" #ifdef _PS3 #include "ps3/ps3_win32stubs.h" #define NEW_WAIT_FOR_MULTIPLE_OBJECTS bool gbCheckNotMultithreaded = true; extern "C" void(*g_pfnPushMarker)( const char * pName ); extern "C" void(*g_pfnPopMarker)(); #endif #define THREADS_DEBUG 1 #define DEBUG_ERROR(XX) Assert(0) // Need to ensure initialized before other clients call in for main thread ID #ifdef _WIN32 #pragma warning(disable:4073) #pragma init_seg(lib) #endif #ifdef _WIN32 ASSERT_INVARIANT(TT_SIZEOF_CRITICALSECTION == sizeof(CRITICAL_SECTION)); ASSERT_INVARIANT(TT_INFINITE == INFINITE); #endif // thread creation counter. // this is used to provide a unique threadid for each running thread in g_nThreadID ( a thread local variable ). const int MAX_THREAD_IDS = 128; static volatile bool s_bThreadIDAllocated[MAX_THREAD_IDS]; #if defined(_PS3) #include "tls_ps3.h" #else DLL_CLASS_EXPORT CTHREADLOCALINT g_nThreadID; #endif static CThreadFastMutex s_ThreadIDMutex; PLATFORM_INTERFACE void AllocateThreadID( void ) { AUTO_LOCK( s_ThreadIDMutex ); for( int i = 1; i < MAX_THREAD_IDS; i++ ) { if ( ! s_bThreadIDAllocated[i] ) { g_nThreadID = i; s_bThreadIDAllocated[i] = true; return; } } Error( "Out of thread ids. Decrease the number of threads or increase MAX_THREAD_IDS\n" ); } PLATFORM_INTERFACE void FreeThreadID( void ) { AUTO_LOCK( s_ThreadIDMutex ); int nThread = g_nThreadID; if ( nThread ) s_bThreadIDAllocated[nThread] = false; } //----------------------------------------------------------------------------- // Simple thread functions. // Because _beginthreadex uses stdcall, we need to convert to cdecl //----------------------------------------------------------------------------- struct ThreadProcInfo_t { ThreadProcInfo_t( ThreadFunc_t pfnThread, void *pParam ) : pfnThread( pfnThread), pParam( pParam ) { } ThreadFunc_t pfnThread; void * pParam; }; //--------------------------------------------------------- #ifdef _WIN32 static DWORD WINAPI ThreadProcConvert( void *pParam ) { ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); AllocateThreadID(); delete ((ThreadProcInfo_t *)pParam); unsigned nRet = (*info.pfnThread)(info.pParam); FreeThreadID(); return nRet; } #elif defined( PS3 ) union ThreadProcInfoUnion_t { struct Val_t { ThreadFunc_t pfnThread; void * pParam; } val; uint64_t val64; }; static void ThreadProcConvertUnion( uint64_t param ) { COMPILE_TIME_ASSERT( sizeof( ThreadProcInfoUnion_t ) == 8 ); ThreadProcInfoUnion_t info; info.val64 = param; AllocateThreadID(); unsigned nRet = (*info.val.pfnThread)(info.val.pParam); FreeThreadID(); sys_ppu_thread_exit( nRet ); } static void* ThreadProcConvert( void *pParam ) { ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); AllocateThreadID(); delete ((ThreadProcInfo_t *)pParam); unsigned nRet = (*info.pfnThread)(info.pParam); FreeThreadID(); return ( void * ) nRet; } #else static void* ThreadProcConvert( void *pParam ) { ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); AllocateThreadID(); delete ((ThreadProcInfo_t *)pParam); unsigned nRet = (*info.pfnThread)(info.pParam); FreeThreadID(); return ( void * ) (uintp) nRet; } #endif #if defined( _PS3 ) /******************************************************************************* * Thread Local Storage globals and functions *******************************************************************************/ #ifndef _PS3 __thread void *gTLSValues[ MAX_TLS_VALUES ] = { NULL }; __thread bool gTLSFlags[ MAX_TLS_VALUES ] = { false }; __thread bool gbWaitObjectsCreated = false; __thread sys_semaphore_t gWaitObjectsSemaphore; #endif // !_PS3 static char gThreadName[28] = ""; // Simple TLS allocator. Linearly searches for a free slot. uint32 TlsAlloc() { for ( int i = 0; i < MAX_TLS_VALUES; ++i ) { if ( !gTLSFlags[i] ) { gTLSFlags[i] = true; return i; } } #ifdef _PS3 DEBUG_ERROR("TlsAlloc(): Out of TLS\n"); #endif return 0xFFFFFFFF; } void TlsFree( uint32 index ) { gTLSValues[ index ] = NULL; gTLSFlags[ index ] = false; } void *TlsGetValue( uint32 index ) { return gTLSValues[ index ]; } void TlsSetValue( uint32 index, void *pValue ) { gTLSValues[ index ] = pValue; } #endif //_PS3 #ifdef _WIN32 class CThreadHandleToIDMap { public: HANDLE m_hThread; uint m_ThreadID; CThreadHandleToIDMap *m_pNext; }; static CThreadHandleToIDMap *g_pThreadHandleToIDMaps = NULL; static CThreadMutex g_ThreadHandleToIDMapMutex; static volatile int g_nThreadHandleToIDMaps = 0; static void AddThreadHandleToIDMap( HANDLE hThread, uint threadID ) { if ( !hThread ) return; // Remember this handle/id combo. CThreadHandleToIDMap *pMap = new CThreadHandleToIDMap; pMap->m_hThread = hThread; pMap->m_ThreadID = threadID; // Add it to the global list. g_ThreadHandleToIDMapMutex.Lock(); pMap->m_pNext = g_pThreadHandleToIDMaps; g_pThreadHandleToIDMaps = pMap; ++g_nThreadHandleToIDMaps; g_ThreadHandleToIDMapMutex.Unlock(); if ( g_nThreadHandleToIDMaps > 500 ) Error( "ThreadHandleToIDMap overflow." ); } // This assumes you've got g_ThreadHandleToIDMapMutex locked!! static bool InternalLookupHandleToThreadIDMap( HANDLE hThread, CThreadHandleToIDMap* &pMap, CThreadHandleToIDMap** &ppPrev ) { ppPrev = &g_pThreadHandleToIDMaps; for ( pMap=g_pThreadHandleToIDMaps; pMap; pMap=pMap->m_pNext ) { if ( pMap->m_hThread == hThread ) return true; ppPrev = &pMap->m_pNext; } return false; } static void RemoveThreadHandleToIDMap( HANDLE hThread ) { if ( !hThread ) return; CThreadHandleToIDMap *pMap, **ppPrev; g_ThreadHandleToIDMapMutex.Lock(); if ( g_nThreadHandleToIDMaps <= 0 ) Error( "ThreadHandleToIDMap underflow." ); if ( InternalLookupHandleToThreadIDMap( hThread, pMap, ppPrev ) ) { *ppPrev = pMap->m_pNext; delete pMap; --g_nThreadHandleToIDMaps; } g_ThreadHandleToIDMapMutex.Unlock(); } static uint LookupThreadIDFromHandle( HANDLE hThread ) { if ( hThread == NULL || hThread == GetCurrentThread() ) return GetCurrentThreadId(); float flStartTime = Plat_FloatTime(); while ( Plat_FloatTime() - flStartTime < 2 ) { CThreadHandleToIDMap *pMap, **ppPrev; g_ThreadHandleToIDMapMutex.Lock(); bool bRet = InternalLookupHandleToThreadIDMap( hThread, pMap, ppPrev ); g_ThreadHandleToIDMapMutex.Unlock(); if ( bRet ) return pMap->m_ThreadID; // We should only get here if a thread that is just starting up is currently in AddThreadHandleToIDMap. // Give up the timeslice and try again. ThreadSleep( 1 ); } Assert( !"LookupThreadIDFromHandle failed!" ); Warning( "LookupThreadIDFromHandle couldn't find thread ID for handle." ); return 0; } #endif //--------------------------------------------------------- ThreadHandle_t * CreateTestThreads( ThreadFunc_t fnThread, int numThreads, int nProcessorsToDistribute ) { ThreadHandle_t *pHandles = (new ThreadHandle_t[numThreads+1]) + 1; pHandles[-1] = (ThreadHandle_t)INT_TO_POINTER( numThreads ); for( int i = 0; i < numThreads; ++i ) { //TestThreads(); ThreadHandle_t hThread; const unsigned int nDefaultStackSize = 64 * 1024; // this stack size is used in case stackSize == 0 hThread = CreateSimpleThread( fnThread, INT_TO_POINTER( i ), nDefaultStackSize ); if ( nProcessorsToDistribute ) { int32 mask = 1 << (i % nProcessorsToDistribute); ThreadSetAffinity( hThread, mask ); } /* ThreadProcInfoUnion_t info; info.val.pfnThread = fnThread; info.val.pParam = (void*)(i); if ( int nError = sys_ppu_thread_create( &hThread, ThreadProcConvertUnion, info.val64, 1001, nDefaultStackSize, SYS_PPU_THREAD_CREATE_JOINABLE, "SimpleThread" ) != CELL_OK ) { printf( "PROBLEM!\n" ); Error( "Cannot create thread, error %d\n", nError ); return 0; } */ //ThreadHandle_t hThread = CreateSimpleThread( fnThread, (void*)i ); pHandles[i] = hThread; } // printf("Finishinged CreateTestThreads(%p,%d)\n", (void*)fnThread, numThreads ); return pHandles; } void JoinTestThreads( ThreadHandle_t *pHandles ) { int nCount = POINTER_TO_INT( (uintp)pHandles[-1] ); // printf("Joining test threads @%p[%d]:\n", pHandles, nCount ); // for( int i = 0; i < nCount; ++i ) // { // printf(" %p,\n", (void*)pHandles[i] ); // } for( int i = 0; i < nCount; ++i ) { // printf( "Joining %p", (void*) pHandles[i] ); // if( !i ) sys_timer_usleep(100000); ThreadJoin( pHandles[i] ); ReleaseThreadHandle( pHandles[i] ); } delete[]( pHandles - 1 ); } ThreadHandle_t CreateSimpleThread( ThreadFunc_t pfnThread, void *pParam, unsigned stackSize ) { #ifdef _WIN32 DWORD threadID; HANDLE hThread = (HANDLE)CreateThread( NULL, stackSize, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ), stackSize ? STACK_SIZE_PARAM_IS_A_RESERVATION : 0, &threadID ); AddThreadHandleToIDMap( hThread, threadID ); return (ThreadHandle_t)hThread; #elif PS3 //TestThreads(); ThreadHandle_t th; ThreadProcInfoUnion_t info; info.val.pfnThread = pfnThread; info.val.pParam = pParam; const unsigned int nDefaultStackSize = 64 * 1024; // this stack size is used in case stackSize == 0 if ( sys_ppu_thread_create( &th, ThreadProcConvertUnion, info.val64, 1001, stackSize ? stackSize : nDefaultStackSize, SYS_PPU_THREAD_CREATE_JOINABLE, "SimpleThread" ) != CELL_OK ) { AssertMsg1( 0, "Failed to create thread (error 0x%x)", errno ); return 0; } return th; #elif POSIX pthread_t tid; pthread_create( &tid, NULL, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ) ); return ( ThreadHandle_t ) tid; #else Assert( 0 ); DebuggerBreak(); return 0; #endif } ThreadHandle_t CreateSimpleThread( ThreadFunc_t pfnThread, void *pParam, ThreadId_t *pID, unsigned stackSize ) { #ifdef _WIN32 DWORD threadID; HANDLE hThread = (HANDLE)CreateThread( NULL, stackSize, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ), stackSize ? STACK_SIZE_PARAM_IS_A_RESERVATION : 0, &threadID ); if( pID ) *pID = (ThreadId_t)threadID; AddThreadHandleToIDMap( hThread, threadID ); return (ThreadHandle_t)hThread; #elif POSIX pthread_t tid; pthread_create( &tid, NULL, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ) ); if( pID ) *pID = (ThreadId_t)tid; return ( ThreadHandle_t ) tid; #else Assert( 0 ); DebuggerBreak(); return 0; #endif } bool ReleaseThreadHandle( ThreadHandle_t hThread ) { #ifdef _WIN32 bool bRetVal = ( CloseHandle( hThread ) != 0 ); RemoveThreadHandleToIDMap( (HANDLE)hThread ); return bRetVal; #else return true; #endif } //----------------------------------------------------------------------------- // // Wrappers for other simple threading operations // //----------------------------------------------------------------------------- void ThreadSleep(unsigned nMilliseconds) { #ifdef _WIN32 #ifdef _WIN32_PC static bool bInitialized = false; if ( !bInitialized ) { bInitialized = true; // Set the timer resolution to 1 ms (default is 10.0, 15.6, 2.5, 1.0 or // some other value depending on hardware and software) so that we can // use Sleep( 1 ) to avoid wasting CPU time without missing our frame // rate. timeBeginPeriod( 1 ); } #endif Sleep( nMilliseconds ); #elif PS3 if( nMilliseconds == 0 ) { // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. // sys_timer_usleep( 60 ); sys_ppu_thread_yield(); } else { sys_timer_usleep( nMilliseconds * 1000 ); } #elif defined(POSIX) usleep( nMilliseconds * 1000 ); #endif } //----------------------------------------------------------------------------- void ThreadNanoSleep(unsigned ns) { #ifdef _WIN32 // ceil Sleep( ( ns + 999 ) / 1000 ); #elif PS3 sys_timer_usleep( ns ); #elif defined(POSIX) struct timespec tm; tm.tv_sec = 0; tm.tv_nsec = ns; nanosleep( &tm, NULL ); #endif } //----------------------------------------------------------------------------- #ifndef ThreadGetCurrentId ThreadId_t ThreadGetCurrentId() { #ifdef _WIN32 return GetCurrentThreadId(); #elif defined( _PS3 ) sys_ppu_thread_t th = 0; sys_ppu_thread_get_id( &th ); return th; #elif defined(POSIX) return (ThreadId_t)pthread_self(); #else Assert(0); DebuggerBreak(); return 0; #endif } #endif //----------------------------------------------------------------------------- ThreadHandle_t ThreadGetCurrentHandle() { #ifdef _WIN32 return (ThreadHandle_t)GetCurrentThread(); #elif defined( _PS3 ) sys_ppu_thread_t th = 0; sys_ppu_thread_get_id( &th ); return th; #elif defined(POSIX) return (ThreadHandle_t)pthread_self(); #else Assert(0); DebuggerBreak(); return 0; #endif } // On PS3, this will return true for zombie threads bool ThreadIsThreadIdRunning( ThreadId_t uThreadId ) { #ifdef _WIN32 bool bRunning = true; HANDLE hThread = ::OpenThread( THREAD_QUERY_INFORMATION , false, uThreadId ); if ( hThread ) { DWORD dwExitCode; if( !::GetExitCodeThread( hThread, &dwExitCode ) || dwExitCode != STILL_ACTIVE ) bRunning = false; CloseHandle( hThread ); } else { bRunning = false; } return bRunning; #elif defined( _PS3 ) // will return CELL_OK for zombie threads int priority; return (sys_ppu_thread_get_priority( uThreadId, &priority ) == CELL_OK ); #elif defined(POSIX) int iResult = pthread_kill( OS_TO_PTHREAD(uThreadId), 0 ); if ( iResult == 0 ) return true; return false; #endif } //----------------------------------------------------------------------------- int ThreadGetPriority( ThreadHandle_t hThread ) { if ( !hThread ) { hThread = ThreadGetCurrentHandle(); } #ifdef _WIN32 return ::GetThreadPriority( (HANDLE)hThread ); #elif defined( _PS3 ) int iPri = 0; sys_ppu_thread_get_priority( hThread, &iPri ); return iPri; #else return 0; #endif } //----------------------------------------------------------------------------- bool ThreadSetPriority( ThreadHandle_t hThread, int priority ) { if ( !hThread ) { hThread = ThreadGetCurrentHandle(); } #ifdef _WIN32 return ( SetThreadPriority(hThread, priority) != 0 ); #elif defined( _PS3 ) int retval = sys_ppu_thread_set_priority( hThread, priority ); return retval >= CELL_OK; #elif defined(POSIX) struct sched_param thread_param; thread_param.sched_priority = priority; //pthread_setschedparam( (pthread_t ) hThread, SCHED_RR, &thread_param ); return true; #endif } //----------------------------------------------------------------------------- void ThreadSetAffinity( ThreadHandle_t hThread, int nAffinityMask ) { if ( !hThread ) { hThread = ThreadGetCurrentHandle(); } #ifdef _WIN32 SetThreadAffinityMask( hThread, nAffinityMask ); #elif defined(POSIX) // cpu_set_t cpuSet; // CPU_ZERO( cpuSet ); // for( int i = 0 ; i < 32; i++ ) // if ( nAffinityMask & ( 1 << i ) ) // CPU_SET( cpuSet, i ); // sched_setaffinity( hThread, sizeof( cpuSet ), &cpuSet ); #endif } //----------------------------------------------------------------------------- #ifndef _X360 ThreadId_t InitMainThread() { ThreadSetDebugName( "MainThrd" ); return ThreadGetCurrentId(); } ThreadId_t g_ThreadMainThreadID = InitMainThread(); bool ThreadInMainThread() { return ( ThreadGetCurrentId() == g_ThreadMainThreadID ); } void DeclareCurrentThreadIsMainThread() { g_ThreadMainThreadID = ThreadGetCurrentId(); } #else byte *InitMainThread() { byte b; return AlignValue( &b, 64*1024 ); } #define STACK_SIZE_360 327680 byte *g_pBaseMainStack = InitMainThread(); byte *g_pLimitMainStack = InitMainThread() - STACK_SIZE_360; #endif //----------------------------------------------------------------------------- bool ThreadJoin( ThreadHandle_t hThread, unsigned timeout ) { if ( !hThread ) { return false; } #ifdef _WIN32 DWORD dwWait = WaitForSingleObject( (HANDLE)hThread, timeout ); if ( dwWait == WAIT_TIMEOUT) return false; if ( dwWait != WAIT_OBJECT_0 && ( dwWait != WAIT_FAILED && GetLastError() != 0 ) ) { Assert( 0 ); return false; } #elif defined( _PS3 ) uint64 uiExitCode = 0; int retval = sys_ppu_thread_join( hThread, &uiExitCode ); return ( retval >= CELL_OK ); #elif defined(POSIX) if ( pthread_join( (pthread_t)hThread, NULL ) != 0 ) return false; #else Assert(0); DebuggerBreak(); #endif return true; } //----------------------------------------------------------------------------- void ThreadSetDebugName( ThreadHandle_t hThread, const char *pszName ) { #ifdef WIN32 if ( Plat_IsInDebugSession() ) { #define MS_VC_EXCEPTION 0x406d1388 typedef struct tagTHREADNAME_INFO { DWORD dwType; // must be 0x1000 LPCSTR szName; // pointer to name (in same addr space) DWORD dwThreadID; // thread ID (-1 caller thread) DWORD dwFlags; // reserved for future use, most be zero } THREADNAME_INFO; THREADNAME_INFO info; info.dwType = 0x1000; info.szName = pszName; info.dwThreadID = LookupThreadIDFromHandle( hThread ); if ( info.dwThreadID != 0 ) { info.dwFlags = 0; __try { RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info); } __except (EXCEPTION_CONTINUE_EXECUTION) { } } } #endif } //----------------------------------------------------------------------------- #ifdef _WIN32 ASSERT_INVARIANT( TW_FAILED == WAIT_FAILED ); ASSERT_INVARIANT( TW_TIMEOUT == WAIT_TIMEOUT ); ASSERT_INVARIANT( WAIT_OBJECT_0 == 0 ); int ThreadWaitForObjects( int nEvents, const HANDLE *pHandles, bool bWaitAll, unsigned timeout ) { return VCRHook_WaitForMultipleObjects( nEvents, pHandles, bWaitAll, timeout ); } #endif //----------------------------------------------------------------------------- // Used to thread LoadLibrary on the 360 //----------------------------------------------------------------------------- static ThreadedLoadLibraryFunc_t s_ThreadedLoadLibraryFunc = 0; PLATFORM_INTERFACE void SetThreadedLoadLibraryFunc( ThreadedLoadLibraryFunc_t func ) { s_ThreadedLoadLibraryFunc = func; } PLATFORM_INTERFACE ThreadedLoadLibraryFunc_t GetThreadedLoadLibraryFunc() { return s_ThreadedLoadLibraryFunc; } //----------------------------------------------------------------------------- // // CThreadSyncObject (note nothing uses this directly (I think) ) // //----------------------------------------------------------------------------- #ifdef _PS3 uint32_t CThreadSyncObject::m_bstaticMutexInitialized = false; uint32_t CThreadSyncObject::m_bstaticMutexInitializing = false; sys_lwmutex_t CThreadSyncObject::m_staticMutex; #endif CThreadSyncObject::CThreadSyncObject() #ifdef _WIN32 : m_hSyncObject( NULL ), m_bCreatedHandle(false) #elif defined(POSIX) && !defined(PS3) : m_bInitalized( false ) #endif { #ifdef _PS3 //Do we nee to initialise the staticMutex? if (m_bstaticMutexInitialized) return; //If we are the first thread then create the mutex if ( cellAtomicCompareAndSwap32(&m_bstaticMutexInitializing, false, true) == false ) { sys_lwmutex_attribute_t mutexAttr; sys_lwmutex_attribute_initialize( mutexAttr ); mutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; int err = sys_lwmutex_create( &m_staticMutex, &mutexAttr ); Assert(err == CELL_OK); m_bstaticMutexInitialized = true; } else { //Another thread is already in the process of initialising the mutex, wait for it while ( !m_bstaticMutexInitialized ) { // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. // sys_timer_usleep( 60 ); sys_ppu_thread_yield(); } } #endif } //--------------------------------------------------------- CThreadSyncObject::~CThreadSyncObject() { #ifdef _WIN32 if ( m_hSyncObject && m_bCreatedHandle ) { if ( !CloseHandle(m_hSyncObject) ) { Assert( 0 ); } } #elif defined(POSIX) && !defined( PS3 ) if ( m_bInitalized ) { pthread_cond_destroy( &m_Condition ); pthread_mutex_destroy( &m_Mutex ); m_bInitalized = false; } #endif } //--------------------------------------------------------- bool CThreadSyncObject::operator!() const { #if PS3 return m_bstaticMutexInitialized; #elif defined( _WIN32 ) return !m_hSyncObject; #elif defined(POSIX) return !m_bInitalized; #endif } //--------------------------------------------------------- void CThreadSyncObject::AssertUseable() { #ifdef THREADS_DEBUG #if PS3 AssertMsg( m_bstaticMutexInitialized, "Thread synchronization object is unuseable" ); #elif defined( _WIN32 ) AssertMsg( m_hSyncObject, "Thread synchronization object is unuseable" ); #elif defined(POSIX) AssertMsg( m_bInitalized, "Thread synchronization object is unuseable" ); #endif #endif } //--------------------------------------------------------- #if defined(_WIN32) || ( defined(POSIX) && !defined( _PS3 ) ) bool CThreadSyncObject::Wait( uint32 dwTimeout ) { #ifdef THREADS_DEBUG AssertUseable(); #endif #ifdef _WIN32 return ( WaitForSingleObject( m_hSyncObject, dwTimeout ) == WAIT_OBJECT_0 ); #elif defined( POSIX ) && !defined( PS3 ) pthread_mutex_lock( &m_Mutex ); bool bRet = false; if ( m_cSet > 0 ) { bRet = true; m_bWakeForEvent = false; } else { volatile int ret = 0; while ( !m_bWakeForEvent && ret != ETIMEDOUT ) { struct timeval tv; gettimeofday( &tv, NULL ); volatile struct timespec tm; uint64 actualTimeout = dwTimeout; if ( dwTimeout == TT_INFINITE && m_bManualReset ) actualTimeout = 10; // just wait 10 msec at most for manual reset events and loop instead volatile uint64 nNanoSec = (uint64)tv.tv_usec*1000 + (uint64)actualTimeout*1000000; tm.tv_sec = tv.tv_sec + nNanoSec /1000000000; tm.tv_nsec = nNanoSec % 1000000000; do { ret = pthread_cond_timedwait( &m_Condition, &m_Mutex, (const timespec *)&tm ); } while( ret == EINTR ); bRet = ( ret == 0 ); if ( m_bManualReset ) { if ( m_cSet ) break; if ( dwTimeout == TT_INFINITE && ret == ETIMEDOUT ) ret = 0; // force the loop to spin back around } } if ( bRet ) m_bWakeForEvent = false; } if ( !m_bManualReset && bRet ) m_cSet = 0; pthread_mutex_unlock( &m_Mutex ); return bRet; #endif } #endif uint32 CThreadSyncObject::WaitForMultiple( int nObjects, CThreadSyncObject **ppObjects, bool bWaitAll, uint32 dwTimeout ) { #if defined( _WIN32 ) CThreadSyncObject *pHandles = (CThreadSyncObject*)stackalloc( sizeof(CThreadSyncObject) * nObjects ); for ( int i=0; i < nObjects; i++ ) { pHandles[i].m_hSyncObject = ppObjects[i]->m_hSyncObject; } return WaitForMultiple( nObjects, pHandles, bWaitAll, dwTimeout ); #else // TODO: Need a more efficient implementation of this. uint32 dwStartTime = 0; if ( dwTimeout != TT_INFINITE ) dwStartTime = Plat_MSTime(); // If bWaitAll = true, then we need to track which ones were triggered. char *pWasTriggered = NULL; int nTriggered = 0; if ( bWaitAll ) { pWasTriggered = (char*)stackalloc( nObjects ); memset( pWasTriggered, 0, nObjects ); } while ( 1 ) { for ( int i=0; i < nObjects; i++ ) { if ( bWaitAll && pWasTriggered[i] ) continue; #ifdef _PS3 Assert( !"Not implemented!" ); if ( false ) #else if ( ppObjects[i]->Wait( 0 ) ) #endif { ++nTriggered; if ( bWaitAll ) { if ( nTriggered == nObjects ) return 0; else pWasTriggered[i] = 1; } else { return i; } } } // Timeout? if ( dwTimeout != TT_INFINITE ) { if ( Plat_MSTime() - dwStartTime >= dwTimeout ) return TW_TIMEOUT; } ThreadSleep( 0 ); } #endif } uint32 CThreadSyncObject::WaitForMultiple( int nObjects, CThreadSyncObject *pObjects, bool bWaitAll, uint32 dwTimeout ) { #if defined(_WIN32 ) HANDLE *pHandles = (HANDLE*)stackalloc( sizeof(HANDLE) * nObjects ); for ( int i=0; i < nObjects; i++ ) { pHandles[i] = pObjects[i].m_hSyncObject; } DWORD ret = WaitForMultipleObjects( nObjects, pHandles, bWaitAll, dwTimeout ); if ( ret == WAIT_TIMEOUT ) return TW_TIMEOUT; else if ( ret >= WAIT_OBJECT_0 && (ret-WAIT_OBJECT_0) < (uint32)nObjects ) return (int)(ret - WAIT_OBJECT_0); else if ( ret >= WAIT_ABANDONED_0 && (ret - WAIT_ABANDONED_0) < (uint32)nObjects ) Error( "Unhandled WAIT_ABANDONED in WaitForMultipleObjects" ); else if ( ret == WAIT_FAILED ) return TW_FAILED; else Error( "Unknown return value (%lu) from WaitForMultipleObjects", ret ); // We'll never get here.. return 0; #else CThreadSyncObject **ppObjects = (CThreadSyncObject**)stackalloc( sizeof( CThreadSyncObject* ) * nObjects ); for ( int i=0; i < nObjects; i++ ) { ppObjects[i] = &pObjects[i]; } return WaitForMultiple( nObjects, ppObjects, bWaitAll, dwTimeout ); #endif } // To implement these, I need to check that casts are safe uint32 CThreadEvent::WaitForMultiple( int nObjects, CThreadEvent *pObjects, bool bWaitAll, uint32 dwTimeout ) { // If data ever gets added to CThreadEvent, then we need a different implementation. #ifdef _PS3 CThreadEvent **ppObjects = (CThreadEvent**)stackalloc( sizeof( CThreadEvent* ) * nObjects ); for ( int i=0; i < nObjects; i++ ) { ppObjects[i] = &pObjects[i]; } return WaitForMultipleObjects( nObjects, ppObjects, bWaitAll, dwTimeout ); #else COMPILE_TIME_ASSERT( sizeof( CThreadSyncObject ) == 0 || sizeof( CThreadEvent ) == sizeof( CThreadSyncObject ) ); return CThreadSyncObject::WaitForMultiple( nObjects, (CThreadSyncObject*)pObjects, bWaitAll, dwTimeout ); #endif } uint32 CThreadEvent::WaitForMultiple( int nObjects, CThreadEvent **ppObjects, bool bWaitAll, uint32 dwTimeout ) { #ifdef _PS3 return WaitForMultipleObjects( nObjects, ppObjects, bWaitAll, dwTimeout ); #else // If data ever gets added to CThreadEvent, then we need a different implementation. COMPILE_TIME_ASSERT( sizeof( CThreadSyncObject )== 0 || sizeof( CThreadEvent ) == sizeof( CThreadSyncObject ) ); return CThreadSyncObject::WaitForMultiple( nObjects, (CThreadSyncObject**)ppObjects, bWaitAll, dwTimeout ); #endif } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CThreadEvent::CThreadEvent( bool bManualReset ) { #ifdef _WIN32 m_hSyncObject = CreateEvent( NULL, bManualReset, FALSE, NULL ); m_bCreatedHandle = true; AssertMsg1(m_hSyncObject, "Failed to create event (error 0x%x)", GetLastError() ); #elif defined( _PS3 ) m_bManualReset = bManualReset; m_bSet = 0; m_bInitalized = false; m_numWaitingThread = 0; // set up linked list of wait objects memset(&m_waitObjects[0], 0, sizeof(m_waitObjects)); m_pWaitObjectsList = &m_waitObjects[0]; m_pWaitObjectsPool = &m_waitObjects[1]; for (int i = 2; i < CTHREADEVENT_MAX_WAITING_THREADS + 2; i++) { LLLinkNode(m_pWaitObjectsPool, &m_waitObjects[i]); } #elif defined( POSIX ) pthread_mutexattr_t Attr; pthread_mutexattr_init( &Attr ); pthread_mutex_init( &m_Mutex, &Attr ); pthread_mutexattr_destroy( &Attr ); pthread_cond_init( &m_Condition, NULL ); m_bInitalized = true; m_cSet = 0; m_bWakeForEvent = false; m_bManualReset = bManualReset; #else #error "Implement me" #endif } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- #ifdef _PS3 // // linked list functionality // //----------------------------------------------------------------------------- // Purpose: Linked list implementation //----------------------------------------------------------------------------- CThreadEventWaitObject* CThreadEvent::LLUnlinkNode(CThreadEventWaitObject *node) { // Note: if you have a null-access crash here, it may mean that CTHREADEVENT_MAX_WAITING_THREADS is not high enough // and the linked list pool is simply exhausted node->m_pPrev->m_pNext = node->m_pNext; if (node->m_pNext) node->m_pNext->m_pPrev = node->m_pPrev; node->m_pNext = node->m_pPrev = NULL; return node; } CThreadEventWaitObject* CThreadEvent::LLLinkNode(CThreadEventWaitObject* list, CThreadEventWaitObject *node) { node->m_pNext = list->m_pNext; if (node->m_pNext) { node->m_pNext->m_pPrev = node; } list->m_pNext = node; node->m_pPrev = list; return node; } //----------------------------------------------------------------------------- // Helper function to atomically write index into destination and set semaphore // This is used by WaitForMultipleObjects(WAIT_ANY) because once the semaphore // is set, the waiting thread also needs to know which event triggered it // We do NOT need this to be atomic because if a number of events fire it doesn't // matter which one of these we pick //----------------------------------------------------------------------------- void CThreadEventWaitObject::Set() { *m_pFlag = m_index; sys_semaphore_post(*m_pSemaphore, 1); } // // CThreadEvent::RegisterWaitingThread // void CThreadEvent::RegisterWaitingThread(sys_semaphore_t *pSemaphore, int index, int *flag) { sys_lwmutex_lock(&m_staticMutex, 0); // if we are already set, then signal this semaphore if (m_bSet) { CThreadEventWaitObject waitObject; waitObject.Init(pSemaphore, index, flag); waitObject.Set(); if (!m_bManualReset) { m_bSet = false; } } else { if (!m_pWaitObjectsPool->m_pNext) { DEBUG_ERROR("CThreadEvent: Ran out of events; cannot register waiting thread\n"); } // add this semaphore to linked list - can be added more than once it doesn't matter CThreadEventWaitObject *pWaitObject = LLUnlinkNode(m_pWaitObjectsPool->m_pNext); pWaitObject->Init(pSemaphore, index, flag); LLLinkNode(m_pWaitObjectsList, pWaitObject); } sys_lwmutex_unlock(&m_staticMutex); } // // CThreadEvent::UnregisterWaitingThread // void CThreadEvent::UnregisterWaitingThread(sys_semaphore_t *pSemaphore) { // remove all instances of this semaphore from linked list sys_lwmutex_lock(&m_staticMutex, 0); CThreadEventWaitObject *pWaitObject = m_pWaitObjectsList->m_pNext; while (pWaitObject) { CThreadEventWaitObject *pNext = pWaitObject->m_pNext; if (pWaitObject->m_pSemaphore == pSemaphore) { LLUnlinkNode(pWaitObject); LLLinkNode(m_pWaitObjectsPool, pWaitObject); } pWaitObject = pNext; } sys_lwmutex_unlock(&m_staticMutex); } #endif // _PS3 #ifdef _WIN32 CThreadEvent::CThreadEvent( const char *name, bool initialState, bool bManualReset ) { m_hSyncObject = CreateEvent( NULL, bManualReset, (BOOL) initialState, name ); AssertMsg1( m_hSyncObject, "Failed to create event (error 0x%x)", GetLastError() ); } NamedEventResult_t CThreadEvent::CheckNamedEvent( const char *name, uint32 dwTimeout ) { HANDLE eHandle = OpenEvent( SYNCHRONIZE, FALSE, name ); if ( eHandle == NULL ) return TT_EventDoesntExist; DWORD result = WaitForSingleObject( eHandle, dwTimeout ); return ( result == WAIT_OBJECT_0 ) ? TT_EventSignaled : TT_EventNotSignaled; } #endif //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- //--------------------------------------------------------- bool CThreadEvent::Set() { ////////////////////////////////////////////////////////////// #ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS ////////////////////////////////////////////////////////////// AssertUseable(); #ifdef _WIN32 return ( SetEvent( m_hSyncObject ) != 0 ); #elif defined( _PS3 ) sys_lwmutex_lock(&m_staticMutex, 0); if (m_bManualReset) { //Mark event as set m_bSet = true; //If any threads are already waiting then signal them to run if (m_bInitalized) { int err = sys_semaphore_post( m_Semaphore, m_numWaitingThread); Assert(err == CELL_OK); } } else { //If any threads are already waiting then signal ONE to run, else signal next to run if (m_numWaitingThread>0) { int err = sys_semaphore_post( m_Semaphore, 1); Assert(err == CELL_OK); } else { m_bSet=true; } } sys_lwmutex_unlock(&m_staticMutex); return true; #elif defined(POSIX) pthread_mutex_lock( &m_Mutex ); m_cSet = 1; m_bWakeForEvent = true; int ret = pthread_cond_signal( &m_Condition ); pthread_mutex_unlock( &m_Mutex ); return ret == 0; #endif ////////////////////////////////////////////////////////////// #else // NEW_WAIT_FOR_MULTIPLE_OBJECTS ////////////////////////////////////////////////////////////// sys_lwmutex_lock(&m_staticMutex, 0); //Mark event as set m_bSet = true; // signal registered semaphores while (m_pWaitObjectsList->m_pNext) { CThreadEventWaitObject *pWaitObject = LLUnlinkNode(m_pWaitObjectsList->m_pNext); pWaitObject->Set(); LLLinkNode(m_pWaitObjectsPool, pWaitObject); g_pfnPopMarker(); if (!m_bManualReset) { m_bSet = false; break; } } sys_lwmutex_unlock(&m_staticMutex); return true; ////////////////////////////////////////////////////////////// #endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS ////////////////////////////////////////////////////////////// } //--------------------------------------------------------- bool CThreadEvent::Reset() { #ifdef THREADS_DEBUG AssertUseable(); #endif #ifdef _WIN32 return ( ResetEvent( m_hSyncObject ) != 0 ); #elif defined( _PS3 ) //Just mark us as no longer signaled m_bSet = 0; return true; #elif defined(POSIX) pthread_mutex_lock( &m_Mutex ); m_cSet = 0; m_bWakeForEvent = false; pthread_mutex_unlock( &m_Mutex ); return true; #endif } //--------------------------------------------------------- bool CThreadEvent::Check() { #ifdef _PS3 return m_bSet; // Please, use for debugging only! #endif #ifdef THREADS_DEBUG AssertUseable(); #endif return Wait( 0 ); } bool CThreadEvent::Wait( uint32 dwTimeout ) { ////////////////////////////////////////////////////////////// #ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS ////////////////////////////////////////////////////////////// #if defined( _WIN32 ) || ( defined( POSIX ) && !defined( _PS3 ) ) return CThreadSyncObject::Wait( dwTimeout ); #elif defined( _PS3 ) { if (dwTimeout == 0) { //If timeout is 0 then just test it now (and reset it if manual ) if (m_bSet) { if ( !m_bManualReset ) m_bSet=false; return true; } return false; } if (!AddWaitingThread()) { //Waiting thread NOT added because m_bSet was already set if ( !m_bManualReset ) m_bSet=false; return true; } uint32 timeout; int countTimeout = 0; int ret = ETIMEDOUT; while ( timeout=MIN(1, dwTimeout) ) { // on the PS3, "infinite timeout" is specified by zero, not // 0xFFFFFFFF, so we need to perform that ternary here. //#error Untested code: ret = sys_semaphore_wait( m_Semaphore, timeout == TT_INFINITE ? 0 : timeout * 1000 ); Assert( (ret == CELL_OK) || (ret == ETIMEDOUT) ); if ( ret == CELL_OK ) break; dwTimeout -= timeout; countTimeout++; if (countTimeout > 30) { // printf("WARNING: possible deadlock in CThreadEvent::Wait() !!!\n"); } } RemoveWaitingThread(); if ( !m_bManualReset ) m_bSet=false; return ret == CELL_OK; } #endif ////////////////////////////////////////////////////////////// #else // NEW_WAIT_FOR_MULTIPLE_OBJECTS ////////////////////////////////////////////////////////////// CThreadEvent *pThis = this; DWORD res = WaitForMultipleObjects(1, &pThis, true, dwTimeout); return res == WAIT_OBJECT_0; ////////////////////////////////////////////////////////////// #endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS ////////////////////////////////////////////////////////////// } #ifdef _WIN32 //----------------------------------------------------------------------------- // // CThreadSemaphore // // To get Posix implementation, try http://www-128.ibm.com/developerworks/eserver/library/es-win32linux-sem.html // //----------------------------------------------------------------------------- CThreadSemaphore::CThreadSemaphore( int32 initialValue, int32 maxValue ) { #ifdef _WIN32 if ( maxValue ) { AssertMsg( maxValue > 0, "Invalid max value for semaphore" ); AssertMsg( initialValue >= 0 && initialValue <= maxValue, "Invalid initial value for semaphore" ); m_hSyncObject = CreateSemaphore( NULL, initialValue, maxValue, NULL ); AssertMsg1(m_hSyncObject, "Failed to create semaphore (error 0x%x)", GetLastError()); } else { m_hSyncObject = NULL; } #elif defined( _PS3 ) if ( maxValue ) { m_sema_max_val = maxValue; m_semaCount = initialValue; } #endif } #ifdef _PS3 //--------------------------------------------------------- bool CThreadSemaphore::AddWaitingThread() { bool result; sys_lwmutex_lock(&m_staticMutex, 0); if (cellAtomicTestAndDecr32(&m_semaCount) > 0) { result=false; } else { result=true; m_numWaitingThread++; if ( m_numWaitingThread == 1 ) { sys_semaphore_attribute_t semAttr; sys_semaphore_attribute_initialize( semAttr ); Assert(m_semaCount == 0); int err = sys_semaphore_create( &m_Semaphore, &semAttr, 0, m_sema_max_val ); Assert( err == CELL_OK ); m_bInitalized = true; } } sys_lwmutex_unlock(&m_staticMutex); return result; } void CThreadSemaphore::RemoveWaitingThread() { sys_lwmutex_lock(&m_staticMutex, 0); m_numWaitingThread--; if ( m_numWaitingThread == 0) { int err = sys_semaphore_destroy( m_Semaphore ); Assert( err == CELL_OK ); m_bInitalized = false; } sys_lwmutex_unlock(&m_staticMutex); } #endif #ifdef _PS3 bool CThreadSemaphore::Wait( uint32 dwTimeout ) { #ifdef THREADS_DEBUG AssertUseable(); #endif #ifndef NO_THREAD_SYNC if (!AddWaitingThread()) { //Waiting thread NOT added because semaphore was already in a signaled state return true; } int ret = sys_semaphore_wait( m_Semaphore, dwTimeout == TT_INFINITE ? 0 : dwTimeout * 1000 ); Assert( (ret == CELL_OK) || (ret == ETIMEDOUT) ); RemoveWaitingThread(); int old = cellAtomicDecr32(&m_semaCount); Assert(old>0); #else int ret = CELL_OK; #endif // sys_ppu_thread_yield doesn't seem to function properly, so sleep instead. // sys_timer_usleep( 60 ); sys_ppu_thread_yield(); return ret == CELL_OK; } #endif //--------------------------------------------------------- bool CThreadSemaphore::Release( int32 releaseCount, int32 *pPreviousCount ) { #ifdef THRDTOOL_DEBUG AssertUseable(); #endif #ifdef _WIN32 return ( ReleaseSemaphore( m_hSyncObject, releaseCount, (LPLONG)pPreviousCount ) != 0 ); #elif defined( _PS3 ) #ifndef NO_THREAD_SYNC if (m_bInitalized) { sys_semaphore_value_t previousVal; sys_semaphore_get_value( m_Semaphore, &previousVal ); cellAtomicAdd32(&m_semaCount, releaseCount); *pPreviousCount = previousVal; int err = sys_semaphore_post( m_Semaphore, releaseCount ); Assert(err == CELL_OK); } #endif return true; #endif } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CThreadFullMutex::CThreadFullMutex( bool bEstablishInitialOwnership, const char *pszName ) { m_hSyncObject = CreateMutex( NULL, bEstablishInitialOwnership, pszName ); AssertMsg1( m_hSyncObject, "Failed to create mutex (error 0x%x)", GetLastError() ); } //--------------------------------------------------------- bool CThreadFullMutex::Release() { #ifdef THRDTOOL_DEBUG AssertUseable(); #endif return ( ReleaseMutex( m_hSyncObject ) != 0 ); } #endif //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- #if defined( WIN32 ) || defined( _PS3 ) || defined( _OSX ) || defined (_LINUX) #if !defined(_PS3) namespace GenericThreadLocals { #endif CThreadLocalBase::CThreadLocalBase() { #if defined(_WIN32) || defined(_PS3) m_index = TlsAlloc(); AssertMsg( m_index != 0xFFFFFFFF, "Bad thread local" ); if ( m_index == 0xFFFFFFFF ) Error( "Out of thread local storage!\n" ); #elif defined(POSIX) if ( pthread_key_create( (pthread_key_t *)&m_index, NULL ) != 0 ) Error( "Out of thread local storage!\n" ); #endif } //--------------------------------------------------------- CThreadLocalBase::~CThreadLocalBase() { #if defined(_WIN32) || defined(_PS3) if ( m_index != 0xFFFFFFFF ) TlsFree( m_index ); m_index = 0xFFFFFFFF; #elif defined(POSIX) pthread_key_delete( m_index ); #endif } //--------------------------------------------------------- void * CThreadLocalBase::Get() const { #if defined(_WIN32) || defined(_PS3) if ( m_index != 0xFFFFFFFF ) return TlsGetValue( m_index ); AssertMsg( 0, "Bad thread local" ); return NULL; #elif defined(POSIX) void *value = pthread_getspecific( m_index ); return value; #endif } //--------------------------------------------------------- void CThreadLocalBase::Set( void *value ) { #if defined(_WIN32) || defined(_PS3) if (m_index != 0xFFFFFFFF) TlsSetValue(m_index, value); else AssertMsg( 0, "Bad thread local" ); #elif defined(POSIX) if ( pthread_setspecific( m_index, value ) != 0 ) AssertMsg( 0, "Bad thread local" ); #endif } #if !defined(_PS3) } // namespace GenericThreadLocals #endif #endif // ( defined(WIN32) ) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- #ifdef MSVC //#ifdef _X360 #define TO_INTERLOCK_PARAM(p) ((volatile long *)p) #define TO_INTERLOCK_PTR_PARAM(p) ((void **)p) //#else //#define TO_INTERLOCK_PARAM(p) (p) //#define TO_INTERLOCK_PTR_PARAM(p) (p) //#endif #if !defined(USE_INTRINSIC_INTERLOCKED) && !defined(_X360) int32 ThreadInterlockedIncrement( int32 volatile *pDest ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedIncrement( TO_INTERLOCK_PARAM(pDest) ); } int32 ThreadInterlockedDecrement( int32 volatile *pDest ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedDecrement( TO_INTERLOCK_PARAM(pDest) ); } int32 ThreadInterlockedExchange( int32 volatile *pDest, int32 value ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedExchange( TO_INTERLOCK_PARAM(pDest), value ); } int32 ThreadInterlockedExchangeAdd( int32 volatile *pDest, int32 value ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedExchangeAdd( TO_INTERLOCK_PARAM(pDest), value ); } int32 ThreadInterlockedCompareExchange( int32 volatile *pDest, int32 value, int32 comperand ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ); } bool ThreadInterlockedAssignIf( int32 volatile *pDest, int32 value, int32 comperand ) { Assert( (size_t)pDest % 4 == 0 ); #if !(defined(_WIN64) || defined (_X360)) __asm { mov eax,comperand mov ecx,pDest mov edx,value lock cmpxchg [ecx],edx mov eax,0 setz al } #else return ( InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ) == comperand ); #endif } #endif #if !defined( USE_INTRINSIC_INTERLOCKED ) || defined( _WIN64 ) void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value ); } void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) { Assert( (size_t)pDest % 4 == 0 ); return InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ); } bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) { Assert( (size_t)pDest % 4 == 0 ); #if !(defined(_WIN64) || defined (_X360)) __asm { mov eax,comperand mov ecx,pDest mov edx,value lock cmpxchg [ecx],edx mov eax,0 setz al } #else return ( InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ) == comperand ); #endif } #endif #ifdef COMPILER_MSVC32 int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) { Assert( (size_t)pDest % 8 == 0 ); __asm { lea esi,comperand; lea edi,value; mov eax,[esi]; mov edx,4[esi]; mov ebx,[edi]; mov ecx,4[edi]; mov esi,pDest; lock CMPXCHG8B [esi]; } } #endif bool ThreadInterlockedAssignIf64(volatile int64 *pDest, int64 value, int64 comperand ) { Assert( (size_t)pDest % 8 == 0 ); #if defined(_X360) || defined(_WIN64) return ( ThreadInterlockedCompareExchange64( pDest, value, comperand ) == comperand ); #else __asm { lea esi,comperand; lea edi,value; mov eax,[esi]; mov edx,4[esi]; mov ebx,[edi]; mov ecx,4[edi]; mov esi,pDest; lock CMPXCHG8B [esi]; mov eax,0; setz al; } #endif } #ifdef _WIN64 bool ThreadInterlockedAssignIf128( volatile int128 *pDest, const int128 &value, const int128 &comperand ) { DbgAssert( ( (size_t)pDest % 16 ) == 0 ); // Must copy comperand to stack because the intrinsic uses it as an in/out param int64 comperandInOut[2] = { comperand.m128i_i64[0], comperand.m128i_i64[1] }; // Description: // The CMPXCHG16B instruction compares the 128-bit value in the RDX:RAX and RCX:RBX registers // with a 128-bit memory location. If the values are equal, the zero flag (ZF) is set, // and the RCX:RBX value is copied to the memory location. // Otherwise, the ZF flag is cleared, and the memory value is copied to RDX:RAX. // _InterlockedCompareExchange128: http://msdn.microsoft.com/en-us/library/bb514094.aspx if ( _InterlockedCompareExchange128( ( volatile int64 * )pDest, value.m128i_i64[1], value.m128i_i64[0], comperandInOut ) ) return true; return false; } #endif #elif defined(GNUC) #ifdef OSX #include #endif long ThreadInterlockedIncrement( long volatile *pDest ) { return __sync_fetch_and_add( pDest, 1 ) + 1; } long ThreadInterlockedDecrement( long volatile *pDest ) { return __sync_fetch_and_sub( pDest, 1 ) - 1; } long ThreadInterlockedExchange( long volatile *pDest, long value ) { return __sync_lock_test_and_set( pDest, value ); } long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) { return __sync_fetch_and_add( pDest, value ); } long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) { return __sync_val_compare_and_swap( pDest, comperand, value ); } bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) { return __sync_bool_compare_and_swap( pDest, comperand, value ); } #if !defined( USE_INTRINSIC_INTERLOCKED ) void *ThreadInterlockedCompareExchangePointer( void *volatile *pDest, void *value, void *comperand ) { return __sync_val_compare_and_swap( pDest, comperand, value ); } bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) { return __sync_bool_compare_and_swap( pDest, comperand, value ); } #elif defined( PLATFORM_64BITS ) void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) { return __sync_lock_test_and_set( pDest, value ); } void *ThreadInterlockedCompareExchangePointer( void * volatile *p, void *value, void *comparand ) { return (void *)( ( intp )ThreadInterlockedCompareExchange64( reinterpret_cast(p), reinterpret_cast(value), reinterpret_cast(comparand) ) ); } #endif int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) { return __sync_val_compare_and_swap( pDest, comperand, value ); } bool ThreadInterlockedAssignIf64( int64 volatile * pDest, int64 value, int64 comperand ) { return __sync_bool_compare_and_swap( pDest, comperand, value ); } #elif defined( _PS3 ) // This is defined in the header! #else // This will perform horribly, #error "Falling back to mutexed interlocked operations, you really don't have intrinsics you can use?"ß CThreadMutex g_InterlockedMutex; long ThreadInterlockedIncrement( long volatile *pDest ) { AUTO_LOCK( g_InterlockedMutex ); return ++(*pDest); } long ThreadInterlockedDecrement( long volatile *pDest ) { AUTO_LOCK( g_InterlockedMutex ); return --(*pDest); } long ThreadInterlockedExchange( long volatile *pDest, long value ) { AUTO_LOCK( g_InterlockedMutex ); long retVal = *pDest; *pDest = value; return retVal; } void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) { AUTO_LOCK( g_InterlockedMutex ); void *retVal = *pDest; *pDest = value; return retVal; } long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) { AUTO_LOCK( g_InterlockedMutex ); long retVal = *pDest; *pDest += value; return retVal; } long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) { AUTO_LOCK( g_InterlockedMutex ); long retVal = *pDest; if ( *pDest == comperand ) *pDest = value; return retVal; } void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) { AUTO_LOCK( g_InterlockedMutex ); void *retVal = *pDest; if ( *pDest == comperand ) *pDest = value; return retVal; } int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) { Assert( (size_t)pDest % 8 == 0 ); AUTO_LOCK( g_InterlockedMutex ); int64 retVal = *pDest; if ( *pDest == comperand ) *pDest = value; return retVal; } #endif #ifdef COMPILER_MSVC32 PLATFORM_INTERFACE int64 ThreadInterlockedOr64( int64 volatile *pDest, int64 value ) { int64 Old; do { Old = *pDest; } while ( ThreadInterlockedCompareExchange64( pDest, Old | value, Old ) != Old ); return Old; } PLATFORM_INTERFACE int64 ThreadInterlockedAnd64( int64 volatile *pDest, int64 value ) { int64 Old; do { Old = *pDest; } while ( ThreadInterlockedCompareExchange64( pDest, Old & value, Old ) != Old ); return Old; } PLATFORM_INTERFACE int64 ThreadInterlockedIncrement64( int64 volatile *pDest ) { int64 Old; do { Old = *pDest; } while ( ThreadInterlockedCompareExchange64( pDest, Old + 1, Old ) != Old ); return Old + 1; } PLATFORM_INTERFACE int64 ThreadInterlockedDecrement64( int64 volatile *pDest ) { int64 Old; do { Old = *pDest; } while ( ThreadInterlockedCompareExchange64( pDest, Old - 1, Old ) != Old ); return Old - 1; } PLATFORM_INTERFACE int64 ThreadInterlockedExchangeAdd64( int64 volatile *pDest, int64 value ) { int64 Old; do { Old = *pDest; } while ( ThreadInterlockedCompareExchange64( pDest, Old + value, Old ) != Old ); return Old; } #endif int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) { Assert( (size_t)pDest % 8 == 0 ); int64 Old; do { Old = *pDest; } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old); return Old; } //----------------------------------------------------------------------------- #if defined(_WIN32) && defined(THREAD_PROFILER) void ThreadNotifySyncNoop(void *p) {} #define MAP_THREAD_PROFILER_CALL( from, to ) \ void from(void *p) \ { \ static CDynamicFunction dynFunc( "libittnotify.dll", #to, ThreadNotifySyncNoop ); \ (*dynFunc)(p); \ } MAP_THREAD_PROFILER_CALL( ThreadNotifySyncPrepare, __itt_notify_sync_prepare ); MAP_THREAD_PROFILER_CALL( ThreadNotifySyncCancel, __itt_notify_sync_cancel ); MAP_THREAD_PROFILER_CALL( ThreadNotifySyncAcquired, __itt_notify_sync_acquired ); MAP_THREAD_PROFILER_CALL( ThreadNotifySyncReleasing, __itt_notify_sync_releasing ); #endif //----------------------------------------------------------------------------- // // CThreadMutex // //----------------------------------------------------------------------------- #ifdef _PS3 CThreadMutex::CThreadMutex() { // sys_mutex with recursion enabled is like a win32 critical section sys_mutex_attribute_t mutexAttr; sys_mutex_attribute_initialize( mutexAttr ); mutexAttr.attr_recursive = SYS_SYNC_RECURSIVE; sys_mutex_create( &m_Mutex, &mutexAttr ); } CThreadMutex::~CThreadMutex() { sys_mutex_destroy( m_Mutex ); } #elif !defined( POSIX ) CThreadMutex::CThreadMutex() { #ifdef THREAD_MUTEX_TRACING_ENABLED memset( &m_CriticalSection, 0, sizeof(m_CriticalSection) ); #endif InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION *)&m_CriticalSection, 4000); #ifdef THREAD_MUTEX_TRACING_SUPPORTED // These need to be initialized unconditionally in case mixing release & debug object modules // Lock and unlock may be emitted as COMDATs, in which case may get spurious output m_currentOwnerID = m_lockCount = 0; m_bTrace = false; #endif } CThreadMutex::~CThreadMutex() { DeleteCriticalSection((CRITICAL_SECTION *)&m_CriticalSection); } #endif // !POSIX #ifdef IS_WINDOWS_PC typedef BOOL (WINAPI*TryEnterCriticalSectionFunc_t)(LPCRITICAL_SECTION); static CDynamicFunction DynTryEnterCriticalSection( "Kernel32.dll", "TryEnterCriticalSection" ); #elif defined( _X360 ) #define DynTryEnterCriticalSection TryEnterCriticalSection #endif bool CThreadMutex::TryLock() { #if defined( MSVC ) #ifdef THREAD_MUTEX_TRACING_ENABLED uint thisThreadID = ThreadGetCurrentId(); if ( m_bTrace && m_currentOwnerID && ( m_currentOwnerID != thisThreadID ) ) Msg( "Thread %u about to try-wait for lock %p owned by %u\n", ThreadGetCurrentId(), (CRITICAL_SECTION *)&m_CriticalSection, m_currentOwnerID ); #endif if ( DynTryEnterCriticalSection != NULL ) { if ( (*DynTryEnterCriticalSection )( (CRITICAL_SECTION *)&m_CriticalSection ) != FALSE ) { #ifdef THREAD_MUTEX_TRACING_ENABLED if (m_lockCount == 0) { // we now own it for the first time. Set owner information m_currentOwnerID = thisThreadID; if ( m_bTrace ) Msg( "Thread %u now owns lock %p\n", m_currentOwnerID, (CRITICAL_SECTION *)&m_CriticalSection ); } m_lockCount++; #endif return true; } return false; } Lock(); return true; #elif defined( _PS3 ) #ifndef NO_THREAD_SYNC if ( sys_mutex_trylock( m_Mutex ) == CELL_OK ) #endif return true; return false; // ?? moved from EA code #elif defined( POSIX ) return pthread_mutex_trylock( &m_Mutex ) == 0; #else #error "Implement me!" return true; #endif } //----------------------------------------------------------------------------- // // CThreadFastMutex // //----------------------------------------------------------------------------- #ifdef THREAD_FAST_MUTEX_TIMINGS // This is meant to be used in combination with breakpoints and in-debugee, so we turn the optimizer off #pragma optimize( "", off ) CThreadFastMutex *g_pIgnoredMutexes[256]; // Ignore noisy non-problem mutex. Probably could be an array. Right now needed only for sound thread float g_MutexTimingTolerance = 5; bool g_bMutexTimingOutput; void TrapMutexTimings( uint32 probableBlocker, uint32 thisThread, volatile CThreadFastMutex *pMutex, CFastTimer &spikeTimer, CAverageCycleCounter &sleepTimer ) { spikeTimer.End(); if ( spikeTimer.GetDuration().GetMillisecondsF() > g_MutexTimingTolerance ) { bool bIgnore = false; for ( int j = 0; j < ARRAYSIZE( g_pIgnoredMutexes ) && g_pIgnoredMutexes[j]; j++ ) { if ( g_pIgnoredMutexes[j] == pMutex ) { bIgnore = true; break; } } if ( !bIgnore && spikeTimer.GetDuration().GetMillisecondsF() < 100 ) { volatile float FastMutexDuration = spikeTimer.GetDuration().GetMillisecondsF(); volatile float average = sleepTimer.GetAverageMilliseconds(); volatile float peak = sleepTimer.GetPeakMilliseconds(); volatile int xx = 6; if ( g_bMutexTimingOutput ) { char szBuf[256]; Msg( "M (%.8x): [%.8x <-- %.8x] (%f,%f,%f)\n", pMutex, probableBlocker, thisThread, FastMutexDuration, average, peak ); } } } } #else #define TrapMutexTimings( a, b, c, d, e ) ((void)0) #endif //------------------------------------- #define THREAD_SPIN (8*1024) void CThreadFastMutex::Lock( const uint32 threadId, unsigned nSpinSleepTime ) volatile { #ifdef THREAD_FAST_MUTEX_TIMINGS CAverageCycleCounter sleepTimer; CFastTimer spikeTimer; uint32 currentOwner = m_ownerID; spikeTimer.Start(); sleepTimer.Init(); #endif int i; if ( nSpinSleepTime != TT_INFINITE ) { for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLock( threadId ) ) { TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); return; } ThreadPause(); } for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLock( threadId ) ) { TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); return; } ThreadPause(); if ( i % 1024 == 0 ) { #ifdef THREAD_FAST_MUTEX_TIMINGS CAverageTimeMarker marker( &sleepTimer ); #endif ThreadSleep( 0 ); } } #ifdef _WIN32 if ( !nSpinSleepTime && GetThreadPriority( GetCurrentThread() ) > THREAD_PRIORITY_NORMAL ) { nSpinSleepTime = 1; } #endif if ( nSpinSleepTime ) { for ( i = THREAD_SPIN; i != 0; --i ) { #ifdef THREAD_FAST_MUTEX_TIMINGS CAverageTimeMarker marker( &sleepTimer ); #endif if ( TryLock( threadId ) ) { TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); return; } ThreadPause(); ThreadSleep( 0 ); } } for ( ;; ) { #ifdef THREAD_FAST_MUTEX_TIMINGS CAverageTimeMarker marker( &sleepTimer ); #endif if ( TryLock( threadId ) ) { TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); return; } ThreadPause(); ThreadSleep( nSpinSleepTime ); } } else { for ( ;; ) { if ( TryLock( threadId ) ) { TrapMutexTimings( currentOwner, threadId, this, spikeTimer, sleepTimer ); return; } ThreadPause(); } } } #ifdef THREAD_FAST_MUTEX_TIMINGS #pragma optimize( "", on ) #endif //----------------------------------------------------------------------------- // // CThreadRWLock // //----------------------------------------------------------------------------- void CThreadRWLock::WaitForRead() { m_nPendingReaders++; do { m_mutex.Unlock(); m_CanRead.Wait(); m_mutex.Lock(); } while (m_nWriters); m_nPendingReaders--; } void CThreadRWLock::LockForWrite() { m_mutex.Lock(); bool bWait = ( m_nWriters != 0 || m_nActiveReaders != 0 ); m_nWriters++; m_CanRead.Reset(); m_mutex.Unlock(); if ( bWait ) { m_CanWrite.Wait(); } } void CThreadRWLock::UnlockWrite() { m_mutex.Lock(); m_nWriters--; if ( m_nWriters == 0) { if ( m_nPendingReaders ) { m_CanRead.Set(); } } else { m_CanWrite.Set(); } m_mutex.Unlock(); } //----------------------------------------------------------------------------- // // CThreadSpinRWLock // //----------------------------------------------------------------------------- #ifndef OLD_SPINRWLOCK void CThreadSpinRWLock::SpinLockForWrite() { int i; if ( TryLockForWrite_UnforcedInline() ) { return; } for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLockForWrite_UnforcedInline() ) { return; } ThreadPause(); } for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLockForWrite_UnforcedInline() ) { return; } ThreadPause(); if ( i % 1024 == 0 ) { ThreadSleep( 0 ); } } for ( i = THREAD_SPIN * 4; i != 0; --i ) { if ( TryLockForWrite_UnforcedInline() ) { return; } ThreadPause(); ThreadSleep( 0 ); } for ( ;; ) // coded as for instead of while to make easy to breakpoint success { if ( TryLockForWrite_UnforcedInline() ) { return; } ThreadPause(); ThreadSleep( 1 ); } } void CThreadSpinRWLock::SpinLockForRead() { int i; for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLockForRead_UnforcedInline() ) { return; } ThreadPause(); } for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLockForRead_UnforcedInline() ) { return; } ThreadPause(); if ( i % 1024 == 0 ) { ThreadSleep( 0 ); } } for ( i = THREAD_SPIN * 4; i != 0; --i ) { if ( TryLockForRead_UnforcedInline() ) { return; } ThreadPause(); ThreadSleep( 0 ); } for ( ;; ) // coded as for instead of while to make easy to breakpoint success { if ( TryLockForRead_UnforcedInline() ) { return; } ThreadPause(); ThreadSleep( 1 ); } } #else /* (commented out to reduce distraction in colorized editor, remove entirely when new implementation settles) void CThreadSpinRWLock::SpinLockForWrite( const uint32 threadId ) { int i; if ( TryLockForWrite( threadId ) ) { return; } for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLockForWrite( threadId ) ) { return; } ThreadPause(); } for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLockForWrite( threadId ) ) { return; } ThreadPause(); if ( i % 1024 == 0 ) { ThreadSleep( 0 ); } } for ( i = THREAD_SPIN * 4; i != 0; --i ) { if ( TryLockForWrite( threadId ) ) { return; } ThreadPause(); ThreadSleep( 0 ); } for ( ;; ) // coded as for instead of while to make easy to breakpoint success { if ( TryLockForWrite( threadId ) ) { return; } ThreadPause(); ThreadSleep( 1 ); } } void CThreadSpinRWLock::LockForRead() { int i; if ( TryLockForRead() ) { return; } for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLockForRead() ) { return; } ThreadPause(); } for ( i = THREAD_SPIN; i != 0; --i ) { if ( TryLockForRead() ) { return; } ThreadPause(); if ( i % 1024 == 0 ) { ThreadSleep( 0 ); } } for ( i = THREAD_SPIN * 4; i != 0; --i ) { if ( TryLockForRead() ) { return; } ThreadPause(); ThreadSleep( 0 ); } for ( ;; ) // coded as for instead of while to make easy to breakpoint success { if ( TryLockForRead() ) { return; } ThreadPause(); ThreadSleep( 1 ); } } void CThreadSpinRWLock::UnlockRead() { int i; Assert( m_lockInfo.m_nReaders > 0 && m_lockInfo.m_writerId == 0 ); //uint32 nLockInfoReaders = m_lockInfo.m_nReaders; LockInfo_t oldValue; LockInfo_t newValue; if( IsX360() ) { // this is the code equivalent to original code (see below) that doesn't cause LHS on Xbox360 // WARNING: This code assumes BIG Endian CPU oldValue.m_i64 = uint32( m_lockInfo.m_nReaders ); newValue.m_i64 = oldValue.m_i64 - 1; // NOTE: when we have -1 (or 0xFFFFFFFF) readers, this will result in non-equivalent code } else { // this is the original code that worked here for a while oldValue.m_nReaders = m_lockInfo.m_nReaders; oldValue.m_writerId = 0; newValue.m_nReaders = oldValue.m_nReaders - 1; newValue.m_writerId = 0; } ThreadMemoryBarrier(); if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1; for ( i = THREAD_SPIN; i != 0; --i ) { if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1; } for ( i = THREAD_SPIN; i != 0; --i ) { if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); if ( i % 512 == 0 ) { ThreadSleep( 0 ); } oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1; } for ( i = THREAD_SPIN * 4; i != 0; --i ) { if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); ThreadSleep( 0 ); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1; } for ( ;; ) // coded as for instead of while to make easy to breakpoint success { if( AssignIf( newValue, oldValue ) ) return; ThreadPause(); ThreadSleep( 1 ); oldValue.m_nReaders = m_lockInfo.m_nReaders; newValue.m_nReaders = oldValue.m_nReaders - 1; } } void CThreadSpinRWLock::UnlockWrite() { Assert( m_lockInfo.m_writerId == ThreadGetCurrentId() && m_lockInfo.m_nReaders == 0 ); static const LockInfo_t newValue = { { 0, 0 } }; ThreadMemoryBarrier(); ThreadInterlockedExchange64( (int64 *)&m_lockInfo, *((int64 *)&newValue) ); m_nWriters--; } */ #endif #if defined( _PS3 ) // All CThread code is inline in the header for PS3 // This function is implemented here rather than the header because g_pCurThread resolves to GetCurThread() on PS3 // and we don't want to create a dependency on the ELF stub for everyone who includes the header. PLATFORM_INTERFACE CThread *GetCurThreadPS3() { return (CThread*)g_pCurThread; } PLATFORM_INTERFACE void SetCurThreadPS3( CThread *pThread ) { g_pCurThread = pThread; } #else // The CThread implementation needs to be inlined for performance on the PS3 - It makes a difference of more than 1ms/frame // for other platforms, we include the .inl in the .cpp file where it existed before #include "../public/tier0/threadtools.inl" #endif //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CWorkerThread::CWorkerThread() : m_EventSend(true), // must be manual-reset for PeekCall() m_EventComplete(true), // must be manual-reset to handle multiple wait with thread properly m_Param(0), m_ReturnVal(0) { } //--------------------------------------------------------- int CWorkerThread::CallWorker(unsigned dw, unsigned timeout, bool fBoostWorkerPriorityToMaster) { return Call(dw, timeout, fBoostWorkerPriorityToMaster); } //--------------------------------------------------------- int CWorkerThread::CallMaster(unsigned dw, unsigned timeout) { return Call(dw, timeout, false); } //--------------------------------------------------------- CThreadEvent &CWorkerThread::GetCallHandle() { return m_EventSend; } //--------------------------------------------------------- unsigned CWorkerThread::GetCallParam() const { return m_Param; } //--------------------------------------------------------- int CWorkerThread::BoostPriority() { int iInitialPriority = GetPriority(); #ifdef WIN32 const int iNewPriority = ThreadGetPriority( GetThreadHandle() ); if (iNewPriority > iInitialPriority) ThreadSetPriority( GetThreadHandle(), iNewPriority); #elif !defined( _PS3 ) const int iNewPriority = ThreadGetPriority( (ThreadHandle_t)GetThreadID() ); if (iNewPriority > iInitialPriority) ThreadSetPriority( (ThreadHandle_t)GetThreadID(), iNewPriority); #endif return iInitialPriority; } //--------------------------------------------------------- static uint32 DefaultWaitFunc( uint32 nHandles, CThreadEvent** ppHandles, int bWaitAll, uint32 timeout ) { return CThreadEvent::WaitForMultiple( nHandles, ppHandles, bWaitAll!=0, timeout ) ; } int CWorkerThread::Call(unsigned dwParam, unsigned timeout, bool fBoostPriority, WaitFunc_t waitFunc) { AssertMsg(!m_EventSend.Check(), "Cannot perform call if there's an existing call pending" ); AUTO_LOCK( m_Lock ); if (!IsAlive()) return WTCR_FAIL; int iInitialPriority = 0; if (fBoostPriority) { iInitialPriority = BoostPriority(); } // set the parameter, signal the worker thread, wait for the completion to be signaled m_Param = dwParam; m_EventComplete.Reset(); m_EventSend.Set(); WaitForReply( timeout, waitFunc ); if (fBoostPriority) SetPriority(iInitialPriority); return m_ReturnVal; } //--------------------------------------------------------- // // Wait for a request from the client // //--------------------------------------------------------- int CWorkerThread::WaitForReply( unsigned timeout ) { return WaitForReply( timeout, NULL ); } int CWorkerThread::WaitForReply( unsigned timeout, WaitFunc_t pfnWait ) { if (!pfnWait) { pfnWait = &DefaultWaitFunc; } CThreadEvent *waits[] = { &m_EventComplete, &m_ExitEvent }; unsigned result; bool bInDebugger = Plat_IsInDebugSession(); uint32 dwActualTimeout = ( (timeout==TT_INFINITE) ? 30000 : timeout ); do { #ifdef WIN32 // Make sure the thread handle hasn't been closed if ( !GetThreadHandle() ) { result = 1; break; } #endif result = (*pfnWait)( ARRAYSIZE( waits ), waits, false, dwActualTimeout ); AssertMsg(timeout != TT_INFINITE || result != TW_TIMEOUT, "Possible hung thread, call to thread timed out"); } while ( bInDebugger && ( timeout == TT_INFINITE && result == TW_TIMEOUT ) ); if ( result != 0 ) { if (result == TW_TIMEOUT) { m_ReturnVal = WTCR_TIMEOUT; } else if (result == 1) { DevMsg( 2, "Thread failed to respond, probably exited\n"); m_EventSend.Reset(); m_ReturnVal = WTCR_TIMEOUT; } else { m_EventSend.Reset(); m_ReturnVal = WTCR_THREAD_GONE; } } return m_ReturnVal; } //--------------------------------------------------------- // // Wait for a request from the client // //--------------------------------------------------------- bool CWorkerThread::WaitForCall(unsigned * pResult) { return WaitForCall(TT_INFINITE, pResult); } //--------------------------------------------------------- bool CWorkerThread::WaitForCall(unsigned dwTimeout, unsigned * pResult) { bool returnVal = m_EventSend.Wait(dwTimeout); if (pResult) *pResult = m_Param; return returnVal; } //--------------------------------------------------------- // // is there a request? // bool CWorkerThread::PeekCall(unsigned * pParam) { if (!m_EventSend.Check()) { return false; } else { if (pParam) { *pParam = m_Param; } return true; } } //--------------------------------------------------------- // // Reply to the request // void CWorkerThread::Reply(unsigned dw) { m_Param = 0; m_ReturnVal = dw; // The request is now complete so PeekCall() should fail from // now on // // This event should be reset BEFORE we signal the client m_EventSend.Reset(); // Tell the client we're finished m_EventComplete.Set(); } //----------------------------------------------------------------------------- #if defined( _PS3 ) /******************************************************************************* * PS3 equivalent to Win32 function for setting events *******************************************************************************/ BOOL SetEvent( CThreadEvent *pEvent ) { bool bRetVal = pEvent->Set(); if ( !bRetVal ) Assert(0); return bRetVal; } /******************************************************************************* * PS3 equivalent to Win32 function for resetting events *******************************************************************************/ BOOL ResetEvent( CThreadEvent *pEvent ) { return pEvent->Reset(); } #define MAXIMUM_WAIT_OBJECTS 64 /******************************************************************************* * Wait for a selection of events to terminate *******************************************************************************/ DWORD WaitForMultipleObjects( DWORD nCount, CThreadEvent **lppHandles, BOOL bWaitAll, DWORD dwMilliseconds ) { ////////////////////////////////////////////////////////////// #ifndef NEW_WAIT_FOR_MULTIPLE_OBJECTS ////////////////////////////////////////////////////////////// // Support for a limited amount of events if ( nCount >= MAXIMUM_WAIT_OBJECTS ) { Assert(0); return false; } bool bRunning = true; unsigned int result = TW_FAILED; // For bWaitAll int numEvent = 0; int eventComplete[ MAXIMUM_WAIT_OBJECTS ] = {0}; uint64_t timeDiffMS = 0; uint64_t startTimeMS = Plat_MSTime(); uint64_t endTimeMS = 0; while ( bRunning ) { // Check for a timeout if ( bRunning && ( dwMilliseconds != INFINITE ) && ( timeDiffMS > dwMilliseconds ) ) { result = TW_TIMEOUT; bRunning = false; } // Wait for all the events to be set if ( bWaitAll ) { for ( int event = 0; event < nCount; ++event ) { if ( lppHandles[event]->Wait(1) ) { // If an event is complete, mark it as complete in our list if ( eventComplete[ event ] == 0 ) { numEvent++; eventComplete[ event ] = 1; } } } // If all the events have been set, terminate the function if ( numEvent >= nCount ) { result = WAIT_OBJECT_0; bRunning = false; } } // Wait for one event to be set else { for ( int event = 0; event < nCount; ++event ) { if ( lppHandles[event]->Wait(1) ) { result = WAIT_OBJECT_0 + event; bRunning = false; break; } } } endTimeMS = Plat_MSTime(); timeDiffMS = endTimeMS - startTimeMS; } return result; ////////////////////////////////////////////////////////////// #else // NEW_WAIT_FOR_MULTIPLE_OBJECTS // (expected PS3 only) ////////////////////////////////////////////////////////////// #ifndef _PS3 #error This code was written expecting to be run on PS3. #endif // check if we have a wait objects semaphore if (!gbWaitObjectsCreated) { sys_semaphore_attribute_t semAttr; sys_semaphore_attribute_initialize(semAttr); sys_semaphore_create(&gWaitObjectsSemaphore, &semAttr, 0, 0xFFFF); gbWaitObjectsCreated = true; } // Support for a limited amount of events if ( nCount >= MAXIMUM_WAIT_OBJECTS ) { Assert(0); return false; } unsigned int result = WAIT_FAILED; int res = CELL_OK; int event = -1; int numEvent = 0; // run through events registering this thread with each one for (int i = 0; i < nCount; i++) { lppHandles[i]->RegisterWaitingThread(&gWaitObjectsSemaphore, i, &event); } // in the Source API, a timeOut of 0 means very short timeOut, not (as in the PS3 spec) an infinite timeout. // TT_INFINITE is #defined to 2^31-1, which means "infinite timeout" on PC and "72 minutes, 35 seconds" on PS3. // conversely, the code below (around deltaTime) expects to be able to compare against the timeout // value given here, so we cannot just replace 0 with 1 and TT_INFINITE with 0. // So, we replace 0 with 1, meaning "a very short time", and test for the special value TT_INFINITE // at the moment of calling sys_semaphore_wait, where we replace it with the real "infinite timeout" // value. It isn't safe to simply increase the declaration size of TT_INFINITE, because as you can // see it is often assigned to uint32s. // Also, Source timeouts are specified in milliseconds, and PS3 timeouts are in microseconds, // so we need to multiply by one thousand. uint32 timeOut = dwMilliseconds; if ( timeOut == 0 ) { timeOut = 1; } else if ( timeOut != TT_INFINITE ) { timeOut *= 1000; // note that it's impossible for dwMilliseconds * 1000 // to coincidentally equal TT_INFINITE since TT_INFINITE // is not divisible by 1000. COMPILE_TIME_ASSERT( TT_INFINITE % 1000 != 0 ); } COMPILE_TIME_ASSERT( TT_INFINITE != 0 ); // The code here was written expecting (working around) that TT_INFINITE is // MAXINT, so if you changed this number, please read the comment above and // carefully examine the code here to make sure that timeouts still work // correctly on PS3. Be aware that in many places in Source, a timeout of // 0 has some special meaning other than "infinite timeout", so track those // down too. // Wait for all the events to be set if ( bWaitAll ) { while (numEvent < nCount) { uint64_t deltaTime = Plat_USTime(); res = sys_semaphore_wait(gWaitObjectsSemaphore, timeOut == TT_INFINITE ? 0 : timeOut ); deltaTime = Plat_USTime() - deltaTime; if (res == ETIMEDOUT) { result = TW_TIMEOUT; break; } else if (res == CELL_OK) { numEvent++; if (deltaTime >= timeOut) { // note - if this is not truly a time out // then it will be set to WAIT_OBJECT_0 // after this loop result = TW_TIMEOUT; break; } else { timeOut -= deltaTime; } } else { result = TW_FAILED; break; } } if (numEvent >= nCount) { result = WAIT_OBJECT_0; } } else // Wait for one event to be set { // no event fired yet, wait on semaphore res = sys_semaphore_wait( gWaitObjectsSemaphore, timeOut == TT_INFINITE ? 0 : timeOut ); if (res == ETIMEDOUT) { result = TW_TIMEOUT; } else if (res == CELL_OK) { if ((event < 0) || (event >= nCount)) { DEBUG_ERROR("Bad event\n"); } result = WAIT_OBJECT_0 + event; } } // run through events unregistering this thread, for benefit // of those events that did not fire, or fired before semaphore // was registered for (int i = 0; i < nCount; i++) { lppHandles[i]->UnregisterWaitingThread(&gWaitObjectsSemaphore); } // reset semaphore while (sys_semaphore_trywait(gWaitObjectsSemaphore) != EBUSY); return result; ////////////////////////////////////////////////////////////// #endif // NEW_WAIT_FOR_MULTIPLE_OBJECTS ////////////////////////////////////////////////////////////// } #endif