summaryrefslogtreecommitdiff
path: root/engine/dt_localtransfer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/dt_localtransfer.cpp')
-rw-r--r--engine/dt_localtransfer.cpp617
1 files changed, 617 insertions, 0 deletions
diff --git a/engine/dt_localtransfer.cpp b/engine/dt_localtransfer.cpp
new file mode 100644
index 0000000..61c78ed
--- /dev/null
+++ b/engine/dt_localtransfer.cpp
@@ -0,0 +1,617 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "server.h"
+#include "dt_stack.h"
+#include "dt_localtransfer.h"
+#include "mathlib/vector.h"
+#include "edict.h"
+#include "convar.h"
+#include "con_nprint.h"
+#include "utldict.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+#define PROP_INDEX_VECTOR_ELEM_MARKER 0x8000
+
+
+static ConVar dt_UsePartialChangeEnts(
+ "dt_UsePartialChangeEnts",
+ "1",
+ 0,
+ "(SP only) - enable FL_EDICT_PARTIAL_CHANGE optimization."
+ );
+
+static ConVar dt_ShowPartialChangeEnts(
+ "dt_ShowPartialChangeEnts",
+ "0",
+ 0,
+ "(SP only) - show entities that were copied using small optimized lists (FL_EDICT_PARTIAL_CHANGE)."
+ );
+
+// Negative numbers represent entities that were fully copied.
+static CUtlVector<int> g_PartialChangeEnts;
+static int g_nTotalPropChanges = 0;
+static int g_nTotalEntChanges = 0;
+
+
+template<class T>
+inline void LocalTransfer_FastType(
+ T *pBlah,
+ CServerDatatableStack &serverStack,
+ CClientDatatableStack &clientStack,
+ CFastLocalTransferPropInfo *pPropList,
+ int nProps )
+{
+ CFastLocalTransferPropInfo *pCur = pPropList;
+ for ( int i=0; i < nProps; i++, pCur++ )
+ {
+ serverStack.SeekToProp( pCur->m_iProp );
+
+ unsigned char *pServerBase = serverStack.GetCurStructBase();
+ if ( pServerBase )
+ {
+ clientStack.SeekToProp( pCur->m_iProp );
+ unsigned char *pClientBase = clientStack.GetCurStructBase();
+ Assert( pClientBase );
+
+ const T *pSource = (const T*)( pServerBase + pCur->m_iSendOffset );
+ T *pDest = (T*)( pClientBase + pCur->m_iRecvOffset );
+ *pDest = *pSource;
+ }
+ }
+}
+
+void AddPropOffsetToMap( CSendTablePrecalc *pPrecalc, int iInProp, int iInOffset )
+{
+ Assert( iInProp < 0xFFFF && iInOffset < 0xFFFF );
+ unsigned short iProp = (unsigned short)iInProp;
+ unsigned short iOffset = (unsigned short)iInOffset;
+
+ unsigned short iOldIndex = pPrecalc->m_PropOffsetToIndexMap.Find( iOffset );
+
+ if ( iOldIndex != pPrecalc->m_PropOffsetToIndexMap.InvalidIndex() )
+ {
+ return;
+ }
+
+ pPrecalc->m_PropOffsetToIndexMap.Insert( iOffset, iProp );
+}
+
+// This helps us figure out which properties can use the super-optimized mode
+// where they are tracked in a list when they change. If their m_pProxies pointers
+// are set to 1, then it means that this property is gotten to by means of SendProxy_DataTableToDataTable.
+// If it's set to 0, then we can't directly take the property's offset.
+class CPropMapStack : public CDatatableStack
+{
+public:
+ CPropMapStack( CSendTablePrecalc *pPrecalc, const CStandardSendProxies *pSendProxies ) :
+ CDatatableStack( pPrecalc, (unsigned char*)1, -1 )
+ {
+ m_pPropMapStackPrecalc = pPrecalc;
+ m_pSendProxies = pSendProxies;
+ }
+
+ bool IsNonPointerModifyingProxy( SendTableProxyFn fn, const CStandardSendProxies *pSendProxies )
+ {
+ if ( fn == m_pSendProxies->m_DataTableToDataTable ||
+ fn == m_pSendProxies->m_SendLocalDataTable )
+ {
+ return true;
+ }
+
+ if( pSendProxies->m_ppNonModifiedPointerProxies )
+ {
+ CNonModifiedPointerProxy *pCur = *pSendProxies->m_ppNonModifiedPointerProxies;
+ while ( pCur )
+ {
+ if ( pCur->m_Fn == fn )
+ return true;
+ pCur = pCur->m_pNext;
+ }
+ }
+
+ return false;
+ }
+
+ inline unsigned char* CallPropProxy( CSendNode *pNode, int iProp, unsigned char *pStructBase )
+ {
+ if ( !pStructBase )
+ return 0;
+
+ const SendProp *pProp = m_pPropMapStackPrecalc->GetDatatableProp( iProp );
+ if ( IsNonPointerModifyingProxy( pProp->GetDataTableProxyFn(), m_pSendProxies ) )
+ {
+ // Note: these are offset by 1 (see the constructor), otherwise it won't recurse
+ // during the Init call because pCurStructBase is 0.
+ return pStructBase + pProp->GetOffset();
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ 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 );
+ }
+ }
+
+public:
+ CSendTablePrecalc *m_pPropMapStackPrecalc;
+ const CStandardSendProxies *m_pSendProxies;
+};
+
+
+void BuildPropOffsetToIndexMap( CSendTablePrecalc *pPrecalc, const CStandardSendProxies *pSendProxies )
+{
+ CPropMapStack pmStack( pPrecalc, pSendProxies );
+ pmStack.Init();
+
+ for ( int i=0; i < pPrecalc->m_Props.Count(); i++ )
+ {
+ pmStack.SeekToProp( i );
+ if ( pmStack.GetCurStructBase() != 0 )
+ {
+ const SendProp *pProp = pPrecalc->m_Props[i];
+
+ int offset = pProp->GetOffset() + (int)pmStack.GetCurStructBase() - 1;
+ int elementCount = 1;
+ int elementStride = 0;
+ if ( pProp->GetType() == DPT_Array )
+ {
+ offset = pProp->GetArrayProp()->GetOffset() + (int)pmStack.GetCurStructBase() - 1;
+ elementCount = pProp->m_nElements;
+ elementStride = pProp->m_ElementStride;
+ }
+ if ( offset != 0 )
+ {
+ for ( int j = 0; j < elementCount; j++ )
+ {
+ if ( pProp->GetFlags() & SPROP_IS_A_VECTOR_ELEM )
+ {
+ AddPropOffsetToMap( pPrecalc, i | PROP_INDEX_VECTOR_ELEM_MARKER, offset );
+ }
+ else
+ {
+ AddPropOffsetToMap( pPrecalc, i, offset );
+ }
+
+ offset += elementStride;
+ }
+ }
+ }
+ }
+}
+
+
+void LocalTransfer_InitFastCopy(
+ const SendTable *pSendTable,
+ const CStandardSendProxies *pSendProxies,
+ RecvTable *pRecvTable,
+ const CStandardRecvProxies *pRecvProxies,
+ int &nSlowCopyProps, // These are incremented to tell you how many fast copy props it found.
+ int &nFastCopyProps
+ )
+{
+ CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc;
+
+ // Setup the offset-to-index map.
+ pPrecalc->m_PropOffsetToIndexMap.RemoveAll();
+ BuildPropOffsetToIndexMap( pPrecalc, pSendProxies );
+
+ // Clear the old lists.
+ pPrecalc->m_FastLocalTransfer.m_FastInt32.Purge();
+ pPrecalc->m_FastLocalTransfer.m_FastInt16.Purge();
+ pPrecalc->m_FastLocalTransfer.m_FastInt8.Purge();
+ pPrecalc->m_FastLocalTransfer.m_FastVector.Purge();
+ pPrecalc->m_FastLocalTransfer.m_OtherProps.Purge();
+
+ CRecvDecoder *pDecoder = pRecvTable->m_pDecoder;
+ int iNumProp = pPrecalc->GetNumProps();
+ for ( int iProp=0; iProp < iNumProp; iProp++ )
+ {
+ const SendProp *pSendProp = pPrecalc->GetProp( iProp );
+ const RecvProp *pRecvProp = pDecoder->GetProp( iProp );
+
+ if ( pRecvProp )
+ {
+ Assert( stricmp( pSendProp->GetName(), pRecvProp->GetName() ) == 0 );
+
+ CUtlVector<CFastLocalTransferPropInfo> *pList = &pPrecalc->m_FastLocalTransfer.m_OtherProps;
+
+ if ( pSendProp->GetType() == DPT_Int &&
+ (pSendProp->GetProxyFn() == pSendProxies->m_Int32ToInt32 || pSendProp->GetProxyFn() == pSendProxies->m_UInt32ToInt32) &&
+ pRecvProp->GetProxyFn() == pRecvProxies->m_Int32ToInt32 )
+ {
+ pList = &pPrecalc->m_FastLocalTransfer.m_FastInt32;
+ ++nFastCopyProps;
+ }
+ else if( pSendProp->GetType() == DPT_Int &&
+ (pSendProp->GetProxyFn() == pSendProxies->m_Int16ToInt32 || pSendProp->GetProxyFn() == pSendProxies->m_UInt16ToInt32) &&
+ pRecvProp->GetProxyFn() == pRecvProxies->m_Int32ToInt16 )
+ {
+ pList = &pPrecalc->m_FastLocalTransfer.m_FastInt16;
+ ++nFastCopyProps;
+ }
+ else if( pSendProp->GetType() == DPT_Int &&
+ (pSendProp->GetProxyFn() == pSendProxies->m_Int8ToInt32 || pSendProp->GetProxyFn() == pSendProxies->m_UInt8ToInt32) &&
+ pRecvProp->GetProxyFn() == pRecvProxies->m_Int32ToInt8 )
+ {
+ pList = &pPrecalc->m_FastLocalTransfer.m_FastInt8;
+ ++nFastCopyProps;
+ }
+ else if( pSendProp->GetType() == DPT_Float &&
+ pSendProp->GetProxyFn() == pSendProxies->m_FloatToFloat &&
+ pRecvProp->GetProxyFn() == pRecvProxies->m_FloatToFloat )
+ {
+ Assert( sizeof( int ) == sizeof( float ) );
+ pList = &pPrecalc->m_FastLocalTransfer.m_FastInt32;
+ ++nFastCopyProps;
+ }
+ else if ( pSendProp->GetType() == DPT_Vector &&
+ pSendProp->GetProxyFn() == pSendProxies->m_VectorToVector &&
+ pRecvProp->GetProxyFn() == pRecvProxies->m_VectorToVector )
+ {
+ pList = &pPrecalc->m_FastLocalTransfer.m_FastVector;
+ ++nFastCopyProps;
+ }
+ else
+ {
+ ++nSlowCopyProps;
+ }
+
+ CFastLocalTransferPropInfo toAdd;
+ toAdd.m_iProp = iProp;
+ toAdd.m_iRecvOffset = pRecvProp->GetOffset();
+ toAdd.m_iSendOffset = pSendProp->GetOffset();
+ pList->AddToTail( toAdd );
+ }
+ }
+}
+
+
+inline int MapPropOffsetsToIndices(
+ const CBaseEdict *pEdict,
+ CSendTablePrecalc *pPrecalc,
+ const unsigned short *pOffsets,
+ unsigned short nOffsets,
+ unsigned short *pOut )
+{
+ int iOut = 0;
+
+ for ( unsigned short i=0; i < nOffsets; i++ )
+ {
+ unsigned short index = pPrecalc->m_PropOffsetToIndexMap.Find( pOffsets[i] );
+ if ( index == pPrecalc->m_PropOffsetToIndexMap.InvalidIndex() )
+ {
+ // Note: this SHOULD be fine. In all known cases, when NetworkStateChanged is called with
+ // an offset, there should be a corresponding SendProp in order for that NetworkStateChanged
+ // call to mean anything.
+ //
+ // It means that if we can't find an offset here, then there isn't a SendProp
+ // associated with the CNetworkVar that triggered the change. Therefore,
+ // the change doesn't matter and we can skip past it.
+ //
+ // If we wanted to be anal, we could force them to use DISABLE_NETWORK_VAR_FOR_DERIVED
+ // appropriately in all these cases, but then we'd need a ton of them for certain classes
+ // (like CBaseViewModel, which has a slew of CNetworkVars in its base classes that
+ // it doesn't want to transmit).
+
+ if ( dt_ShowPartialChangeEnts.GetInt() )
+ {
+ static CUtlDict<int,int> testDict;
+ char str[512];
+ Q_snprintf( str, sizeof( str ), "LocalTransfer offset miss - class: %s, DT: %s, offset: %d", pEdict->GetClassName(), pPrecalc->m_pSendTable->m_pNetTableName, pOffsets[i] );
+ if ( testDict.Find( str ) == testDict.InvalidIndex() )
+ {
+ testDict.Insert( str );
+ Warning( "%s\n", str );
+ }
+ }
+ }
+ else
+ {
+ unsigned short propIndex = pPrecalc->m_PropOffsetToIndexMap[index];
+
+ if ( propIndex & PROP_INDEX_VECTOR_ELEM_MARKER )
+ {
+ // Look for all 3 vector elems here.
+ unsigned short curOffset = pOffsets[i];
+ for ( int iVectorElem=0; iVectorElem < 3; iVectorElem++ )
+ {
+ index = pPrecalc->m_PropOffsetToIndexMap.Find( curOffset );
+ if ( index == 0xFFFF )
+ {
+ break;
+ }
+ else
+ {
+ propIndex = pPrecalc->m_PropOffsetToIndexMap[index];
+ if ( propIndex & PROP_INDEX_VECTOR_ELEM_MARKER )
+ pOut[iOut++] = (propIndex & ~PROP_INDEX_VECTOR_ELEM_MARKER);
+ }
+
+ curOffset += sizeof( float );
+ }
+ }
+ else
+ {
+ pOut[iOut++] = propIndex;
+ }
+ }
+ }
+
+ return iOut;
+}
+
+
+inline void FastSortList( unsigned short *pList, unsigned short nEntries )
+{
+ if ( nEntries == 1 )
+ return;
+
+ unsigned short i = 0;
+ while ( 1 )
+ {
+ if ( pList[i+1] < pList[i] )
+ {
+ unsigned short tmp = pList[i+1];
+ pList[i+1] = pList[i];
+ pList[i] = tmp;
+
+ if ( i > 0 )
+ --i;
+ }
+ else
+ {
+ ++i;
+ if ( i >= (nEntries-1) )
+ return;
+ }
+ }
+}
+
+
+inline void AddToPartialChangeEntsList( int iEnt, bool bPartial )
+{
+ if ( !dt_ShowPartialChangeEnts.GetInt() )
+ return;
+
+#if !defined( _XBOX )
+ if ( !bPartial )
+ iEnt = -iEnt;
+
+ if ( g_PartialChangeEnts.Find( iEnt ) == -1 )
+ g_PartialChangeEnts.AddToTail( iEnt );
+#endif
+}
+
+
+void PrintPartialChangeEntsList()
+{
+ if ( !dt_ShowPartialChangeEnts.GetInt() )
+ return;
+
+#if !defined( _XBOX )
+ int iCurRow = 15;
+ Con_NPrintf( iCurRow++, "----- dt_ShowPartialChangeEnts -----" );
+ Con_NPrintf( iCurRow++, "" );
+ Con_NPrintf( iCurRow++, "Ent changes: %3d, prop changes: %3d",
+ g_nTotalEntChanges, g_nTotalPropChanges );
+ Con_NPrintf( iCurRow++, "" );
+
+ char str[512];
+ bool bFirst = true;
+
+
+ // Write the partial ents.
+ str[0] = 0;
+ for ( int i=0; i < g_PartialChangeEnts.Count(); i++ )
+ {
+ if ( g_PartialChangeEnts[i] >= 0 )
+ {
+ if ( !bFirst )
+ Q_strncat( str, ", ", sizeof( str ) );
+
+ char tempStr[512];
+ Q_snprintf( tempStr, sizeof( tempStr ), "%d", g_PartialChangeEnts[i] );
+ Q_strncat( str, tempStr, sizeof( str ) );
+
+ bFirst = false;
+ }
+ }
+ Q_strncat( str, " - PARTIAL", sizeof( str ) );
+ Con_NPrintf( iCurRow++, "%s", str );
+
+
+ // Write the full ents.
+ bFirst = true;
+ str[0] = 0;
+ for ( int i=0; i < g_PartialChangeEnts.Count(); i++ )
+ {
+ if ( g_PartialChangeEnts[i] < 0 )
+ {
+ if ( !bFirst )
+ Q_strncat( str, ", ", sizeof( str ) );
+
+ char tempStr[512];
+ Q_snprintf( tempStr, sizeof( tempStr ), "%d", -g_PartialChangeEnts[i] );
+ Q_strncat( str, tempStr, sizeof( str ) );
+
+ bFirst = false;
+ }
+ }
+ Q_strncat( str, " - FULL", sizeof( str ) );
+ Con_NPrintf( iCurRow++, "%s", str );
+
+ g_PartialChangeEnts.Purge();
+ g_nTotalPropChanges = g_nTotalEntChanges = 0;
+#endif
+}
+
+
+void LocalTransfer_TransferEntity(
+ const CBaseEdict *pEdict,
+ const SendTable *pSendTable,
+ const void *pSrcEnt,
+ RecvTable *pRecvTable,
+ void *pDestEnt,
+ bool bNewlyCreated,
+ bool bJustEnteredPVS,
+ int objectID )
+{
+ ++g_nTotalEntChanges;
+ CEdictChangeInfo *pCI = &g_pSharedChangeInfo->m_ChangeInfos[pEdict->GetChangeInfo()];
+
+ unsigned short propIndices[MAX_CHANGE_OFFSETS*3];
+
+ // This code tries to only copy fields expressly marked as "changed" (by having the field offsets added to the changeoffsets vectors)
+ if ( pEdict->GetChangeInfoSerialNumber() == g_pSharedChangeInfo->m_iSerialNumber &&
+ !bNewlyCreated &&
+ !bJustEnteredPVS &&
+ dt_UsePartialChangeEnts.GetInt()
+ )
+ {
+ CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc;
+
+ int nChangeOffsets = MapPropOffsetsToIndices( pEdict, pPrecalc, pCI->m_ChangeOffsets, pCI->m_nChangeOffsets, propIndices );
+ if ( nChangeOffsets == 0 )
+ return;
+
+ AddToPartialChangeEntsList( (edict_t*)pEdict - sv.edicts, true );
+ FastSortList( propIndices, nChangeOffsets );
+
+ // Setup the structure to traverse the source tree.
+ ErrorIfNot( pPrecalc, ("SendTable_Encode: Missing m_pPrecalc for SendTable %s.", pSendTable->m_pNetTableName) );
+ CServerDatatableStack serverStack( pPrecalc, (unsigned char*)pSrcEnt, objectID );
+ serverStack.Init( true );
+
+ // Setup the structure to traverse the dest tree.
+ CRecvDecoder *pDecoder = pRecvTable->m_pDecoder;
+ ErrorIfNot( pDecoder, ("RecvTable_Decode: table '%s' missing a decoder.", pRecvTable->GetName()) );
+ CClientDatatableStack clientStack( pDecoder, (unsigned char*)pDestEnt, objectID );
+ clientStack.Init( true );
+
+ // Cool. We can get away with just transferring a few.
+ for ( int iChanged=0; iChanged < nChangeOffsets; iChanged++ )
+ {
+ int iProp = propIndices[iChanged];
+
+ ++g_nTotalPropChanges;
+
+ serverStack.SeekToProp( iProp );
+ const SendProp *pSendProp = serverStack.GetCurProp();
+ unsigned char *pSendBase = serverStack.UpdateRoutesExplicit();
+ if ( pSendBase )
+ {
+ const RecvProp *pRecvProp = pDecoder->GetProp( iProp );
+ Assert( pRecvProp );
+
+ clientStack.SeekToProp( iProp );
+ unsigned char *pRecvBase = clientStack.UpdateRoutesExplicit();
+ Assert( pRecvBase );
+
+ g_PropTypeFns[pRecvProp->GetType()].FastCopy( pSendProp, pRecvProp, pSendBase, pRecvBase, objectID );
+ }
+ }
+ }
+ // Whereas the below code copies _all_ fields, regardless of whether they were changed or not. We run this only newly created entities, or entities
+ // which were previously dormant/outside the pvs but are now back in the PVS since we could have missed field updates since the changeoffsets get cleared every
+ // frame.
+ else
+ {
+ // Setup the structure to traverse the source tree.
+ CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc;
+ ErrorIfNot( pPrecalc, ("SendTable_Encode: Missing m_pPrecalc for SendTable %s.", pSendTable->m_pNetTableName) );
+ CServerDatatableStack serverStack( pPrecalc, (unsigned char*)pSrcEnt, objectID );
+ serverStack.Init();
+
+ // Setup the structure to traverse the dest tree.
+ CRecvDecoder *pDecoder = pRecvTable->m_pDecoder;
+ ErrorIfNot( pDecoder, ("RecvTable_Decode: table '%s' missing a decoder.", pRecvTable->GetName()) );
+ CClientDatatableStack clientStack( pDecoder, (unsigned char*)pDestEnt, objectID );
+ clientStack.Init();
+
+ AddToPartialChangeEntsList( (edict_t*)pEdict - sv.edicts, false );
+
+ // Copy the properties that require proxies.
+ CFastLocalTransferPropInfo *pPropList = pPrecalc->m_FastLocalTransfer.m_OtherProps.Base();
+ int nProps = pPrecalc->m_FastLocalTransfer.m_OtherProps.Count();
+
+ for ( int i=0; i < nProps; i++ )
+ {
+ int iProp = pPropList[i].m_iProp;
+
+ serverStack.SeekToProp( iProp );
+ const SendProp *pSendProp = serverStack.GetCurProp();
+ unsigned char *pSendBase = serverStack.GetCurStructBase();
+
+ if ( pSendBase )
+ {
+ const RecvProp *pRecvProp = pDecoder->GetProp( iProp );
+ Assert( pRecvProp );
+
+ clientStack.SeekToProp( iProp );
+ unsigned char *pRecvBase = clientStack.GetCurStructBase();
+ Assert( pRecvBase );
+
+ g_PropTypeFns[pRecvProp->GetType()].FastCopy( pSendProp, pRecvProp, pSendBase, pRecvBase, objectID );
+ }
+ }
+
+ // Transfer over the fast properties.
+ LocalTransfer_FastType( (int*)0, serverStack, clientStack, pPrecalc->m_FastLocalTransfer.m_FastInt32.Base(), pPrecalc->m_FastLocalTransfer.m_FastInt32.Count() );
+ LocalTransfer_FastType( (short*)0, serverStack, clientStack, pPrecalc->m_FastLocalTransfer.m_FastInt16.Base(), pPrecalc->m_FastLocalTransfer.m_FastInt16.Count() );
+ LocalTransfer_FastType( (char*)0, serverStack, clientStack, pPrecalc->m_FastLocalTransfer.m_FastInt8.Base(), pPrecalc->m_FastLocalTransfer.m_FastInt8.Count() );
+ LocalTransfer_FastType( (Vector*)0, serverStack, clientStack, pPrecalc->m_FastLocalTransfer.m_FastVector.Base(), pPrecalc->m_FastLocalTransfer.m_FastVector.Count() );
+ }
+
+
+ // The old, slow method to copy all props using their proxies.
+ /*
+ int iEndProp = pPrecalc->m_Root.GetLastPropIndex();
+ for ( int iProp=0; iProp <= iEndProp; iProp++ )
+ {
+ serverStack.SeekToProp( iProp );
+ clientStack.SeekToProp( iProp );
+
+ const SendProp *pSendProp = serverStack.GetCurProp();
+ const RecvProp *pRecvProp = pDecoder->GetProp( iProp );
+ if ( pRecvProp )
+ {
+ unsigned char *pSendBase = serverStack.GetCurStructBase();
+ unsigned char *pRecvBase = clientStack.GetCurStructBase();
+ if ( pSendBase && pRecvBase )
+ {
+ Assert( stricmp( pSendProp->GetName(), pRecvProp->GetName() ) == 0 );
+
+ g_PropTypeFns[pRecvProp->GetType()].FastCopy( pSendProp, pRecvProp, pSendBase, pRecvBase, objectID );
+ }
+ }
+ }
+ */
+}
+
+
+