//========= Copyright  1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//


#include "server_pch.h"
#include "client.h"
#include "sv_packedentities.h"
#include "bspfile.h"
#include "eiface.h"
#include "dt_send_eng.h"
#include "dt_common_eng.h"
#include "changeframelist.h"
#include "sv_main.h"
#include "hltvserver.h"
#if defined( REPLAY_ENABLED )
#include "replayserver.h"
#endif
#include "dt_instrumentation_server.h"
#include "LocalNetworkBackdoor.h"
#include "tier0/vprof.h"
#include "host.h"
#include "networkstringtableserver.h"
#include "networkstringtable.h"
#include "utlbuffer.h"
#include "dt.h"
#include "con_nprint.h"
#include "smooth_average.h"
#include "vengineserver_impl.h"
#include "vstdlib/jobthread.h"
#include "enginethreads.h"
#include "networkvar.h"

#include "serializedentity.h"


#ifdef DEDICATED
IClientEntityList *entitylist = NULL;
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

ConVar sv_validate_edict_change_infos( "sv_validate_edict_change_infos", "0", FCVAR_RELEASE, "Verify that edict changeinfos are being calculated properly (used to debug local network backdoor mode)." );
ConVar sv_enable_delta_packing( "sv_enable_delta_packing", "0", FCVAR_RELEASE, "When enabled, this allows for entity packing to use the property changes for building up the data. This is many times faster, but can be disabled for error checking." );
ConVar sv_debugmanualmode( "sv_debugmanualmode", "0", FCVAR_RELEASE, "Make sure entities correctly report whether or not their network data has changed." );

static struct SPackedEntityStats
{
	uint32 m_numFastPathEncodes;
	uint32 m_numSlowPathEncodes;
} g_PackedEntityStats;

// Returns false and calls Host_Error if the edict's pvPrivateData is NULL.
static inline bool SV_EnsurePrivateData(edict_t *pEdict)
{
	if(pEdict->GetUnknown())
	{
		return true;
	}
	else
	{
		Host_Error("SV_EnsurePrivateData: pEdict->pvPrivateData==NULL (ent %d).\n", pEdict - sv.edicts);
		return false;
	}
}

// This function makes sure that this entity class has an instance baseline.
// If it doesn't have one yet, it makes a new one.
void SV_EnsureInstanceBaseline( ServerClass *pServerClass, int iEdict, SerializedEntityHandle_t handle )
{
	edict_t *pEnt = &sv.edicts[iEdict];
	ErrorIfNot( pEnt->GetNetworkable(),
		("SV_EnsureInstanceBaseline: edict %d missing ent", iEdict)
	);

	ServerClass *pClass = pEnt->GetNetworkable()->GetServerClass();

	// See if we already have a baseline for this class.
	if ( pClass->m_InstanceBaselineIndex == INVALID_STRING_INDEX )
	{
		AUTO_LOCK_FM( g_svInstanceBaselineMutex );

		// We need this second check in case multiple instances of the same class have grabbed the lock.
		if ( pClass->m_InstanceBaselineIndex == INVALID_STRING_INDEX )
		{
			char packedData[ MAX_PACKEDENTITY_DATA ];
			bf_write buf( "SV_EnsureInstanceBaseline", packedData, sizeof( packedData ) );

			// Write all fields (NULL) change list
			SendTable_WritePropList( pServerClass->m_pTable, handle, &buf, iEdict, NULL );

			char idString[32];
			Q_snprintf( idString, sizeof( idString ), "%d", pClass->m_ClassID );

			// Ok, make a new instance baseline so they can reference it.
			int temp = sv.GetInstanceBaselineTable()->AddString( 
				true, idString,	// Note we're sending a string with the ID number, not the class name.
				buf.GetNumBytesWritten(), packedData );
			Assert( temp != INVALID_STRING_INDEX );
			// Copy the baseline data into the handles table
			sv.m_BaselineHandles.Insert( temp, g_pSerializedEntities->CopySerializedEntity( handle, __FILE__, __LINE__ ) );
			// Insert a compiler and/or CPU memory barrier to ensure that all side-effects have
			// been published before the index is published. Otherwise the string index may
			// be visible before its initialization has finished. This potential problem is caused
			// by the use of double-checked locking -- the problem is that the code outside of the
			// lock is looking at the variable that is protected by the lock. See this article for details:
			// http://en.wikipedia.org/wiki/Double-checked_locking
			// Write-release barrier
			ThreadMemoryBarrier();
			pClass->m_InstanceBaselineIndex = temp;
		}
	}
	// Read-acquire barrier. This should be safe to omit because of dependencies
	// which enforce read ordering.
	//ThreadMemoryBarrier();
}


