//---------------------------------------------------------------------------// // Copyright (C) Microsoft Corporation. All rights reserved. // //===========================================================================// // * // * File: Hash.h // * // * Contents: Hash table containers // * // *****************************************************************************/ #pragma once #ifndef __HASH_H__ #define __HASH_H__ #pragma warning( disable : 4244 ) // // Node structures // #pragma pack( push, 4 ) struct CMTListNode { CMTListNode* m_Next; // next item in list CMTListNode* m_Prev; // previous item in list void* m_Data; // user's blob long m_DeletedAndIdx; // node's index & high bit is lazy delete flag void * operator new(size_t size); void * operator new[](size_t size); void operator delete( void* ptr ); void operator delete[] ( void* ptr ); }; #ifndef ASSERT #define ASSERT(x) #endif #define DELETED_MASK 0x80000000 #define IDX_MASK 0x7fffffff #define MARK_NODE_DELETED( pNode ) ( pNode->m_DeletedAndIdx |= DELETED_MASK ) #define CLEAR_NODE_DELETED( pNode ) ( pNode->m_DeletedAndIdx &= IDX_MASK ) #define IS_NODE_DELETED( pNode ) ( pNode->m_DeletedAndIdx & DELETED_MASK ) #define GET_NODE_IDX( pNode ) ( pNode->m_DeletedAndIdx & IDX_MASK ) #define SET_NODE_IDX( pNode, idx ) ( pNode->m_DeletedAndIdx = (pNode->m_DeletedAndIdx & DELETED_MASK) | idx ) #pragma pack( pop ) // // Handle typedefs // typedef CMTListNode* MTListNodeHandle; /////////////////////////////////////////////////////////////////////////////// // Basic hash table (NOT thread safe) /////////////////////////////////////////////////////////////////////////////// template class CHash { public: // // Callback typedefs // typedef DWORD ( *PFHASHFUNC)( K ); typedef bool ( *PFCOMPAREFUNC)( T*, K ); typedef bool ( *PFITERCALLBACK)( T*, MTListNodeHandle, void*); typedef void ( *PFDELFUNC)( T*, void* ); typedef void ( *PFGETFUNC)( T* ); // // Constructor and destructor // CHash( PFHASHFUNC HashFunc, PFCOMPAREFUNC CompareFunc, PFGETFUNC GetFunc = NULL, WORD NumBuckets = 256, WORD NumLocks = 16 ); ~CHash(); // // Adds object into hash table using the specified key. // It does not check for duplicate keys. // MTListNodeHandle Add( K Key, T* Object ); // // Returns the number of objects in the hash table // long Count() { return m_NumObjects; } // // Returns first object it finds associated with the specified key. // T* Get( K Key ); // // Removes and returns first object it finds associated with the specified key. // T* Delete( K Key ); // // Removes specified node from table. // void DeleteNode( MTListNodeHandle node, PFDELFUNC pfDelete = NULL, void* Cookie = NULL ); // // Marks node as deleted without locking the table. It can be // called from within the iterator callback (see ForEach). // void MarkNodeDeleted( MTListNodeHandle node, PFDELFUNC pfDelete = NULL, void* Cookie = NULL ); // // Callback iterator. Returns false if the iterator was prematurely // ended by the callback function, otherwise true. // // Callback: // Form: // bool callback_function( T* pObject, MTListNodeHandle hNode, void* Cookie ) // Behavior: // If the callback returns false, the iterator immediately stops. // If the callback returns true, the iterator continues on to the next node. // Restrictions: // (1) Using any CMTHash function other than MarkNodeDeleted may result in a deadlock. // bool ForEach( PFITERCALLBACK pfCallback, void* Cookie ); // // Removed all nodes from hash table. // void RemoveAll( PFDELFUNC pfDelete = NULL, void* Cookie = NULL ); // // Removes nodes marked as deleted // void TrashDay(); void * operator new(size_t size); void operator delete( void* ptr ); void operator delete[] ( void* ptr ); protected: typedef DWORD ( *PFHASHFUNC)( K ); typedef bool ( *PFCOMPAREFUNC)( T*, K ); typedef bool ( *PFITERCALLBACK)( T*, MTListNodeHandle, void*); typedef void ( *PFDELFUNC)( T*, void* ); WORD m_NumBuckets; WORD m_BucketMask; CMTListNode* m_Buckets; PFCOMPAREFUNC m_CompareFunc; PFHASHFUNC m_HashFunc; PFGETFUNC m_GetFunc; CMTListNode m_PreAllocatedBuckets[1]; long m_NumObjects; long m_Recursion; }; /////////////////////////////////////////////////////////////////////////////// // Inline implementation of CHash /////////////////////////////////////////////////////////////////////////////// template inline CHash::CHash( PFHASHFUNC HashFunc, PFCOMPAREFUNC CompareFunc, PFGETFUNC GetFunc, WORD NumBuckets, WORD NumLocks ) { CMTListNode* pBucket; WORD i; // // Set callback functions // m_HashFunc = HashFunc; m_CompareFunc = CompareFunc; m_GetFunc = GetFunc; ASSERT( HashFunc != NULL ); ASSERT( CompareFunc != NULL ); m_NumObjects = 0; m_Recursion = 0; // // Force the number of buckets to a power of 2 so we can replace // MOD with an AND. // for (i = 15; i >= 0; i--) { m_BucketMask = (1 << i); if (NumBuckets & m_BucketMask) { if (NumBuckets ^ m_BucketMask) { i++; m_BucketMask = (1 << i); } break; } } ASSERT( i < 16); m_NumBuckets = m_BucketMask; m_BucketMask--; // // Allocate and initialize buckets // if ( m_NumBuckets <= (sizeof(m_PreAllocatedBuckets) / sizeof(CMTListNode)) ) m_Buckets = m_PreAllocatedBuckets; else m_Buckets = new CMTListNode[ m_NumBuckets ]; ASSERT( m_Buckets != NULL ); for ( i = 0; i < m_NumBuckets; i++ ) { pBucket = &m_Buckets[i]; pBucket->m_Next = pBucket; pBucket->m_Prev = pBucket; pBucket->m_Data = NULL; SET_NODE_IDX( pBucket, i ); MARK_NODE_DELETED( pBucket ); // unusual but it simplifies the lookup routines } } template inline CHash::~CHash() { ASSERT(!m_NumObjects); CMTListNode* pBucket; CMTListNode* node; CMTListNode* next; WORD i; // delete buckets for (i = 0; i < m_NumBuckets; i++) { pBucket = &m_Buckets[i]; for (node = pBucket->m_Next; node != pBucket; node = next) { next = node->m_Next; delete node ; } } if ( m_NumBuckets > (sizeof(m_PreAllocatedBuckets) / sizeof(CMTListNode)) ) delete [] m_Buckets; m_Buckets = NULL; } template inline MTListNodeHandle CHash::Add( K Key, T* Object ) { CMTListNode* node; WORD idx = (WORD) m_HashFunc( Key ) & m_BucketMask; CMTListNode* pBucket = &m_Buckets[ idx ]; node = new CMTListNode; if ( !node ) return NULL; node->m_Data = Object; CLEAR_NODE_DELETED( node ); SET_NODE_IDX( node, idx ); node->m_Prev = pBucket; node->m_Next = pBucket->m_Next; pBucket->m_Next = node; node->m_Next->m_Prev = node; m_NumObjects++; return node; } template inline T* CHash::Get( K Key ) { T* Object; T* Found; CMTListNode* node; WORD idx = (WORD) m_HashFunc( Key ) & m_BucketMask; CMTListNode* pBucket = &m_Buckets[ idx ]; // look for object in bucket list Found = NULL; for ( node = pBucket->m_Next; node != pBucket; node = node->m_Next ) { ASSERT( GET_NODE_IDX(node) == idx ); // skip deleted nodes if ( IS_NODE_DELETED( node ) ) continue; // node we're looking for? Object = (T*)( node->m_Data ); if ( m_CompareFunc( Object, Key ) ) { if ( m_GetFunc ) { m_GetFunc( Object ); } Found = Object; break; } } return Found; } template inline T* CHash::Delete( K Key ) { T* Object; T* Found; CMTListNode* node; CMTListNode* next; WORD idx = (WORD)m_HashFunc( Key ) & m_BucketMask; CMTListNode* pBucket = &m_Buckets[ idx ]; Found = NULL; for ( node = pBucket->m_Next; node != pBucket; node = next ) { ASSERT( GET_NODE_IDX(node) == idx ); next = node->m_Next; // remove deleted objects if ( IS_NODE_DELETED( node ) ) { node->m_Prev->m_Next = next; next->m_Prev = node->m_Prev; node->m_Prev = NULL; node->m_Next = NULL; delete node ; continue; } // node we're looking for? Object = (T*)( node->m_Data ); if ( m_CompareFunc( Object, Key ) ) { Found = Object; if ( m_Recursion == 0 ) { node->m_Prev->m_Next = next; next->m_Prev = node->m_Prev; node->m_Prev = NULL; node->m_Next = NULL; delete node ; m_NumObjects--; } else { MarkNodeDeleted( node ); } break; } } return Found; } template inline void CHash::DeleteNode( MTListNodeHandle node, PFDELFUNC pfDelete, void* Cookie ) { ASSERT( GET_NODE_IDX(node) < m_NumBuckets ); if ( pfDelete && node->m_Data ) { pfDelete( (T*) node->m_Data, Cookie ); node->m_Data = NULL; } node->m_Prev->m_Next = node->m_Next; node->m_Next->m_Prev = node->m_Prev; node->m_Prev = NULL; node->m_Next = NULL; delete node ; m_NumObjects--; } template inline void CHash::MarkNodeDeleted( MTListNodeHandle node, PFDELFUNC pfDelete, void* Cookie ) { if ( !IS_NODE_DELETED( node ) ) { MARK_NODE_DELETED( node ); if ( pfDelete && node->m_Data ) { pfDelete( (T*) node->m_Data, Cookie ); } node->m_Data = NULL; m_NumObjects--; } } template inline bool CHash::ForEach( PFITERCALLBACK pfCallback, void* Cookie ) { CMTListNode* pBucket; CMTListNode* node; WORD idx; m_Recursion++; for (idx = 0; idx < m_NumBuckets; idx++) { // step through bucket pBucket = &m_Buckets[ idx ]; for (node = pBucket->m_Next; node != pBucket; node = node->m_Next) { // skip deleted nodes if ( IS_NODE_DELETED( node ) ) continue; if (!pfCallback( (T*) node->m_Data, node, Cookie )) { m_Recursion--; return false; } } } m_Recursion--; return true; } template inline void CHash::RemoveAll( PFDELFUNC pfDelete, void* Cookie ) { CMTListNode* pBucket; CMTListNode* node; CMTListNode* next; WORD idx; for (idx = 0; idx < m_NumBuckets; idx++) { pBucket = &m_Buckets[ idx ]; for (node = pBucket->m_Next; node != pBucket; node = next ) { if ( pfDelete && node->m_Data ) { pfDelete( (T*) node->m_Data, Cookie ); node->m_Data = NULL; } next = node->m_Next; node->m_Prev->m_Next = node->m_Next; node->m_Next->m_Prev = node->m_Prev; node->m_Prev = NULL; node->m_Next = NULL; delete node ; } } m_NumObjects = 0;; } template inline void CHash::TrashDay() { CMTListNode* pBucket; CMTListNode* node; CMTListNode* next; WORD idx; for (idx = 0; idx < m_NumBuckets; idx++) { // step through buckets deleting marked nodes pBucket = &m_Buckets[ idx ]; for ( node = pBucket->m_Next; node != pBucket; node = next ) { ASSERT( GET_NODE_IDX(node) == idx ); next = node->m_Next; if ( IS_NODE_DELETED( node ) ) { node->m_Prev->m_Next = next; next->m_Prev = node->m_Prev; node->m_Prev = NULL; node->m_Next = NULL; delete node ; } } } } #pragma warning( default : 4244 ) #endif //!__HASH_H__