summaryrefslogtreecommitdiff
path: root/replay/sv_replaycontext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'replay/sv_replaycontext.cpp')
-rw-r--r--replay/sv_replaycontext.cpp326
1 files changed, 326 insertions, 0 deletions
diff --git a/replay/sv_replaycontext.cpp b/replay/sv_replaycontext.cpp
new file mode 100644
index 0000000..8c627fe
--- /dev/null
+++ b/replay/sv_replaycontext.cpp
@@ -0,0 +1,326 @@
+//========= 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 );
+}
+
+//----------------------------------------------------------------------------------------