static inline bool DoesEdictChangeInfoContainPropIndex( SendTable *pSendTable, const CEdictChangeInfo *pCI, int nProp )
{
	CSendTablePrecalc *pPrecalc = pSendTable->m_pPrecalc;

	bool bFound = false;
	for ( int i=0; i < pCI->m_nChangeOffsets && !bFound; i++ )
	{
		unsigned short index = pPrecalc->m_PropOffsetToIndexMap.Find( pCI->m_ChangeOffsets[i] );
		if ( index == pPrecalc->m_PropOffsetToIndexMap.InvalidIndex() )
			continue;

		const PropIndicesCollection_t &coll = pPrecalc->m_PropOffsetToIndexMap[index];
		for ( int nIndex=0; nIndex < ARRAYSIZE( coll.m_Indices ); nIndex++ )
		{
			if ( coll.m_Indices[nIndex] == nProp )
			{
				bFound = true;
				break;
			}
		}
	}

	return bFound;
}

//given a previous frame and a change list of the delta properties, this will return a change list (either by taking it from the last frame if possible, or creating
//a copy) with the specified delta properties with an updated tick count
static inline CChangeFrameList * GetMergedChangeFrameList( PackedEntity* pPrevFrame, uint32 nNumProps, const CalcDeltaResultsList_t& ChangeList, uint32 nTick )
{
	//if we have a previous frame, we need to try and reuse it's change list, either as a copy or a baseline
	CChangeFrameList *pChangeFrame = NULL;

	if( pPrevFrame )
	{
		CActiveHltvServerIterator hltv;

#ifndef _XBOX	
#if defined( REPLAY_ENABLED )
		if ( hltv || (replay && replay->IsActive()) )
#else
		if ( hltv )
#endif	
		{
			// in HLTV or Replay mode every PackedEntity keeps it's own ChangeFrameList
			// we just copy the ChangeFrameList from prev frame and update it
			pChangeFrame = pPrevFrame->GetChangeFrameList()->Copy();
		}
		else
#endif
		{
			// Ok, now snag the changeframe from the previous frame and update the 'last frame changed'
			// for the properties in the delta.
			pChangeFrame = pPrevFrame->SnagChangeFrameList();
		}

		ErrorIfNot( pChangeFrame, ("SV_PackEntity: SnagChangeFrameList returned null") );
		ErrorIfNot( pChangeFrame->GetNumProps() == nNumProps,
			("SV_PackEntity: SnagChangeFrameList mismatched number of props[%d vs %d]", nNumProps, pChangeFrame->GetNumProps() ) );

		pChangeFrame->SetChangeTick( ChangeList.Base(), ChangeList.Count(), nTick );
	}
	else
	{
		pChangeFrame = new CChangeFrameList( nNumProps, nTick );
	}

	return pChangeFrame;
}


static void ValidateIncrementalChanges( ServerClass* pServerClass, const CalcDeltaResultsList_t& incremental, edict_t* edict, int edictIdx, SerializedEntityHandle_t tPrevProps )
{
	const SendTable *pSendTable = pServerClass->m_pTable;

	unsigned char tempData[ sizeof( CSendProxyRecipients ) * MAX_DATATABLE_PROXIES ];
	CUtlMemory< CSendProxyRecipients > recip( (CSendProxyRecipients*)tempData, pSendTable->m_pPrecalc->GetNumDataTableProxies() );

	//debug path, useful for detecting properties that differ from the binary diff and the change list
	SerializedEntityHandle_t comparePackedProps = g_pSerializedEntities->AllocateSerializedEntity(__FILE__, __LINE__);
	CalcDeltaResultsList_t compareDeltaProps;

	//generate a change list using the normal route
	SendTable_Encode( pSendTable, comparePackedProps, edict->GetUnknown(), edictIdx, &recip );
	SendTable_CalcDelta( pSendTable, tPrevProps, comparePackedProps, edictIdx, compareDeltaProps );

	//check to see if we have any missing properties or differing properties
	for( uint32 nCurrIncremental = 0; nCurrIncremental < incremental.Count(); ++nCurrIncremental )
	{
		int nPropIndex = incremental[ nCurrIncremental ];
		if( compareDeltaProps.Find( nPropIndex ) == -1 )
		{
			//missing a prop from our diff			
			const SendProp* pProp = pSendTable->m_pPrecalc->GetProp( nPropIndex );
			Msg( "SV_PackEntity: Encountered property %s:%s for class %s (idx:%d) in incremental change that was not in the diff change. This can lead to slight network inefficiency (Index: %d, Array: %s)\n", 
					pSendTable->GetName(), pProp->GetName(), pServerClass->GetName(), edictIdx, nPropIndex, (pProp->m_pParentArrayPropName ? pProp->m_pParentArrayPropName : "N/A") );
		}
	}

	for( uint32 nCurrDiff = 0; nCurrDiff < compareDeltaProps.Count(); ++nCurrDiff )
	{
		int nPropIndex = compareDeltaProps[ nCurrDiff ];
		if( incremental.Find( nPropIndex ) == -1 )
		{
			//missing a prop from our incremental
			const SendProp* pProp = pSendTable->m_pPrecalc->GetProp( nPropIndex );

			//some properties are encoded against the tick count, of which can change with out the property changing. However, the value on the receiving end stays constants, so we need to
			//be able to filter these out
			if( pProp->GetFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT )
				continue;

			Msg( "SV_PackEntity: Encountered property %s:%s for class %s (idx:%d) in diff change that was not in the incremental change. This can lead to values not making it down to the client (Index: %d, Array: %s)\n", 
					pSendTable->GetName(), pProp->GetName(), pServerClass->GetName(), edictIdx, nPropIndex, (pProp->m_pParentArrayPropName ? pProp->m_pParentArrayPropName : "N/A") );
		}
	}

	g_pSerializedEntities->ReleaseSerializedEntity( comparePackedProps );
}


