913 lines
25 KiB
C++
913 lines
25 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
|
|
#include "dt.h"
|
|
#include "dt_recv_eng.h"
|
|
#include "dt_encode.h"
|
|
#include "dt_instrumentation.h"
|
|
#include "dt_stack.h"
|
|
#include "utllinkedlist.h"
|
|
#include "tier0/dbg.h"
|
|
#include "dt_recv_decoder.h"
|
|
#include "tier1/strtools.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "dt_common_eng.h"
|
|
#include "common.h"
|
|
#include "serializedentity.h"
|
|
#include "netmessages.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
class CClientSendTable;
|
|
|
|
|
|
// Testing out this pattern.. you can write simple code blocks inside of
|
|
// codeToRun. The thing that sucks is that you can't access your function's
|
|
// local variables inside of codeToRun.
|
|
//
|
|
// If it used an iterator class, it could access local function variables,
|
|
// but the iterator class might be more trouble than it's worth.
|
|
#define FOR_EACH_PROP_R( TableType, pTablePointer, tableCode, propCode ) \
|
|
class CPropVisitor \
|
|
{ \
|
|
public: \
|
|
static void Visit_R( TableType *pTable ) \
|
|
{ \
|
|
tableCode; \
|
|
\
|
|
for ( int i=0; i < pTable->GetNumProps(); i++ ) \
|
|
{ \
|
|
TableType::PropType *pProp = pTable->GetProp( i ); \
|
|
\
|
|
propCode; \
|
|
\
|
|
if ( pProp->GetType() == DPT_DataTable ) \
|
|
Visit_R( pProp->GetDataTable() ); \
|
|
} \
|
|
} \
|
|
}; \
|
|
CPropVisitor::Visit_R( pTablePointer );
|
|
|
|
#define SENDPROP_VISIT( pTablePointer, tableCode, propCode ) FOR_EACH_PROP_R( SendTable, pTablePointer, tableCode, propCode )
|
|
#define RECVPROP_VISIT( pTablePointer, tableCode, propCode ) FOR_EACH_PROP_R( RecvTable, pTablePointer, tableCode, propCode )
|
|
#define SETUP_VISIT() class CDummyClass {} // Workaround for parser bug in VC7.1
|
|
|
|
// ------------------------------------------------------------------------------------ //
|
|
// Globals.
|
|
// ------------------------------------------------------------------------------------ //
|
|
|
|
CUtlLinkedList< RecvTable*, unsigned short > g_RecvTables;
|
|
CUtlLinkedList< CRecvDecoder *, unsigned short > g_RecvDecoders;
|
|
CUtlLinkedList< CClientSendTable*, unsigned short > g_ClientSendTables;
|
|
|
|
// ------------------------------------------------------------------------------------ //
|
|
// Static helper functions.
|
|
// ------------------------------------------------------------------------------------ //
|
|
|
|
RecvTable* FindRecvTable( const char *pName )
|
|
{
|
|
FOR_EACH_LL( g_RecvTables, i )
|
|
{
|
|
if ( stricmp( g_RecvTables[i]->GetName(), pName ) == 0 )
|
|
return g_RecvTables[i];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static CClientSendTable* FindClientSendTable( const char *pName )
|
|
{
|
|
FOR_EACH_LL( g_ClientSendTables, i )
|
|
{
|
|
CClientSendTable *pTable = g_ClientSendTables[i];
|
|
|
|
if ( stricmp( pTable->GetName(), pName ) == 0 )
|
|
return pTable;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Find all child datatable properties for the send tables.
|
|
bool SetupClientSendTableHierarchy()
|
|
{
|
|
FOR_EACH_LL( g_ClientSendTables, iClientTable )
|
|
{
|
|
CClientSendTable *pTable = g_ClientSendTables[iClientTable];
|
|
|
|
// For each datatable property, find the table it references.
|
|
for ( int iProp=0; iProp < pTable->GetNumProps(); iProp++ )
|
|
{
|
|
CClientSendProp *pClientProp = pTable->GetClientProp( iProp );
|
|
SendProp *pProp = &pTable->m_SendTable.m_pProps[iProp];
|
|
|
|
if ( pProp->m_Type == DPT_DataTable )
|
|
{
|
|
const char *pTableName = pClientProp->GetTableName();
|
|
ErrorIfNot( pTableName,
|
|
("SetupClientSendTableHierarchy: missing table name for prop '%s'.", pProp->GetName())
|
|
);
|
|
|
|
CClientSendTable *pChild = FindClientSendTable( pTableName );
|
|
if ( !pChild )
|
|
{
|
|
DataTable_Warning( "SetupClientSendTableHierarchy: missing SendTable '%s' (referenced by '%s').\n", pTableName, pTable->GetName() );
|
|
return false;
|
|
}
|
|
|
|
pProp->SetDataTable( &pChild->m_SendTable );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static RecvProp* FindRecvProp( RecvTable *pTable, const char *pName )
|
|
{
|
|
for ( int i=0; i < pTable->GetNumProps(); i++ )
|
|
{
|
|
RecvProp *pProp = pTable->GetProp( i );
|
|
|
|
#ifdef DBGFLAG_ASSERT
|
|
// Debug validation to handle that no network fields get created with colliding names to special UTL vector networking fields
|
|
// because we will have custom receive table remapping below and don't want to mistakenly route bytes into wrong memory
|
|
// See dt_utlvector_recv.cpp / RecvPropUtlVector for details of this remapping
|
|
if ( char const *szLength = StringAfterPrefix( pProp->GetName(), "lengthprop" ) )
|
|
{
|
|
// *pLengthProp = RecvPropInt( AllocateStringHelper( "lengthprop%d", nMaxElements ), 0, 0, 0, RecvProxy_UtlVectorLength );
|
|
Assert( pProp->GetType() == DPT_Int );
|
|
Assert( *szLength );
|
|
for ( char const *szCheck = szLength; szCheck && *szCheck; ++ szCheck )
|
|
{
|
|
Assert( V_isdigit( *szCheck ) ); // assert that the number forms the length of array
|
|
Assert( szCheck - szLength < 5 ); // arrays are never that large!
|
|
}
|
|
}
|
|
else if ( char const *szLPT = StringAfterPrefix( pProp->GetName(), "_LPT_" ) )
|
|
{
|
|
// char *pLengthProxyTableName = AllocateUniqueDataTableName( false, "_LPT_%s_%d", pVarName, nMaxElements );
|
|
Assert( pProp->GetType() == DPT_DataTable );
|
|
char const *szLPTsize = strrchr( szLPT, '_' );
|
|
Assert( szLPTsize );
|
|
if ( szLPTsize )
|
|
{
|
|
++ szLPTsize;
|
|
Assert( *szLPTsize );
|
|
}
|
|
for ( char const *szCheck = szLPTsize; szCheck && *szCheck; ++szCheck )
|
|
{
|
|
Assert( V_isdigit( *szCheck ) ); // assert that the number forms the length of array
|
|
Assert( szCheck - szLength < 5 ); // arrays are never that large!
|
|
}
|
|
}
|
|
else if ( char const *szST = StringAfterPrefix( pProp->GetName(), "_ST_" ) )
|
|
{
|
|
// AllocateUniqueDataTableName( false, "_ST_%s_%d", pVarName, nMaxElements )
|
|
Assert( pProp->GetType() == DPT_DataTable );
|
|
char const *szSTsize = strrchr( szST, '_' );
|
|
Assert( szSTsize );
|
|
if ( szSTsize )
|
|
{
|
|
++szSTsize;
|
|
Assert( *szSTsize );
|
|
}
|
|
for ( char const *szCheck = szSTsize; szCheck && *szCheck; ++szCheck )
|
|
{
|
|
Assert( V_isdigit( *szCheck ) ); // assert that the number forms the length of array
|
|
Assert( szCheck - szLength < 5 ); // arrays are never that large!
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( !V_stricmp( pProp->GetName(), pName ) )
|
|
return pProp;
|
|
|
|
//
|
|
// Special case to receive UTL vector networked prop into a larger UTL vector networked prop on the client
|
|
// See dt_utlvector_recv.cpp / RecvPropUtlVector for details of this remapping
|
|
//
|
|
if ( char const *p_SEND_Length = StringAfterPrefix( pName, "lengthprop" ) )
|
|
{
|
|
// We are being sent a lengthprop##
|
|
if ( char const *p_RECV_Length = StringAfterPrefix( pProp->GetName(), "lengthprop" ) )
|
|
{
|
|
if ( Q_atoi( p_SEND_Length ) <= Q_atoi( p_RECV_Length ) )
|
|
return pProp;
|
|
}
|
|
}
|
|
else if ( char const *p_SEND_LPT = StringAfterPrefix( pName, "_LPT_" ) )
|
|
{
|
|
// We are being sent an _LPT_(varname)_## field
|
|
if ( char const *p_RECV_LPT = StringAfterPrefix( pProp->GetName(), "_LPT_" ) )
|
|
{
|
|
// Trim the length from the field
|
|
char const *p_SEND_LPT_size = strrchr( p_SEND_LPT, '_' );
|
|
char const *p_RECV_LPT_size = strrchr( p_RECV_LPT, '_' );
|
|
if ( p_SEND_LPT_size && p_RECV_LPT_size &&
|
|
( p_SEND_LPT_size - p_SEND_LPT == p_RECV_LPT_size - p_RECV_LPT ) &&
|
|
!V_strnicmp( p_SEND_LPT, p_RECV_LPT, p_RECV_LPT_size - p_RECV_LPT ) &&
|
|
( Q_atoi( p_SEND_LPT_size + 1 ) <= Q_atoi( p_RECV_LPT_size + 1 ) ) )
|
|
return pProp;
|
|
}
|
|
}
|
|
else if ( char const *p_SEND_ST = StringAfterPrefix( pName, "_ST_" ) )
|
|
{
|
|
// We are being sent an _ST_(varname)_## field
|
|
if ( char const *p_RECV_ST = StringAfterPrefix( pProp->GetName(), "_ST_" ) )
|
|
{
|
|
// Trim the length from the field
|
|
char const *p_SEND_ST_size = strrchr( p_SEND_ST, '_' );
|
|
char const *p_RECV_ST_size = strrchr( p_RECV_ST, '_' );
|
|
if ( p_SEND_ST_size && p_RECV_ST_size &&
|
|
( p_SEND_ST_size - p_SEND_ST == p_RECV_ST_size - p_RECV_ST ) &&
|
|
!V_strnicmp( p_SEND_ST, p_RECV_ST, p_RECV_ST_size - p_RECV_ST ) &&
|
|
( Q_atoi( p_SEND_ST_size + 1 ) <= Q_atoi( p_RECV_ST_size + 1 ) ) )
|
|
return pProp;
|
|
}
|
|
}
|
|
//
|
|
// End of UTL vector backwards compatibility receiving remap
|
|
//
|
|
}
|
|
|
|
// Support recursing into base classes to find the required field:
|
|
if ( pTable->GetNumProps() )
|
|
{
|
|
RecvProp *pSubProp = pTable->GetProp( 0 );
|
|
if ( ( pSubProp->GetType() == DPT_DataTable ) &&
|
|
!V_stricmp( pSubProp->GetName(), "baseclass" ) )
|
|
return FindRecvProp( pSubProp->GetDataTable(), pName );
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// See if the RecvProp is fit to receive the SendProp's data.
|
|
bool CompareRecvPropToSendProp( const RecvProp *pRecvProp, const SendProp *pSendProp )
|
|
{
|
|
while ( 1 )
|
|
{
|
|
ErrorIfNot( pRecvProp && pSendProp,
|
|
("CompareRecvPropToSendProp: missing a property.")
|
|
);
|
|
|
|
if ( pRecvProp->GetType() != pSendProp->GetType() || pRecvProp->IsInsideArray() != pSendProp->IsInsideArray() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( pRecvProp->GetType() == DPT_Array )
|
|
{
|
|
// It should be OK to receive into a larger array, just later elements
|
|
// will not ever be received
|
|
if ( pRecvProp->GetNumElements() < pSendProp->GetNumElements() )
|
|
return false;
|
|
|
|
pRecvProp = pRecvProp->GetArrayProp();
|
|
pSendProp = pSendProp->GetArrayProp();
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct MatchingProp_t
|
|
{
|
|
SendProp *m_pProp;
|
|
RecvProp *m_pMatchingRecvProp;
|
|
|
|
static bool LessFunc( const MatchingProp_t& lhs, const MatchingProp_t& rhs )
|
|
{
|
|
return lhs.m_pProp < rhs.m_pProp;
|
|
}
|
|
};
|
|
|
|
static bool MatchRecvPropsToSendProps_R( CUtlRBTree< MatchingProp_t, unsigned short >& lookup, char const *sendTableName, SendTable *pSendTable, RecvTable *pRecvTable, bool bAllowMismatches, bool *pAnyMismatches )
|
|
{
|
|
for ( int i=0; i < pSendTable->m_nProps; i++ )
|
|
{
|
|
SendProp *pSendProp = &pSendTable->m_pProps[i];
|
|
|
|
if ( pSendProp->IsExcludeProp() || pSendProp->IsInsideArray() )
|
|
continue;
|
|
|
|
// Find a RecvProp by the same name and type.
|
|
RecvProp *pRecvProp = 0;
|
|
if ( pRecvTable )
|
|
pRecvProp = FindRecvProp( pRecvTable, pSendProp->GetName() );
|
|
|
|
if ( pRecvProp )
|
|
{
|
|
if ( !CompareRecvPropToSendProp( pRecvProp, pSendProp ) )
|
|
{
|
|
Warning( "RecvProp type doesn't match server type for %s/%s\n", pSendTable->GetName(), pSendProp->GetName() );
|
|
return false;
|
|
}
|
|
|
|
MatchingProp_t info;
|
|
info.m_pProp = pSendProp;
|
|
info.m_pMatchingRecvProp = pRecvProp;
|
|
|
|
lookup.Insert( info );
|
|
}
|
|
else
|
|
{
|
|
if ( pAnyMismatches )
|
|
{
|
|
*pAnyMismatches = true;
|
|
}
|
|
|
|
DevWarning( "Missing RecvProp for %s - %s/%s\n", sendTableName, pSendTable->GetName(), pSendProp->GetName() );
|
|
if ( !bAllowMismatches )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Recurse.
|
|
if ( pSendProp->GetType() == DPT_DataTable )
|
|
{
|
|
if ( !MatchRecvPropsToSendProps_R( lookup, sendTableName, pSendProp->GetDataTable(), pRecvProp ? pRecvProp->GetDataTable() : 0, bAllowMismatches, pAnyMismatches ) )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
extern bool s_debug_info_shown;
|
|
extern int s_debug_bits_start;
|
|
|
|
static inline void ShowDecodeDeltaWatchInfo(
|
|
char *what,
|
|
const RecvTable *pTable,
|
|
const SendProp *pProp,
|
|
bf_read &buffer,
|
|
const int objectID,
|
|
const int index )
|
|
{
|
|
if ( !ShouldWatchThisProp( pTable, objectID, pProp->GetName()) )
|
|
return;
|
|
|
|
extern int host_framecount;
|
|
|
|
static int lastframe = -1;
|
|
if ( host_framecount != lastframe )
|
|
{
|
|
lastframe = host_framecount;
|
|
ConDMsg( "D: delta entity: %i %s\n", objectID, pTable->GetName() );
|
|
}
|
|
|
|
// work on copy of bitbuffer
|
|
bf_read copy = buffer;
|
|
|
|
s_debug_info_shown = true;
|
|
|
|
DecodeInfo info;
|
|
info.m_pStruct = NULL;
|
|
info.m_pData = NULL;
|
|
info.m_pRecvProp = NULL;
|
|
info.m_pProp = pProp;
|
|
info.m_pIn = ©
|
|
info.m_ObjectID = objectID;
|
|
info.m_Value.m_Type = (SendPropType)pProp->m_Type;
|
|
|
|
int startBit = copy.GetNumBitsRead();
|
|
|
|
g_PropTypeFns[pProp->m_Type].Decode( &info );
|
|
|
|
int bits = copy.GetNumBitsRead() - startBit;
|
|
|
|
const char *type = g_PropTypeFns[pProp->m_Type].GetTypeNameString();
|
|
const char *value = info.m_Value.ToString();
|
|
|
|
ConDMsg( "D[%s]:%s %s, %s, index %i, offset %i, bits %i, value %s\n", what, pTable->GetName(), pProp->GetName(), type, index, startBit, bits, value );
|
|
}
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------ //
|
|
// Interface functions.
|
|
// ------------------------------------------------------------------------------------ //
|
|
bool RecvTable_Init( RecvTable **pTables, int nTables )
|
|
{
|
|
SETUP_VISIT();
|
|
|
|
for ( int i=0; i < nTables; i++ )
|
|
{
|
|
RECVPROP_VISIT( pTables[i],
|
|
{
|
|
if ( pTable->IsInMainList() )
|
|
return;
|
|
|
|
// Shouldn't have a decoder yet.
|
|
ErrorIfNot( !pTable->m_pDecoder,
|
|
("RecvTable_Init: table '%s' has a decoder already.", pTable->GetName()));
|
|
|
|
pTable->SetInMainList( true );
|
|
g_RecvTables.AddToTail( pTable );
|
|
},
|
|
|
|
{}
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void RecvTable_Term( bool clearall /*= true*/ )
|
|
{
|
|
DTI_Term();
|
|
|
|
SETUP_VISIT();
|
|
|
|
FOR_EACH_LL( g_RecvTables, i )
|
|
{
|
|
RECVPROP_VISIT( g_RecvTables[i],
|
|
{
|
|
if ( !pTable->IsInMainList() )
|
|
return;
|
|
|
|
pTable->SetInMainList( false );
|
|
pTable->m_pDecoder = 0;
|
|
},
|
|
|
|
{}
|
|
);
|
|
}
|
|
|
|
if ( clearall )
|
|
{
|
|
g_RecvTables.Purge();
|
|
}
|
|
g_RecvDecoders.PurgeAndDeleteElements();
|
|
g_ClientSendTables.PurgeAndDeleteElements();
|
|
}
|
|
|
|
void RecvTable_FreeSendTable( SendTable *pTable )
|
|
{
|
|
for ( int iProp=0; iProp < pTable->m_nProps; iProp++ )
|
|
{
|
|
SendProp *pProp = &pTable->m_pProps[iProp];
|
|
|
|
delete [] pProp->m_pVarName;
|
|
|
|
if ( pProp->m_pExcludeDTName )
|
|
delete [] pProp->m_pExcludeDTName;
|
|
}
|
|
|
|
if ( pTable->m_pProps )
|
|
delete [] pTable->m_pProps;
|
|
|
|
delete [] pTable->m_pNetTableName;
|
|
delete pTable;
|
|
}
|
|
|
|
static char* AllocString( const char *pStr )
|
|
{
|
|
int allocLen = strlen( pStr ) + 1;
|
|
|
|
char *pOut = new char[allocLen];
|
|
V_strncpy( pOut, pStr, allocLen );
|
|
return pOut;
|
|
}
|
|
|
|
SendTable *RecvTable_ReadInfos( const CSVCMsg_SendTable& msg, int nDemoProtocol )
|
|
{
|
|
SendTable *pTable = new SendTable;
|
|
|
|
pTable->m_pNetTableName = AllocString( msg.net_table_name().c_str() );
|
|
|
|
// Read the property list.
|
|
pTable->m_nProps = msg.props_size();
|
|
pTable->m_pProps = pTable->m_nProps ? new SendProp[ pTable->m_nProps ] : NULL;
|
|
|
|
for ( int iProp=0; iProp < pTable->m_nProps; iProp++ )
|
|
{
|
|
SendProp *pProp = &pTable->m_pProps[iProp];
|
|
const CSVCMsg_SendTable::sendprop_t& sendProp = msg.props( iProp );
|
|
|
|
pProp->m_Type = (SendPropType)sendProp.type();
|
|
pProp->m_pVarName = AllocString( sendProp.var_name().c_str() );
|
|
|
|
pProp->SetFlags( sendProp.flags() );
|
|
pProp->SetPriority( sendProp.priority() );
|
|
|
|
if ( ( pProp->m_Type == DPT_DataTable ) || ( pProp->IsExcludeProp() ) )
|
|
{
|
|
pProp->m_pExcludeDTName = AllocString( sendProp.dt_name().c_str() );
|
|
}
|
|
else if ( pProp->GetType() == DPT_Array )
|
|
{
|
|
pProp->SetNumElements( sendProp.num_elements() );
|
|
}
|
|
else
|
|
{
|
|
pProp->m_fLowValue = sendProp.low_value();
|
|
pProp->m_fHighValue = sendProp.high_value();
|
|
pProp->m_nBits = sendProp.num_bits();
|
|
}
|
|
}
|
|
|
|
return pTable;
|
|
}
|
|
|
|
bool RecvTable_RecvClassInfos( const CSVCMsg_SendTable& msg, int nDemoProtocol )
|
|
{
|
|
SendTable *pSendTable = RecvTable_ReadInfos( msg, nDemoProtocol );
|
|
|
|
if ( !pSendTable )
|
|
return false;
|
|
|
|
bool ret = DataTable_SetupReceiveTableFromSendTable( pSendTable, msg.needs_decoder() );
|
|
|
|
RecvTable_FreeSendTable( pSendTable );
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void CopySendPropsToRecvProps(
|
|
CUtlRBTree< MatchingProp_t, unsigned short >& lookup,
|
|
const CUtlVector<const SendProp*> &sendProps,
|
|
CUtlVector<const RecvProp*> &recvProps
|
|
)
|
|
{
|
|
recvProps.SetSize( sendProps.Count() );
|
|
for ( int iSendProp=0; iSendProp < sendProps.Count(); iSendProp++ )
|
|
{
|
|
const SendProp *pSendProp = sendProps[iSendProp];
|
|
MatchingProp_t search;
|
|
search.m_pProp = (SendProp *)pSendProp;
|
|
int idx = lookup.Find( search );
|
|
if ( idx == lookup.InvalidIndex() )
|
|
{
|
|
recvProps[iSendProp] = 0;
|
|
}
|
|
else
|
|
{
|
|
recvProps[iSendProp] = lookup[ idx ].m_pMatchingRecvProp;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RecvTable_CreateDecoders( const CStandardSendProxies *pSendProxies, bool bAllowMismatches, bool *pAnyMismatches )
|
|
{
|
|
DTI_Init();
|
|
|
|
SETUP_VISIT();
|
|
|
|
if ( pAnyMismatches )
|
|
{
|
|
*pAnyMismatches = false;
|
|
}
|
|
|
|
// First, now that we've supposedly received all the SendTables that we need,
|
|
// set their datatable child pointers.
|
|
if ( !SetupClientSendTableHierarchy() )
|
|
return false;
|
|
|
|
bool bRet = true;
|
|
|
|
FOR_EACH_LL( g_RecvDecoders, i )
|
|
{
|
|
CRecvDecoder *pDecoder = g_RecvDecoders[i];
|
|
|
|
|
|
// It should already have been linked to its ClientSendTable.
|
|
Assert( pDecoder->m_pClientSendTable );
|
|
if ( !pDecoder->m_pClientSendTable )
|
|
return false;
|
|
|
|
|
|
// For each decoder, precalculate the SendTable's flat property list.
|
|
if ( !pDecoder->m_Precalc.SetupFlatPropertyArray() )
|
|
return false;
|
|
|
|
CUtlRBTree< MatchingProp_t, unsigned short > PropLookup( 0, 0, MatchingProp_t::LessFunc );
|
|
|
|
// Now match RecvProp with SendProps.
|
|
if ( !MatchRecvPropsToSendProps_R( PropLookup, pDecoder->GetSendTable()->m_pNetTableName, pDecoder->GetSendTable(), pDecoder->GetRecvTable(), bAllowMismatches, pAnyMismatches ) )
|
|
{
|
|
bRet = false;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Now fill out the matching RecvProp array.
|
|
CSendTablePrecalc *pPrecalc = &pDecoder->m_Precalc;
|
|
CopySendPropsToRecvProps( PropLookup, pPrecalc->m_Props, pDecoder->m_Props );
|
|
CopySendPropsToRecvProps( PropLookup, pPrecalc->m_DatatableProps, pDecoder->m_DatatableProps );
|
|
|
|
DTI_HookRecvDecoder( pDecoder );
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
bool RecvTable_Decode(
|
|
RecvTable *pTable,
|
|
void *pStruct,
|
|
SerializedEntityHandle_t dest,
|
|
int objectID
|
|
)
|
|
{
|
|
CRecvDecoder *pDecoder = pTable->m_pDecoder;
|
|
ErrorIfNot( pDecoder,
|
|
("RecvTable_Decode: table '%s' missing a decoder.", pTable->GetName())
|
|
);
|
|
|
|
CSerializedEntity *pEntity = reinterpret_cast< CSerializedEntity * >( dest );
|
|
Assert( pEntity );
|
|
|
|
// While there are properties, decode them.. walk the stack as you go.
|
|
CClientDatatableStack theStack( pDecoder, (unsigned char*)pStruct, objectID );
|
|
theStack.Init( false, false );
|
|
|
|
bf_read buf;
|
|
buf.SetDebugName( "CFlattenedSerializer::Decode" );
|
|
pEntity->StartReading( buf );
|
|
|
|
CFieldPath path;
|
|
|
|
int nDataOffset;
|
|
int nNextDataOffset;
|
|
|
|
for ( int nFieldIndex = 0 ; nFieldIndex < pEntity->GetFieldCount() ; ++nFieldIndex )
|
|
{
|
|
pEntity->GetField( nFieldIndex, path, &nDataOffset, &nNextDataOffset );
|
|
buf.Seek( nDataOffset );
|
|
|
|
theStack.SeekToProp( path );
|
|
|
|
const RecvProp *pProp = pDecoder->GetProp( path );
|
|
|
|
DecodeInfo decodeInfo;
|
|
decodeInfo.m_pStruct = theStack.GetCurStructBase();
|
|
|
|
if ( pProp )
|
|
{
|
|
decodeInfo.m_pData = theStack.GetCurStructBase() + pProp->GetOffset();
|
|
}
|
|
else
|
|
{
|
|
// They're allowed to be missing props here if they're playing back a demo.
|
|
// This allows us to change the datatables and still preserve old demos.
|
|
decodeInfo.m_pData = NULL;
|
|
}
|
|
|
|
decodeInfo.m_pRecvProp = theStack.IsCurProxyValid() ? pProp : NULL; // Just skip the data if the proxies are screwed.
|
|
decodeInfo.m_pProp = pDecoder->GetSendProp( path );
|
|
decodeInfo.m_pIn = &buf;
|
|
decodeInfo.m_ObjectID = objectID;
|
|
|
|
g_PropTypeFns[ decodeInfo.m_pProp->GetType() ].Decode( &decodeInfo );
|
|
}
|
|
|
|
return !buf.IsOverflowed();
|
|
}
|
|
|
|
void RecvTable_DecodeZeros( RecvTable *pTable, void *pStruct, int objectID )
|
|
{
|
|
CRecvDecoder *pDecoder = pTable->m_pDecoder;
|
|
ErrorIfNot( pDecoder,
|
|
("RecvTable_DecodeZeros: table '%s' missing a decoder.", pTable->GetName())
|
|
);
|
|
|
|
// While there are properties, decode them.. walk the stack as you go.
|
|
CClientDatatableStack theStack( pDecoder, (unsigned char*)pStruct, objectID );
|
|
|
|
theStack.Init( false, false );
|
|
|
|
for ( int iProp=0; iProp < pDecoder->GetNumProps(); iProp++ )
|
|
{
|
|
theStack.SeekToProp( iProp );
|
|
|
|
const RecvProp *pProp = pDecoder->GetProp( iProp );
|
|
|
|
DecodeInfo decodeInfo;
|
|
decodeInfo.m_pStruct = theStack.GetCurStructBase();
|
|
decodeInfo.m_pData = theStack.GetCurStructBase() + pProp->GetOffset();
|
|
decodeInfo.m_pRecvProp = theStack.IsCurProxyValid() ? pProp : NULL; // Just skip the data if the proxies are screwed.
|
|
decodeInfo.m_pProp = pDecoder->GetSendProp( iProp );
|
|
decodeInfo.m_pIn = NULL;
|
|
decodeInfo.m_ObjectID = objectID;
|
|
|
|
g_PropTypeFns[pProp->GetType()].DecodeZero( &decodeInfo );
|
|
}
|
|
}
|
|
|
|
|
|
// Copies pProp's state from pIn to pOut. pDecodeInfo MUST be setup by calling InitDecodeInfoForSkippingProps
|
|
// with pIn.
|
|
//
|
|
// NOTE: this routine isn't optimal. If it shows up on the profiles, then it's easy to
|
|
// make this fast by adding a special routine to copy a property's state to PropTypeFns.
|
|
static void CopyPropState(
|
|
CRecvDecoder *pDecoder,
|
|
int iSendProp,
|
|
bf_read *pIn,
|
|
CDeltaBitsWriter *pOut
|
|
)
|
|
{
|
|
const SendProp *pProp = pDecoder->GetSendProp( iSendProp );
|
|
|
|
int iStartBit = pIn->GetNumBitsRead();
|
|
|
|
// skip over data
|
|
SkipPropData( pIn, pProp );
|
|
|
|
// Figure out how many bits it took.
|
|
int nBits = pIn->GetNumBitsRead() - iStartBit;
|
|
|
|
// Copy the data
|
|
pIn->Seek( iStartBit );
|
|
|
|
pOut->WritePropIndex( iSendProp );
|
|
pOut->GetBitBuf()->WriteBitsFromBuffer( pIn, nBits );
|
|
}
|
|
|
|
|
|
bool RecvTable_MergeDeltas(
|
|
RecvTable *pTable,
|
|
|
|
SerializedEntityHandle_t oldState, // Can be invalid
|
|
SerializedEntityHandle_t newState,
|
|
SerializedEntityHandle_t mergedState,
|
|
|
|
int objectID,
|
|
CUtlVector< int > *pVecChanges
|
|
)
|
|
{
|
|
Assert( SERIALIZED_ENTITY_HANDLE_INVALID != newState );
|
|
Assert( SERIALIZED_ENTITY_HANDLE_INVALID != mergedState );
|
|
|
|
CSerializedEntity *pOldState = oldState != SERIALIZED_ENTITY_HANDLE_INVALID ? reinterpret_cast< CSerializedEntity * >( oldState ) : NULL;
|
|
|
|
CSerializedEntity *pNewState = reinterpret_cast< CSerializedEntity * >( newState );
|
|
Assert( pNewState );
|
|
|
|
CSerializedEntity *pMergedState = reinterpret_cast< CSerializedEntity * >( mergedState );
|
|
Assert( pMergedState );
|
|
|
|
ErrorIfNot( pTable && pNewState && pMergedState, ("RecvTable_MergeDeltas: invalid parameters passed.")
|
|
);
|
|
|
|
CRecvDecoder *pDecoder = pTable->m_pDecoder;
|
|
ErrorIfNot( pDecoder, ("RecvTable_MergeDeltas: table '%s' is missing its decoder.", pTable->GetName()) );
|
|
|
|
CSerializedEntityFieldIterator oldIterator( pOldState );
|
|
CSerializedEntityFieldIterator newIterator( pNewState );
|
|
bf_read oldBits, newBits;
|
|
oldBits.SetDebugName( "CFlattenedSerializer::MergeDeltas: oldBits" );
|
|
newBits.SetDebugName( "CFlattenedSerializer::MergeDeltas: newBits" );
|
|
|
|
if ( pOldState )
|
|
{
|
|
pOldState->StartReading( oldBits );
|
|
}
|
|
pNewState->StartReading( newBits );
|
|
|
|
pMergedState->Clear();
|
|
|
|
const CFieldPath *oldFieldPath = oldIterator.FirstPtr();
|
|
const CFieldPath *newFieldPath = newIterator.FirstPtr();
|
|
|
|
uint8 packedData[MAX_PACKEDENTITY_DATA];
|
|
bf_write fieldDataBuf( "CFlattenedSerializer::WriteFieldList fieldDataBuf", packedData, sizeof( packedData ) );
|
|
|
|
while ( 1 )
|
|
{
|
|
// Write any properties in the previous state that aren't in the new state.
|
|
while ( *oldFieldPath < *newFieldPath )
|
|
{
|
|
pMergedState->AddPathAndOffset( *oldFieldPath, fieldDataBuf.GetNumBitsWritten() );
|
|
|
|
oldBits.Seek( oldIterator.GetOffset() );
|
|
fieldDataBuf.WriteBitsFromBuffer( &oldBits, oldIterator.GetLength() );
|
|
|
|
oldFieldPath = oldIterator.NextPtr();
|
|
}
|
|
|
|
if ( *newFieldPath == PROP_SENTINEL )
|
|
break;
|
|
|
|
// Check if we're at the end here so the while() statement above can seek the old buffer
|
|
// to its end too.
|
|
bool bBoth = ( *oldFieldPath == *newFieldPath );
|
|
// If the old state has this property too, then just skip over its data.
|
|
if ( bBoth )
|
|
{
|
|
oldFieldPath = oldIterator.NextPtr();
|
|
}
|
|
|
|
pMergedState->AddPathAndOffset( *newFieldPath, fieldDataBuf.GetNumBitsWritten() );
|
|
newBits.Seek( newIterator.GetOffset() );
|
|
fieldDataBuf.WriteBitsFromBuffer( &newBits, newIterator.GetLength() );
|
|
|
|
if ( pVecChanges )
|
|
{
|
|
pVecChanges->AddToTail( *newFieldPath );
|
|
}
|
|
|
|
newFieldPath = newIterator.NextPtr();
|
|
}
|
|
|
|
pMergedState->PackWithFieldData( packedData, fieldDataBuf.GetNumBitsWritten() );
|
|
|
|
ErrorIfNot( !fieldDataBuf.IsOverflowed(), ("RecvTable_MergeDeltas: overflowed in RecvTable '%s'.", pTable->GetName() ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
template< bool bDTIEnabled >
|
|
bool RecvTable_ReadFieldList_Guts(
|
|
RecvTable *pTable,
|
|
|
|
bf_read &buf,
|
|
SerializedEntityHandle_t dest,
|
|
int nObjectId
|
|
)
|
|
{
|
|
CSerializedEntity *pEntity = reinterpret_cast< CSerializedEntity * >( dest );
|
|
Assert( pEntity );
|
|
|
|
pEntity->Clear();
|
|
|
|
CUtlVector< int > fieldBits;
|
|
pEntity->ReadFieldPaths( &buf, bDTIEnabled ? &fieldBits : NULL );
|
|
|
|
ErrorIfNot( pTable, ("RecvTable_ReadFieldListt: Missing RecvTable for class\n" ) );
|
|
if ( !pTable )
|
|
return false;
|
|
|
|
CRecvDecoder *pDecoder = pTable->m_pDecoder;
|
|
ErrorIfNot( pDecoder, ("RecvTable_ReadFieldList: table '%s' missing a decoder.", pTable->GetName()) );
|
|
|
|
// Remember where the "data" payload started
|
|
int nStartBit = buf.GetNumBitsRead();
|
|
|
|
CFieldPath path;
|
|
for ( int nFieldIndex = 0; nFieldIndex < pEntity->GetFieldCount(); ++nFieldIndex )
|
|
{
|
|
int nDataOffset = buf.GetNumBitsRead();
|
|
|
|
path = pEntity->GetFieldPath( nFieldIndex );
|
|
pEntity->SetFieldDataBitOffset( nFieldIndex, nDataOffset - nStartBit ); // Offset from start of data payload
|
|
|
|
const SendProp *pSendProp = pDecoder->GetSendProp( path );
|
|
g_PropTypeFns[ pSendProp->GetType() ].SkipProp( pSendProp, &buf );
|
|
// buffer now just after payload
|
|
|
|
if ( bDTIEnabled )
|
|
{
|
|
DTI_HookDeltaBits( pDecoder, path, buf.GetNumBitsRead() - nDataOffset, fieldBits[ nFieldIndex ] );
|
|
}
|
|
}
|
|
|
|
int nLastBit = buf.GetNumBitsRead();
|
|
|
|
// Rewind
|
|
buf.Seek( nStartBit );
|
|
// Copy
|
|
pEntity->PackWithFieldData( buf, nLastBit - nStartBit );
|
|
// Put head back to end
|
|
buf.Seek( nLastBit );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RecvTable_ReadFieldList(
|
|
RecvTable *pTable,
|
|
|
|
bf_read &buf,
|
|
SerializedEntityHandle_t dest,
|
|
int nObjectId,
|
|
bool bUpdateDTI
|
|
)
|
|
{
|
|
if ( g_bDTIEnabled && bUpdateDTI )
|
|
{
|
|
return RecvTable_ReadFieldList_Guts< true >( pTable, buf, dest, nObjectId );
|
|
}
|
|
|
|
return RecvTable_ReadFieldList_Guts< false >( pTable, buf, dest, nObjectId );
|
|
}
|
|
|