summaryrefslogtreecommitdiff
path: root/engine/sv_ents_write.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/sv_ents_write.cpp
downloadarchived-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.cpp1043
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" );
+ }
+}
+
+
+
+
+