//-----------------------------------------------------------------------------
// Pack the entity....
//-----------------------------------------------------------------------------

static inline void SV_PackEntity( 
	int edictIdx, 
	edict_t* edict, 
	ServerClass* pServerClass,
	CFrameSnapshot *pSnapshot )
{
	Assert( edictIdx < pSnapshot->m_nNumEntities );

	MEM_ALLOC_CREDIT();

	const int iSerialNum = pSnapshot->m_pEntities[ edictIdx ].m_nSerialNumber;

	//if the entity has no state changes, we can just reuse the previously sent packet
	if( !edict->HasStateChanged() )
	{
		// Now this may not work if we didn't previously send a packet;
		// if not, then we gotta compute it
		bool bUsedPrev = framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum );
		if ( bUsedPrev && !sv_debugmanualmode.GetInt() )
		{
			edict->ClearStateChanged();
			return;
		}
	}

	//try and get our last frame
	PackedEntity *pPrevFrame = framesnapshotmanager->GetPreviouslySentPacket( edictIdx, iSerialNum );
	const SendTable *pSendTable = pServerClass->m_pTable;
	
	//memory to hold onto our recipient list on the stack to avoid allocator overhead.
	unsigned char tempData[ sizeof( CSendProxyRecipients ) * MAX_DATATABLE_PROXIES ];
	CUtlMemory< CSendProxyRecipients > recip( (CSendProxyRecipients*)tempData, pSendTable->m_pPrecalc->GetNumDataTableProxies() );
	
	//the new packed up properties and the delta list from the last frame we will be building
	SerializedEntityHandle_t newPackedProps = SERIALIZED_ENTITY_HANDLE_INVALID;
	CalcDeltaResultsList_t deltaProps;

	//--------------------------------------
	// Fast path - Partial updating (catches about 95% of entities)
	//--------------------------------------

	//see if we can take the fast path (requires a previous frame)
	bool bCanFastPath = ( pPrevFrame && ( pPrevFrame->GetPackedData() != SERIALIZED_ENTITY_HANDLE_INVALID ) );
	//also, we can't fast path if the entire object has changed
	bCanFastPath &= !( edict->m_fStateFlags & FL_FULL_EDICT_CHANGED );
	//and we need a valid change list set
	bCanFastPath &= ( edict->GetChangeInfoSerialNumber() == g_pSharedChangeInfo->m_iSerialNumber );
	//and allow for disabling this through the console
	bCanFastPath &= ( sv_enable_delta_packing.GetBool() || sv_validate_edict_change_infos.GetBool() );

	//see if we can do an incremental update from our last frame
	if( bCanFastPath )
	{
		//see if we are in a situation where we are just doing diagnostics, meaning that we want to go through the work, but ultimately throw the results away after validating them
		bool bDiscardResults = !( sv_enable_delta_packing.GetBool() );

		//we can attempt a partial update
		bool bCanReuseOldData;
		newPackedProps = SendTable_BuildDeltaProperties( edict, edictIdx, pSendTable, pPrevFrame->GetPackedData(), deltaProps, &recip, bCanReuseOldData );
		
		//see if we were unable to merge (which may have been a result of us being able to reuse the original data)
		if( ( newPackedProps == SERIALIZED_ENTITY_HANDLE_INVALID ) )
		{
			//it wasn't able to overlay, so we need to fall back to the old way, unless it says nothing changed, at which point we can just reuse
			if( bCanReuseOldData && pPrevFrame->CompareRecipients( recip ) )
			{
				if( framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ) )
				{
					//we are going to use the previous, but make sure that there is no delta that we are missing
					if( sv_validate_edict_change_infos.GetBool() )
						ValidateIncrementalChanges( pServerClass, deltaProps, edict, edictIdx, pPrevFrame->GetPackedData() );	

					//successfully used the last one
					edict->ClearStateChanged();
					return;
				}
			}
		}
		else 
		{
			++g_PackedEntityStats.m_numFastPathEncodes;

			if( sv_validate_edict_change_infos.GetBool() )
			{
				ValidateIncrementalChanges( pServerClass, deltaProps, edict, edictIdx, pPrevFrame->GetPackedData() );	
				//we need to throw away our data if this is just for diagnostics
				if( bDiscardResults )
				{
					g_pSerializedEntities->ReleaseSerializedEntity( newPackedProps );
					newPackedProps = SERIALIZED_ENTITY_HANDLE_INVALID;
				}
			}		
		}
	}

	//--------------------------------------
	// Slow path - Must build a full data block and do a binary diff to spot the changes
	//--------------------------------------

	//if we made it through the fast path with no packed data, we need to pack it via the slow path
	if( newPackedProps == SERIALIZED_ENTITY_HANDLE_INVALID )
	{	
		++g_PackedEntityStats.m_numSlowPathEncodes;

		//build all the properties for this object into a serialized entity data block
		newPackedProps = g_pSerializedEntities->AllocateSerializedEntity(__FILE__, __LINE__);
		if( !SendTable_Encode( pSendTable, newPackedProps, edict->GetUnknown(), edictIdx, &recip ) )
		{							 
			Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", edictIdx );
		}

		// A "SetOnly", etc. for indicating the entity should be sent to player B who is a piggybacked split screen player sharing the network pipe for for player A, needs to be changed to go to player player A
		SV_EnsureInstanceBaseline( pServerClass, edictIdx, newPackedProps );
		
		// If this entity was previously in there, then it should have a valid CChangeFrameList 
		// which we can delta against to figure out which properties have changed.
		if( pPrevFrame )
		{
			//do a binary diff and see which properties changed that way
			SendTable_CalcDelta( pSendTable, pPrevFrame->GetPackedData(), newPackedProps, edictIdx, deltaProps );

			// If it's non-manual-mode, but we detect that there are no changes here, then just
			// use the previous pSnapshot if it's available (as though the entity were manual mode).
			if ( ( deltaProps.Count() == 0 ) && pPrevFrame->CompareRecipients( recip ) )
			{
				if ( framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ) )
				{
					edict->ClearStateChanged();
					g_pSerializedEntities->ReleaseSerializedEntity( newPackedProps );
					return;
				}
			}		
		}
	}

	// Now make a PackedEntity and store the new packed data in there.
	{
		//get our change frame list for our new property set
		int nFlatProps = SendTable_GetNumFlatProps( pSendTable );
		CChangeFrameList* pChangeFrame = GetMergedChangeFrameList( pPrevFrame, nFlatProps, deltaProps, pSnapshot->m_nTickCount );

		//and setup our packed entity to hold all of our data
		PackedEntity *pPackedEntity = framesnapshotmanager->CreatePackedEntity( pSnapshot, edictIdx );
		pPackedEntity->SetChangeFrameList( pChangeFrame );
		pPackedEntity->SetServerAndClientClass( pServerClass, NULL );
		pPackedEntity->SetPackedData( newPackedProps );
		pPackedEntity->SetRecipients( recip );
	}

	edict->ClearStateChanged();
}

