diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/sv_ents_write.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'engine/sv_ents_write.cpp')
| -rw-r--r-- | engine/sv_ents_write.cpp | 1043 |
1 files changed, 1043 insertions, 0 deletions
diff --git a/engine/sv_ents_write.cpp b/engine/sv_ents_write.cpp new file mode 100644 index 0000000..084d16d --- /dev/null +++ b/engine/sv_ents_write.cpp @@ -0,0 +1,1043 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "server_pch.h" +#include <eiface.h> +#include <dt_send.h> +#include <utllinkedlist.h> +#include "tier0/etwprof.h" +#include "dt_send_eng.h" +#include "dt.h" +#include "net_synctags.h" +#include "dt_instrumentation_server.h" +#include "LocalNetworkBackdoor.h" +#include "ents_shared.h" +#include "hltvserver.h" +#include "replayserver.h" +#include "tier0/vcrmode.h" +#include "framesnapshot.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar g_CV_DTWatchEnt; + +//----------------------------------------------------------------------------- +// Delta timing stuff. +//----------------------------------------------------------------------------- + +static ConVar sv_deltatime( "sv_deltatime", "0", 0, "Enable profiling of CalcDelta calls" ); +static ConVar sv_deltaprint( "sv_deltaprint", "0", 0, "Print accumulated CalcDelta profiling data (only if sv_deltatime is on)" ); + +#if defined( DEBUG_NETWORKING ) +ConVar sv_packettrace( "sv_packettrace", "1", 0, "For debugging, print entity creation/deletion info to console." ); +#endif + +class CChangeTrack +{ +public: + char *m_pName; + int m_nChanged; + int m_nUnchanged; + + CCycleCount m_Count; + CCycleCount m_EncodeCount; +}; + + +static CUtlLinkedList<CChangeTrack*, int> g_Tracks; + + +// These are the main variables used by the SV_CreatePacketEntities function. +// The function is split up into multiple smaller ones and they pass this structure around. +class CEntityWriteInfo : public CEntityInfo +{ +public: + bf_write *m_pBuf; + int m_nClientEntity; + + PackedEntity *m_pOldPack; + PackedEntity *m_pNewPack; + + // For each entity handled in the to packet, mark that's it has already been deleted if that's the case + CBitVec<MAX_EDICTS> m_DeletionFlags; + + CFrameSnapshot *m_pFromSnapshot; // = m_pFrom->GetSnapshot(); + CFrameSnapshot *m_pToSnapshot; // = m_pTo->GetSnapshot(); + + CFrameSnapshot *m_pBaseline; // the clients baseline + + CBaseServer *m_pServer; // the server who writes this entity + + int m_nFullProps; // number of properties send as full update (Enter PVS) + bool m_bCullProps; // filter props by clients in recipient lists + + /* Some profiling data + int m_nTotalGap; + int m_nTotalGapCount; */ +}; + + + +//----------------------------------------------------------------------------- +// Delta timing helpers. +//----------------------------------------------------------------------------- + +CChangeTrack* GetChangeTrack( const char *pName ) +{ + FOR_EACH_LL( g_Tracks, i ) + { + CChangeTrack *pCur = g_Tracks[i]; + + if ( stricmp( pCur->m_pName, pName ) == 0 ) + return pCur; + } + + CChangeTrack *pCur = new CChangeTrack; + int len = strlen(pName)+1; + pCur->m_pName = new char[len]; + Q_strncpy( pCur->m_pName, pName, len ); + pCur->m_nChanged = pCur->m_nUnchanged = 0; + + g_Tracks.AddToTail( pCur ); + + return pCur; +} + + +void PrintChangeTracks() +{ + ConMsg( "\n\n" ); + ConMsg( "------------------------------------------------------------------------\n" ); + ConMsg( "CalcDelta MS / %% time / Encode MS / # Changed / # Unchanged / Class Name\n" ); + ConMsg( "------------------------------------------------------------------------\n" ); + + CCycleCount total, encodeTotal; + FOR_EACH_LL( g_Tracks, i ) + { + CChangeTrack *pCur = g_Tracks[i]; + CCycleCount::Add( pCur->m_Count, total, total ); + CCycleCount::Add( pCur->m_EncodeCount, encodeTotal, encodeTotal ); + } + + FOR_EACH_LL( g_Tracks, j ) + { + CChangeTrack *pCur = g_Tracks[j]; + + ConMsg( "%6.2fms %5.2f %6.2fms %4d %4d %s\n", + pCur->m_Count.GetMillisecondsF(), + pCur->m_Count.GetMillisecondsF() * 100.0f / total.GetMillisecondsF(), + pCur->m_EncodeCount.GetMillisecondsF(), + pCur->m_nChanged, + pCur->m_nUnchanged, + pCur->m_pName + ); + } + + ConMsg( "\n\n" ); + ConMsg( "Total CalcDelta MS: %.2f\n\n", total.GetMillisecondsF() ); + ConMsg( "Total Encode MS: %.2f\n\n", encodeTotal.GetMillisecondsF() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Entity wasn't dealt with in packet, but it has been deleted, we'll flag +// the entity for destruction +// Input : type - +// entnum - +// *from - +// *to - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +static inline bool SV_NeedsExplicitDestroy( int entnum, CFrameSnapshot *from, CFrameSnapshot *to ) +{ + // Never on uncompressed packet + + if( entnum >= to->m_nNumEntities || to->m_pEntities[entnum].m_pClass == NULL ) // doesn't exits in new + { + if ( entnum >= from->m_nNumEntities ) + return false; // didn't exist in old + + // in old, but not in new, destroy. + if( from->m_pEntities[ entnum ].m_pClass != NULL ) + { + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Creates a delta header for the entity +//----------------------------------------------------------------------------- +static inline void SV_UpdateHeaderDelta( + CEntityWriteInfo &u, + int entnum ) +{ + // Profiling info + // u.m_nTotalGap += entnum - u.m_nHeaderBase; + // u.m_nTotalGapCount++; + + // Keep track of number of headers so we can tell the client + u.m_nHeaderCount++; + u.m_nHeaderBase = entnum; +} + + +// +// Write the delta header. Also update the header delta info if bUpdateHeaderDelta is true. +// +// There are some cases where you want to tenatively write a header, then possibly back it out. +// In these cases: +// - pass in false for bUpdateHeaderDelta +// - store the return value from SV_WriteDeltaHeader +// - call SV_UpdateHeaderDelta ONLY if you want to keep the delta header it wrote +// +static inline void SV_WriteDeltaHeader( + CEntityWriteInfo &u, + int entnum, + int flags ) +{ + bf_write *pBuf = u.m_pBuf; + + // int startbit = pBuf->GetNumBitsWritten(); + + int offset = entnum - u.m_nHeaderBase - 1; + + Assert ( offset >= 0 ); + + SyncTag_Write( u.m_pBuf, "Hdr" ); + + pBuf->WriteUBitVar( offset ); + + if ( flags & FHDR_LEAVEPVS ) + { + pBuf->WriteOneBit( 1 ); // leave PVS bit + pBuf->WriteOneBit( flags & FHDR_DELETE ); + } + else + { + pBuf->WriteOneBit( 0 ); // delta or enter PVS + pBuf->WriteOneBit( flags & FHDR_ENTERPVS ); + } + + SV_UpdateHeaderDelta( u, entnum ); +} + + +// Calculates the delta between the two states and writes the delta and the new properties +// into u.m_pBuf. Returns false if the states are the same. +// +// Also uses the IFrameChangeList in pTo to come up with a smaller set of properties to delta against. +// It deltas against any properties that have changed since iFromFrame. +// If iFromFrame is -1, then it deltas all properties. +static int SV_CalcDeltaAndWriteProps( + CEntityWriteInfo &u, + + const void *pFromData, + int nFromBits, + + PackedEntity *pTo + ) +{ + // Calculate the delta props. + int deltaProps[MAX_DATATABLE_PROPS]; + void *pToData = pTo->GetData(); + int nToBits = pTo->GetNumBits(); + SendTable *pToTable = pTo->m_pServerClass->m_pTable; + + // TODO if our baseline is compressed, uncompress first + Assert( !pTo->IsCompressed() ); + + int nDeltaProps = SendTable_CalcDelta( + pToTable, + + pFromData, + nFromBits, + + pToData, + nToBits, + + deltaProps, + ARRAYSIZE( deltaProps ), + + pTo->m_nEntityIndex ); + + + + // Cull out props given what the proxies say. + int culledProps[MAX_DATATABLE_PROPS]; + + int nCulledProps = 0; + if ( nDeltaProps ) + { + nCulledProps = SendTable_CullPropsFromProxies( + pToTable, + deltaProps, + nDeltaProps, + u.m_nClientEntity-1, + NULL, + -1, + + pTo->GetRecipients(), + pTo->GetNumRecipients(), + + culledProps, + ARRAYSIZE( culledProps ) ); + } + + + // Write the properties. + SendTable_WritePropList( + pToTable, + + pToData, // object data + pTo->GetNumBits(), + + u.m_pBuf, // output buffer + + pTo->m_nEntityIndex, + culledProps, + nCulledProps ); + + return nCulledProps; +} + + +// NOTE: to optimize this, it could store the bit offsets of each property in the packed entity. +// It would only have to store the offsets for the entities for each frame, since it only reaches +// into the current frame's entities here. +static inline void SV_WritePropsFromPackedEntity( + CEntityWriteInfo &u, + const int *pCheckProps, + const int nCheckProps + ) +{ + PackedEntity * pTo = u.m_pNewPack; + PackedEntity * pFrom = u.m_pOldPack; + SendTable *pSendTable = pTo->m_pServerClass->m_pTable; + + CServerDTITimer timer( pSendTable, SERVERDTI_WRITE_DELTA_PROPS ); + if ( g_bServerDTIEnabled && !u.m_pServer->IsHLTV() && !u.m_pServer->IsReplay() ) + { + ICollideable *pEnt = sv.edicts[pTo->m_nEntityIndex].GetCollideable(); + ICollideable *pClientEnt = sv.edicts[u.m_nClientEntity].GetCollideable(); + if ( pEnt && pClientEnt ) + { + float flDist = (pEnt->GetCollisionOrigin() - pClientEnt->GetCollisionOrigin()).Length(); + ServerDTI_AddEntityEncodeEvent( pSendTable, flDist ); + } + } + + const void *pToData; + int nToBits; + + if ( pTo->IsCompressed() ) + { + // let server uncompress PackedEntity + pToData = u.m_pServer->UncompressPackedEntity( pTo, nToBits ); + } + else + { + // get raw data direct + pToData = pTo->GetData(); + nToBits = pTo->GetNumBits(); + } + + Assert( pToData != NULL ); + + // Cull out the properties that their proxies said not to send to this client. + int pSendProps[MAX_DATATABLE_PROPS]; + const int *sendProps = pCheckProps; + int nSendProps = nCheckProps; + bf_write bufStart; + + + // cull properties that are removed by SendProxies for this client. + // don't do that for HLTV relay proxies + if ( u.m_bCullProps ) + { + sendProps = pSendProps; + + nSendProps = SendTable_CullPropsFromProxies( + pSendTable, + pCheckProps, + nCheckProps, + u.m_nClientEntity-1, + + pFrom->GetRecipients(), + pFrom->GetNumRecipients(), + + pTo->GetRecipients(), + pTo->GetNumRecipients(), + + pSendProps, + ARRAYSIZE( pSendProps ) + ); + } + else + { + // this is a HLTV relay proxy + bufStart = *u.m_pBuf; + } + + SendTable_WritePropList( + pSendTable, + pToData, + nToBits, + u.m_pBuf, + pTo->m_nEntityIndex, + + sendProps, + nSendProps + ); + + if ( !u.m_bCullProps && hltv ) + { + // this is a HLTV relay proxy, cache delta bits + int nBits = u.m_pBuf->GetNumBitsWritten() - bufStart.GetNumBitsWritten(); + hltv->m_DeltaCache.AddDeltaBits( pTo->m_nEntityIndex, u.m_pFromSnapshot->m_nTickCount, nBits, &bufStart ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: See if the entity needs a "hard" reset ( i.e., and explicit creation tag ) +// This should only occur if the entity slot deleted and re-created an entity faster than +// the last two updates toa player. Should never or almost never occur. You never know though. +// Input : type - +// entnum - +// *from - +// *to - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +static bool SV_NeedsExplicitCreate( CEntityWriteInfo &u ) +{ + // Never on uncompressed packet + if ( !u.m_bAsDelta ) + { + return false; + } + + const int index = u.m_nNewEntity; + + if ( index >= u.m_pFromSnapshot->m_nNumEntities ) + return true; // entity didn't exist in old frame, so create + + // Server thinks the entity was continues, but the serial # changed, so we might need to destroy and recreate it + const CFrameSnapshotEntry *pFromEnt = &u.m_pFromSnapshot->m_pEntities[index]; + const CFrameSnapshotEntry *pToEnt = &u.m_pToSnapshot->m_pEntities[index]; + + bool bNeedsExplicitCreate = (pFromEnt->m_pClass == NULL) || pFromEnt->m_nSerialNumber != pToEnt->m_nSerialNumber; + +#ifdef _DEBUG + if ( !bNeedsExplicitCreate ) + { + // If it doesn't need explicit create, then the classnames should match. + // This assert is analagous to the "Server / Client mismatch" one on the client. + static int nWhines = 0; + if ( pFromEnt->m_pClass->GetName() != pToEnt->m_pClass->GetName() ) + { + if ( ++nWhines < 4 ) + { + Msg( "ERROR in SV_NeedsExplicitCreate: ent %d from/to classname (%s/%s) mismatch.\n", u.m_nNewEntity, pFromEnt->m_pClass->GetName(), pToEnt->m_pClass->GetName() ); + } + } + } +#endif + + return bNeedsExplicitCreate; +} + + +static inline void SV_DetermineUpdateType( CEntityWriteInfo &u ) +{ + // Figure out how we want to update the entity. + if( u.m_nNewEntity < u.m_nOldEntity ) + { + // If the entity was not in the old packet (oldnum == 9999), then + // delta from the baseline since this is a new entity. + u.m_UpdateType = EnterPVS; + return; + } + + if( u.m_nNewEntity > u.m_nOldEntity ) + { + // If the entity was in the old list, but is not in the new list + // (newnum == 9999), then construct a special remove message. + u.m_UpdateType = LeavePVS; + return; + } + + Assert( u.m_pToSnapshot->m_pEntities[ u.m_nNewEntity ].m_pClass ); + + bool recreate = SV_NeedsExplicitCreate( u ); + + if ( recreate ) + { + u.m_UpdateType = EnterPVS; + return; + } + + // These should be the same! If they're not, then it should detect an explicit create message. + Assert( u.m_pOldPack->m_pServerClass == u.m_pNewPack->m_pServerClass); + + // We can early out with the delta bits if we are using the same pack handles... + if ( u.m_pOldPack == u.m_pNewPack ) + { + Assert( u.m_pOldPack != NULL ); + u.m_UpdateType = PreserveEnt; + return; + } + +#ifndef _X360 + int nBits; +#if defined( REPLAY_ENABLED ) + if ( !u.m_bCullProps && (hltv || replay) ) + { + unsigned char *pBuffer = hltv ? hltv ->m_DeltaCache.FindDeltaBits( u.m_nNewEntity, u.m_pFromSnapshot->m_nTickCount, nBits ) + : replay->m_DeltaCache.FindDeltaBits( u.m_nNewEntity, u.m_pFromSnapshot->m_nTickCount, nBits ); +#else + if ( !u.m_bCullProps && hltv ) + { + unsigned char *pBuffer = hltv->m_DeltaCache.FindDeltaBits( u.m_nNewEntity, u.m_pFromSnapshot->m_nTickCount, nBits ); +#endif + + if ( pBuffer ) + { + if ( nBits > 0 ) + { + // Write a header. + SV_WriteDeltaHeader( u, u.m_nNewEntity, FHDR_ZERO ); + + // just write the cached bit stream + u.m_pBuf->WriteBits( pBuffer, nBits ); + + u.m_UpdateType = DeltaEnt; + } + else + { + u.m_UpdateType = PreserveEnt; + } + + return; // we used the cache, great + } + } +#endif + + int checkProps[MAX_DATATABLE_PROPS]; + int nCheckProps = u.m_pNewPack->GetPropsChangedAfterTick( u.m_pFromSnapshot->m_nTickCount, checkProps, ARRAYSIZE( checkProps ) ); + + if ( nCheckProps == -1 ) + { + // check failed, we have to recalc delta props based on from & to snapshot + // that should happen only in HLTV/Replay demo playback mode, this code is really expensive + + const void *pOldData, *pNewData; + int nOldBits, nNewBits; + + if ( u.m_pOldPack->IsCompressed() ) + { + pOldData = u.m_pServer->UncompressPackedEntity( u.m_pOldPack, nOldBits ); + } + else + { + pOldData = u.m_pOldPack->GetData(); + nOldBits = u.m_pOldPack->GetNumBits(); + } + + if ( u.m_pNewPack->IsCompressed() ) + { + pNewData = u.m_pServer->UncompressPackedEntity( u.m_pNewPack, nNewBits ); + } + else + { + pNewData = u.m_pNewPack->GetData(); + nNewBits = u.m_pNewPack->GetNumBits(); + } + + nCheckProps = SendTable_CalcDelta( + u.m_pOldPack->m_pServerClass->m_pTable, + pOldData, + nOldBits, + pNewData, + nNewBits, + checkProps, + ARRAYSIZE( checkProps ), + u.m_nNewEntity + ); + } + +#ifndef NO_VCR + if ( vcr_verbose.GetInt() ) + { + VCRGenericValueVerify( "checkProps", checkProps, sizeof( checkProps[0] ) * nCheckProps ); + } +#endif + + if ( nCheckProps > 0 ) + { + // Write a header. + SV_WriteDeltaHeader( u, u.m_nNewEntity, FHDR_ZERO ); +#if defined( DEBUG_NETWORKING ) + int startBit = u.m_pBuf->GetNumBitsWritten(); +#endif + SV_WritePropsFromPackedEntity( u, checkProps, nCheckProps ); +#if defined( DEBUG_NETWORKING ) + int endBit = u.m_pBuf->GetNumBitsWritten(); + TRACE_PACKET( ( " Delta Bits (%d) = %d (%d bytes)\n", u.m_nNewEntity, (endBit - startBit), ( (endBit - startBit) + 7 ) / 8 ) ); +#endif + // If the numbers are the same, then the entity was in the old and new packet. + // Just delta compress the differences. + u.m_UpdateType = DeltaEnt; + } + else + { +#ifndef _X360 + if ( !u.m_bCullProps ) + { + if ( hltv ) + { + // no bits changed, PreserveEnt + hltv->m_DeltaCache.AddDeltaBits( u.m_nNewEntity, u.m_pFromSnapshot->m_nTickCount, 0, NULL ); + } + +#if defined( REPLAY_ENABLED ) + if ( replay ) + { + // no bits changed, PreserveEnt + replay->m_DeltaCache.AddDeltaBits( u.m_nNewEntity, u.m_pFromSnapshot->m_nTickCount, 0, NULL ); + } +#endif + } +#endif + u.m_UpdateType = PreserveEnt; + } +} + +static inline ServerClass* GetEntServerClass(edict_t *pEdict) +{ + return pEdict->GetNetworkable()->GetServerClass(); +} + + + +static inline void SV_WriteEnterPVS( CEntityWriteInfo &u ) +{ + TRACE_PACKET(( " SV Enter PVS (%d) %s\n", u.m_nNewEntity, u.m_pNewPack->m_pServerClass->m_pNetworkName ) ); + + SV_WriteDeltaHeader( u, u.m_nNewEntity, FHDR_ENTERPVS ); + + Assert( u.m_nNewEntity < u.m_pToSnapshot->m_nNumEntities ); + + CFrameSnapshotEntry *entry = &u.m_pToSnapshot->m_pEntities[u.m_nNewEntity]; + + ServerClass *pClass = entry->m_pClass; + + if ( !pClass ) + { + Host_Error("SV_CreatePacketEntities: GetEntServerClass failed for ent %d.\n", u.m_nNewEntity); + } + + TRACE_PACKET(( " SV Enter Class %s\n", pClass->m_pNetworkName ) ); + + if ( pClass->m_ClassID >= u.m_pServer->serverclasses ) + { + ConMsg( "pClass->m_ClassID(%i) >= %i\n", pClass->m_ClassID, u.m_pServer->serverclasses ); + Assert( 0 ); + } + + u.m_pBuf->WriteUBitLong( pClass->m_ClassID, u.m_pServer->serverclassbits ); + + // Write some of the serial number's bits. + u.m_pBuf->WriteUBitLong( entry->m_nSerialNumber, NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS ); + + // Get the baseline. + // Since the ent is in the fullpack, then it must have either a static or an instance baseline. + PackedEntity *pBaseline = u.m_bAsDelta ? framesnapshotmanager->GetPackedEntity( u.m_pBaseline, u.m_nNewEntity ) : NULL; + const void *pFromData; + int nFromBits; + + if ( pBaseline && (pBaseline->m_pServerClass == u.m_pNewPack->m_pServerClass) ) + { + Assert( !pBaseline->IsCompressed() ); + pFromData = pBaseline->GetData(); + nFromBits = pBaseline->GetNumBits(); + } + else + { + // Since the ent is in the fullpack, then it must have either a static or an instance baseline. + int nFromBytes; + if ( !u.m_pServer->GetClassBaseline( pClass, &pFromData, &nFromBytes ) ) + { + Error( "SV_WriteEnterPVS: missing instance baseline for '%s'.", pClass->m_pNetworkName ); + } + + ErrorIfNot( pFromData, + ("SV_WriteEnterPVS: missing pFromData for '%s'.", pClass->m_pNetworkName) + ); + + nFromBits = nFromBytes * 8; // NOTE: this isn't the EXACT number of bits but that's ok since it's + // only used to detect if we overran the buffer (and if we do, it's probably + // by more than 7 bits). + } + + if ( u.m_pTo->from_baseline ) + { + // remember that we sent this entity as full update from entity baseline + u.m_pTo->from_baseline->Set( u.m_nNewEntity ); + } + + const void *pToData; + int nToBits; + + if ( u.m_pNewPack->IsCompressed() ) + { + pToData = u.m_pServer->UncompressPackedEntity( u.m_pNewPack, nToBits ); + } + else + { + pToData = u.m_pNewPack->GetData(); + nToBits = u.m_pNewPack->GetNumBits(); + } + + /*if ( server->IsHLTV() || server->IsReplay() ) + {*/ + // send all changed properties when entering PVS (no SendProxy culling since we may use it as baseline + u.m_nFullProps += SendTable_WriteAllDeltaProps( pClass->m_pTable, pFromData, nFromBits, + pToData, nToBits, u.m_pNewPack->m_nEntityIndex, u.m_pBuf ); + /*} + else + { + // remove all props that are excluded for this client + u.m_nFullProps += SV_CalcDeltaAndWriteProps( u, pFromData, nFromBits, u.m_pNewPack ); + }*/ + + if ( u.m_nNewEntity == u.m_nOldEntity ) + u.NextOldEntity(); // this was a entity recreate + + u.NextNewEntity(); +} + + +static inline void SV_WriteLeavePVS( CEntityWriteInfo &u ) +{ + int headerflags = FHDR_LEAVEPVS; + bool deleteentity = false; + + if ( u.m_bAsDelta ) + { + deleteentity = SV_NeedsExplicitDestroy( u.m_nOldEntity, u.m_pFromSnapshot, u.m_pToSnapshot ); + } + + if ( deleteentity ) + { + // Mark that we handled deletion of this index + u.m_DeletionFlags.Set( u.m_nOldEntity ); + + headerflags |= FHDR_DELETE; + } + + TRACE_PACKET( ( " SV Leave PVS (%d) %s %s\n", u.m_nOldEntity, + deleteentity ? "deleted" : "left pvs", + u.m_pOldPack->m_pServerClass->m_pNetworkName ) ); + + SV_WriteDeltaHeader( u, u.m_nOldEntity, headerflags ); + + u.NextOldEntity(); +} + + +static inline void SV_WriteDeltaEnt( CEntityWriteInfo &u ) +{ + TRACE_PACKET( ( " SV Delta PVS (%d %d) %s\n", u.m_nNewEntity, u.m_nOldEntity, u.m_pOldPack->m_pServerClass->m_pNetworkName ) ); + + // NOTE: it was already written in DetermineUpdateType. By doing it this way, we avoid an expensive + // (non-byte-aligned) copy of the data. + + u.NextOldEntity(); + u.NextNewEntity(); +} + + +static inline void SV_PreserveEnt( CEntityWriteInfo &u ) +{ + TRACE_PACKET( ( " SV Preserve PVS (%d) %s\n", u.m_nOldEntity, u.m_pOldPack->m_pServerClass->m_pNetworkName ) ); + + // updateType is preserveEnt. The client will detect this because our next entity will have a newnum + // that is greater than oldnum, in which case the client just keeps the current entity alive. + u.NextOldEntity(); + u.NextNewEntity(); +} + + +static inline void SV_WriteEntityUpdate( CEntityWriteInfo &u ) +{ + switch( u.m_UpdateType ) + { + case EnterPVS: + { + SV_WriteEnterPVS( u ); + } + break; + + case LeavePVS: + { + SV_WriteLeavePVS( u ); + } + break; + + case DeltaEnt: + { + SV_WriteDeltaEnt( u ); + } + break; + + case PreserveEnt: + { + SV_PreserveEnt( u ); + } + break; + } +} + + +static inline int SV_WriteDeletions( CEntityWriteInfo &u ) +{ + if( !u.m_bAsDelta ) + return 0; + + int nNumDeletions = 0; + + CFrameSnapshot *pFromSnapShot = u.m_pFromSnapshot; + CFrameSnapshot *pToSnapShot = u.m_pToSnapshot; + + int nLast = MAX( pFromSnapShot->m_nNumEntities, pToSnapShot->m_nNumEntities ); + for ( int i = 0; i < nLast; i++ ) + { + // Packet update didn't clear it out expressly + if ( u.m_DeletionFlags.Get( i ) ) + continue; + + // If the entity is marked to transmit in the u.m_pTo, then it can never be destroyed by the m_iExplicitDeleteSlots + // Another possible fix would be to clear any slots in the explicit deletes list that were actually occupied when a snapshot was taken + if ( u.m_pTo->transmit_entity.Get(i) ) + continue; + + // Looks like it should be gone + bool bNeedsExplicitDelete = SV_NeedsExplicitDestroy( i, pFromSnapShot, pToSnapShot ); + if ( !bNeedsExplicitDelete && u.m_pTo ) + { + bNeedsExplicitDelete = ( pToSnapShot->m_iExplicitDeleteSlots.Find(i) != pToSnapShot->m_iExplicitDeleteSlots.InvalidIndex() ); + // We used to do more stuff here as a sanity check, but I don't think it was necessary since the only thing that would unset the bould would be a "recreate" in the same slot which is + // already implied by the u.m_pTo->transmit_entity.Get(i) check + } + + // Check conditions + if ( bNeedsExplicitDelete ) + { + TRACE_PACKET( ( " SV Explicit Destroy (%d)\n", i ) ); + + u.m_pBuf->WriteOneBit(1); + u.m_pBuf->WriteUBitLong( i, MAX_EDICT_BITS ); + ++nNumDeletions; + } + } + // No more entities.. + u.m_pBuf->WriteOneBit(0); + + return nNumDeletions; +} + + +/* +============= +WritePacketEntities + +Computes either a compressed, or uncompressed delta buffer for the client. +Returns the size IN BITS of the message buffer created. +============= +*/ + +void CBaseServer::WriteDeltaEntities( CBaseClient *client, CClientFrame *to, CClientFrame *from, bf_write &pBuf ) +{ + VPROF_BUDGET( "CBaseServer::WriteDeltaEntities", VPROF_BUDGETGROUP_OTHER_NETWORKING ); + // Setup the CEntityWriteInfo structure. + CEntityWriteInfo u; + u.m_pBuf = &pBuf; + u.m_pTo = to; + u.m_pToSnapshot = to->GetSnapshot(); + u.m_pBaseline = client->m_pBaseline; + u.m_nFullProps = 0; + u.m_pServer = this; + u.m_nClientEntity = client->m_nEntityIndex; +#ifndef _XBOX + if ( IsHLTV() || IsReplay() ) + { + // cull props only on master proxy + u.m_bCullProps = sv.IsActive(); + } + else +#endif + { + u.m_bCullProps = true; // always cull props for players + } + + if ( from != NULL ) + { + u.m_bAsDelta = true; + u.m_pFrom = from; + u.m_pFromSnapshot = from->GetSnapshot(); + Assert( u.m_pFromSnapshot ); + } + else + { + u.m_bAsDelta = false; + u.m_pFrom = NULL; + u.m_pFromSnapshot = NULL; + } + + u.m_nHeaderCount = 0; +// u.m_nTotalGap = 0; +// u.m_nTotalGapCount = 0; + + // set from_baseline pointer if this snapshot may become a baseline update + if ( client->m_nBaselineUpdateTick == -1 ) + { + client->m_BaselinesSent.ClearAll(); + to->from_baseline = &client->m_BaselinesSent; + } + + // Write the header, TODO use class SVC_PacketEntities + + TRACE_PACKET(( "WriteDeltaEntities (%d)\n", u.m_pToSnapshot->m_nNumEntities )); + + u.m_pBuf->WriteUBitLong( svc_PacketEntities, NETMSG_TYPE_BITS ); + + u.m_pBuf->WriteUBitLong( u.m_pToSnapshot->m_nNumEntities, MAX_EDICT_BITS ); + + if ( u.m_bAsDelta ) + { + u.m_pBuf->WriteOneBit( 1 ); // use delta sequence + + u.m_pBuf->WriteLong( u.m_pFrom->tick_count ); // This is the sequence # that we are updating from. + } + else + { + u.m_pBuf->WriteOneBit( 0 ); // use baseline + } + + u.m_pBuf->WriteUBitLong ( client->m_nBaselineUsed, 1 ); // tell client what baseline we are using + + // Store off current position + bf_write savepos = *u.m_pBuf; + + // Save room for number of headers to parse, too + u.m_pBuf->WriteUBitLong ( 0, MAX_EDICT_BITS+DELTASIZE_BITS+1 ); + + int startbit = u.m_pBuf->GetNumBitsWritten(); + + bool bIsTracing = client->IsTracing(); + if ( bIsTracing ) + { + client->TraceNetworkData( pBuf, "Delta Entities Overhead" ); + } + + // Don't work too hard if we're using the optimized single-player mode. + if ( !g_pLocalNetworkBackdoor ) + { + // Iterate through the in PVS bitfields until we find an entity + // that was either in the old pack or the new pack + u.NextOldEntity(); + u.NextNewEntity(); + + while ( (u.m_nOldEntity != ENTITY_SENTINEL) || (u.m_nNewEntity != ENTITY_SENTINEL) ) + { + u.m_pNewPack = (u.m_nNewEntity != ENTITY_SENTINEL) ? framesnapshotmanager->GetPackedEntity( u.m_pToSnapshot, u.m_nNewEntity ) : NULL; + u.m_pOldPack = (u.m_nOldEntity != ENTITY_SENTINEL) ? framesnapshotmanager->GetPackedEntity( u.m_pFromSnapshot, u.m_nOldEntity ) : NULL; + int nEntityStartBit = pBuf.GetNumBitsWritten(); + + // Figure out how we want to write this entity. + SV_DetermineUpdateType( u ); + SV_WriteEntityUpdate( u ); + + if ( !bIsTracing ) + continue; + + switch ( u.m_UpdateType ) + { + default: + case PreserveEnt: + break; + case EnterPVS: + { + char const *eString = sv.edicts[ u.m_pNewPack->m_nEntityIndex ].GetNetworkable()->GetClassName(); + client->TraceNetworkData( pBuf, "enter [%s]", eString ); + ETWMark1I( eString, pBuf.GetNumBitsWritten() - nEntityStartBit ); + } + break; + case LeavePVS: + { + // Note, can't use GetNetworkable() since the edict has been freed at this point + char const *eString = u.m_pOldPack->m_pServerClass->m_pNetworkName; + client->TraceNetworkData( pBuf, "leave [%s]", eString ); + ETWMark1I( eString, pBuf.GetNumBitsWritten() - nEntityStartBit ); + } + break; + case DeltaEnt: + { + char const *eString = sv.edicts[ u.m_pOldPack->m_nEntityIndex ].GetNetworkable()->GetClassName(); + client->TraceNetworkData( pBuf, "delta [%s]", eString ); + ETWMark1I( eString, pBuf.GetNumBitsWritten() - nEntityStartBit ); + } + break; + } + } + + // Now write out the express deletions + int nNumDeletions = SV_WriteDeletions( u ); + if ( bIsTracing ) + { + client->TraceNetworkData( pBuf, "Delta: [%d] deletions", nNumDeletions ); + } + } + + // get number of written bits + int length = u.m_pBuf->GetNumBitsWritten() - startbit; + + // go back to header and fill in correct length now + savepos.WriteUBitLong( u.m_nHeaderCount, MAX_EDICT_BITS ); + savepos.WriteUBitLong( length, DELTASIZE_BITS ); + + bool bUpdateBaseline = ( (client->m_nBaselineUpdateTick == -1) && + (u.m_nFullProps > 0 || !u.m_bAsDelta) ); + + if ( bUpdateBaseline && u.m_pBaseline ) + { + // tell client to use this snapshot as baseline update + savepos.WriteOneBit( 1 ); + client->m_nBaselineUpdateTick = to->tick_count; + } + else + { + savepos.WriteOneBit( 0 ); + } + + if ( bIsTracing ) + { + client->TraceNetworkData( pBuf, "Delta Finish" ); + } +} + + + + + |