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.
1046 lines
26 KiB
1046 lines
26 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include <tier0/dbg.h>
|
||
|
#include <tier0/vprof.h>
|
||
|
#include <tier0/icommandline.h>
|
||
|
#include <commonmacros.h>
|
||
|
#include <checksum_crc.h>
|
||
|
|
||
|
#include "dt_send_eng.h"
|
||
|
#include "dt_encode.h"
|
||
|
#include "dt_instrumentation_server.h"
|
||
|
#include "dt_stack.h"
|
||
|
#include "common.h"
|
||
|
#include "packed_entity.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include <tier0/memdbgon.h>
|
||
|
|
||
|
extern int host_framecount;
|
||
|
CRC32_t SendTable_ComputeCRC();
|
||
|
|
||
|
class CSendTablePrecalc;
|
||
|
class CSendNode;
|
||
|
|
||
|
extern bool Sendprop_UsingDebugWatch();
|
||
|
|
||
|
|
||
|
// This stack doesn't actually call any proxies. It uses the CSendProxyRecipients to tell
|
||
|
// what can be sent to the specified client.
|
||
|
class CPropCullStack : public CDatatableStack
|
||
|
{
|
||
|
public:
|
||
|
CPropCullStack(
|
||
|
CSendTablePrecalc *pPrecalc,
|
||
|
int iClient,
|
||
|
const CSendProxyRecipients *pOldStateProxies,
|
||
|
const int nOldStateProxies,
|
||
|
const CSendProxyRecipients *pNewStateProxies,
|
||
|
const int nNewStateProxies
|
||
|
) :
|
||
|
|
||
|
CDatatableStack( pPrecalc, (unsigned char*)1, -1 ),
|
||
|
m_pOldStateProxies( pOldStateProxies ),
|
||
|
m_nOldStateProxies( nOldStateProxies ),
|
||
|
m_pNewStateProxies( pNewStateProxies ),
|
||
|
m_nNewStateProxies( nNewStateProxies )
|
||
|
{
|
||
|
m_pPrecalc = pPrecalc;
|
||
|
m_iClient = iClient;
|
||
|
}
|
||
|
|
||
|
inline unsigned char* CallPropProxy( CSendNode *pNode, int iProp, unsigned char *pStructBase )
|
||
|
{
|
||
|
if ( pNode->GetDataTableProxyIndex() == DATATABLE_PROXY_INDEX_NOPROXY )
|
||
|
{
|
||
|
return (unsigned char*)1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Assert( pNode->GetDataTableProxyIndex() < m_nNewStateProxies );
|
||
|
bool bCur = m_pNewStateProxies[ pNode->GetDataTableProxyIndex() ].m_Bits.Get( m_iClient ) != 0;
|
||
|
|
||
|
if ( m_pOldStateProxies )
|
||
|
{
|
||
|
Assert( pNode->GetDataTableProxyIndex() < m_nOldStateProxies );
|
||
|
bool bPrev = m_pOldStateProxies[ pNode->GetDataTableProxyIndex() ].m_Bits.Get( m_iClient ) != 0;
|
||
|
if ( bPrev != bCur )
|
||
|
{
|
||
|
if ( bPrev )
|
||
|
{
|
||
|
// Old state had the data and the new state doesn't.
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Add ALL properties under this proxy because the previous state didn't have any of them.
|
||
|
for ( int i=0; i < pNode->m_nRecursiveProps; i++ )
|
||
|
{
|
||
|
if ( m_nNewProxyProps < ARRAYSIZE( m_NewProxyProps ) )
|
||
|
{
|
||
|
m_NewProxyProps[m_nNewProxyProps] = pNode->m_iFirstRecursiveProp + i;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Error( "CPropCullStack::CallPropProxy - overflowed m_nNewProxyProps" );
|
||
|
}
|
||
|
|
||
|
++m_nNewProxyProps;
|
||
|
}
|
||
|
|
||
|
// Tell the outer loop that writes to m_pOutProps not to write anything from this
|
||
|
// proxy since we just did.
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (unsigned char*)bCur;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
virtual void RecurseAndCallProxies( CSendNode *pNode, unsigned char *pStructBase )
|
||
|
{
|
||
|
// Remember where the game code pointed us for this datatable's data so
|
||
|
m_pProxies[ pNode->GetRecursiveProxyIndex() ] = pStructBase;
|
||
|
|
||
|
for ( int iChild=0; iChild < pNode->GetNumChildren(); iChild++ )
|
||
|
{
|
||
|
CSendNode *pCurChild = pNode->GetChild( iChild );
|
||
|
|
||
|
unsigned char *pNewStructBase = NULL;
|
||
|
if ( pStructBase )
|
||
|
{
|
||
|
pNewStructBase = CallPropProxy( pCurChild, pCurChild->m_iDatatableProp, pStructBase );
|
||
|
}
|
||
|
|
||
|
RecurseAndCallProxies( pCurChild, pNewStructBase );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void AddProp( int iProp )
|
||
|
{
|
||
|
if ( m_nOutProps < m_nMaxOutProps )
|
||
|
{
|
||
|
m_pOutProps[m_nOutProps] = iProp;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Error( "CPropCullStack::AddProp - m_pOutProps overflowed" );
|
||
|
}
|
||
|
|
||
|
++m_nOutProps;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CullPropsFromProxies( const int *pStartProps, int nStartProps, int *pOutProps, int nMaxOutProps )
|
||
|
{
|
||
|
m_nOutProps = 0;
|
||
|
m_pOutProps = pOutProps;
|
||
|
m_nMaxOutProps = nMaxOutProps;
|
||
|
m_nNewProxyProps = 0;
|
||
|
|
||
|
Init();
|
||
|
|
||
|
// This list will have any newly available props written into it. Write a sentinel at the end.
|
||
|
m_NewProxyProps[m_nNewProxyProps] = -1; // invalid marker
|
||
|
int *pCurNewProxyProp = m_NewProxyProps;
|
||
|
|
||
|
for ( int i=0; i < nStartProps; i++ )
|
||
|
{
|
||
|
int iProp = pStartProps[i];
|
||
|
|
||
|
// Fill in the gaps with any properties that are newly enabled by the proxies.
|
||
|
while ( (unsigned int) *pCurNewProxyProp < (unsigned int) iProp )
|
||
|
{
|
||
|
AddProp( *pCurNewProxyProp );
|
||
|
++pCurNewProxyProp;
|
||
|
}
|
||
|
|
||
|
// Now write this property's index if the proxies are allowing this property to be written.
|
||
|
if ( IsPropProxyValid( iProp ) )
|
||
|
{
|
||
|
AddProp( iProp );
|
||
|
|
||
|
// avoid that we add it twice.
|
||
|
if ( *pCurNewProxyProp == iProp )
|
||
|
++pCurNewProxyProp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// add any remaining new proxy props
|
||
|
while ( (unsigned int) *pCurNewProxyProp < MAX_DATATABLE_PROPS )
|
||
|
{
|
||
|
AddProp( *pCurNewProxyProp );
|
||
|
++pCurNewProxyProp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int GetNumOutProps()
|
||
|
{
|
||
|
return m_nOutProps;
|
||
|
}
|
||
|
|
||
|
|
||
|
private:
|
||
|
CSendTablePrecalc *m_pPrecalc;
|
||
|
int m_iClient; // Which client it's encoding out for.
|
||
|
const CSendProxyRecipients *m_pOldStateProxies;
|
||
|
const int m_nOldStateProxies;
|
||
|
|
||
|
const CSendProxyRecipients *m_pNewStateProxies;
|
||
|
const int m_nNewStateProxies;
|
||
|
|
||
|
// The output property list.
|
||
|
int *m_pOutProps;
|
||
|
int m_nMaxOutProps;
|
||
|
int m_nOutProps;
|
||
|
|
||
|
int m_NewProxyProps[MAX_DATATABLE_PROPS+1];
|
||
|
int m_nNewProxyProps;
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
// ----------------------------------------------------------------------------- //
|
||
|
// CEncodeInfo
|
||
|
// Used by SendTable_Encode.
|
||
|
// ----------------------------------------------------------------------------- //
|
||
|
class CEncodeInfo : public CServerDatatableStack
|
||
|
{
|
||
|
public:
|
||
|
CEncodeInfo( CSendTablePrecalc *pPrecalc, unsigned char *pStructBase, int objectID, bf_write *pOut ) :
|
||
|
CServerDatatableStack( pPrecalc, pStructBase, objectID ),
|
||
|
m_DeltaBitsWriter( pOut )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
CDeltaBitsWriter m_DeltaBitsWriter;
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
// ------------------------------------------------------------------------ //
|
||
|
// Globals.
|
||
|
// ------------------------------------------------------------------------ //
|
||
|
|
||
|
CUtlVector< SendTable* > g_SendTables;
|
||
|
CRC32_t g_SendTableCRC = 0;
|
||
|
|
||
|
|
||
|
|
||
|
// ------------------------------------------------------------------------ //
|
||
|
// SendTable functions.
|
||
|
// ------------------------------------------------------------------------ //
|
||
|
|
||
|
static bool s_debug_info_shown = false;
|
||
|
static int s_debug_bits_start = 0;
|
||
|
|
||
|
|
||
|
static inline void ShowEncodeDeltaWatchInfo(
|
||
|
const SendTable *pTable,
|
||
|
const SendProp *pProp,
|
||
|
bf_read &buffer,
|
||
|
const int objectID,
|
||
|
const int index )
|
||
|
{
|
||
|
if ( !ShouldWatchThisProp( pTable, objectID, pProp->GetName()) )
|
||
|
return;
|
||
|
|
||
|
static int lastframe = -1;
|
||
|
if ( host_framecount != lastframe )
|
||
|
{
|
||
|
lastframe = host_framecount;
|
||
|
ConDMsg( "delta entity: %i\n", objectID );
|
||
|
}
|
||
|
|
||
|
// 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_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( "+ %s %s, %s, index %i, bits %i, value %s\n", pTable->GetName(), pProp->GetName(), type, index, bits, value );
|
||
|
}
|
||
|
|
||
|
|
||
|
static FORCEINLINE void SendTable_EncodeProp( CEncodeInfo * pInfo, unsigned long iProp )
|
||
|
{
|
||
|
// Call their proxy to get the property's value.
|
||
|
DVariant var;
|
||
|
|
||
|
const SendProp *pProp = pInfo->GetCurProp();
|
||
|
unsigned char *pStructBase = pInfo->GetCurStructBase();
|
||
|
|
||
|
pProp->GetProxyFn()(
|
||
|
pProp,
|
||
|
pStructBase,
|
||
|
pStructBase + pProp->GetOffset(),
|
||
|
&var,
|
||
|
0, // iElement
|
||
|
pInfo->GetObjectID()
|
||
|
);
|
||
|
|
||
|
// Write the index.
|
||
|
pInfo->m_DeltaBitsWriter.WritePropIndex( iProp );
|
||
|
|
||
|
g_PropTypeFns[pProp->m_Type].Encode(
|
||
|
pStructBase,
|
||
|
&var,
|
||
|
pProp,
|
||
|
pInfo->m_DeltaBitsWriter.GetBitBuf(),
|
||
|
pInfo->GetObjectID()
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
static bool SendTable_IsPropZero( CEncodeInfo *pInfo, unsigned long iProp )
|
||
|
{
|
||
|
const SendProp *pProp = pInfo->GetCurProp();
|
||
|
|
||
|
// Call their proxy to get the property's value.
|
||
|
DVariant var;
|
||
|
unsigned char *pBase = pInfo->GetCurStructBase();
|
||
|
|
||
|
pProp->GetProxyFn()(
|
||
|
pProp,
|
||
|
pBase,
|
||
|
pBase + pProp->GetOffset(),
|
||
|
&var,
|
||
|
0, // iElement
|
||
|
pInfo->GetObjectID()
|
||
|
);
|
||
|
|
||
|
return g_PropTypeFns[pProp->m_Type].IsZero( pBase, &var, pProp );
|
||
|
}
|
||
|
|
||
|
|
||
|
int SendTable_CullPropsFromProxies(
|
||
|
const SendTable *pTable,
|
||
|
|
||
|
const int *pStartProps,
|
||
|
int nStartProps,
|
||
|
|
||
|
const int iClient,
|
||
|
|
||
|
const CSendProxyRecipients *pOldStateProxies,
|
||
|
const int nOldStateProxies,
|
||
|
|
||
|
const CSendProxyRecipients *pNewStateProxies,
|
||
|
const int nNewStateProxies,
|
||
|
|
||
|
int *pOutProps,
|
||
|
int nMaxOutProps
|
||
|
)
|
||
|
{
|
||
|
Assert( !( nNewStateProxies && !pNewStateProxies ) );
|
||
|
CPropCullStack stack( pTable->m_pPrecalc, iClient, pOldStateProxies, nOldStateProxies, pNewStateProxies, nNewStateProxies );
|
||
|
|
||
|
stack.CullPropsFromProxies( pStartProps, nStartProps, pOutProps, nMaxOutProps );
|
||
|
|
||
|
ErrorIfNot( stack.GetNumOutProps() <= nMaxOutProps, ("CullPropsFromProxies: overflow in '%s'.", pTable->GetName()) );
|
||
|
return stack.GetNumOutProps();
|
||
|
}
|
||
|
|
||
|
|
||
|
// compares properties and writes delta properties, it ignores reciepients
|
||
|
int SendTable_WriteAllDeltaProps(
|
||
|
const SendTable *pTable,
|
||
|
const void *pFromData,
|
||
|
const int nFromDataBits,
|
||
|
const void *pToData,
|
||
|
const int nToDataBits,
|
||
|
const int nObjectID,
|
||
|
bf_write *pBufOut )
|
||
|
{
|
||
|
// Calculate the delta props.
|
||
|
int deltaProps[MAX_DATATABLE_PROPS];
|
||
|
|
||
|
int nDeltaProps = SendTable_CalcDelta(
|
||
|
pTable,
|
||
|
pFromData,
|
||
|
nFromDataBits,
|
||
|
pToData,
|
||
|
nToDataBits,
|
||
|
deltaProps,
|
||
|
ARRAYSIZE( deltaProps ),
|
||
|
nObjectID );
|
||
|
|
||
|
// Write the properties.
|
||
|
SendTable_WritePropList(
|
||
|
pTable,
|
||
|
pToData, // object data
|
||
|
nToDataBits,
|
||
|
pBufOut, // output buffer
|
||
|
nObjectID,
|
||
|
deltaProps,
|
||
|
nDeltaProps
|
||
|
);
|
||
|
|
||
|
return nDeltaProps;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool SendTable_Encode(
|
||
|
const SendTable *pTable,
|
||
|
const void *pStruct,
|
||
|
bf_write *pOut,
|
||
|
int objectID,
|
||
|
CUtlMemory<CSendProxyRecipients> *pRecipients,
|
||
|
bool bNonZeroOnly
|
||
|
)
|
||
|
{
|
||
|
CSendTablePrecalc *pPrecalc = pTable->m_pPrecalc;
|
||
|
ErrorIfNot( pPrecalc, ("SendTable_Encode: Missing m_pPrecalc for SendTable %s.", pTable->m_pNetTableName) );
|
||
|
if ( pRecipients )
|
||
|
{
|
||
|
ErrorIfNot( pRecipients->NumAllocated() >= pPrecalc->GetNumDataTableProxies(), ("SendTable_Encode: pRecipients array too small.") );
|
||
|
}
|
||
|
|
||
|
VPROF( "SendTable_Encode" );
|
||
|
|
||
|
CServerDTITimer timer( pTable, SERVERDTI_ENCODE );
|
||
|
|
||
|
// Setup all the info we'll be walking the tree with.
|
||
|
CEncodeInfo info( pPrecalc, (unsigned char*)pStruct, objectID, pOut );
|
||
|
info.m_pRecipients = pRecipients; // optional buffer to store the bits for which clients get what data.
|
||
|
|
||
|
info.Init();
|
||
|
|
||
|
int iNumProps = pPrecalc->GetNumProps();
|
||
|
|
||
|
for ( int iProp=0; iProp < iNumProps; iProp++ )
|
||
|
{
|
||
|
// skip if we don't have a valid prop proxy
|
||
|
if ( !info.IsPropProxyValid( iProp ) )
|
||
|
continue;
|
||
|
|
||
|
info.SeekToProp( iProp );
|
||
|
|
||
|
// skip empty prop if we only encode non-zero values
|
||
|
if ( bNonZeroOnly && SendTable_IsPropZero(&info, iProp) )
|
||
|
continue;
|
||
|
|
||
|
SendTable_EncodeProp( &info, iProp );
|
||
|
}
|
||
|
|
||
|
return !pOut->IsOverflowed();
|
||
|
}
|
||
|
|
||
|
|
||
|
void SendTable_WritePropList(
|
||
|
const SendTable *pTable,
|
||
|
const void *pState,
|
||
|
const int nBits,
|
||
|
bf_write *pOut,
|
||
|
const int objectID,
|
||
|
const int *pCheckProps,
|
||
|
const int nCheckProps
|
||
|
)
|
||
|
{
|
||
|
if ( nCheckProps == 0 )
|
||
|
{
|
||
|
// Write single final zero bit, signifying that there no changed properties
|
||
|
pOut->WriteOneBit( 0 );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bool bDebugWatch = Sendprop_UsingDebugWatch();
|
||
|
|
||
|
s_debug_info_shown = false;
|
||
|
s_debug_bits_start = pOut->GetNumBitsWritten();
|
||
|
|
||
|
CSendTablePrecalc *pPrecalc = pTable->m_pPrecalc;
|
||
|
CDeltaBitsWriter deltaBitsWriter( pOut );
|
||
|
|
||
|
bf_read inputBuffer( "SendTable_WritePropList->inputBuffer", pState, BitByte( nBits ), nBits );
|
||
|
CDeltaBitsReader inputBitsReader( &inputBuffer );
|
||
|
|
||
|
// Ok, they want to specify a small list of properties to check.
|
||
|
unsigned int iToProp = inputBitsReader.ReadNextPropIndex();
|
||
|
int i = 0;
|
||
|
while ( i < nCheckProps )
|
||
|
{
|
||
|
// Seek the 'to' state to the current property we want to check.
|
||
|
while ( iToProp < (unsigned int) pCheckProps[i] )
|
||
|
{
|
||
|
inputBitsReader.SkipPropData( pPrecalc->GetProp( iToProp ) );
|
||
|
iToProp = inputBitsReader.ReadNextPropIndex();
|
||
|
}
|
||
|
|
||
|
if ( iToProp >= MAX_DATATABLE_PROPS )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( iToProp == (unsigned int) pCheckProps[i] )
|
||
|
{
|
||
|
const SendProp *pProp = pPrecalc->GetProp( iToProp );
|
||
|
|
||
|
// Show debug stuff.
|
||
|
if ( bDebugWatch )
|
||
|
{
|
||
|
ShowEncodeDeltaWatchInfo( pTable, pProp, inputBuffer, objectID, iToProp );
|
||
|
}
|
||
|
|
||
|
// See how many bits the data for this property takes up.
|
||
|
int nToStateBits;
|
||
|
int iStartBit = pOut->GetNumBitsWritten();
|
||
|
|
||
|
deltaBitsWriter.WritePropIndex( iToProp );
|
||
|
inputBitsReader.CopyPropData( deltaBitsWriter.GetBitBuf(), pProp );
|
||
|
|
||
|
nToStateBits = pOut->GetNumBitsWritten() - iStartBit;
|
||
|
|
||
|
TRACE_PACKET( ( " Send Field (%s) = %d (%d bytes)\n", pProp->GetName(), nToStateBits, ( nToStateBits + 7 ) / 8 ) );
|
||
|
|
||
|
// Seek to the next prop.
|
||
|
iToProp = inputBitsReader.ReadNextPropIndex();
|
||
|
}
|
||
|
|
||
|
++i;
|
||
|
}
|
||
|
|
||
|
if ( s_debug_info_shown )
|
||
|
{
|
||
|
int bits = pOut->GetNumBitsWritten() - s_debug_bits_start;
|
||
|
ConDMsg( "= %i bits (%i bytes)\n", bits, Bits2Bytes(bits) );
|
||
|
}
|
||
|
|
||
|
inputBitsReader.ForceFinished(); // avoid a benign assert
|
||
|
}
|
||
|
|
||
|
|
||
|
int SendTable_CalcDelta(
|
||
|
const SendTable *pTable,
|
||
|
|
||
|
const void *pFromState,
|
||
|
const int nFromBits,
|
||
|
|
||
|
const void *pToState,
|
||
|
const int nToBits,
|
||
|
|
||
|
int *pDeltaProps,
|
||
|
int nMaxDeltaProps,
|
||
|
|
||
|
const int objectID
|
||
|
)
|
||
|
{
|
||
|
CServerDTITimer timer( pTable, SERVERDTI_CALCDELTA );
|
||
|
|
||
|
int *pDeltaPropsBase = pDeltaProps;
|
||
|
int *pDeltaPropsEnd = pDeltaProps + nMaxDeltaProps;
|
||
|
|
||
|
VPROF( "SendTable_CalcDelta" );
|
||
|
|
||
|
// Trivial reject.
|
||
|
//if ( CompareBitArrays( pFromState, pToState, nFromBits, nToBits ) )
|
||
|
//{
|
||
|
// return 0;
|
||
|
//}
|
||
|
|
||
|
CSendTablePrecalc* pPrecalc = pTable->m_pPrecalc;
|
||
|
|
||
|
bf_read toBits( "SendTable_CalcDelta/toBits", pToState, BitByte(nToBits), nToBits );
|
||
|
CDeltaBitsReader toBitsReader( &toBits );
|
||
|
unsigned int iToProp = toBitsReader.ReadNextPropIndex();
|
||
|
|
||
|
if ( pFromState )
|
||
|
{
|
||
|
bf_read fromBits( "SendTable_CalcDelta/fromBits", pFromState, BitByte(nFromBits), nFromBits );
|
||
|
CDeltaBitsReader fromBitsReader( &fromBits );
|
||
|
unsigned int iFromProp = fromBitsReader.ReadNextPropIndex();
|
||
|
|
||
|
for ( ; iToProp < MAX_DATATABLE_PROPS; iToProp = toBitsReader.ReadNextPropIndex() )
|
||
|
{
|
||
|
Assert( (int)iToProp >= 0 );
|
||
|
|
||
|
// Skip any properties in the from state that aren't in the to state.
|
||
|
while ( iFromProp < iToProp )
|
||
|
{
|
||
|
fromBitsReader.SkipPropData( pPrecalc->GetProp( iFromProp ) );
|
||
|
iFromProp = fromBitsReader.ReadNextPropIndex();
|
||
|
}
|
||
|
|
||
|
if ( iFromProp == iToProp )
|
||
|
{
|
||
|
// The property is in both states, so compare them and write the index
|
||
|
// if the states are different.
|
||
|
if ( fromBitsReader.ComparePropData( &toBitsReader, pPrecalc->GetProp( iToProp ) ) )
|
||
|
{
|
||
|
*pDeltaProps++ = iToProp;
|
||
|
if ( pDeltaProps >= pDeltaPropsEnd )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Seek to the next property.
|
||
|
iFromProp = fromBitsReader.ReadNextPropIndex();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Only the 'to' state has this property, so just skip its data and register a change.
|
||
|
toBitsReader.SkipPropData( pPrecalc->GetProp( iToProp ) );
|
||
|
*pDeltaProps++ = iToProp;
|
||
|
if ( pDeltaProps >= pDeltaPropsEnd )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Assert( iToProp == ~0u );
|
||
|
|
||
|
fromBitsReader.ForceFinished();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for ( ; iToProp != (uint)-1; iToProp = toBitsReader.ReadNextPropIndex() )
|
||
|
{
|
||
|
Assert( (int)iToProp >= 0 && iToProp < MAX_DATATABLE_PROPS );
|
||
|
|
||
|
const SendProp *pProp = pPrecalc->GetProp( iToProp );
|
||
|
if ( !g_PropTypeFns[pProp->m_Type].IsEncodedZero( pProp, &toBits ) )
|
||
|
{
|
||
|
*pDeltaProps++ = iToProp;
|
||
|
if ( pDeltaProps >= pDeltaPropsEnd )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return the # of properties that changed between 'from' and 'to'.
|
||
|
return pDeltaProps - pDeltaPropsBase;
|
||
|
}
|
||
|
|
||
|
bool SendTable_WriteInfos( SendTable *pTable, bf_write *pBuf )
|
||
|
{
|
||
|
pBuf->WriteString( pTable->GetName() );
|
||
|
pBuf->WriteUBitLong( pTable->GetNumProps(), PROPINFOBITS_NUMPROPS );
|
||
|
|
||
|
// Send each property.
|
||
|
for ( int iProp=0; iProp < pTable->m_nProps; iProp++ )
|
||
|
{
|
||
|
const SendProp *pProp = &pTable->m_pProps[iProp];
|
||
|
|
||
|
pBuf->WriteUBitLong( (unsigned int)pProp->m_Type, PROPINFOBITS_TYPE );
|
||
|
pBuf->WriteString( pProp->GetName() );
|
||
|
// we now have some flags that aren't networked so strip them off
|
||
|
unsigned int networkFlags = pProp->GetFlags() & ((1<<PROPINFOBITS_FLAGS)-1);
|
||
|
pBuf->WriteUBitLong( networkFlags, PROPINFOBITS_FLAGS );
|
||
|
|
||
|
if( pProp->m_Type == DPT_DataTable )
|
||
|
{
|
||
|
// Just write the name and it will be able to reuse the table with a matching name.
|
||
|
pBuf->WriteString( pProp->GetDataTable()->m_pNetTableName );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( pProp->IsExcludeProp() )
|
||
|
{
|
||
|
pBuf->WriteString( pProp->GetExcludeDTName() );
|
||
|
}
|
||
|
else if ( pProp->GetType() == DPT_Array )
|
||
|
{
|
||
|
pBuf->WriteUBitLong( pProp->GetNumElements(), PROPINFOBITS_NUMELEMENTS );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pBuf->WriteBitFloat( pProp->m_fLowValue );
|
||
|
pBuf->WriteBitFloat( pProp->m_fHighValue );
|
||
|
pBuf->WriteUBitLong( pProp->m_nBits, PROPINFOBITS_NUMBITS );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return !pBuf->IsOverflowed();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// Spits out warnings for invalid properties and forces property values to
|
||
|
// be in valid ranges for the encoders and decoders.
|
||
|
static void SendTable_Validate( CSendTablePrecalc *pPrecalc )
|
||
|
{
|
||
|
SendTable *pTable = pPrecalc->m_pSendTable;
|
||
|
for( int i=0; i < pTable->m_nProps; i++ )
|
||
|
{
|
||
|
SendProp *pProp = &pTable->m_pProps[i];
|
||
|
|
||
|
if ( pProp->GetArrayProp() )
|
||
|
{
|
||
|
if ( pProp->GetArrayProp()->GetType() == DPT_DataTable )
|
||
|
{
|
||
|
Error( "Invalid property: %s/%s (array of datatables) [on prop %d of %d (%s)].", pTable->m_pNetTableName, pProp->GetName(), i, pTable->m_nProps, pProp->GetArrayProp()->GetName() );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ErrorIfNot( pProp->GetNumElements() == 1, ("Prop %s/%s has an invalid element count for a non-array.", pTable->m_pNetTableName, pProp->GetName()) );
|
||
|
}
|
||
|
|
||
|
// Check for 1-bit signed properties (their value doesn't get down to the client).
|
||
|
if ( pProp->m_nBits == 1 && !(pProp->GetFlags() & SPROP_UNSIGNED) )
|
||
|
{
|
||
|
DataTable_Warning("SendTable prop %s::%s is a 1-bit signed property. Use SPROP_UNSIGNED or the client will never receive a value.\n", pTable->m_pNetTableName, pProp->GetName());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for ( int i = 0; i < pPrecalc->GetNumProps(); ++i )
|
||
|
{
|
||
|
const SendProp *pProp = pPrecalc->GetProp( i );
|
||
|
if ( pProp->GetFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT )
|
||
|
{
|
||
|
pTable->SetHasPropsEncodedAgainstTickcount( true );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void SendTable_CalcNextVectorElems( SendTable *pTable )
|
||
|
{
|
||
|
for ( int i=0; i < pTable->GetNumProps(); i++ )
|
||
|
{
|
||
|
SendProp *pProp = pTable->GetProp( i );
|
||
|
|
||
|
if ( pProp->GetType() == DPT_DataTable )
|
||
|
{
|
||
|
SendTable_CalcNextVectorElems( pProp->GetDataTable() );
|
||
|
}
|
||
|
else if ( pProp->GetOffset() < 0 )
|
||
|
{
|
||
|
pProp->SetOffset( -pProp->GetOffset() );
|
||
|
pProp->SetFlags( pProp->GetFlags() | SPROP_IS_A_VECTOR_ELEM );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static bool SendTable_InitTable( SendTable *pTable )
|
||
|
{
|
||
|
if( pTable->m_pPrecalc )
|
||
|
return true;
|
||
|
|
||
|
// Create the CSendTablePrecalc.
|
||
|
CSendTablePrecalc *pPrecalc = new CSendTablePrecalc;
|
||
|
pTable->m_pPrecalc = pPrecalc;
|
||
|
|
||
|
pPrecalc->m_pSendTable = pTable;
|
||
|
pTable->m_pPrecalc = pPrecalc;
|
||
|
|
||
|
SendTable_CalcNextVectorElems( pTable );
|
||
|
|
||
|
// Bind the instrumentation if -dti was specified.
|
||
|
pPrecalc->m_pDTITable = ServerDTI_HookTable( pTable );
|
||
|
|
||
|
// Setup its flat property array.
|
||
|
if ( !pPrecalc->SetupFlatPropertyArray() )
|
||
|
return false;
|
||
|
|
||
|
SendTable_Validate( pPrecalc );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void SendTable_TermTable( SendTable *pTable )
|
||
|
{
|
||
|
if( !pTable->m_pPrecalc )
|
||
|
return;
|
||
|
|
||
|
delete pTable->m_pPrecalc;
|
||
|
Assert( !pTable->m_pPrecalc ); // Make sure it unbound itself.
|
||
|
}
|
||
|
|
||
|
|
||
|
int SendTable_GetNumFlatProps( SendTable *pSendTable )
|
||
|
{
|
||
|
CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc;
|
||
|
ErrorIfNot( pPrecalc,
|
||
|
("SendTable_GetNumFlatProps: missing pPrecalc.")
|
||
|
);
|
||
|
return pPrecalc->GetNumProps();
|
||
|
}
|
||
|
|
||
|
CRC32_t SendTable_CRCTable( CRC32_t &crc, SendTable *pTable )
|
||
|
{
|
||
|
CRC32_ProcessBuffer( &crc, (void *)pTable->m_pNetTableName, Q_strlen( pTable->m_pNetTableName) );
|
||
|
|
||
|
int nProps = LittleLong( pTable->m_nProps );
|
||
|
CRC32_ProcessBuffer( &crc, (void *)&nProps, sizeof( pTable->m_nProps ) );
|
||
|
|
||
|
// Send each property.
|
||
|
for ( int iProp=0; iProp < pTable->m_nProps; iProp++ )
|
||
|
{
|
||
|
const SendProp *pProp = &pTable->m_pProps[iProp];
|
||
|
|
||
|
int type = LittleLong( pProp->m_Type );
|
||
|
CRC32_ProcessBuffer( &crc, (void *)&type, sizeof( type ) );
|
||
|
CRC32_ProcessBuffer( &crc, (void *)pProp->GetName() , Q_strlen( pProp->GetName() ) );
|
||
|
|
||
|
int flags = LittleLong( pProp->GetFlags() );
|
||
|
CRC32_ProcessBuffer( &crc, (void *)&flags, sizeof( flags ) );
|
||
|
|
||
|
if( pProp->m_Type == DPT_DataTable )
|
||
|
{
|
||
|
CRC32_ProcessBuffer( &crc, (void *)pProp->GetDataTable()->m_pNetTableName, Q_strlen( pProp->GetDataTable()->m_pNetTableName ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( pProp->IsExcludeProp() )
|
||
|
{
|
||
|
CRC32_ProcessBuffer( &crc, (void *)pProp->GetExcludeDTName(), Q_strlen( pProp->GetExcludeDTName() ) );
|
||
|
}
|
||
|
else if ( pProp->GetType() == DPT_Array )
|
||
|
{
|
||
|
int numelements = LittleLong( pProp->GetNumElements() );
|
||
|
CRC32_ProcessBuffer( &crc, (void *)&numelements, sizeof( numelements ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float lowvalue;
|
||
|
LittleFloat( &lowvalue, &pProp->m_fLowValue );
|
||
|
CRC32_ProcessBuffer( &crc, (void *)&lowvalue, sizeof( lowvalue ) );
|
||
|
|
||
|
float highvalue;
|
||
|
LittleFloat( &highvalue, &pProp->m_fHighValue );
|
||
|
CRC32_ProcessBuffer( &crc, (void *)&highvalue, sizeof( highvalue ) );
|
||
|
|
||
|
int bits = LittleLong( pProp->m_nBits );
|
||
|
CRC32_ProcessBuffer( &crc, (void *)&bits, sizeof( bits ) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return crc;
|
||
|
}
|
||
|
|
||
|
void SendTable_PrintStats( void )
|
||
|
{
|
||
|
int numTables = 0;
|
||
|
int numFloats = 0;
|
||
|
int numStrings = 0;
|
||
|
int numArrays = 0;
|
||
|
int numInts = 0;
|
||
|
int numVecs = 0;
|
||
|
int numVecXYs = 0;
|
||
|
int numSubTables = 0;
|
||
|
int numSendProps = 0;
|
||
|
int numFlatProps = 0;
|
||
|
int numExcludeProps = 0;
|
||
|
|
||
|
for ( int i=0; i < g_SendTables.Count(); i++ )
|
||
|
{
|
||
|
SendTable *st = g_SendTables[i];
|
||
|
|
||
|
numTables++;
|
||
|
numSendProps += st->GetNumProps();
|
||
|
numFlatProps += st->m_pPrecalc->GetNumProps();
|
||
|
|
||
|
for ( int j=0; j < st->GetNumProps(); j++ )
|
||
|
{
|
||
|
SendProp* sp = st->GetProp( j );
|
||
|
|
||
|
if ( sp->IsExcludeProp() )
|
||
|
{
|
||
|
numExcludeProps++;
|
||
|
continue; // no real sendprops
|
||
|
}
|
||
|
|
||
|
if ( sp->IsInsideArray() )
|
||
|
continue;
|
||
|
|
||
|
switch( sp->GetType() )
|
||
|
{
|
||
|
case DPT_Int : numInts++; break;
|
||
|
case DPT_Float : numFloats++; break;
|
||
|
case DPT_Vector : numVecs++; break;
|
||
|
case DPT_VectorXY : numVecXYs++; break;
|
||
|
case DPT_String : numStrings++; break;
|
||
|
case DPT_Array : numArrays++; break;
|
||
|
case DPT_DataTable : numSubTables++; break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Msg("Total Send Table stats\n");
|
||
|
Msg("Send Tables : %i\n", numTables );
|
||
|
Msg("Send Props : %i\n", numSendProps );
|
||
|
Msg("Flat Props : %i\n", numFlatProps );
|
||
|
Msg("Int Props : %i\n", numInts );
|
||
|
Msg("Float Props : %i\n", numFloats );
|
||
|
Msg("Vector Props : %i\n", numVecs );
|
||
|
Msg("VectorXY Props: %i\n", numVecXYs );
|
||
|
Msg("String Props : %i\n", numStrings );
|
||
|
Msg("Array Props : %i\n", numArrays );
|
||
|
Msg("Table Props : %i\n", numSubTables );
|
||
|
Msg("Exclu Props : %i\n", numExcludeProps );
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
bool SendTable_Init( SendTable **pTables, int nTables )
|
||
|
{
|
||
|
ErrorIfNot( g_SendTables.Count() == 0,
|
||
|
("SendTable_Init: called twice.")
|
||
|
);
|
||
|
|
||
|
// Initialize them all.
|
||
|
for ( int i=0; i < nTables; i++ )
|
||
|
{
|
||
|
if ( !SendTable_InitTable( pTables[i] ) )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Store off the SendTable list.
|
||
|
g_SendTables.CopyArray( pTables, nTables );
|
||
|
|
||
|
g_SendTableCRC = SendTable_ComputeCRC( );
|
||
|
|
||
|
if ( CommandLine()->FindParm("-dti" ) )
|
||
|
{
|
||
|
SendTable_PrintStats();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
void SendTable_Term()
|
||
|
{
|
||
|
// Term all the SendTables.
|
||
|
for ( int i=0; i < g_SendTables.Count(); i++ )
|
||
|
SendTable_TermTable( g_SendTables[i] );
|
||
|
|
||
|
// Clear the list of SendTables.
|
||
|
g_SendTables.Purge();
|
||
|
g_SendTableCRC = 0;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Computes the crc for all sendtables for the data sent in the class/table definitions
|
||
|
// Output : CRC32_t
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CRC32_t SendTable_ComputeCRC()
|
||
|
{
|
||
|
CRC32_t result;
|
||
|
CRC32_Init( &result );
|
||
|
|
||
|
// walk the tables and checksum them
|
||
|
int c = g_SendTables.Count();
|
||
|
for ( int i = 0 ; i < c; i++ )
|
||
|
{
|
||
|
SendTable *st = g_SendTables[ i ];
|
||
|
result = SendTable_CRCTable( result, st );
|
||
|
}
|
||
|
|
||
|
|
||
|
CRC32_Final( &result );
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
SendTable *SendTabe_GetTable(int index)
|
||
|
{
|
||
|
return g_SendTables[index];
|
||
|
}
|
||
|
|
||
|
int SendTable_GetNum()
|
||
|
{
|
||
|
return g_SendTables.Count();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Output : CRC32_t
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CRC32_t SendTable_GetCRC()
|
||
|
{
|
||
|
return g_SendTableCRC;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: check integrity of an unpacked entity send table
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool SendTable_CheckIntegrity( SendTable *pTable, const void *pData, const int nDataBits )
|
||
|
{
|
||
|
#ifdef _DEBUG
|
||
|
if ( pData == NULL && nDataBits == 0 )
|
||
|
return true;
|
||
|
|
||
|
bf_read bfRead( "SendTable_CheckIntegrity", pData, Bits2Bytes(nDataBits), nDataBits );
|
||
|
CDeltaBitsReader bitsReader( &bfRead );
|
||
|
|
||
|
int iProp = -1;
|
||
|
int iLastProp = -1;
|
||
|
int nMaxProps = pTable->m_pPrecalc->GetNumProps();
|
||
|
int nPropCount = 0;
|
||
|
|
||
|
Assert( nMaxProps > 0 && nMaxProps < MAX_DATATABLE_PROPS );
|
||
|
|
||
|
while( -1 != (iProp = bitsReader.ReadNextPropIndex()) )
|
||
|
{
|
||
|
Assert( (iProp>=0) && (iProp<nMaxProps) );
|
||
|
|
||
|
// must be larger
|
||
|
Assert( iProp > iLastProp );
|
||
|
|
||
|
const SendProp *pProp = pTable->m_pPrecalc->GetProp( iProp );
|
||
|
|
||
|
Assert( pProp );
|
||
|
|
||
|
// ok check that SkipProp & IsEncodedZero read the same bit length
|
||
|
int iStartBit = bfRead.GetNumBitsRead();
|
||
|
g_PropTypeFns[ pProp->GetType() ].SkipProp( pProp, &bfRead );
|
||
|
int nLength = bfRead.GetNumBitsRead() - iStartBit;
|
||
|
|
||
|
Assert( nLength > 0 ); // a prop must have some bits
|
||
|
|
||
|
bfRead.Seek( iStartBit ); // read it again
|
||
|
|
||
|
g_PropTypeFns[ pProp->GetType() ].IsEncodedZero( pProp, &bfRead );
|
||
|
|
||
|
Assert( nLength == (bfRead.GetNumBitsRead() - iStartBit) );
|
||
|
|
||
|
nPropCount++;
|
||
|
iLastProp = iProp;
|
||
|
}
|
||
|
|
||
|
Assert( nPropCount <= nMaxProps );
|
||
|
Assert( bfRead.GetNumBytesLeft() < 4 );
|
||
|
Assert( !bfRead.IsOverflowed() );
|
||
|
|
||
|
#endif
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|