CON_COMMAND( sv_dump_entity_pack_stats, "Show stats on entity packing." )
{
	Msg("Entity Packing stats:\n");
	Msg("  numFastPathEncodes=%u\n", g_PackedEntityStats.m_numFastPathEncodes );
	Msg("  numSlowPathEncodes=%u\n", g_PackedEntityStats.m_numSlowPathEncodes );
}


// in HLTV mode we ALWAYS have to store position and PVS info, even if entity didnt change
void SV_FillHLTVData( CFrameSnapshot *pSnapshot, edict_t *edict, int iValidEdict )
{
	if ( pSnapshot->m_pHLTVEntityData && edict )
	{
		CHLTVEntityData *pHLTVData = &pSnapshot->m_pHLTVEntityData[iValidEdict];

		PVSInfo_t *pvsInfo = edict->GetNetworkable()->GetPVSInfo();

		if ( pvsInfo->m_nClusterCount == 1 )
		{
			// store cluster, if entity spawns only over one cluster
			pHLTVData->m_nNodeCluster = pvsInfo->m_pClusters[0];
		}
		else
		{
			// otherwise save PVS head node for larger entities
			pHLTVData->m_nNodeCluster = pvsInfo->m_nHeadNode | (1<<31);
		}

		// remember origin
		pHLTVData->origin[0] = pvsInfo->m_vCenter[0];
		pHLTVData->origin[1] = pvsInfo->m_vCenter[1];
		pHLTVData->origin[2] = pvsInfo->m_vCenter[2];
	}
}

#if defined( REPLAY_ENABLED )
// in Replay mode we ALWAYS have to store position and PVS info, even if entity didnt change
void SV_FillReplayData( CFrameSnapshot *pSnapshot, edict_t *edict, int iValidEdict )
{
	if ( pSnapshot->m_pReplayEntityData && edict )
	{
		CReplayEntityData *pReplayData = &pSnapshot->m_pReplayEntityData[iValidEdict];

		PVSInfo_t *pvsInfo = edict->GetNetworkable()->GetPVSInfo();

		if ( pvsInfo->m_nClusterCount == 1 )
		{
			// store cluster, if entity spawns only over one cluster
			pReplayData->m_nNodeCluster = pvsInfo->m_pClusters[0];
		}
		else
		{
			// otherwise save PVS head node for larger entities
			pReplayData->m_nNodeCluster = pvsInfo->m_nHeadNode | (1<<31);
		}

		// remember origin
		pReplayData->origin[0] = pvsInfo->m_vCenter[0];
		pReplayData->origin[1] = pvsInfo->m_vCenter[1];
		pReplayData->origin[2] = pvsInfo->m_vCenter[2];
	}
}
#endif

