Team Fortress 2 Source Code as on 22/4/2020
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.
 
 
 
 
 
 

326 lines
8.9 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
//=======================================================================================//
#include "sv_replaycontext.h"
#include "replay/shared_defs.h" // BUILD_CURL defined here
#include "sv_sessionrecorder.h"
#include "sv_fileservercleanup.h"
#include "sv_recordingsession.h"
#include "sv_publishtest.h"
#include "replaysystem.h"
#include "icommandline.h"
#if BUILD_CURL
#include "curl/curl.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//----------------------------------------------------------------------------------------
#undef CreateEvent
//----------------------------------------------------------------------------------------
CServerReplayContext::CServerReplayContext()
: m_pSessionRecorder( NULL ),
m_pFileserverCleaner( NULL ),
m_bShouldAbortRecording( false ),
m_flConVarSanityCheckTime( 0.0f )
{
}
CServerReplayContext::~CServerReplayContext()
{
delete m_pSessionRecorder;
delete m_pFileserverCleaner;
}
bool CServerReplayContext::Init( CreateInterfaceFn fnFactory )
{
#if BUILD_CURL
// Initialize cURL - in windows, this will init the winsock stuff.
curl_global_init( CURL_GLOBAL_ALL );
#endif
m_pShared = new CSharedReplayContext( this );
m_pShared->m_strSubDir = GetServerSubDirName();
m_pShared->m_pRecordingSessionManager = new CServerRecordingSessionManager( this );
m_pShared->m_pRecordingSessionBlockManager = new CServerRecordingSessionBlockManager( this );
m_pShared->m_pErrorSystem = new CErrorSystem( this );
m_pShared->Init( fnFactory );
// Create directory for temp files
CFmtStr fmtTmpDir( "%s%s", SV_GetBasePath(), SUBDIR_TMP );
g_pFullFileSystem->CreateDirHierarchy( fmtTmpDir.Access() );
// Remove any extraneous files from the temp directory
CleanTmpDir();
m_pSessionRecorder = new CSessionRecorder();
m_pSessionRecorder->Init();
m_pFileserverCleaner = new CFileserverCleaner();
return true;
}
void CServerReplayContext::CleanTmpDir()
{
int nFilesRemoved = 0;
Log( "Cleaning files from temp dir, \"%s\" ...", SV_GetTmpDir() );
FileFindHandle_t hFind;
CFmtStr fmtPath( "%s*", SV_GetTmpDir() );
const char *pFilename = g_pFullFileSystem->FindFirst( fmtPath.Access(), &hFind );
while ( pFilename )
{
if ( pFilename[0] != '.' )
{
// Remove the file
CFmtStr fmtFullFilename( "%s%s", SV_GetTmpDir(), pFilename );
g_pFullFileSystem->RemoveFile( fmtFullFilename.Access() );
++nFilesRemoved;
}
// Get next file
pFilename = g_pFullFileSystem->FindNext( hFind );
}
if ( nFilesRemoved )
{
Log( "%i %s removed.\n", nFilesRemoved, nFilesRemoved == 1 ? "file" : "files" );
}
else
{
Log( "no files removed.\n" );
}
}
void CServerReplayContext::Shutdown()
{
m_pShared->Shutdown();
#if BUILD_CURL
// Shutdown cURL
curl_global_cleanup();
#endif
}
void CServerReplayContext::Think()
{
ConVarSanityThink();
if ( !g_pReplay->IsReplayEnabled() )
return;
if ( m_bShouldAbortRecording )
{
g_pBlockSpewer->PrintBlockStart();
g_pBlockSpewer->PrintMsg( "Replay recording shutting down due to publishing error! Recording will begin" );
g_pBlockSpewer->PrintMsg( "at the beginning of the next round, but may fail again." );
g_pBlockSpewer->PrintBlockEnd();
// Shutdown recording
m_pSessionRecorder->AbortCurrentSessionRecording();
m_bShouldAbortRecording = false;
}
m_pShared->Think();
}
void CServerReplayContext::ConVarSanityThink()
{
if ( m_flConVarSanityCheckTime == 0.0f )
return;
DoSanityCheckNow();
}
void CServerReplayContext::UpdateFileserverIPFromHostname( const char *pHostname )
{
if ( !g_pEngine->NET_GetHostnameAsIP( pHostname, m_szFileserverIP, sizeof( m_szFileserverIP ) ) )
{
V_strcpy( m_szFileserverIP, "0.0.0.0" );
Log( "ERROR: Could not resolve fileserver hostname \"%s\" !\n", pHostname );
return;
}
Log( "Cached resolved fileserver hostname to IP address: \"%s\" -> \"%s\"\n", pHostname, m_szFileserverIP );
}
void CServerReplayContext::UpdateFileserverProxyIPFromHostname( const char *pHostname )
{
if ( !g_pEngine->NET_GetHostnameAsIP( pHostname, m_szFileserverProxyIP, sizeof( m_szFileserverProxyIP ) ) )
{
V_strcpy( m_szFileserverProxyIP, "0.0.0.0" );
Log( "ERROR: Could not resolve fileserver proxy hostname \"%s\" !\n", pHostname );
return;
}
Log( "Cached resolved fileserver proxy hostname to IP address: \"%s\" -> \"%s\"\n", pHostname, m_szFileserverProxyIP );
}
void CServerReplayContext::DoSanityCheckNow()
{
// Check now?
if ( m_flConVarSanityCheckTime <= g_pEngine->GetHostTime() )
{
// Reset
m_flConVarSanityCheckTime = 0.0f;
g_pBlockSpewer->PrintBlockStart();
extern ConVar replay_enable;
if ( replay_enable.GetBool() )
{
// Test publish
const bool bPublishResult = SV_DoTestPublish();
g_pBlockSpewer->PrintEmptyLine();
if ( bPublishResult )
{
g_pBlockSpewer->PrintEmptyLine();
g_pBlockSpewer->PrintEmptyLine();
g_pBlockSpewer->PrintMsg( "SUCCESS - REPLAY IS ENABLED!" );
g_pBlockSpewer->PrintEmptyLine();
g_pBlockSpewer->PrintMsg( "A 'changelevel' or 'map' is required - recording will" );
g_pBlockSpewer->PrintMsg( "begin at the start of the next round." );
g_pBlockSpewer->PrintEmptyLine();
}
else
{
replay_enable.SetValue( 0 );
g_pBlockSpewer->PrintEmptyLine();
g_pBlockSpewer->PrintMsg( "FAILURE - REPLAY DISABLED! \"replay_enable\" is now 0." );
g_pBlockSpewer->PrintEmptyLine();
g_pBlockSpewer->PrintEmptyLine();
g_pBlockSpewer->PrintMsg( "Address any failures above and re-exec replay.cfg." );
}
}
g_pBlockSpewer->PrintBlockEnd();
}
}
void CServerReplayContext::FlagForConVarSanityCheck()
{
m_flConVarSanityCheckTime = g_pEngine->GetHostTime() + 0.2f;
}
IGameEvent *CServerReplayContext::CreateReplaySessionInfoEvent()
{
IGameEvent *pEvent = g_pGameEventManager->CreateEvent( "replay_sessioninfo", true );
if ( !pEvent )
return NULL;
extern ConVar replay_block_dump_interval;
// Fill event
pEvent->SetString( "sn", m_pShared->m_pRecordingSessionManager->GetCurrentSessionName() );
pEvent->SetInt( "di", replay_block_dump_interval.GetInt() );
pEvent->SetInt( "cb", m_pShared->m_pRecordingSessionManager->GetCurrentSessionBlockIndex() );
pEvent->SetInt( "st", m_pSessionRecorder->GetCurrentRecordingStartTick() );
return pEvent;
}
IReplaySessionRecorder *CServerReplayContext::GetSessionRecorder()
{
return g_pServerReplayContext->m_pSessionRecorder;
}
const char *CServerReplayContext::GetLocalFileServerPath() const
{
static char s_szBuf[MAX_OSPATH];
extern ConVar replay_local_fileserver_path;
// Fix up the path name - NOTE: We intentionally avoid calling V_FixupPathName(), which
// pushes the entire output string to lower case.
V_strncpy( s_szBuf, replay_local_fileserver_path.GetString(), sizeof( s_szBuf ) );
V_FixSlashes( s_szBuf );
V_RemoveDotSlashes( s_szBuf );
V_FixDoubleSlashes( s_szBuf );
V_StripTrailingSlash( s_szBuf );
V_AppendSlash( s_szBuf, sizeof( s_szBuf ) );
return s_szBuf;
}
void CServerReplayContext::CreateSessionOnClient( int nClientSlot )
{
// If we have a session (i.e. if we're recording)
if ( SV_GetRecordingSessionInProgress() )
{
// Create the session on the client
IGameEvent *pSessionInfoEvent = CreateReplaySessionInfoEvent();
g_pReplay->SV_SendReplayEvent( pSessionInfoEvent, nClientSlot );
}
}
const char *CServerReplayContext::GetServerSubDirName() const
{
const char *pSubDirName = CommandLine()->ParmValue( "-replayserverdir" );
if ( !pSubDirName || !pSubDirName[0] )
{
Msg( "No '-replayserverdir' parameter found - using default replay folder.\n" );
return SUBDIR_SERVER;
}
Msg( "\n** Using custom replay dir name: \"%s%c%s\"\n\n", SUBDIR_REPLAY, CORRECT_PATH_SEPARATOR, pSubDirName );
return pSubDirName;
}
void CServerReplayContext::ReportErrorsToUser( wchar_t *pErrorText )
{
char szErrorText[4096];
g_pVGuiLocalize->ConvertUnicodeToANSI( pErrorText, szErrorText, sizeof( szErrorText ) );
static Color s_clrRed( 255, 0, 0 );
Warning( "\n-----------------------------------------------\n" );
Warning( "%s", szErrorText );
Warning( "-----------------------------------------------\n\n" );
}
void CServerReplayContext::OnPublishFailed()
{
// Don't report publish failure and shutdown publishing more than once per session.
if ( !m_pSessionRecorder->RecordingAborted() )
{
m_bShouldAbortRecording = true;
}
}
//----------------------------------------------------------------------------------------
CServerRecordingSession *SV_GetRecordingSessionInProgress()
{
return SV_CastSession( SV_GetRecordingSessionManager()->GetRecordingSessionInProgress() );
}
const char *SV_GetTmpDir()
{
return Replay_va( "%s%s%c", SV_GetBasePath(), SUBDIR_TMP, CORRECT_PATH_SEPARATOR );
}
bool SV_IsOffloadingEnabled()
{
return false;
}
bool SV_RunJobToCompletion( CJob *pJob )
{
return RunJobToCompletion( SV_GetThreadPool(), pJob );
}
//----------------------------------------------------------------------------------------