Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

988 lines
40 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: a fast growable hashtable with stored hashes, L2-friendly behavior.
  4. // Useful as a string dictionary or a low-overhead set/map for small POD types.
  5. //
  6. // Usage notes:
  7. // - handles are NOT STABLE across element removal! use RemoveAndAdvance()
  8. // if you are removing elements while iterating through the hashtable.
  9. // Use CUtlStableHashtable if you need stable handles (less efficient).
  10. // - handles are also NOT STABLE across element insertion. The handle
  11. // resulting from the insertion of an element may not retreive the
  12. // same (or any!) element after further insertions. Again, use
  13. // CUtlStableHashtable if you need stable handles
  14. // - Insert() first searches for an existing match and returns it if found
  15. // - a value type of "empty_t" can be used to eliminate value storage and
  16. // switch Element() to return const Key references instead of values
  17. // - an extra user flag bit is accessible via Get/SetUserFlag()
  18. // - hash function pointer / functor is exposed via GetHashRef()
  19. // - comparison function pointer / functor is exposed via GetEqualRef()
  20. // - if your value type cannot be copy-constructed, use key-only Insert()
  21. // to default-initialize the value and then manipulate it afterwards.
  22. // - The reason that UtlHashtable permutes itself and invalidates
  23. // iterators is to make it faster in the case where you are not
  24. // tracking iterators. If you use it as a set or a map ("is this
  25. // value a member?") as opposed to a long-term container, then you
  26. // probably don't need stable iterators. Hashtable tries to place
  27. // newly inserted data in the primary hash slot, making an
  28. // assumption that if you inserted it recently, you're more likely
  29. // to access it than if you inserted something a long time
  30. // ago. It's effectively trying to minimize cache misses for hot
  31. // data if you add and remove a lot.
  32. // If you don't care too much about cache misses, UtlStableHashtable
  33. // is what you're looking for
  34. //
  35. // Implementation notes:
  36. // - overall hash table load is kept between .25 and .75
  37. // - items which would map to the same ideal slot are chained together
  38. // - chained items are stored sequentially in adjacent free spaces
  39. // - "root" entries are prioritized over chained entries; if a
  40. // slot is not occupied by an item in its root position, the table
  41. // is guaranteed to contain no keys which would hash to that slot.
  42. // - new items go at the head of the chain (ie, in their root slot)
  43. // and evict / "bump" any chained entries which occupy that slot
  44. // - chain-following skips over unused holes and continues examining
  45. // table entries until a chain entry with FLAG_LAST is encountered
  46. //
  47. // CUtlHashtable< uint32 > setOfIntegers;
  48. // CUtlHashtable< const char* > setOfStringPointers;
  49. // CUtlHashtable< int, CUtlVector<blah_t> > mapFromIntsToArrays;
  50. //
  51. // $NoKeywords: $
  52. //
  53. // A closed-form (open addressing) hashtable with linear sequential probing.
  54. //=============================================================================//
  55. #ifndef UTLHASHTABLE_H
  56. #define UTLHASHTABLE_H
  57. #pragma once
  58. #include "utlcommon.h"
  59. #include "utlmemory.h"
  60. #include "mathlib/mathlib.h"
  61. #include "utllinkedlist.h"
  62. //-----------------------------------------------------------------------------
  63. // Henry Goffin (henryg) was here. Questions? Bugs? Go slap him around a bit.
  64. //-----------------------------------------------------------------------------
  65. typedef unsigned int UtlHashHandle_t;
  66. #define FOR_EACH_HASHTABLE( table, iter ) \
  67. for ( UtlHashHandle_t iter = (table).FirstHandle(); iter != (table).InvalidHandle(); iter = (table).NextHandle( iter ) )
  68. // CUtlHashtableEntry selects between 16 and 32 bit storage backing
  69. // for flags_and_hash depending on the size of the stored types.
  70. template < typename KeyT, typename ValueT = empty_t >
  71. class CUtlHashtableEntry
  72. {
  73. public:
  74. typedef CUtlKeyValuePair< KeyT, ValueT > KVPair;
  75. enum { INT16_STORAGE = ( sizeof( KVPair ) <= 2 ) };
  76. typedef typename CTypeSelect< INT16_STORAGE, int16, int32 >::type storage_t;
  77. enum
  78. {
  79. FLAG_FREE = INT16_STORAGE ? 0x8000 : 0x80000000, // must be high bit for IsValid and IdealIndex to work
  80. FLAG_LAST = INT16_STORAGE ? 0x4000 : 0x40000000,
  81. MASK_HASH = INT16_STORAGE ? 0x3FFF : 0x3FFFFFFF
  82. };
  83. storage_t flags_and_hash;
  84. storage_t data[ ( sizeof(KVPair) + sizeof(storage_t) - 1 ) / sizeof(storage_t) ];
  85. bool IsValid() const { return flags_and_hash >= 0; }
  86. void MarkInvalid() { int32 flag = FLAG_FREE; flags_and_hash = (storage_t)flag; }
  87. const KVPair *Raw() const { return reinterpret_cast< const KVPair * >( &data[0] ); }
  88. const KVPair *operator->() const { Assert( IsValid() ); return reinterpret_cast< const KVPair * >( &data[0] ); }
  89. KVPair *Raw() { return reinterpret_cast< KVPair * >( &data[0] ); }
  90. KVPair *operator->() { Assert( IsValid() ); return reinterpret_cast< KVPair * >( &data[0] ); }
  91. // Returns the ideal index of the data in this slot, or all bits set if invalid
  92. uint32 FORCEINLINE IdealIndex( uint32 slotmask ) const { return IdealIndex( flags_and_hash, slotmask ) | ( (int32)flags_and_hash >> 31 ); }
  93. // Use template tricks to fully define only one function that takes either 16 or 32 bits
  94. // and performs different logic without using "if ( INT16_STORAGE )", because GCC and MSVC
  95. // sometimes have trouble removing the constant branch, which is dumb... but whatever.
  96. // 16-bit hashes are simply too narrow for large hashtables; more mask bits than hash bits!
  97. // So we duplicate the hash bits. (Note: h *= MASK_HASH+2 is the same as h += h<<HASH_BITS)
  98. typedef typename CTypeSelect< INT16_STORAGE, int16, undefined_t >::type uint32_if16BitStorage;
  99. typedef typename CTypeSelect< INT16_STORAGE, undefined_t, int32 >::type uint32_if32BitStorage;
  100. static FORCEINLINE uint32 IdealIndex( uint32_if16BitStorage h, uint32 m ) { h &= MASK_HASH; h *= MASK_HASH + 2; return h & m; }
  101. static FORCEINLINE uint32 IdealIndex( uint32_if32BitStorage h, uint32 m ) { return h & m; }
  102. // More efficient than memcpy for the small types that are stored in a hashtable
  103. void MoveDataFrom( CUtlHashtableEntry &src )
  104. {
  105. storage_t * RESTRICT srcData = &src.data[0];
  106. for ( int i = 0; i < ARRAYSIZE( data ); ++i ) { data[i] = srcData[i]; }
  107. }
  108. };
  109. template <typename KeyT, typename ValueT = empty_t, typename KeyHashT = DefaultHashFunctor<KeyT>, typename KeyIsEqualT = DefaultEqualFunctor<KeyT>, typename AlternateKeyT = typename ArgumentTypeInfo<KeyT>::Alt_t >
  110. class CUtlHashtable
  111. {
  112. public:
  113. typedef UtlHashHandle_t handle_t;
  114. protected:
  115. typedef CUtlKeyValuePair<KeyT, ValueT> KVPair;
  116. typedef typename ArgumentTypeInfo<KeyT>::Arg_t KeyArg_t;
  117. typedef typename ArgumentTypeInfo<ValueT>::Arg_t ValueArg_t;
  118. typedef typename ArgumentTypeInfo<AlternateKeyT>::Arg_t KeyAlt_t;
  119. typedef CUtlHashtableEntry< KeyT, ValueT > entry_t;
  120. enum { FLAG_FREE = entry_t::FLAG_FREE };
  121. enum { FLAG_LAST = entry_t::FLAG_LAST };
  122. enum { MASK_HASH = entry_t::MASK_HASH };
  123. CUtlMemory< entry_t > m_table;
  124. int m_nUsed;
  125. int m_nMinSize;
  126. bool m_bSizeLocked;
  127. KeyIsEqualT m_eq;
  128. KeyHashT m_hash;
  129. // Allocate an empty table and then re-insert all existing entries.
  130. void DoRealloc( int size );
  131. // Move an existing entry to a free slot, leaving a hole behind
  132. void BumpEntry( unsigned int idx );
  133. // Insert an unconstructed KVPair at the primary slot
  134. int DoInsertUnconstructed( unsigned int h, bool allowGrow );
  135. // Implementation for Insert functions, constructs a KVPair
  136. // with either a default-construted or copy-constructed value
  137. template <typename KeyParamT> handle_t DoInsert( KeyParamT k, unsigned int h );
  138. template <typename KeyParamT> handle_t DoInsert( KeyParamT k, typename ArgumentTypeInfo<ValueT>::Arg_t v, unsigned int h, bool* pDidInsert );
  139. template <typename KeyParamT> handle_t DoInsertNoCheck( KeyParamT k, typename ArgumentTypeInfo<ValueT>::Arg_t v, unsigned int h );
  140. // Key lookup. Can also return previous-in-chain if result is chained.
  141. template <typename KeyParamT> handle_t DoLookup( KeyParamT x, unsigned int h, handle_t *pPreviousInChain ) const;
  142. // Remove single element by key + hash. Returns the index of the new hole
  143. // that was created. Returns InvalidHandle() if element was not found.
  144. template <typename KeyParamT> int DoRemove( KeyParamT x, unsigned int h );
  145. // Friend CUtlStableHashtable so that it can call our Do* functions directly
  146. template < typename K, typename V, typename S, typename H, typename E, typename A > friend class CUtlStableHashtable;
  147. public:
  148. explicit CUtlHashtable( int minimumSize = 32 )
  149. : m_nUsed(0), m_nMinSize(MAX(8, minimumSize)), m_bSizeLocked(false), m_eq(), m_hash() { }
  150. CUtlHashtable( int minimumSize, const KeyHashT &hash, KeyIsEqualT const &eq = KeyIsEqualT() )
  151. : m_nUsed(0), m_nMinSize(MAX(8, minimumSize)), m_bSizeLocked(false), m_eq(eq), m_hash(hash) { }
  152. CUtlHashtable( entry_t* pMemory, unsigned int nCount, const KeyHashT &hash = KeyHashT(), KeyIsEqualT const &eq = KeyIsEqualT() )
  153. : m_nUsed(0), m_nMinSize(8), m_bSizeLocked(false), m_eq(eq), m_hash(hash) { SetExternalBuffer( pMemory, nCount ); }
  154. ~CUtlHashtable() { RemoveAll(); }
  155. CUtlHashtable &operator=( CUtlHashtable const &src );
  156. // Set external memory
  157. void SetExternalBuffer( byte* pRawBuffer, unsigned int nBytes, bool bAssumeOwnership = false, bool bGrowable = false );
  158. void SetExternalBuffer( entry_t* pBuffer, unsigned int nSize, bool bAssumeOwnership = false, bool bGrowable = false );
  159. // Functor/function-pointer access
  160. KeyHashT& GetHashRef() { return m_hash; }
  161. KeyIsEqualT& GetEqualRef() { return m_eq; }
  162. KeyHashT const &GetHashRef() const { return m_hash; }
  163. KeyIsEqualT const &GetEqualRef() const { return m_eq; }
  164. // Handle validation
  165. bool IsValidHandle( handle_t idx ) const { return (unsigned)idx < (unsigned)m_table.Count() && m_table[idx].IsValid(); }
  166. static handle_t InvalidHandle() { return (handle_t) -1; }
  167. // Iteration functions
  168. handle_t FirstHandle() const { return NextHandle( (handle_t) -1 ); }
  169. handle_t NextHandle( handle_t start ) const;
  170. // Returns the number of unique keys in the table
  171. int Count() const { return m_nUsed; }
  172. // Key lookup, returns InvalidHandle() if not found
  173. handle_t Find( KeyArg_t k ) const { return DoLookup<KeyArg_t>( k, m_hash(k), NULL ); }
  174. handle_t Find( KeyArg_t k, unsigned int hash) const { Assert( hash == m_hash(k) ); return DoLookup<KeyArg_t>( k, hash, NULL ); }
  175. // Alternate-type key lookup, returns InvalidHandle() if not found
  176. handle_t Find( KeyAlt_t k ) const { return DoLookup<KeyAlt_t>( k, m_hash(k), NULL ); }
  177. handle_t Find( KeyAlt_t k, unsigned int hash) const { Assert( hash == m_hash(k) ); return DoLookup<KeyAlt_t>( k, hash, NULL ); }
  178. // True if the key is in the table
  179. bool HasElement( KeyArg_t k ) const { return InvalidHandle() != Find( k ); }
  180. bool HasElement( KeyAlt_t k ) const { return InvalidHandle() != Find( k ); }
  181. // Key insertion or lookup, always returns a valid handle
  182. handle_t Insert( KeyArg_t k ) { return DoInsert<KeyArg_t>( k, m_hash(k) ); }
  183. handle_t Insert( KeyArg_t k, ValueArg_t v, bool *pDidInsert = NULL ) { return DoInsert<KeyArg_t>( k, v, m_hash(k), pDidInsert ); }
  184. handle_t Insert( KeyArg_t k, ValueArg_t v, unsigned int hash, bool *pDidInsert = NULL ) { Assert( hash == m_hash(k) ); return DoInsert<KeyArg_t>( k, v, hash, pDidInsert ); }
  185. // Alternate-type key insertion or lookup, always returns a valid handle
  186. handle_t Insert( KeyAlt_t k ) { return DoInsert<KeyAlt_t>( k, m_hash(k) ); }
  187. handle_t Insert( KeyAlt_t k, ValueArg_t v, bool *pDidInsert = NULL ) { return DoInsert<KeyAlt_t>( k, v, m_hash(k), pDidInsert ); }
  188. handle_t Insert( KeyAlt_t k, ValueArg_t v, unsigned int hash, bool *pDidInsert = NULL ) { Assert( hash == m_hash(k) ); return DoInsert<KeyAlt_t>( k, v, hash, pDidInsert ); }
  189. // Key removal, returns false if not found
  190. bool Remove( KeyArg_t k ) { return DoRemove<KeyArg_t>( k, m_hash(k) ) >= 0; }
  191. bool Remove( KeyArg_t k, unsigned int hash ) { Assert( hash == m_hash(k) ); return DoRemove<KeyArg_t>( k, hash ) >= 0; }
  192. // Alternate-type key removal, returns false if not found
  193. bool Remove( KeyAlt_t k ) { return DoRemove<KeyAlt_t>( k, m_hash(k) ) >= 0; }
  194. bool Remove( KeyAlt_t k, unsigned int hash ) { Assert( hash == m_hash(k) ); return DoRemove<KeyAlt_t>( k, hash ) >= 0; }
  195. // Remove while iterating, returns the next handle for forward iteration
  196. // Note: aside from this, ALL handles are invalid if an element is removed
  197. handle_t RemoveAndAdvance( handle_t idx );
  198. // Remove by handle, convenient when you look up a handle and do something with it before removing the element
  199. void RemoveByHandle( handle_t idx );
  200. // Nuke contents
  201. void RemoveAll();
  202. // Nuke and release memory.
  203. void Purge() { RemoveAll(); m_table.Purge(); }
  204. // Reserve table capacity up front to avoid reallocation during insertions
  205. void Reserve( int expected ) { if ( expected > m_nUsed ) DoRealloc( expected * 4 / 3 ); }
  206. // Shrink to best-fit size, re-insert keys for optimal lookup
  207. void Compact( bool bMinimal ) { DoRealloc( bMinimal ? m_nUsed : ( m_nUsed * 4 / 3 ) ); }
  208. // Access functions. Note: if ValueT is empty_t, all functions return const keys.
  209. typedef typename KVPair::ValueReturn_t Element_t;
  210. KeyT const &Key( handle_t idx ) const { return m_table[idx]->m_key; }
  211. Element_t const &Element( handle_t idx ) const { return m_table[idx]->GetValue(); }
  212. Element_t &Element(handle_t idx) { return m_table[idx]->GetValue(); }
  213. Element_t const &operator[]( handle_t idx ) const { return m_table[idx]->GetValue(); }
  214. Element_t &operator[]( handle_t idx ) { return m_table[idx]->GetValue(); }
  215. void ReplaceKey( handle_t idx, KeyArg_t k ) { Assert( m_eq( m_table[idx]->m_key, k ) && m_hash( k ) == m_hash( m_table[idx]->m_key ) ); m_table[idx]->m_key = k; }
  216. void ReplaceKey( handle_t idx, KeyAlt_t k ) { Assert( m_eq( m_table[idx]->m_key, k ) && m_hash( k ) == m_hash( m_table[idx]->m_key ) ); m_table[idx]->m_key = k; }
  217. Element_t const &Get( KeyArg_t k, Element_t const &defaultValue ) const { handle_t h = Find( k ); if ( h != InvalidHandle() ) return Element( h ); return defaultValue; }
  218. Element_t const &Get( KeyAlt_t k, Element_t const &defaultValue ) const { handle_t h = Find( k ); if ( h != InvalidHandle() ) return Element( h ); return defaultValue; }
  219. Element_t const *GetPtr( KeyArg_t k ) const { handle_t h = Find(k); if ( h != InvalidHandle() ) return &Element( h ); return NULL; }
  220. Element_t const *GetPtr( KeyAlt_t k ) const { handle_t h = Find(k); if ( h != InvalidHandle() ) return &Element( h ); return NULL; }
  221. Element_t *GetPtr( KeyArg_t k ) { handle_t h = Find( k ); if ( h != InvalidHandle() ) return &Element( h ); return NULL; }
  222. Element_t *GetPtr( KeyAlt_t k ) { handle_t h = Find( k ); if ( h != InvalidHandle() ) return &Element( h ); return NULL; }
  223. // Swap memory and contents with another identical hashtable
  224. // (NOTE: if using function pointers or functors with state,
  225. // it is up to the caller to ensure that they are compatible!)
  226. void Swap( CUtlHashtable &other ) { m_table.Swap(other.m_table); ::V_swap(m_nUsed, other.m_nUsed); }
  227. // GetMemoryUsage returns all memory held by this class
  228. // and its held classes. It does not include sizeof(*this).
  229. size_t GetMemoryUsage() const
  230. {
  231. return m_table.AllocSize();
  232. }
  233. size_t GetReserveCount( )const
  234. {
  235. return m_table.Count();
  236. }
  237. #if _DEBUG
  238. // Validate the integrity of the hashtable
  239. void DbgCheckIntegrity() const;
  240. #endif
  241. private:
  242. CUtlHashtable(const CUtlHashtable& copyConstructorIsNotImplemented);
  243. };
  244. // Set external memory (raw byte buffer, best-fit)
  245. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  246. void CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::SetExternalBuffer( byte* pRawBuffer, unsigned int nBytes, bool bAssumeOwnership, bool bGrowable )
  247. {
  248. Assert( ((uintptr_t)pRawBuffer % __alignof(int)) == 0 );
  249. uint32 bestSize = LargestPowerOfTwoLessThanOrEqual( nBytes / sizeof(entry_t) );
  250. Assert( bestSize != 0 && bestSize*sizeof(entry_t) <= nBytes );
  251. return SetExternalBuffer( (entry_t*) pRawBuffer, bestSize, bAssumeOwnership, bGrowable );
  252. }
  253. // Set external memory (typechecked, must be power of two)
  254. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  255. void CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::SetExternalBuffer( entry_t* pBuffer, unsigned int nSize, bool bAssumeOwnership, bool bGrowable )
  256. {
  257. Assert( IsPowerOfTwo(nSize) );
  258. Assert( m_nUsed == 0 );
  259. for ( uint i = 0; i < nSize; ++i )
  260. pBuffer[i].MarkInvalid();
  261. if ( bAssumeOwnership )
  262. m_table.AssumeMemory( pBuffer, nSize );
  263. else
  264. m_table.SetExternalBuffer( pBuffer, nSize );
  265. m_bSizeLocked = !bGrowable;
  266. }
  267. // Allocate an empty table and then re-insert all existing entries.
  268. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  269. void CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::DoRealloc( int size )
  270. {
  271. Assert( !m_bSizeLocked );
  272. size = SmallestPowerOfTwoGreaterOrEqual( MAX( m_nMinSize, size ) );
  273. Assert( size > 0 && (uint)size <= entry_t::IdealIndex( ~0, 0x1FFFFFFF ) ); // reasonable power of 2
  274. Assert( size > m_nUsed );
  275. CUtlMemory<entry_t> oldTable;
  276. oldTable.Swap( m_table );
  277. entry_t * RESTRICT const pOldBase = oldTable.Base();
  278. m_table.EnsureCapacity( size );
  279. entry_t * const pNewBase = m_table.Base();
  280. for ( int i = 0; i < size; ++i )
  281. pNewBase[i].MarkInvalid();
  282. int nLeftToMove = m_nUsed;
  283. m_nUsed = 0;
  284. for ( int i = oldTable.Count() - 1; i >= 0; --i )
  285. {
  286. if ( pOldBase[i].IsValid() )
  287. {
  288. int newIdx = DoInsertUnconstructed( pOldBase[i].flags_and_hash, false );
  289. pNewBase[newIdx].MoveDataFrom( pOldBase[i] );
  290. if ( --nLeftToMove == 0 )
  291. break;
  292. }
  293. }
  294. Assert( nLeftToMove == 0 );
  295. }
  296. // Move an existing entry to a free slot, leaving a hole behind
  297. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  298. void CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::BumpEntry( unsigned int idx )
  299. {
  300. Assert( m_table[idx].IsValid() );
  301. Assert( m_nUsed < m_table.Count() );
  302. entry_t* table = m_table.Base();
  303. unsigned int slotmask = m_table.Count()-1;
  304. unsigned int new_flags_and_hash = table[idx].flags_and_hash & (FLAG_LAST | MASK_HASH);
  305. unsigned int chainid = entry_t::IdealIndex( new_flags_and_hash, slotmask );
  306. // Look for empty slots scanning forward, stripping FLAG_LAST as we go.
  307. // Note: this potentially strips FLAG_LAST from table[idx] if we pass it
  308. int newIdx = chainid; // start at ideal slot
  309. for ( ; ; newIdx = (newIdx + 1) & slotmask )
  310. {
  311. if ( table[newIdx].IdealIndex( slotmask ) == chainid )
  312. {
  313. if ( table[newIdx].flags_and_hash & FLAG_LAST )
  314. {
  315. table[newIdx].flags_and_hash &= ~FLAG_LAST;
  316. new_flags_and_hash |= FLAG_LAST;
  317. }
  318. continue;
  319. }
  320. if ( table[newIdx].IsValid() )
  321. {
  322. continue;
  323. }
  324. break;
  325. }
  326. // Did we pick something closer to the ideal slot, leaving behind a
  327. // FLAG_LAST bit on the current slot because we didn't scan past it?
  328. if ( table[idx].flags_and_hash & FLAG_LAST )
  329. {
  330. #ifdef _DEBUG
  331. Assert( new_flags_and_hash & FLAG_LAST );
  332. // Verify logic: we must have moved to an earlier slot, right?
  333. uint offset = ((uint)idx - chainid + slotmask + 1) & slotmask;
  334. uint newOffset = ((uint)newIdx - chainid + slotmask + 1) & slotmask;
  335. Assert( newOffset < offset );
  336. #endif
  337. // Scan backwards from old to new location, depositing FLAG_LAST on
  338. // the first match we find. (+slotmask) is the same as (-1) without
  339. // having to make anyone think about two's complement shenanigans.
  340. int scan = (idx + slotmask) & slotmask;
  341. while ( scan != newIdx )
  342. {
  343. if ( table[scan].IdealIndex( slotmask ) == chainid )
  344. {
  345. table[scan].flags_and_hash |= FLAG_LAST;
  346. new_flags_and_hash &= ~FLAG_LAST;
  347. break;
  348. }
  349. scan = (scan + slotmask) & slotmask;
  350. }
  351. }
  352. // Move entry to the free slot we found, leaving a hole at idx
  353. table[newIdx].flags_and_hash = new_flags_and_hash;
  354. table[newIdx].MoveDataFrom( table[idx] );
  355. table[idx].MarkInvalid();
  356. }
  357. // Insert a value at the root position for that value's hash chain.
  358. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  359. int CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::DoInsertUnconstructed( unsigned int h, bool allowGrow )
  360. {
  361. if ( allowGrow && !m_bSizeLocked )
  362. {
  363. // Keep the load factor between .25 and .75
  364. int newSize = m_nUsed + 1;
  365. if ( ( newSize*4 < m_table.Count() && m_table.Count() > m_nMinSize*2 ) || newSize*4 > m_table.Count()*3 )
  366. {
  367. DoRealloc( newSize * 4 / 3 );
  368. }
  369. }
  370. Assert( m_nUsed < m_table.Count() );
  371. ++m_nUsed;
  372. entry_t* table = m_table.Base();
  373. unsigned int slotmask = m_table.Count()-1;
  374. unsigned int new_flags_and_hash = FLAG_LAST | (h & MASK_HASH);
  375. unsigned int idx = entry_t::IdealIndex( h, slotmask );
  376. if ( table[idx].IdealIndex( slotmask ) == idx )
  377. {
  378. // There is already an entry in this chain.
  379. new_flags_and_hash &= ~FLAG_LAST;
  380. BumpEntry(idx);
  381. }
  382. else if ( table[idx].IsValid() )
  383. {
  384. // Somebody else is living in our ideal index but does not belong
  385. // to our entry chain; move it out of the way, start a new chain.
  386. BumpEntry(idx);
  387. }
  388. table[idx].flags_and_hash = new_flags_and_hash;
  389. return idx;
  390. }
  391. // Key lookup. Can also return previous-in-chain if result is a chained slot.
  392. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  393. template <typename KeyParamT>
  394. UtlHashHandle_t CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::DoLookup( KeyParamT x, unsigned int h, handle_t *pPreviousInChain ) const
  395. {
  396. if ( m_nUsed == 0 )
  397. {
  398. // Empty table.
  399. return (handle_t) -1;
  400. }
  401. const entry_t* table = m_table.Base();
  402. unsigned int slotmask = m_table.Count()-1;
  403. Assert( m_table.Count() > 0 && (slotmask & m_table.Count()) == 0 );
  404. unsigned int chainid = entry_t::IdealIndex( h, slotmask );
  405. unsigned int idx = chainid;
  406. if ( table[idx].IdealIndex( slotmask ) != chainid )
  407. {
  408. // Nothing in root position? No match.
  409. return (handle_t) -1;
  410. }
  411. // Linear scan until found or end of chain
  412. handle_t lastIdx = (handle_t) -1;
  413. while (1)
  414. {
  415. // Only examine this slot if it is valid and belongs to our hash chain
  416. if ( table[idx].IdealIndex( slotmask ) == chainid )
  417. {
  418. // Test the full-width hash to avoid unnecessary calls to m_eq()
  419. if ( ((table[idx].flags_and_hash ^ h) & MASK_HASH) == 0 && m_eq( table[idx]->m_key, x ) )
  420. {
  421. // Found match!
  422. if (pPreviousInChain)
  423. *pPreviousInChain = lastIdx;
  424. return (handle_t) idx;
  425. }
  426. if ( table[idx].flags_and_hash & FLAG_LAST )
  427. {
  428. // End of chain. No match.
  429. return (handle_t) -1;
  430. }
  431. lastIdx = (handle_t) idx;
  432. }
  433. idx = (idx + 1) & slotmask;
  434. }
  435. }
  436. // Key insertion, or return index of existing key if found
  437. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  438. template <typename KeyParamT>
  439. UtlHashHandle_t CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::DoInsert( KeyParamT k, unsigned int h )
  440. {
  441. handle_t idx = DoLookup<KeyParamT>( k, h, NULL );
  442. if ( idx == (handle_t) -1 )
  443. {
  444. idx = (handle_t) DoInsertUnconstructed( h, true );
  445. ConstructOneArg( m_table[ idx ].Raw(), k );
  446. }
  447. return idx;
  448. }
  449. // Key insertion, or return index of existing key if found
  450. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  451. template <typename KeyParamT>
  452. UtlHashHandle_t CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::DoInsert( KeyParamT k, typename ArgumentTypeInfo<ValueT>::Arg_t v, unsigned int h, bool *pDidInsert )
  453. {
  454. handle_t idx = DoLookup<KeyParamT>( k, h, NULL );
  455. if ( idx == (handle_t) -1 )
  456. {
  457. idx = (handle_t) DoInsertUnconstructed( h, true );
  458. ConstructTwoArg( m_table[ idx ].Raw(), k, v );
  459. if ( pDidInsert ) *pDidInsert = true;
  460. }
  461. else
  462. {
  463. if ( pDidInsert ) *pDidInsert = false;
  464. }
  465. return idx;
  466. }
  467. // Key insertion
  468. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  469. template <typename KeyParamT>
  470. UtlHashHandle_t CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::DoInsertNoCheck( KeyParamT k, typename ArgumentTypeInfo<ValueT>::Arg_t v, unsigned int h )
  471. {
  472. Assert( DoLookup<KeyParamT>( k, h, NULL ) == (handle_t) -1 );
  473. handle_t idx = (handle_t) DoInsertUnconstructed( h, true );
  474. ConstructTwoArg( m_table[ idx ].Raw(), k, v );
  475. return idx;
  476. }
  477. // Remove single element by key + hash. Returns the location of the new empty hole.
  478. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  479. template <typename KeyParamT>
  480. int CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::DoRemove( KeyParamT x, unsigned int h )
  481. {
  482. unsigned int slotmask = m_table.Count()-1;
  483. handle_t previous = (handle_t) -1;
  484. int idx = (int) DoLookup<KeyParamT>( x, h, &previous );
  485. if (idx == -1)
  486. {
  487. return -1;
  488. }
  489. enum { FAKEFLAG_ROOT = 1 };
  490. int nLastAndRootFlags = m_table[idx].flags_and_hash & FLAG_LAST;
  491. nLastAndRootFlags |= ( (uint)idx == m_table[idx].IdealIndex( slotmask ) );
  492. // Remove from table
  493. m_table[idx].MarkInvalid();
  494. Destruct( m_table[idx].Raw() );
  495. --m_nUsed;
  496. if ( nLastAndRootFlags == FLAG_LAST ) // last only, not root
  497. {
  498. // This was the end of the chain - mark previous as last.
  499. // (This isn't the root, so there must be a previous.)
  500. Assert( previous != (handle_t) -1 );
  501. m_table[previous].flags_and_hash |= FLAG_LAST;
  502. }
  503. if ( nLastAndRootFlags == FAKEFLAG_ROOT ) // root only, not last
  504. {
  505. // If we are removing the root and there is more to the chain,
  506. // scan to find the next chain entry and move it to the root.
  507. unsigned int chainid = entry_t::IdealIndex( h, slotmask );
  508. unsigned int nextIdx = idx;
  509. while (1)
  510. {
  511. nextIdx = (nextIdx + 1) & slotmask;
  512. if ( m_table[nextIdx].IdealIndex( slotmask ) == chainid )
  513. {
  514. break;
  515. }
  516. }
  517. Assert( !(m_table[nextIdx].flags_and_hash & FLAG_FREE) );
  518. // Leave a hole where the next entry in the chain was.
  519. m_table[idx].flags_and_hash = m_table[nextIdx].flags_and_hash;
  520. m_table[idx].MoveDataFrom( m_table[nextIdx] );
  521. m_table[nextIdx].MarkInvalid();
  522. return nextIdx;
  523. }
  524. // The hole is still where the element used to be.
  525. return idx;
  526. }
  527. // Assignment operator. It's up to the user to make sure that the hash and equality functors match.
  528. template <typename K, typename V, typename H, typename E, typename A>
  529. CUtlHashtable<K,V,H,E,A> &CUtlHashtable<K,V,H,E,A>::operator=( CUtlHashtable<K,V,H,E,A> const &src )
  530. {
  531. if ( &src != this )
  532. {
  533. Assert( !m_bSizeLocked || m_table.Count() >= src.m_nUsed );
  534. if ( !m_bSizeLocked )
  535. {
  536. Purge();
  537. Reserve(src.m_nUsed);
  538. }
  539. else
  540. {
  541. RemoveAll();
  542. }
  543. const entry_t * srcTable = src.m_table.Base();
  544. for ( int i = src.m_table.Count() - 1; i >= 0; --i )
  545. {
  546. if ( srcTable[i].IsValid() )
  547. {
  548. // If this assert trips, double-check that both hashtables
  549. // have the same hash function pointers or hash functor state!
  550. Assert( m_hash(srcTable[i]->m_key) == src.m_hash(srcTable[i]->m_key) );
  551. int newIdx = DoInsertUnconstructed( srcTable[i].flags_and_hash , false );
  552. CopyConstruct( m_table[newIdx].Raw(), *srcTable[i].Raw() ); // copy construct KVPair
  553. }
  554. }
  555. }
  556. return *this;
  557. }
  558. // Remove and return the next valid iterator for a forward iteration.
  559. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  560. UtlHashHandle_t CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::RemoveAndAdvance( UtlHashHandle_t idx )
  561. {
  562. Assert( IsValidHandle( idx ) );
  563. // TODO optimize, implement DoRemoveAt that does not need to re-evaluate equality in DoLookup
  564. int hole = DoRemove< KeyArg_t >( m_table[idx]->m_key, m_table[idx].flags_and_hash & MASK_HASH );
  565. // DoRemove returns the index of the element that it moved to fill the hole, if any.
  566. if ( hole <= (int) idx )
  567. {
  568. // Didn't fill, or filled from a previously seen element.
  569. return NextHandle( idx );
  570. }
  571. else
  572. {
  573. // Do not advance; slot has a new un-iterated value.
  574. Assert( IsValidHandle(idx) );
  575. return idx;
  576. }
  577. }
  578. // Remove and return the next valid iterator for a forward iteration.
  579. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  580. void CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::RemoveByHandle( UtlHashHandle_t idx )
  581. {
  582. Assert( IsValidHandle( idx ) );
  583. // Copied from RemoveAndAdvance(): TODO optimize, implement DoRemoveAt that does not need to re-evaluate equality in DoLookup
  584. DoRemove< KeyArg_t >( m_table[idx]->m_key, m_table[idx].flags_and_hash & MASK_HASH );
  585. }
  586. // Burn it with fire.
  587. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  588. void CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::RemoveAll()
  589. {
  590. int used = m_nUsed;
  591. if ( used != 0 )
  592. {
  593. entry_t* table = m_table.Base();
  594. for ( int i = m_table.Count() - 1; i >= 0; --i )
  595. {
  596. if ( table[i].IsValid() )
  597. {
  598. table[i].MarkInvalid();
  599. Destruct( table[i].Raw() );
  600. if ( --used == 0 )
  601. break;
  602. }
  603. }
  604. m_nUsed = 0;
  605. }
  606. }
  607. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  608. UtlHashHandle_t CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::NextHandle( handle_t start ) const
  609. {
  610. const entry_t *table = m_table.Base();
  611. for ( int i = (int)start + 1; i < m_table.Count(); ++i )
  612. {
  613. if ( table[i].IsValid() )
  614. return (handle_t) i;
  615. }
  616. return (handle_t) -1;
  617. }
  618. #if _DEBUG
  619. template <typename KeyT, typename ValueT, typename KeyHashT, typename KeyIsEqualT, typename AltKeyT>
  620. void CUtlHashtable<KeyT, ValueT, KeyHashT, KeyIsEqualT, AltKeyT>::DbgCheckIntegrity() const
  621. {
  622. // Stress test the hash table as a test of both container functionality
  623. // and also the validity of the user's Hash and Equal function objects.
  624. // NOTE: will fail if function objects require any sort of state!
  625. CUtlHashtable clone;
  626. unsigned int bytes = sizeof(entry_t)*max(16,m_table.Count());
  627. byte* tempbuf = (byte*) malloc(bytes);
  628. clone.SetExternalBuffer( tempbuf, bytes, false, false );
  629. clone = *this;
  630. int count = 0, roots = 0, ends = 0;
  631. int slotmask = m_table.Count() - 1;
  632. for (int i = 0; i < m_table.Count(); ++i)
  633. {
  634. if (!(m_table[i].flags_and_hash & FLAG_FREE)) ++count;
  635. if (m_table[i].IdealIndex(slotmask) == (uint)i) ++roots;
  636. if (m_table[i].flags_and_hash & FLAG_LAST) ++ends;
  637. if (m_table[i].IsValid())
  638. {
  639. Assert( Find(m_table[i]->m_key) == (handle_t)i );
  640. Verify( clone.Remove(m_table[i]->m_key) );
  641. }
  642. else
  643. {
  644. Assert( m_table[i].flags_and_hash == FLAG_FREE );
  645. }
  646. }
  647. Assert( count == Count() && count >= roots && roots == ends );
  648. Assert( clone.Count() == 0 );
  649. clone.Purge();
  650. free(tempbuf);
  651. }
  652. #endif
  653. //-----------------------------------------------------------------------
  654. // CUtlStableHashtable
  655. //-----------------------------------------------------------------------
  656. // Stable hashtables are less memory and cache efficient, but can be
  657. // iterated quickly and their element handles are completely stable.
  658. // Implemented as a hashtable which only stores indices, and a separate
  659. // CUtlLinkedList data table which contains key-value pairs; this may
  660. // change to a more efficient structure in the future if space becomes
  661. // critical. I have some ideas about that but not the time to implement
  662. // at the moment. -henryg
  663. // Note: RemoveAndAdvance is slower than in CUtlHashtable because the
  664. // key needs to be re-hashed under the current implementation.
  665. template <typename KeyT, typename ValueT = empty_t, typename KeyHashT = DefaultHashFunctor<KeyT>, typename KeyIsEqualT = DefaultEqualFunctor<KeyT>, typename IndexStorageT = uint16, typename AlternateKeyT = typename ArgumentTypeInfo<KeyT>::Alt_t >
  666. class CUtlStableHashtable
  667. {
  668. public:
  669. typedef typename ArgumentTypeInfo<KeyT>::Arg_t KeyArg_t;
  670. typedef typename ArgumentTypeInfo<ValueT>::Arg_t ValueArg_t;
  671. typedef typename ArgumentTypeInfo<AlternateKeyT>::Arg_t KeyAlt_t;
  672. typedef typename CTypeSelect< sizeof( IndexStorageT ) == 2, uint16, uint32 >::type IndexStorage_t;
  673. protected:
  674. COMPILE_TIME_ASSERT( sizeof( IndexStorage_t ) == sizeof( IndexStorageT ) );
  675. typedef CUtlKeyValuePair< KeyT, ValueT > KVPair;
  676. struct HashProxy;
  677. struct EqualProxy;
  678. struct IndirectIndex;
  679. typedef CUtlHashtable< IndirectIndex, empty_t, HashProxy, EqualProxy, AlternateKeyT > Hashtable_t;
  680. typedef CUtlLinkedList< KVPair, IndexStorage_t > LinkedList_t;
  681. template <typename KeyArgumentT> bool DoRemove( KeyArgumentT k );
  682. template <typename KeyArgumentT> UtlHashHandle_t DoFind( KeyArgumentT k ) const;
  683. template <typename KeyArgumentT> UtlHashHandle_t DoInsert( KeyArgumentT k );
  684. template <typename KeyArgumentT, typename ValueArgumentT> UtlHashHandle_t DoInsert( KeyArgumentT k, ValueArgumentT v );
  685. public:
  686. KeyHashT &GetHashRef() { return m_table.GetHashRef().m_hash; }
  687. KeyIsEqualT &GetEqualRef() { return m_table.GetEqualRef().m_eq; }
  688. KeyHashT const &GetHashRef() const { return m_table.GetHashRef().m_hash; }
  689. KeyIsEqualT const &GetEqualRef() const { return m_table.GetEqualRef().m_eq; }
  690. UtlHashHandle_t Insert( KeyArg_t k ) { return DoInsert<KeyArg_t>( k ); }
  691. UtlHashHandle_t Insert( KeyAlt_t k ) { return DoInsert<KeyAlt_t>( k ); }
  692. UtlHashHandle_t Insert( KeyArg_t k, ValueArg_t v ) { return DoInsert<KeyArg_t, ValueArg_t>( k, v ); }
  693. UtlHashHandle_t Insert( KeyAlt_t k, ValueArg_t v ) { return DoInsert<KeyAlt_t, ValueArg_t>( k, v ); }
  694. UtlHashHandle_t Find( KeyArg_t k ) const { return DoFind<KeyArg_t>( k ); }
  695. UtlHashHandle_t Find( KeyAlt_t k ) const { return DoFind<KeyAlt_t>( k ); }
  696. bool Remove( KeyArg_t k ) { return DoRemove<KeyArg_t>( k ); }
  697. bool Remove( KeyAlt_t k ) { return DoRemove<KeyAlt_t>( k ); }
  698. void RemoveAll() { m_table.RemoveAll(); m_data.RemoveAll(); }
  699. void Purge() { m_table.Purge(); m_data.Purge(); }
  700. int Count() const { return m_table.Count(); }
  701. typedef typename KVPair::ValueReturn_t Element_t;
  702. KeyT const &Key( UtlHashHandle_t idx ) const { return m_data[idx].m_key; }
  703. Element_t const &Element( UtlHashHandle_t idx ) const { return m_data[idx].GetValue(); }
  704. Element_t &Element( UtlHashHandle_t idx ) { return m_data[idx].GetValue(); }
  705. Element_t const &operator[]( UtlHashHandle_t idx ) const { return m_data[idx].GetValue(); }
  706. Element_t &operator[]( UtlHashHandle_t idx ) { return m_data[idx].GetValue(); }
  707. void ReplaceKey( UtlHashHandle_t idx, KeyArg_t k ) { Assert( GetEqualRef()( m_data[idx].m_key, k ) && GetHashRef()( k ) == GetHashRef()( m_data[idx].m_key ) ); m_data[idx].m_key = k; }
  708. void ReplaceKey( UtlHashHandle_t idx, KeyAlt_t k ) { Assert( GetEqualRef()( m_data[idx].m_key, k ) && GetHashRef()( k ) == GetHashRef()( m_data[idx].m_key ) ); m_data[idx].m_key = k; }
  709. Element_t const &Get( KeyArg_t k, Element_t const &defaultValue ) const { UtlHashHandle_t h = Find( k ); if ( h != InvalidHandle() ) return Element( h ); return defaultValue; }
  710. Element_t const &Get( KeyAlt_t k, Element_t const &defaultValue ) const { UtlHashHandle_t h = Find( k ); if ( h != InvalidHandle() ) return Element( h ); return defaultValue; }
  711. Element_t const *GetPtr( KeyArg_t k ) const { UtlHashHandle_t h = Find(k); if ( h != InvalidHandle() ) return &Element( h ); return NULL; }
  712. Element_t const *GetPtr( KeyAlt_t k ) const { UtlHashHandle_t h = Find(k); if ( h != InvalidHandle() ) return &Element( h ); return NULL; }
  713. Element_t *GetPtr( KeyArg_t k ) { UtlHashHandle_t h = Find( k ); if ( h != InvalidHandle() ) return &Element( h ); return NULL; }
  714. Element_t *GetPtr( KeyAlt_t k ) { UtlHashHandle_t h = Find( k ); if ( h != InvalidHandle() ) return &Element( h ); return NULL; }
  715. UtlHashHandle_t FirstHandle() const { return ExtendInvalidHandle( m_data.Head() ); }
  716. UtlHashHandle_t NextHandle( UtlHashHandle_t h ) const { return ExtendInvalidHandle( m_data.Next( h ) ); }
  717. bool IsValidHandle( UtlHashHandle_t h ) const { return m_data.IsValidIndex( h ); }
  718. UtlHashHandle_t InvalidHandle() const { return (UtlHashHandle_t)-1; }
  719. UtlHashHandle_t RemoveAndAdvance( UtlHashHandle_t h )
  720. {
  721. Assert( m_data.IsValidIndex( h ) );
  722. m_table.Remove( IndirectIndex( h ) );
  723. IndexStorage_t next = m_data.Next( h );
  724. m_data.Remove( h );
  725. return ExtendInvalidHandle(next);
  726. }
  727. void Compact( bool bMinimal ) { m_table.Compact( bMinimal ); /*m_data.Compact();*/ }
  728. void Swap( CUtlStableHashtable &other )
  729. {
  730. m_table.Swap(other.m_table);
  731. // XXX swapping CUtlLinkedList by block memory swap, ugh
  732. char buf[ sizeof(m_data) ];
  733. memcpy( buf, &m_data, sizeof(m_data) );
  734. memcpy( &m_data, &other.m_data, sizeof(m_data) );
  735. memcpy( &other.m_data, buf, sizeof(m_data) );
  736. }
  737. protected:
  738. // Perform extension of 0xFFFF to 0xFFFFFFFF if necessary. Note: ( a < CONSTANT ) ? 0 : -1 is usually branchless
  739. static UtlHashHandle_t ExtendInvalidHandle( uint32 x ) { return x; }
  740. static UtlHashHandle_t ExtendInvalidHandle( uint16 x ) { uint32 a = x; return a | ( ( a < 0xFFFFu ) ? 0 : -1 ); }
  741. struct IndirectIndex
  742. {
  743. explicit IndirectIndex(IndexStorage_t i) : m_index(i) { }
  744. IndexStorage_t m_index;
  745. };
  746. struct HashProxy
  747. {
  748. KeyHashT m_hash;
  749. unsigned int operator()( IndirectIndex idx ) const
  750. {
  751. const ptrdiff_t tableoffset = (uintptr_t)(&((Hashtable_t*)1024)->GetHashRef()) - 1024;
  752. const ptrdiff_t owneroffset = offsetof(CUtlStableHashtable, m_table) + tableoffset;
  753. CUtlStableHashtable* pOwner = (CUtlStableHashtable*)((uintptr_t)this - owneroffset);
  754. return m_hash( pOwner->m_data[ idx.m_index ].m_key );
  755. }
  756. unsigned int operator()( KeyArg_t k ) const { return m_hash( k ); }
  757. unsigned int operator()( KeyAlt_t k ) const { return m_hash( k ); }
  758. };
  759. struct EqualProxy
  760. {
  761. KeyIsEqualT m_eq;
  762. unsigned int operator()( IndirectIndex lhs, IndirectIndex rhs ) const
  763. {
  764. return lhs.m_index == rhs.m_index;
  765. }
  766. unsigned int operator()( IndirectIndex lhs, KeyArg_t rhs ) const
  767. {
  768. const ptrdiff_t tableoffset = (uintptr_t)(&((Hashtable_t*)1024)->GetEqualRef()) - 1024;
  769. const ptrdiff_t owneroffset = offsetof(CUtlStableHashtable, m_table) + tableoffset;
  770. CUtlStableHashtable* pOwner = (CUtlStableHashtable*)((uintptr_t)this - owneroffset);
  771. return m_eq( pOwner->m_data[ lhs.m_index ].m_key, rhs );
  772. }
  773. unsigned int operator()( IndirectIndex lhs, KeyAlt_t rhs ) const
  774. {
  775. const ptrdiff_t tableoffset = (uintptr_t)(&((Hashtable_t*)1024)->GetEqualRef()) - 1024;
  776. const ptrdiff_t owneroffset = offsetof(CUtlStableHashtable, m_table) + tableoffset;
  777. CUtlStableHashtable* pOwner = (CUtlStableHashtable*)((uintptr_t)this - owneroffset);
  778. return m_eq( pOwner->m_data[ lhs.m_index ].m_key, rhs );
  779. }
  780. };
  781. class CCustomLinkedList : public LinkedList_t
  782. {
  783. public:
  784. int AddToTailUnconstructed()
  785. {
  786. IndexStorage_t newNode = this->AllocInternal();
  787. if ( newNode != this->InvalidIndex() )
  788. this->LinkToTail( newNode );
  789. return newNode;
  790. }
  791. };
  792. Hashtable_t m_table;
  793. CCustomLinkedList m_data;
  794. };
  795. template <typename K, typename V, typename H, typename E, typename S, typename A>
  796. template <typename KeyArgumentT>
  797. inline bool CUtlStableHashtable<K,V,H,E,S,A>::DoRemove( KeyArgumentT k )
  798. {
  799. unsigned int hash = m_table.GetHashRef()( k );
  800. UtlHashHandle_t h = m_table.template DoLookup<KeyArgumentT>( k, hash, NULL );
  801. if ( h == m_table.InvalidHandle() )
  802. return false;
  803. int idx = m_table[ h ].m_index;
  804. m_table.template DoRemove<IndirectIndex>( IndirectIndex( idx ), hash );
  805. m_data.Remove( idx );
  806. return true;
  807. }
  808. template <typename K, typename V, typename H, typename E, typename S, typename A>
  809. template <typename KeyArgumentT>
  810. inline UtlHashHandle_t CUtlStableHashtable<K,V,H,E,S,A>::DoFind( KeyArgumentT k ) const
  811. {
  812. unsigned int hash = m_table.GetHashRef()( k );
  813. UtlHashHandle_t h = m_table.template DoLookup<KeyArgumentT>( k, hash, NULL );
  814. if ( h != m_table.InvalidHandle() )
  815. return m_table[ h ].m_index;
  816. return (UtlHashHandle_t) -1;
  817. }
  818. template <typename K, typename V, typename H, typename E, typename S, typename A>
  819. template <typename KeyArgumentT>
  820. inline UtlHashHandle_t CUtlStableHashtable<K,V,H,E,S,A>::DoInsert( KeyArgumentT k )
  821. {
  822. unsigned int hash = m_table.GetHashRef()( k );
  823. UtlHashHandle_t h = m_table.template DoLookup<KeyArgumentT>( k, hash, NULL );
  824. if ( h != m_table.InvalidHandle() )
  825. return m_table[ h ].m_index;
  826. int idx = m_data.AddToTailUnconstructed();
  827. ConstructOneArg( &m_data[idx], k );
  828. m_table.template DoInsertNoCheck<IndirectIndex>( IndirectIndex( idx ), empty_t(), hash );
  829. return idx;
  830. }
  831. template <typename K, typename V, typename H, typename E, typename S, typename A>
  832. template <typename KeyArgumentT, typename ValueArgumentT>
  833. inline UtlHashHandle_t CUtlStableHashtable<K,V,H,E,S,A>::DoInsert( KeyArgumentT k, ValueArgumentT v )
  834. {
  835. unsigned int hash = m_table.GetHashRef()( k );
  836. UtlHashHandle_t h = m_table.template DoLookup<KeyArgumentT>( k, hash, NULL );
  837. if ( h != m_table.InvalidHandle() )
  838. return m_table[ h ].m_index;
  839. int idx = m_data.AddToTailUnconstructed();
  840. ConstructTwoArg( &m_data[idx], k, v );
  841. m_table.template DoInsertNoCheck<IndirectIndex>( IndirectIndex( idx ), empty_t(), hash );
  842. return idx;
  843. }
  844. #endif // UTLHASHTABLE_H