//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "server_pch.h" #include "client.h" #include "sv_packedentities.h" #include "bspfile.h" #include "eiface.h" #include "dt_send_eng.h" #include "dt_common_eng.h" #include "changeframelist.h" #include "sv_main.h" #include "hltvserver.h" #if defined( REPLAY_ENABLED ) #include "replayserver.h" #endif #include "dt_instrumentation_server.h" #include "LocalNetworkBackdoor.h" #include "tier0/vprof.h" #include "host.h" #include "networkstringtableserver.h" #include "networkstringtable.h" #include "utlbuffer.h" #include "dt.h" #include "con_nprint.h" #include "smooth_average.h" #include "vengineserver_impl.h" #include "tier0/vcrmode.h" #include "vstdlib/jobthread.h" #include "enginethreads.h" #ifdef SWDS IClientEntityList *entitylist = NULL; #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar sv_debugmanualmode( "sv_debugmanualmode", "0", 0, "Make sure entities correctly report whether or not their network data has changed." ); // Returns false and calls Host_Error if the edict's pvPrivateData is NULL. static inline bool SV_EnsurePrivateData(edict_t *pEdict) { if(pEdict->GetUnknown()) { return true; } else { Host_Error("SV_EnsurePrivateData: pEdict->pvPrivateData==NULL (ent %d).\n", pEdict - sv.edicts); return false; } } // 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( ServerClass *pServerClass, int iEdict, const void *pData, int nBytes ) { edict_t *pEnt = &sv.edicts[iEdict]; ErrorIfNot( pEnt->GetNetworkable(), ("SV_EnsureInstanceBaseline: edict %d missing ent", iEdict) ); ServerClass *pClass = pEnt->GetNetworkable()->GetServerClass(); // See if we already have a baseline for this class. if ( pClass->m_InstanceBaselineIndex == INVALID_STRING_INDEX ) { AUTO_LOCK( g_svInstanceBaselineMutex ); // We need this second check in case multiple instances of the same class have grabbed the lock. 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. int temp = sv.GetInstanceBaselineTable()->AddString( true, idString, // Note we're sending a string with the ID number, not the class name. nBytes, pData ); // Insert a compiler and/or CPU memory barrier to ensure that all side-effects have // been published before the index is published. Otherwise the string index may // be visible before its initialization has finished. This potential problem is caused // by the use of double-checked locking -- the problem is that the code outside of the // lock is looking at the variable that is protected by the lock. See this article for details: // http://en.wikipedia.org/wiki/Double-checked_locking // Write-release barrier ThreadMemoryBarrier(); pClass->m_InstanceBaselineIndex = temp; Assert( pClass->m_InstanceBaselineIndex != INVALID_STRING_INDEX ); } } // Read-acquire barrier ThreadMemoryBarrier(); } //----------------------------------------------------------------------------- // Pack the entity.... //----------------------------------------------------------------------------- static inline void SV_PackEntity( int edictIdx, edict_t* edict, ServerClass* pServerClass, CFrameSnapshot *pSnapshot ) { Assert( edictIdx < pSnapshot->m_nNumEntities ); tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "PackEntities_Normal%s", __FUNCTION__ ); int iSerialNum = pSnapshot->m_pEntities[ 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 ( !edict->HasStateChanged() ) { // Now this may not work if we didn't previously send a packet; // if not, then we gotta compute it bUsedPrev = framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ); } if ( bUsedPrev && !sv_debugmanualmode.GetInt() ) { edict->ClearStateChanged(); return; } // First encode the entity's data. ALIGN4 char packedData[MAX_PACKEDENTITY_DATA] ALIGN4_POST; bf_write writeBuf( "SV_PackEntity->writeBuf", packedData, sizeof( packedData ) ); SendTable *pSendTable = pServerClass->m_pTable; // (avoid constructor overhead). unsigned char tempData[ sizeof( CSendProxyRecipients ) * MAX_DATATABLE_PROXIES ]; CUtlMemory< CSendProxyRecipients > recip( (CSendProxyRecipients*)tempData, pSendTable->m_pPrecalc->GetNumDataTableProxies() ); if( !SendTable_Encode( pSendTable, edict->GetUnknown(), &writeBuf, edictIdx, &recip, false ) ) { Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", edictIdx ); } #ifndef NO_VCR // VCR mode stuff.. if ( vcr_verbose.GetInt() && writeBuf.GetNumBytesWritten() > 0 ) VCRGenericValueVerify( "writebuf", writeBuf.GetBasePointer(), writeBuf.GetNumBytesWritten()-1 ); #endif SV_EnsureInstanceBaseline( pServerClass, edictIdx, packedData, writeBuf.GetNumBytesWritten() ); int nFlatProps = SendTable_GetNumFlatProps( pSendTable ); IChangeFrameList *pChangeFrame = NULL; // 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 = framesnapshotmanager->GetPreviouslySentPacket( edictIdx, pSnapshot->m_pEntities[ edictIdx ].m_nSerialNumber ); if ( pPrevFrame ) { // Calculate a delta. Assert( !pPrevFrame->IsCompressed() ); int deltaProps[MAX_DATATABLE_PROPS]; int nChanges = SendTable_CalcDelta( pSendTable, pPrevFrame->GetData(), pPrevFrame->GetNumBits(), packedData, writeBuf.GetNumBitsWritten(), deltaProps, ARRAYSIZE( deltaProps ), edictIdx ); #ifndef NO_VCR if ( vcr_verbose.GetInt() ) VCRGenericValueVerify( "nChanges", &nChanges, sizeof( nChanges ) ); #endif // 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 ( pPrevFrame->CompareRecipients( recip ) ) { if ( framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ) ) { edict->ClearStateChanged(); return; } } } else { if ( !edict->HasStateChanged() ) { for ( int iDeltaProp=0; iDeltaProp < nChanges; iDeltaProp++ ) { Assert( pSendTable->m_pPrecalc ); Assert( deltaProps[iDeltaProp] < pSendTable->m_pPrecalc->GetNumProps() ); const SendProp *pProp = pSendTable->m_pPrecalc->GetProp( deltaProps[iDeltaProp] ); // If a field changed, but it changed because it encoded against tickcount, // then it's just like the entity changed the underlying field, not an error, that is. if ( pProp->GetFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT ) continue; Msg( "Entity %d (class '%s') reported ENTITY_CHANGE_NONE but '%s' changed.\n", edictIdx, edict->GetClassName(), pProp->GetName() ); } } } #ifndef _XBOX #if defined( REPLAY_ENABLED ) if ( (hltv && hltv->IsActive()) || (replay && replay->IsActive()) ) #else if ( hltv && hltv->IsActive() ) #endif { // in HLTV or Replay mode every PackedEntity keeps it's own ChangeFrameList // we just copy the ChangeFrameList from prev frame and update it pChangeFrame = pPrevFrame->GetChangeFrameList(); pChangeFrame = pChangeFrame->Copy(); // allocs and copies ChangeFrameList } else #endif { // 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, ("SV_PackEntity: SnagChangeFrameList returned null") ); ErrorIfNot( pChangeFrame->GetNumProps() == nFlatProps, ("SV_PackEntity: SnagChangeFrameList mismatched number of props[%d vs %d]", nFlatProps, pChangeFrame->GetNumProps() ) ); pChangeFrame->SetChangeTick( deltaProps, nChanges, pSnapshot->m_nTickCount ); } else { // Ok, init the change frames for the first time. pChangeFrame = AllocChangeFrameList( nFlatProps, pSnapshot->m_nTickCount ); } // Now make a PackedEntity and store the new packed data in there. { PackedEntity *pPackedEntity = framesnapshotmanager->CreatePackedEntity( pSnapshot, edictIdx ); pPackedEntity->SetChangeFrameList( pChangeFrame ); pPackedEntity->SetServerAndClientClass( pServerClass, NULL ); pPackedEntity->AllocAndCopyPadded( packedData, writeBuf.GetNumBytesWritten() ); pPackedEntity->SetRecipients( recip ); } edict->ClearStateChanged(); } // in HLTV mode we ALWAYS have to store position and PVS info, even if entity didnt change void SV_FillHLTVData( CFrameSnapshot *pSnapshot, edict_t *edict, int iValidEdict ) { #if !defined( _XBOX ) if ( pSnapshot->m_pHLTVEntityData && edict ) { CHLTVEntityData *pHLTVData = &pSnapshot->m_pHLTVEntityData[iValidEdict]; PVSInfo_t *pvsInfo = edict->GetNetworkable()->GetPVSInfo(); if ( pvsInfo->m_nClusterCount == 1 ) { // store cluster, if entity spawns only over one cluster pHLTVData->m_nNodeCluster = pvsInfo->m_pClusters[0]; } else { // otherwise save PVS head node for larger entities pHLTVData->m_nNodeCluster = pvsInfo->m_nHeadNode | (1<<31); } // remember origin pHLTVData->origin[0] = pvsInfo->m_vCenter[0]; pHLTVData->origin[1] = pvsInfo->m_vCenter[1]; pHLTVData->origin[2] = pvsInfo->m_vCenter[2]; } #endif } // in Replay mode we ALWAYS have to store position and PVS info, even if entity didnt change void SV_FillReplayData( CFrameSnapshot *pSnapshot, edict_t *edict, int iValidEdict ) { #if !defined( _XBOX ) if ( pSnapshot->m_pReplayEntityData && edict ) { CReplayEntityData *pReplayData = &pSnapshot->m_pReplayEntityData[iValidEdict]; PVSInfo_t *pvsInfo = edict->GetNetworkable()->GetPVSInfo(); if ( pvsInfo->m_nClusterCount == 1 ) { // store cluster, if entity spawns only over one cluster pReplayData->m_nNodeCluster = pvsInfo->m_pClusters[0]; } else { // otherwise save PVS head node for larger entities pReplayData->m_nNodeCluster = pvsInfo->m_nHeadNode | (1<<31); } // remember origin pReplayData->origin[0] = pvsInfo->m_vCenter[0]; pReplayData->origin[1] = pvsInfo->m_vCenter[1]; pReplayData->origin[2] = pvsInfo->m_vCenter[2]; } #endif } // Returns the SendTable that should be used with the specified edict. SendTable* GetEntSendTable(edict_t *pEdict) { if ( pEdict->GetNetworkable() ) { ServerClass *pClass = pEdict->GetNetworkable()->GetServerClass(); if ( pClass ) { return pClass->m_pTable; } } return NULL; } void PackEntities_NetworkBackDoor( int clientCount, CGameClient **clients, CFrameSnapshot *snapshot ) { Assert( clientCount == 1 ); VPROF_BUDGET( "PackEntities_NetworkBackDoor", VPROF_BUDGETGROUP_OTHER_NETWORKING ); CGameClient *client = clients[0]; // update variables cl, pInfo, frame for current client CCheckTransmitInfo *pInfo = &client->m_PackInfo; for ( int iValidEdict=0; iValidEdict < snapshot->m_nValidEntities; iValidEdict++ ) { int index = snapshot->m_pValidEntities[iValidEdict]; edict_t* edict = &sv.edicts[ index ]; // this is a bit of a hack to ensure that we get a "preview" of the // packet timstamp that the server will send so that things that // are encoded relative to packet time will be correct Assert( edict->m_NetworkSerialNumber != -1 ); bool bShouldTransmit = pInfo->m_pTransmitEdict->Get( index ) ? true : false; //CServerDTITimer timer( pSendTable, SERVERDTI_ENCODE ); // If we're using the fast path for a single-player game, just pass the entity // directly over to the client. Assert( index < snapshot->m_nNumEntities ); ServerClass *pSVClass = snapshot->m_pEntities[ index ].m_pClass; g_pLocalNetworkBackdoor->EntState( index, edict->m_NetworkSerialNumber, pSVClass->m_ClassID, pSVClass->m_pTable, edict->GetUnknown(), edict->HasStateChanged(), bShouldTransmit ); edict->ClearStateChanged(); } // Tell the client about any entities that are now dormant. g_pLocalNetworkBackdoor->ProcessDormantEntities(); InvalidateSharedEdictChangeInfos(); } static ConVar sv_parallel_packentities( "sv_parallel_packentities", "1" ); struct PackWork_t { int nIdx; edict_t *pEdict; CFrameSnapshot *pSnapshot; static void Process( PackWork_t &item ) { SV_PackEntity( item.nIdx, item.pEdict, item.pSnapshot->m_pEntities[ item.nIdx ].m_pClass, item.pSnapshot ); } }; void PackEntities_Normal( int clientCount, CGameClient **clients, CFrameSnapshot *snapshot ) { Assert( snapshot->m_nValidEntities >= 0 && snapshot->m_nValidEntities <= MAX_EDICTS ); tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s %d", __FUNCTION__, snapshot->m_nValidEntities ); CUtlVectorFixed< PackWork_t, MAX_EDICTS > workItems; // check for all active entities, if they are seen by at least on client, if // so, bit pack them for ( int iValidEdict=0; iValidEdict < snapshot->m_nValidEntities; ++iValidEdict ) { int index = snapshot->m_pValidEntities[ iValidEdict ]; Assert( index < snapshot->m_nNumEntities ); edict_t* edict = &sv.edicts[ index ]; // if HLTV is running save PVS info for each entity SV_FillHLTVData( snapshot, edict, iValidEdict ); // if Replay is running save PVS info for each entity SV_FillReplayData( snapshot, edict, iValidEdict ); // Check to see if the entity changed this frame... //ServerDTI_RegisterNetworkStateChange( pSendTable, ent->m_bStateChanged ); for ( int iClient = 0; iClient < clientCount; ++iClient ) { // entities is seen by at least this client, pack it and exit loop CGameClient *client = clients[iClient]; // update variables cl, pInfo, frame for current client CClientFrame *frame = client->m_pCurrentFrame; if( frame->transmit_entity.Get( index ) ) { PackWork_t w; w.nIdx = index; w.pEdict = edict; w.pSnapshot = snapshot; workItems.AddToTail( w ); break; } } } // Process work if ( sv_parallel_packentities.GetBool() ) { ParallelProcess( "PackWork_t::Process", workItems.Base(), workItems.Count(), &PackWork_t::Process ); } else { int c = workItems.Count(); for ( int i = 0; i < c; ++i ) { PackWork_t &w = workItems[ i ]; SV_PackEntity( w.nIdx, w.pEdict, w.pSnapshot->m_pEntities[ w.nIdx ].m_pClass, w.pSnapshot ); } } InvalidateSharedEdictChangeInfos(); } //----------------------------------------------------------------------------- // Writes the compressed packet of entities to all clients //----------------------------------------------------------------------------- void SV_ComputeClientPacks( int clientCount, CGameClient **clients, CFrameSnapshot *snapshot ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); MDLCACHE_CRITICAL_SECTION_(g_pMDLCache); // Do some setup for each client { VPROF_BUDGET_FLAGS( "SV_ComputeClientPacks", "CheckTransmit", BUDGETFLAG_SERVER ); for (int iClient = 0; iClient < clientCount; ++iClient) { CCheckTransmitInfo *pInfo = &clients[iClient]->m_PackInfo; clients[iClient]->SetupPackInfo( snapshot ); serverGameEnts->CheckTransmit( pInfo, snapshot->m_pValidEntities, snapshot->m_nValidEntities ); clients[iClient]->SetupPrevPackInfo(); } } VPROF_BUDGET_FLAGS( "SV_ComputeClientPacks", "ComputeClientPacks", BUDGETFLAG_SERVER ); if ( g_pLocalNetworkBackdoor ) { // This will force network string table updates for local client to go through the backdoor if it's active #ifdef SHARED_NET_STRING_TABLES sv.m_StringTables->TriggerCallbacks( clients[0]->m_nDeltaTick ); #else sv.m_StringTables->DirectUpdate( clients[0]->GetMaxAckTickCount() ); #endif g_pLocalNetworkBackdoor->StartEntityStateUpdate(); #ifndef SWDS int saveClientTicks = cl.GetClientTickCount(); int saveServerTicks = cl.GetServerTickCount(); bool bSaveSimulation = cl.insimulation; float flSaveLastServerTickTime = cl.m_flLastServerTickTime; cl.insimulation = true; cl.SetClientTickCount( sv.m_nTickCount ); cl.SetServerTickCount( sv.m_nTickCount ); cl.m_flLastServerTickTime = sv.m_nTickCount * host_state.interval_per_tick; g_ClientGlobalVariables.tickcount = cl.GetClientTickCount(); g_ClientGlobalVariables.curtime = cl.GetTime(); #endif PackEntities_NetworkBackDoor( clientCount, clients, snapshot ); g_pLocalNetworkBackdoor->EndEntityStateUpdate(); #ifndef SWDS cl.SetClientTickCount( saveClientTicks ); cl.SetServerTickCount( saveServerTicks ); cl.insimulation = bSaveSimulation; cl.m_flLastServerTickTime = flSaveLastServerTickTime; g_ClientGlobalVariables.tickcount = cl.GetClientTickCount(); g_ClientGlobalVariables.curtime = cl.GetTime(); #endif PrintPartialChangeEntsList(); } else { PackEntities_Normal( clientCount, clients, snapshot ); } } // If the table's ID is -1, writes its info into the buffer and increments curID. void SV_MaybeWriteSendTable( SendTable *pTable, bf_write &pBuf, bool bNeedDecoder ) { // Already sent? if ( pTable->GetWriteFlag() ) return; pTable->SetWriteFlag( true ); SVC_SendTable sndTbl; byte tmpbuf[4096]; sndTbl.m_DataOut.StartWriting( tmpbuf, sizeof(tmpbuf) ); // write send table properties into message buffer SendTable_WriteInfos(pTable, &sndTbl.m_DataOut ); sndTbl.m_bNeedsDecoder = bNeedDecoder; // write message to stream sndTbl.WriteToBuffer( pBuf ); } // Calls SV_MaybeWriteSendTable recursively. void SV_MaybeWriteSendTable_R( SendTable *pTable, bf_write &pBuf ) { SV_MaybeWriteSendTable( pTable, pBuf, false ); // Make sure we send child send tables.. for(int i=0; i < pTable->m_nProps; i++) { SendProp *pProp = &pTable->m_pProps[i]; if( pProp->m_Type == DPT_DataTable ) SV_MaybeWriteSendTable_R( pProp->GetDataTable(), pBuf ); } } // Sets up SendTable IDs and sends an svc_sendtable for each table. void SV_WriteSendTables( ServerClass *pClasses, bf_write &pBuf ) { ServerClass *pCur; DataTable_ClearWriteFlags( pClasses ); // First, we send all the leaf classes. These are the ones that will need decoders // on the client. for ( pCur=pClasses; pCur; pCur=pCur->m_pNext ) { SV_MaybeWriteSendTable( pCur->m_pTable, pBuf, true ); } // Now, we send their base classes. These don't need decoders on the client // because we will never send these SendTables by themselves. for ( pCur=pClasses; pCur; pCur=pCur->m_pNext ) { SV_MaybeWriteSendTable_R( pCur->m_pTable, pBuf ); } } //----------------------------------------------------------------------------- // Purpose: // Input : crc - //----------------------------------------------------------------------------- void SV_ComputeClassInfosCRC( CRC32_t* crc ) { ServerClass *pClasses = serverGameDLL->GetAllServerClasses(); for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext ) { CRC32_ProcessBuffer( crc, (void *)pClass->m_pNetworkName, Q_strlen( pClass->m_pNetworkName ) ); CRC32_ProcessBuffer( crc, (void *)pClass->m_pTable->GetName(), Q_strlen(pClass->m_pTable->GetName() ) ); } } void CGameServer::AssignClassIds() { ServerClass *pClasses = serverGameDLL->GetAllServerClasses(); // Count the number of classes. int nClasses = 0; for ( ServerClass *pCount=pClasses; pCount; pCount=pCount->m_pNext ) { ++nClasses; } // These should be the same! If they're not, then it should detect an explicit create message. ErrorIfNot( nClasses <= MAX_SERVER_CLASSES, ("CGameServer::AssignClassIds: too many server classes (%i, MAX = %i).\n", nClasses, MAX_SERVER_CLASSES ); ); serverclasses = nClasses; serverclassbits = Q_log2( serverclasses ) + 1; bool bSpew = CommandLine()->FindParm( "-netspike" ) != 0; int curID = 0; for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext ) { pClass->m_ClassID = curID++; if ( bSpew ) { Msg( "%d == '%s'\n", pClass->m_ClassID, pClass->GetName() ); } } } // Assign each class and ID and write an svc_classinfo for each one. void SV_WriteClassInfos(ServerClass *pClasses, bf_write &pBuf) { // Assert( sv.serverclasses < MAX_SERVER_CLASSES ); SVC_ClassInfo classinfomsg; classinfomsg.m_bCreateOnClient = false; for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext ) { SVC_ClassInfo::class_t svclass; svclass.classID = pClass->m_ClassID; Q_strncpy( svclass.datatablename, pClass->m_pTable->GetName(), sizeof(svclass.datatablename) ); Q_strncpy( svclass.classname, pClass->m_pNetworkName, sizeof(svclass.classname) ); classinfomsg.m_Classes.AddToTail( svclass ); // add all known classes to message } classinfomsg.WriteToBuffer( pBuf ); } // This is implemented for the datatable code so its warnings can include an object's classname. const char* GetObjectClassName( int objectID ) { if ( objectID >= 0 && objectID < sv.num_edicts ) { return sv.edicts[objectID].GetClassName(); } else { return "[unknown]"; } }