summaryrefslogtreecommitdiff
path: root/engine/sv_framesnapshot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/sv_framesnapshot.cpp')
-rw-r--r--engine/sv_framesnapshot.cpp508
1 files changed, 508 insertions, 0 deletions
diff --git a/engine/sv_framesnapshot.cpp b/engine/sv_framesnapshot.cpp
new file mode 100644
index 0000000..3e9f37c
--- /dev/null
+++ b/engine/sv_framesnapshot.cpp
@@ -0,0 +1,508 @@
+//========= Copyright 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 );
+ Q_memset( m_pPackedData, 0x00, MAX_EDICTS * sizeof(PackedEntityHandle_t) );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFrameSnapshotManager::~CFrameSnapshotManager( void )
+{
+ AssertMsg1( m_FrameSnapshots.Count() == 0 || IsInErrorExit(), "Expected m_FrameSnapshots to be empty. It had %i items.", m_FrameSnapshots.Count() );
+
+ // TODO: This assert has been failing. HenryG says it's a valid assert and that we're probably leaking memory.
+ AssertMsg1( m_PackedEntitiesPool.Count() == 0 || IsInErrorExit(), "Expected m_PackedEntitiesPool to be empty. It had %i items.", m_PackedEntitiesPool.Count() );
+}
+
+//-----------------------------------------------------------------------------
+// Called when a level change happens
+//-----------------------------------------------------------------------------
+
+void CFrameSnapshotManager::LevelChanged()
+{
+ // Clear all lists...
+ Assert( m_FrameSnapshots.Count() == 0 );
+
+ // Release the most recent snapshot...
+ m_PackedEntityCache.RemoveAll();
+ COMPILE_TIME_ASSERT( INVALID_PACKED_ENTITY_HANDLE == 0 );
+ Q_memset( m_pPackedData, 0x00, MAX_EDICTS * sizeof(PackedEntityHandle_t) );
+}
+
+CFrameSnapshot* CFrameSnapshotManager::NextSnapshot( const 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( int tickcount, int maxEntities )
+{
+ CFrameSnapshot *snap = new CFrameSnapshot;
+ snap->AddReference();
+ snap->m_nTickCount = tickcount;
+ snap->m_nNumEntities = maxEntities;
+ snap->m_nValidEntities = 0;
+ snap->m_pValidEntities = NULL;
+ snap->m_pHLTVEntityData = NULL;
+ snap->m_pReplayEntityData = NULL;
+ 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++;
+ }
+
+ snap->m_ListIndex = m_FrameSnapshots.AddToTail( snap );
+ return snap;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : framenumber -
+//-----------------------------------------------------------------------------
+CFrameSnapshot* CFrameSnapshotManager::TakeTickSnapshot( int tickcount )
+{
+ unsigned short nValidEntities[MAX_EDICTS];
+
+ CFrameSnapshot *snap = CreateEmptySnapshot( tickcount, sv.num_edicts );
+
+ 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++;
+
+ 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 ( hltv && hltv->IsActive() )
+ {
+ 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 = reinterpret_cast< PackedEntity * >( handle );
+
+ if ( --packedEntity->m_ReferenceCount <= 0)
+ {
+ AUTO_LOCK( m_WriteMutex );
+
+ 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 );
+ reinterpret_cast< PackedEntity * >( handle )->m_ReferenceCount++;
+}
+
+void CFrameSnapshotManager::AddExplicitDelete( int iSlot )
+{
+ AUTO_LOCK( 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 = reinterpret_cast< PackedEntity * >( 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_pPackedData[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;
+ reinterpret_cast< PackedEntity * >( handle )->m_ReferenceCount++;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return false;
+}
+
+
+PackedEntity* CFrameSnapshotManager::GetPreviouslySentPacket( int iEntity, int iSerialNumber )
+{
+ PackedEntityHandle_t handle = m_pPackedData[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 reinterpret_cast< PackedEntity * >( handle );
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+CThreadFastMutex &CFrameSnapshotManager::GetMutex()
+{
+ return m_WriteMutex;
+}
+
+//-----------------------------------------------------------------------------
+// Returns the pack data for a particular entity for a particular snapshot
+//-----------------------------------------------------------------------------
+
+PackedEntity* CFrameSnapshotManager::CreatePackedEntity( CFrameSnapshot* pSnapshot, int entity )
+{
+ m_WriteMutex.Lock();
+ PackedEntity *packedEntity = m_PackedEntitiesPool.Alloc();
+ PackedEntityHandle_t handle = reinterpret_cast< PackedEntityHandle_t >( packedEntity );
+ m_WriteMutex.Unlock();
+
+ Assert( entity < pSnapshot->m_nNumEntities );
+
+ // Referenced twice: in the mru
+ packedEntity->m_ReferenceCount = 2;
+ packedEntity->m_nEntityIndex = entity;
+ pSnapshot->m_pEntities[entity].m_pPackedData = handle;
+
+ // 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_pPackedData[entity] != INVALID_PACKED_ENTITY_HANDLE )
+ {
+ RemoveEntityReference( m_pPackedData[entity] );
+ }
+
+ m_pPackedData[entity] = handle;
+ m_pSerialNumber[entity] = pSnapshot->m_pEntities[entity].m_nSerialNumber;
+
+ packedEntity->SetSnapshotCreationTick( pSnapshot->m_nTickCount );
+
+ return packedEntity;
+}
+
+//-----------------------------------------------------------------------------
+// Returns the pack data for a particular entity for a particular snapshot
+//-----------------------------------------------------------------------------
+
+PackedEntity* CFrameSnapshotManager::GetPackedEntity( CFrameSnapshot* pSnapshot, int entity )
+{
+ if ( !pSnapshot )
+ return NULL;
+
+ Assert( entity < pSnapshot->m_nNumEntities );
+
+ PackedEntityHandle_t index = pSnapshot->m_pEntities[entity].m_pPackedData;
+
+ if ( index == INVALID_PACKED_ENTITY_HANDLE )
+ return NULL;
+
+ PackedEntity *packedEntity = reinterpret_cast< PackedEntity * >( index );
+ Assert( packedEntity->m_nEntityIndex == entity );
+ 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;
+}
+
+
+
+
+// ------------------------------------------------------------------------------------------------ //
+// 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
+}
+
+
+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 ( m_pReplayEntityData )
+ {
+ delete [] m_pReplayEntityData;
+ }
+ 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()
+{
+ Assert( m_nReferences > 0 );
+
+ --m_nReferences;
+ if ( m_nReferences == 0 )
+ {
+ g_FrameSnapshotManager.DeleteFrameSnapshot( this );
+ }
+}
+
+CFrameSnapshot* CFrameSnapshot::NextSnapshot() const
+{
+ return g_FrameSnapshotManager.NextSnapshot( this );
+}
+
+