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.
698 lines
22 KiB
698 lines
22 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//============================================================================= |
|
|
|
#ifndef THASH_H |
|
#define THASH_H |
|
#ifdef _WIN32 |
|
#pragma once |
|
#endif |
|
|
|
#include <typeinfo> |
|
|
|
//#define DBGFLAG_THASH // Perform extra sanity checks on the THash |
|
|
|
|
|
// THash |
|
// This is a heavyweight, templatized version of DHash. |
|
// It differs from DHash in the following ways: |
|
// - It's templetized, and automatically constructs and destructs its records as appropriate |
|
// - It provides a scheduling service, which can be used to touch every record in the table |
|
// at a specified interval. The scheduler is low-overhead, and provides a very smooth |
|
// distribution of touches across frames. |
|
|
|
// Template arguments: |
|
// Data: the class to be stored in the hash table |
|
// I: the type of the primary key (uint32 by default) |
|
template<class Data, typename I=uint32> |
|
class CTHash |
|
{ |
|
private: |
|
// RecHdr |
|
// We insert one of these at the beginning of every record. It's used for |
|
// keeping the records in a linked list. |
|
typedef struct RecHdr_t |
|
{ |
|
RecHdr_t *m_pRecHdrNext; // Next item in our linked list |
|
RecHdr_t *m_pRecHdrPrev; // Previous item in our linked list |
|
I m_unKey; // Key of this item |
|
int m_iBucket; // The bucket we're in |
|
int m_nRunRatio; // We want to run 1 cycle out of every m_nRunRatio |
|
// cycles (not at all if 0). |
|
#ifdef DBGFLAG_THASH |
|
uint m_iCycleLast; // Last cycle we were visited (whether or not we ran) |
|
#endif |
|
} RecHdr_t; |
|
|
|
// Bucket |
|
// Each hash bucket is represented by a Bucket structure, which points to the |
|
// first record with the bucket's hash. |
|
typedef struct Bucket_t |
|
{ |
|
RecHdr_t *m_pRecHdrFirst; // First record in our list |
|
} Bucket_t; |
|
|
|
|
|
public: |
|
// Constructors & destructors |
|
CTHash( int cFramesPerCycle ); |
|
~CTHash(); |
|
|
|
// Initializer |
|
void Init( int cRecordInit, int cBuckets ); |
|
|
|
// Insert a record into the table |
|
Data *PvRecordInsert( I unKey ); |
|
// Insert a record into the table and set the allocated object's pointer as the hash key |
|
Data *PvRecordInsertAutoKey(); |
|
// Changes the key for a previously inserted item |
|
void ChangeKey( Data * pvRecord, I unOldKey, I unNewKey ); |
|
|
|
// Remove a record |
|
void Remove( I unKey ); |
|
// Remove a record |
|
void Remove( Data * pvRecord ); |
|
// Remove all records |
|
void RemoveAll(); |
|
|
|
// Find a record |
|
Data *PvRecordFind( I unKey ) const; |
|
|
|
// How many records do we have |
|
int Count() const { return m_cRecordInUse; } |
|
|
|
// Iterate through our members |
|
Data *PvRecordFirst() const; |
|
Data *PvRecordNext( Data *pvRecordCur ) const; |
|
|
|
// We provide a scheduling service. Call StartFrameSchedule when you want to start running |
|
// records in a given frame, and repeatedly call PvRecordRun until it returns NULL (or you |
|
// run our of time). |
|
void SetRunRatio( Data *pvRecord, int nRunRatio ); |
|
void SetMicroSecPerCycle( int cMicroSecPerCycle, int cMicroSecPerFrame ) { m_cFramesPerCycle = cMicroSecPerCycle / cMicroSecPerFrame; } |
|
void StartFrameSchedule( bool bNewFrame ); |
|
Data *PvRecordRun(); |
|
|
|
bool BCompletedPass(); |
|
|
|
#ifdef DBGFLAG_VALIDATE |
|
virtual void Validate( CValidator &validator, const char *pchName ); |
|
#endif // DBGFLAG_VALIDATE |
|
|
|
|
|
private: |
|
// Insert a record into the table |
|
Data *PvRecordInsertInternal( RecHdr_t *pRecHdr, I unKey ); |
|
|
|
// Get the record associated with a THashHdr |
|
Data *PvRecordFromPRecHdr( RecHdr_t *pRecHdr ) const { return ( Data * ) ( ( ( uint8 * ) pRecHdr + sizeof( RecHdr_t ) ) ); } |
|
|
|
// Get the RecHdr preceding a PvRecord |
|
RecHdr_t *PRecHdrFromPvRecord( Data *pvRecord ) const { return ( ( ( RecHdr_t * ) pvRecord ) - 1 ); } |
|
|
|
// Get the hash bucket corresponding to a key |
|
int IBucket( I unKey ) const; |
|
|
|
void InsertIntoHash( RecHdr_t *pRecHdr, I unKey ); |
|
void RemoveFromHash( Data * pvRecord ); |
|
|
|
int m_cBucket; // # of hash buckets we have |
|
Bucket_t *m_pBucket; // Big array of hash buckets |
|
|
|
CUtlMemoryPool *m_pMemoryPoolRecord; // All our data records |
|
|
|
int m_cRecordInUse; // # of records in use |
|
RecHdr_t m_RecHdrHead; // Head of our linked list |
|
RecHdr_t m_RecHdrTail; // Tail of our linked list |
|
|
|
int m_cFramesPerCycle; // Run each of our records once every m_cFramesPerCycle frames |
|
RecHdr_t *m_pRecHdrRunNext; // Next record to run (be careful-- this is more complicated than it sounds) |
|
int m_iBucketRunMax; // Stop running when we get to this bucket |
|
uint m_iCycleCur; // Current cycle (ie, how many times we've made a complete scheduler pass) |
|
uint m_iCycleLast; // Our previous cycle |
|
uint m_iFrameCur; // Our current frame (incremented once each StartFrameSchedule) |
|
uint m_iCycleLastReported; // Last cycle checked by BCompletedPass() |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Constructor |
|
// Input: cMicroSecRunInterval - How often we want the scheduler to run each of our records |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
CTHash<Data,I>::CTHash( int cFramesPerCycle ) |
|
{ |
|
m_cBucket = 0; |
|
m_pBucket = NULL; |
|
m_pMemoryPoolRecord = NULL; |
|
m_cRecordInUse = 0; |
|
|
|
m_cFramesPerCycle = cFramesPerCycle; |
|
m_pRecHdrRunNext = &m_RecHdrTail; // This will make us start at the beginning on our first frame |
|
m_iBucketRunMax = 0; |
|
m_iCycleCur = 0; |
|
m_iCycleLast = 0; |
|
m_iFrameCur = 0; |
|
m_iCycleLastReported = 0; |
|
|
|
m_RecHdrHead.m_pRecHdrPrev = NULL; |
|
m_RecHdrHead.m_pRecHdrNext = &m_RecHdrTail; |
|
m_RecHdrHead.m_iBucket = -1; |
|
|
|
m_RecHdrTail.m_pRecHdrPrev = &m_RecHdrHead; |
|
m_RecHdrTail.m_pRecHdrNext = NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Destructor |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
CTHash<Data,I>::~CTHash() |
|
{ |
|
RemoveAll(); |
|
|
|
if ( NULL != m_pBucket ) |
|
FreePv( m_pBucket ); |
|
m_pBucket = NULL; |
|
|
|
if ( NULL != m_pMemoryPoolRecord ) |
|
delete( m_pMemoryPoolRecord ); |
|
m_pMemoryPoolRecord = NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Initializer. Allocate our various arrays, and set up the free |
|
// list. |
|
// Input: cRecordInit - Initial # of data records we can contain |
|
// cBucket - # of hash buckets we should use |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::Init( int cRecordInit, int cBucket ) |
|
{ |
|
Assert( cRecordInit > 0 ); // need to init with non-zero value or memory pool will never grow |
|
|
|
// Copy our parameters |
|
m_cBucket = cBucket; |
|
|
|
// Alloc our arrays |
|
m_pBucket = ( Bucket_t * ) PvAlloc( sizeof( Bucket_t ) * m_cBucket ); |
|
m_pMemoryPoolRecord = new CUtlMemoryPool( sizeof( Data ) + sizeof( RecHdr_t ), cRecordInit, |
|
CUtlMemoryPool::GROW_SLOW ); |
|
|
|
// Init the hash buckets |
|
for ( int iBucket = 0; iBucket < m_cBucket; iBucket++ ) |
|
m_pBucket[iBucket].m_pRecHdrFirst = NULL; |
|
|
|
// Make the tail have an illegally large bucket |
|
m_RecHdrTail.m_iBucket = m_cBucket + 1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Inserts a new record into the table |
|
// Input: unKey - Primary key of the new record |
|
// Output: Pointer to the new record |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
Data *CTHash<Data,I>::PvRecordInsert( I unKey ) |
|
{ |
|
Assert( PvRecordFind( unKey ) == NULL ); // keys are unique; no record with this key may exist |
|
|
|
// Find a free record |
|
RecHdr_t *pRecHdr = ( RecHdr_t * ) m_pMemoryPoolRecord->Alloc(); |
|
|
|
return PvRecordInsertInternal( pRecHdr, unKey ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Inserts a new record into the table and sets its key to the pointer |
|
// value of the record |
|
// Output: Pointer to the new record |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
Data *CTHash<Data,I>::PvRecordInsertAutoKey() |
|
{ |
|
// Find a free record |
|
RecHdr_t *pRecHdr = ( RecHdr_t * ) m_pMemoryPoolRecord->Alloc(); |
|
|
|
return PvRecordInsertInternal( pRecHdr, (I) PvRecordFromPRecHdr( pRecHdr ) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Inserts an allocated record into the hash table with specified key |
|
// and calls the constructor of the allocated object |
|
// Input: pRecHdr - record to insert |
|
// unKey - hash key to use for record |
|
// Output: Pointer to the new record |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
Data *CTHash<Data,I>::PvRecordInsertInternal( RecHdr_t *pRecHdr, I unKey ) |
|
{ |
|
InsertIntoHash( pRecHdr, unKey ); |
|
|
|
// assert that we don't have too many items per bucket |
|
static bool s_bPerfWarning = false; |
|
if ( !s_bPerfWarning && Count() >= ( 5 * m_cBucket ) ) |
|
{ |
|
s_bPerfWarning = true; |
|
AssertMsg( false, "Performance warning: too many items, not enough buckets" ); |
|
Msg( "not enough buckets in thash class %s (%d records, %d buckets)\n", |
|
#ifdef _WIN32 |
|
typeid(*this).raw_name(), |
|
#else |
|
typeid(*this).name(), |
|
#endif |
|
Count(), m_cBucket ); |
|
} |
|
|
|
// Construct ourselves |
|
Data *pData = PvRecordFromPRecHdr( pRecHdr ); |
|
Construct<Data>( pData ); |
|
return pData; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Changes key on previously inserted item |
|
// Input: pvRecord - record to change key for |
|
// unOldKey - old key (not strictly needed, but helpful consistency check) |
|
// unNewKey - new key to use |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::ChangeKey( Data * pvRecord, I unOldKey, I unNewKey ) |
|
{ |
|
Data * pvRecordFound = PvRecordFind( unOldKey ); |
|
Assert( pvRecordFound == pvRecord ); |
|
if ( pvRecordFound == pvRecord ) |
|
{ |
|
RemoveFromHash( pvRecord ); |
|
InsertIntoHash( PRecHdrFromPvRecord( pvRecord), unNewKey ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes the entry with a specified key from the table |
|
// Input: unKey - Key of the entry to remove |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::Remove( I unKey ) |
|
{ |
|
Data *pvRemove = ( Data * ) PvRecordFind( unKey ); |
|
Assert( pvRemove ); |
|
if ( !pvRemove ) |
|
return; |
|
Remove( pvRemove ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes the specified entry from the table |
|
// Input: pvRemove - Pointer to the entry to remove |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::Remove( Data * pvRemove ) |
|
{ |
|
// Destruct the record we're removing |
|
Destruct<Data>( pvRemove ); |
|
|
|
RemoveFromHash( pvRemove ); |
|
m_pMemoryPoolRecord->Free( PRecHdrFromPvRecord( pvRemove ) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes all entries from the table |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::RemoveAll() |
|
{ |
|
Data * pvRecord = PvRecordFirst(); |
|
while ( pvRecord ) |
|
{ |
|
Data *pvRecordNext = PvRecordNext( pvRecord ); |
|
Remove( pvRecord ); |
|
pvRecord = pvRecordNext; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the entry with a specified key |
|
// Input: unKey - Key to find |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
Data *CTHash<Data,I>::PvRecordFind( I unKey ) const |
|
{ |
|
// Find our hash bucket |
|
int iBucket = IBucket( unKey ); |
|
|
|
// Walk the bucket's list looking for an exact match |
|
for ( RecHdr_t *pRecHdr = m_pBucket[iBucket].m_pRecHdrFirst; |
|
NULL != pRecHdr && pRecHdr->m_iBucket == iBucket; |
|
pRecHdr = pRecHdr->m_pRecHdrNext ) |
|
{ |
|
if ( unKey == pRecHdr->m_unKey ) |
|
return PvRecordFromPRecHdr( pRecHdr ); |
|
} |
|
|
|
// Didn't find a match |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds our first record |
|
// Output: Pointer to our first record |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
Data *CTHash<Data,I>::PvRecordFirst() const |
|
{ |
|
if ( &m_RecHdrTail != m_RecHdrHead.m_pRecHdrNext ) |
|
return PvRecordFromPRecHdr( m_RecHdrHead.m_pRecHdrNext ); |
|
else |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Iterates to the record after a given record |
|
// Input: Pointer to a current record |
|
// Output: Pointer to the next record |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
Data *CTHash<Data,I>::PvRecordNext( Data *pvRecordCur ) const |
|
{ |
|
RecHdr_t *pRecHdr = PRecHdrFromPvRecord( pvRecordCur ); |
|
if ( &m_RecHdrTail == pRecHdr->m_pRecHdrNext ) |
|
return NULL; |
|
|
|
return PvRecordFromPRecHdr( pRecHdr->m_pRecHdrNext ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sets the run ratio of a particular record in the hash table. |
|
// The record will be run 1 cycle out of every nRunRatio cycles. |
|
// Input: pvRecord - The record we're setting |
|
// nRunRatio - The run ratio for this record |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::SetRunRatio( Data *pvRecord, int nRunRatio ) |
|
{ |
|
PRecHdrFromPvRecord( pvRecord )->m_nRunRatio = nRunRatio; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Prepares us to run all records that are due to be run this frame. |
|
// Records are run at a particular time dependent on their hash bucket, |
|
// regardless of when they were last run. |
|
// Input: bNewFrame - True if this is a new frame. If false, we've run |
|
// off the end of the list and are checking whether |
|
// we need to keep going at the beginning. |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::StartFrameSchedule( bool bNewFrame ) |
|
{ |
|
// Calculate our current frame and cycle cycle |
|
if ( bNewFrame ) |
|
{ |
|
m_iCycleLast = m_iCycleCur; |
|
m_iFrameCur++; |
|
m_iCycleCur = m_iFrameCur / m_cFramesPerCycle; |
|
} |
|
|
|
// Calculate the last bucket to run |
|
int iFrameInCycle = m_iFrameCur % m_cFramesPerCycle; |
|
m_iBucketRunMax = ( int ) ( ( ( int64 ) ( iFrameInCycle + 1 ) * ( int64 ) m_cBucket ) |
|
/ ( int64 ) m_cFramesPerCycle ); |
|
AssertFatal( m_iBucketRunMax >= 0 && m_iBucketRunMax <= m_cBucket ); |
|
|
|
// Are we starting a new cycle? |
|
if ( m_iCycleCur > m_iCycleLast ) |
|
{ |
|
#ifdef DBGFLAG_THASH |
|
Assert( m_iCycleCur == m_iCycleLast + 1 ); |
|
#endif |
|
|
|
// Did we finish the last cycle? |
|
if ( &m_RecHdrTail == m_pRecHdrRunNext ) |
|
{ |
|
m_pRecHdrRunNext = m_RecHdrHead.m_pRecHdrNext; |
|
} |
|
// No-- finish it up before moving on |
|
else |
|
{ |
|
m_iBucketRunMax = m_cBucket + 1; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the next record to run, if any |
|
// Output: Pointer to the next record that needs to run (NULL if we're done) |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
Data *CTHash<Data,I>::PvRecordRun() |
|
{ |
|
// Loop until we find a record to run, or until we pass m_iBucketRunMax |
|
for ( ; ; ) |
|
{ |
|
// Are we past our stopping point? |
|
if ( m_pRecHdrRunNext->m_iBucket >= m_iBucketRunMax ) |
|
{ |
|
// If this cycle ran to the very end, see if we need to start over |
|
if ( m_iBucketRunMax > m_cBucket ) |
|
{ |
|
StartFrameSchedule( false ); |
|
continue; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
#ifdef DBGFLAG_THASH |
|
Assert( m_pRecHdrRunNext->m_iBucket >= m_iBucketRunFirst ); |
|
if ( 0 != m_pRecHdrRunNext->m_iCycleLast ) |
|
{ |
|
if ( m_pRecHdrRunNext->m_iCycleLast == m_iCycleCur ) |
|
{ |
|
DMsg( SPEW_CONSOLE, 1, "Double cycle: hdr = 0x%x, last frame = %d, curFrame = %d, first = %d, last = %d, bucket = %d\n", |
|
m_pRecHdrRunNext, m_pRecHdrRunNext->m_iFrameLast, m_iFrame, |
|
m_iBucketRunFirst, m_iBucketRunMax, m_pRecHdrRunNext->m_iBucket ); |
|
} |
|
else if ( m_pRecHdrRunNext->m_iCycleLast != m_iCycleCur - 1 ) |
|
{ |
|
DMsg( SPEW_CONSOLE, 1, "Skipped cycle: hdr = 0x%x, cycleLast = %u, cycleCur = %u (missed %u cycles)\n", |
|
m_pRecHdrRunNext, m_pRecHdrRunNext->m_iCycleLast, m_iCycleCur, |
|
m_iCycleCur - m_pRecHdrRunNext->m_iCycleLast ); |
|
Assert( false ); |
|
} |
|
} |
|
m_pRecHdrRunNext->m_iCycleLast = m_iCycleCur; |
|
m_pRecHdrRunNext->m_iFrameLast = m_iFrame; |
|
#endif |
|
|
|
// Set up the record to run next time |
|
RecHdr_t *pRecHdrCur = m_pRecHdrRunNext; |
|
m_pRecHdrRunNext = m_pRecHdrRunNext->m_pRecHdrNext; |
|
|
|
// Does this record need to run? |
|
if ( 0 == pRecHdrCur->m_nRunRatio ) |
|
continue; |
|
|
|
if ( 0 == m_iCycleCur % pRecHdrCur->m_nRunRatio ) |
|
return PvRecordFromPRecHdr( pRecHdrCur ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if we've completed a scheduler pass since last called |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
bool CTHash<Data,I>::BCompletedPass() |
|
{ |
|
if ( m_iCycleCur != m_iCycleLastReported ) |
|
{ |
|
m_iCycleLastReported = m_iCycleCur; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
extern const unsigned char g_CTHashRandomValues[256]; // definition lives in globals.cpp |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the index of the hash bucket corresponding to a particular key |
|
// Input: unKey - Key to find |
|
// Output: Index of the hash bucket corresponding to unKey |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
int CTHash<Data,I>::IBucket( I unKey ) const |
|
{ |
|
AssertFatal( m_cBucket > 0 ); |
|
|
|
// This is a pearsons hash variant that returns a maximum of 32 bits |
|
size_t size = sizeof(I); |
|
const uint8 * k = (const uint8 *) &unKey; |
|
uint32 byte_one = 0, byte_two = 0, byte_three = 0, byte_four = 0, n; |
|
|
|
while (size) |
|
{ |
|
--size; |
|
n = *k++; |
|
byte_one = g_CTHashRandomValues[byte_one ^ n]; |
|
|
|
if (size) |
|
{ |
|
--size; |
|
n = *k++; |
|
byte_two = g_CTHashRandomValues[byte_two ^ n]; |
|
} |
|
else |
|
break; |
|
|
|
if (size) |
|
{ |
|
--size; |
|
n = *k++; |
|
byte_three = g_CTHashRandomValues[byte_three ^ n]; |
|
} |
|
else |
|
break; |
|
|
|
if (size) |
|
{ |
|
--size; |
|
n = *k++; |
|
byte_four = g_CTHashRandomValues[byte_four ^ n]; |
|
} |
|
else |
|
break; |
|
} |
|
|
|
uint32 idx = ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one; |
|
idx = idx % m_cBucket; |
|
return ( (int) idx ); |
|
} |
|
|
|
|
|
#ifdef DBGFLAG_VALIDATE |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Run a global validation pass on all of our data structures and memory |
|
// allocations. |
|
// Input: validator - Our global validator object |
|
// pchName - Our name (typically a member var in our container) |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::Validate( CValidator &validator, const char *pchName ) |
|
{ |
|
VALIDATE_SCOPE(); |
|
|
|
validator.ClaimMemory( m_pBucket ); |
|
ValidatePtr( m_pMemoryPoolRecord ); |
|
|
|
#if defined( _DEBUG ) |
|
// first verify m_cRecordInUse |
|
Data * pvRecord = PvRecordFirst(); |
|
int cItems = 0; |
|
while ( pvRecord ) |
|
{ |
|
Data *pvRecordNext = PvRecordNext( pvRecord ); |
|
cItems++; |
|
pvRecord = pvRecordNext; |
|
} |
|
Assert( m_cRecordInUse == cItems ); |
|
// then ask the mempool to verify this |
|
if ( m_pMemoryPoolRecord ) |
|
m_pMemoryPoolRecord->LeakCheck( cItems ); |
|
#endif // _DEBUG |
|
} |
|
#endif // DBGFLAG_VALIDATE |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Inserts a new record into the table |
|
// Input: unKey - Primary key of the new record |
|
// Output: Pointer to the new record |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::InsertIntoHash( RecHdr_t *pRecHdr, I unKey ) |
|
{ |
|
m_cRecordInUse++; |
|
|
|
// Init the RecHdr |
|
pRecHdr->m_unKey = unKey; |
|
pRecHdr->m_nRunRatio = 1; |
|
|
|
// Find our hash bucket |
|
int iBucket = IBucket( unKey ); |
|
pRecHdr->m_iBucket = iBucket; |
|
#ifdef DBGFLAG_THASH |
|
pRecHdr->m_iCycleLast = 0; |
|
#endif |
|
|
|
// Find where to insert ourselves in the linked list |
|
RecHdr_t *pRecHdrInsertBefore = &m_RecHdrTail; |
|
// Find the first bucket with anything in it that's at or after our bucket |
|
for ( int iBucketT = iBucket; iBucketT < m_cBucket; iBucketT++ ) |
|
{ |
|
if ( NULL != m_pBucket[iBucketT].m_pRecHdrFirst ) |
|
{ |
|
pRecHdrInsertBefore = m_pBucket[iBucketT].m_pRecHdrFirst; |
|
break; |
|
} |
|
} |
|
|
|
// Insert ourselves |
|
pRecHdr->m_pRecHdrNext = pRecHdrInsertBefore; |
|
pRecHdr->m_pRecHdrPrev = pRecHdrInsertBefore->m_pRecHdrPrev; |
|
pRecHdrInsertBefore->m_pRecHdrPrev = pRecHdr; |
|
pRecHdr->m_pRecHdrPrev->m_pRecHdrNext = pRecHdr; |
|
|
|
// Our bucket should point to us |
|
m_pBucket[iBucket].m_pRecHdrFirst = pRecHdr; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes the specified entry from the table |
|
// Input: pvRemove - Pointer to the entry to remove |
|
//----------------------------------------------------------------------------- |
|
template<class Data, class I> |
|
void CTHash<Data,I>::RemoveFromHash( Data * pvRemove ) |
|
{ |
|
// Find our RecHdr |
|
RecHdr_t *pRecHdr = PRecHdrFromPvRecord( pvRemove ); |
|
|
|
// If our bucket points to us, point it to the next record (or else NULL) |
|
int iBucket = IBucket( pRecHdr->m_unKey ); |
|
if ( pRecHdr == m_pBucket[iBucket].m_pRecHdrFirst ) |
|
{ |
|
if ( pRecHdr->m_pRecHdrNext->m_iBucket == iBucket ) |
|
m_pBucket[iBucket].m_pRecHdrFirst = pRecHdr->m_pRecHdrNext; |
|
else |
|
m_pBucket[iBucket].m_pRecHdrFirst = NULL; |
|
} |
|
|
|
// Remove us from the linked list |
|
pRecHdr->m_pRecHdrPrev->m_pRecHdrNext = pRecHdr->m_pRecHdrNext; |
|
pRecHdr->m_pRecHdrNext->m_pRecHdrPrev = pRecHdr->m_pRecHdrPrev; |
|
|
|
// Are we the next record to run? |
|
if ( pRecHdr == m_pRecHdrRunNext ) |
|
m_pRecHdrRunNext = pRecHdr->m_pRecHdrNext; |
|
|
|
m_cRecordInUse--; |
|
} |
|
|
|
#endif // THASH_H
|
|
|