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.
578 lines
16 KiB
578 lines
16 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
#include "server_pch.h"
|
|
#include <utllinkedlist.h>
|
|
|
|
#include "hltvserver.h"
|
|
#if defined( REPLAY_ENABLED )
|
|
#include "replayserver.h"
|
|
#endif
|
|
#include "framesnapshot.h"
|
|
#include "sys_dll.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
DEFINE_FIXEDSIZE_ALLOCATOR( CFrameSnapshot, 64, 64 );
|
|
|
|
|
|
static ConVar sv_creationtickcheck( "sv_creationtickcheck", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Do extended check for encoding of timestamps against tickcount" );
|
|
extern CGlobalVars g_ServerGlobalVariables;
|
|
|
|
// Expose interface
|
|
static CFrameSnapshotManager g_FrameSnapshotManager;
|
|
CFrameSnapshotManager *framesnapshotmanager = &g_FrameSnapshotManager;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CFrameSnapshotManager::CFrameSnapshotManager( void ) : m_PackedEntitiesPool( MAX_EDICTS / 16, CUtlMemoryPool::GROW_SLOW )
|
|
{
|
|
COMPILE_TIME_ASSERT( INVALID_PACKED_ENTITY_HANDLE == 0 );
|
|
Assert( INVALID_PACKED_ENTITY_HANDLE == m_PackedEntities.InvalidIndex() );
|
|
Q_memset( m_pLastPackedData, 0x00, MAX_EDICTS * sizeof(PackedEntityHandle_t) );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CFrameSnapshotManager::~CFrameSnapshotManager( void )
|
|
{
|
|
#ifdef _DEBUG
|
|
if ( IsInErrorExit() )
|
|
{
|
|
// These may have been freed already. Don't crash when freeing these.
|
|
Q_memset( &m_PackedEntities, 0, sizeof( m_PackedEntities ) );
|
|
}
|
|
else
|
|
{
|
|
Assert( m_FrameSnapshots.Count() == 0 );
|
|
Assert( m_PackedEntities.Count() == 0 );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Called when a level change happens
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CFrameSnapshotManager::LevelChanged()
|
|
{
|
|
// Clear all lists...
|
|
Assert( m_FrameSnapshots.Count() == 0 );
|
|
|
|
// Release the most recent snapshot...
|
|
m_PackedEntities.RemoveAll();
|
|
m_PackedEntitiesPool.Clear();
|
|
m_PackedEntityCache.RemoveAll();
|
|
COMPILE_TIME_ASSERT( INVALID_PACKED_ENTITY_HANDLE == 0 );
|
|
Q_memset( m_pLastPackedData, 0x00, MAX_EDICTS * sizeof(PackedEntityHandle_t) );
|
|
}
|
|
|
|
CFrameSnapshot *CFrameSnapshotManager::NextSnapshot( CFrameSnapshot *pSnapshot )
|
|
{
|
|
if ( !pSnapshot || ((unsigned short)pSnapshot->m_ListIndex == m_FrameSnapshots.InvalidIndex()) )
|
|
return NULL;
|
|
|
|
int next = m_FrameSnapshots.Next(pSnapshot->m_ListIndex);
|
|
if ( next == m_FrameSnapshots.InvalidIndex() )
|
|
return NULL;
|
|
|
|
// return next element in list
|
|
return m_FrameSnapshots[ next ];
|
|
}
|
|
|
|
CFrameSnapshot* CFrameSnapshotManager::CreateEmptySnapshot(
|
|
#ifdef DEBUG_SNAPSHOT_REFERENCES
|
|
char const *szDebugName,
|
|
#endif
|
|
int tickcount, int maxEntities, uint32 nSnapshotSet )
|
|
{
|
|
CFrameSnapshot *snap = NULL;
|
|
{
|
|
AUTO_LOCK_FM( m_FrameSnapshotsWriteMutex );
|
|
snap = new CFrameSnapshot;
|
|
snap->AddReference();
|
|
}
|
|
#ifdef DEBUG_SNAPSHOT_REFERENCES
|
|
Q_strncpy( snap->m_chDebugSnapshotName, szDebugName, sizeof( snap->m_chDebugSnapshotName ) );
|
|
#endif
|
|
snap->m_nSnapshotSet = nSnapshotSet;
|
|
snap->m_nTickCount = tickcount;
|
|
snap->m_nNumEntities = maxEntities;
|
|
snap->m_nValidEntities = 0;
|
|
snap->m_pValidEntities = NULL;
|
|
snap->m_pHLTVEntityData = NULL;
|
|
#if defined( REPLAY_ENABLED )
|
|
snap->m_pReplayEntityData = NULL;
|
|
#endif
|
|
snap->m_pEntities = new CFrameSnapshotEntry[maxEntities];
|
|
|
|
CFrameSnapshotEntry *entry = snap->m_pEntities;
|
|
|
|
// clear entries
|
|
for ( int i=0; i < maxEntities; i++)
|
|
{
|
|
entry->m_pClass = NULL;
|
|
entry->m_nSerialNumber = -1;
|
|
entry->m_pPackedData = INVALID_PACKED_ENTITY_HANDLE;
|
|
entry++;
|
|
}
|
|
|
|
{
|
|
AUTO_LOCK_FM( m_FrameSnapshotsWriteMutex );
|
|
snap->m_ListIndex = m_FrameSnapshots.AddToTail( snap );
|
|
}
|
|
|
|
return snap;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : framenumber -
|
|
//-----------------------------------------------------------------------------
|
|
CFrameSnapshot* CFrameSnapshotManager::TakeTickSnapshot(
|
|
#ifdef DEBUG_SNAPSHOT_REFERENCES
|
|
char const *szDebugName,
|
|
#endif
|
|
int tickcount, uint32 nSnapshotSet )
|
|
{
|
|
unsigned short nValidEntities[MAX_EDICTS];
|
|
|
|
SNPROF( __FUNCTION__ );
|
|
|
|
CFrameSnapshot *snap = CreateEmptySnapshot(
|
|
#ifdef DEBUG_SNAPSHOT_REFERENCES
|
|
szDebugName,
|
|
#endif
|
|
tickcount, sv.num_edicts, nSnapshotSet );
|
|
|
|
int maxclients = sv.GetClientCount();
|
|
|
|
CFrameSnapshotEntry *entry = snap->m_pEntities - 1;
|
|
edict_t *edict= sv.edicts - 1;
|
|
|
|
// Build the snapshot.
|
|
for ( int i = 0; i < sv.num_edicts; i++ )
|
|
{
|
|
edict++;
|
|
entry++;
|
|
|
|
if ( IsGameConsole() && edict->GetNetworkable() )
|
|
{
|
|
PREFETCH360( edict->GetNetworkable(), 0 );
|
|
}
|
|
|
|
IServerUnknown *pUnk = edict->GetUnknown();
|
|
|
|
if ( !pUnk )
|
|
continue;
|
|
|
|
if ( edict->IsFree() )
|
|
continue;
|
|
|
|
// We don't want entities from inactive clients in the fullpack,
|
|
if ( i > 0 && i <= maxclients )
|
|
{
|
|
// this edict is a client
|
|
if ( !sv.GetClient(i-1)->IsActive() )
|
|
continue;
|
|
}
|
|
|
|
// entity exists and is not marked as 'free'
|
|
Assert( edict->m_NetworkSerialNumber != -1 );
|
|
Assert( edict->GetNetworkable() );
|
|
Assert( edict->GetNetworkable()->GetServerClass() );
|
|
|
|
entry->m_nSerialNumber = edict->m_NetworkSerialNumber;
|
|
entry->m_pClass = edict->GetNetworkable()->GetServerClass();
|
|
nValidEntities[snap->m_nValidEntities++] = i;
|
|
}
|
|
|
|
// create dynamic valid entities array and copy indices
|
|
snap->m_pValidEntities = new unsigned short[snap->m_nValidEntities];
|
|
Q_memcpy( snap->m_pValidEntities, nValidEntities, snap->m_nValidEntities * sizeof(unsigned short) );
|
|
|
|
if ( IsHltvActive() )
|
|
{
|
|
snap->m_pHLTVEntityData = new CHLTVEntityData[snap->m_nValidEntities];
|
|
Q_memset( snap->m_pHLTVEntityData, 0, snap->m_nValidEntities * sizeof(CHLTVEntityData) );
|
|
}
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
if ( replay && replay->IsActive() )
|
|
{
|
|
snap->m_pReplayEntityData = new CReplayEntityData[snap->m_nValidEntities];
|
|
Q_memset( snap->m_pReplayEntityData, 0, snap->m_nValidEntities * sizeof(CReplayEntityData) );
|
|
}
|
|
#endif
|
|
snap->m_iExplicitDeleteSlots.CopyArray( m_iExplicitDeleteSlots.Base(), m_iExplicitDeleteSlots.Count() );
|
|
m_iExplicitDeleteSlots.Purge();
|
|
|
|
return snap;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Cleans up packed entity data
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CFrameSnapshotManager::DeleteFrameSnapshot( CFrameSnapshot* pSnapshot )
|
|
{
|
|
// Decrement reference counts of all packed entities
|
|
for (int i = 0; i < pSnapshot->m_nNumEntities; ++i)
|
|
{
|
|
if ( pSnapshot->m_pEntities[i].m_pPackedData != INVALID_PACKED_ENTITY_HANDLE )
|
|
{
|
|
RemoveEntityReference( pSnapshot->m_pEntities[i].m_pPackedData );
|
|
}
|
|
}
|
|
|
|
|
|
m_FrameSnapshots.Remove( pSnapshot->m_ListIndex );
|
|
delete pSnapshot;
|
|
}
|
|
|
|
void CFrameSnapshotManager::RemoveEntityReference( PackedEntityHandle_t handle )
|
|
{
|
|
Assert( handle != INVALID_PACKED_ENTITY_HANDLE );
|
|
|
|
PackedEntity *packedEntity = m_PackedEntities[ handle ];
|
|
|
|
if ( --packedEntity->m_ReferenceCount <= 0)
|
|
{
|
|
AUTO_LOCK_FM( m_WriteMutex );
|
|
|
|
m_PackedEntities.Remove( handle );
|
|
m_PackedEntitiesPool.Free( packedEntity );
|
|
|
|
// if we have a uncompression cache, remove reference too
|
|
FOR_EACH_VEC( m_PackedEntityCache, i )
|
|
{
|
|
UnpackedDataCache_t &pdc = m_PackedEntityCache[i];
|
|
if ( pdc.pEntity == packedEntity )
|
|
{
|
|
pdc.pEntity = NULL;
|
|
pdc.counter = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CFrameSnapshotManager::AddEntityReference( PackedEntityHandle_t handle )
|
|
{
|
|
Assert( handle != INVALID_PACKED_ENTITY_HANDLE );
|
|
|
|
m_PackedEntities[ handle ]->m_ReferenceCount++;
|
|
}
|
|
|
|
void CFrameSnapshotManager::AddExplicitDelete( int iSlot )
|
|
{
|
|
AUTO_LOCK_FM( m_WriteMutex );
|
|
|
|
if ( m_iExplicitDeleteSlots.Find(iSlot) == m_iExplicitDeleteSlots.InvalidIndex() )
|
|
{
|
|
m_iExplicitDeleteSlots.AddToTail( iSlot );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the "basis" for encoding m_flAnimTime, m_flSimulationTime has changed
|
|
// since the time this entity was packed to the time we're trying to re-use the packing.
|
|
//-----------------------------------------------------------------------------
|
|
bool CFrameSnapshotManager::ShouldForceRepack( CFrameSnapshot* pSnapshot, int entity, PackedEntityHandle_t handle )
|
|
{
|
|
if ( sv_creationtickcheck.GetBool() )
|
|
{
|
|
PackedEntity *pe = m_PackedEntities[ handle ];
|
|
Assert( pe );
|
|
if ( pe && pe->ShouldCheckCreationTick() )
|
|
{
|
|
int nCurrentNetworkBase = g_ServerGlobalVariables.GetNetworkBase( pSnapshot->m_nTickCount, entity );
|
|
int nPackedEntityNetworkBase = g_ServerGlobalVariables.GetNetworkBase( pe->GetSnapshotCreationTick(), entity );
|
|
if ( nCurrentNetworkBase != nPackedEntityNetworkBase )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CFrameSnapshotManager::UsePreviouslySentPacket( CFrameSnapshot* pSnapshot,
|
|
int entity, int entSerialNumber )
|
|
{
|
|
PackedEntityHandle_t handle = m_pLastPackedData[entity];
|
|
if ( handle != INVALID_PACKED_ENTITY_HANDLE )
|
|
{
|
|
// NOTE: We can't use the previously sent packet if there was a
|
|
// serial number change....
|
|
if ( m_pSerialNumber[entity] == entSerialNumber )
|
|
{
|
|
// Check if we need to re-pack entity due to encoding against gpGlobals->tickcount
|
|
if ( framesnapshotmanager->ShouldForceRepack( pSnapshot, entity, handle ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Assert( entity < pSnapshot->m_nNumEntities );
|
|
pSnapshot->m_pEntities[entity].m_pPackedData = handle;
|
|
m_PackedEntities[handle]->m_ReferenceCount++;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
PackedEntity* CFrameSnapshotManager::GetPreviouslySentPacket( int iEntity, int iSerialNumber )
|
|
{
|
|
PackedEntityHandle_t handle = m_pLastPackedData[iEntity];
|
|
if ( handle != INVALID_PACKED_ENTITY_HANDLE )
|
|
{
|
|
// NOTE: We can't use the previously sent packet if there was a
|
|
// serial number change....
|
|
if ( m_pSerialNumber[iEntity] == iSerialNumber )
|
|
{
|
|
return m_PackedEntities[handle];
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the pack data for a particular entity for a particular snapshot
|
|
//-----------------------------------------------------------------------------
|
|
|
|
PackedEntity* CFrameSnapshotManager::CreatePackedEntity( CFrameSnapshot* pSnapshot, int entity )
|
|
{
|
|
PackedEntity* pNewEntity = CreateLocalPackedEntity( pSnapshot, entity );
|
|
|
|
// Add a reference into the global list of last entity packets seen...
|
|
// and remove the reference to the last entity packet we saw
|
|
if (m_pLastPackedData[entity] != INVALID_PACKED_ENTITY_HANDLE )
|
|
{
|
|
RemoveEntityReference( m_pLastPackedData[entity] );
|
|
}
|
|
|
|
m_pLastPackedData[entity] = pSnapshot->m_pEntities[entity].m_pPackedData;
|
|
m_pSerialNumber[entity] = pSnapshot->m_pEntities[entity].m_nSerialNumber;
|
|
pNewEntity->m_ReferenceCount++;
|
|
pNewEntity->SetSnapshotCreationTick( pSnapshot->m_nTickCount );
|
|
|
|
return pNewEntity;
|
|
}
|
|
|
|
PackedEntity* CFrameSnapshotManager::CreateLocalPackedEntity( CFrameSnapshot* pSnapshot, int entity )
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
m_WriteMutex.Lock();
|
|
PackedEntity *packedEntity = m_PackedEntitiesPool.Alloc();
|
|
PackedEntityHandle_t handle = m_PackedEntities.AddToTail( packedEntity );
|
|
m_WriteMutex.Unlock();
|
|
|
|
Assert( entity < pSnapshot->m_nNumEntities );
|
|
Assert( entity >= 0 && entity < ARRAYSIZE(m_pLastPackedData) );
|
|
|
|
packedEntity->m_ReferenceCount = 1;
|
|
packedEntity->m_nEntityIndex = entity;
|
|
pSnapshot->m_pEntities[entity].m_pPackedData = handle;
|
|
|
|
return packedEntity;
|
|
}
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------ //
|
|
// purpose: lookup cache if we have an uncompressed version of this packed entity
|
|
// ------------------------------------------------------------------------------------------------ //
|
|
UnpackedDataCache_t *CFrameSnapshotManager::GetCachedUncompressedEntity( PackedEntity *packedEntity )
|
|
{
|
|
if ( m_PackedEntityCache.Count() == 0 )
|
|
{
|
|
// ops, we have no cache yet, create one and reset counter
|
|
m_nPackedEntityCacheCounter = 0;
|
|
m_PackedEntityCache.SetCount( 128 );
|
|
|
|
FOR_EACH_VEC( m_PackedEntityCache, i )
|
|
{
|
|
m_PackedEntityCache[i].pEntity = NULL;
|
|
m_PackedEntityCache[i].counter = 0;
|
|
}
|
|
}
|
|
|
|
m_nPackedEntityCacheCounter++;
|
|
|
|
// remember oldest cache entry
|
|
UnpackedDataCache_t *pdcOldest = NULL;
|
|
int oldestValue = m_nPackedEntityCacheCounter;
|
|
|
|
|
|
FOR_EACH_VEC( m_PackedEntityCache, i )
|
|
{
|
|
UnpackedDataCache_t *pdc = &m_PackedEntityCache[i];
|
|
|
|
if ( pdc->pEntity == packedEntity )
|
|
{
|
|
// hit, found it, update counter
|
|
pdc->counter = m_nPackedEntityCacheCounter;
|
|
return pdc;
|
|
}
|
|
|
|
if( pdc->counter < oldestValue )
|
|
{
|
|
oldestValue = pdc->counter;
|
|
pdcOldest = pdc;
|
|
}
|
|
}
|
|
|
|
Assert ( pdcOldest );
|
|
|
|
// hmm, not in cache, clear & return oldest one
|
|
pdcOldest->counter = m_nPackedEntityCacheCounter;
|
|
pdcOldest->bits = -1; // important, this is the signal for the caller to fill this structure
|
|
pdcOldest->pEntity = packedEntity;
|
|
return pdcOldest;
|
|
}
|
|
|
|
void CFrameSnapshotManager::BuildSnapshotList( CFrameSnapshot *pCurrentSnapshot, CFrameSnapshot *pLastSnapshot, uint32 nSnapshotSet, CReferencedSnapshotList &list )
|
|
{
|
|
// Keep list building thread-safe
|
|
AUTO_LOCK_FM( m_FrameSnapshotsWriteMutex );
|
|
|
|
int nInsanity = 0;
|
|
CFrameSnapshot *pSnapshot;
|
|
if ( pLastSnapshot )
|
|
{
|
|
pSnapshot = NextSnapshot( pLastSnapshot );
|
|
}
|
|
else
|
|
{
|
|
pSnapshot = pCurrentSnapshot;
|
|
}
|
|
|
|
while ( pSnapshot )
|
|
{
|
|
//only add snapshots that match the desired set to the list
|
|
if( pSnapshot->m_nSnapshotSet == nSnapshotSet )
|
|
{
|
|
pSnapshot->AddReference();
|
|
list.m_vecSnapshots.AddToTail( pSnapshot );
|
|
}
|
|
|
|
++nInsanity;
|
|
if ( nInsanity > 100000 )
|
|
{
|
|
Error( "CFrameSnapshotManager::BuildSnapshotList: infinite loop building list!!!" );
|
|
}
|
|
|
|
if ( pSnapshot == pCurrentSnapshot )
|
|
break;
|
|
|
|
// got to next snapshot
|
|
pSnapshot = NextSnapshot( pSnapshot );
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------ //
|
|
// CFrameSnapshot
|
|
// ------------------------------------------------------------------------------------------------ //
|
|
|
|
#if defined( _DEBUG )
|
|
int g_nAllocatedSnapshots = 0;
|
|
#endif
|
|
|
|
|
|
CFrameSnapshot::CFrameSnapshot()
|
|
{
|
|
m_nTempEntities = 0;
|
|
m_pTempEntities = NULL;
|
|
m_pValidEntities = NULL;
|
|
m_nReferences = 0;
|
|
#if defined( _DEBUG )
|
|
++g_nAllocatedSnapshots;
|
|
Assert( g_nAllocatedSnapshots < 80000 ); // this probably would indicate a memory leak.
|
|
#endif
|
|
#ifdef DEBUG_SNAPSHOT_REFERENCES
|
|
Q_memset( m_chDebugSnapshotName, 0, sizeof( m_chDebugSnapshotName ) );
|
|
#endif
|
|
}
|
|
|
|
|
|
CFrameSnapshot::~CFrameSnapshot()
|
|
{
|
|
delete [] m_pValidEntities;
|
|
delete [] m_pEntities;
|
|
|
|
if ( m_pTempEntities )
|
|
{
|
|
Assert( m_nTempEntities>0 );
|
|
for (int i = 0; i < m_nTempEntities; i++ )
|
|
{
|
|
delete m_pTempEntities[i];
|
|
}
|
|
|
|
delete [] m_pTempEntities;
|
|
}
|
|
|
|
if ( m_pHLTVEntityData )
|
|
{
|
|
delete [] m_pHLTVEntityData;
|
|
}
|
|
|
|
#if defined( REPLAY_ENABLED )
|
|
if ( m_pReplayEntityData )
|
|
{
|
|
delete [] m_pReplayEntityData;
|
|
}
|
|
#endif
|
|
Assert ( m_nReferences == 0 );
|
|
|
|
#if defined( _DEBUG )
|
|
--g_nAllocatedSnapshots;
|
|
Assert( g_nAllocatedSnapshots >= 0 );
|
|
#endif
|
|
}
|
|
|
|
|
|
void CFrameSnapshot::AddReference()
|
|
{
|
|
Assert( m_nReferences < 0xFFFF );
|
|
++m_nReferences;
|
|
}
|
|
|
|
void CFrameSnapshot::ReleaseReference()
|
|
{
|
|
// Keep list building thread-safe
|
|
// This lock was moved to to fix bug https://bugbait.valvesoftware.com/show_bug.cgi?id=53403
|
|
// Crash in CFrameSnapshotManager::GetPackedEntity where a CBaseClient's m_pBaseline snapshot could be removed the CReferencedSnapshotList destructor
|
|
// for another client that is in WriteTempEntities
|
|
//
|
|
AUTO_LOCK_FM( framesnapshotmanager->m_FrameSnapshotsWriteMutex );
|
|
|
|
Assert( m_nReferences > 0 );
|
|
|
|
--m_nReferences;
|
|
if ( m_nReferences == 0 )
|
|
{
|
|
g_FrameSnapshotManager.DeleteFrameSnapshot( this );
|
|
}
|
|
}
|
|
|