From 2e6fe42703222ee05915c2891fccde86639b891c Mon Sep 17 00:00:00 2001 From: RaphaelIT7 <64648134+RaphaelIT7@users.noreply.github.com> Date: Sun, 16 Nov 2025 23:45:58 +0100 Subject: [PATCH] tier1: make keyvalues threadsafe & add other threadsafety related things --- public/tier1/mempool.h | 24 +++++++++ public/tier1/utldict.h | 102 +++++++++++++++++++-------------------- public/tier1/utlmapmt.h | 91 ++++++++++++++++++++++++++++++++++ public/tier1/utlsymbol.h | 7 +++ tier1/KeyValues.cpp | 6 +-- 5 files changed, 176 insertions(+), 54 deletions(-) create mode 100644 public/tier1/utlmapmt.h diff --git a/public/tier1/mempool.h b/public/tier1/mempool.h index 68837d0be..7f71ee615 100644 --- a/public/tier1/mempool.h +++ b/public/tier1/mempool.h @@ -150,6 +150,30 @@ class CClassMemoryPool : public CUtlMemoryPool void Clear(); }; +template< class T > +class CClassMemoryPoolMT : private CClassMemoryPool< T > +{ +public: + // dimhotepus: Alignment should derive from class. + CClassMemoryPoolMT( intp numElements, int growMode = GROW_FAST, unsigned short nAlignment = alignof(T) ) : + CClassMemoryPool( numElements, growMode, nAlignment ) { + #ifdef PLATFORM_64BITS + COMPILE_TIME_ASSERT( sizeof(CUtlMemoryPool) == 96 ); + #else + COMPILE_TIME_ASSERT( sizeof(CUtlMemoryPool) == 48 ); + #endif + } + + [[nodiscard]] T* Alloc() { AUTO_LOCK(m_lock); return CClassMemoryPool::Alloc(); }; + [[nodiscard]] T* AllocZero() { AUTO_LOCK(m_lock); return CClassMemoryPool::AllocZero(); }; + void Free( T *pMem ) { AUTO_LOCK(m_lock); CClassMemoryPool::Free( pMem ); }; + + void Clear() { AUTO_LOCK(m_lock); CClassMemoryPool::Clear(); }; + +private: + CThreadMutex m_lock; +}; + //----------------------------------------------------------------------------- // Specialized pool for aligned data management (e.g., Xbox cubemaps) diff --git a/public/tier1/utldict.h b/public/tier1/utldict.h index 9ac41695b..1a988500d 100644 --- a/public/tier1/utldict.h +++ b/public/tier1/utldict.h @@ -37,7 +37,7 @@ enum EDictCompareType //----------------------------------------------------------------------------- // A dictionary mapping from symbol to structure //----------------------------------------------------------------------------- -template +template class MapType = CUtlMap> class CUtlDict { public: @@ -99,7 +99,7 @@ class CUtlDict using IndexType_t = I; protected: - using DictElementMap_t = CUtlMap; + using DictElementMap_t = MapType; DictElementMap_t m_Elements; }; @@ -107,8 +107,8 @@ class CUtlDict //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- -template -CUtlDict::CUtlDict( int compareType, intp growSize, intp initSize ) : m_Elements( growSize, initSize ) +template class MapType> +CUtlDict::CUtlDict( int compareType, intp growSize, intp initSize ) : m_Elements( growSize, initSize ) { if ( compareType == k_eDictCompareTypeFilenames ) { @@ -124,14 +124,14 @@ CUtlDict::CUtlDict( int compareType, intp growSize, intp initSize ) : m_El } } -template -CUtlDict::~CUtlDict() +template class MapType> +CUtlDict::~CUtlDict() { Purge(); } -template -inline void CUtlDict::EnsureCapacity( intp num ) +template class MapType> +inline void CUtlDict::EnsureCapacity( intp num ) { return m_Elements.EnsureCapacity( num ); } @@ -139,14 +139,14 @@ inline void CUtlDict::EnsureCapacity( intp num ) //----------------------------------------------------------------------------- // gets particular elements //----------------------------------------------------------------------------- -template -inline T& CUtlDict::Element( I i ) +template class MapType> +inline T& CUtlDict::Element( I i ) { return m_Elements[i]; } -template -inline const T& CUtlDict::Element( I i ) const +template class MapType> +inline const T& CUtlDict::Element( I i ) const { return m_Elements[i]; } @@ -154,32 +154,32 @@ inline const T& CUtlDict::Element( I i ) const //----------------------------------------------------------------------------- // gets element names //----------------------------------------------------------------------------- -template -inline char *CUtlDict::GetElementName( I i ) +template class MapType> +inline char *CUtlDict::GetElementName( I i ) { return (char *)m_Elements.Key( i ); } -template -inline char const *CUtlDict::GetElementName( I i ) const +template class MapType> +inline char const *CUtlDict::GetElementName( I i ) const { return m_Elements.Key( i ); } -template -inline T& CUtlDict::operator[]( I i ) +template class MapType> +inline T& CUtlDict::operator[]( I i ) { return Element(i); } -template -inline const T & CUtlDict::operator[]( I i ) const +template class MapType> +inline const T & CUtlDict::operator[]( I i ) const { return Element(i); } -template -inline void CUtlDict::SetElementName( I i, char const *pName ) +template class MapType> +inline void CUtlDict::SetElementName( I i, char const *pName ) { MEM_ALLOC_CREDIT_CLASS(); // TODO: This makes a copy of the old element @@ -192,8 +192,8 @@ inline void CUtlDict::SetElementName( I i, char const *pName ) //----------------------------------------------------------------------------- // Num elements //----------------------------------------------------------------------------- -template -inline I CUtlDict::Count() const +template class MapType> +inline I CUtlDict::Count() const { return m_Elements.Count(); } @@ -201,8 +201,8 @@ inline I CUtlDict::Count() const //----------------------------------------------------------------------------- // Number of allocated slots //----------------------------------------------------------------------------- -template -inline I CUtlDict::MaxElement() const +template class MapType> +inline I CUtlDict::MaxElement() const { return m_Elements.MaxElement(); } @@ -210,8 +210,8 @@ inline I CUtlDict::MaxElement() const //----------------------------------------------------------------------------- // Checks if a node is valid and in the tree //----------------------------------------------------------------------------- -template -inline bool CUtlDict::IsValidIndex( I i ) const +template class MapType> +inline bool CUtlDict::IsValidIndex( I i ) const { return m_Elements.IsValidIndex(i); } @@ -220,8 +220,8 @@ inline bool CUtlDict::IsValidIndex( I i ) const //----------------------------------------------------------------------------- // Invalid index //----------------------------------------------------------------------------- -template -inline constexpr I CUtlDict::InvalidIndex() +template class MapType> +inline constexpr I CUtlDict::InvalidIndex() { return DictElementMap_t::InvalidIndex(); } @@ -230,8 +230,8 @@ inline constexpr I CUtlDict::InvalidIndex() //----------------------------------------------------------------------------- // Delete a node from the tree //----------------------------------------------------------------------------- -template -void CUtlDict::RemoveAt(I elem) +template class MapType> +void CUtlDict::RemoveAt(I elem) { free( (void *)m_Elements.Key( elem ) ); m_Elements.RemoveAt(elem); @@ -241,7 +241,7 @@ void CUtlDict::RemoveAt(I elem) //----------------------------------------------------------------------------- // remove a node in the tree //----------------------------------------------------------------------------- -template void CUtlDict::Remove( const char *search ) +template class MapType> void CUtlDict::Remove( const char *search ) { I node = Find( search ); if (node != InvalidIndex()) @@ -254,8 +254,8 @@ template void CUtlDict::Remove( const char *search ) //----------------------------------------------------------------------------- // Removes all nodes from the tree //----------------------------------------------------------------------------- -template -void CUtlDict::RemoveAll() +template class MapType> +void CUtlDict::RemoveAll() { typename DictElementMap_t::IndexType_t index = m_Elements.FirstInorder(); while ( index != m_Elements.InvalidIndex() ) @@ -267,15 +267,15 @@ void CUtlDict::RemoveAll() m_Elements.RemoveAll(); } -template -void CUtlDict::Purge() +template class MapType> +void CUtlDict::Purge() { RemoveAll(); } -template -void CUtlDict::PurgeAndDeleteElements() +template class MapType> +void CUtlDict::PurgeAndDeleteElements() { // Delete all the elements. I index = m_Elements.FirstInorder(); @@ -293,15 +293,15 @@ void CUtlDict::PurgeAndDeleteElements() //----------------------------------------------------------------------------- // inserts a node into the tree //----------------------------------------------------------------------------- -template -I CUtlDict::Insert( const char *pName, const T &element ) +template class MapType> +I CUtlDict::Insert( const char *pName, const T &element ) { MEM_ALLOC_CREDIT_CLASS(); return m_Elements.Insert( strdup( pName ), element ); } -template -I CUtlDict::Insert( const char *pName ) +template class MapType> +I CUtlDict::Insert( const char *pName ) { MEM_ALLOC_CREDIT_CLASS(); return m_Elements.Insert( strdup( pName ) ); @@ -311,8 +311,8 @@ I CUtlDict::Insert( const char *pName ) //----------------------------------------------------------------------------- // finds a node in the tree //----------------------------------------------------------------------------- -template -I CUtlDict::Find( const char *pName ) const +template class MapType> +I CUtlDict::Find( const char *pName ) const { MEM_ALLOC_CREDIT_CLASS(); if ( pName ) @@ -324,8 +324,8 @@ I CUtlDict::Find( const char *pName ) const //----------------------------------------------------------------------------- // returns true if we already have this node //----------------------------------------------------------------------------- -template -bool CUtlDict::HasElement( const char *pName ) const +template class MapType> +bool CUtlDict::HasElement( const char *pName ) const { if ( pName ) return m_Elements.IsValidIndex( m_Elements.Find( pName ) ); @@ -337,14 +337,14 @@ bool CUtlDict::HasElement( const char *pName ) const //----------------------------------------------------------------------------- // Iteration methods //----------------------------------------------------------------------------- -template -I CUtlDict::First() const +template class MapType> +I CUtlDict::First() const { return m_Elements.FirstInorder(); } -template -I CUtlDict::Next( I i ) const +template class MapType> +I CUtlDict::Next( I i ) const { return m_Elements.NextInorder(i); } diff --git a/public/tier1/utlmapmt.h b/public/tier1/utlmapmt.h new file mode 100644 index 000000000..0b148d0dc --- /dev/null +++ b/public/tier1/utlmapmt.h @@ -0,0 +1,91 @@ +#ifndef UTLMAPMT_H +#define UTLMAPMT_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/dbg.h" +#include "utlmap.h" + +//----------------------------------------------------------------------------- +// +// Purpose: Same as CUtlMap but threadsafe +// +//----------------------------------------------------------------------------- + +template +class CUtlMapMT : public CUtlMap +{ +public: + using Base = CUtlMap; + using KeyType_t = typename Base::KeyType_t; + using ElemType_t = typename Base::ElemType_t; + using IndexType_t = typename Base::IndexType_t; + using LessFunc_t = typename Base::LessFunc_t; + + // Constructors + CUtlMapMT(intp growSize = 0, intp initSize = 0, LessFunc_t lessfunc = 0) + : Base(growSize, initSize, lessfunc) {} + + CUtlMapMT(LessFunc_t lessfunc) : Base(lessfunc) {} + + void EnsureCapacity(intp num) { AUTO_LOCK_WRITE(m_lock); Base::EnsureCapacity(num); } + + ElemType_t &Element(IndexType_t i) { AUTO_LOCK_READ(m_lock); return Base::Element(i); } + const ElemType_t &Element(IndexType_t i) const { AUTO_LOCK_READ(m_lock); return Base::Element(i); } + + ElemType_t &operator[](IndexType_t i) { AUTO_LOCK_READ(m_lock); return Base::operator[](i); } + const ElemType_t &operator[](IndexType_t i) const { AUTO_LOCK_READ(m_lock); return Base::operator[](i); } + + KeyType_t &Key(IndexType_t i) { AUTO_LOCK_READ(m_lock); return Base::Key(i); } + const KeyType_t &Key(IndexType_t i) const { AUTO_LOCK_READ(m_lock); return Base::Key(i); } + + size_t Count() const { AUTO_LOCK_READ(m_lock); return Base::Count(); } + IndexType_t MaxElement() const { AUTO_LOCK_READ(m_lock); return Base::MaxElement(); } + bool IsValidIndex(IndexType_t i) const { AUTO_LOCK_READ(m_lock); return Base::IsValidIndex(i); } + bool IsValid() const { AUTO_LOCK_READ(m_lock); return Base::IsValid(); } + + void SetLessFunc(LessFunc_t func) { AUTO_LOCK_WRITE(m_lock); Base::SetLessFunc(func); } + + IndexType_t Insert(const KeyType_t &key, const ElemType_t &insert) { AUTO_LOCK_WRITE(m_lock); return Base::Insert(key, insert); } + IndexType_t Insert(const KeyType_t &key) { AUTO_LOCK_WRITE(m_lock); return Base::Insert(key); } + + IndexType_t Find(const KeyType_t &key) const { AUTO_LOCK_READ(m_lock); return Base::Find(key); } + + void RemoveAt(IndexType_t i) { AUTO_LOCK_WRITE(m_lock); Base::RemoveAt(i); } + bool Remove(const KeyType_t &key) { AUTO_LOCK_WRITE(m_lock); return Base::Remove(key); } + void RemoveAll() { AUTO_LOCK_WRITE(m_lock); Base::RemoveAll(); } + void Purge() { AUTO_LOCK_WRITE(m_lock); Base::Purge(); } + void PurgeAndDeleteElements() { AUTO_LOCK_WRITE(m_lock); Base::PurgeAndDeleteElements(); } + + IndexType_t FirstInorder() const { AUTO_LOCK_READ(m_lock); return Base::FirstInorder(); } + IndexType_t NextInorder(IndexType_t i) const { AUTO_LOCK_READ(m_lock); return Base::NextInorder(i); } + IndexType_t PrevInorder(IndexType_t i) const { AUTO_LOCK_READ(m_lock); return Base::PrevInorder(i); } + IndexType_t LastInorder() const { AUTO_LOCK_READ(m_lock); return Base::LastInorder(); } + + void Reinsert(const KeyType_t &key, IndexType_t i) { AUTO_LOCK_WRITE(m_lock); Base::Reinsert(key, i); } + + IndexType_t InsertOrReplace(const KeyType_t &key, const ElemType_t &insert) + { + AUTO_LOCK_WRITE(m_lock); + return Base::InsertOrReplace(key, insert); + } + + void Swap(CUtlMap &that) + { + // Lock both maps to avoid race + AUTO_LOCK_WRITE(m_lock); + AUTO_LOCK_WRITE(static_cast&>(that).m_lock); + Base::Swap(that); + } + +private: +#if defined(WIN32) || defined(_WIN32) + mutable CThreadSpinRWLock m_lock; +#else + mutable CThreadRWLock m_lock; +#endif +}; + +#endif // UTLMAPMT_H diff --git a/public/tier1/utlsymbol.h b/public/tier1/utlsymbol.h index a5f56b4fb..ec3367f24 100644 --- a/public/tier1/utlsymbol.h +++ b/public/tier1/utlsymbol.h @@ -197,6 +197,13 @@ class CUtlSymbolTableMT : private CUtlSymbolTable m_lock.UnlockRead(); return pszResult; } + + void RemoveAll() + { + m_lock.LockForWrite(); + CUtlSymbolTable::RemoveAll(); + m_lock.UnlockWrite(); + } private: #if defined(WIN32) || defined(_WIN32) diff --git a/tier1/KeyValues.cpp b/tier1/KeyValues.cpp index 4838b300f..75fb8d89e 100644 --- a/tier1/KeyValues.cpp +++ b/tier1/KeyValues.cpp @@ -34,7 +34,7 @@ // memdbgon must be the last include file in a .cpp file!!! #include -static const char * s_LastFileLoadingFrom = "unknown"; // just needed for error messages +static thread_local const char * s_LastFileLoadingFrom = "unknown"; // just needed for error messages // Statics for the growable string table HKeySymbol (*KeyValues::s_pfGetSymbolForString)( const char *name, bool bCreate ) = &KeyValues::GetSymbolForStringClassic; @@ -42,7 +42,7 @@ const char *(*KeyValues::s_pfGetStringForSymbol)( HKeySymbol symbol ) = &KeyValu CKeyValuesGrowableStringTable *KeyValues::s_pGrowableStringTable = nullptr; #define KEYVALUES_TOKEN_SIZE 4096 -static char s_pTokenBuf[KEYVALUES_TOKEN_SIZE]; +static thread_local char s_pTokenBuf[KEYVALUES_TOKEN_SIZE]; #define INTERNALWRITE( pData, len ) InternalWrite( filesystem, f, pBuf, pData, len ) @@ -124,7 +124,7 @@ class CKeyValuesErrorStack const char *m_pFilename{"NULL"}; int m_errorIndex{0}; int m_maxErrorIndex{0}; -} g_KeyValuesErrorStack; +} thread_local g_KeyValuesErrorStack; // a simple helper that creates stack entries as it goes in & out of scope