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 ); // This writes and delta-compresses the delta bits. CDeltaBitsWriter deltaBitsWriter( pOut ); // Setup all the info we'll be walking the tree with. CEncodeInfo info( pPrecalc, (unsigned char*)pStruct, objectID ); info.m_pOut = pOut; info.m_ObjectID = objectID; info.m_nDataBits = 0; info.m_nOverheadBits = 0; // unused info.m_pDeltaBitsWriter = &deltaBitsWriter; 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(); }
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(); }
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; }
int SendTable_GetNumFlatProps( SendTable *pSendTable ) { CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc; ErrorIfNot( pPrecalc, ("SendTable_GetNumFlatProps: missing pPrecalc.") ); return pPrecalc->GetNumProps(); }
void ExportCoreDispAllowedVertList( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) { ErrorIfNot( pIn->GetAllowedVerts().GetNumDWords() == sizeof( pOut->m_AllowedVerts ) / 4, ("ExportCoreDispAllowedVertList: size mismatch") ); for ( int i=0; i < pIn->GetAllowedVerts().GetNumDWords(); i++ ) pOut->m_AllowedVerts[i] = pIn->GetAllowedVerts().GetDWord( i ); }
CDeltaCalculator::~CDeltaCalculator() { // Make sure we didn't overflow. ErrorIfNot( m_nDeltaProps <= m_nMaxDeltaProps && !m_bfFromState.IsOverflowed() && !m_bfToState.IsOverflowed(), ( "SendTable_CalcDelta: overflowed on datatable '%s'.", m_pPrecalc->GetSendTable()->GetName() ) ); // We may not have read to the end of our input bits, but we don't care. m_FromBitsReader.ForceFinished(); m_ToBitsReader.ForceFinished(); }
void LocalTransfer_TransferEntity( const SendTable *pSendTable, const void *pSrcEnt, RecvTable *pRecvTable, void *pDestEnt, int objectID ) { // 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 ); // 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 ); // Walk through each property in each tree and transfer the data. 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 ); } } } }
bool SV_GetInstanceBaseline( ServerClass *pClass, void const **pData, int *pDatalen ) { if ( sv_instancebaselines.GetInt() ) { ErrorIfNot( pClass->m_InstanceBaselineIndex != INVALID_STRING_INDEX, ("SV_GetInstanceBaseline: missing instance baseline for class '%s'", pClass->m_pNetworkName) ); *pData = networkStringTableContainerServer->GetStringUserData( sv.GetInstanceBaselineTable(), pClass->m_InstanceBaselineIndex, pDatalen ); return *pData != NULL; } else { static char dummy[1] = {0}; *pData = dummy; *pDatalen = 1; return true; } }
// 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; } } }
// This function makes sure that this entity class has an instance baseline. // If it doesn't have one yet, it makes a new one. void SV_EnsureInstanceBaseline( int iEdict, const void *pData, int nBytes ) { edict_t *pEnt = &sv.edicts[iEdict]; ErrorIfNot( pEnt->m_pEnt, ("SV_EnsureInstanceBaseline: edict %d missing ent", iEdict) ); ServerClass *pClass = pEnt->m_pEnt->GetServerClass(); if ( pClass->m_InstanceBaselineIndex == INVALID_STRING_INDEX ) { char idString[32]; Q_snprintf( idString, sizeof( idString ), "%d", pClass->m_ClassID ); // Ok, make a new instance baseline so they can reference it. pClass->m_InstanceBaselineIndex = networkStringTableContainerServer->AddString( sv.GetInstanceBaselineTable(), idString, // Note we're sending a string with the ID number, not the class name. nBytes, pData ); Assert( pClass->m_InstanceBaselineIndex != INVALID_STRING_INDEX ); } }
void CHLTVClientState::CopyNewEntity( CEntityReadInfo &u, int iClass, int iSerialNum ) { ServerClass *pServerClass = SV_FindServerClass( iClass ); Assert( pServerClass ); ClientClass *pClientClass = GetClientClass( iClass ); Assert( pClientClass ); const int ent = u.m_nNewEntity; // copy class & serial CFrameSnapshot *pSnapshot = u.m_pTo->GetSnapshot(); pSnapshot->m_pEntities[ent].m_nSerialNumber = iSerialNum; pSnapshot->m_pEntities[ent].m_pClass = pServerClass; // Get either the static or instance baseline. const void *pFromData = NULL; int nFromBits = 0; int nFromTick = 0; // MOTODO get tick when baseline last changed PackedEntity *baseline = u.m_bAsDelta ? GetEntityBaseline( u.m_nBaseline, ent ) : NULL; if ( baseline && baseline->m_pClientClass == pClientClass ) { Assert( !baseline->IsCompressed() ); pFromData = baseline->GetData(); nFromBits = baseline->GetNumBits(); } else { // Every entity must have a static or an instance baseline when we get here. ErrorIfNot( GetClassBaseline( iClass, &pFromData, &nFromBits ), ("HLTV_CopyNewEntity: GetDynamicBaseline(%d) failed.", iClass) ); nFromBits *= 8; // convert to bits } // create new ChangeFrameList containing all properties set as changed int nFlatProps = SendTable_GetNumFlatProps( pServerClass->m_pTable ); IChangeFrameList *pChangeFrame = NULL; if ( !m_bSaveMemory ) { pChangeFrame = AllocChangeFrameList( nFlatProps, nFromTick ); } // Now make a PackedEntity and store the new packed data in there. PackedEntity *pPackedEntity = framesnapshotmanager->CreatePackedEntity( pSnapshot, ent ); pPackedEntity->SetChangeFrameList( pChangeFrame ); pPackedEntity->SetServerAndClientClass( pServerClass, pClientClass ); // Make space for the baseline data. char packedData[MAX_PACKEDENTITY_DATA]; bf_read fromBuf( "HLTV_ReadEnterPVS1", pFromData, Bits2Bytes( nFromBits ), nFromBits ); bf_write writeBuf( "HLTV_ReadEnterPVS2", packedData, sizeof( packedData ) ); int changedProps[MAX_DATATABLE_PROPS]; // decode basline, is compressed against zero values int nChangedProps = RecvTable_MergeDeltas( pClientClass->m_pRecvTable, &fromBuf, u.m_pBuf, &writeBuf, -1, false, changedProps ); // update change tick in ChangeFrameList if ( pChangeFrame ) { pChangeFrame->SetChangeTick( changedProps, nChangedProps, pSnapshot->m_nTickCount ); } if ( u.m_bUpdateBaselines ) { SetEntityBaseline( (u.m_nBaseline==0)?1:0, pClientClass, u.m_nNewEntity, packedData, writeBuf.GetNumBytesWritten() ); } pPackedEntity->AllocAndCopyPadded( packedData, writeBuf.GetNumBytesWritten() ); // If ent doesn't think it's in PVS, signal that it is Assert( u.m_pTo->last_entity <= ent ); u.m_pTo->last_entity = ent; u.m_pTo->transmit_entity.Set( ent ); }
//----------------------------------------------------------------------------- // Purpose: Parse string update //----------------------------------------------------------------------------- void CNetworkStringTable::ParseUpdate( bf_read &buf, int entries ) { int lastEntry = -1; CUtlVector< StringHistoryEntry > history; for (int i=0; i<entries; i++) { int entryIndex = lastEntry + 1; if ( !buf.ReadOneBit() ) { entryIndex = buf.ReadUBitLong( GetEntryBits() ); } lastEntry = entryIndex; if ( entryIndex < 0 || entryIndex >= GetMaxStrings() ) { Host_Error( "Server sent bogus string index %i for table %s\n", entryIndex, GetTableName() ); } const char *pEntry = NULL; char entry[ 1024 ]; char substr[ 1024 ]; if ( buf.ReadOneBit() ) { bool substringcheck = buf.ReadOneBit() ? true : false; if ( substringcheck ) { int index = buf.ReadUBitLong( 5 ); int bytestocopy = buf.ReadUBitLong( SUBSTRING_BITS ); Q_strncpy( entry, history[ index ].string, bytestocopy + 1 ); buf.ReadString( substr, sizeof(substr) ); Q_strncat( entry, substr, sizeof(entry), COPY_ALL_CHARACTERS ); } else { buf.ReadString( entry, sizeof( entry ) ); } pEntry = entry; } // Read in the user data. unsigned char tempbuf[ CNetworkStringTableItem::MAX_USERDATA_SIZE ]; memset( tempbuf, 0, sizeof( tempbuf ) ); const void *pUserData = NULL; int nBytes = 0; if ( buf.ReadOneBit() ) { if ( IsUserDataFixedSize() ) { // Don't need to read length, it's fixed length and the length was networked down already. nBytes = GetUserDataSize(); Assert( nBytes > 0 ); tempbuf[nBytes-1] = 0; // be safe, clear last byte buf.ReadBits( tempbuf, GetUserDataSizeBits() ); } else { nBytes = buf.ReadUBitLong( CNetworkStringTableItem::MAX_USERDATA_BITS ); ErrorIfNot( nBytes <= sizeof( tempbuf ), ("CNetworkStringTableClient::ParseUpdate: message too large (%d bytes).", nBytes) ); buf.ReadBytes( tempbuf, nBytes ); } pUserData = tempbuf; } // Check if we are updating an old entry or adding a new one if ( entryIndex < GetNumStrings() ) { SetStringUserData( entryIndex, nBytes, pUserData ); #ifdef _DEBUG if ( pEntry ) { Assert( !Q_strcmp( pEntry, GetString( entryIndex ) ) ); // make sure string didn't change } #endif pEntry = GetString( entryIndex ); // string didn't change } else { // Grow the table (entryindex must be the next empty slot) Assert( (entryIndex == GetNumStrings()) && (pEntry != NULL) ); if ( pEntry == NULL ) { Msg("CNetworkStringTable::ParseUpdate: NULL pEntry, table %s, index %i\n", GetTableName(), entryIndex ); pEntry = "";// avoid crash because of NULL strings } AddString( true, pEntry, nBytes, pUserData ); } if ( history.Count() > 31 ) { history.Remove( 0 ); } StringHistoryEntry she; Q_strncpy( she.string, pEntry, sizeof( she.string ) ); history.AddToTail( she ); } }
static inline void SV_PackEntity( int edictIdx, edict_t* ent, SendTable* pSendTable, EntityChange_t changeType, CFrameSnapshot *pSnapshot ) { int iSerialNum = pSnapshot->m_Entities[ edictIdx ].m_nSerialNumber; // Check to see if this entity specifies its changes. // If so, then try to early out making the fullpack bool bUsedPrev = false; if ( changeType == ENTITY_CHANGE_NONE ) { // Now this may not work if we didn't previously send a packet; // if not, then we gotta compute it bUsedPrev = framesnapshot->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ); } if ( !bUsedPrev || sv_debugmanualmode.GetInt() ) { // First encode the entity's data. char packedData[MAX_PACKEDENTITY_DATA]; bf_write writeBuf( "SV_PackEntity->writeBuf", packedData, sizeof( packedData ) ); // (avoid constructor overhead). unsigned char tempData[ sizeof( CSendProxyRecipients ) * MAX_DATATABLE_PROXIES ]; CUtlMemory< CSendProxyRecipients > recip( (CSendProxyRecipients*)tempData, pSendTable->GetNumDataTableProxies() ); if( !SendTable_Encode( pSendTable, ent->m_pEnt, &writeBuf, NULL, edictIdx, &recip ) ) { Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", edictIdx ); } SV_EnsureInstanceBaseline( edictIdx, packedData, writeBuf.GetNumBytesWritten() ); int nFlatProps = SendTable_GetNumFlatProps( pSendTable ); IChangeFrameList *pChangeFrame; // If this entity was previously in there, then it should have a valid IChangeFrameList // which we can delta against to figure out which properties have changed. // // If not, then we want to setup a new IChangeFrameList. PackedEntity *pPrevFrame = framesnapshot->GetPreviouslySentPacket( edictIdx, pSnapshot->m_Entities[ edictIdx ].m_nSerialNumber ); if ( pPrevFrame ) { // Calculate a delta. bf_read bfPrev( "SV_PackEntity->bfPrev", pPrevFrame->LockData(), pPrevFrame->GetNumBytes() ); bf_read bfNew( "SV_PackEntity->bfNew", packedData, writeBuf.GetNumBytesWritten() ); int deltaProps[MAX_DATATABLE_PROPS]; int nChanges = SendTable_CalcDelta( pSendTable, pPrevFrame->LockData(), pPrevFrame->GetNumBits(), packedData, writeBuf.GetNumBitsWritten(), deltaProps, ARRAYSIZE( deltaProps ), edictIdx ); // If it's non-manual-mode, but we detect that there are no changes here, then just // use the previous pSnapshot if it's available (as though the entity were manual mode). // It would be interesting to hook here and see how many non-manual-mode entities // are winding up with no changes. if ( nChanges == 0 ) { if ( changeType == ENTITY_CHANGE_NONE ) { for ( int iDeltaProp=0; iDeltaProp < nChanges; iDeltaProp++ ) { Msg( "Entity %d (class '%s') reported ENTITY_CHANGE_NONE but '%s' changed.\n", edictIdx, STRING( ent->classname ), pSendTable->GetProp( deltaProps[iDeltaProp] )->GetName() ); } } else { if ( pPrevFrame->CompareRecipients( recip ) ) { if ( framesnapshot->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ) ) return; } } } // Ok, now snag the changeframe from the previous frame and update the 'last frame changed' // for the properties in the delta. pChangeFrame = pPrevFrame->SnagChangeFrameList(); ErrorIfNot( pChangeFrame && pChangeFrame->GetNumProps() == nFlatProps, ("SV_PackEntity: SnagChangeFrameList returned null") ); pChangeFrame->SetChangeTick( deltaProps, nChanges, pSnapshot->m_nTickNumber ); } else { // Ok, init the change frames for the first time. pChangeFrame = AllocChangeFrameList( nFlatProps, pSnapshot->m_nTickNumber ); } // Now make a PackedEntity and store the new packed data in there. PackedEntity *pCurFrame = framesnapshot->CreatePackedEntity( pSnapshot, edictIdx ); pCurFrame->SetChangeFrameList( pChangeFrame ); pCurFrame->m_nEntityIndex = edictIdx; pCurFrame->m_pSendTable = pSendTable; pCurFrame->AllocAndCopyPadded( packedData, writeBuf.GetNumBytesWritten(), &g_PackedDataAllocator ); pCurFrame->SetRecipients( recip ); } }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGameServer::DumpPrecacheStats( INetworkStringTable *table ) { if ( table == NULL ) { ConMsg( "Can only dump stats when active in a level\n" ); return; } CPrecacheItem *items = NULL; if ( table == m_pModelPrecacheTable ) { items = model_precache; } else if ( table == m_pGenericPrecacheTable ) { items = generic_precache; } else if ( table == m_pSoundPrecacheTable ) { items = sound_precache; } else if ( table == m_pDecalPrecacheTable ) { items = decal_precache; } if ( !items ) return; int count = table->GetNumStrings(); int maxcount = table->GetMaxStrings(); ConMsg( "\n" ); ConMsg( "Precache table %s: %i of %i slots used\n", table->GetTableName(), count, maxcount ); for ( int i = 0; i < count; i++ ) { char const *name = table->GetString( i ); CPrecacheItem *slot = &items[ i ]; int testLength; const CPrecacheUserData *p = ( const CPrecacheUserData * )table->GetStringUserData( i, &testLength ); ErrorIfNot( testLength == sizeof( *p ), ("CGameServer::DumpPrecacheStats: invalid CPrecacheUserData length (%d)", testLength) ); if ( !name || !slot || !p ) continue; ConMsg( "%03i: %s (%s): ", i, name, GetFlagString( p->flags ) ); if ( slot->GetReferenceCount() == 0 ) { ConMsg( " never used\n" ); } else { ConMsg( " %i refs, first %.2f mru %.2f\n", slot->GetReferenceCount(), slot->GetFirstReference(), slot->GetMostRecentReference() ); } } ConMsg( "\n" ); }