//========= 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 g_PartialChangeEnts; static int g_nTotalPropChanges = 0; static int g_nTotalEntChanges = 0; template 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 *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 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 ); } } } */ }