You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1044 lines
28 KiB
1044 lines
28 KiB
5 years ago
|
//========= 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" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|