|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "host.h"
#include "sysexternal.h"
#include "networkstringtable.h"
#include "utlbuffer.h"
#include "bitbuf.h"
#include "netmessages.h"
#include "net.h"
#include "filesystem_engine.h"
#include "baseclient.h"
#include "vprof.h"
#include <tier1/utlstring.h>
#include <tier1/utlhashtable.h>
#include <tier0/etwprof.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar sv_dumpstringtables( "sv_dumpstringtables", "0", FCVAR_CHEAT ); ConVar sv_compressstringtablebaselines_threshhold( "sv_compressstringtablebaselines_threshold", "2048", 0, "Minimum size (in bytes) for stringtablebaseline buffer to be compressed." );
#define SUBSTRING_BITS 5
struct StringHistoryEntry { char string[ (1<<SUBSTRING_BITS) ]; };
static int CountSimilarCharacters( char const *str1, char const *str2 ) { int c = 0; while ( *str1 && *str2 && *str1 == *str2 && c < ((1<<SUBSTRING_BITS) -1 )) { str1++; str2++; c++; }
return c; }
static int GetBestPreviousString( CUtlVector< StringHistoryEntry >& history, char const *newstring, int& substringsize ) { int bestindex = -1; int bestcount = 0; int c = history.Count(); for ( int i = 0; i < c; i++ ) { char const *prev = history[ i ].string; int similar = CountSimilarCharacters( prev, newstring ); if ( similar < 3 ) continue;
if ( similar > bestcount ) { bestcount = similar; bestindex = i; } }
substringsize = bestcount; return bestindex; }
bool CNetworkStringTable_LessFunc( FileNameHandle_t const &a, FileNameHandle_t const &b ) { return a < b; }
//-----------------------------------------------------------------------------
// Implementation when dictionary strings are filenames
//-----------------------------------------------------------------------------
class CNetworkStringFilenameDict : public INetworkStringDict { public: CNetworkStringFilenameDict() { m_Items.SetLessFunc( CNetworkStringTable_LessFunc ); }
virtual ~CNetworkStringFilenameDict() { Purge(); }
unsigned int Count() { return m_Items.Count(); }
void Purge() { m_Items.Purge(); }
const char *String( int index ) { char* pString = tmpstr512(); g_pFileSystem->String( m_Items.Key( index ), pString, 512 ); return pString; }
bool IsValidIndex( int index ) { return m_Items.IsValidIndex( index ); }
int Insert( const char *pString ) { FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pString ); return m_Items.Insert( fnHandle ); }
int Find( const char *pString ) { FileNameHandle_t fnHandle = g_pFileSystem->FindFileName( pString ); if ( !fnHandle ) return m_Items.InvalidIndex(); return m_Items.Find( fnHandle ); }
CNetworkStringTableItem &Element( int index ) { return m_Items.Element( index ); }
const CNetworkStringTableItem &Element( int index ) const { return m_Items.Element( index ); }
private: CUtlMap< FileNameHandle_t, CNetworkStringTableItem > m_Items; };
//-----------------------------------------------------------------------------
// Implementation for general purpose strings
//-----------------------------------------------------------------------------
class CNetworkStringDict : public INetworkStringDict { public: CNetworkStringDict() { }
virtual ~CNetworkStringDict() { }
unsigned int Count() { return m_Lookup.Count(); }
void Purge() { m_Lookup.Purge(); }
const char *String( int index ) { return m_Lookup.Key( index ).Get(); }
bool IsValidIndex( int index ) { return m_Lookup.IsValidHandle( index ); }
int Insert( const char *pString ) { return m_Lookup.Insert( pString ); }
int Find( const char *pString ) { return pString ? m_Lookup.Find( pString ) : m_Lookup.InvalidHandle(); }
CNetworkStringTableItem &Element( int index ) { return m_Lookup.Element( index ); }
const CNetworkStringTableItem &Element( int index ) const { return m_Lookup.Element( index ); }
private: CUtlStableHashtable< CUtlConstString, CNetworkStringTableItem, CaselessStringHashFunctor, UTLConstStringCaselessStringEqualFunctor<char> > m_Lookup; };
//-----------------------------------------------------------------------------
// Purpose:
// Input : id -
// *tableName -
// maxentries -
//-----------------------------------------------------------------------------
CNetworkStringTable::CNetworkStringTable( TABLEID id, const char *tableName, int maxentries, int userdatafixedsize, int userdatanetworkbits, bool bIsFilenames ) : m_bAllowClientSideAddString( false ), m_pItemsClientSide( NULL ) { m_id = id; int len = strlen( tableName ) + 1; m_pszTableName = new char[ len ]; Assert( m_pszTableName ); Assert( tableName ); Q_strncpy( m_pszTableName, tableName, len );
m_changeFunc = NULL; m_pObject = NULL; m_nTickCount = 0; m_pMirrorTable = NULL; m_nLastChangedTick = 0; m_bChangeHistoryEnabled = false; m_bLocked = false;
m_nMaxEntries = maxentries; m_nEntryBits = Q_log2( m_nMaxEntries );
m_bUserDataFixedSize = userdatafixedsize != 0; m_nUserDataSize = userdatafixedsize; m_nUserDataSizeBits = userdatanetworkbits;
if ( m_nUserDataSizeBits > CNetworkStringTableItem::MAX_USERDATA_BITS ) { Host_Error( "String tables user data bits restricted to %i bits, requested %i is too large\n", CNetworkStringTableItem::MAX_USERDATA_BITS, m_nUserDataSizeBits ); }
if ( m_nUserDataSize > CNetworkStringTableItem::MAX_USERDATA_SIZE ) { Host_Error( "String tables user data size restricted to %i bytes, requested %i is too large\n", CNetworkStringTableItem::MAX_USERDATA_SIZE, m_nUserDataSize ); }
// Make sure maxentries is power of 2
if ( ( 1 << m_nEntryBits ) != maxentries ) { Host_Error( "String tables must be powers of two in size!, %i is not a power of 2\n", maxentries ); }
if ( IsXbox() || bIsFilenames ) { m_bIsFilenames = true; m_pItems = new CNetworkStringFilenameDict; } else { m_bIsFilenames = false; m_pItems = new CNetworkStringDict; } }
void CNetworkStringTable::SetAllowClientSideAddString( bool state ) { if ( state == m_bAllowClientSideAddString ) return;
m_bAllowClientSideAddString = state; if ( m_pItemsClientSide ) { delete m_pItemsClientSide; m_pItemsClientSide = NULL; }
if ( m_bAllowClientSideAddString ) { m_pItemsClientSide = new CNetworkStringDict; m_pItemsClientSide->Insert( "___clientsideitemsplaceholder0___" ); // 0 slot can't be used
m_pItemsClientSide->Insert( "___clientsideitemsplaceholder1___" ); // -1 can't be used since it looks like the "invalid" index from other string lookups
} }
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNetworkStringTable::IsUserDataFixedSize() const { return m_bUserDataFixedSize; }
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNetworkStringTable::HasFileNameStrings() const { return m_bIsFilenames; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CNetworkStringTable::GetUserDataSize() const { return m_nUserDataSize; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CNetworkStringTable::GetUserDataSizeBits() const { return m_nUserDataSizeBits; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNetworkStringTable::~CNetworkStringTable( void ) { delete[] m_pszTableName; delete m_pItems; delete m_pItemsClientSide; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNetworkStringTable::DeleteAllStrings( void ) { delete m_pItems; if ( m_bIsFilenames ) { m_pItems = new CNetworkStringFilenameDict; } else { m_pItems = new CNetworkStringDict; }
if ( m_pItemsClientSide ) { delete m_pItemsClientSide; m_pItemsClientSide = new CNetworkStringDict; m_pItemsClientSide->Insert( "___clientsideitemsplaceholder0___" ); // 0 slot can't be used
m_pItemsClientSide->Insert( "___clientsideitemsplaceholder1___" ); // -1 can't be used since it looks like the "invalid" index from other string lookups
} }
//-----------------------------------------------------------------------------
// Purpose:
// Input : i -
// Output : CNetworkStringTableItem
//-----------------------------------------------------------------------------
CNetworkStringTableItem *CNetworkStringTable::GetItem( int i ) { if ( i >= 0 ) { return &m_pItems->Element( i ); }
Assert( m_pItemsClientSide ); return &m_pItemsClientSide->Element( -i ); }
//-----------------------------------------------------------------------------
// Purpose: Returns the table identifier
// Output : TABLEID
//-----------------------------------------------------------------------------
TABLEID CNetworkStringTable::GetTableId( void ) const { return m_id; }
//-----------------------------------------------------------------------------
// Purpose: Returns the max size of the table
// Output : int
//-----------------------------------------------------------------------------
int CNetworkStringTable::GetMaxStrings( void ) const { return m_nMaxEntries; }
//-----------------------------------------------------------------------------
// Purpose: Returns a table, by name
// Output : const char
//-----------------------------------------------------------------------------
const char *CNetworkStringTable::GetTableName( void ) const { return m_pszTableName; }
//-----------------------------------------------------------------------------
// Purpose: Returns the number of bits needed to encode an entry index
// Output : int
//-----------------------------------------------------------------------------
int CNetworkStringTable::GetEntryBits( void ) const { return m_nEntryBits; }
void CNetworkStringTable::SetTick(int tick_count) { Assert( tick_count >= m_nTickCount ); m_nTickCount = tick_count; }
void CNetworkStringTable::Lock( bool bLock ) { m_bLocked = bLock; }
pfnStringChanged CNetworkStringTable::GetCallback() { return m_changeFunc; }
#ifndef SHARED_NET_STRING_TABLES
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNetworkStringTable::EnableRollback() { // stringtable must be empty
Assert( m_pItems->Count() == 0); m_bChangeHistoryEnabled = true; }
void CNetworkStringTable::SetMirrorTable(INetworkStringTable *table) { m_pMirrorTable = table; }
void CNetworkStringTable::RestoreTick(int tick) { // TODO optimize this, most of the time the tables doens't really change
m_nLastChangedTick = 0;
int count = m_pItems->Count(); for ( int i = 0; i < count; i++ ) { // restore tick in all entries
int tickChanged = m_pItems->Element( i ).RestoreTick( tick );
if ( tickChanged > m_nLastChangedTick ) m_nLastChangedTick = tickChanged; } }
//-----------------------------------------------------------------------------
// Purpose: updates the mirror table, if set one
// Output : return true if some entries were updates
//-----------------------------------------------------------------------------
void CNetworkStringTable::UpdateMirrorTable( int tick_ack ) { if ( !m_pMirrorTable ) return;
m_pMirrorTable->SetTick( m_nTickCount ); // use same tick
int count = m_pItems->Count(); for ( int i = 0; i < count; i++ ) { CNetworkStringTableItem *p = &m_pItems->Element( i );
// mirror is up to date
if ( p->GetTickChanged() <= tick_ack ) continue;
const void *pUserData = p->GetUserData();
int nBytes = p->GetUserDataLength();
if ( !nBytes || !pUserData ) { nBytes = 0; pUserData = NULL; }
// Check if we are updating an old entry or adding a new one
if ( i < m_pMirrorTable->GetNumStrings() ) { m_pMirrorTable->SetStringUserData( i, nBytes, pUserData ); } else { // Grow the table (entryindex must be the next empty slot)
Assert( i == m_pMirrorTable->GetNumStrings() ); char const *pName = m_pItems->String( i ); m_pMirrorTable->AddString( true, pName, nBytes, pUserData ); } } }
int CNetworkStringTable::WriteUpdate( CBaseClient *client, bf_write &buf, int tick_ack ) { CUtlVector< StringHistoryEntry > history;
int entriesUpdated = 0; int lastEntry = -1; int nTableStartBit = buf.GetNumBitsWritten();
int count = m_pItems->Count();
for ( int i = 0; i < count; i++ ) { CNetworkStringTableItem *p = &m_pItems->Element( i );
// Client is up to date
if ( p->GetTickChanged() <= tick_ack ) continue;
int nStartBit = buf.GetNumBitsWritten();
// Write Entry index
if ( (lastEntry+1) == i ) { buf.WriteOneBit( 1 ); } else { buf.WriteOneBit( 0 ); buf.WriteUBitLong( i, m_nEntryBits ); }
// check if string can use older string as base eg "models/weapons/gun1" & "models/weapons/gun2"
char const *pEntry = m_pItems->String( i );
if ( p->GetTickCreated() > tick_ack ) { // this item has just been created, send string itself
buf.WriteOneBit( 1 ); int substringsize = 0; int bestprevious = GetBestPreviousString( history, pEntry, substringsize ); if ( bestprevious != -1 ) { buf.WriteOneBit( 1 ); buf.WriteUBitLong( bestprevious, 5 ); // history never has more than 32 entries
buf.WriteUBitLong( substringsize, SUBSTRING_BITS ); buf.WriteString( pEntry + substringsize ); } else { buf.WriteOneBit( 0 ); buf.WriteString( pEntry ); } } else { buf.WriteOneBit( 0 ); }
// Write the item's user data.
int len; const void *pUserData = GetStringUserData( i, &len ); if ( pUserData && len > 0 ) { buf.WriteOneBit( 1 );
if ( IsUserDataFixedSize() ) { // Don't have to send length, it was sent as part of the table definition
buf.WriteBits( pUserData, GetUserDataSizeBits() ); } else { buf.WriteUBitLong( len, CNetworkStringTableItem::MAX_USERDATA_BITS ); buf.WriteBits( pUserData, len*8 ); } } else { buf.WriteOneBit( 0 ); }
// limit string history to 32 entries
if ( history.Count() > 31 ) { history.Remove( 0 ); }
// add string to string history
StringHistoryEntry she; Q_strncpy( she.string, pEntry, sizeof( she.string ) ); history.AddToTail( she );
entriesUpdated++; lastEntry = i;
if ( client && client->IsTracing() ) { int nBits = buf.GetNumBitsWritten() - nStartBit; client->TraceNetworkMsg( nBits, " [%s] %d:%s ", GetTableName(), i, GetString( i ) ); } }
ETWMark2I( GetTableName(), entriesUpdated, buf.GetNumBitsWritten() - nTableStartBit );
return entriesUpdated; }
//-----------------------------------------------------------------------------
// Purpose: Parse string update
//-----------------------------------------------------------------------------
void CNetworkStringTable::ParseUpdate( bf_read &buf, int entries ) { int lastEntry = -1;
CUtlVector< StringHistoryEntry > history;
for (int i=0; i<entries; i++) { int entryIndex = lastEntry + 1;
if ( !buf.ReadOneBit() ) { entryIndex = buf.ReadUBitLong( GetEntryBits() ); }
lastEntry = entryIndex; if ( entryIndex < 0 || entryIndex >= GetMaxStrings() ) { Host_Error( "Server sent bogus string index %i for table %s\n", entryIndex, GetTableName() ); }
const char *pEntry = NULL; char entry[ 1024 ]; char substr[ 1024 ];
if ( buf.ReadOneBit() ) { bool substringcheck = buf.ReadOneBit() ? true : false;
if ( substringcheck ) { unsigned int index = buf.ReadUBitLong( 5 ); unsigned int bytestocopy = buf.ReadUBitLong( SUBSTRING_BITS ); if ( index >= (unsigned int)history.Count() ) { Host_Error( "Server sent bogus substring index %i for table %s\n", entryIndex, GetTableName() ); } Q_strncpy( entry, history[ index ].string, Min( sizeof( entry ), (size_t)bytestocopy + 1 ) ); buf.ReadString( substr, sizeof(substr) ); Q_strncat( entry, substr, sizeof(entry), COPY_ALL_CHARACTERS ); } else { buf.ReadString( entry, sizeof( entry ) ); }
pEntry = entry; } // Read in the user data.
unsigned char tempbuf[ CNetworkStringTableItem::MAX_USERDATA_SIZE ]; memset( tempbuf, 0, sizeof( tempbuf ) ); const void *pUserData = NULL; int nBytes = 0;
if ( buf.ReadOneBit() ) { if ( IsUserDataFixedSize() ) { // Don't need to read length, it's fixed length and the length was networked down already.
nBytes = GetUserDataSize(); Assert( nBytes > 0 ); tempbuf[nBytes-1] = 0; // be safe, clear last byte
buf.ReadBits( tempbuf, GetUserDataSizeBits() ); } else { nBytes = buf.ReadUBitLong( CNetworkStringTableItem::MAX_USERDATA_BITS ); ErrorIfNot( nBytes <= sizeof( tempbuf ), ("CNetworkStringTableClient::ParseUpdate: message too large (%d bytes).", nBytes) );
buf.ReadBytes( tempbuf, nBytes ); }
pUserData = tempbuf; }
// Check if we are updating an old entry or adding a new one
if ( entryIndex < GetNumStrings() ) { SetStringUserData( entryIndex, nBytes, pUserData ); #ifdef _DEBUG
if ( pEntry ) { Assert( !Q_strcmp( pEntry, GetString( entryIndex ) ) ); // make sure string didn't change
} #endif
pEntry = GetString( entryIndex ); // string didn't change
} else { // Grow the table (entryindex must be the next empty slot)
Assert( (entryIndex == GetNumStrings()) && (pEntry != NULL) ); if ( pEntry == NULL ) { Msg("CNetworkStringTable::ParseUpdate: NULL pEntry, table %s, index %i\n", GetTableName(), entryIndex ); pEntry = "";// avoid crash because of NULL strings
}
AddString( true, pEntry, nBytes, pUserData ); }
if ( history.Count() > 31 ) { history.Remove( 0 ); }
StringHistoryEntry she; Q_strncpy( she.string, pEntry, sizeof( she.string ) ); history.AddToTail( she ); } }
void CNetworkStringTable::CopyStringTable(CNetworkStringTable * table) { Assert (m_pItems->Count() == 0); // table must be empty before coping
for ( unsigned int i = 0; i < table->m_pItems->Count() ; ++i ) { CNetworkStringTableItem *item = &table->m_pItems->Element( i );
m_nTickCount = item->m_nTickChanged;
AddString( true, table->GetString( i ), item->m_nUserDataLength, item->m_pUserData ); } }
#endif
void CNetworkStringTable::TriggerCallbacks( int tick_ack ) { if ( m_changeFunc == NULL ) return;
COM_TimestampedLog( "Change(%s):Start", GetTableName() );
int count = m_pItems->Count();
for ( int i = 0; i < count; i++ ) { CNetworkStringTableItem *pItem = &m_pItems->Element( i );
// mirror is up to date
if ( pItem->GetTickChanged() <= tick_ack ) continue;
int userDataSize; const void *pUserData = pItem->GetUserData( &userDataSize );
// fire the callback function
( *m_changeFunc )( m_pObject, this, i, GetString( i ), pUserData ); }
COM_TimestampedLog( "Change(%s):End", GetTableName() ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : changeFunc -
//-----------------------------------------------------------------------------
void CNetworkStringTable::SetStringChangedCallback( void *object, pfnStringChanged changeFunc ) { m_changeFunc = changeFunc; m_pObject = object; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *client -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNetworkStringTable::ChangedSinceTick( int tick ) const { return ( m_nLastChangedTick > tick ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *value -
// Output : int
//-----------------------------------------------------------------------------
int CNetworkStringTable::AddString( bool bIsServer, const char *string, int length /*= -1*/, const void *userdata /*= NULL*/ ) { bool bHasChanged; CNetworkStringTableItem *item; if ( !string ) { Assert( string ); ConMsg( "Warning: Can't add NULL string to table %s\n", GetTableName() ); return INVALID_STRING_INDEX; }
#ifdef _DEBUG
if ( m_bLocked ) { DevMsg("Warning! CNetworkStringTable::AddString: adding '%s' while locked.\n", string ); } #endif
int i = m_pItems->Find( string ); if ( !bIsServer ) { if ( m_pItems->IsValidIndex( i ) && !m_pItemsClientSide ) { bIsServer = true; } }
if ( !bIsServer && m_pItemsClientSide ) { i = m_pItemsClientSide->Find( string );
if ( !m_pItemsClientSide->IsValidIndex( i ) ) { // not in list yet, create it now
if ( m_pItemsClientSide->Count() >= (unsigned int)GetMaxStrings() ) { // Too many strings, FIXME: Print warning message
ConMsg( "Warning: Table %s is full, can't add %s\n", GetTableName(), string ); return INVALID_STRING_INDEX; }
// create new item
{ MEM_ALLOC_CREDIT(); i = m_pItemsClientSide->Insert( string ); }
item = &m_pItemsClientSide->Element( i );
// set changed ticks
item->m_nTickChanged = m_nTickCount;
#ifndef SHARED_NET_STRING_TABLES
item->m_nTickCreated = m_nTickCount;
if ( m_bChangeHistoryEnabled ) { item->EnableChangeHistory(); } #endif
bHasChanged = true; } else { item = &m_pItemsClientSide->Element( i ); // item already exists
bHasChanged = false; // not changed yet
}
if ( length > -1 ) { if ( item->SetUserData( m_nTickCount, length, userdata ) ) { bHasChanged = true; } }
if ( bHasChanged && !m_bChangeHistoryEnabled ) { DataChanged( -i, item ); }
// Negate i for returning to client
i = -i; } else { // See if it's already there
i = m_pItems->Find( string );
if ( !m_pItems->IsValidIndex( i ) ) { // not in list yet, create it now
if ( m_pItems->Count() >= (unsigned int)GetMaxStrings() ) { // Too many strings, FIXME: Print warning message
ConMsg( "Warning: Table %s is full, can't add %s\n", GetTableName(), string ); return INVALID_STRING_INDEX; }
// create new item
{ MEM_ALLOC_CREDIT(); i = m_pItems->Insert( string ); }
item = &m_pItems->Element( i );
// set changed ticks
item->m_nTickChanged = m_nTickCount;
#ifndef SHARED_NET_STRING_TABLES
item->m_nTickCreated = m_nTickCount;
if ( m_bChangeHistoryEnabled ) { item->EnableChangeHistory(); } #endif
bHasChanged = true; } else { item = &m_pItems->Element( i ); // item already exists
bHasChanged = false; // not changed yet
}
if ( length > -1 ) { if ( item->SetUserData( m_nTickCount, length, userdata ) ) { bHasChanged = true; } }
if ( bHasChanged && !m_bChangeHistoryEnabled ) { DataChanged( i, item ); } }
return i; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : stringNumber -
// Output : const char
//-----------------------------------------------------------------------------
const char *CNetworkStringTable::GetString( int stringNumber ) { INetworkStringDict *dict = m_pItems; if ( m_pItemsClientSide && stringNumber < -1 ) { dict = m_pItemsClientSide; stringNumber = -stringNumber; }
Assert( dict->IsValidIndex( stringNumber ) );
if ( dict->IsValidIndex( stringNumber ) ) { return dict->String( stringNumber ); } return NULL; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : stringNumber -
// length -
// *userdata -
//-----------------------------------------------------------------------------
void CNetworkStringTable::SetStringUserData( int stringNumber, int length /*=0*/, const void *userdata /*= 0*/ ) { #ifdef _DEBUG
if ( m_bLocked ) { DevMsg("Warning! CNetworkStringTable::SetStringUserData (%s): changing entry %i while locked.\n", GetTableName(), stringNumber ); } #endif
INetworkStringDict *dict = m_pItems; int saveStringNumber = stringNumber; if ( m_pItemsClientSide && stringNumber < -1 ) { dict = m_pItemsClientSide; stringNumber = -stringNumber; }
Assert( (length == 0 && userdata == NULL) || ( length > 0 && userdata != NULL) ); Assert( dict->IsValidIndex( stringNumber ) ); CNetworkStringTableItem *p = &dict->Element( stringNumber ); Assert( p );
if ( p->SetUserData( m_nTickCount, length, userdata ) ) { // Mark changed
DataChanged( saveStringNumber, p ); } }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *item -
//-----------------------------------------------------------------------------
void CNetworkStringTable::DataChanged( int stringNumber, CNetworkStringTableItem *item ) { Assert( item );
if ( !item ) return;
// Mark table as changed
m_nLastChangedTick = m_nTickCount; // Invoke callback if one was installed
#ifndef SHARED_NET_STRING_TABLES // but not if client & server share the same containers, we trigger that later
if ( m_changeFunc != NULL ) { int userDataSize; const void *pUserData = item->GetUserData( &userDataSize ); ( *m_changeFunc )( m_pObject, this, stringNumber, GetString( stringNumber ), pUserData ); }
#endif
}
#ifndef SHARED_NET_STRING_TABLES
void CNetworkStringTable::WriteStringTable( bf_write& buf ) { int numstrings = m_pItems->Count(); buf.WriteWord( numstrings ); for ( int i = 0 ; i < numstrings; i++ ) { buf.WriteString( GetString( i ) ); int userDataSize; const void *pUserData = GetStringUserData( i, &userDataSize ); if ( userDataSize > 0 ) { buf.WriteOneBit( 1 ); buf.WriteWord( (short)userDataSize ); buf.WriteBytes( pUserData, userDataSize ); } else { buf.WriteOneBit( 0 ); } }
if ( m_pItemsClientSide ) { buf.WriteOneBit( 1 );
numstrings = m_pItemsClientSide->Count(); buf.WriteWord( numstrings ); for ( int i = 0 ; i < numstrings; i++ ) { buf.WriteString( m_pItemsClientSide->String( i ) ); int userDataSize; const void *pUserData = m_pItemsClientSide->Element( i ).GetUserData( &userDataSize ); if ( userDataSize > 0 ) { buf.WriteOneBit( 1 ); buf.WriteWord( (short)userDataSize ); buf.WriteBytes( pUserData, userDataSize ); } else { buf.WriteOneBit( 0 ); } }
} else { buf.WriteOneBit( 0 ); } }
bool CNetworkStringTable::ReadStringTable( bf_read& buf ) { DeleteAllStrings();
int numstrings = buf.ReadWord(); for ( int i = 0 ; i < numstrings; i++ ) { char stringname[4096]; buf.ReadString( stringname, sizeof( stringname ) );
if ( buf.ReadOneBit() == 1 ) { int userDataSize = (int)buf.ReadWord(); Assert( userDataSize > 0 ); byte *data = new byte[ userDataSize + 4 ]; Assert( data );
buf.ReadBytes( data, userDataSize );
AddString( true, stringname, userDataSize, data );
delete[] data; } else { AddString( true, stringname ); } }
// Client side stuff
if ( buf.ReadOneBit() == 1 ) { numstrings = buf.ReadWord(); for ( int i = 0 ; i < numstrings; i++ ) { char stringname[4096];
buf.ReadString( stringname, sizeof( stringname ) );
if ( buf.ReadOneBit() == 1 ) { int userDataSize = (int)buf.ReadWord(); Assert( userDataSize > 0 ); byte *data = new byte[ userDataSize + 4 ]; Assert( data );
buf.ReadBytes( data, userDataSize );
if ( i >= 2 ) { AddString( false, stringname, userDataSize, data ); }
delete[] data;
} else { if ( i >= 2 ) { AddString( false, stringname ); } } } }
return true; }
#endif
//-----------------------------------------------------------------------------
// Purpose:
// Input : stringNumber -
// length -
// Output : const void
//-----------------------------------------------------------------------------
const void *CNetworkStringTable::GetStringUserData( int stringNumber, int *length ) { INetworkStringDict *dict = m_pItems; if ( m_pItemsClientSide && stringNumber < -1 ) { dict = m_pItemsClientSide; stringNumber = -stringNumber; }
CNetworkStringTableItem *p;
Assert( dict->IsValidIndex( stringNumber ) ); p = &dict->Element( stringNumber ); Assert( p ); return p->GetUserData( length ); }
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CNetworkStringTable::GetNumStrings( void ) const { return m_pItems->Count(); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : stringTable -
// *string -
// Output : int
//-----------------------------------------------------------------------------
int CNetworkStringTable::FindStringIndex( char const *string ) { if ( !string ) return INVALID_STRING_INDEX; int i = m_pItems->Find( string ); if ( m_pItems->IsValidIndex( i ) ) return i; return INVALID_STRING_INDEX; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNetworkStringTable::Dump( void ) { ConMsg( "Table %s\n", GetTableName() ); ConMsg( " %i/%i items\n", GetNumStrings(), GetMaxStrings() ); for ( int i = 0; i < GetNumStrings() ; i++ ) { ConMsg( " %i : %s\n", i, GetString( i ) ); } if ( m_pItemsClientSide ) { for ( int i = 0; i < (int)m_pItemsClientSide->Count() ; i++ ) { ConMsg( " (c)%i : %s\n", i, m_pItemsClientSide->String( i ) ); } } ConMsg( "\n" ); }
#ifndef SHARED_NET_STRING_TABLES
bool CNetworkStringTable::WriteBaselines( SVC_CreateStringTable &msg, char *msg_buffer, int msg_buffer_size ) { VPROF_BUDGET( "CNetworkStringTable::WriteBaselines", VPROF_BUDGETGROUP_OTHER_NETWORKING ); msg.m_DataOut.StartWriting( msg_buffer, msg_buffer_size );
msg.m_bIsFilenames = m_bIsFilenames; msg.m_szTableName = GetTableName(); msg.m_nMaxEntries = GetMaxStrings(); msg.m_nNumEntries = GetNumStrings(); msg.m_bUserDataFixedSize = IsUserDataFixedSize(); msg.m_nUserDataSize = GetUserDataSize(); msg.m_nUserDataSizeBits = GetUserDataSizeBits();
// tick = -1 ensures that all entries are updated = baseline
int entries = WriteUpdate( NULL, msg.m_DataOut, -1 );
return entries == msg.m_nNumEntries; }
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNetworkStringTableContainer::CNetworkStringTableContainer( void ) { m_bAllowCreation = false; m_bLocked = true; m_nTickCount = 0; m_bEnableRollback = false; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNetworkStringTableContainer::~CNetworkStringTableContainer( void ) { RemoveAllTables(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNetworkStringTableContainer::AllowCreation( bool state ) { m_bAllowCreation = state; }
bool CNetworkStringTableContainer::Lock( bool bLock ) { bool oldLock = m_bLocked;
m_bLocked = bLock;
// Determine if an update is needed
for ( int i = 0; i < m_Tables.Count(); i++ ) { CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
table->Lock( bLock ); } return oldLock; }
void CNetworkStringTableContainer::SetAllowClientSideAddString( INetworkStringTable *table, bool bAllowClientSideAddString ) { for ( int i = 0; i < m_Tables.Count(); i++ ) { CNetworkStringTable *t = (CNetworkStringTable*) GetTable( i ); if ( t == table ) { t->SetAllowClientSideAddString( bAllowClientSideAddString ); return; } } }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *tableName -
// maxentries -
// Output : TABLEID
//-----------------------------------------------------------------------------
INetworkStringTable *CNetworkStringTableContainer::CreateStringTableEx( const char *tableName, int maxentries, int userdatafixedsize /*= 0*/, int userdatanetworkbits /*= 0*/, bool bIsFilenames /*= false */ ) { if ( !m_bAllowCreation ) { Sys_Error( "Tried to create string table '%s' at wrong time\n", tableName ); return NULL; }
CNetworkStringTable *pTable = (CNetworkStringTable*) FindTable( tableName );
if ( pTable != NULL ) { Sys_Error( "Tried to create string table '%s' twice\n", tableName ); return NULL; }
if ( m_Tables.Count() >= MAX_TABLES ) { Sys_Error( "Only %i string tables allowed, can't create'%s'", MAX_TABLES, tableName); return NULL; }
TABLEID id = m_Tables.Count();
pTable = new CNetworkStringTable( id, tableName, maxentries, userdatafixedsize, userdatanetworkbits, bIsFilenames );
Assert( pTable );
#ifndef SHARED_NET_STRING_TABLES
if ( m_bEnableRollback ) { pTable->EnableRollback(); } #endif
pTable->SetTick( m_nTickCount );
m_Tables.AddToTail( pTable );
return pTable; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *tableName -
//-----------------------------------------------------------------------------
INetworkStringTable *CNetworkStringTableContainer::FindTable( const char *tableName ) const { for ( int i = 0; i < m_Tables.Count(); i++ ) { if ( !Q_stricmp( tableName, m_Tables[ i ]->GetTableName() ) ) return m_Tables[i]; }
return NULL; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : stringTable -
// Output : CNetworkStringTableServer
//-----------------------------------------------------------------------------
INetworkStringTable *CNetworkStringTableContainer::GetTable( TABLEID stringTable ) const { if ( stringTable < 0 || stringTable >= m_Tables.Count() ) return NULL;
return m_Tables[ stringTable ]; }
int CNetworkStringTableContainer::GetNumTables( void ) const { return m_Tables.Count(); }
#ifndef SHARED_NET_STRING_TABLES
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNetworkStringTableContainer::WriteBaselines( bf_write &buf ) { VPROF_BUDGET( "CNetworkStringTableContainer::WriteBaselines", VPROF_BUDGETGROUP_OTHER_NETWORKING );
SVC_CreateStringTable msg;
size_t msg_buffer_size = 2 * NET_MAX_PAYLOAD; char *msg_buffer = new char[ msg_buffer_size ]; if ( !msg_buffer ) { Host_Error( "Failed to allocate %llu bytes of memory in CNetworkStringTableContainer::WriteBaselines\n", (uint64)msg_buffer_size ); }
for ( int i = 0 ; i < m_Tables.Count() ; i++ ) { CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
int before = buf.GetNumBytesWritten(); if ( !table->WriteBaselines( msg, msg_buffer, msg_buffer_size ) ) { Host_Error( "Index error writing string table baseline %s\n", table->GetTableName() ); }
if ( msg.m_DataOut.IsOverflowed() ) { Warning( "Warning: Overflowed writing uncompressed string table data for %s\n", table->GetTableName() ); }
msg.m_bDataCompressed = false; if ( msg.m_DataOut.GetNumBytesWritten() >= sv_compressstringtablebaselines_threshhold.GetInt() ) { CFastTimer compressTimer; compressTimer.Start();
// TERROR: bzip-compress the stringtable before adding it to the packet. Yes, the whole packet will be bzip'd,
// but the uncompressed data also has to be under the NET_MAX_PAYLOAD limit.
unsigned int numBytes = msg.m_DataOut.GetNumBytesWritten(); unsigned int compressedSize = (unsigned int)numBytes; char *compressedData = new char[numBytes];
if ( COM_BufferToBufferCompress_Snappy( compressedData, &compressedSize, (char *)msg.m_DataOut.GetData(), numBytes ) ) { msg.m_bDataCompressed = true; msg.m_DataOut.Reset(); msg.m_DataOut.WriteLong( numBytes ); // uncompressed size
msg.m_DataOut.WriteLong( compressedSize ); // compressed size
msg.m_DataOut.WriteBits( compressedData, compressedSize * 8 ); // compressed data
// if ( compressstringtablbaselines > 1 )
{ compressTimer.End(); DevMsg( "Stringtable %s compression: %d -> %d bytes: %.2fms\n", table->GetTableName(), numBytes, compressedSize, compressTimer.GetDuration().GetMillisecondsF() ); } }
delete [] compressedData; }
if ( !msg.WriteToBuffer( buf ) ) { Host_Error( "Overflow error writing string table baseline %s\n", table->GetTableName() ); }
int after = buf.GetNumBytesWritten(); if ( sv_dumpstringtables.GetBool() ) { DevMsg( "CNetworkStringTableContainer::WriteBaselines wrote %d bytes for table %s [space remaining %d bytes]\n", after - before, table->GetTableName(), buf.GetNumBytesLeft() ); } }
delete[] msg_buffer; }
void CNetworkStringTableContainer::WriteStringTables( bf_write& buf ) { int numTables = m_Tables.Size();
buf.WriteByte( numTables ); for ( int i = 0; i < numTables; i++ ) { CNetworkStringTable *table = m_Tables[ i ]; buf.WriteString( table->GetTableName() ); table->WriteStringTable( buf ); } }
bool CNetworkStringTableContainer::ReadStringTables( bf_read& buf ) { int numTables = buf.ReadByte(); for ( int i = 0 ; i < numTables; i++ ) { char tablename[ 256 ]; buf.ReadString( tablename, sizeof( tablename ) );
// Find this table by name
CNetworkStringTable *table = (CNetworkStringTable*)FindTable( tablename ); Assert( table );
// Now read the data for the table
if ( table && !table->ReadStringTable( buf ) ) { Host_Error( "Error reading string table %s\n", tablename ); } else { Warning( "Could not find table \"%s\"\n", tablename ); } }
return true; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *cl -
// *msg -
//-----------------------------------------------------------------------------
void CNetworkStringTableContainer::WriteUpdateMessage( CBaseClient *client, int tick_ack, bf_write &buf ) { VPROF_BUDGET( "CNetworkStringTableContainer::WriteUpdateMessage", VPROF_BUDGETGROUP_OTHER_NETWORKING );
char buffer[NET_MAX_PAYLOAD];
// Determine if an update is needed
for ( int i = 0; i < m_Tables.Count(); i++ ) { CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
if ( !table ) continue;
if ( !table->ChangedSinceTick( tick_ack ) ) continue;
SVC_UpdateStringTable msg;
msg.m_DataOut.StartWriting( buffer, NET_MAX_PAYLOAD ); msg.m_nTableID = table->GetTableId(); msg.m_nChangedEntries = table->WriteUpdate( client, msg.m_DataOut, tick_ack );
Assert( msg.m_nChangedEntries > 0 ); // don't send unnecessary empty updates
msg.WriteToBuffer( buf );
if ( client && client->IsTracing() ) { client->TraceNetworkData( buf, "StringTable %s", table->GetTableName() ); } } }
//-----------------------------------------------------------------------------
// Purpose:
// Input : *cl -
// *msg -
//-----------------------------------------------------------------------------
void CNetworkStringTableContainer::DirectUpdate( int tick_ack ) { VPROF_BUDGET( "CNetworkStringTableContainer::DirectUpdate", VPROF_BUDGETGROUP_OTHER_NETWORKING );
// Determine if an update is needed
for ( int i = 0; i < m_Tables.Count(); i++ ) { CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
Assert( table ); if ( !table->ChangedSinceTick( tick_ack ) ) continue;
table->UpdateMirrorTable( tick_ack ); } }
void CNetworkStringTableContainer::EnableRollback( bool bState ) { // we can't dis/enable rollback if we already created tabled
Assert( m_Tables.Count() == 0 );
m_bEnableRollback = bState; }
void CNetworkStringTableContainer::RestoreTick( int tick ) { for ( int i = 0; i < m_Tables.Count(); i++ ) { CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
Assert( table );
table->RestoreTick( tick ); } }
#endif
void CNetworkStringTableContainer::TriggerCallbacks( int tick_ack ) { // Determine if an update is needed
for ( int i = 0; i < m_Tables.Count(); i++ ) { CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
Assert( table );
if ( !table->ChangedSinceTick( tick_ack ) ) continue;
table->TriggerCallbacks( tick_ack ); } }
void CNetworkStringTableContainer::SetTick( int tick_count) { Assert( tick_count > 0 );
m_nTickCount = tick_count;
// Determine if an update is needed
for ( int i = 0; i < m_Tables.Count(); i++ ) { CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
Assert( table );
table->SetTick( tick_count ); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNetworkStringTableContainer::RemoveAllTables( void ) { while ( m_Tables.Count() > 0 ) { CNetworkStringTable *table = m_Tables[ 0 ]; m_Tables.Remove( 0 ); delete table; } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNetworkStringTableContainer::Dump( void ) { for ( int i = 0; i < m_Tables.Count(); i++ ) { m_Tables[ i ]->Dump(); } }
|