//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include #include "dt_send.h" #include "dt.h" #include "dt_recv.h" #include "dt_encode.h" #include "convar.h" #include "commonmacros.h" #include "tier1/strtools.h" #include "tier0/dbg.h" #include "dt_stack.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define PROPINDEX_NUMBITS 12 #define MAX_TOTAL_SENDTABLE_PROPS (1 << PROPINDEX_NUMBITS) ConVar g_CV_DTWatchEnt( "dtwatchent", "-1", 0, "Watch this entities data table encoding." ); ConVar g_CV_DTWatchVar( "dtwatchvar", "", 0, "Watch the named variable." ); ConVar g_CV_DTWarning( "dtwarning", "0", 0, "Print data table warnings?" ); ConVar g_CV_DTWatchClass( "dtwatchclass", "", 0, "Watch all fields encoded with this table." ); // ----------------------------------------------------------------------------- // // // CBuildHierarchyStruct // // Used while building a CSendNode hierarchy. // // ----------------------------------------------------------------------------- // class CBuildHierarchyStruct { public: const ExcludeProp *m_pExcludeProps; int m_nExcludeProps; const SendProp *m_pDatatableProps[MAX_TOTAL_SENDTABLE_PROPS]; int m_nDatatableProps; const SendProp *m_pProps[MAX_TOTAL_SENDTABLE_PROPS]; unsigned char m_PropProxyIndices[MAX_TOTAL_SENDTABLE_PROPS]; int m_nProps; unsigned char m_nPropProxies; }; // ----------------------------------------------------------------------------- // // CSendNode. // ----------------------------------------------------------------------------- // CSendNode::CSendNode() { m_iDatatableProp = -1; m_pTable = NULL; m_iFirstRecursiveProp = m_nRecursiveProps = 0; m_DataTableProxyIndex = DATATABLE_PROXY_INDEX_INVALID; // set it to a questionable value. } CSendNode::~CSendNode() { int c = GetNumChildren(); for ( int i = c - 1 ; i >= 0 ; i-- ) { delete GetChild( i ); } m_Children.Purge(); } // ----------------------------------------------------------------------------- // // CSendTablePrecalc // ----------------------------------------------------------------------------- // bool PropOffsetLT( const unsigned short &a, const unsigned short &b ) { return a < b; } CSendTablePrecalc::CSendTablePrecalc() : m_PropOffsetToIndexMap( 0, 0, PropOffsetLT ) { m_pDTITable = NULL; m_pSendTable = 0; m_nDataTableProxies = 0; } CSendTablePrecalc::~CSendTablePrecalc() { if ( m_pSendTable ) m_pSendTable->m_pPrecalc = 0; } const ExcludeProp* FindExcludeProp( char const *pTableName, char const *pPropName, const ExcludeProp *pExcludeProps, int nExcludeProps) { for ( int i=0; i < nExcludeProps; i++ ) { if ( stricmp(pExcludeProps[i].m_pTableName, pTableName) == 0 && stricmp(pExcludeProps[i].m_pPropName, pPropName ) == 0 ) return &pExcludeProps[i]; } return NULL; } // Fill in a list of all the excluded props. static bool SendTable_GetPropsExcluded( const SendTable *pTable, ExcludeProp *pExcludeProps, int &nExcludeProps, int nMaxExcludeProps ) { for(int i=0; i < pTable->m_nProps; i++) { SendProp *pProp = &pTable->m_pProps[i]; if ( pProp->IsExcludeProp() ) { char const *pName = pProp->GetExcludeDTName(); ErrorIfNot( pName, ("Found an exclude prop missing a name.") ); ErrorIfNot( nExcludeProps < nMaxExcludeProps, ("SendTable_GetPropsExcluded: Overflowed max exclude props with %s.", pName) ); pExcludeProps[nExcludeProps].m_pTableName = pName; pExcludeProps[nExcludeProps].m_pPropName = pProp->GetName(); nExcludeProps++; } else if ( pProp->GetDataTable() ) { if( !SendTable_GetPropsExcluded( pProp->GetDataTable(), pExcludeProps, nExcludeProps, nMaxExcludeProps ) ) return false; } } return true; } // Set the datatable proxy indices in all datatable SendProps. static void SetDataTableProxyIndices_R( CSendTablePrecalc *pMainTable, CSendNode *pCurTable, CBuildHierarchyStruct *bhs ) { for ( int i=0; i < pCurTable->GetNumChildren(); i++ ) { CSendNode *pNode = pCurTable->GetChild( i ); const SendProp *pProp = bhs->m_pDatatableProps[pNode->m_iDatatableProp]; if ( pProp->GetFlags() & SPROP_PROXY_ALWAYS_YES ) { pNode->SetDataTableProxyIndex( DATATABLE_PROXY_INDEX_NOPROXY ); } else { pNode->SetDataTableProxyIndex( pMainTable->GetNumDataTableProxies() ); pMainTable->SetNumDataTableProxies( pMainTable->GetNumDataTableProxies() + 1 ); } SetDataTableProxyIndices_R( pMainTable, pNode, bhs ); } } // Set the datatable proxy indices in all datatable SendProps. static void SetRecursiveProxyIndices_R( SendTable *pBaseTable, CSendNode *pCurTable, int &iCurProxyIndex ) { if ( iCurProxyIndex >= CDatatableStack::MAX_PROXY_RESULTS ) Error( "Too many proxies for datatable %s.", pBaseTable->GetName() ); pCurTable->SetRecursiveProxyIndex( iCurProxyIndex ); iCurProxyIndex++; for ( int i=0; i < pCurTable->GetNumChildren(); i++ ) { CSendNode *pNode = pCurTable->GetChild( i ); SetRecursiveProxyIndices_R( pBaseTable, pNode, iCurProxyIndex ); } } void SendTable_BuildHierarchy( CSendNode *pNode, const SendTable *pTable, CBuildHierarchyStruct *bhs ); void SendTable_BuildHierarchy_IterateProps( CSendNode *pNode, const SendTable *pTable, CBuildHierarchyStruct *bhs, const SendProp *pNonDatatableProps[MAX_TOTAL_SENDTABLE_PROPS], int &nNonDatatableProps ) { int i; for ( i=0; i < pTable->m_nProps; i++ ) { const SendProp *pProp = &pTable->m_pProps[i]; if ( pProp->IsExcludeProp() || pProp->IsInsideArray() || FindExcludeProp( pTable->GetName(), pProp->GetName(), bhs->m_pExcludeProps, bhs->m_nExcludeProps ) ) { continue; } if ( pProp->GetType() == DPT_DataTable ) { if ( pProp->GetFlags() & SPROP_COLLAPSIBLE ) { // This is a base class.. no need to make a new CSendNode (and trigger a bunch of // unnecessary send proxy calls in the datatable stacks). SendTable_BuildHierarchy_IterateProps( pNode, pProp->GetDataTable(), bhs, pNonDatatableProps, nNonDatatableProps ); } else { // Setup a child datatable reference. CSendNode *pChild = new CSendNode; // Setup a datatable prop for this node to reference (so the recursion // routines can get at the proxy). if ( bhs->m_nDatatableProps >= ARRAYSIZE( bhs->m_pDatatableProps ) ) Error( "Overflowed datatable prop list in SendTable '%s'.", pTable->GetName() ); bhs->m_pDatatableProps[bhs->m_nDatatableProps] = pProp; pChild->m_iDatatableProp = bhs->m_nDatatableProps; ++bhs->m_nDatatableProps; pNode->m_Children.AddToTail( pChild ); // Recurse into the new child datatable. SendTable_BuildHierarchy( pChild, pProp->GetDataTable(), bhs ); } } else { if ( nNonDatatableProps >= MAX_TOTAL_SENDTABLE_PROPS ) Error( "SendTable_BuildHierarchy: overflowed non-datatable props with '%s'.", pProp->GetName() ); pNonDatatableProps[nNonDatatableProps] = pProp; ++nNonDatatableProps; } } } void SendTable_BuildHierarchy( CSendNode *pNode, const SendTable *pTable, CBuildHierarchyStruct *bhs ) { pNode->m_pTable = pTable; pNode->m_iFirstRecursiveProp = bhs->m_nProps; Assert( bhs->m_nPropProxies < 255 ); unsigned char curPropProxy = bhs->m_nPropProxies; ++bhs->m_nPropProxies; const SendProp *pNonDatatableProps[MAX_TOTAL_SENDTABLE_PROPS]; int nNonDatatableProps = 0; // First add all the child datatables. SendTable_BuildHierarchy_IterateProps( pNode, pTable, bhs, pNonDatatableProps, nNonDatatableProps ); // Now add the properties. // Make sure there's room, then just copy the pointers from the loop above. ErrorIfNot( bhs->m_nProps + nNonDatatableProps < ARRAYSIZE( bhs->m_pProps ), ("SendTable_BuildHierarchy: overflowed prop buffer.") ); for ( int i=0; i < nNonDatatableProps; i++ ) { bhs->m_pProps[bhs->m_nProps] = pNonDatatableProps[i]; bhs->m_PropProxyIndices[bhs->m_nProps] = curPropProxy; ++bhs->m_nProps; } pNode->m_nRecursiveProps = bhs->m_nProps - pNode->m_iFirstRecursiveProp; } void SendTable_SortByPriority(CBuildHierarchyStruct *bhs) { int i, start = 0; while( true ) { for ( i = start; i < bhs->m_nProps; i++ ) { const SendProp *p = bhs->m_pProps[i]; unsigned char c = bhs->m_PropProxyIndices[i]; if ( p->GetFlags() & SPROP_CHANGES_OFTEN ) { bhs->m_pProps[i] = bhs->m_pProps[start]; bhs->m_PropProxyIndices[i] = bhs->m_PropProxyIndices[start]; bhs->m_pProps[start] = p; bhs->m_PropProxyIndices[start] = c; start++; break; } } if ( i == bhs->m_nProps ) return; } } void CalcPathLengths_R( CSendNode *pNode, CUtlVector &pathLengths, int curPathLength, int &totalPathLengths ) { pathLengths[pNode->GetRecursiveProxyIndex()] = curPathLength; totalPathLengths += curPathLength; for ( int i=0; i < pNode->GetNumChildren(); i++ ) { CalcPathLengths_R( pNode->GetChild( i ), pathLengths, curPathLength+1, totalPathLengths ); } } void FillPathEntries_R( CSendTablePrecalc *pPrecalc, CSendNode *pNode, CSendNode *pParent, int &iCurEntry ) { // Fill in this node's path. CSendTablePrecalc::CProxyPath &outProxyPath = pPrecalc->m_ProxyPaths[ pNode->GetRecursiveProxyIndex() ]; outProxyPath.m_iFirstEntry = (unsigned short)iCurEntry; // Copy all the proxies leading to the parent. if ( pParent ) { CSendTablePrecalc::CProxyPath &parentProxyPath = pPrecalc->m_ProxyPaths[pParent->GetRecursiveProxyIndex()]; outProxyPath.m_nEntries = parentProxyPath.m_nEntries + 1; for ( int i=0; i < parentProxyPath.m_nEntries; i++ ) pPrecalc->m_ProxyPathEntries[iCurEntry++] = pPrecalc->m_ProxyPathEntries[parentProxyPath.m_iFirstEntry+i]; // Now add this node's own proxy. pPrecalc->m_ProxyPathEntries[iCurEntry].m_iProxy = pNode->GetRecursiveProxyIndex(); pPrecalc->m_ProxyPathEntries[iCurEntry].m_iDatatableProp = pNode->m_iDatatableProp; ++iCurEntry; } else { outProxyPath.m_nEntries = 0; } for ( int i=0; i < pNode->GetNumChildren(); i++ ) { FillPathEntries_R( pPrecalc, pNode->GetChild( i ), pNode, iCurEntry ); } } void SendTable_GenerateProxyPaths( CSendTablePrecalc *pPrecalc, int nProxyIndices ) { // Initialize the array. pPrecalc->m_ProxyPaths.SetSize( nProxyIndices ); for ( int i=0; i < nProxyIndices; i++ ) pPrecalc->m_ProxyPaths[i].m_iFirstEntry = pPrecalc->m_ProxyPaths[i].m_nEntries = 0xFFFF; // Figure out how long the path down the tree is to each node. int totalPathLengths = 0; CUtlVector pathLengths; pathLengths.SetSize( nProxyIndices ); memset( pathLengths.Base(), 0, sizeof( pathLengths[0] ) * nProxyIndices ); CalcPathLengths_R( pPrecalc->GetRootNode(), pathLengths, 0, totalPathLengths ); // int iCurEntry = 0; pPrecalc->m_ProxyPathEntries.SetSize( totalPathLengths ); FillPathEntries_R( pPrecalc, pPrecalc->GetRootNode(), NULL, iCurEntry ); } bool CSendTablePrecalc::SetupFlatPropertyArray() { SendTable *pTable = GetSendTable(); // First go through and set SPROP_INSIDEARRAY when appropriate, and set array prop pointers. SetupArrayProps_R( pTable ); // Make a list of which properties are excluded. ExcludeProp excludeProps[MAX_EXCLUDE_PROPS]; int nExcludeProps = 0; if( !SendTable_GetPropsExcluded( pTable, excludeProps, nExcludeProps, MAX_EXCLUDE_PROPS ) ) return false; // Now build the hierarchy. CBuildHierarchyStruct bhs; bhs.m_pExcludeProps = excludeProps; bhs.m_nExcludeProps = nExcludeProps; bhs.m_nProps = bhs.m_nDatatableProps = 0; bhs.m_nPropProxies = 0; SendTable_BuildHierarchy( GetRootNode(), pTable, &bhs ); SendTable_SortByPriority( &bhs ); // Copy the SendProp pointers into the precalc. MEM_ALLOC_CREDIT(); m_Props.CopyArray( bhs.m_pProps, bhs.m_nProps ); m_DatatableProps.CopyArray( bhs.m_pDatatableProps, bhs.m_nDatatableProps ); m_PropProxyIndices.CopyArray( bhs.m_PropProxyIndices, bhs.m_nProps ); // Assign the datatable proxy indices. SetNumDataTableProxies( 0 ); SetDataTableProxyIndices_R( this, GetRootNode(), &bhs ); int nProxyIndices = 0; SetRecursiveProxyIndices_R( pTable, GetRootNode(), nProxyIndices ); SendTable_GenerateProxyPaths( this, nProxyIndices ); return true; } // ---------------------------------------------------------------------------------------- // // Helpers. // ---------------------------------------------------------------------------------------- // // Compares two arrays of bits. // Returns true if they are equal. bool AreBitArraysEqual( void const *pvBits1, void const *pvBits2, int nBits ) { unsigned int const *pBits1 = (unsigned int const *)pvBits1; unsigned int const *pBits2 = (unsigned int const *)pvBits2; // Compare words. int nWords = nBits >> 5; for ( int i = 0 ; i < nWords; ++i ) { if ( pBits1[i] != pBits2[i] ) return false; } if ( nBits & 31 ) { // Compare remaining bits. unsigned int mask = (1 << (nBits & 31)) - 1; return ((pBits1[nWords] ^ pBits2[nWords]) & mask) == 0; } return true; } // Does a fast memcmp-based test to determine if the two bit arrays are different. // Returns true if they are equal. bool CompareBitArrays( void const *pPacked1, void const *pPacked2, int nBits1, int nBits2 ) { if( nBits1 >= 0 && nBits1 == nBits2 ) { if ( pPacked1 == pPacked2 ) { return true; } else { return AreBitArraysEqual( pPacked1, pPacked2, nBits1 ); } } else return false; } // Looks at the DTWatchEnt and DTWatchProp console variables and returns true // if the user wants to watch this property. bool ShouldWatchThisProp( const SendTable *pTable, int objectID, const char *pPropName ) { if(g_CV_DTWatchEnt.GetInt() != -1 && g_CV_DTWatchEnt.GetInt() == objectID) { const char *pStr = g_CV_DTWatchVar.GetString(); if ( pStr && pStr[0] != 0 ) { return stricmp( pStr, pPropName ) == 0; } else { return true; } } if ( g_CV_DTWatchClass.GetString()[ 0 ] && Q_stristr( pTable->GetName(), g_CV_DTWatchClass.GetString() ) ) return true; return false; } bool Sendprop_UsingDebugWatch() { if ( g_CV_DTWatchEnt.GetInt() != -1 ) return true; if ( g_CV_DTWatchClass.GetString()[ 0 ] ) return true; return false; } // Prints a datatable warning into the console. void DataTable_Warning( const char *pInMessage, ... ) { char msg[4096]; va_list marker; #if 0 #if !defined(_DEBUG) if(!g_CV_DTWarning.GetInt()) return; #endif #endif va_start(marker, pInMessage); Q_vsnprintf( msg, sizeof( msg ), pInMessage, marker); va_end(marker); Warning( "DataTable warning: %s", msg ); }