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.
754 lines
22 KiB
754 lines
22 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Parsing of entity network packets. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
|
|
#include "client_pch.h" |
|
#include "con_nprint.h" |
|
#include "iprediction.h" |
|
#include "cl_entityreport.h" |
|
#include "dt_recv_eng.h" |
|
#include "net_synctags.h" |
|
#include "ispatialpartitioninternal.h" |
|
#include "LocalNetworkBackdoor.h" |
|
#include "basehandle.h" |
|
#include "dt_localtransfer.h" |
|
#include "iprediction.h" |
|
#include "netmessages.h" |
|
#include "ents_shared.h" |
|
#include "cl_ents_parse.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
static ConVar cl_flushentitypacket("cl_flushentitypacket", "0", FCVAR_CHEAT, "For debugging. Force the engine to flush an entity packet."); |
|
|
|
// Prints important entity creation/deletion events to console |
|
#if defined( _DEBUG ) |
|
static ConVar cl_deltatrace( "cl_deltatrace", "0", 0, "For debugging, print entity creation/deletion info to console." ); |
|
#define TRACE_DELTA( text ) if ( cl_deltatrace.GetInt() ) { ConMsg( "%s", text ); }; |
|
#else |
|
#define TRACE_DELTA( funcs ) |
|
#endif |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Debug networking stuff. |
|
//----------------------------------------------------------------------------- |
|
|
|
|
|
// #define DEBUG_NETWORKING 1 |
|
|
|
#if defined( DEBUG_NETWORKING ) |
|
void SpewToFile( char const* pFmt, ... ); |
|
static ConVar cl_packettrace( "cl_packettrace", "1", 0, "For debugging, massive spew to file." ); |
|
#define TRACE_PACKET( text ) if ( cl_packettrace.GetInt() ) { SpewToFile text ; }; |
|
#else |
|
#define TRACE_PACKET( text ) |
|
#endif |
|
|
|
|
|
#if defined( DEBUG_NETWORKING ) |
|
|
|
//----------------------------------------------------------------------------- |
|
// Opens the recording file |
|
//----------------------------------------------------------------------------- |
|
|
|
static FileHandle_t OpenRecordingFile() |
|
{ |
|
FileHandle_t fp = 0; |
|
static bool s_CantOpenFile = false; |
|
static bool s_NeverOpened = true; |
|
if (!s_CantOpenFile) |
|
{ |
|
fp = g_pFileSystem->Open( "cltrace.txt", s_NeverOpened ? "wt" : "at" ); |
|
if (!fp) |
|
{ |
|
s_CantOpenFile = true; |
|
} |
|
s_NeverOpened = false; |
|
} |
|
return fp; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Records an argument for a command, flushes when the command is done |
|
//----------------------------------------------------------------------------- |
|
|
|
void SpewToFile( char const* pFmt, ... ) |
|
{ |
|
static CUtlVector<unsigned char> s_RecordingBuffer; |
|
|
|
char temp[2048]; |
|
va_list args; |
|
|
|
va_start( args, pFmt ); |
|
int len = Q_vsnprintf( temp, sizeof( temp ), pFmt, args ); |
|
va_end( args ); |
|
assert( len < 2048 ); |
|
|
|
int idx = s_RecordingBuffer.AddMultipleToTail( len ); |
|
memcpy( &s_RecordingBuffer[idx], temp, len ); |
|
if ( 1 ) //s_RecordingBuffer.Size() > 8192) |
|
{ |
|
FileHandle_t fp = OpenRecordingFile(); |
|
g_pFileSystem->Write( s_RecordingBuffer.Base(), s_RecordingBuffer.Size(), fp ); |
|
g_pFileSystem->Close( fp ); |
|
|
|
s_RecordingBuffer.RemoveAll(); |
|
} |
|
} |
|
|
|
#endif // DEBUG_NETWORKING |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Frees the client DLL's binding to the object. |
|
// Input : iEnt - |
|
//----------------------------------------------------------------------------- |
|
void CL_DeleteDLLEntity( int iEnt, const char *reason, bool bOnRecreatingAllEntities ) |
|
{ |
|
IClientNetworkable *pNet = entitylist->GetClientNetworkable( iEnt ); |
|
|
|
if ( pNet ) |
|
{ |
|
ClientClass *pClientClass = pNet->GetClientClass(); |
|
TRACE_DELTA( va( "Trace %i (%s): delete (%s)\n", iEnt, pClientClass ? pClientClass->m_pNetworkName : "unknown", reason ) ); |
|
#ifndef _XBOX |
|
CL_RecordDeleteEntity( iEnt, pClientClass ); |
|
#endif |
|
if ( bOnRecreatingAllEntities ) |
|
{ |
|
pNet->SetDestroyedOnRecreateEntities(); |
|
} |
|
|
|
pNet->Release(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Has the client DLL allocate its data for the object. |
|
// Input : iEnt - |
|
// iClass - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
IClientNetworkable* CL_CreateDLLEntity( int iEnt, int iClass, int iSerialNum ) |
|
{ |
|
#if defined( _DEBUG ) |
|
IClientNetworkable *pOldNetworkable = entitylist->GetClientNetworkable( iEnt ); |
|
Assert( !pOldNetworkable ); |
|
#endif |
|
|
|
ClientClass *pClientClass; |
|
if ( ( pClientClass = cl.m_pServerClasses[iClass].m_pClientClass ) != NULL ) |
|
{ |
|
TRACE_DELTA( va( "Trace %i (%s): create\n", iEnt, pClientClass->m_pNetworkName ) ); |
|
#ifndef _XBOX |
|
CL_RecordAddEntity( iEnt ); |
|
#endif |
|
|
|
if ( !cl.IsActive() ) |
|
{ |
|
COM_TimestampedLog( "cl: create '%s'", pClientClass->m_pNetworkName ); |
|
} |
|
|
|
// Create the entity. |
|
return pClientClass->m_pCreateFn( iEnt, iSerialNum ); |
|
} |
|
|
|
Assert(false); |
|
return NULL; |
|
} |
|
|
|
void SpewBitStream( unsigned char* pMem, int bit, int lastbit ) |
|
{ |
|
int val = 0; |
|
char buf[1024]; |
|
char* pTemp = buf; |
|
int bitcount = 0; |
|
int charIdx = 1; |
|
while( bit < lastbit ) |
|
{ |
|
int byte = bit >> 3; |
|
int bytebit = bit & 0x7; |
|
|
|
val |= ((pMem[byte] & bytebit) != 0) << bitcount; |
|
|
|
++bit; |
|
++bitcount; |
|
|
|
if (bitcount == 4) |
|
{ |
|
if ((val >= 0) && (val <= 9)) |
|
pTemp[charIdx] = '0' + val; |
|
else |
|
pTemp[charIdx] = 'A' + val - 0xA; |
|
if (charIdx == 1) |
|
charIdx = 0; |
|
else |
|
{ |
|
charIdx = 1; |
|
pTemp += 2; |
|
} |
|
bitcount = 0; |
|
val = 0; |
|
} |
|
} |
|
if ((bitcount != 0) || (charIdx != 0)) |
|
{ |
|
if (bitcount > 0) |
|
{ |
|
if ((val >= 0) && (val <= 9)) |
|
pTemp[charIdx] = '0' + val; |
|
else |
|
pTemp[charIdx] = 'A' + val - 0xA; |
|
} |
|
if (charIdx == 1) |
|
{ |
|
pTemp[0] = '0'; |
|
} |
|
pTemp += 2; |
|
} |
|
pTemp[0] = '\0'; |
|
|
|
TRACE_PACKET(( " CL Bitstream %s\n", buf )); |
|
} |
|
|
|
|
|
inline static void CL_AddPostDataUpdateCall( CEntityReadInfo &u, int iEnt, DataUpdateType_t updateType ) |
|
{ |
|
ErrorIfNot( u.m_nPostDataUpdateCalls < MAX_EDICTS, |
|
("CL_AddPostDataUpdateCall: overflowed u.m_PostDataUpdateCalls") ); |
|
|
|
u.m_PostDataUpdateCalls[u.m_nPostDataUpdateCalls].m_iEnt = iEnt; |
|
u.m_PostDataUpdateCalls[u.m_nPostDataUpdateCalls].m_UpdateType = updateType; |
|
++u.m_nPostDataUpdateCalls; |
|
} |
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the receive table for the specified entity |
|
// Input : *pEnt - |
|
// Output : RecvTable* |
|
//----------------------------------------------------------------------------- |
|
static inline RecvTable* GetEntRecvTable( int entnum ) |
|
{ |
|
IClientNetworkable *pNet = entitylist->GetClientNetworkable( entnum ); |
|
if ( pNet ) |
|
return pNet->GetClientClass()->m_pRecvTable; |
|
else |
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns true if the entity index corresponds to a player slot |
|
// Input : index - |
|
// Output : bool |
|
//----------------------------------------------------------------------------- |
|
static inline bool CL_IsPlayerIndex( int index ) |
|
{ |
|
return ( index >= 1 && index <= cl.m_nMaxClients ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Bad data was received, just flushes incoming delta data. |
|
//----------------------------------------------------------------------------- |
|
void CL_FlushEntityPacket( CClientFrame *packet, char const *errorString, ... ) |
|
{ |
|
con_nprint_t np; |
|
char str[2048]; |
|
va_list marker; |
|
|
|
// Spit out an error. |
|
va_start(marker, errorString); |
|
Q_vsnprintf(str, sizeof(str), errorString, marker); |
|
va_end(marker); |
|
|
|
ConMsg("%s", str); |
|
|
|
np.fixed_width_font = false; |
|
np.time_to_live = 1.0; |
|
np.index = 0; |
|
np.color[ 0 ] = 1.0; |
|
np.color[ 1 ] = 0.2; |
|
np.color[ 2 ] = 0.0; |
|
Con_NXPrintf( &np, "WARNING: CL_FlushEntityPacket, %s", str ); |
|
|
|
// Free packet memory. |
|
delete packet; |
|
} |
|
|
|
|
|
// ----------------------------------------------------------------------------- // |
|
// Regular handles for ReadPacketEntities. |
|
// ----------------------------------------------------------------------------- // |
|
|
|
void CL_CopyNewEntity( |
|
CEntityReadInfo &u, |
|
int iClass, |
|
int iSerialNum |
|
) |
|
{ |
|
if ( u.m_nNewEntity < 0 || u.m_nNewEntity >= MAX_EDICTS ) |
|
{ |
|
Host_Error ("CL_CopyNewEntity: u.m_nNewEntity < 0 || m_nNewEntity >= MAX_EDICTS"); |
|
return; |
|
} |
|
|
|
// If it's new, make sure we have a slot for it. |
|
IClientNetworkable *ent = entitylist->GetClientNetworkable( u.m_nNewEntity ); |
|
|
|
if( iClass >= cl.m_nServerClasses ) |
|
{ |
|
Host_Error("CL_CopyNewEntity: invalid class index (%d).\n", iClass); |
|
return; |
|
} |
|
|
|
// Delete the entity. |
|
ClientClass *pClass = cl.m_pServerClasses[iClass].m_pClientClass; |
|
bool bNew = false; |
|
if ( ent ) |
|
{ |
|
// if serial number is different, destory old entity |
|
if ( ent->GetIClientUnknown()->GetRefEHandle().GetSerialNumber() != iSerialNum ) |
|
{ |
|
CL_DeleteDLLEntity( u.m_nNewEntity, "CopyNewEntity" ); |
|
ent = NULL; // force a recreate |
|
} |
|
} |
|
|
|
if ( !ent ) |
|
{ |
|
// Ok, it doesn't exist yet, therefore this is not an "entered PVS" message. |
|
ent = CL_CreateDLLEntity( u.m_nNewEntity, iClass, iSerialNum ); |
|
if( !ent ) |
|
{ |
|
const char *pNetworkName = cl.m_pServerClasses[iClass].m_pClientClass ? cl.m_pServerClasses[iClass].m_pClientClass->m_pNetworkName : ""; |
|
Host_Error( "CL_ParsePacketEntities: Error creating entity %s(%i)\n", pNetworkName, u.m_nNewEntity ); |
|
return; |
|
} |
|
|
|
bNew = true; |
|
} |
|
|
|
int start_bit = u.m_pBuf->GetNumBitsRead(); |
|
|
|
DataUpdateType_t updateType = bNew ? DATA_UPDATE_CREATED : DATA_UPDATE_DATATABLE_CHANGED; |
|
ent->PreDataUpdate( updateType ); |
|
|
|
// Get either the static or instance baseline. |
|
const void *pFromData; |
|
int nFromBits; |
|
|
|
PackedEntity *baseline = u.m_bAsDelta ? cl.GetEntityBaseline( u.m_nBaseline, u.m_nNewEntity ) : NULL; |
|
if ( baseline && baseline->m_pClientClass == pClass ) |
|
{ |
|
Assert( !baseline->IsCompressed() ); |
|
pFromData = baseline->GetData(); |
|
nFromBits = baseline->GetNumBits(); |
|
} |
|
else |
|
{ |
|
// Every entity must have a static or an instance baseline when we get here. |
|
ErrorIfNot( |
|
cl.GetClassBaseline( iClass, &pFromData, &nFromBits ), |
|
("CL_CopyNewEntity: GetClassBaseline(%d) failed.", iClass) |
|
); |
|
|
|
nFromBits *= 8; // convert to bits |
|
} |
|
|
|
// Delta from baseline and merge to new baseline |
|
bf_read fromBuf( "CL_CopyNewEntity->fromBuf", pFromData, Bits2Bytes(nFromBits), nFromBits ); |
|
|
|
RecvTable *pRecvTable = GetEntRecvTable( u.m_nNewEntity ); |
|
|
|
if( !pRecvTable ) |
|
Host_Error( "CL_ParseDelta: invalid recv table for ent %d.\n", u.m_nNewEntity ); |
|
|
|
if ( u.m_bUpdateBaselines ) |
|
{ |
|
// store this baseline in u.m_pUpdateBaselines |
|
ALIGN4 char packedData[MAX_PACKEDENTITY_DATA] ALIGN4_POST; |
|
bf_write writeBuf( "CL_CopyNewEntity->newBuf", packedData, sizeof(packedData) ); |
|
|
|
RecvTable_MergeDeltas( pRecvTable, &fromBuf, u.m_pBuf, &writeBuf, -1, NULL, true ); |
|
|
|
// set the other baseline |
|
cl.SetEntityBaseline( (u.m_nBaseline==0)?1:0, pClass, u.m_nNewEntity, packedData, writeBuf.GetNumBytesWritten() ); |
|
|
|
fromBuf.StartReading( packedData, writeBuf.GetNumBytesWritten() ); |
|
|
|
RecvTable_Decode( pRecvTable, ent->GetDataTableBasePtr(), &fromBuf, u.m_nNewEntity, false ); |
|
|
|
} |
|
else |
|
{ |
|
// write data from baseline into entity |
|
RecvTable_Decode( pRecvTable, ent->GetDataTableBasePtr(), &fromBuf, u.m_nNewEntity, false ); |
|
|
|
// Now parse in the contents of the network stream. |
|
RecvTable_Decode( pRecvTable, ent->GetDataTableBasePtr(), u.m_pBuf, u.m_nNewEntity, true ); |
|
} |
|
|
|
CL_AddPostDataUpdateCall( u, u.m_nNewEntity, updateType ); |
|
|
|
// If ent doesn't think it's in PVS, signal that it is |
|
Assert( u.m_pTo->last_entity <= u.m_nNewEntity ); |
|
u.m_pTo->last_entity = u.m_nNewEntity; |
|
Assert( !u.m_pTo->transmit_entity.Get(u.m_nNewEntity) ); |
|
u.m_pTo->transmit_entity.Set( u.m_nNewEntity ); |
|
|
|
// |
|
// Net stats.. |
|
// |
|
int bit_count = u.m_pBuf->GetNumBitsRead() - start_bit; |
|
#ifndef _XBOX |
|
if ( cl_entityreport.GetBool() ) |
|
CL_RecordEntityBits( u.m_nNewEntity, bit_count ); |
|
#endif |
|
if ( CL_IsPlayerIndex( u.m_nNewEntity ) ) |
|
{ |
|
if ( u.m_nNewEntity == cl.m_nPlayerSlot + 1 ) |
|
{ |
|
u.m_nLocalPlayerBits += bit_count; |
|
} |
|
else |
|
{ |
|
u.m_nOtherPlayerBits += bit_count; |
|
} |
|
} |
|
} |
|
|
|
void CL_PreserveExistingEntity( int nOldEntity ) |
|
{ |
|
IClientNetworkable *pEnt = entitylist->GetClientNetworkable( nOldEntity ); |
|
if ( !pEnt ) |
|
{ |
|
// If you hit this, this is because there's a networked client entity that got released |
|
// by some method other than a server update. This can happen if client code calls |
|
// release on a networked entity. |
|
|
|
#if defined( STAGING_ONLY ) |
|
// Try to use the cl_removeentity_backtrace_capture code in cliententitylist.cpp... |
|
Msg( "%s: missing client entity %d.\n", __FUNCTION__, nOldEntity ); |
|
Cbuf_AddText( CFmtStr( "cl_removeentity_backtrace_dump %d\n", nOldEntity ) ); |
|
Cbuf_Execute(); |
|
#endif // STAGING_ONLY |
|
|
|
Host_Error( "CL_PreserveExistingEntity: missing client entity %d.\n", nOldEntity ); |
|
return; |
|
} |
|
|
|
pEnt->OnDataUnchangedInPVS(); |
|
} |
|
|
|
void CL_CopyExistingEntity( CEntityReadInfo &u ) |
|
{ |
|
int start_bit = u.m_pBuf->GetNumBitsRead(); |
|
|
|
IClientNetworkable *pEnt = entitylist->GetClientNetworkable( u.m_nNewEntity ); |
|
if ( !pEnt ) |
|
{ |
|
Host_Error( "CL_CopyExistingEntity: missing client entity %d.\n", u.m_nNewEntity ); |
|
return; |
|
} |
|
|
|
Assert( u.m_pFrom->transmit_entity.Get(u.m_nNewEntity) ); |
|
|
|
// Read raw data from the network stream |
|
pEnt->PreDataUpdate( DATA_UPDATE_DATATABLE_CHANGED ); |
|
|
|
RecvTable *pRecvTable = GetEntRecvTable( u.m_nNewEntity ); |
|
|
|
if( !pRecvTable ) |
|
{ |
|
Host_Error( "CL_ParseDelta: invalid recv table for ent %d.\n", u.m_nNewEntity ); |
|
return; |
|
} |
|
|
|
RecvTable_Decode( pRecvTable, pEnt->GetDataTableBasePtr(), u.m_pBuf, u.m_nNewEntity ); |
|
|
|
CL_AddPostDataUpdateCall( u, u.m_nNewEntity, DATA_UPDATE_DATATABLE_CHANGED ); |
|
|
|
u.m_pTo->last_entity = u.m_nNewEntity; |
|
Assert( !u.m_pTo->transmit_entity.Get(u.m_nNewEntity) ); |
|
u.m_pTo->transmit_entity.Set( u.m_nNewEntity ); |
|
|
|
int bit_count = u.m_pBuf->GetNumBitsRead() - start_bit; |
|
#ifndef _XBOX |
|
if ( cl_entityreport.GetBool() ) |
|
CL_RecordEntityBits( u.m_nNewEntity, bit_count ); |
|
#endif |
|
if ( CL_IsPlayerIndex( u.m_nNewEntity ) ) |
|
{ |
|
if ( u.m_nNewEntity == cl.m_nPlayerSlot + 1 ) |
|
{ |
|
u.m_nLocalPlayerBits += bit_count; |
|
} |
|
else |
|
{ |
|
u.m_nOtherPlayerBits += bit_count; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CL_MarkEntitiesOutOfPVS( CBitVec<MAX_EDICTS> *pvs_flags ) |
|
{ |
|
int highest_index = entitylist->GetHighestEntityIndex(); |
|
// Note that we go up to and including the highest_index |
|
for ( int i = 0; i <= highest_index; i++ ) |
|
{ |
|
IClientNetworkable *ent = entitylist->GetClientNetworkable( i ); |
|
if ( !ent ) |
|
continue; |
|
|
|
// FIXME: We can remove IClientEntity here if we keep track of the |
|
// last frame's entity_in_pvs |
|
bool curstate = !ent->IsDormant(); |
|
bool newstate = pvs_flags->Get( i ) ? true : false; |
|
|
|
if ( !curstate && newstate ) |
|
{ |
|
// Inform the client entity list that the entity entered the PVS |
|
ent->NotifyShouldTransmit( SHOULDTRANSMIT_START ); |
|
} |
|
else if ( curstate && !newstate ) |
|
{ |
|
// Inform the client entity list that the entity left the PVS |
|
ent->NotifyShouldTransmit( SHOULDTRANSMIT_END ); |
|
#ifndef _XBOX |
|
CL_RecordLeavePVS( i ); |
|
#endif |
|
} |
|
} |
|
} |
|
|
|
static void CL_CallPostDataUpdates( CEntityReadInfo &u ) |
|
{ |
|
for ( int i=0; i < u.m_nPostDataUpdateCalls; i++ ) |
|
{ |
|
MDLCACHE_CRITICAL_SECTION_(g_pMDLCache); |
|
CPostDataUpdateCall *pCall = &u.m_PostDataUpdateCalls[i]; |
|
|
|
IClientNetworkable *pEnt = entitylist->GetClientNetworkable( pCall->m_iEnt ); |
|
ErrorIfNot( pEnt, |
|
("CL_CallPostDataUpdates: missing ent %d", pCall->m_iEnt) ); |
|
|
|
pEnt->PostDataUpdate( pCall->m_UpdateType ); |
|
} |
|
} |
|
|
|
static float g_flLastPerfRequest = 0.0f; |
|
|
|
static ConVar cl_debug_player_perf( "cl_debug_player_perf", "0", 0 ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: An svc_packetentities has just been parsed, deal with the |
|
// rest of the data stream. This can be a delta from the baseline or from a previous |
|
// client frame for this client. |
|
// Input : delta - |
|
// *playerbits - |
|
// Output : void CL_ParsePacketEntities |
|
//----------------------------------------------------------------------------- |
|
bool CL_ProcessPacketEntities ( SVC_PacketEntities *entmsg ) |
|
{ |
|
VPROF( "_CL_ParsePacketEntities" ); |
|
|
|
// Packed entities for that frame |
|
// Allocate space for new packet info. |
|
CClientFrame *newFrame = cl.AllocateFrame(); |
|
newFrame->Init( cl.GetServerTickCount() ); |
|
CClientFrame *oldFrame = NULL; |
|
|
|
// if cl_flushentitypacket is set to N, the next N entity updates will be flushed |
|
if ( cl_flushentitypacket.GetInt() ) |
|
{ |
|
// we can't use this, it is too old |
|
CL_FlushEntityPacket( newFrame, "Forced by cvar\n" ); |
|
cl_flushentitypacket.SetValue( cl_flushentitypacket.GetInt() - 1 ); // Reduce the cvar. |
|
return false; |
|
} |
|
|
|
if ( entmsg->m_bIsDelta ) |
|
{ |
|
int nDeltaTicks = cl.GetServerTickCount() - entmsg->m_nDeltaFrom; |
|
float flDeltaSeconds = TICKS_TO_TIME( nDeltaTicks ); |
|
|
|
// If we have cl_debug_player_perf set and we see a huge delta between what we've ack'd to the server and where it's at |
|
// ask it for an instantaneous perf snapshot |
|
if ( cl_debug_player_perf.GetBool() && |
|
( flDeltaSeconds > 0.5f ) && // delta is pretty out of date |
|
( ( realtime - g_flLastPerfRequest ) > 5.0f ) ) // haven't requested in a while |
|
{ |
|
g_flLastPerfRequest = realtime; |
|
Warning( "Gap in server data, requesting connection perf data\n" ); |
|
cl.SendStringCmd( "playerperf\n" ); |
|
} |
|
|
|
if ( cl.GetServerTickCount() == entmsg->m_nDeltaFrom ) |
|
{ |
|
Host_Error( "Update self-referencing, connection dropped.\n" ); |
|
return false; |
|
} |
|
|
|
// Otherwise, mark where we are valid to and point to the packet entities we'll be updating from. |
|
oldFrame = cl.GetClientFrame( entmsg->m_nDeltaFrom ); |
|
|
|
if ( !oldFrame ) |
|
{ |
|
CL_FlushEntityPacket( newFrame, "Update delta not found.\n" ); |
|
return false; |
|
} |
|
} |
|
else |
|
{ |
|
if ( cl_debug_player_perf.GetBool() ) |
|
{ |
|
Warning( "Received uncompressed server update\n" ); |
|
} |
|
|
|
// Clear out the client's entity states.. |
|
for ( int i=0; i <= entitylist->GetHighestEntityIndex(); i++ ) |
|
{ |
|
CL_DeleteDLLEntity( i, "ProcessPacketEntities", true ); |
|
} |
|
} |
|
|
|
// signal client DLL that we have started updating entities |
|
ClientDLL_FrameStageNotify( FRAME_NET_UPDATE_START ); |
|
|
|
g_nPropsDecoded = 0; |
|
|
|
Assert( entmsg->m_nBaseline >= 0 && entmsg->m_nBaseline < 2 ); |
|
|
|
if ( entmsg->m_bUpdateBaseline ) |
|
{ |
|
// server requested to use this snapshot as baseline update |
|
int nUpdateBaseline = (entmsg->m_nBaseline == 0) ? 1 : 0; |
|
cl.CopyEntityBaseline( entmsg->m_nBaseline, nUpdateBaseline ); |
|
|
|
// send new baseline acknowledgement(as reliable) |
|
cl.m_NetChannel->SendNetMsg( CLC_BaselineAck( cl.GetServerTickCount(), entmsg->m_nBaseline ), true ); |
|
|
|
} |
|
|
|
CEntityReadInfo u; |
|
u.m_pBuf = &entmsg->m_DataIn; |
|
u.m_pFrom = oldFrame; |
|
u.m_pTo = newFrame; |
|
u.m_bAsDelta = entmsg->m_bIsDelta; |
|
u.m_nHeaderCount = entmsg->m_nUpdatedEntries; |
|
u.m_nBaseline = entmsg->m_nBaseline; |
|
u.m_bUpdateBaselines = entmsg->m_bUpdateBaseline; |
|
|
|
// update the entities |
|
cl.ReadPacketEntities( u ); |
|
|
|
ClientDLL_FrameStageNotify( FRAME_NET_UPDATE_POSTDATAUPDATE_START ); |
|
|
|
// call PostDataUpdate() for each entity |
|
CL_CallPostDataUpdates( u ); |
|
|
|
ClientDLL_FrameStageNotify( FRAME_NET_UPDATE_POSTDATAUPDATE_END ); |
|
|
|
// call NotifyShouldTransmit() for entities that entered or left the PVS |
|
CL_MarkEntitiesOutOfPVS( &newFrame->transmit_entity ); |
|
|
|
// adjust net channel stats |
|
|
|
cl.m_NetChannel->UpdateMessageStats( INetChannelInfo::LOCALPLAYER, u.m_nLocalPlayerBits ); |
|
cl.m_NetChannel->UpdateMessageStats( INetChannelInfo::OTHERPLAYERS, u.m_nOtherPlayerBits ); |
|
cl.m_NetChannel->UpdateMessageStats( INetChannelInfo::ENTITIES, -(u.m_nLocalPlayerBits+u.m_nOtherPlayerBits) ); |
|
|
|
cl.DeleteClientFrames( entmsg->m_nDeltaFrom ); |
|
|
|
// If the client has more than 64 frames, the host will start to eat too much memory. |
|
// TODO: We should enforce this somehow. |
|
if ( MAX_CLIENT_FRAMES < cl.AddClientFrame( newFrame ) ) |
|
{ |
|
DevMsg( 1, "CL_ProcessPacketEntities: frame window too big (>%i)\n", MAX_CLIENT_FRAMES ); |
|
} |
|
|
|
// all update activities are finished |
|
ClientDLL_FrameStageNotify( FRAME_NET_UPDATE_END ); |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
================== |
|
CL_PreprocessEntities |
|
|
|
Server information pertaining to this client only |
|
================== |
|
*/ |
|
namespace CDebugOverlay |
|
{ |
|
extern void PurgeServerOverlays( void ); |
|
} |
|
|
|
void CL_PreprocessEntities( void ) |
|
{ |
|
// Zero latency!!! (single player or listen server?) |
|
bool bIsUsingMultiplayerNetworking = NET_IsMultiplayer(); |
|
bool bLastOutgoingCommandEqualsLastAcknowledgedCommand = cl.lastoutgoingcommand == cl.command_ack; |
|
|
|
// We always want to re-run prediction when using the multiplayer networking, or if we're the listen server and we get a packet |
|
// before any frames have run |
|
if ( bIsUsingMultiplayerNetworking || |
|
bLastOutgoingCommandEqualsLastAcknowledgedCommand ) |
|
{ |
|
//Msg( "%i/%i CL_ParseClientdata: no latency server ack %i\n", |
|
// host_framecount, cl.tickcount, |
|
// command_ack ); |
|
CL_RunPrediction( PREDICTION_SIMULATION_RESULTS_ARRIVING_ON_SEND_FRAME ); |
|
} |
|
|
|
// Copy some results from prediction back into right spot |
|
// Anything not sent over the network from server to client must be specified here. |
|
//if ( cl.last_command_ack ) |
|
{ |
|
int number_of_commands_executed = ( cl.command_ack - cl.last_command_ack ); |
|
|
|
#if 0 |
|
COM_Log( "cl.log", "Receiving frame acknowledging %i commands\n", |
|
number_of_commands_executed ); |
|
|
|
COM_Log( "cl.log", " last command number executed %i\n", |
|
cl.command_ack ); |
|
|
|
COM_Log( "cl.log", " previous last command number executed %i\n", |
|
cl.last_command_ack ); |
|
|
|
COM_Log( "cl.log", " current world frame %i\n", |
|
cl.m_nCurrentSequence ); |
|
#endif |
|
|
|
// Copy last set of changes right into current frame. |
|
g_pClientSidePrediction->PreEntityPacketReceived( number_of_commands_executed, cl.m_nCurrentSequence ); |
|
} |
|
|
|
CDebugOverlay::PurgeServerOverlays(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|