2020-04-23 00:56:21 +08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
# include <vstdlib/IKeyValuesSystem.h>
# include <KeyValues.h>
# include "mempool.h"
# include "utlsymbol.h"
# include "tier0/threadtools.h"
# include "tier1/memstack.h"
# include "tier1/utlmap.h"
# include "tier1/utlstring.h"
# include "tier1/fmtstr.h"
// 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
//-----------------------------------------------------------------------------
// 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 ) ;
// maintain a cache of KeyValues we load from disk. This saves us quite a lot of time on app startup.
virtual void AddFileKeyValuesToCache ( const KeyValues * _kv , const char * resourceName , const char * pathID ) ;
virtual bool LoadFileKeyValuesFromCache ( KeyValues * outKv , const char * resourceName , const char * pathID , IBaseFileSystem * filesystem ) const ;
virtual void InvalidateCache ( ) ;
virtual void InvalidateCacheForFile ( const char * resourceName , const char * pathID ) ;
private :
# ifdef KEYVALUES_USE_POOL
CUtlMemoryPool * m_pMemPool ;
# endif
int m_iMaxKeyValuesSize ;
// string hash table
CMemoryStack m_Strings ;
struct hash_item_t
{
2022-02-23 19:50:30 +08:00
intp stringIndex ;
2020-04-23 00:56:21 +08:00
hash_item_t * next ;
} ;
CUtlMemoryPool m_HashItemMemPool ;
CUtlVector < hash_item_t > m_HashTable ;
int CaseInsensitiveHash ( const char * string , int iBounds ) ;
void DoInvalidateCache ( ) ;
struct MemoryLeakTracker_t
{
2022-02-23 19:50:30 +08:00
intp nameIndex ;
2020-04-23 00:56:21 +08:00
void * pMem ;
} ;
static bool MemoryLeakTrackerLessFunc ( const MemoryLeakTracker_t & lhs , const MemoryLeakTracker_t & rhs )
{
return lhs . pMem < rhs . pMem ;
}
CUtlRBTree < MemoryLeakTracker_t , int > m_KeyValuesTrackingList ;
CThreadFastMutex m_mutex ;
CUtlMap < CUtlString , KeyValues * > m_KeyValueCache ;
} ;
// 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
//-----------------------------------------------------------------------------
2022-06-14 18:09:10 +08:00
CKeyValuesSystem : : CKeyValuesSystem ( )
: m_HashItemMemPool ( sizeof ( hash_item_t ) , 64 , CUtlMemoryPool : : GROW_FAST , " CKeyValuesSystem::m_HashItemMemPool " )
2020-04-23 00:56:21 +08:00
, m_KeyValuesTrackingList ( 0 , 0 , MemoryLeakTrackerLessFunc )
, m_KeyValueCache ( UtlStringLessFunc )
{
// 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 ( 4 * 1024 * 1024 , 64 * 1024 , 0 , 4 ) ;
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 ( " \t leaked KeyValues(%s) \n " , & m_Strings [ m_KeyValuesTrackingList [ i ] . nameIndex ] ) ;
}
}
# endif
delete m_pMemPool ;
# endif
DoInvalidateCache ( ) ;
}
//-----------------------------------------------------------------------------
// 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 ;
}
}
# 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 ( " %s " , data ) ;
}
# endif
//-----------------------------------------------------------------------------
// 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 , UTLMEMORYPOOL_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 ) ;
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 ;
char * pString = ( char * ) m_Strings . Alloc ( V_strlen ( name ) + 1 ) ;
if ( ! pString )
{
Error ( " Out of keyvalue string space " ) ;
return - 1 ;
}
item - > stringIndex = pString - ( char * ) m_Strings . GetBase ( ) ;
strcpy ( pString , name ) ;
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: Removes a particular key value file (from a particular source) from the cache if present.
//-----------------------------------------------------------------------------
void CKeyValuesSystem : : InvalidateCacheForFile ( const char * resourceName , const char * pathID )
{
CUtlString identString ( CFmtStr ( " %s::%s " , resourceName ? resourceName : " " , pathID ? pathID : " " ) ) ;
CUtlMap < CUtlString , KeyValues * > : : IndexType_t index = m_KeyValueCache . Find ( identString ) ;
if ( m_KeyValueCache . IsValidIndex ( index ) )
{
m_KeyValueCache [ index ] - > deleteThis ( ) ;
m_KeyValueCache . RemoveAt ( index ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Adds a particular key value file (from a particular source) to the cache if not already present.
//-----------------------------------------------------------------------------
void CKeyValuesSystem : : AddFileKeyValuesToCache ( const KeyValues * _kv , const char * resourceName , const char * pathID )
{
CUtlString identString ( CFmtStr ( " %s::%s " , resourceName ? resourceName : " " , pathID ? pathID : " " ) ) ;
// Some files actually have multiple roots, and if you use regular MakeCopy (without passing true), those
// will be missed. This caused a bug in soundscapes on dedicated servers.
m_KeyValueCache . Insert ( identString , _kv - > MakeCopy ( true ) ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Fetches a particular keyvalue from the cache, and copies into _outKv (clearing anything there already).
//-----------------------------------------------------------------------------
bool CKeyValuesSystem : : LoadFileKeyValuesFromCache ( KeyValues * outKv , const char * resourceName , const char * pathID , IBaseFileSystem * filesystem ) const
{
Assert ( outKv ) ;
Assert ( resourceName ) ;
COM_TimestampedLog ( " CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): Begin " , pathID ? pathID : " " , pathID & & resourceName ? " / " : " " , resourceName ? resourceName : " " ) ;
CUtlString identString ( CFmtStr ( " %s::%s " , resourceName ? resourceName : " " , pathID ? pathID : " " ) ) ;
CUtlMap < CUtlString , KeyValues * > : : IndexType_t index = m_KeyValueCache . Find ( identString ) ;
if ( m_KeyValueCache . IsValidIndex ( index ) ) {
( * outKv ) = ( * m_KeyValueCache [ index ] ) ;
COM_TimestampedLog ( " CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): End / Hit " , pathID ? pathID : " " , pathID & & resourceName ? " / " : " " , resourceName ? resourceName : " " ) ;
return true ;
}
COM_TimestampedLog ( " CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): End / Miss " , pathID ? pathID : " " , pathID & & resourceName ? " / " : " " , resourceName ? resourceName : " " ) ;
return false ;
}
//-----------------------------------------------------------------------------
// Purpose: Evicts everything from the cache, cleans up the memory used.
//-----------------------------------------------------------------------------
void CKeyValuesSystem : : InvalidateCache ( )
{
DoInvalidateCache ( ) ;
}
//-----------------------------------------------------------------------------
// 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: Evicts everything from the cache, cleans up the memory used.
//-----------------------------------------------------------------------------
void CKeyValuesSystem : : DoInvalidateCache ( )
{
// Cleanup the cache.
FOR_EACH_MAP_FAST ( m_KeyValueCache , mapIndex )
{
m_KeyValueCache [ mapIndex ] - > deleteThis ( ) ;
}
// Apparently you cannot call RemoveAll on a map without also purging the contents because... ?
// If you do and you continue to use the map, you will eventually wind up in a case where you
// have an empty map but it still iterates over elements. Awesome?
m_KeyValueCache . Purge ( ) ;
}