diff --git a/src/public/tier0/threadtools.h b/src/public/tier0/threadtools.h index 1ce388aa..a531269c 100644 --- a/src/public/tier0/threadtools.h +++ b/src/public/tier0/threadtools.h @@ -95,11 +95,6 @@ FORCEINLINE bool ThreadInterlockedAssignIf64(int64 volatile* pDest, int64 value, // Thread checking methods. // //----------------------------------------------------------------------------- -#ifndef BUILDING_MATHLIB - -extern ThreadId_t* g_ThreadMainThreadID; -extern ThreadId_t* g_ThreadServerFrameThreadID; - FORCEINLINE ThreadId_t ThreadGetCurrentId() { #ifdef _WIN32 @@ -117,6 +112,11 @@ FORCEINLINE ThreadId_t ThreadGetCurrentId() #endif } +#ifndef BUILDING_MATHLIB + +extern ThreadId_t* g_ThreadMainThreadID; +extern ThreadId_t* g_ThreadServerFrameThreadID; + FORCEINLINE bool ThreadInMainThread() { return (ThreadGetCurrentId() == (*g_ThreadMainThreadID)); @@ -340,6 +340,221 @@ template <> struct CAutoLockTypeDeducer { typedef CThr AUTO_LOCK_(CAutoLockTypeDeducer::Type_t, mutex) #endif +//----------------------------------------------------------------------------- +// +// CThreadSpinRWLock inline functions +// +//----------------------------------------------------------------------------- + +class ALIGN8 PLATFORM_CLASS CThreadSpinRWLock +{ +public: + CThreadSpinRWLock() + { + m_lockInfo.m_i32 = 0; + m_writerId = 0; +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + m_iWriteDepth = 0; +#endif + } + + bool IsLockedForWrite(); + bool IsLockedForRead(); + + FORCEINLINE bool TryLockForWrite(); + bool TryLockForWrite_UnforcedInline(); + + void LockForWrite(); + void SpinLockForWrite(); + + FORCEINLINE bool TryLockForRead(); + bool TryLockForRead_UnforcedInline(); + + void LockForRead(); + void SpinLockForRead(); + + void UnlockWrite(); + void UnlockRead(); + + bool TryLockForWrite() const { return const_cast(this)->TryLockForWrite(); } + bool TryLockForRead() const { return const_cast(this)->TryLockForRead(); } + void LockForRead() const { const_cast(this)->LockForRead(); } + void UnlockRead() const { const_cast(this)->UnlockRead(); } + void LockForWrite() const { const_cast(this)->LockForWrite(); } + void UnlockWrite() const { const_cast(this)->UnlockWrite(); } + +private: + enum + { + THREAD_SPIN = (8 * 1024) + }; + +#pragma warning(push) +#pragma warning(disable : 4201) + union LockInfo_t + { + struct + { +#if PLAT_LITTLE_ENDIAN + uint16 m_nReaders; + uint16 m_fWriting; +#else + uint16 m_fWriting; + uint16 m_nReaders; +#endif + }; + uint32 m_i32; + }; +#pragma warning(pop) + + LockInfo_t m_lockInfo; + ThreadId_t m_writerId; +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + int m_iWriteDepth; + uint32 pad; +#endif +} ALIGN8_POST; + +inline bool CThreadSpinRWLock::IsLockedForWrite() +{ + return (m_lockInfo.m_fWriting == 1); +} + +inline bool CThreadSpinRWLock::IsLockedForRead() +{ + return (m_lockInfo.m_nReaders > 0); +} + +FORCEINLINE bool CThreadSpinRWLock::TryLockForWrite() +{ + volatile LockInfo_t& curValue = m_lockInfo; + if (!(curValue.m_i32 & 0x00010000) && ThreadInterlockedAssignIf((LONG*)&curValue.m_i32, 0x00010000, 0)) + { + ThreadMemoryBarrier(); + Assert(m_iWriteDepth == 0 && m_writerId == 0); + m_writerId = ThreadGetCurrentId(); +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + m_iWriteDepth++; +#endif + return true; + } + + return false; +} + +inline bool CThreadSpinRWLock::TryLockForWrite_UnforcedInline() +{ + if (TryLockForWrite()) + { + return true; + } + +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + if (m_writerId != ThreadGetCurrentId()) + { + return false; + } + m_iWriteDepth++; + return true; +#else + return false; +#endif +} + +FORCEINLINE void CThreadSpinRWLock::LockForWrite() +{ + if (!TryLockForWrite()) + { + SpinLockForWrite(); + } +} + +FORCEINLINE bool CThreadSpinRWLock::TryLockForRead() +{ + volatile LockInfo_t& curValue = m_lockInfo; + if (!(curValue.m_i32 & 0x00010000)) // !m_lockInfo.m_fWriting + { + LockInfo_t oldValue; + LockInfo_t newValue; + oldValue.m_i32 = (curValue.m_i32 & 0xffff); + newValue.m_i32 = oldValue.m_i32 + 1; + + if (ThreadInterlockedAssignIf((LONG*)&m_lockInfo.m_i32, newValue.m_i32, oldValue.m_i32)) + { + ThreadMemoryBarrier(); + Assert(m_lockInfo.m_fWriting == 0); + return true; + } + } + return false; +} + +inline bool CThreadSpinRWLock::TryLockForRead_UnforcedInline() +{ +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + if (m_lockInfo.m_i32 & 0x00010000) // m_lockInfo.m_fWriting + { + if (m_writerId == ThreadGetCurrentId()) + { + m_lockInfo.m_nReaders++; + return true; + } + + return false; + } +#endif + return TryLockForRead(); +} + +FORCEINLINE void CThreadSpinRWLock::LockForRead() +{ + if (!TryLockForRead()) + { + SpinLockForRead(); + } +} + +FORCEINLINE void CThreadSpinRWLock::UnlockWrite() +{ + Assert(m_writerId == ThreadGetCurrentId()); +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + if (--m_iWriteDepth == 0) +#endif + { + m_writerId = 0; + ThreadMemoryBarrier(); + m_lockInfo.m_i32 = 0; + } +} + +#ifndef REENTRANT_THREAD_SPIN_RW_LOCK +FORCEINLINE +#else +inline +#endif +void CThreadSpinRWLock::UnlockRead() +{ + Assert(m_writerId == 0 || (m_writerId == ThreadGetCurrentId() && m_lockInfo.m_fWriting)); +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + if (!(m_lockInfo.m_i32 & 0x00010000)) // !m_lockInfo.m_fWriting +#endif + { + ThreadMemoryBarrier(); + ThreadInterlockedDecrement((int32*)&m_lockInfo.m_i32); + Assert(m_writerId == 0 && !m_lockInfo.m_fWriting); + } +#ifdef REENTRANT_THREAD_SPIN_RW_LOCK + else if (m_writerId == ThreadGetCurrentId()) + { + m_lockInfo.m_nReaders--; + } + else + { + RWLAssert(0); + } +#endif +} + #ifndef BUILDING_MATHLIB /////////////////////////////////////////////////////////////////////////////// class VThreadTools : public IDetour diff --git a/src/tier0/threadtools.cpp b/src/tier0/threadtools.cpp index edb33318..952cd4cc 100644 --- a/src/tier0/threadtools.cpp +++ b/src/tier0/threadtools.cpp @@ -97,6 +97,107 @@ int CThreadFastMutex::Unlock() return result; } +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); + } +} + // NOTE: originally the game exported 'ThreadInMainThread()' and ThreadInServerFrameThread(), // but since the game is built static, and all instances of said functions are inline, we had // to export the variable symbols instead and get them here to reimplement said functions.