#ifndef UTLTSHASH_H #define UTLTSHASH_H #ifdef _WIN32 #pragma once #endif #include #include "threadtools.h" #include "mempool.h" #include "generichash.h" typedef intp UtlTSHashHandle_t; template < class T > abstract_class ITSHashConstructor { public: virtual void Construct(T * pElement) = 0; }; template < class T > class CDefaultTSHashConstructor : public ITSHashConstructor< T > { public: virtual void Construct(T* pElement) { ::Construct(pElement); } }; template < int BUCKET_COUNT, class KEYTYPE = intp > class CUtlTSHashGenericHash { public: static int Hash(const KEYTYPE& key, int nBucketMask) { int nHash = HashIntConventional((intp)key); if (BUCKET_COUNT <= USHRT_MAX) { nHash ^= (nHash >> 16); } if (BUCKET_COUNT <= UCHAR_MAX) { nHash ^= (nHash >> 8); } return (nHash & nBucketMask); } static bool Compare(const KEYTYPE& lhs, const KEYTYPE& rhs) { return lhs == rhs; } }; template < int BUCKET_COUNT, class KEYTYPE > class CUtlTSHashUseKeyHashMethod { public: static int Hash(const KEYTYPE& key, int nBucketMask) { uint32 nHash = key.HashValue(); return (nHash & nBucketMask); } static bool Compare(const KEYTYPE& lhs, const KEYTYPE& rhs) { return lhs == rhs; } }; template< class T, int BUCKET_COUNT, class KEYTYPE = intp, class HashFuncs = CUtlTSHashGenericHash< BUCKET_COUNT, KEYTYPE >, int nAlignment = 0 > class CUtlTSHash { public: CUtlTSHash(int nAllocationCount); ~CUtlTSHash(); static UtlTSHashHandle_t InvalidHandle(void) { return (UtlTSHashHandle_t)0; } UtlTSHashHandle_t Find(KEYTYPE uiKey); UtlTSHashHandle_t Insert(KEYTYPE uiKey, const T& data, bool* pDidInsert = NULL); UtlTSHashHandle_t Insert(KEYTYPE uiKey, ITSHashConstructor* pConstructor, bool* pDidInsert = NULL); UtlTSHashHandle_t FastInsert(KEYTYPE uiKey, const T& data); UtlTSHashHandle_t FastInsert(KEYTYPE uiKey, ITSHashConstructor* pConstructor); void Commit(); void FindAndRemove(KEYTYPE uiKey); void Remove(UtlTSHashHandle_t hHash) { FindAndRemove(GetID(hHash)); } void RemoveAll(void); void Purge(void); int Count() const; int GetElements(int nFirstElement, int nCount, UtlTSHashHandle_t* pHandles) const; T& Element(UtlTSHashHandle_t hHash); T const& Element(UtlTSHashHandle_t hHash) const; T& operator[](UtlTSHashHandle_t hHash); T const& operator[](UtlTSHashHandle_t hHash) const; KEYTYPE GetID(UtlTSHashHandle_t hHash) const; UtlTSHashHandle_t ElementPtrToHandle(T* pElement) const; private: template < typename Data_t > struct HashFixedDataInternal_t { KEYTYPE m_uiKey; HashFixedDataInternal_t< Data_t >* m_pNext; Data_t m_Data; }; typedef HashFixedDataInternal_t HashFixedData_t; enum { BUCKET_MASK = BUCKET_COUNT - 1 }; struct HashBucket_t { HashFixedData_t* m_pFirst; HashFixedData_t* m_pFirstUncommitted; CThreadSpinRWLock m_AddLock; }; UtlTSHashHandle_t Find(KEYTYPE uiKey, HashFixedData_t* pFirstElement, HashFixedData_t* pLastElement); UtlTSHashHandle_t InsertUncommitted(KEYTYPE uiKey, HashBucket_t& bucket); CMemoryPoolMT m_EntryMemory; HashBucket_t m_aBuckets[BUCKET_COUNT]; bool m_bNeedsCommit; #ifdef _DEBUG CInterlockedInt m_ContentionCheck; #endif }; template CUtlTSHash::CUtlTSHash(int nAllocationCount) : m_EntryMemory(sizeof(HashFixedData_t), nAllocationCount, CUtlMemoryPool::GROW_SLOW, MEM_ALLOC_CLASSNAME(HashFixedData_t), nAlignment) { #ifdef _DEBUG m_ContentionCheck = 0; #endif m_bNeedsCommit = false; for (int i = 0; i < BUCKET_COUNT; i++) { HashBucket_t& bucket = m_aBuckets[i]; bucket.m_pFirst = NULL; bucket.m_pFirstUncommitted = NULL; } } template CUtlTSHash::~CUtlTSHash() { #ifdef _DEBUG if (m_ContentionCheck != 0) { DebuggerBreak(); } #endif Purge(); } template inline void CUtlTSHash::Purge(void) { RemoveAll(); } template inline int CUtlTSHash::Count() const { return m_EntryMemory.Count(); } template int CUtlTSHash::GetElements(int nFirstElement, int nCount, UtlTSHashHandle_t* pHandles) const { int nIndex = 0; for (int i = 0; i < BUCKET_COUNT; i++) { const HashBucket_t& bucket = m_aBuckets[i]; bucket.m_AddLock.LockForRead(); for (HashFixedData_t* pElement = bucket.m_pFirstUncommitted; pElement; pElement = pElement->m_pNext) { if (--nFirstElement >= 0) continue; pHandles[nIndex++] = (UtlTSHashHandle_t)pElement; if (nIndex >= nCount) { bucket.m_AddLock.UnlockRead(); return nIndex; } } bucket.m_AddLock.UnlockRead(); } return nIndex; } template inline UtlTSHashHandle_t CUtlTSHash::InsertUncommitted(KEYTYPE uiKey, HashBucket_t& bucket) { m_bNeedsCommit = true; HashFixedData_t* pNewElement = static_cast(m_EntryMemory.Alloc()); pNewElement->m_pNext = bucket.m_pFirstUncommitted; bucket.m_pFirstUncommitted = pNewElement; pNewElement->m_uiKey = uiKey; return (UtlTSHashHandle_t)pNewElement; } template inline UtlTSHashHandle_t CUtlTSHash::Insert(KEYTYPE uiKey, const T& data, bool* pDidInsert) { #ifdef _DEBUG if (m_ContentionCheck != 0) { DebuggerBreak(); } #endif if (pDidInsert) { *pDidInsert = false; } int iBucket = HashFuncs::Hash(uiKey, BUCKET_MASK); HashBucket_t& bucket = m_aBuckets[iBucket]; UtlTSHashHandle_t h = Find(uiKey); if (h != InvalidHandle()) return h; bucket.m_AddLock.LockForWrite(); h = Find(uiKey, bucket.m_pFirstUncommitted, bucket.m_pFirst); if (h == InvalidHandle()) { h = InsertUncommitted(uiKey, bucket); CopyConstruct(&Element(h), data); if (pDidInsert) { *pDidInsert = true; } } bucket.m_AddLock.UnlockWrite(); return h; } template inline UtlTSHashHandle_t CUtlTSHash::Insert(KEYTYPE uiKey, ITSHashConstructor* pConstructor, bool* pDidInsert) { #ifdef _DEBUG if (m_ContentionCheck != 0) { DebuggerBreak(); } #endif if (pDidInsert) { *pDidInsert = false; } UtlTSHashHandle_t h = Find(uiKey); if (h != InvalidHandle()) return h; int iBucket = HashFuncs::Hash(uiKey, BUCKET_MASK); HashBucket_t& bucket = m_aBuckets[iBucket]; bucket.m_AddLock.LockForWrite(); h = Find(uiKey, bucket.m_pFirstUncommitted, bucket.m_pFirst); if (h == InvalidHandle()) { h = InsertUncommitted(uiKey, bucket); pConstructor->Construct(&Element(h)); if (pDidInsert) { *pDidInsert = true; } } bucket.m_AddLock.UnlockWrite(); return h; } template inline UtlTSHashHandle_t CUtlTSHash::FastInsert(KEYTYPE uiKey, const T& data) { #ifdef _DEBUG if (m_ContentionCheck != 0) { DebuggerBreak(); } #endif int iBucket = HashFuncs::Hash(uiKey, BUCKET_MASK); HashBucket_t& bucket = m_aBuckets[iBucket]; bucket.m_AddLock.LockForWrite(); UtlTSHashHandle_t h = InsertUncommitted(uiKey, bucket); CopyConstruct(&Element(h), data); bucket.m_AddLock.UnlockWrite(); return h; } template inline UtlTSHashHandle_t CUtlTSHash::FastInsert(KEYTYPE uiKey, ITSHashConstructor* pConstructor) { #ifdef _DEBUG if (m_ContentionCheck != 0) { DebuggerBreak(); } #endif int iBucket = HashFuncs::Hash(uiKey, BUCKET_MASK); HashBucket_t& bucket = m_aBuckets[iBucket]; bucket.m_AddLock.LockForWrite(); UtlTSHashHandle_t h = InsertUncommitted(uiKey, bucket); pConstructor->Construct(&Element(h)); bucket.m_AddLock.UnlockWrite(); return h; } template inline void CUtlTSHash::Commit() { if (!m_bNeedsCommit) return; #ifdef _DEBUG m_ContentionCheck++; #endif for (int i = 0; i < BUCKET_COUNT; i++) { HashBucket_t& bucket = m_aBuckets[i]; bucket.m_AddLock.LockForRead(); bucket.m_pFirst = bucket.m_pFirstUncommitted; bucket.m_AddLock.UnlockRead(); } m_bNeedsCommit = false; #ifdef _DEBUG m_ContentionCheck--; #endif } template inline void CUtlTSHash::FindAndRemove(KEYTYPE uiKey) { if (m_EntryMemory.Count() == 0) return; #ifdef _DEBUG m_ContentionCheck++; #endif int iBucket = HashFuncs::Hash(uiKey, BUCKET_MASK); HashBucket_t& bucket = m_aBuckets[iBucket]; bucket.m_AddLock.LockForWrite(); HashFixedData_t* pPrev = NULL; for (HashFixedData_t* pElement = bucket.m_pFirstUncommitted; pElement; pPrev = pElement, pElement = pElement->m_pNext) { if (!HashFuncs::Compare(pElement->m_uiKey, uiKey)) continue; if (pPrev) { pPrev->m_pNext = pElement->m_pNext; } else { bucket.m_pFirstUncommitted = pElement->m_pNext; } if (bucket.m_pFirst == pElement) { bucket.m_pFirst = bucket.m_pFirst->m_pNext; } Destruct(&pElement->m_Data); #ifdef _DEBUG memset(pElement, 0xDD, sizeof(HashFixedData_t)); #endif m_EntryMemory.Free(pElement); break; } bucket.m_AddLock.UnlockWrite(); #ifdef _DEBUG m_ContentionCheck--; #endif } template inline void CUtlTSHash::RemoveAll(void) { m_bNeedsCommit = false; if (m_EntryMemory.Count() == 0) return; #ifdef _DEBUG m_ContentionCheck++; #endif for (int i = 0; i < BUCKET_COUNT; i++) { HashBucket_t& bucket = m_aBuckets[i]; bucket.m_AddLock.LockForWrite(); for (HashFixedData_t* pElement = bucket.m_pFirstUncommitted; pElement; pElement = pElement->m_pNext) { Destruct(&pElement->m_Data); } bucket.m_pFirst = NULL; bucket.m_pFirstUncommitted = NULL; bucket.m_AddLock.UnlockWrite(); } m_EntryMemory.Clear(); #ifdef _DEBUG m_ContentionCheck--; #endif } template inline UtlTSHashHandle_t CUtlTSHash::Find(KEYTYPE uiKey, HashFixedData_t* pFirstElement, HashFixedData_t* pLastElement) { #ifdef _DEBUG if (m_ContentionCheck != 0) { DebuggerBreak(); } #endif for (HashFixedData_t* pElement = pFirstElement; pElement != pLastElement; pElement = pElement->m_pNext) { if (HashFuncs::Compare(pElement->m_uiKey, uiKey)) return (UtlTSHashHandle_t)pElement; } return InvalidHandle(); } template inline UtlTSHashHandle_t CUtlTSHash::Find(KEYTYPE uiKey) { int iBucket = HashFuncs::Hash(uiKey, BUCKET_MASK); const HashBucket_t& bucket = m_aBuckets[iBucket]; UtlTSHashHandle_t h = Find(uiKey, bucket.m_pFirst, NULL); if (h != InvalidHandle()) return h; bucket.m_AddLock.LockForRead(); h = Find(uiKey, bucket.m_pFirstUncommitted, bucket.m_pFirst); bucket.m_AddLock.UnlockRead(); return h; } template inline T& CUtlTSHash::Element(UtlTSHashHandle_t hHash) { return ((HashFixedData_t*)hHash)->m_Data; } template inline T const& CUtlTSHash::Element(UtlTSHashHandle_t hHash) const { return ((HashFixedData_t*)hHash)->m_Data; } template inline T& CUtlTSHash::operator[](UtlTSHashHandle_t hHash) { return ((HashFixedData_t*)hHash)->m_Data; } template inline T const& CUtlTSHash::operator[](UtlTSHashHandle_t hHash) const { return ((HashFixedData_t*)hHash)->m_Data; } template inline KEYTYPE CUtlTSHash::GetID(UtlTSHashHandle_t hHash) const { return ((HashFixedData_t*)hHash)->m_uiKey; } template inline UtlTSHashHandle_t CUtlTSHash::ElementPtrToHandle(T* pElement) const { Assert(pElement); HashFixedData_t* pFixedData = (HashFixedData_t*)((uint8*)pElement - offsetof(HashFixedData_t, m_Data)); Assert(m_EntryMemory.IsAllocationWithinPool(pFixedData)); return (UtlTSHashHandle_t)pFixedData; } #endif