2023-08-04 14:32:06 +02:00
|
|
|
//=============================================================================//
|
|
|
|
//
|
|
|
|
// Purpose:
|
|
|
|
//
|
|
|
|
//=============================================================================//
|
|
|
|
#include "concommandhash.h"
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Console command hash data structure
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CConCommandHash::CConCommandHash()
|
|
|
|
{
|
|
|
|
Purge(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
CConCommandHash::~CConCommandHash()
|
|
|
|
{
|
|
|
|
Purge(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CConCommandHash::Purge(bool bReinitialize)
|
|
|
|
{
|
|
|
|
m_aBuckets.Purge();
|
|
|
|
m_aDataPool.Purge();
|
|
|
|
if (bReinitialize)
|
|
|
|
{
|
|
|
|
Init();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize.
|
|
|
|
void CConCommandHash::Init(void)
|
|
|
|
{
|
|
|
|
// kNUM_BUCKETS must be a power of two.
|
|
|
|
COMPILE_TIME_ASSERT((kNUM_BUCKETS & (kNUM_BUCKETS - 1)) == 0);
|
|
|
|
|
|
|
|
// Set the bucket size.
|
|
|
|
m_aBuckets.SetSize(kNUM_BUCKETS);
|
|
|
|
for (int iBucket = 0; iBucket < kNUM_BUCKETS; ++iBucket)
|
|
|
|
{
|
|
|
|
m_aBuckets[iBucket] = m_aDataPool.InvalidIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the grow size.
|
|
|
|
int nGrowSize = 4 * kNUM_BUCKETS;
|
|
|
|
m_aDataPool.SetGrowSize(nGrowSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Insert data into the hash table given its key (unsigned int),
|
|
|
|
// WITH a check to see if the element already exists within the hash.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CConCommandHash::CCommandHashHandle_t CConCommandHash::Insert(ConCommandBase* cmd)
|
|
|
|
{
|
|
|
|
// Check to see if that key already exists in the buckets (should be unique).
|
|
|
|
CCommandHashHandle_t hHash = Find(cmd);
|
|
|
|
if (hHash != InvalidHandle())
|
|
|
|
return hHash;
|
|
|
|
|
|
|
|
return FastInsert(cmd);
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Insert data into the hash table given its key (unsigned int),
|
|
|
|
// WITHOUT a check to see if the element already exists within the hash.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CConCommandHash::CCommandHashHandle_t CConCommandHash::FastInsert(ConCommandBase* cmd)
|
|
|
|
{
|
|
|
|
// Get a new element from the pool.
|
|
|
|
intptr_t iHashData = m_aDataPool.Alloc(true);
|
|
|
|
HashEntry_t* RESTRICT pHashData = &m_aDataPool[iHashData];
|
|
|
|
if (!pHashData)
|
|
|
|
return InvalidHandle();
|
|
|
|
|
|
|
|
HashKey_t key = Hash(cmd);
|
|
|
|
|
|
|
|
// Add data to new element.
|
|
|
|
pHashData->m_uiKey = key;
|
|
|
|
pHashData->m_Data = cmd;
|
|
|
|
|
|
|
|
// Link element.
|
|
|
|
int iBucket = key & kBUCKETMASK; // HashFuncs::Hash( uiKey, m_uiBucketMask );
|
|
|
|
m_aDataPool.LinkBefore(m_aBuckets[iBucket], iHashData);
|
|
|
|
m_aBuckets[iBucket] = iHashData;
|
|
|
|
|
|
|
|
return iHashData;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Remove a given element from the hash.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CConCommandHash::Remove(CCommandHashHandle_t hHash) /*RESTRICT*/
|
|
|
|
{
|
|
|
|
HashEntry_t* /*RESTRICT*/ entry = &m_aDataPool[hHash];
|
|
|
|
HashKey_t iBucket = entry->m_uiKey & kBUCKETMASK;
|
|
|
|
if (m_aBuckets[iBucket] == hHash)
|
|
|
|
{
|
|
|
|
// It is a bucket head.
|
|
|
|
m_aBuckets[iBucket] = m_aDataPool.Next(hHash);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Not a bucket head.
|
|
|
|
m_aDataPool.Unlink(hHash);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the element.
|
|
|
|
m_aDataPool.Remove(hHash);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Remove all elements from the hash
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CConCommandHash::RemoveAll(void)
|
|
|
|
{
|
|
|
|
m_aBuckets.RemoveAll();
|
|
|
|
m_aDataPool.RemoveAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Find hash entry corresponding to a string name
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CConCommandHash::CCommandHashHandle_t CConCommandHash::Find(
|
|
|
|
const char* name, HashKey_t hashkey) const /*RESTRICT*/
|
|
|
|
{
|
|
|
|
// hash the "key" - get the correct hash table "bucket"
|
|
|
|
int iBucket = hashkey & kBUCKETMASK;
|
|
|
|
|
|
|
|
for (datapool_t::IndexLocalType_t iElement = m_aBuckets[iBucket];
|
|
|
|
iElement != m_aDataPool.InvalidIndex(); iElement = m_aDataPool.Next(iElement))
|
|
|
|
{
|
|
|
|
const HashEntry_t& element = m_aDataPool[iElement];
|
|
|
|
if (element.m_uiKey == hashkey && // if hashes of strings match,
|
|
|
|
Q_stricmp(name, element.m_Data->GetName()) == 0) // then test the actual strings
|
|
|
|
{
|
|
|
|
return iElement;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// found nuffink
|
|
|
|
return InvalidHandle();
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Find a command in the hash.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CConCommandHash::CCommandHashHandle_t CConCommandHash::Find(const ConCommandBase* cmd) const /*RESTRICT*/
|
|
|
|
{
|
|
|
|
// Set this #if to 1 if the assert at bottom starts whining --
|
|
|
|
// that indicates that a console command is being double-registered,
|
|
|
|
// or something similarly non-fatally bad. With this #if 1, we'll search
|
|
|
|
// by name instead of by pointer, which is more robust in the face
|
|
|
|
// of double registered commands, but obviously slower.
|
|
|
|
#if 0
|
|
|
|
return Find(cmd->GetName());
|
|
|
|
#else
|
|
|
|
HashKey_t hashkey = Hash(cmd);
|
|
|
|
int iBucket = hashkey & kBUCKETMASK;
|
|
|
|
|
|
|
|
// hunt through all entries in that bucket
|
|
|
|
for (datapool_t::IndexLocalType_t iElement = m_aBuckets[iBucket];
|
|
|
|
iElement != m_aDataPool.InvalidIndex(); iElement = m_aDataPool.Next(iElement))
|
|
|
|
{
|
|
|
|
const HashEntry_t& element = m_aDataPool[iElement];
|
|
|
|
if (element.m_uiKey == hashkey && // if the hashes match...
|
|
|
|
element.m_Data == cmd) // and the pointers...
|
|
|
|
{
|
|
|
|
// in debug, test to make sure we don't have commands under the same name
|
|
|
|
// or something goofy like that
|
|
|
|
Assert(iElement == Find(cmd->GetName()),
|
|
|
|
"ConCommand %s had two entries in the hash!", cmd->GetName());
|
|
|
|
|
|
|
|
// return this element
|
|
|
|
return iElement;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// found nothing.
|
|
|
|
#ifdef DBGFLAG_ASSERT // double check against search by name
|
|
|
|
CCommandHashHandle_t dbghand = Find(cmd->GetName());
|
|
|
|
|
|
|
|
Assert(InvalidHandle() == dbghand,
|
|
|
|
"ConCommand %s couldn't be found by pointer, but was found by name!", cmd->GetName());
|
|
|
|
#endif
|
|
|
|
return InvalidHandle();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//#ifdef _DEBUG
|
|
|
|
// Dump a report to MSG
|
|
|
|
void CConCommandHash::Report(void)
|
|
|
|
{
|
2023-08-21 19:12:29 +02:00
|
|
|
Msg(eDLL_T::COMMON, "Console command hash bucket load:\n");
|
2023-08-04 14:32:06 +02:00
|
|
|
int total = 0;
|
|
|
|
for (int iBucket = 0; iBucket < kNUM_BUCKETS; ++iBucket)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
CCommandHashHandle_t iElement = m_aBuckets[iBucket]; // get the head of the bucket
|
|
|
|
while (iElement != m_aDataPool.InvalidIndex())
|
|
|
|
{
|
|
|
|
++count;
|
|
|
|
iElement = m_aDataPool.Next(iElement);
|
|
|
|
}
|
|
|
|
|
2023-08-21 19:12:29 +02:00
|
|
|
Msg(eDLL_T::COMMON, "%d: %d\n", iBucket, count);
|
2023-08-04 14:32:06 +02:00
|
|
|
total += count;
|
|
|
|
}
|
|
|
|
|
2023-08-21 19:12:29 +02:00
|
|
|
Msg(eDLL_T::COMMON, "\tAverage: %.1f\n", total / ((float)(kNUM_BUCKETS)));
|
2023-08-04 14:32:06 +02:00
|
|
|
}
|
|
|
|
//#endif
|