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.
693 lines
21 KiB
693 lines
21 KiB
//========= Copyright 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 "tier0/vcrmode.h" |
|
#include "vstdlib/jobthread.h" |
|
#include "enginethreads.h" |
|
|
|
#ifdef SWDS |
|
IClientEntityList *entitylist = NULL; |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar sv_debugmanualmode( "sv_debugmanualmode", "0", 0, "Make sure entities correctly report whether or not their network data has changed." ); |
|
|
|
// 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, const void *pData, int nBytes ) |
|
{ |
|
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( 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 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. |
|
nBytes, |
|
pData ); |
|
|
|
// 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; |
|
Assert( pClass->m_InstanceBaselineIndex != INVALID_STRING_INDEX ); |
|
} |
|
} |
|
// Read-acquire barrier |
|
ThreadMemoryBarrier(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Pack the entity.... |
|
//----------------------------------------------------------------------------- |
|
|
|
static inline void SV_PackEntity( |
|
int edictIdx, |
|
edict_t* edict, |
|
ServerClass* pServerClass, |
|
CFrameSnapshot *pSnapshot ) |
|
{ |
|
Assert( edictIdx < pSnapshot->m_nNumEntities ); |
|
tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "PackEntities_Normal%s", __FUNCTION__ ); |
|
|
|
int iSerialNum = pSnapshot->m_pEntities[ edictIdx ].m_nSerialNumber; |
|
|
|
// Check to see if this entity specifies its changes. |
|
// If so, then try to early out making the fullpack |
|
bool bUsedPrev = false; |
|
|
|
if ( !edict->HasStateChanged() ) |
|
{ |
|
// Now this may not work if we didn't previously send a packet; |
|
// if not, then we gotta compute it |
|
bUsedPrev = framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ); |
|
} |
|
|
|
if ( bUsedPrev && !sv_debugmanualmode.GetInt() ) |
|
{ |
|
edict->ClearStateChanged(); |
|
return; |
|
} |
|
|
|
// First encode the entity's data. |
|
ALIGN4 char packedData[MAX_PACKEDENTITY_DATA] ALIGN4_POST; |
|
bf_write writeBuf( "SV_PackEntity->writeBuf", packedData, sizeof( packedData ) ); |
|
|
|
SendTable *pSendTable = pServerClass->m_pTable; |
|
|
|
// (avoid constructor overhead). |
|
unsigned char tempData[ sizeof( CSendProxyRecipients ) * MAX_DATATABLE_PROXIES ]; |
|
CUtlMemory< CSendProxyRecipients > recip( (CSendProxyRecipients*)tempData, pSendTable->m_pPrecalc->GetNumDataTableProxies() ); |
|
|
|
if( !SendTable_Encode( pSendTable, edict->GetUnknown(), &writeBuf, edictIdx, &recip, false ) ) |
|
{ |
|
Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", edictIdx ); |
|
} |
|
|
|
#ifndef NO_VCR |
|
// VCR mode stuff.. |
|
if ( vcr_verbose.GetInt() && writeBuf.GetNumBytesWritten() > 0 ) |
|
VCRGenericValueVerify( "writebuf", writeBuf.GetBasePointer(), writeBuf.GetNumBytesWritten()-1 ); |
|
#endif |
|
|
|
SV_EnsureInstanceBaseline( pServerClass, edictIdx, packedData, writeBuf.GetNumBytesWritten() ); |
|
|
|
int nFlatProps = SendTable_GetNumFlatProps( pSendTable ); |
|
IChangeFrameList *pChangeFrame = NULL; |
|
|
|
// If this entity was previously in there, then it should have a valid IChangeFrameList |
|
// which we can delta against to figure out which properties have changed. |
|
// |
|
// If not, then we want to setup a new IChangeFrameList. |
|
|
|
PackedEntity *pPrevFrame = framesnapshotmanager->GetPreviouslySentPacket( edictIdx, pSnapshot->m_pEntities[ edictIdx ].m_nSerialNumber ); |
|
if ( pPrevFrame ) |
|
{ |
|
// Calculate a delta. |
|
Assert( !pPrevFrame->IsCompressed() ); |
|
|
|
int deltaProps[MAX_DATATABLE_PROPS]; |
|
|
|
int nChanges = SendTable_CalcDelta( |
|
pSendTable, |
|
pPrevFrame->GetData(), pPrevFrame->GetNumBits(), |
|
packedData, writeBuf.GetNumBitsWritten(), |
|
|
|
deltaProps, |
|
ARRAYSIZE( deltaProps ), |
|
|
|
edictIdx |
|
); |
|
|
|
#ifndef NO_VCR |
|
if ( vcr_verbose.GetInt() ) |
|
VCRGenericValueVerify( "nChanges", &nChanges, sizeof( nChanges ) ); |
|
#endif |
|
|
|
// 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). |
|
// It would be interesting to hook here and see how many non-manual-mode entities |
|
// are winding up with no changes. |
|
if ( nChanges == 0 ) |
|
{ |
|
if ( pPrevFrame->CompareRecipients( recip ) ) |
|
{ |
|
if ( framesnapshotmanager->UsePreviouslySentPacket( pSnapshot, edictIdx, iSerialNum ) ) |
|
{ |
|
edict->ClearStateChanged(); |
|
return; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if ( !edict->HasStateChanged() ) |
|
{ |
|
for ( int iDeltaProp=0; iDeltaProp < nChanges; iDeltaProp++ ) |
|
{ |
|
Assert( pSendTable->m_pPrecalc ); |
|
Assert( deltaProps[iDeltaProp] < pSendTable->m_pPrecalc->GetNumProps() ); |
|
|
|
const SendProp *pProp = pSendTable->m_pPrecalc->GetProp( deltaProps[iDeltaProp] ); |
|
// If a field changed, but it changed because it encoded against tickcount, |
|
// then it's just like the entity changed the underlying field, not an error, that is. |
|
if ( pProp->GetFlags() & SPROP_ENCODED_AGAINST_TICKCOUNT ) |
|
continue; |
|
|
|
Msg( "Entity %d (class '%s') reported ENTITY_CHANGE_NONE but '%s' changed.\n", |
|
edictIdx, |
|
edict->GetClassName(), |
|
pProp->GetName() ); |
|
|
|
} |
|
} |
|
} |
|
|
|
#ifndef _XBOX |
|
#if defined( REPLAY_ENABLED ) |
|
if ( (hltv && hltv->IsActive()) || (replay && replay->IsActive()) ) |
|
#else |
|
if ( hltv && hltv->IsActive() ) |
|
#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(); |
|
pChangeFrame = pChangeFrame->Copy(); // allocs and copies ChangeFrameList |
|
} |
|
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() == nFlatProps, |
|
("SV_PackEntity: SnagChangeFrameList mismatched number of props[%d vs %d]", nFlatProps, pChangeFrame->GetNumProps() ) ); |
|
|
|
pChangeFrame->SetChangeTick( deltaProps, nChanges, pSnapshot->m_nTickCount ); |
|
} |
|
else |
|
{ |
|
// Ok, init the change frames for the first time. |
|
pChangeFrame = AllocChangeFrameList( nFlatProps, pSnapshot->m_nTickCount ); |
|
} |
|
|
|
// Now make a PackedEntity and store the new packed data in there. |
|
{ |
|
PackedEntity *pPackedEntity = framesnapshotmanager->CreatePackedEntity( pSnapshot, edictIdx ); |
|
pPackedEntity->SetChangeFrameList( pChangeFrame ); |
|
pPackedEntity->SetServerAndClientClass( pServerClass, NULL ); |
|
pPackedEntity->AllocAndCopyPadded( packedData, writeBuf.GetNumBytesWritten() ); |
|
pPackedEntity->SetRecipients( recip ); |
|
} |
|
|
|
edict->ClearStateChanged(); |
|
} |
|
|
|
// 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 !defined( _XBOX ) |
|
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]; |
|
} |
|
#endif |
|
} |
|
|
|
// 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 !defined( _XBOX ) |
|
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 ); |
|
|
|
VPROF_BUDGET( "PackEntities_NetworkBackDoor", VPROF_BUDGETGROUP_OTHER_NETWORKING ); |
|
|
|
CGameClient *client = clients[0]; // update variables cl, pInfo, frame for current client |
|
CCheckTransmitInfo *pInfo = &client->m_PackInfo; |
|
|
|
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", "1" ); |
|
|
|
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 ) |
|
{ |
|
Assert( snapshot->m_nValidEntities >= 0 && snapshot->m_nValidEntities <= MAX_EDICTS ); |
|
tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s %d", __FUNCTION__, snapshot->m_nValidEntities ); |
|
|
|
CUtlVectorFixed< PackWork_t, MAX_EDICTS > workItems; |
|
|
|
// 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 Replay is running save PVS info for each entity |
|
SV_FillReplayData( snapshot, edict, iValidEdict ); |
|
|
|
// 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 |
|
CClientFrame *frame = client->m_pCurrentFrame; |
|
|
|
if( frame->transmit_entity.Get( index ) ) |
|
{ |
|
PackWork_t w; |
|
w.nIdx = index; |
|
w.pEdict = edict; |
|
w.pSnapshot = snapshot; |
|
|
|
workItems.AddToTail( w ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// Process work |
|
if ( sv_parallel_packentities.GetBool() ) |
|
{ |
|
ParallelProcess( "PackWork_t::Process", workItems.Base(), workItems.Count(), &PackWork_t::Process ); |
|
} |
|
else |
|
{ |
|
int c = workItems.Count(); |
|
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 ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
MDLCACHE_CRITICAL_SECTION_(g_pMDLCache); |
|
// Do some setup for each client |
|
{ |
|
VPROF_BUDGET_FLAGS( "SV_ComputeClientPacks", "CheckTransmit", BUDGETFLAG_SERVER ); |
|
|
|
for (int iClient = 0; iClient < clientCount; ++iClient) |
|
{ |
|
CCheckTransmitInfo *pInfo = &clients[iClient]->m_PackInfo; |
|
clients[iClient]->SetupPackInfo( snapshot ); |
|
serverGameEnts->CheckTransmit( pInfo, snapshot->m_pValidEntities, snapshot->m_nValidEntities ); |
|
clients[iClient]->SetupPrevPackInfo(); |
|
} |
|
} |
|
|
|
VPROF_BUDGET_FLAGS( "SV_ComputeClientPacks", "ComputeClientPacks", BUDGETFLAG_SERVER ); |
|
|
|
if ( g_pLocalNetworkBackdoor ) |
|
{ |
|
// 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 SWDS |
|
int saveClientTicks = cl.GetClientTickCount(); |
|
int saveServerTicks = cl.GetServerTickCount(); |
|
bool bSaveSimulation = cl.insimulation; |
|
float flSaveLastServerTickTime = cl.m_flLastServerTickTime; |
|
|
|
cl.insimulation = true; |
|
cl.SetClientTickCount( sv.m_nTickCount ); |
|
cl.SetServerTickCount( sv.m_nTickCount ); |
|
|
|
cl.m_flLastServerTickTime = sv.m_nTickCount * host_state.interval_per_tick; |
|
g_ClientGlobalVariables.tickcount = cl.GetClientTickCount(); |
|
g_ClientGlobalVariables.curtime = cl.GetTime(); |
|
#endif |
|
|
|
PackEntities_NetworkBackDoor( clientCount, clients, snapshot ); |
|
|
|
g_pLocalNetworkBackdoor->EndEntityStateUpdate(); |
|
|
|
#ifndef SWDS |
|
cl.SetClientTickCount( saveClientTicks ); |
|
cl.SetServerTickCount( saveServerTicks ); |
|
cl.insimulation = bSaveSimulation; |
|
cl.m_flLastServerTickTime = flSaveLastServerTickTime; |
|
|
|
g_ClientGlobalVariables.tickcount = cl.GetClientTickCount(); |
|
g_ClientGlobalVariables.curtime = cl.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 ); |
|
|
|
SVC_SendTable sndTbl; |
|
|
|
byte tmpbuf[4096]; |
|
sndTbl.m_DataOut.StartWriting( tmpbuf, sizeof(tmpbuf) ); |
|
|
|
// write send table properties into message buffer |
|
SendTable_WriteInfos(pTable, &sndTbl.m_DataOut ); |
|
|
|
sndTbl.m_bNeedsDecoder = bNeedDecoder; |
|
|
|
// write message to stream |
|
sndTbl.WriteToBuffer( pBuf ); |
|
} |
|
|
|
// 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" ) != 0; |
|
|
|
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 ); |
|
|
|
SVC_ClassInfo classinfomsg; |
|
|
|
classinfomsg.m_bCreateOnClient = false; |
|
|
|
for ( ServerClass *pClass=pClasses; pClass; pClass=pClass->m_pNext ) |
|
{ |
|
SVC_ClassInfo::class_t svclass; |
|
|
|
svclass.classID = pClass->m_ClassID; |
|
Q_strncpy( svclass.datatablename, pClass->m_pTable->GetName(), sizeof(svclass.datatablename) ); |
|
Q_strncpy( svclass.classname, pClass->m_pNetworkName, sizeof(svclass.classname) ); |
|
|
|
classinfomsg.m_Classes.AddToTail( svclass ); // add all known classes to message |
|
} |
|
|
|
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]"; |
|
} |
|
} |
|
|
|
|
|
|
|
|