You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
13 KiB
511 lines
13 KiB
//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#ifndef STRINGPOOL_H |
|
#define STRINGPOOL_H |
|
|
|
#if defined( _WIN32 ) |
|
#pragma once |
|
#endif |
|
|
|
#include "utlrbtree.h" |
|
#include "utlvector.h" |
|
#include "utlbuffer.h" |
|
#include "generichash.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Allocates memory for strings, checking for duplicates first, |
|
// reusing exising strings if duplicate found. |
|
//----------------------------------------------------------------------------- |
|
|
|
enum StringPoolCase_t |
|
{ |
|
StringPoolCaseInsensitive, |
|
StringPoolCaseSensitive |
|
}; |
|
|
|
class CStringPool |
|
{ |
|
public: |
|
CStringPool( StringPoolCase_t caseSensitivity = StringPoolCaseInsensitive ); |
|
~CStringPool(); |
|
|
|
unsigned int Count() const; |
|
|
|
const char * Allocate( const char *pszValue ); |
|
// This feature is deliberately not supported because it's pretty dangerous |
|
// given current uses of CStringPool, which assume they can copy string pointers without |
|
// any refcounts. |
|
//void Free( const char *pszValue ); |
|
void FreeAll(); |
|
|
|
// searches for a string already in the pool |
|
const char * Find( const char *pszValue ); |
|
|
|
protected: |
|
typedef CUtlRBTree<const char *, unsigned short> CStrSet; |
|
|
|
CStrSet m_Strings; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: A reference counted string pool. |
|
// |
|
// Elements are stored more efficiently than in the conventional string pool, |
|
// quicker to look up, and storage is tracked via reference counts. |
|
// |
|
// At some point this should replace CStringPool |
|
//----------------------------------------------------------------------------- |
|
template<class T> |
|
class CCountedStringPoolBase |
|
{ |
|
public: // HACK, hash_item_t structure should not be public. |
|
|
|
struct hash_item_t |
|
{ |
|
char* pString; |
|
T nNextElement; |
|
unsigned char nReferenceCount; |
|
unsigned char pad; |
|
}; |
|
|
|
enum |
|
{ |
|
INVALID_ELEMENT = 0, |
|
MAX_REFERENCE = 0xFF, |
|
HASH_TABLE_SIZE = 1024 |
|
}; |
|
|
|
CUtlVector<T> m_HashTable; // Points to each element |
|
CUtlVector<hash_item_t> m_Elements; |
|
T m_FreeListStart; |
|
StringPoolCase_t m_caseSensitivity; |
|
|
|
public: |
|
CCountedStringPoolBase( StringPoolCase_t caseSensitivity = StringPoolCaseInsensitive ); |
|
virtual ~CCountedStringPoolBase(); |
|
|
|
void FreeAll(); |
|
|
|
char *FindString( const char* pIntrinsic ); |
|
char *ReferenceString( const char* pIntrinsic ); |
|
void DereferenceString( const char* pIntrinsic ); |
|
|
|
// These are only reliable if there are less than 64k strings in your string pool |
|
T FindStringHandle( const char* pIntrinsic ); |
|
T ReferenceStringHandle( const char* pIntrinsic ); |
|
char *HandleToString( T handle ); |
|
void SpewStrings(); |
|
unsigned Hash( const char *pszKey ); |
|
|
|
bool SaveToBuffer( CUtlBuffer &buffer ); |
|
bool RestoreFromBuffer( CUtlBuffer &buffer ); |
|
|
|
// Debug helper method to validate that we didn't overflow |
|
void VerifyNotOverflowed( unsigned int value ); |
|
}; |
|
|
|
typedef CCountedStringPoolBase<unsigned short> CCountedStringPool; |
|
|
|
template<class T> |
|
inline CCountedStringPoolBase<T>::CCountedStringPoolBase( StringPoolCase_t caseSensitivity ) |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
m_HashTable.EnsureCount(HASH_TABLE_SIZE); |
|
|
|
for( int i = 0; i < m_HashTable.Count(); i++ ) |
|
{ |
|
m_HashTable[i] = INVALID_ELEMENT; |
|
} |
|
|
|
m_FreeListStart = INVALID_ELEMENT; |
|
m_Elements.AddToTail(); |
|
m_Elements[0].pString = NULL; |
|
m_Elements[0].nReferenceCount = 0; |
|
m_Elements[0].nNextElement = INVALID_ELEMENT; |
|
|
|
m_caseSensitivity = caseSensitivity; |
|
} |
|
|
|
template<class T> |
|
inline CCountedStringPoolBase<T>::~CCountedStringPoolBase() |
|
{ |
|
FreeAll(); |
|
} |
|
|
|
template<class T> |
|
inline void CCountedStringPoolBase<T>::FreeAll() |
|
{ |
|
int i; |
|
|
|
// Reset the hash table: |
|
for( i = 0; i < m_HashTable.Count(); i++ ) |
|
{ |
|
m_HashTable[i] = INVALID_ELEMENT; |
|
} |
|
|
|
// Blow away the free list: |
|
m_FreeListStart = INVALID_ELEMENT; |
|
|
|
for( i = 0; i < m_Elements.Count(); i++ ) |
|
{ |
|
if( m_Elements[i].pString ) |
|
{ |
|
delete [] m_Elements[i].pString; |
|
m_Elements[i].pString = NULL; |
|
m_Elements[i].nReferenceCount = 0; |
|
m_Elements[i].nNextElement = INVALID_ELEMENT; |
|
} |
|
} |
|
|
|
// Remove all but the invalid element: |
|
m_Elements.RemoveAll(); |
|
m_Elements.AddToTail(); |
|
m_Elements[0].pString = NULL; |
|
m_Elements[0].nReferenceCount = 0; |
|
m_Elements[0].nNextElement = INVALID_ELEMENT; |
|
} |
|
|
|
template<class T> |
|
inline unsigned CCountedStringPoolBase<T>::Hash( const char *pszKey ) |
|
{ |
|
if ( m_caseSensitivity == StringPoolCaseInsensitive ) |
|
{ |
|
return HashStringCaseless( pszKey ); |
|
} |
|
return HashString( pszKey ); |
|
} |
|
|
|
template<class T> |
|
inline T CCountedStringPoolBase<T>::FindStringHandle( const char* pIntrinsic ) |
|
{ |
|
if( pIntrinsic == NULL ) |
|
return INVALID_ELEMENT; |
|
|
|
T nHashBucketIndex = ( Hash( pIntrinsic ) %HASH_TABLE_SIZE); |
|
T nCurrentBucket = m_HashTable[ nHashBucketIndex ]; |
|
|
|
// Does the bucket already exist? |
|
if( nCurrentBucket != INVALID_ELEMENT ) |
|
{ |
|
for( ; nCurrentBucket != INVALID_ELEMENT ; nCurrentBucket = m_Elements[nCurrentBucket].nNextElement ) |
|
{ |
|
if( !Q_stricmp( pIntrinsic, m_Elements[nCurrentBucket].pString ) ) |
|
{ |
|
return nCurrentBucket; |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
|
|
} |
|
|
|
template<class T> |
|
inline char* CCountedStringPoolBase<T>::FindString( const char* pIntrinsic ) |
|
{ |
|
if( pIntrinsic == NULL ) |
|
return NULL; |
|
|
|
// Yes, this will be NULL on failure. |
|
return m_Elements[FindStringHandle(pIntrinsic)].pString; |
|
} |
|
|
|
template<class T> |
|
inline T CCountedStringPoolBase<T>::ReferenceStringHandle( const char* pIntrinsic ) |
|
{ |
|
if( pIntrinsic == NULL ) |
|
return INVALID_ELEMENT; |
|
|
|
T nHashBucketIndex = ( Hash( pIntrinsic ) % HASH_TABLE_SIZE); |
|
T nCurrentBucket = m_HashTable[ nHashBucketIndex ]; |
|
|
|
// Does the bucket already exist? |
|
if( nCurrentBucket != INVALID_ELEMENT ) |
|
{ |
|
for( ; nCurrentBucket != INVALID_ELEMENT ; nCurrentBucket = m_Elements[nCurrentBucket].nNextElement ) |
|
{ |
|
if( !Q_stricmp( pIntrinsic, m_Elements[nCurrentBucket].pString ) ) |
|
{ |
|
// Anyone who hits 65k references is permanant |
|
if( m_Elements[nCurrentBucket].nReferenceCount < MAX_REFERENCE ) |
|
{ |
|
m_Elements[nCurrentBucket].nReferenceCount ++ ; |
|
} |
|
return nCurrentBucket; |
|
} |
|
} |
|
} |
|
|
|
if( m_FreeListStart != INVALID_ELEMENT ) |
|
{ |
|
nCurrentBucket = m_FreeListStart; |
|
m_FreeListStart = m_Elements[nCurrentBucket].nNextElement; |
|
} |
|
else |
|
{ |
|
unsigned int newElement = m_Elements.AddToTail(); |
|
VerifyNotOverflowed( newElement ); |
|
nCurrentBucket = newElement; |
|
} |
|
|
|
m_Elements[nCurrentBucket].nReferenceCount = 1; |
|
|
|
// Insert at the beginning of the bucket: |
|
m_Elements[nCurrentBucket].nNextElement = m_HashTable[ nHashBucketIndex ]; |
|
m_HashTable[ nHashBucketIndex ] = nCurrentBucket; |
|
|
|
m_Elements[nCurrentBucket].pString = new char[Q_strlen( pIntrinsic ) + 1]; |
|
Q_strcpy( m_Elements[nCurrentBucket].pString, pIntrinsic ); |
|
|
|
return nCurrentBucket; |
|
} |
|
|
|
template<> |
|
inline void CCountedStringPoolBase<unsigned short>::VerifyNotOverflowed( unsigned int value ) { Assert( value < 0xffff ); } |
|
|
|
template<> |
|
inline void CCountedStringPoolBase<unsigned int>::VerifyNotOverflowed( unsigned int value ) {} |
|
|
|
template<class T> |
|
inline char* CCountedStringPoolBase<T>::ReferenceString( const char* pIntrinsic ) |
|
{ |
|
if(!pIntrinsic) |
|
return NULL; |
|
|
|
return m_Elements[ReferenceStringHandle( pIntrinsic)].pString; |
|
} |
|
|
|
template<class T> |
|
inline void CCountedStringPoolBase<T>::DereferenceString( const char* pIntrinsic ) |
|
{ |
|
// If we get a NULL pointer, just return |
|
if (!pIntrinsic) |
|
return; |
|
|
|
T nHashBucketIndex = (Hash( pIntrinsic ) % m_HashTable.Count()); |
|
T nCurrentBucket = m_HashTable[ nHashBucketIndex ]; |
|
|
|
// If there isn't anything in the bucket, just return. |
|
if ( nCurrentBucket == INVALID_ELEMENT ) |
|
return; |
|
|
|
for( T previous = INVALID_ELEMENT; nCurrentBucket != INVALID_ELEMENT ; nCurrentBucket = m_Elements[nCurrentBucket].nNextElement ) |
|
{ |
|
if( !Q_stricmp( pIntrinsic, m_Elements[nCurrentBucket].pString ) ) |
|
{ |
|
// Anyone who hits 65k references is permanant |
|
if( m_Elements[nCurrentBucket].nReferenceCount < MAX_REFERENCE ) |
|
{ |
|
m_Elements[nCurrentBucket].nReferenceCount --; |
|
} |
|
|
|
if( m_Elements[nCurrentBucket].nReferenceCount == 0 ) |
|
{ |
|
if( previous == INVALID_ELEMENT ) |
|
{ |
|
m_HashTable[nHashBucketIndex] = m_Elements[nCurrentBucket].nNextElement; |
|
} |
|
else |
|
{ |
|
m_Elements[previous].nNextElement = m_Elements[nCurrentBucket].nNextElement; |
|
} |
|
|
|
delete [] m_Elements[nCurrentBucket].pString; |
|
m_Elements[nCurrentBucket].pString = NULL; |
|
m_Elements[nCurrentBucket].nReferenceCount = 0; |
|
|
|
m_Elements[nCurrentBucket].nNextElement = m_FreeListStart; |
|
m_FreeListStart = nCurrentBucket; |
|
break; |
|
|
|
} |
|
} |
|
|
|
previous = nCurrentBucket; |
|
} |
|
} |
|
|
|
template<class T> |
|
inline char* CCountedStringPoolBase<T>::HandleToString( T handle ) |
|
{ |
|
return m_Elements[handle].pString; |
|
} |
|
|
|
template<class T> |
|
inline void CCountedStringPoolBase<T>::SpewStrings() |
|
{ |
|
int i; |
|
for ( i = 0; i < m_Elements.Count(); i++ ) |
|
{ |
|
char* string = m_Elements[i].pString; |
|
|
|
Msg("String %d: ref:%d %s\n", i, m_Elements[i].nReferenceCount, string == NULL? "EMPTY - ok for slot zero only!" : string); |
|
} |
|
|
|
Msg("\n%d total counted strings.", m_Elements.Count()); |
|
} |
|
|
|
#define STRING_POOL_VERSION MAKEID( 'C', 'S', 'P', '1' ) |
|
#define MAX_STRING_SAVE 1024 |
|
|
|
template<> |
|
inline bool CCountedStringPoolBase<unsigned short>::SaveToBuffer( CUtlBuffer &buffer ) |
|
{ |
|
if ( m_Elements.Count() <= 1 ) |
|
{ |
|
// pool is empty, saving nothing |
|
// caller can check put position of buffer to detect |
|
return true; |
|
} |
|
|
|
// signature/version |
|
buffer.PutInt( STRING_POOL_VERSION ); |
|
|
|
buffer.PutUnsignedShort( m_FreeListStart ); |
|
|
|
buffer.PutInt( m_HashTable.Count() ); |
|
for ( int i = 0; i < m_HashTable.Count(); i++ ) |
|
{ |
|
buffer.PutUnsignedShort( m_HashTable[i] ); |
|
} |
|
|
|
buffer.PutInt( m_Elements.Count() ); |
|
for ( int i = 1; i < m_Elements.Count(); i++ ) |
|
{ |
|
buffer.PutUnsignedShort( m_Elements[i].nNextElement ); |
|
buffer.PutUnsignedChar( m_Elements[i].nReferenceCount ); |
|
|
|
const char *pString = m_Elements[i].pString; |
|
if ( strlen( pString ) >= MAX_STRING_SAVE ) |
|
{ |
|
return false; |
|
} |
|
buffer.PutString( pString ? pString : "" ); |
|
} |
|
|
|
return buffer.IsValid(); |
|
} |
|
|
|
template<> |
|
inline bool CCountedStringPoolBase<unsigned short>::RestoreFromBuffer( CUtlBuffer &buffer ) |
|
{ |
|
int signature = buffer.GetInt(); |
|
if ( signature != STRING_POOL_VERSION ) |
|
{ |
|
// wrong version |
|
return false; |
|
} |
|
|
|
FreeAll(); |
|
|
|
m_FreeListStart = buffer.GetUnsignedShort(); |
|
|
|
int hashCount = buffer.GetInt(); |
|
m_HashTable.SetCount( hashCount ); |
|
|
|
for ( int i = 0; i < hashCount; i++ ) |
|
{ |
|
m_HashTable[i] = buffer.GetUnsignedShort(); |
|
} |
|
|
|
int tableCount = buffer.GetInt(); |
|
if ( tableCount > 1 ) |
|
{ |
|
m_Elements.AddMultipleToTail( tableCount-1 ); |
|
} |
|
|
|
char tempString[MAX_STRING_SAVE]; |
|
for ( int i = 1; i < tableCount; i++ ) |
|
{ |
|
m_Elements[i].nNextElement = buffer.GetUnsignedShort(); |
|
m_Elements[i].nReferenceCount = buffer.GetUnsignedChar(); |
|
buffer.GetString( tempString, sizeof( tempString ) ); |
|
m_Elements[i].pString = strdup( tempString ); |
|
} |
|
|
|
return buffer.IsValid(); |
|
} |
|
|
|
template<> |
|
inline bool CCountedStringPoolBase<unsigned int>::SaveToBuffer( CUtlBuffer &buffer ) |
|
{ |
|
if ( m_Elements.Count() <= 1 ) |
|
{ |
|
// pool is empty, saving nothing |
|
// caller can check put position of buffer to detect |
|
return true; |
|
} |
|
|
|
// signature/version |
|
buffer.PutInt( STRING_POOL_VERSION ); |
|
|
|
buffer.PutUnsignedInt( m_FreeListStart ); |
|
|
|
buffer.PutInt( m_HashTable.Count() ); |
|
for ( int i = 0; i < m_HashTable.Count(); i++ ) |
|
{ |
|
buffer.PutUnsignedInt( m_HashTable[i] ); |
|
} |
|
|
|
buffer.PutInt( m_Elements.Count() ); |
|
for ( int i = 1; i < m_Elements.Count(); i++ ) |
|
{ |
|
buffer.PutUnsignedInt( m_Elements[i].nNextElement ); |
|
buffer.PutUnsignedChar( m_Elements[i].nReferenceCount ); |
|
|
|
const char *pString = m_Elements[i].pString; |
|
if ( strlen( pString ) >= MAX_STRING_SAVE ) |
|
{ |
|
return false; |
|
} |
|
buffer.PutString( pString ? pString : "" ); |
|
} |
|
|
|
return buffer.IsValid(); |
|
} |
|
|
|
template<> |
|
inline bool CCountedStringPoolBase<unsigned int>::RestoreFromBuffer( CUtlBuffer &buffer ) |
|
{ |
|
int signature = buffer.GetInt(); |
|
if ( signature != STRING_POOL_VERSION ) |
|
{ |
|
// wrong version |
|
return false; |
|
} |
|
|
|
FreeAll(); |
|
|
|
m_FreeListStart = buffer.GetUnsignedInt(); |
|
|
|
int hashCount = buffer.GetInt(); |
|
m_HashTable.SetCount( hashCount ); |
|
|
|
for ( int i = 0; i < hashCount; i++ ) |
|
{ |
|
m_HashTable[i] = buffer.GetUnsignedInt(); |
|
} |
|
|
|
int tableCount = buffer.GetInt(); |
|
if ( tableCount > 1 ) |
|
{ |
|
m_Elements.AddMultipleToTail( tableCount-1 ); |
|
} |
|
|
|
char tempString[MAX_STRING_SAVE]; |
|
for ( int i = 1; i < tableCount; i++ ) |
|
{ |
|
m_Elements[i].nNextElement = buffer.GetUnsignedInt(); |
|
m_Elements[i].nReferenceCount = buffer.GetUnsignedChar(); |
|
buffer.GetString( tempString, sizeof( tempString ) ); |
|
m_Elements[i].pString = strdup( tempString ); |
|
} |
|
|
|
return buffer.IsValid(); |
|
} |
|
#endif // STRINGPOOL_H
|
|
|