|
|
//========= 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
|