//========= 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();
}