csgo-2018-source/vstdlib/keyvaluessystem.cpp
2021-07-24 21:11:47 -07:00

621 lines
19 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include <vstdlib/ikeyvaluessystem.h>
#include <keyvalues.h>
#include "tier1/mempool.h"
#include "utlsymbol.h"
#include "utlmap.h"
#include "tier0/threadtools.h"
#include "tier1/memstack.h"
#include "tier1/convar.h"
#ifdef _PS3
#include "ps3/ps3_core.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
#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: Central storage point for KeyValues memory and symbols
//-----------------------------------------------------------------------------
class CKeyValuesSystem : public IKeyValuesSystem
{
public:
CKeyValuesSystem();
~CKeyValuesSystem();
// 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
void RegisterSizeofKeyValues(int size);
// allocates/frees a KeyValues object from the shared mempool
void *AllocKeyValuesMemory(int size);
void FreeKeyValuesMemory(void *pMem);
// symbol table access (used for key names)
HKeySymbol GetSymbolForString( const char *name, bool bCreate );
const char *GetStringForSymbol(HKeySymbol symbol);
// returns the wide version of ansi, also does the lookup on #'d strings
void GetLocalizedFromANSI( const char *ansi, wchar_t *outBuf, int unicodeBufferSizeInBytes);
void GetANSIFromLocalized( const wchar_t *wchar, char *outBuf, int ansiBufferSizeInBytes );
// for debugging, adds KeyValues record into global list so we can track memory leaks
virtual void AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name);
virtual void RemoveKeyValuesFromMemoryLeakList(void *pMem);
// set/get a value for keyvalues resolution symbol
// e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables [$LOWVIOLENCE]
virtual void SetKeyValuesExpressionSymbol( const char *name, bool bValue );
virtual bool GetKeyValuesExpressionSymbol( const char *name );
// symbol table access from code with case-preserving requirements (used for key names)
virtual HKeySymbol GetSymbolForStringCaseSensitive( HKeySymbol &hCaseInsensitiveSymbol, const char *name, bool bCreate = true );
private:
#ifdef KEYVALUES_USE_POOL
CUtlMemoryPool *m_pMemPool;
#endif
int 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;
hash_item_t *next;
};
CUtlMemoryPool m_HashItemMemPool;
CUtlVector<hash_item_t> m_HashTable;
int CaseInsensitiveHash(const char *string, int iBounds);
struct MemoryLeakTracker_t
{
int nameIndex;
void *pMem;
};
static bool MemoryLeakTrackerLessFunc( const MemoryLeakTracker_t &lhs, const MemoryLeakTracker_t &rhs )
{
return lhs.pMem < rhs.pMem;
}
CUtlRBTree<MemoryLeakTracker_t, int> m_KeyValuesTrackingList;
CUtlMap< HKeySymbol, bool > m_KvConditionalSymbolTable;
CThreadFastMutex m_mutex;
};
// EXPOSE_SINGLE_INTERFACE(CKeyValuesSystem, IKeyValuesSystem, KEYVALUES_INTERFACE_VERSION);
//-----------------------------------------------------------------------------
// Instance singleton and expose interface to rest of code
//-----------------------------------------------------------------------------
static CKeyValuesSystem g_KeyValuesSystem;
IKeyValuesSystem *KeyValuesSystem()
{
return &g_KeyValuesSystem;
}
//-----------------------------------------------------------------------------
// 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
//-----------------------------------------------------------------------------
void CKeyValuesSystem::RegisterSizeofKeyValues(int size)
{
if (size > m_iMaxKeyValuesSize)
{
m_iMaxKeyValuesSize = size;
}
}
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( "%s", data );
}
//-----------------------------------------------------------------------------
// Purpose: allocates a KeyValues object from the shared mempool
//-----------------------------------------------------------------------------
void *CKeyValuesSystem::AllocKeyValuesMemory(int size)
{
#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
//-----------------------------------------------------------------------------
void CKeyValuesSystem::FreeKeyValuesMemory(void *pMem)
{
#ifdef KEYVALUES_USE_POOL
m_pMemPool->Free(pMem);
#else
free( pMem );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: symbol table access (used for key names)
//-----------------------------------------------------------------------------
HKeySymbol CKeyValuesSystem::GetSymbolForString( const char *name, bool 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;
int numStringBytes = strlen(name);
char *pString = (char *)m_Strings.Alloc( numStringBytes + 1 + 3 );
if ( !pString )
{
Error( "Out of keyvalue string space" );
return -1;
}
item->stringIndex = pString - (char *)m_Strings.GetBase();
Q_memcpy( pString, name, numStringBytes );
* reinterpret_cast< uint32 * >( 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 *name, bool bCreate )
{
if ( !name )
{
return (-1);
}
AUTO_LOCK( m_mutex );
MEM_ALLOC_CREDIT();
int hash = CaseInsensitiveHash(name, m_HashTable.Count());
int numNameStringBytes = -1;
int i = 0;
hash_item_t *item = &m_HashTable[hash];
while (1)
{
char *pCompareString = (char *)m_Strings.GetBase() + item->stringIndex;
int iResult = _V_stricmp_NegativeForUnequal( name, pCompareString );
if ( iResult == 0 )
{
// strings are exactly equal matching every letter's case
hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex;
return (HKeySymbol)item->stringIndex;
}
else if ( iResult > 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< uint32 * >( pCompareString + numNameStringBytes );
hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex;
while ( int nAlternativeStringIndex = MEM_4BYTES_FROM_0_AND_3BYTES( *pnCaseResolveIndex ) )
{
pCompareString = (char *)m_Strings.GetBase() + nAlternativeStringIndex;
int iResult = strcmp( name, pCompareString );
if ( !iResult )
{
// found an exact match
return (HKeySymbol)nAlternativeStringIndex;
}
// Keep traversing alternative case-resolving chain
pnCaseResolveIndex = reinterpret_cast< uint32 * >( 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( "Out of keyvalue string space" );
return -1;
}
int nNewAlternativeStringIndex = pString - (char *)m_Strings.GetBase();
Q_memcpy( pString, name, numNameStringBytes );
* reinterpret_cast< uint32 * >( 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;
int numStringBytes = strlen(name);
char *pString = (char *)m_Strings.Alloc( numStringBytes + 1 + 3 );
if ( !pString )
{
Error( "Out of keyvalue string space" );
return -1;
}
item->stringIndex = pString - (char *)m_Strings.GetBase();
Q_memcpy( pString, name, numStringBytes );
* reinterpret_cast< uint32 * >( 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
//-----------------------------------------------------------------------------
const char *CKeyValuesSystem::GetStringForSymbol(HKeySymbol symbol)
{
if ( symbol == -1 )
{
return "";
}
return ((char *)m_Strings.GetBase() + (size_t)symbol);
}
//-----------------------------------------------------------------------------
// Purpose: adds KeyValues record into global list so we can track memory leaks
//-----------------------------------------------------------------------------
void CKeyValuesSystem::AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name)
{
#ifdef _DEBUG
// only track the memory leaks in debug builds
MemoryLeakTracker_t item = { name, pMem };
m_KeyValuesTrackingList.Insert(item);
#endif
}
//-----------------------------------------------------------------------------
// Purpose: used to track memory leaks
//-----------------------------------------------------------------------------
void CKeyValuesSystem::RemoveKeyValuesFromMemoryLeakList(void *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 *string, int iBounds)
{
unsigned int hash = 0;
for ( ; *string != 0; string++ )
{
if (*string >= 'A' && *string <= 'Z')
{
hash = (hash << 1) + (*string - 'A' + 'a');
}
else
{
hash = (hash << 1) + *string;
}
}
return hash % iBounds;
}
//-----------------------------------------------------------------------------
// Purpose: set/get a value for keyvalues resolution symbol
// e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables [$LOWVIOLENCE]
//-----------------------------------------------------------------------------
void CKeyValuesSystem::SetKeyValuesExpressionSymbol( const char *name, bool bValue )
{
if ( !name )
return;
if ( name[0] == '$' )
++ name;
HKeySymbol hSym = GetSymbolForString( name, true ); // find or create symbol
{
AUTO_LOCK( m_mutex );
m_KvConditionalSymbolTable.InsertOrReplace( hSym, bValue );
}
}
bool CKeyValuesSystem::GetKeyValuesExpressionSymbol( const char *name )
{
if ( !name )
return false;
if ( name[0] == '$' )
++ name;
HKeySymbol hSym = GetSymbolForString( name, 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( name, "GAMECONSOLESPLITSCREEN" ) )
{
#if defined( _GAMECONSOLE )
return ( XBX_GetNumGameUsers() > 1 );
#else
return false;
#endif
}
if ( !V_stricmp( name, "GAMECONSOLEGUEST" ) )
{
#if defined( _GAMECONSOLE )
return ( XBX_GetPrimaryUserIsGuest() != 0 );
#else
return false;
#endif
}
if ( !V_stricmp( name, "ENGLISH" ) ||
!V_stricmp( name, "JAPANESE" ) ||
!V_stricmp( name, "GERMAN" ) ||
!V_stricmp( name, "FRENCH" ) ||
!V_stricmp( name, "SPANISH" ) ||
!V_stricmp( name, "ITALIAN" ) ||
!V_stricmp( name, "KOREAN" ) ||
!V_stricmp( name, "TCHINESE" ) ||
!V_stricmp( name, "PORTUGUESE" ) ||
!V_stricmp( name, "SCHINESE" ) ||
!V_stricmp( name, "POLISH" ) ||
!V_stricmp( name, "RUSSIAN" ) ||
!V_stricmp( name, "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( name, pLanguageString ) )
{
return true;
}
else
{
return false;
}
}
// very expensive, back door for DLC updates
if ( !V_strnicmp( name, "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( "KV Conditional: Unknown symbol %s\n", name );
return false;
}