1 //========= Copyright Valve Corporation, All rights reserved. =================//
2 //
3 // Purpose: index-based hash map container
4 // Use FOR_EACH_HASHMAP to iterate through CUtlHashMap.
5 //
6 //=============================================================================//
7
8 #ifndef UTLHASHMAP_H
9 #define UTLHASHMAP_H
10 #pragma once
11
12 #include <tier0/dbg.h>
13 #include "utlvector.h"
14
15 #define FOR_EACH_HASHMAP( mapName, iteratorName ) \
16 for ( int iteratorName = 0; iteratorName < (mapName).MaxElement(); ++iteratorName ) if ( !(mapName).IsValidIndex( iteratorName ) ) continue; else
17
18 //-----------------------------------------------------------------------------
19 //
20 // Purpose: An associative container. Similar to std::unordered_map,
21 // but without STL's rather wacky interface. Also, each item is not a separate
22 // allocation, so insertion of items can cause existing items to move in memory.
23 //
24 // This differs from the one in Steam by not having any default hash or equality
25 // class. We will use std::hash and std::equal_to insetad of our own hand-rolled
26 // versions, which I suspect do not add any value (any more at least). Valve's
27 // CDefEquals unfortunately is not exactly the same as std::equal_to in the way
28 // it handles pointers, so let's require the few uses of hashmaps in use
29 // to be explicit in the equality operation.
30 //
31 //-----------------------------------------------------------------------------
32 template <typename K, typename T, typename L, typename H >
33 class CUtlHashMap
34 {
35 protected:
36 enum ReplaceExisting
37 {
38 False = 0,
39 True = 1,
40 };
41
42 public:
43 using KeyType_t= K;
44 using ElemType_t = T;
45 using IndexType_t = int;
46 using EqualityFunc_t = L;
47 using HashFunc_t = H;
48 static constexpr IndexType_t kInvalidIndex = -1;
49
CUtlHashMap()50 CUtlHashMap()
51 {
52 m_cElements = 0;
53 m_nMaxElement = 0;
54 m_nNeedRehashStart = 0;
55 m_nNeedRehashEnd = 0;
56 m_nMinBucketMask = 1;
57 m_iNodeFreeListHead = kInvalidIndex;
58 }
59
CUtlHashMap(int cElementsExpected)60 CUtlHashMap( int cElementsExpected )
61 {
62 m_cElements = 0;
63 m_nMaxElement = 0;
64 m_nNeedRehashStart = 0;
65 m_nNeedRehashEnd = 0;
66 m_nMinBucketMask = 1;
67 m_iNodeFreeListHead = kInvalidIndex;
68 EnsureCapacity( cElementsExpected );
69 }
70
~CUtlHashMap()71 ~CUtlHashMap()
72 {
73 Purge();
74 }
75
CopyFullHashMap(CUtlHashMap<K,T,L,H> & target)76 void CopyFullHashMap( CUtlHashMap< K, T, L, H > &target ) const
77 {
78 target.RemoveAll();
79 FOR_EACH_HASHMAP( *this, i )
80 {
81 target.Insert( this->Key( i ), this->Element( i ) );
82 }
83 }
84
85 // gets particular elements
Element(IndexType_t i)86 ElemType_t & Element( IndexType_t i ) { return m_memNodes.Element( i ).m_elem; }
Element(IndexType_t i)87 const ElemType_t & Element( IndexType_t i ) const { return m_memNodes.Element( i ).m_elem; }
88 ElemType_t & operator[]( IndexType_t i ) { return m_memNodes.Element( i ).m_elem; }
89 const ElemType_t & operator[]( IndexType_t i ) const { return m_memNodes.Element( i ).m_elem; }
Key(IndexType_t i)90 const KeyType_t & Key( IndexType_t i ) const { return m_memNodes.Element( i ).m_key; }
91
92 // Num elements
Count()93 IndexType_t Count() const { return m_cElements; }
94
95 // Max "size" of the vector
MaxElement()96 IndexType_t MaxElement() const { return m_nMaxElement; }
97
98 /// Checks if a node is valid and in the map.
99 /// NOTE: Do not use this function on the result of Find(). That is overkill
100 /// and slower. Instead, compare the returned index against InvalidIndex().
101 /// (Or better use, use one of the methods sue as FindGetPtr() or HasElement()
102 /// that makes it unnecessary to deal with indices at all.
IsValidIndex(IndexType_t i)103 bool IsValidIndex( IndexType_t i ) const { return /* i >= 0 && i < m_nMaxElement */ (unsigned)i < (unsigned)m_nMaxElement && m_memNodes[i].m_iNextNode >= -1; }
104
105 // Invalid index
InvalidIndex()106 static constexpr IndexType_t InvalidIndex() { return -1; }
107
108 // Insert method
Insert(const KeyType_t & key)109 IndexType_t Insert( const KeyType_t &key ) { return FindOrInsert_Internal( key, ReplaceExisting::True ); }
Insert(KeyType_t && key)110 IndexType_t Insert( KeyType_t &&key ) { return FindOrInsert_Internal( std::move(key), ReplaceExisting::True ); }
111
112 // Insert or replace the existing if found (no dupes)
Insert(const KeyType_t & key,const ElemType_t & insert)113 IndexType_t Insert( const KeyType_t &key, const ElemType_t &insert ) { return FindOrInsert_Internal( key, insert, ReplaceExisting::True ); }
Insert(const KeyType_t & key,ElemType_t && insert)114 IndexType_t Insert( const KeyType_t &key, ElemType_t &&insert ) { return FindOrInsert_Internal( key, std::move(insert), ReplaceExisting::True ); }
Insert(KeyType_t && key,const ElemType_t & insert)115 IndexType_t Insert( KeyType_t &&key, const ElemType_t &insert ) { return FindOrInsert_Internal( std::move(key), insert, ReplaceExisting::True ); }
Insert(KeyType_t && key,ElemType_t && insert)116 IndexType_t Insert( KeyType_t &&key, ElemType_t &&insert ) { return FindOrInsert_Internal( std::move(key), std::move(insert), ReplaceExisting::True ); }
117
118 // Insert or replace the existing if found (no dupes)
InsertOrReplace(const KeyType_t & key,const ElemType_t & insert)119 IndexType_t InsertOrReplace( const KeyType_t &key, const ElemType_t &insert ) { return FindOrInsert_Internal( key, insert, ReplaceExisting::True ); }
InsertOrReplace(const KeyType_t & key,ElemType_t && insert)120 IndexType_t InsertOrReplace( const KeyType_t &key, ElemType_t &&insert ) { return FindOrInsert_Internal( key, std::move(insert), ReplaceExisting::True ); }
InsertOrReplace(KeyType_t && key,const ElemType_t & insert)121 IndexType_t InsertOrReplace( KeyType_t &&key, const ElemType_t &insert ) { return FindOrInsert_Internal( std::move(key), insert, ReplaceExisting::True ); }
InsertOrReplace(KeyType_t && key,ElemType_t && insert)122 IndexType_t InsertOrReplace( KeyType_t &&key, ElemType_t &&insert ) { return FindOrInsert_Internal( std::move(key), std::move(insert), ReplaceExisting::True ); }
123
124 // Insert ALWAYS, possibly creating a dupe
InsertWithDupes(const KeyType_t & key,const ElemType_t & insert)125 IndexType_t InsertWithDupes( const KeyType_t &key, const ElemType_t &insert ) { return InsertWithDupes_Internal( key, insert ); }
InsertWithDupes(const KeyType_t & key,ElemType_t && insert)126 IndexType_t InsertWithDupes( const KeyType_t &key, ElemType_t &&insert ) { return InsertWithDupes_Internal( key, std::move(insert) ); }
InsertWithDupes(KeyType_t && key,const ElemType_t & insert)127 IndexType_t InsertWithDupes( KeyType_t &&key, const ElemType_t &insert ) { return InsertWithDupes_Internal( std::move(key), insert ); }
InsertWithDupes(KeyType_t && key,ElemType_t && insert)128 IndexType_t InsertWithDupes( KeyType_t &&key, ElemType_t &&insert ) { return InsertWithDupes_Internal( std::move(key), std::move(insert) ); }
129
130 // Find-or-insert method, one-arg - can insert default-constructed element
131 // when there is no available copy constructor or assignment operator
FindOrInsert(const KeyType_t & key)132 IndexType_t FindOrInsert( const KeyType_t &key ) { return FindOrInsert_Internal( key, ReplaceExisting::False ); }
FindOrInsert(KeyType_t && key)133 IndexType_t FindOrInsert( KeyType_t &&key ) { return FindOrInsert_Internal( std::move(key), ReplaceExisting::False ); }
134
135 // Find-or-insert method, two-arg - can insert an element when there is no
136 // copy constructor for the type (but does require assignment operator)
FindOrInsert(const KeyType_t & key,const ElemType_t & insert)137 IndexType_t FindOrInsert( const KeyType_t &key, const ElemType_t &insert ) { return FindOrInsert_Internal( key, insert, ReplaceExisting::False ); }
FindOrInsert(const KeyType_t & key,ElemType_t && insert)138 IndexType_t FindOrInsert( const KeyType_t &key, ElemType_t &&insert ) { return FindOrInsert_Internal( key, std::move(insert), ReplaceExisting::False ); }
FindOrInsert(KeyType_t && key,const ElemType_t & insert)139 IndexType_t FindOrInsert( KeyType_t &&key, const ElemType_t &insert ) { return FindOrInsert_Internal( std::move(key), insert, ReplaceExisting::False ); }
FindOrInsert(KeyType_t && key,ElemType_t && insert)140 IndexType_t FindOrInsert( KeyType_t &&key, ElemType_t &&insert ) { return FindOrInsert_Internal( std::move(key), std::move(insert), ReplaceExisting::False ); }
141
142 // Find key, insert with default value if not found. Returns pointer to the
143 // element
FindOrInsertGetPtr(const KeyType_t & key)144 ElemType_t *FindOrInsertGetPtr( const KeyType_t &key )
145 {
146 IndexType_t i = FindOrInsert(key);
147 return &m_memNodes.Element( i ).m_elem;
148 }
FindOrInsertGetPtr(KeyType_t && key)149 ElemType_t *FindOrInsertGetPtr( KeyType_t &&key )
150 {
151 IndexType_t i = FindOrInsert( std::move( key ) );
152 return &m_memNodes.Element( i ).m_elem;
153 }
154
155 // Finds an element. Returns index of element, or InvalidIndex() if not found
156 IndexType_t Find( const KeyType_t &key ) const;
157
158 // Finds an element, returns pointer to element or NULL if not found
FindGetPtr(const KeyType_t & key)159 ElemType_t *FindGetPtr( const KeyType_t &key )
160 {
161 IndexType_t i = Find(key);
162 return i == kInvalidIndex ? nullptr : &m_memNodes.Element( i ).m_elem;
163 }
FindGetPtr(const KeyType_t & key)164 const ElemType_t *FindGetPtr( const KeyType_t &key ) const
165 {
166 IndexType_t i = Find(key);
167 return i == kInvalidIndex ? nullptr : &m_memNodes.Element( i ).m_elem;
168 }
169
170 /// Returns true if the specified *key* (not the "element"!!!) can be found
171 /// This name is definitely unfortunate, but remains because of compatibility
172 /// with other containers and also other Valve codebases.
HasElement(const KeyType_t & key)173 bool HasElement( const KeyType_t &key ) const { return Find( key ) != kInvalidIndex; }
174
175 // Finds an exact key/value match, even with duplicate keys. Requires operator== for ElemType_t.
176 IndexType_t FindExact( const KeyType_t &key, const ElemType_t &elem ) const;
177
178 // Find next element with same key
179 IndexType_t NextSameKey( IndexType_t i ) const;
180
181 void EnsureCapacity( int num );
182
183 //
184 // DANGER DANGER
185 // This doesn't really work if you pass a temporary to defaultValue!!!
186 //
FindElement(const KeyType_t & key,const ElemType_t & defaultValue)187 const ElemType_t &FindElement( const KeyType_t &key, const ElemType_t &defaultValue ) const
188 {
189 IndexType_t i = Find( key );
190 if ( i == kInvalidIndex )
191 return defaultValue;
192 return Element( i );
193 }
194
195 void RemoveAt( IndexType_t i );
Remove(const KeyType_t & key)196 bool Remove( const KeyType_t &key )
197 {
198 int iMap = Find( key );
199 if ( iMap != kInvalidIndex )
200 {
201 RemoveAt( iMap );
202 return true;
203 }
204 return false;
205 }
206 void RemoveAll();
207 void Purge();
208
209 // call delete on each element (as a pointer) and then purge
PurgeAndDeleteElements()210 void PurgeAndDeleteElements()
211 {
212 FOR_EACH_HASHMAP( *this, i )
213 delete this->Element(i);
214 Purge();
215 }
216
217 void Swap( CUtlHashMap< K, T, L, H > &that );
218
219 protected:
220 template < typename pf_key >
221 IndexType_t FindOrInsert_Internal( pf_key &&key, ReplaceExisting bReplace );
222
223 template < typename pf_key, typename pf_elem >
224 IndexType_t FindOrInsert_Internal( pf_key &&key, pf_elem &&insert, ReplaceExisting bReplace );
225
226 template < typename pf_key, typename pf_elem >
227 IndexType_t InsertWithDupes_Internal( pf_key &&key, pf_elem &&insert );
228
229 template < typename KeyType_universal_ref >
230 IndexType_t InsertUnconstructed( KeyType_universal_ref &&key, IndexType_t *pExistingIndex, bool bAllowDupes );
231
FreeNodeIDToIndex(IndexType_t i)232 inline IndexType_t FreeNodeIDToIndex( IndexType_t i ) const { return (0-i)-3; }
FreeNodeIndexToID(IndexType_t i)233 inline IndexType_t FreeNodeIndexToID( IndexType_t i ) const { return (-3)-i; }
234
235 int FindInBucket( int iBucket, const KeyType_t &key ) const;
236 int AllocNode();
237 void RehashNodesInBucket( int iBucket );
238 void LinkNodeIntoBucket( int iBucket, int iNewNode );
239 bool RemoveNodeFromBucket( int iBucket, int iNodeToRemove );
240 void IncrementalRehash();
241
242 struct HashBucket_t
243 {
244 IndexType_t m_iNode;
245 };
246 CUtlVector<HashBucket_t> m_vecHashBuckets;
247
248 struct Node_t
249 {
250 KeyType_t m_key;
251 ElemType_t m_elem;
252 int m_iNextNode;
253 };
254
255 CUtlMemory<Node_t> m_memNodes;
256 IndexType_t m_iNodeFreeListHead;
257
258 IndexType_t m_cElements;
259 IndexType_t m_nMaxElement;
260 IndexType_t m_nNeedRehashStart, m_nNeedRehashEnd; // Range of buckets that need to be rehashed
261 IndexType_t m_nMinBucketMask; // Mask at the time we last finished completed rehashing. So no need to check hash buckets based on mask smaller than this.
262 EqualityFunc_t m_EqualityFunc;
263 HashFunc_t m_HashFunc;
264
265 public:
266 //
267 // Range-based for loop iteration over the map. You can iterate
268 // over the keys, the values ("elements"), or both ("items").
269 // This naming style comes from Python.
270 //
271 // Examples:
272 //
273 // CUtlHashMap<uint64,CUtlString> map;
274 //
275 // Iterate over the keys. Your loop variable will receive
276 // const KeyType_t &. (You can use an ordinary KeyType_t
277 // variable for small KeyType_t.)
278 //
279 // for ( uint64 k: map.IterKeys() ) { ... }
280 //
281 // Iterate over the values ("elements"). Your loop variable will receive
282 // [const] ElemType_t &. (You can use an ordinary ElemType_t
283 // variable for small ElemType_t if you don't need to modify the element.
284 // for ( CUtlString &v: map.IterValues() )
285 //
286 // Iterate over the "items" (key/value pairs) in the map. Your
287 // loop variable will receive an an ItemRef or MutableItemRef. This is
288 // a small proxy object that is very fast to copy, so you
289 // will usually use plain "auto" (not auto&). (Like std::map iterators,
290 // using the actual type would be really messy and verbose, since it's a
291 // template type, hence using auto.)
292 //
293 // for ( auto item: map.IterItems() )
294 // {
295 // int i = item.Index();
296 // uint64 k = item.Key();
297 // CUtlString &v = item.Value();
298 // }
299
300 // A reference to a key/value pair in a map
301 class ItemRef
302 {
303 protected:
304 Node_t &m_node;
305 const int m_idx;
306 public:
ItemRef(const CUtlHashMap<K,T,L,H> & map,int idx)307 inline ItemRef( const CUtlHashMap< K, T, L, H > &map, int idx ) : m_node( const_cast< Node_t &>( map.m_memNodes[idx] ) ), m_idx(idx) {}
308 ItemRef( const ItemRef &x ) = default;
Index()309 inline int Index() const { return m_idx; }
Key()310 inline const KeyType_t &Key() const { return m_node.m_key; }
Element()311 inline const ElemType_t &Element() const { return m_node.m_elem; }
312 };
313 struct MutableItemRef : ItemRef
314 {
MutableItemRefMutableItemRef315 inline MutableItemRef( CUtlHashMap< K, T, L, H > &map, int idx ) : ItemRef( map, idx ) {}
316 MutableItemRef( const MutableItemRef &x ) = default;
317 using ItemRef::Element; // const reference
ElementMutableItemRef318 inline ElemType_t &Element() const { return this->m_node.m_elem; } // non-const reference
319 };
320
321 // Base class iterator
322 class Iterator
323 {
324 protected:
325 CUtlHashMap<K, T, L, H > &m_map;
326 int m_idx;
327 public:
Iterator(const CUtlHashMap<K,T,L,H> & map,int idx)328 inline Iterator( const CUtlHashMap< K, T, L, H > &map, int idx ) : m_map( const_cast< CUtlHashMap< K, T, L, H > &>( map ) ), m_idx(idx) {}
329 Iterator( const Iterator &x ) = default;
330 inline bool operator==( const Iterator &x ) const { return &m_map == &x.m_map && m_idx == x.m_idx; } // Comparing the map reference is probably not necessary in 99% of cases, but needed to be correct
331 inline bool operator!=( const Iterator &x ) const { return &m_map != &x.m_map || m_idx != x.m_idx; }
332 inline void operator++()
333 {
334 if ( m_idx == kInvalidIndex )
335 return;
336 do
337 {
338 ++m_idx;
339 if ( m_idx >= m_map.m_nMaxElement )
340 {
341 m_idx = kInvalidIndex;
342 break;
343 }
344 } while ( m_map.m_memNodes[m_idx].m_iNextNode < -1 );
345 }
346 };
347 struct MutableIterator : Iterator
348 {
349 inline MutableIterator( const MutableIterator &x ) = default;
MutableIteratorMutableIterator350 inline MutableIterator( CUtlHashMap< K, T, L, H > &map, int idx ) : Iterator( map, idx ) {}
351 };
352 struct KeyIterator : Iterator
353 {
354 using Iterator::Iterator;
355 inline const KeyType_t &operator*() { return this->m_map.m_memNodes[this->m_idx].m_key; }
356 };
357 struct ConstValueIterator : Iterator
358 {
359 using Iterator::Iterator;
360 inline const ElemType_t &operator*() { return this->m_map.m_memNodes[this->m_idx].m_elem; }
361 };
362 struct MutableValueIterator : MutableIterator
363 {
364 using MutableIterator::MutableIterator;
365 inline ElemType_t &operator*() { return this->m_map.m_memNodes[this->m_idx].m_elem; }
366 };
367 struct ConstItemIterator : Iterator
368 {
369 using Iterator::Iterator;
370 inline ItemRef operator*() { return ItemRef( this->m_map, this->m_idx ); }
371 };
372 struct MutableItemIterator : public MutableIterator
373 {
374 using MutableIterator::MutableIterator;
375 inline MutableItemRef operator*() { return MutableItemRef( this->m_map, this->m_idx ); }
376 };
377
378 // Internal type used by the IterXxx functions. You normally won't use
379 // this directly, because it will be consumed by the machinery of the
380 // range-based for loop.
381 template <typename TIterator>
382 class Range
383 {
384 CUtlHashMap< K, T, L, H > &m_map;
385 public:
Range(const CUtlHashMap<K,T,L,H> & map)386 Range( const CUtlHashMap< K, T, L, H > &map ) : m_map( const_cast< CUtlHashMap< K, T, L, H > &>( map ) ) {}
begin()387 TIterator begin() const
388 {
389 int idx;
390 if ( m_map.m_cElements <= 0 )
391 {
392 idx = kInvalidIndex;
393 }
394 else
395 {
396 idx = 0;
397 while ( m_map.m_memNodes[idx].m_iNextNode < -1 )
398 {
399 ++idx;
400 Assert( idx < m_map.m_nMaxElement ); // Or else how is m_map.m_cElements > 0?
401 }
402 }
403 return TIterator( m_map, idx );
404 }
end()405 TIterator end() const { return TIterator( m_map, kInvalidIndex ); }
406 };
407
408 /// Iterate over the keys. You will receive a reference to the key/
IterKeys()409 Range<KeyIterator> IterKeys() const { return Range<KeyIterator>(*this); }
410
411 /// Iterate over the values ("elements"). You will receive a reference to the value.
IterValues()412 Range<ConstValueIterator> IterValues() const { return Range<ConstValueIterator>(*this); }
IterValues()413 Range<MutableValueIterator> IterValues() { return Range<MutableValueIterator>(*this); }
414
415 /// Iterate over the "items" (key/value pairs). You will receive a small reference
416 /// object that is cheap to copy.
IterItems()417 Range<ConstItemIterator> IterItems() const { return Range<ConstItemIterator>(*this); }
IterItems()418 Range<MutableItemIterator> IterItems() { return Range<MutableItemIterator>(*this); }
419
420 };
421
422
423 //-----------------------------------------------------------------------------
424 // Purpose: inserts and constructs a key into the map.
425 // Element member is left unconstructed (to be copy constructed or default-constructed by a wrapper function)
426 // Supports both copy and move constructors for KeyType_t via universal refs and type deduction
427 //-----------------------------------------------------------------------------
428 template <typename K, typename T, typename L, typename H>
429 template <typename KeyType_universal_ref>
InsertUnconstructed(KeyType_universal_ref && key,int * piNodeExistingIfDupe,bool bAllowDupes)430 inline int CUtlHashMap<K,T,L,H>::InsertUnconstructed( KeyType_universal_ref &&key, int *piNodeExistingIfDupe, bool bAllowDupes )
431 {
432 // make sure we have room in the hash table
433 if ( m_cElements >= m_vecHashBuckets.Count() )
434 EnsureCapacity( MAX( 16, m_vecHashBuckets.Count() * 2 ) );
435 if ( m_cElements >= m_memNodes.Count() )
436 m_memNodes.Grow( m_memNodes.Count() * 2 );
437
438 // Do a bit of cleanup, if table is not already clean. If statement here
439 // avoids the function call in the (hopefully common!) case that the table
440 // is already clean
441 if ( m_nNeedRehashStart < m_nNeedRehashEnd )
442 IncrementalRehash();
443
444 // hash the item
445 int hash = (int)m_HashFunc( key );
446
447 // Make sure any buckets that might contain duplicates have been rehashed, so that we only need
448 // to check one bucket below. Also, we have the invariant that all duplicates (if they are allowed)
449 // are in the same bucket. This rehashing might not actually be necessary, because we might have
450 // already done it. But it's probably not worth keeping track of. 1.) The number of back probes
451 // in normal usage is at most 1. 2.) If hashing is reasonably effective, then the number of items
452 // in each bucket should be small.
453 int nBucketMaskMigrate = ( m_vecHashBuckets.Count() >> 1 ) - 1;
454 while ( nBucketMaskMigrate >= m_nMinBucketMask )
455 {
456 int iBucketMigrate = hash & nBucketMaskMigrate;
457 if ( iBucketMigrate < m_nNeedRehashStart )
458 break;
459 RehashNodesInBucket( iBucketMigrate );
460 nBucketMaskMigrate >>= 1;
461 }
462
463 int iBucket = hash & ( m_vecHashBuckets.Count()-1 );
464
465 // return existing node without insert, if duplicates are not permitted
466 if ( !bAllowDupes )
467 {
468 // look in the bucket to see if we have a conflict
469 IndexType_t iNode = FindInBucket( iBucket, key );
470 if ( iNode != kInvalidIndex )
471 {
472 if ( piNodeExistingIfDupe )
473 {
474 *piNodeExistingIfDupe = iNode;
475 }
476 return kInvalidIndex;
477 }
478 }
479
480 // make an item
481 int iNewNode = AllocNode();
482 m_memNodes[iNewNode].m_iNextNode = kInvalidIndex;
483 Construct( &m_memNodes[iNewNode].m_key, std::forward<KeyType_universal_ref>( key ) );
484 // Note: m_elem remains intentionally unconstructed here
485 // Note: key may have been moved depending on which constructor was called.
486
487 // link ourselves in
488 // ::OutputDebugStr( CFmtStr( "insert %d into bucket %d\n", key, iBucket ).Access() );
489 LinkNodeIntoBucket( iBucket, iNewNode );
490
491 // Initialized to placate the compiler's uninitialized value checking.
492 if ( piNodeExistingIfDupe )
493 {
494 *piNodeExistingIfDupe = kInvalidIndex;
495 }
496
497 // return the new node
498 return iNewNode;
499 }
500
501
502 //-----------------------------------------------------------------------------
503 // Purpose: inserts a default item into the map, no change if key already exists
504 //-----------------------------------------------------------------------------
505 template <typename K, typename T, typename L, typename H>
506 template < typename pf_key >
FindOrInsert_Internal(pf_key && key,ReplaceExisting bReplace)507 inline int CUtlHashMap<K,T,L,H>::FindOrInsert_Internal( pf_key &&key, ReplaceExisting bReplace )
508 {
509 int iNodeExisting;
510 int iNodeInserted = InsertUnconstructed( std::forward<pf_key>( key ), &iNodeExisting, false /*no duplicates allowed*/ );
511 // If replacing, stomp the existing one
512 if ( bReplace && iNodeExisting != kInvalidIndex )
513 {
514 Destruct( &m_memNodes[ iNodeExisting ].m_elem );
515 ValueInitializeConstruct( &m_memNodes[ iNodeExisting ].m_elem );
516 }
517 else if ( iNodeInserted != kInvalidIndex )
518 {
519 ValueInitializeConstruct( &m_memNodes[ iNodeInserted ].m_elem );
520 return iNodeInserted;
521 }
522 return iNodeExisting;
523 }
524
525
526 //-----------------------------------------------------------------------------
527 // Purpose: inserts an item into the map, no change if key already exists
528 //-----------------------------------------------------------------------------
529 template <typename K, typename T, typename L, typename H>
530 template < typename pf_key, typename pf_elem >
FindOrInsert_Internal(pf_key && key,pf_elem && elem,ReplaceExisting bReplace)531 inline int CUtlHashMap<K,T,L,H>::FindOrInsert_Internal( pf_key &&key, pf_elem &&elem, ReplaceExisting bReplace )
532 {
533 int iNodeExisting;
534 int iNodeInserted = InsertUnconstructed( std::forward<pf_key>( key ), &iNodeExisting, false /*no duplicates allowed*/ );
535 // If replacing, stomp the existing one
536 if ( bReplace && iNodeExisting != kInvalidIndex )
537 {
538 Destruct( &m_memNodes[ iNodeExisting ].m_elem );
539 Construct( &m_memNodes[ iNodeExisting ].m_elem, std::forward<pf_elem>( elem ) );
540 }
541 else if ( iNodeInserted != kInvalidIndex )
542 {
543 Construct( &m_memNodes[ iNodeInserted ].m_elem, std::forward<pf_elem>( elem ) );
544 return iNodeInserted;
545 }
546 return iNodeExisting;
547 }
548
549 //-----------------------------------------------------------------------------
550 // Purpose: inserts element no matter what, even if key already exists
551 //-----------------------------------------------------------------------------
552 template <typename K, typename T, typename L, typename H>
553 template < typename pf_key, typename pf_elem >
InsertWithDupes_Internal(pf_key && key,pf_elem && insert)554 inline int CUtlHashMap<K,T,L,H>::InsertWithDupes_Internal( pf_key &&key, pf_elem &&insert )
555 {
556 int iNodeInserted = InsertUnconstructed( std::forward<pf_key>( key ), NULL, true /*duplicates allowed!*/ ); // copies key
557 if ( iNodeInserted != kInvalidIndex )
558 {
559 Construct( &m_memNodes[ iNodeInserted ].m_elem, std::forward<pf_elem>( insert ) );
560 }
561 return iNodeInserted;
562 }
563
564
565 //-----------------------------------------------------------------------------
566 // Purpose: grows the map to fit the specified amount
567 //-----------------------------------------------------------------------------
568 template <typename K, typename T, typename L, typename H>
EnsureCapacity(int amount)569 inline void CUtlHashMap<K,T,L,H>::EnsureCapacity( int amount )
570 {
571 m_memNodes.EnsureCapacity( amount );
572 // ::OutputDebugStr( CFmtStr( "grown m_memNodes from %d to %d\n", m_cElements, m_memNodes.Count() ).Access() );
573
574 if ( amount <= m_vecHashBuckets.Count() )
575 return;
576 int cBucketsNeeded = MAX( 16, m_vecHashBuckets.Count() );
577 while ( cBucketsNeeded < amount )
578 cBucketsNeeded <<= 1;
579 DbgAssert( ( cBucketsNeeded & (cBucketsNeeded-1) ) == 0 ); // It's a power of 2
580
581 // ::OutputDebugStr( CFmtStr( "grown m_vecHashBuckets from %d to %d\n", m_vecHashBuckets.Count(), cBucketsNeeded ).Access() );
582
583 // grow the hash buckets
584 int grow = cBucketsNeeded - m_vecHashBuckets.Count();
585 int iFirst = m_vecHashBuckets.AddMultipleToTail( grow );
586 // clear all the new data to invalid bits
587 memset( &m_vecHashBuckets[iFirst], 0xFFFFFFFF, grow*sizeof(m_vecHashBuckets[iFirst]) );
588 DbgAssert( m_vecHashBuckets.Count() == cBucketsNeeded );
589
590 // Mark appropriate range for rehashing
591 if ( m_cElements > 0 )
592 {
593 // we'll have to rehash, all the buckets that existed before growth
594 m_nNeedRehashStart = 0;
595 m_nNeedRehashEnd = iFirst;
596 }
597 else
598 {
599 // no elements - no rehashing!
600 m_nNeedRehashStart = m_vecHashBuckets.Count();
601 m_nNeedRehashEnd = m_nNeedRehashStart;
602 m_nMinBucketMask = m_nNeedRehashStart-1;
603 }
604 }
605
606
607 //-----------------------------------------------------------------------------
608 // Purpose: gets a new node, from the free list if possible
609 //-----------------------------------------------------------------------------
610 template <typename K, typename T, typename L, typename H>
AllocNode()611 inline int CUtlHashMap<K,T,L,H>::AllocNode()
612 {
613 // if we're out of free elements, get the max
614 if ( m_cElements == m_nMaxElement )
615 {
616 m_cElements++;
617 return m_nMaxElement++;
618 }
619
620 // pull from the free list
621 DbgAssert( m_iNodeFreeListHead != kInvalidIndex );
622 int iNewNode = m_iNodeFreeListHead;
623 m_iNodeFreeListHead = FreeNodeIDToIndex( m_memNodes[iNewNode].m_iNextNode );
624 m_cElements++;
625 return iNewNode;
626 }
627
628
629 //-----------------------------------------------------------------------------
630 // Purpose: takes a bucket of nodes and re-hashes them into a more optimal bucket
631 //-----------------------------------------------------------------------------
632 template <typename K, typename T, typename L, typename H>
RehashNodesInBucket(int iBucketSrc)633 inline void CUtlHashMap<K,T,L,H>::RehashNodesInBucket( int iBucketSrc )
634 {
635
636 // walk the list of items, re-hashing them
637 IndexType_t *pLink = &m_vecHashBuckets[iBucketSrc].m_iNode;
638 for (;;)
639 {
640 IndexType_t iNode = *pLink;
641 if ( iNode == kInvalidIndex )
642 break;
643 Node_t &node = m_memNodes[iNode];
644 DbgAssert( node.m_iNextNode != iNode );
645
646 // work out where the node should go
647 int hash = (int)m_HashFunc( node.m_key );
648 int iBucketDest = hash & (m_vecHashBuckets.Count()-1);
649
650 // if the hash bucket has changed, move it
651 if ( iBucketDest != iBucketSrc )
652 {
653 // ::OutputDebugStr( CFmtStr( "moved key %d from bucket %d to %d\n", key, iBucketSrc, iBucketDest ).Access() );
654
655 // remove from this bucket list
656 *pLink = node.m_iNextNode;
657
658 // link into new bucket list
659 LinkNodeIntoBucket( iBucketDest, iNode );
660 }
661 else
662 {
663 pLink = &node.m_iNextNode;
664 }
665 }
666 }
667
668
669 //-----------------------------------------------------------------------------
670 // Purpose: searches for an item by key, returning the index handle
671 //-----------------------------------------------------------------------------
672 template <typename K, typename T, typename L, typename H>
Find(const KeyType_t & key)673 inline int CUtlHashMap<K,T,L,H>::Find( const KeyType_t &key ) const
674 {
675 if ( m_cElements == 0 )
676 return kInvalidIndex;
677
678 // Rehash incrementally. Note that this is really a "const"
679 // function, since it is only shuffling around the buckets. The
680 // items do not move in memory, and their index does not change.
681 // So this can be called during iteration, etc. The buckets are
682 // invisible to the app code.
683 //
684 // It's a better tradeoff to make this function slightly slower
685 // until we get rehashed, to make sure that we do eventually get
686 // rehashed, even if we have stopped Inserting. This minimizes
687 // the number of back probes that need to be made.
688 //
689 // NOTE: This means that you cannot call the "read-only" Find()
690 // function from different threads at the same time!
691 if ( m_nNeedRehashStart < m_nNeedRehashEnd )
692 (const_cast<CUtlHashMap<K,T,L,H> *>( this ))->IncrementalRehash();
693
694 // hash the item
695 int hash = (int)m_HashFunc( key );
696
697 // find the bucket
698 int cBucketsMask = m_vecHashBuckets.Count()-1;
699 int iBucket = hash & cBucketsMask;
700 do
701 {
702
703 // Look in the bucket for the item
704 int iNode = FindInBucket( iBucket, key );
705 if ( iNode != kInvalidIndex )
706 return iNode;
707
708 // Not found. Might be in an older bucket.
709 cBucketsMask >>= 1;
710 if ( cBucketsMask < m_nMinBucketMask )
711 break;
712 iBucket = hash & cBucketsMask;
713 } while ( iBucket >= m_nNeedRehashStart );
714
715 return kInvalidIndex;
716 }
717
718
719 //-----------------------------------------------------------------------------
720 // Purpose: searches for an item by key and element equality, returning the index handle
721 //-----------------------------------------------------------------------------
722 template <typename K, typename T, typename L, typename H>
FindExact(const KeyType_t & key,const ElemType_t & elem)723 inline int CUtlHashMap<K, T, L, H>::FindExact( const KeyType_t &key, const ElemType_t &elem ) const
724 {
725 int iNode = Find( key );
726 while ( iNode != kInvalidIndex )
727 {
728 if ( elem == m_memNodes[iNode].m_elem )
729 return iNode;
730 iNode = NextSameKey( iNode );
731 }
732 return kInvalidIndex;
733 }
734
735
736 //-----------------------------------------------------------------------------
737 // Purpose: find the next element with the same key, if insertwithdupes was used
738 //-----------------------------------------------------------------------------
739 template <typename K, typename T, typename L, typename H>
NextSameKey(IndexType_t i)740 inline int CUtlHashMap<K, T, L, H>::NextSameKey( IndexType_t i ) const
741 {
742 if ( m_memNodes.IsIdxValid( i ) )
743 {
744 const KeyType_t &key = m_memNodes[i].m_key;
745 IndexType_t iNode = m_memNodes[i].m_iNextNode;
746 while ( iNode != kInvalidIndex )
747 {
748 DbgAssert( iNode < m_nMaxElement );
749
750 // equality check
751 if ( m_EqualityFunc( key, m_memNodes[iNode].m_key ) )
752 return iNode;
753
754 iNode = m_memNodes[iNode].m_iNextNode;
755 }
756 }
757 return kInvalidIndex;
758 }
759
760
761 //-----------------------------------------------------------------------------
762 // Purpose: searches for an item by key, returning the index handle
763 //-----------------------------------------------------------------------------
764 template <typename K, typename T, typename L, typename H>
FindInBucket(int iBucket,const KeyType_t & key)765 inline int CUtlHashMap<K,T,L,H>::FindInBucket( int iBucket, const KeyType_t &key ) const
766 {
767 IndexType_t iNode = m_vecHashBuckets[iBucket].m_iNode;
768 while ( iNode != kInvalidIndex )
769 {
770 DbgAssert( iNode < m_nMaxElement );
771
772 // equality check
773 const Node_t &node = m_memNodes[iNode];
774 if ( m_EqualityFunc( key, node.m_key ) )
775 return iNode;
776
777 iNode = node.m_iNextNode;
778 }
779
780 return kInvalidIndex;
781 }
782
783
784 //-----------------------------------------------------------------------------
785 // Purpose: links a node into a bucket
786 //-----------------------------------------------------------------------------
787 template <typename K, typename T, typename L, typename H>
LinkNodeIntoBucket(int iBucket,int iNewNode)788 inline void CUtlHashMap<K,T,L,H>::LinkNodeIntoBucket( int iBucket, int iNewNode )
789 {
790 // add into the start of the bucket's list
791 m_memNodes[iNewNode].m_iNextNode = m_vecHashBuckets[iBucket].m_iNode;
792 m_vecHashBuckets[iBucket].m_iNode = iNewNode;
793 }
794
795
796 //-----------------------------------------------------------------------------
797 // Purpose: removes a single item from the map
798 //-----------------------------------------------------------------------------
799 template <typename K, typename T, typename L, typename H>
RemoveAt(IndexType_t i)800 inline void CUtlHashMap<K,T,L,H>::RemoveAt( IndexType_t i )
801 {
802 if ( !IsValidIndex( i ) )
803 {
804 Assert( false );
805 return;
806 }
807
808 // Rehash incrementally
809 if ( m_nNeedRehashStart < m_nNeedRehashEnd )
810 IncrementalRehash();
811
812 // unfortunately, we have to re-hash to find which bucket we're in
813 int hash = (int)m_HashFunc( m_memNodes[i].m_key );
814 int nBucketMask = m_vecHashBuckets.Count()-1;
815 if ( RemoveNodeFromBucket( hash & nBucketMask, i ) )
816 return;
817
818 // wasn't found; look in older buckets
819 for (;;)
820 {
821 nBucketMask >>= 1;
822 if ( nBucketMask < m_nMinBucketMask )
823 break;
824 int iBucket = hash & nBucketMask;
825 if ( iBucket < m_nNeedRehashStart )
826 break;
827 if ( RemoveNodeFromBucket( iBucket, i ) )
828 return;
829 }
830
831 // never found, container is busted
832 Assert( false );
833 }
834
835
836 //-----------------------------------------------------------------------------
837 // Purpose: removes a node from the bucket, return true if it was found
838 //-----------------------------------------------------------------------------
839 template <typename K, typename T, typename L, typename H>
RemoveNodeFromBucket(IndexType_t iBucket,int iNodeToRemove)840 inline bool CUtlHashMap<K,T,L,H>::RemoveNodeFromBucket( IndexType_t iBucket, int iNodeToRemove )
841 {
842 // walk the list of items
843 IndexType_t *pLink = &m_vecHashBuckets[iBucket].m_iNode;
844 for (;;)
845 {
846 IndexType_t iNode = *pLink;
847 if ( iNode == kInvalidIndex )
848 break;
849 Node_t &node = m_memNodes[iNode];
850 DbgAssert( node.m_iNextNode != iNode );
851
852 if ( iNodeToRemove == iNode )
853 {
854 // found it, remove
855 *pLink = node.m_iNextNode;
856 Destruct( &node.m_key );
857 Destruct( &node.m_elem );
858
859 // link into free list
860 node.m_iNextNode = FreeNodeIndexToID( m_iNodeFreeListHead );
861 m_iNodeFreeListHead = iNode;
862 m_cElements--;
863 if ( m_cElements == 0 )
864 {
865 // No items left in container, so no rehashing necessary
866 m_nNeedRehashStart = m_vecHashBuckets.Count();
867 m_nNeedRehashEnd = m_nNeedRehashStart;
868 m_nMinBucketMask = m_vecHashBuckets.Count()-1;
869 }
870 return true;
871 }
872
873 pLink = &node.m_iNextNode;
874 }
875
876 return false;
877 }
878
879
880 //-----------------------------------------------------------------------------
881 // Purpose: removes all items from the hash map
882 //-----------------------------------------------------------------------------
883 template <typename K, typename T, typename L, typename H>
RemoveAll()884 inline void CUtlHashMap<K,T,L,H>::RemoveAll()
885 {
886 if ( m_cElements > 0 )
887 {
888 FOR_EACH_HASHMAP( *this, i )
889 {
890 Node_t &node = m_memNodes[i];
891 Destruct( &node.m_key );
892 Destruct( &node.m_elem );
893 }
894
895 m_cElements = 0;
896 m_nMaxElement = 0;
897 m_iNodeFreeListHead = kInvalidIndex;
898 m_nNeedRehashStart = m_vecHashBuckets.Count();
899 m_nNeedRehashEnd = m_nNeedRehashStart;
900 DbgAssert( m_vecHashBuckets.Count() >= 2 );
901 m_nMinBucketMask = m_vecHashBuckets.Count()-1;
902 memset( m_vecHashBuckets.Base(), 0xFF, m_vecHashBuckets.Count() * sizeof(HashBucket_t) );
903 }
904 }
905
906
907 //-----------------------------------------------------------------------------
908 // Purpose: removes all items from the hash map and frees all memory
909 //-----------------------------------------------------------------------------
910 template <typename K, typename T, typename L, typename H>
Purge()911 inline void CUtlHashMap<K,T,L,H>::Purge()
912 {
913 if ( m_cElements > 0 )
914 {
915 FOR_EACH_HASHMAP( *this, i )
916 {
917 Node_t &node = m_memNodes[i];
918 Destruct( &node.m_key );
919 Destruct( &node.m_elem );
920 }
921 }
922
923 m_cElements = 0;
924 m_nMaxElement = 0;
925 m_iNodeFreeListHead = kInvalidIndex;
926 m_nNeedRehashStart = 0;
927 m_nNeedRehashEnd = 0;
928 m_nMinBucketMask = 1;
929 m_vecHashBuckets.Purge();
930 m_memNodes.Purge();
931 }
932
933
934 //-----------------------------------------------------------------------------
935 // Purpose: rehashes buckets
936 //-----------------------------------------------------------------------------
937 template <typename K, typename T, typename L, typename H>
IncrementalRehash()938 inline void CUtlHashMap<K,T,L,H>::IncrementalRehash()
939 {
940 // Each call site should check this, to avoid the function call in the
941 // common case where the table is already clean.
942 DbgAssert( m_nNeedRehashStart < m_nNeedRehashEnd );
943
944 do
945 {
946 int iBucketSrc = m_nNeedRehashStart;
947 ++m_nNeedRehashStart;
948
949 // Bucket empty?
950 if ( m_vecHashBuckets[iBucketSrc].m_iNode != kInvalidIndex )
951 {
952 RehashNodesInBucket( iBucketSrc );
953
954 // only actively do one - don't want to do it too fast since we may be on a rapid growth path
955 if ( m_nNeedRehashStart < m_nNeedRehashEnd )
956 return;
957 break;
958 }
959 } while ( m_nNeedRehashStart < m_nNeedRehashEnd );
960
961 // We're done; don't need any bits anymore
962 DbgAssert( m_vecHashBuckets.Count() >= 2 );
963 m_nNeedRehashStart = m_vecHashBuckets.Count();
964 m_nNeedRehashEnd = m_nNeedRehashStart;
965 m_nMinBucketMask = m_vecHashBuckets.Count()-1;
966 }
967
968
969 //-----------------------------------------------------------------------------
970 // Purpose: swaps with another hash map
971 //-----------------------------------------------------------------------------
972 template <typename K, typename T, typename L, typename H>
Swap(CUtlHashMap<K,T,L,H> & that)973 inline void CUtlHashMap<K,T,L,H>::Swap( CUtlHashMap<K,T,L,H> &that )
974 {
975 m_vecHashBuckets.Swap( that.m_vecHashBuckets );
976 m_memNodes.Swap( that.m_memNodes );
977 SWAP( m_iNodeFreeListHead, that.m_iNodeFreeListHead );
978 SWAP( m_cElements, that.m_cElements );
979 SWAP( m_nMaxElement, that.m_nMaxElement );
980 SWAP( m_nNeedRehashStart, that.m_nNeedRehashStart );
981 SWAP( m_nNeedRehashEnd, that.m_nNeedRehashEnd );
982 SWAP( m_nMinBucketMask, that.m_nMinBucketMask );
983 }
984
985 #endif // UTLHASHMAP_H
986