|
|
//========= 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); V_vsnprintf(data, sizeof( data ), fmt, argptr); va_end(argptr);
Msg( 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(); V_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 = V_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(); V_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(); V_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; }
|