// Returns the SendTable that should be used with the specified edict.
SendTable* GetEntSendTable(edict_t *pEdict)
{
	if ( pEdict->GetNetworkable() )
	{
		ServerClass *pClass = pEdict->GetNetworkable()->GetServerClass();
		if ( pClass )
		{
			return pClass->m_pTable;
		}
	}

	return NULL;
}


void PackEntities_NetworkBackDoor( 
	int clientCount, 
	CGameClient **clients,
	CFrameSnapshot *snapshot )
{
	Assert( clientCount == 1 );

	CGameClient *client = clients[0];	// update variables cl, pInfo, frame for current client
	CCheckTransmitInfo *pInfo =  &client->m_PackInfo;

	//Msg ( " Using local network back door" );

	for ( int iValidEdict=0; iValidEdict < snapshot->m_nValidEntities; iValidEdict++ )
	{
		int index = snapshot->m_pValidEntities[iValidEdict];
		edict_t* edict = &sv.edicts[ index ];
		
		// this is a bit of a hack to ensure that we get a "preview" of the
		//  packet timstamp that the server will send so that things that
		//  are encoded relative to packet time will be correct
		Assert( edict->m_NetworkSerialNumber != -1 );

		bool bShouldTransmit = pInfo->m_pTransmitEdict->Get( index ) ? true : false;

		//CServerDTITimer timer( pSendTable, SERVERDTI_ENCODE );
		// If we're using the fast path for a single-player game, just pass the entity
		// directly over to the client.
		Assert( index < snapshot->m_nNumEntities );
		ServerClass *pSVClass = snapshot->m_pEntities[ index ].m_pClass;
		g_pLocalNetworkBackdoor->EntState( index, edict->m_NetworkSerialNumber, 
			pSVClass->m_ClassID, pSVClass->m_pTable, edict->GetUnknown(), edict->HasStateChanged(), bShouldTransmit );
		edict->ClearStateChanged();
	}
	
	// Tell the client about any entities that are now dormant.
	g_pLocalNetworkBackdoor->ProcessDormantEntities();
	InvalidateSharedEdictChangeInfos();
}

static ConVar sv_parallel_packentities( "sv_parallel_packentities", 
#ifndef DEDICATED
										"1", 
#else //DEDICATED
										"1", 
#endif //DEDICATED
										FCVAR_RELEASE );

struct PackWork_t
{
	int				nIdx;
	edict_t			*pEdict;
	CFrameSnapshot	*pSnapshot;

	static void Process( PackWork_t &item )
	{
		SV_PackEntity( item.nIdx, item.pEdict, item.pSnapshot->m_pEntities[ item.nIdx ].m_pClass, item.pSnapshot );
	}
};


void PackEntities_Normal( 
	int clientCount, 
	CGameClient **clients,
	CFrameSnapshot *snapshot )
{
	SNPROF("PackEntities_Normal");

	Assert( snapshot->m_nValidEntities <= MAX_EDICTS );

	PackWork_t workItems[MAX_EDICTS];
	int workItemCount = 0;

	// check for all active entities, if they are seen by at least on client, if
	// so, bit pack them 
	for ( int iValidEdict=0; iValidEdict < snapshot->m_nValidEntities; ++iValidEdict )
	{
		int index = snapshot->m_pValidEntities[ iValidEdict ];
		
		Assert( index < snapshot->m_nNumEntities );

		edict_t* edict = &sv.edicts[ index ];

		// if HLTV is running save PVS info for each entity
		SV_FillHLTVData( snapshot, edict, iValidEdict );
		
#if defined( REPLAY_ENABLED )
		// if Replay is running save PVS info for each entity
		SV_FillReplayData( snapshot, edict, iValidEdict );
#endif

		// Check to see if the entity changed this frame...
		//ServerDTI_RegisterNetworkStateChange( pSendTable, ent->m_bStateChanged );

		for ( int iClient = 0; iClient < clientCount; ++iClient )
		{
			// entities is seen by at least this client, pack it and exit loop
			CGameClient *client = clients[iClient];	// update variables cl, pInfo, frame for current client
			if ( client->IsHltvReplay() )
				continue; // clients in HLTV replay use HLTV stream that has already been pre-packed for them by HLTV master client. No need to do any packing while streaming HLTV contents

			CClientFrame *frame = client->m_pCurrentFrame;

			if( frame->transmit_entity.Get( index ) )
			{	
				workItems[workItemCount].nIdx = index;
				workItems[workItemCount].pEdict = edict;
				workItems[workItemCount].pSnapshot = snapshot;
				workItemCount++;
				break;
			}
		}
	}

	// Process work
	if ( sv_parallel_packentities.GetBool() )
	{
		ParallelProcess( workItems, workItemCount, &PackWork_t::Process );
	}
	else
	{
		int c = workItemCount;
		for ( int i = 0; i < c; ++i )
		{
			PackWork_t &w = workItems[ i ];
			SV_PackEntity( w.nIdx, w.pEdict, w.pSnapshot->m_pEntities[ w.nIdx ].m_pClass, w.pSnapshot );
		}
	}

	InvalidateSharedEdictChangeInfos();
}


