initial
This commit is contained in:
532
engine/replayhistorymanager.cpp
Normal file
532
engine/replayhistorymanager.cpp
Normal file
@@ -0,0 +1,532 @@
|
||||
//========= Copyright (c) 1996-2009, Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
//=======================================================================================//
|
||||
|
||||
#if defined( REPLAY_ENABLED )
|
||||
#include "replayhistorymanager.h"
|
||||
#include "client.h"
|
||||
#include "net_chan.h"
|
||||
#include "dmxloader/dmxelement.h"
|
||||
#include <time.h>
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
#define REPLAY_HISTORY_FILE_CLIENT "client_replay_history.dmx"
|
||||
#define REPLAY_HISTORY_FILE_SERVER "server_replay_history.dmx"
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
void CClientReplayHistoryEntryData::BeginDownload()
|
||||
{
|
||||
// Request the .dem file from the server
|
||||
GetBaseLocalClient().m_NetChannel->RequestFile( m_szFilename, true );
|
||||
|
||||
m_bTransferring = true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
BEGIN_DMXELEMENT_UNPACK( CBaseReplayHistoryEntryData )
|
||||
DMXELEMENT_UNPACK_FIELD_STRING( "filename", "NONE", m_szFilename )
|
||||
DMXELEMENT_UNPACK_FIELD_STRING( "map" , "NONE", m_szMapName )
|
||||
|
||||
DMXELEMENT_UNPACK_FIELD( "lifespan" , "0", int , m_nLifeSpan )
|
||||
DMXELEMENT_UNPACK_FIELD( "demo_length", "0", DmeTime_t, m_DemoLength )
|
||||
DMXELEMENT_UNPACK_FIELD( "transferred", "0", int , m_nBytesTransferred )
|
||||
DMXELEMENT_UNPACK_FIELD( "size" , "0", int , m_nSize )
|
||||
DMXELEMENT_UNPACK_FIELD( "transferid" , "0", int , m_nTransferId )
|
||||
DMXELEMENT_UNPACK_FIELD( "complete" , "0", bool , m_bTransferComplete )
|
||||
DMXELEMENT_UNPACK_FIELD( "downloading", "0", bool , m_bTransferring )
|
||||
END_DMXELEMENT_UNPACK( CBaseReplayHistoryEntryData, s_ClientEntryDataUnpack )
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
template< class T >
|
||||
class CBaseReplayHistoryManager : public IReplayHistoryManager
|
||||
{
|
||||
public:
|
||||
CBaseReplayHistoryManager()
|
||||
: m_bInit( false )
|
||||
{
|
||||
}
|
||||
|
||||
virtual void Init()
|
||||
{
|
||||
// Load all entries from disk
|
||||
if ( !LoadEntriesFromDisk() )
|
||||
{
|
||||
Warning( "Replay history file %s not found.\n", GetCacheFilename() );
|
||||
}
|
||||
|
||||
m_bInit = true;
|
||||
}
|
||||
|
||||
virtual bool IsInitialized() const { return m_bInit; }
|
||||
|
||||
virtual void Shutdown()
|
||||
{
|
||||
m_bInit = false;
|
||||
m_lstEntries.PurgeAndDeleteElements();
|
||||
}
|
||||
|
||||
virtual int GetNumEntries() const
|
||||
{
|
||||
return m_lstEntries.Count();
|
||||
}
|
||||
|
||||
virtual const CBaseReplayHistoryEntryData *GetEntryAtIndex( int iIndex ) const
|
||||
{
|
||||
Assert( iIndex >= 0 && iIndex < GetNumEntries() );
|
||||
return static_cast< CBaseReplayHistoryEntryData *>( m_lstEntries[ iIndex ] );
|
||||
}
|
||||
|
||||
virtual CBaseReplayHistoryEntryData *FindEntry( const char *pFilename )
|
||||
{
|
||||
FOR_EACH_LL( m_lstEntries, i )
|
||||
{
|
||||
if ( !V_stricmp( pFilename, m_lstEntries[ i ]->m_szFilename ) )
|
||||
{
|
||||
return static_cast< T *>( m_lstEntries[ i ] );
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
virtual void FlushEntriesToDisk()
|
||||
{
|
||||
Assert( m_bInit );
|
||||
|
||||
DECLARE_DMX_CONTEXT();
|
||||
|
||||
CDmxElement* pEntries = CreateDmxElement( "Entries" );
|
||||
CDmxElementModifyScope modify( pEntries );
|
||||
|
||||
int const nNumDemos = m_lstEntries.Count();
|
||||
pEntries->SetValue( "num_demos", nNumDemos );
|
||||
|
||||
CDmxAttribute* pDemoEntriesAttr = pEntries->AddAttribute( "demos" );
|
||||
CUtlVector< CDmxElement* >& entries = pDemoEntriesAttr->GetArrayForEdit< CDmxElement* >();
|
||||
|
||||
modify.Release();
|
||||
|
||||
FOR_EACH_LL( m_lstEntries, i )
|
||||
{
|
||||
T *pEntryData = m_lstEntries[ i ];
|
||||
|
||||
CDmxElement* pEntryElement = CreateDmxElement( "demo" );
|
||||
entries.AddToTail( pEntryElement );
|
||||
|
||||
CDmxElementModifyScope modifyClass( pEntryElement );
|
||||
pEntryElement->AddAttributesFromStructure( pEntryData, s_ClientEntryDataUnpack );
|
||||
|
||||
pEntryElement->SetValue( "record_time", pEntryData->m_nRecordTime );
|
||||
|
||||
RecordAdditionalEntryData( pEntryData, pEntryElement );
|
||||
}
|
||||
|
||||
{
|
||||
MEM_ALLOC_CREDIT();
|
||||
const char *pFilename = GetCacheFilename();
|
||||
if ( !SerializeDMX( pFilename, "GAME", false, pEntries ) )
|
||||
{
|
||||
Warning( "Replay: Failed to write ragdoll cache, %s.\n", pFilename );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CleanupDMX( pEntries );
|
||||
}
|
||||
|
||||
bool LoadEntriesFromDisk()
|
||||
{
|
||||
Assert( !m_bInit );
|
||||
|
||||
const char* pFilename = GetCacheFilename();
|
||||
|
||||
DECLARE_DMX_CONTEXT();
|
||||
|
||||
// Attempt to read from disk
|
||||
CDmxElement* pDemos = NULL;
|
||||
if ( !UnserializeDMX( pFilename, "GAME", false, &pDemos ) )
|
||||
return false;
|
||||
|
||||
CUtlVector< CDmxElement* > const& demos = pDemos->GetArray< CDmxElement* >( "demos" );
|
||||
for ( int i = 0; i < demos.Count(); ++i )
|
||||
{
|
||||
CDmxElement* pCurDemoInput = demos[ i ];
|
||||
|
||||
// Create a new ragdoll entry and add to list
|
||||
T *pNewEntry = new T();
|
||||
m_lstEntries.AddToTail( pNewEntry );
|
||||
|
||||
// Read
|
||||
pCurDemoInput->UnpackIntoStructure( pNewEntry, s_ClientEntryDataUnpack );
|
||||
|
||||
// This should always be false
|
||||
pNewEntry->m_bTransferring = false;
|
||||
|
||||
// Load record time
|
||||
pNewEntry->m_nRecordTime = pCurDemoInput->GetValue( "record_time", 0 );
|
||||
|
||||
LoadAdditionalEntryData( pNewEntry, pCurDemoInput );
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
CleanupDMX( pDemos );
|
||||
|
||||
PostLoadEntries();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void StopDownloads()
|
||||
{
|
||||
FOR_EACH_LL( m_lstEntries, i )
|
||||
{
|
||||
m_lstEntries[ i ]->m_bTransferring = false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char *GetCacheFilename() const = 0;
|
||||
|
||||
protected:
|
||||
//
|
||||
// Called from FlushEntriesToDisk() for each entry - opportunity to record additional data
|
||||
//
|
||||
virtual void RecordAdditionalEntryData( const CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement ) {}
|
||||
|
||||
//
|
||||
// Called from LoadEntriesFromDisk() for each entry - opportunity to load additional data
|
||||
//
|
||||
virtual void LoadAdditionalEntryData( CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement ) {}
|
||||
|
||||
//
|
||||
// Called at the end of LoadEntriesFromDisk()
|
||||
//
|
||||
virtual void PostLoadEntries() {}
|
||||
|
||||
virtual void Update() {}
|
||||
|
||||
CUtlLinkedList< T* > m_lstEntries;
|
||||
|
||||
private:
|
||||
bool m_bInit;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CON_COMMAND_F( replay_add_test_client_history_entry, "Add a test entry to the replay client history manager", 0 )
|
||||
{
|
||||
// Record in client history
|
||||
extern ConVar replay_demolifespan;
|
||||
CClientReplayHistoryEntryData *pNewEntry = new CClientReplayHistoryEntryData();
|
||||
if ( !pNewEntry )
|
||||
return;
|
||||
tm now;
|
||||
Plat_GetLocalTime( &now );
|
||||
time_t now_time_t = mktime( &now );
|
||||
pNewEntry->m_nRecordTime = static_cast< int >( now_time_t );
|
||||
pNewEntry->m_nLifeSpan = replay_demolifespan.GetInt() * 24 * 3600;
|
||||
pNewEntry->m_DemoLength.SetSeconds( 0 );
|
||||
V_strcpy( pNewEntry->m_szFilename, "test_filename.dem" );
|
||||
V_strcpy( pNewEntry->m_szMapName, "mapname" );
|
||||
V_strcpy( pNewEntry->m_szServerAddress, "192.168.0.1" );
|
||||
pNewEntry->m_nBytesTransferred = 0;
|
||||
pNewEntry->m_bTransferComplete = false;
|
||||
pNewEntry->m_nSize = atoi( args[3] );
|
||||
pNewEntry->m_bTransferring = false;
|
||||
pNewEntry->m_nTransferId = -1;
|
||||
if ( !g_pClientReplayHistoryManager->RecordEntry( pNewEntry ) )
|
||||
{
|
||||
Warning( "Replay: Failed to record entry.\n" );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
class CClientReplayHistoryManager : public CBaseReplayHistoryManager< CClientReplayHistoryEntryData >
|
||||
{
|
||||
public:
|
||||
virtual const char *GetCacheFilename() const { return REPLAY_HISTORY_FILE_CLIENT; }
|
||||
|
||||
virtual void Update()
|
||||
{
|
||||
if ( !GetBaseLocalClient().m_NetChannel )
|
||||
return;
|
||||
|
||||
FOR_EACH_LL( m_lstEntries, i )
|
||||
{
|
||||
CClientReplayHistoryEntryData *pEntry = m_lstEntries[ i ];
|
||||
if ( !pEntry->m_bTransferComplete )
|
||||
{
|
||||
GetBaseLocalClient().m_NetChannel->GetStreamProgress( FLOW_INCOMING, &pEntry->m_nBytesTransferred, &pEntry->m_nSize );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool RecordEntry( CBaseReplayHistoryEntryData *pNewEntry )
|
||||
{
|
||||
if ( !IsInitialized() || !pNewEntry )
|
||||
return false;
|
||||
|
||||
m_lstEntries.AddToTail( static_cast< CClientReplayHistoryEntryData * >( pNewEntry ) );
|
||||
|
||||
// Write all entries to disk now, just to be safe
|
||||
FlushEntriesToDisk();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void RecordAdditionalEntryData( const CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement )
|
||||
{
|
||||
const CClientReplayHistoryEntryData *pClientEntry = static_cast< const CClientReplayHistoryEntryData *>( pEntry );
|
||||
pElement->SetValue( "server", pClientEntry->m_szServerAddress );
|
||||
}
|
||||
|
||||
virtual void LoadAdditionalEntryData( CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement )
|
||||
{
|
||||
CClientReplayHistoryEntryData *pClientEntry = static_cast< CClientReplayHistoryEntryData *>( pEntry );
|
||||
V_strcpy( pClientEntry->m_szServerAddress, pElement->GetValueString( "server" ) );
|
||||
}
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
class CServerReplayHistoryManager : public CBaseReplayHistoryManager< CServerReplayHistoryEntryData >
|
||||
{
|
||||
public:
|
||||
CServerReplayHistoryManager()
|
||||
: m_flNextScheduledCleanup( 0.0f )
|
||||
{
|
||||
}
|
||||
|
||||
virtual const char *GetCacheFilename() const { return REPLAY_HISTORY_FILE_SERVER; }
|
||||
|
||||
// To be used with UpdateDemoFileEntries()
|
||||
enum EUpdateDemoFileEntryFlags
|
||||
{
|
||||
UPDATE_DELETESTALEFROMDISK = 0x1, // Delete stale demos from disk
|
||||
UPDATE_PRINTSTATS = 0x2, // Print statistics on all files
|
||||
UPDATE_SYNC = 0x4, // If the file does not exist on disk anymore, remove it from the history file
|
||||
UPDATE_REMOVEEXPIREDENTRIES = 0x8, // Remove any expired entries and flush to disk
|
||||
};
|
||||
|
||||
bool UpdateDemoFileEntries( int nFlags )
|
||||
{
|
||||
bool bRemovedAny = false;
|
||||
bool bFlushToDisk = false;
|
||||
|
||||
time_t now = time( NULL );
|
||||
|
||||
if ( nFlags & UPDATE_PRINTSTATS )
|
||||
{
|
||||
Msg( "\nReplay history stats\n" );
|
||||
Msg( "----------------------------------------------------\n" );
|
||||
}
|
||||
|
||||
int i = m_lstEntries.Head();
|
||||
while ( i != m_lstEntries.InvalidIndex() )
|
||||
{
|
||||
CServerReplayHistoryEntryData *pEntry = static_cast< CServerReplayHistoryEntryData *>( m_lstEntries[ i ] );
|
||||
|
||||
time_t recordtime = static_cast< time_t >( pEntry->m_nRecordTime + pEntry->m_nLifeSpan );
|
||||
double delta = difftime( recordtime, now );
|
||||
|
||||
// If the file is no longer on disk and it should be
|
||||
if ( ( nFlags & UPDATE_SYNC ) &&
|
||||
( pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXISTS ) &&
|
||||
!g_pFullFileSystem->FileExists( pEntry->m_szFilename ) )
|
||||
{
|
||||
pEntry->m_nFileStatus = CServerReplayHistoryEntryData::FILESTATUS_NOTONDISK;
|
||||
bFlushToDisk = true;
|
||||
}
|
||||
|
||||
// Stale demo file?
|
||||
bool bStale = false;
|
||||
if ( pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXISTS && delta <= 0 )
|
||||
{
|
||||
bRemovedAny = true;
|
||||
bStale = true;
|
||||
|
||||
// Delete the file from disk
|
||||
if ( g_pFullFileSystem->FileExists( pEntry->m_szFilename ) )
|
||||
{
|
||||
if ( nFlags & UPDATE_DELETESTALEFROMDISK )
|
||||
{
|
||||
Assert( 0 ); // Just making sure this gets hit...
|
||||
|
||||
Msg( "Replay: Removing stale demo \"%s\" from disk.\n", pEntry->m_szFilename );
|
||||
|
||||
// Remove the file from disk
|
||||
g_pFullFileSystem->RemoveFile( pEntry->m_szFilename );
|
||||
|
||||
// Mark as deleted
|
||||
pEntry->m_nFileStatus = CServerReplayHistoryEntryData::FILESTATUS_EXPIRED;
|
||||
|
||||
bFlushToDisk = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print stats if necessary
|
||||
if ( nFlags & UPDATE_PRINTSTATS )
|
||||
{
|
||||
static const int nSecsPerDay = 86400;
|
||||
int nDays = (int)delta / nSecsPerDay;
|
||||
int nHours = (int)delta % nSecsPerDay / 3600;
|
||||
int nMins = (int)delta % 60;
|
||||
Msg( "Demo \"%s\" ", pEntry->m_szFilename );
|
||||
if ( pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXPIRED )
|
||||
{
|
||||
Msg( "expired and was removed from disk.\n" );
|
||||
}
|
||||
else if ( pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXISTS )
|
||||
{
|
||||
Msg( "expires in %i days, %i hours, %i mins.\n", nDays, nHours, nMins );
|
||||
}
|
||||
else
|
||||
{
|
||||
Msg( "not found on disk.\n" );
|
||||
}
|
||||
}
|
||||
|
||||
int itCurrent = i;
|
||||
|
||||
// Update iterator before we do any syncing
|
||||
i = m_lstEntries.Next( i );
|
||||
|
||||
// Sync what's in memory with what's actually on disk
|
||||
if ( ( nFlags & UPDATE_REMOVEEXPIREDENTRIES ) &&
|
||||
pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXPIRED )
|
||||
{
|
||||
// Remove the element
|
||||
m_lstEntries.Remove( itCurrent );
|
||||
|
||||
AssertValidReadWritePtr( pEntry ); // TODO: Make sure this test fails!
|
||||
|
||||
bFlushToDisk = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush?
|
||||
if ( bFlushToDisk )
|
||||
{
|
||||
FlushEntriesToDisk();
|
||||
}
|
||||
|
||||
if ( nFlags & UPDATE_PRINTSTATS )
|
||||
{
|
||||
Msg( "\n" );
|
||||
}
|
||||
|
||||
return bRemovedAny;
|
||||
}
|
||||
|
||||
virtual void Update()
|
||||
{
|
||||
if ( host_time < m_flNextScheduledCleanup )
|
||||
return;
|
||||
|
||||
extern ConVar replay_cleanup_time;
|
||||
m_flNextScheduledCleanup += replay_cleanup_time.GetInt() * 3600;
|
||||
|
||||
UpdateDemoFileEntries( UPDATE_SYNC | UPDATE_DELETESTALEFROMDISK );
|
||||
}
|
||||
|
||||
virtual bool RecordEntry( CBaseReplayHistoryEntryData *pNewEntry )
|
||||
{
|
||||
if ( !IsInitialized() || !pNewEntry )
|
||||
return false;
|
||||
|
||||
m_lstEntries.AddToTail( static_cast< CServerReplayHistoryEntryData * >( pNewEntry ) );
|
||||
|
||||
// Write all entries to disk now, just to be safe
|
||||
FlushEntriesToDisk();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void RecordAdditionalEntryData( const CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement )
|
||||
{
|
||||
const CServerReplayHistoryEntryData *pServerEntry = static_cast< const CServerReplayHistoryEntryData *>( pEntry );
|
||||
pElement->SetValue( "client_steam_id", pServerEntry->m_uClientSteamId );
|
||||
pElement->SetValue( "file_status", (int)pServerEntry->m_nFileStatus );
|
||||
}
|
||||
|
||||
virtual void LoadAdditionalEntryData( CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement )
|
||||
{
|
||||
CServerReplayHistoryEntryData *pServerEntry = static_cast< CServerReplayHistoryEntryData *>( pEntry );
|
||||
pServerEntry->m_uClientSteamId = pElement->GetValue( "client_steam_id", (uint64)0 );
|
||||
pServerEntry->m_nFileStatus = (CServerReplayHistoryEntryData::EFileStatus)pElement->GetValue< int >( "file_status", (int)CServerReplayHistoryEntryData::FILESTATUS_EXISTS );
|
||||
}
|
||||
|
||||
private:
|
||||
float m_flNextScheduledCleanup;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
inline CServerReplayHistoryManager *GetServerReplayHistoryManager()
|
||||
{
|
||||
return static_cast< CServerReplayHistoryManager * >( g_pServerReplayHistoryManager );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CON_COMMAND_F( replay_delete_stale_demos, "Deletes stale replay demo files", FCVAR_GAMEDLL | FCVAR_DONTRECORD )
|
||||
{
|
||||
if ( GetServerReplayHistoryManager() &&
|
||||
!GetServerReplayHistoryManager()->UpdateDemoFileEntries( CServerReplayHistoryManager::UPDATE_DELETESTALEFROMDISK ) )
|
||||
{
|
||||
Msg( "No demos were deleted.\n" );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CON_COMMAND_F( replay_print_history_stats, "Deletes stale replay demo files", FCVAR_GAMEDLL | FCVAR_DONTRECORD )
|
||||
{
|
||||
if ( GetServerReplayHistoryManager() )
|
||||
{
|
||||
GetServerReplayHistoryManager()->UpdateDemoFileEntries( CServerReplayHistoryManager::UPDATE_PRINTSTATS );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CON_COMMAND_F( replay_remove_expired_entries, "Removes all expired entries from replay history", FCVAR_GAMEDLL | FCVAR_DONTRECORD )
|
||||
{
|
||||
if ( GetServerReplayHistoryManager() )
|
||||
{
|
||||
GetServerReplayHistoryManager()->UpdateDemoFileEntries( CServerReplayHistoryManager::UPDATE_REMOVEEXPIREDENTRIES );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
IReplayHistoryManager *CreateServerReplayHistoryManager()
|
||||
{
|
||||
return new CServerReplayHistoryManager();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
static CClientReplayHistoryManager s_ClientReplayHistoryManager;
|
||||
IReplayHistoryManager *g_pClientReplayHistoryManager = &s_ClientReplayHistoryManager;
|
||||
IReplayHistoryManager *g_pServerReplayHistoryManager = NULL;
|
||||
|
||||
// Expose interface to the client (needed by demo browser) - no need to do this for the server.
|
||||
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(
|
||||
CClientReplayHistoryManager,
|
||||
IReplayHistoryManager,
|
||||
REPLAYHISTORYMANAGER_INTERFACE_VERSION,
|
||||
s_ClientReplayHistoryManager
|
||||
);
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user