diff --git a/src/public/ikeyvaluessystem.h b/src/public/ikeyvaluessystem.h index 00c587f4..6099ab4f 100644 --- a/src/public/ikeyvaluessystem.h +++ b/src/public/ikeyvaluessystem.h @@ -26,29 +26,31 @@ public: // registers the size of the KeyValues in the specified instance // so it can build a properly sized memory pool for the KeyValues objects // the sizes will usually never differ but this is for versioning safety - virtual void RegisterSizeofKeyValues( ssize_t nSize ) = 0; + virtual void RegisterSizeofKeyValues( const ssize_t nSize ) = 0; // allocates/frees a KeyValues object from the shared mempool - virtual void *AllocKeyValuesMemory( ssize_t nSize ) = 0; - virtual void FreeKeyValuesMemory( void *pMem ) = 0; + virtual void *AllocKeyValuesMemory( const ssize_t nSize ) = 0; + virtual void FreeKeyValuesMemory( void *const pMem ) = 0; // symbol table access (used for key names) - virtual HKeySymbol GetSymbolForString( const char *pName, bool bCreate = true ) = 0; - virtual const char *GetStringForSymbol( HKeySymbol symbol ) = 0; + virtual HKeySymbol GetSymbolForString( const char *const pName, const bool bCreate = true ) = 0; + virtual const char *GetStringForSymbol( const HKeySymbol symbol ) = 0; // for debugging, adds KeyValues record into global list so we can track memory leaks - virtual void AddKeyValuesToMemoryLeakList( void *pMem, HKeySymbol name ) = 0; - virtual void RemoveKeyValuesFromMemoryLeakList( void *pMem ) = 0; + virtual void AddKeyValuesToMemoryLeakList( const void *const pMem, const HKeySymbol name ) = 0; + virtual void RemoveKeyValuesFromMemoryLeakList( const void *const pMem ) = 0; // Returns the globally allocated keyvalues memory pool. virtual void *GetKeyValuesMemory( void ) = 0; // set/get a value for keyvalues resolution symbol // e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables [$LOWVIOLENCE] - virtual void SetKeyValuesExpressionSymbol( const char *pName, bool bValue ) = 0; - virtual bool GetKeyValuesExpressionSymbol( const char *pName ) = 0; + virtual void SetKeyValuesExpressionSymbol( const char *const pName, const bool bValue ) = 0; + virtual bool GetKeyValuesExpressionSymbol( const char *const pName ) = 0; // symbol table access from code with case-preserving requirements (used for key names) - virtual HKeySymbol GetSymbolForStringCaseSensitive( HKeySymbol &hCaseInsensitiveSymbol, const char *pName, bool bCreate = true ) = 0; + virtual HKeySymbol GetSymbolForStringCaseSensitive( HKeySymbol &hCaseInsensitiveSymbol, const char *const pName, const bool bCreate = true ) = 0; }; +IKeyValuesSystem* KeyValuesSystem(); + #endif // VSTDLIB_IKEYVALUESSYSTEM_H diff --git a/src/vstdlib/keyvaluessystem.cpp b/src/vstdlib/keyvaluessystem.cpp index c27e0bf4..8bd06393 100644 --- a/src/vstdlib/keyvaluessystem.cpp +++ b/src/vstdlib/keyvaluessystem.cpp @@ -1,110 +1,521 @@ -//=============================================================================// +//====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======// // // Purpose: // // $NoKeywords: $ //=============================================================================// -#include "core/stdafx.h" -#include "vstdlib/keyvaluessystem.h" +#include +#include "keyvaluessystem.h" +#include "tier0/threadtools.h" +#include "tier1/keyvalues.h" +#include "tier1/mempool.h" +#include "tier1/utlsymbol.h" +#include "tier1/utlmap.h" +#include "tier1/memstack.h" +#include "tier1/convar.h" +#include "tier1/strtools.h" + +#ifdef _PS3 +#include "ps3/ps3_core.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include + +#ifdef NO_SBH // no need to pool if using tier0 small block heap +#define KEYVALUES_USE_POOL 1 +#endif + +// +// Defines platform-endian-specific macros: +// MEM_4BYTES_AS_0_AND_3BYTES : present a 4 byte uint32 as a memory +// layout where first memory byte is zero +// and the other 3 bytes represent value +// MEM_4BYTES_FROM_0_AND_3BYTES: unpack from memory with first zero byte +// and 3 value bytes the original uint32 value +// +// used for efficiently reading/writing storing 3 byte values into memory +// region immediately following a null-byte-terminated string, essentially +// sharing the null-byte-terminator with the first memory byte +// +#if defined( PLAT_LITTLE_ENDIAN ) +// Number in memory has lowest-byte in front, use shifts to make it zero +#define MEM_4BYTES_AS_0_AND_3BYTES( x4bytes ) ( ( (uint32) (x4bytes) ) << 8 ) +#define MEM_4BYTES_FROM_0_AND_3BYTES( x03bytes ) ( ( (uint32) (x03bytes) ) >> 8 ) +#endif +#if defined( PLAT_BIG_ENDIAN ) +// Number in memory has highest-byte in front, use masking to make it zero +#define MEM_4BYTES_AS_0_AND_3BYTES( x4bytes ) ( ( (uint32) (x4bytes) ) & 0x00FFFFFF ) +#define MEM_4BYTES_FROM_0_AND_3BYTES( x03bytes ) ( ( (uint32) (x03bytes) ) & 0x00FFFFFF ) +#endif + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CKeyValuesSystem::CKeyValuesSystem() : + m_HashItemMemPool(sizeof(hash_item_t), 64, CUtlMemoryPool::GROW_FAST, "CKeyValuesSystem::m_HashItemMemPool"), + m_KeyValuesTrackingList(0, 0, MemoryLeakTrackerLessFunc), + m_KvConditionalSymbolTable(DefLessFunc(HKeySymbol)) +{ + MEM_ALLOC_CREDIT(); + // initialize hash table + m_HashTable.AddMultipleToTail(2047); + for (int i = 0; i < m_HashTable.Count(); i++) + { + m_HashTable[i].stringIndex = 0; + m_HashTable[i].next = NULL; + } + + m_Strings.Init("CKeyValuesSystem::m_Strings", 4 * 1024 * 1024, 64 * 1024, 0, 4); + // Make 0 stringIndex to never be returned, by allocating + // and wasting minimal number of alignment bytes now: + char* pszEmpty = ((char*)m_Strings.Alloc(1)); + *pszEmpty = 0; + +#ifdef KEYVALUES_USE_POOL + m_pMemPool = NULL; +#endif + m_iMaxKeyValuesSize = sizeof(KeyValues); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CKeyValuesSystem::~CKeyValuesSystem() +{ +#ifdef KEYVALUES_USE_POOL +#ifdef _DEBUG + // display any memory leaks + if (m_pMemPool && m_pMemPool->Count() > 0) + { + DevMsg("Leaked KeyValues blocks: %d\n", m_pMemPool->Count()); + } + + // iterate all the existing keyvalues displaying their names + for (int i = 0; i < m_KeyValuesTrackingList.MaxElement(); i++) + { + if (m_KeyValuesTrackingList.IsValidIndex(i)) + { + DevMsg("\tleaked KeyValues(%s)\n", &m_Strings[m_KeyValuesTrackingList[i].nameIndex]); + } + } +#endif + + delete m_pMemPool; +#endif +} //----------------------------------------------------------------------------- // Purpose: registers the size of the KeyValues in the specified instance // so it can build a properly sized memory pool for the KeyValues objects // the sizes will usually never differ but this is for versioning safety -// Input : nSize - //----------------------------------------------------------------------------- -void CKeyValuesSystem::RegisterSizeofKeyValues(int64_t nSize) +void CKeyValuesSystem::RegisterSizeofKeyValues(const ssize_t size) { - const static int index = 0; - CallVFunc(index, this, nSize); + if (size > m_iMaxKeyValuesSize) + { + m_iMaxKeyValuesSize = size; + } } +#ifdef KEYVALUES_USE_POOL +static void KVLeak(char const* fmt, ...) +{ + va_list argptr; + char data[1024]; + + va_start(argptr, fmt); + Q_vsnprintf(data, sizeof(data), fmt, argptr); + va_end(argptr); + + Msg(eDLL_T::COMMON, "%s", data); +} +#endif + //----------------------------------------------------------------------------- // Purpose: allocates a KeyValues object from the shared mempool -// Input : nSize - -// Output : pointer to allocated pool //----------------------------------------------------------------------------- -void* CKeyValuesSystem::AllocKeyValuesMemory(int64_t nSize) +void* CKeyValuesSystem::AllocKeyValuesMemory(const ssize_t size) { - const static int index = 1; - return CallVFunc(index, this, nSize); +#ifdef KEYVALUES_USE_POOL + // allocate, if we don't have one yet + if (!m_pMemPool) + { + m_pMemPool = new CUtlMemoryPool(m_iMaxKeyValuesSize, 1024, CUtlMemoryPool::GROW_FAST, "CKeyValuesSystem::m_pMemPool"); + m_pMemPool->SetErrorReportFunc(KVLeak); + } + + return m_pMemPool->Alloc(size); +#else + return malloc(size); +#endif } //----------------------------------------------------------------------------- // Purpose: frees a KeyValues object from the shared mempool -// Input : *pMem - //----------------------------------------------------------------------------- -void CKeyValuesSystem::FreeKeyValuesMemory(void* pMem) +void CKeyValuesSystem::FreeKeyValuesMemory(void* const pMem) { - const static int index = 2; - CallVFunc(index, this, pMem); +#ifdef KEYVALUES_USE_POOL + m_pMemPool->Free(pMem); +#else + free(pMem); +#endif } //----------------------------------------------------------------------------- // Purpose: symbol table access (used for key names) -// Input : *szName - -// bCreate - -// Output : handle to KeyValue symbol on success, -1 on failure //----------------------------------------------------------------------------- -HKeySymbol CKeyValuesSystem::GetSymbolForString(const char* szName, bool bCreate) +HKeySymbol CKeyValuesSystem::GetSymbolForString(const char* const name, const bool bCreate) { - const static int index = 3; - return CallVFunc(index, this, szName, bCreate); + if (!name) + { + return (-1); + } + + AUTO_LOCK(m_Mutex); + MEM_ALLOC_CREDIT(); + + int hash = CaseInsensitiveHash(name, m_HashTable.Count()); + int i = 0; + hash_item_t* item = &m_HashTable[hash]; + while (1) + { + if (!stricmp(name, (char*)m_Strings.GetBase() + item->stringIndex)) + { + return (HKeySymbol)item->stringIndex; + } + + i++; + + if (item->next == NULL) + { + if (!bCreate) + { + // not found + return -1; + } + + // we're not in the table + if (item->stringIndex != 0) + { + // first item is used, an new item + item->next = (hash_item_t*)m_HashItemMemPool.Alloc(sizeof(hash_item_t)); + item = item->next; + } + + // build up the new item + item->next = NULL; + const size_t numStringBytes = strlen(name); + char* pString = (char*)m_Strings.Alloc(numStringBytes + 1 + 3); + if (!pString) + { + Error(eDLL_T::COMMON, EXIT_FAILURE, "Out of keyvalue string space"); + return -1; + } + item->stringIndex = pString - (char*)m_Strings.GetBase(); + memcpy(pString, name, numStringBytes); + *reinterpret_cast(pString + numStringBytes) = 0; // string null-terminator + 3 alternative spelling bytes + return (HKeySymbol)item->stringIndex; + } + + item = item->next; + } + + // shouldn't be able to get here + Assert(0); + return (-1); +} + +//----------------------------------------------------------------------------- +// Purpose: symbol table access (used for key names) +//----------------------------------------------------------------------------- +HKeySymbol CKeyValuesSystem::GetSymbolForStringCaseSensitive(HKeySymbol& hCaseInsensitiveSymbol, const char* const name, const bool bCreate) +{ + if (!name) + { + return (-1); + } + + AUTO_LOCK(m_Mutex); + MEM_ALLOC_CREDIT(); + + const int hash = CaseInsensitiveHash(name, m_HashTable.Count()); + + ssize_t numNameStringBytes = -1; + ssize_t i = 0; + hash_item_t* item = &m_HashTable[hash]; + + while (1) + { + char* pCompareString = (char*)m_Strings.GetBase() + item->stringIndex; + const int iResultNegative = _V_stricmp_NegativeForUnequal(name, pCompareString); + if (iResultNegative == 0) + { + // strings are exactly equal matching every letter's case + hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; + return (HKeySymbol)item->stringIndex; + } + else if (iResultNegative > 0) + { + // strings are equal in a case-insensitive compare, but have different case for some letters + // Need to walk the case-resolving chain + numNameStringBytes = Q_strlen(pCompareString); + uint32* pnCaseResolveIndex = reinterpret_cast(pCompareString + numNameStringBytes); + hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; + while (int nAlternativeStringIndex = MEM_4BYTES_FROM_0_AND_3BYTES(*pnCaseResolveIndex)) + { + pCompareString = (char*)m_Strings.GetBase() + nAlternativeStringIndex; + const int iResult = strcmp(name, pCompareString); + if (!iResult) + { + // found an exact match + return (HKeySymbol)nAlternativeStringIndex; + } + // Keep traversing alternative case-resolving chain + pnCaseResolveIndex = reinterpret_cast(pCompareString + numNameStringBytes); + } + // Reached the end of alternative case-resolving chain, pnCaseResolveIndex is pointing at 0 bytes + // indicating no further alternative stringIndex + if (!bCreate) + { + // If we aren't interested in creating the actual string index, + // then return symbol with default capitalization + // NOTE: this is not correct value, but it cannot be used to create a new value anyway, + // only for locating a pre-existing value and lookups are case-insensitive + return (HKeySymbol)item->stringIndex; + } + else + { + char* pString = (char*)m_Strings.Alloc(numNameStringBytes + 1 + 3); + if (!pString) + { + Error(eDLL_T::COMMON, EXIT_FAILURE, "Out of keyvalue string space"); + return -1; + } + int64_t nNewAlternativeStringIndex = pString - (char*)m_Strings.GetBase(); + memcpy(pString, name, numNameStringBytes); + *reinterpret_cast(pString + numNameStringBytes) = 0; // string null-terminator + 3 alternative spelling bytes + *pnCaseResolveIndex = MEM_4BYTES_AS_0_AND_3BYTES(nNewAlternativeStringIndex); // link previous spelling entry to the new entry + return (HKeySymbol)nNewAlternativeStringIndex; + } + } + + i++; + + if (item->next == NULL) + { + if (!bCreate) + { + // not found + return -1; + } + + // we're not in the table + if (item->stringIndex != 0) + { + // first item is used, an new item + item->next = (hash_item_t*)m_HashItemMemPool.Alloc(sizeof(hash_item_t)); + item = item->next; + } + + // build up the new item + item->next = NULL; + size_t numStringBytes = strlen(name); + char* pString = (char*)m_Strings.Alloc(numStringBytes + 1 + 3); + if (!pString) + { + Error(eDLL_T::COMMON, EXIT_FAILURE, "Out of keyvalue string space"); + return -1; + } + item->stringIndex = pString - (char*)m_Strings.GetBase(); + memcpy(pString, name, numStringBytes); + *reinterpret_cast(pString + numStringBytes) = 0; // string null-terminator + 3 alternative spelling bytes + hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; + return (HKeySymbol)item->stringIndex; + } + + item = item->next; + } + + // shouldn't be able to get here + Assert(0); + return (-1); } //----------------------------------------------------------------------------- // Purpose: symbol table access -// Input : symbol - -// Output : symbol string if found, empty if not found //----------------------------------------------------------------------------- -const char* CKeyValuesSystem::GetStringForSymbol(HKeySymbol symbol) +const char* CKeyValuesSystem::GetStringForSymbol(const HKeySymbol symbol) { - const static int index = 4; - return CallVFunc(index, this, symbol); + if (symbol == -1) + { + return ""; + } + return ((char*)m_Strings.GetBase() + (size_t)symbol); } //----------------------------------------------------------------------------- -// Purpose: gets the global KeyValues memory pool -// Output : *g_pKeyValuesMemPool - +// Purpose: adds KeyValues record into global list so we can track memory leaks //----------------------------------------------------------------------------- -void* CKeyValuesSystem::GetMemPool(void) +void CKeyValuesSystem::AddKeyValuesToMemoryLeakList(const void* const pMem, const HKeySymbol name) { - const static int index = 7; - return CallVFunc(index, this); +#ifdef _DEBUG + // only track the memory leaks in debug builds + MemoryLeakTracker_t item = { name, pMem }; + m_KeyValuesTrackingList.Insert(item); +#endif } //----------------------------------------------------------------------------- -// Purpose: set a value for keyvalues resolution symbol +// Purpose: used to track memory leaks +//----------------------------------------------------------------------------- +void CKeyValuesSystem::RemoveKeyValuesFromMemoryLeakList(const void* const pMem) +{ +#ifdef _DEBUG + // only track the memory leaks in debug builds + MemoryLeakTracker_t item = { 0, pMem }; + int index = m_KeyValuesTrackingList.Find(item); + m_KeyValuesTrackingList.RemoveAt(index); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: generates a simple hash value for a string +//----------------------------------------------------------------------------- +int CKeyValuesSystem::CaseInsensitiveHash(const char* const string, const int iBounds) +{ + unsigned int hash = 0; + const char* iter = string; + + for (; *iter != 0; iter++) + { + if (*iter >= 'A' && *iter <= 'Z') + { + hash = (hash << 1) + (*iter - 'A' + 'a'); + } + else + { + hash = (hash << 1) + *iter; + } + } + + return hash % iBounds; +} + +//----------------------------------------------------------------------------- +// Purpose: set/get a value for keyvalues resolution symbol // e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables [$LOWVIOLENCE] -// Input : *szName - -// bValue - //----------------------------------------------------------------------------- -void CKeyValuesSystem::SetKeyValuesExpressionSymbol(const char* szName, bool bValue) +void CKeyValuesSystem::SetKeyValuesExpressionSymbol(const char* name, const bool bValue) { - const static int index = 8; - CallVFunc(index, this, szName, bValue); + if (!name) + return; + + const char* pName = name; + + if (pName[0] == '$') + ++pName; + + HKeySymbol hSym = GetSymbolForString(pName, true); // find or create symbol + { + AUTO_LOCK(m_Mutex); + m_KvConditionalSymbolTable.InsertOrReplace(hSym, bValue); + } } -//----------------------------------------------------------------------------- -// Purpose: get a value for keyvalues resolution symbol -// Input : *szName - -//----------------------------------------------------------------------------- -bool CKeyValuesSystem::GetKeyValuesExpressionSymbol(const char* szName) +bool CKeyValuesSystem::GetKeyValuesExpressionSymbol(const char* const name) { - const static int index = 9; - return CallVFunc(index, this, szName); -} + if (!name) + return false; -//----------------------------------------------------------------------------- -// Purpose: symbol table access (used for key names) -// Input : *hCaseInsensitiveSymbol - -// *szName - -// bCreate - -// Output : handle to KeyValue symbol on success, INVALID_KEY_SYMBOL on failure -//----------------------------------------------------------------------------- -HKeySymbol CKeyValuesSystem::GetSymbolForStringCaseSensitive(HKeySymbol& hCaseInsensitiveSymbol, const char* szName, bool bCreate) -{ - const static int index = 10; - return CallVFunc(index, this, hCaseInsensitiveSymbol, szName, bCreate); + const char* pName = name; + + if (pName[0] == '$') + ++pName; + + const HKeySymbol hSym = GetSymbolForString(pName, false); // find or create symbol + if (hSym != -1) + { + AUTO_LOCK(m_Mutex); + CUtlMap< HKeySymbol, bool >::IndexType_t idx = m_KvConditionalSymbolTable.Find(hSym); + if (idx != m_KvConditionalSymbolTable.InvalidIndex()) + { + // Found the symbol value in conditional symbol table + return m_KvConditionalSymbolTable.Element(idx); + } + } + + // + // Fallback conditionals + // + +// if (!V_stricmp(pName, "GAMECONSOLESPLITSCREEN")) +// { +//#if defined( _GAMECONSOLE ) +// return (XBX_GetNumGameUsers() > 1); +//#else +// return false; +//#endif +// } +// +// if (!V_stricmp(pName, "GAMECONSOLEGUEST")) +// { +//#if defined( _GAMECONSOLE ) +// return (XBX_GetPrimaryUserIsGuest() != 0); +//#else +// return false; +//#endif +// } +// +// if (!V_stricmp(pName, "ENGLISH") || +// !V_stricmp(pName, "JAPANESE") || +// !V_stricmp(pName, "GERMAN") || +// !V_stricmp(pName, "FRENCH") || +// !V_stricmp(pName, "SPANISH") || +// !V_stricmp(pName, "ITALIAN") || +// !V_stricmp(pName, "KOREAN") || +// !V_stricmp(pName, "TCHINESE") || +// !V_stricmp(pName, "PORTUGUESE") || +// !V_stricmp(pName, "SCHINESE") || +// !V_stricmp(pName, "POLISH") || +// !V_stricmp(pName, "RUSSIAN") || +// !V_stricmp(pName, "TURKISH")) +// { +// // the language symbols are true if we are in that language +// // english is assumed when no language is present +// const char* pLanguageString; +//#ifdef _GAMECONSOLE +// pLanguageString = XBX_GetLanguageString(); +//#else +// static ConVarRef cl_language("cl_language"); +// pLanguageString = cl_language.GetString(); +//#endif +// if (!pLanguageString || !pLanguageString[0]) +// { +// pLanguageString = "english"; +// } +// if (!V_stricmp(pName, pLanguageString)) +// { +// return true; +// } +// else +// { +// return false; +// } +// } +// +// // very expensive, back door for DLC updates +// if (!V_strnicmp(pName, "CVAR_", 5)) +// { +// ConVarRef cvRef(name + 5); +// if (cvRef.IsValid()) +// return cvRef.GetBool(); +// } + + // purposely warn on these to prevent syntax errors + // need to get these fixed asap, otherwise unintended false behavior + Warning(eDLL_T::COMMON, "KV Conditional: Unknown symbol %s\n", pName); + return false; } diff --git a/src/vstdlib/keyvaluessystem.h b/src/vstdlib/keyvaluessystem.h index 883299d8..43905ba9 100644 --- a/src/vstdlib/keyvaluessystem.h +++ b/src/vstdlib/keyvaluessystem.h @@ -6,38 +6,93 @@ #include "tier1/utlvector.h" #include "tier1/utlmap.h" +inline void* g_pKeyValuesMemPool = nullptr; + class CKeyValuesSystem : public IKeyValuesSystem// VTABLE @ 0x1413AA1E8 in R5pc_r5launch_N1094_CL456479_2019_10_30_05_20_PM { public: - void RegisterSizeofKeyValues(int64_t nSize); - void* AllocKeyValuesMemory(int64_t nSize); - void FreeKeyValuesMemory(void* pMem); - HKeySymbol GetSymbolForString(const char* szName, bool bCreate = true); - const char* GetStringForSymbol(HKeySymbol symbol); + CKeyValuesSystem(); + ~CKeyValuesSystem(); - void* GetMemPool(void); // GetMemPool returns a global variable called m_pMemPool, it gets modified by AllocKeyValuesMemory and with FreeKeyValuesMemory you can see where to find it in FreeKeyValuesMemory. - void SetKeyValuesExpressionSymbol(const char* szName, bool bValue); - bool GetKeyValuesExpressionSymbol(const char* szName); - HKeySymbol GetSymbolForStringCaseSensitive(HKeySymbol& hCaseInsensitiveSymbol, const char* szName, bool bCreate = true); + // registers the size of the KeyValues in the specified instance + // so it can build a properly sized memory pool for the KeyValues objects + // the sizes will usually never differ but this is for versioning safety + virtual void RegisterSizeofKeyValues(const ssize_t nSize); + + // allocates/frees a KeyValues object from the shared mempool + virtual void* AllocKeyValuesMemory(const ssize_t nSize); + virtual void FreeKeyValuesMemory(void*const pMem); + + // symbol table access (used for key names) + virtual HKeySymbol GetSymbolForString(const char*const szName, const bool bCreate = true); + virtual const char* GetStringForSymbol(const HKeySymbol symbol); + + // for debugging, adds KeyValues record into global list so we can track memory leaks + virtual void AddKeyValuesToMemoryLeakList(const void*const pMem, const HKeySymbol name); + virtual void RemoveKeyValuesFromMemoryLeakList(const void*const pMem); + + // GetMemPool returns a global variable called m_pMemPool, it gets modified by AllocKeyValuesMemory and with FreeKeyValuesMemory you can see where to find it in FreeKeyValuesMemory. + virtual void* GetKeyValuesMemory(void) { return g_pKeyValuesMemPool; } + + // set/get a value for keyvalues resolution symbol + // e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables [$LOWVIOLENCE] + virtual void SetKeyValuesExpressionSymbol(const char*const szName, const bool bValue); + virtual bool GetKeyValuesExpressionSymbol(const char*const szName); + + // symbol table access from code with case-preserving requirements (used for key names) + virtual HKeySymbol GetSymbolForStringCaseSensitive(HKeySymbol& hCaseInsensitiveSymbol, const char*const szName, const bool bCreate = true); private: - int64 m_iMaxKeyValuesSize; + ssize_t m_iMaxKeyValuesSize; + + // string hash table + /* + Here's the way key values system data structures are laid out: + hash table with 2047 hash buckets: + [0] { hash_item_t } + [1] + [2] + ... + each hash_item_t's stringIndex is an offset in m_Strings memory + at that offset we store the actual null-terminated string followed + by another 3 bytes for an alternative capitalization. + These 3 trailing bytes are set to 0 if no alternative capitalization + variants are present in the dictionary. + These trailing 3 bytes are interpreted as stringIndex into m_Strings + memory for the next alternative capitalization + + Getting a string value by HKeySymbol : constant time access at the + string memory represented by stringIndex + + Getting a symbol for a string value: + 1) compute the hash + 2) start walking the hash-bucket using special version of stricmp + until a case insensitive match is found + 3a) for case-insensitive lookup return the found stringIndex + 3b) for case-sensitive lookup keep walking the list of alternative + capitalizations using strcmp until exact case match is found + */ CMemoryStack m_Strings; struct hash_item_t { - int stringIndex; + int64_t stringIndex; hash_item_t* next; }; CUtlMemoryPool m_HashItemMemPool; CUtlVector m_HashTable; + int CaseInsensitiveHash(const char *const string, const int iBounds); struct MemoryLeakTracker_t { - int nameIndex; - void* pMem; + int64_t nameIndex; + const void* pMem; }; + static bool MemoryLeakTrackerLessFunc(const MemoryLeakTracker_t& lhs, const MemoryLeakTracker_t& rhs) + { + return lhs.pMem < rhs.pMem; + } // Unknown less func. void* m_pCompareFunc; @@ -49,13 +104,12 @@ private: }; /* ==== KEYVALUESSYSTEM ================================================================================================================================================= */ -inline void* g_pKeyValuesMemPool = nullptr; inline CKeyValuesSystem* g_pKeyValuesSystem = nullptr; //----------------------------------------------------------------------------- // Instance singleton and expose interface to rest of code //----------------------------------------------------------------------------- -FORCEINLINE CKeyValuesSystem* KeyValuesSystem() +FORCEINLINE IKeyValuesSystem* KeyValuesSystem() { return g_pKeyValuesSystem; }