//-----------------------------------------------------------------------------
// Writes the compressed packet of entities to all clients
//-----------------------------------------------------------------------------
void SV_ComputeClientPacks( 
	int clientCount, 
	CGameClient **clients,
	CFrameSnapshot *snapshot )
{
	SNPROF( "SV_ComputeClientPacks" );
	MDLCACHE_CRITICAL_SECTION_(g_pMDLCache);
	// Do some setup for each client
	{
		for (int iClient = 0; iClient < clientCount; ++iClient)
		{
			TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "CheckTransmit:%d", iClient );

			if ( clients[ iClient ]->IsHltvReplay() )
			{
				clients[ iClient ]->SetupHltvFrame( snapshot->m_nTickCount);
				continue; // skip all the transmit checks if we already have packs prepared by HLTV that we'll be sending anyway
			}

			CCheckTransmitInfo *pInfo = &clients[iClient]->m_PackInfo;
			clients[iClient]->SetupPackInfo( snapshot );
			if ( clients[iClient]->m_nDeltaTick < 0 )
			{
				serverGameEnts->PrepareForFullUpdate( clients[iClient]->edict );
			}
			serverGameEnts->CheckTransmit( pInfo, snapshot->m_pValidEntities, snapshot->m_nValidEntities );
			clients[iClient]->SetupPrevPackInfo();
		}
	}

	if ( g_pLocalNetworkBackdoor )
	{
#if !defined( DEDICATED )
		if ( GetBaseLocalClient().m_pServerClasses == NULL )
		{
			// Edge case - the local client has been cleaned up but we have a deferred tick to execute.
			// As far as I know, this can only happen if the player quits the game with a synchronous cbuf_execute while a server tick is in flight.
			// (e.g. user pauses the game, files a bug with vxconsole, then vxconsole unpauses the game while the user is still in the menu, then the user quits -> CRASH)
			Warning( "Attempting to pack entities on server with invalid local client state. Probably a result of VXConsole or con commands. Aborting SV_ComputeClientPacks.\n" );
			return;
		}
#endif

		// This will force network string table updates for local client to go through the backdoor if it's active
#ifdef SHARED_NET_STRING_TABLES
		sv.m_StringTables->TriggerCallbacks( clients[0]->m_nDeltaTick );
#else
		sv.m_StringTables->DirectUpdate( clients[0]->GetMaxAckTickCount() );
#endif
		
		g_pLocalNetworkBackdoor->StartEntityStateUpdate();

#ifndef DEDICATED
		int saveClientTicks = GetBaseLocalClient().GetClientTickCount();
		int saveServerTicks = GetBaseLocalClient().GetServerTickCount();
		bool bSaveSimulation = GetBaseLocalClient().insimulation;
		float flSaveLastServerTickTime = GetBaseLocalClient().m_flLastServerTickTime;

		GetBaseLocalClient().insimulation = true;
		GetBaseLocalClient().SetClientTickCount( sv.m_nTickCount );
		GetBaseLocalClient().SetServerTickCount( sv.m_nTickCount );

		GetBaseLocalClient().m_flLastServerTickTime = sv.m_nTickCount * host_state.interval_per_tick;
		g_ClientGlobalVariables.tickcount = GetBaseLocalClient().GetClientTickCount();
		g_ClientGlobalVariables.curtime = GetBaseLocalClient().GetTime();
#endif

		PackEntities_NetworkBackDoor( clientCount, clients, snapshot );

		g_pLocalNetworkBackdoor->EndEntityStateUpdate();

#ifndef DEDICATED
		GetBaseLocalClient().SetClientTickCount( saveClientTicks );
		GetBaseLocalClient().SetServerTickCount( saveServerTicks );
		GetBaseLocalClient().insimulation = bSaveSimulation;
		GetBaseLocalClient().m_flLastServerTickTime = flSaveLastServerTickTime;

		g_ClientGlobalVariables.tickcount = GetBaseLocalClient().GetClientTickCount();
		g_ClientGlobalVariables.curtime = GetBaseLocalClient().GetTime();
#endif

		PrintPartialChangeEntsList();
	}
	else
	{
		PackEntities_Normal( clientCount, clients, snapshot );
	}
}



// If the table's ID is -1, writes its info into the buffer and increments curID.
void SV_MaybeWriteSendTable( SendTable *pTable, bf_write &pBuf, bool bNeedDecoder )
{
	// Already sent?
	if ( pTable->GetWriteFlag() )
		return;

	pTable->SetWriteFlag( true );

	// write send table properties into stream
	if ( !SendTable_WriteInfos(pTable, pBuf, bNeedDecoder, false ) )
	{
		Host_Error( "Send Table buffer for class '%s' overflowed!!!\n", pTable->GetName() );
	}
}

// Calls SV_MaybeWriteSendTable recursively.
void SV_MaybeWriteSendTable_R( SendTable *pTable, bf_write &pBuf )
{
	SV_MaybeWriteSendTable( pTable, pBuf, false );

	// Make sure we send child send tables..
	for(int i=0; i < pTable->m_nProps; i++)
	{
		SendProp *pProp = &pTable->m_pProps[i];

		if( pProp->m_Type == DPT_DataTable )
			SV_MaybeWriteSendTable_R( pProp->GetDataTable(), pBuf );
	}
}


// Sets up SendTable IDs and sends an svc_sendtable for each table.
void SV_WriteSendTables( ServerClass *pClasses, bf_write &pBuf )
{
	ServerClass *pCur;

	DataTable_ClearWriteFlags( pClasses );

	// First, we send all the leaf classes. These are the ones that will need decoders
	// on the client.
	for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		SV_MaybeWriteSendTable( pCur->m_pTable, pBuf, true );
	}

	// Now, we send their base classes. These don't need decoders on the client
	// because we will never send these SendTables by themselves.
	for ( pCur=pClasses; pCur; pCur=pCur->m_pNext )
	{
		SV_MaybeWriteSendTable_R( pCur->m_pTable, pBuf );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : crc - 
//-----------------------------------------------------------------------------
void SV_ComputeClassInfosCRC( CRC32_t* crc )
{
	ServerClass *pClasses = serverGameDLL->GetAllServerClasses();

	for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext )
	{
		CRC32_ProcessBuffer( crc, (void *)pClass->m_pNetworkName, Q_strlen( pClass->m_pNetworkName ) );
		CRC32_ProcessBuffer( crc, (void *)pClass->m_pTable->GetName(), Q_strlen(pClass->m_pTable->GetName() ) );
	}
}

void CGameServer::AssignClassIds()
{
	ServerClass *pClasses = serverGameDLL->GetAllServerClasses();

	// Count the number of classes.
	int nClasses = 0;
	for ( ServerClass *pCount=pClasses; pCount; pCount=pCount->m_pNext )
	{
		++nClasses;
	}
	
	// These should be the same! If they're not, then it should detect an explicit create message.
	ErrorIfNot( nClasses <= MAX_SERVER_CLASSES,
		("CGameServer::AssignClassIds: too many server classes (%i, MAX = %i).\n", nClasses, MAX_SERVER_CLASSES );
	);

	serverclasses = nClasses;
	serverclassbits = Q_log2( serverclasses ) + 1;

	bool bSpew = CommandLine()->FindParm( "-netspike" ) ? true : false;

	int curID = 0;
	for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext )
	{
		pClass->m_ClassID = curID++;

		if ( bSpew )
		{
			Msg( "%d == '%s'\n", pClass->m_ClassID, pClass->GetName() );
		}
	}
}

// Assign each class and ID and write an svc_classinfo for each one.
void SV_WriteClassInfos(ServerClass *pClasses, bf_write &pBuf)
{
	// Assert( sv.serverclasses < MAX_SERVER_CLASSES );

	CSVCMsg_ClassInfo_t classinfomsg;

	classinfomsg.set_create_on_client( false );

	for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext )
	{
		CSVCMsg_ClassInfo::class_t *svclass = classinfomsg.add_classes();

		svclass->set_class_id( pClass->m_ClassID );
		svclass->set_data_table_name( pClass->m_pTable->GetName() );
		svclass->set_class_name( pClass->m_pNetworkName );
	}

	classinfomsg.WriteToBuffer( pBuf );
}

// This is implemented for the datatable code so its warnings can include an object's classname.
const char* GetObjectClassName( int objectID )
{
	if ( objectID >= 0 && objectID < sv.num_edicts )
	{
		return sv.edicts[objectID].GetClassName();
	}
	else
	{
		return "[unknown]";
	}
}


//-----------------------------------------------------------------------------
CON_COMMAND( sv_dump_class_info, "Dump server class infos." )
{
	ServerClass *pClasses = serverGameDLL->GetAllServerClasses();

	Msg( "ServerClassInfo:\n");
	Msg( "\tNetName(TableName):nProps PreCalcProps\n");

	for ( ServerClass *pCount=pClasses; pCount; pCount=pCount->m_pNext )
	{
		Msg("\t%s(%s):%d %d\n", pCount->GetName(), pCount->m_pTable->GetName(), pCount->m_pTable->m_nProps, pCount->m_pTable->m_pPrecalc ? pCount->m_pTable->m_pPrecalc->GetNumProps() : 0 );
	}
}


//-----------------------------------------------------------------------------
#include "tier1/tokenset.h"

const tokenset_t< SendPropType > gSendPropTypeTokenSet[] =
{
	{ "Int",       DPT_Int },
	{ "Float",     DPT_Float },
	{ "Vector",    DPT_Vector },
	{ "VectorXY",  DPT_VectorXY }, // Only encodes the XY of a vector, ignores Z
	{ "String",    DPT_String },
	{ "Array",     DPT_Array },	// An array of the base types (can't be of datatables).
	{ "DataTable", DPT_DataTable },
#if 0 // We can't ship this since it changes the size of DTVariant to be 20 bytes instead of 16 and that breaks MODs!!!
	{ "Quaternion,DPT_Quaternion },
#endif               
	{ "Int64",     DPT_Int64 },
	{ NULL, (SendPropType)0 }
};

//-----------------------------------------------------------------------------
static void PrintSendProp( int index, const SendProp *pSendProp )
{
	CUtlString flags;

	if ( pSendProp->GetFlags() & SPROP_VARINT ) { flags += "|VARINT"; }
	if ( pSendProp->GetFlags() & SPROP_UNSIGNED	) { flags += "|UNSIGNED"; }
	if ( pSendProp->GetFlags() & SPROP_COORD ) { flags += "|COORD"; }                       
	if ( pSendProp->GetFlags() & SPROP_NOSCALE ) { flags += "|NOSCALE"; }                     
	if ( pSendProp->GetFlags() & SPROP_ROUNDDOWN ) { flags += "|ROUNDDOWN"; }                   
	if ( pSendProp->GetFlags() & SPROP_ROUNDUP ) { flags += "|ROUNDUP"; }                     
	if ( pSendProp->GetFlags() & SPROP_NORMAL ) { flags += "|NORMAL"; }                      
	if ( pSendProp->GetFlags() & SPROP_EXCLUDE ) { flags += "|EXCLUDE"; }                     
	if ( pSendProp->GetFlags() & SPROP_XYZE ) { flags += "|XYZE"; }                        
	if ( pSendProp->GetFlags() & SPROP_INSIDEARRAY ) { flags += "|INSIDEARRAY"; }                 
	if ( pSendProp->GetFlags() & SPROP_PROXY_ALWAYS_YES ) { flags += "|PROXY_ALWAYS_YES"; }            
	if ( pSendProp->GetFlags() & SPROP_IS_A_VECTOR_ELEM ) { flags += "|IS_A_VECTOR_ELEM"; }            
	if ( pSendProp->GetFlags() & SPROP_COLLAPSIBLE ) { flags += "|COLLAPSIBLE"; }                 
	if ( pSendProp->GetFlags() & SPROP_COORD_MP ) { flags += "|COORD_MP"; }                    
	if ( pSendProp->GetFlags() & SPROP_COORD_MP_LOWPRECISION ) { flags += "|COORD_MP_LOWPRECISION"; }       
	if ( pSendProp->GetFlags() & SPROP_COORD_MP_INTEGRAL ) { flags += "|COORD_MP_INTEGRAL"; }           
	if ( pSendProp->GetFlags() & SPROP_CELL_COORD ) { flags += "|CELL_COORD"; }                  
	if ( pSendProp->GetFlags() & SPROP_CELL_COORD_LOWPRECISION ) { flags += "|CELL_COORD_LOWPRECISION"; }     
	if ( pSendProp->GetFlags() & SPROP_CELL_COORD_INTEGRAL ) { flags += "|CELL_COORD_INTEGRAL"; }         
	if ( pSendProp->GetFlags() & SPROP_CHANGES_OFTEN ) { flags += "|CHANGES_OFTEN"; }               
	if ( pSendProp->GetFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT ) { flags += "|ENCODED_AGAINST_TICKCOUNT"; }   

	const char *pFlags = flags.Get();
	if ( !flags.IsEmpty() && *flags.Get() == '|' )
	{
		pFlags = flags.Get() + 1;
	}

	CUtlString name;
	if ( pSendProp->GetParentArrayPropName() )
	{
		name.Format( "%s:%s", pSendProp->GetParentArrayPropName(), pSendProp->GetName() );
	}
	else
	{
		name = pSendProp->GetName();
	}

	Msg( "\t%d,%s,%d,%d,%s,%s,%d\n", 
		 index,
		 name.String(),
		 pSendProp->GetOffset(), 
		 pSendProp->m_nBits,
		 gSendPropTypeTokenSet->GetNameByToken( pSendProp->GetType() ), 
		 pFlags,
		 pSendProp->GetPriority()
		 );
}

//-----------------------------------------------------------------------------
CON_COMMAND( sv_dump_class_table, "Dump server class table matching the pattern (substr)." )
{
	if ( args.ArgC() < 2 )
	{
		Msg( "Provide a substr to match class info against.\n" );
		return;
	}

	ServerClass *pClasses = serverGameDLL->GetAllServerClasses();

	for ( int a = 1; a < args.ArgC(); ++a )
	{
		for ( ServerClass *pCount=pClasses; pCount; pCount=pCount->m_pNext )
		{
			if ( pCount->m_pTable->m_pPrecalc && Q_stristr( pCount->GetName(), args[a] ) != NULL )
			{
				Msg( "%s:\n", pCount->GetName() );
				Msg( "\tIndex,Name,Offset,Bits,Type,Flags,Priority\n" );

				const CSendTablePrecalc *pPrecalc = pCount->m_pTable->m_pPrecalc;

				for ( int i = 0; i < pPrecalc->GetNumProps(); ++i )
				{
					const SendProp *pSendProp = pPrecalc->GetProp( i );

					PrintSendProp( i, pSendProp );

				}
			}
		}
	}
}