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.
223 lines
5.9 KiB
223 lines
5.9 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
//=======================================================================================// |
|
|
|
#include "sv_sessionrecorder.h" |
|
#include "replay/replayutils.h" |
|
#include "replay/shared_defs.h" |
|
#include "baserecordingsessionblock.h" |
|
#include "replaysystem.h" |
|
#include "baserecordingsessionblockmanager.h" |
|
#include "sv_recordingsessionmanager.h" |
|
#include "sv_replaycontext.h" |
|
#include "sv_sessionpublishmanager.h" |
|
#include "sv_recordingsession.h" |
|
#include "sv_recordingsessionblock.h" |
|
#include "fmtstr.h" |
|
#include "vprof.h" |
|
#include "iserver.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#undef CreateEvent |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
#define SERVER_REPLAY_INDEX_FILENAME ".replayindex" |
|
#define SERVER_REPLAY_ERROR_LOST "The server crashed before the replay could be finalized. Replay lost." |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
CSessionRecorder::CSessionRecorder() |
|
: m_bRecordingAborted( false ), |
|
m_nCurrentRecordingStartTick( -1 ) |
|
{ |
|
} |
|
|
|
CSessionRecorder::~CSessionRecorder() |
|
{ |
|
} |
|
|
|
bool CSessionRecorder::Init() |
|
{ |
|
g_pFullFileSystem->CreateDirHierarchy( Replay_va( "%s%s", SV_GetBasePath(), SUBDIR_SESSIONS ) ); |
|
return true; |
|
} |
|
|
|
void CSessionRecorder::AbortCurrentSessionRecording() |
|
{ |
|
StopRecording( true ); |
|
|
|
CSessionPublishManager *pCurrentPublishManager = GetCurrentPublishManager(); |
|
if ( !pCurrentPublishManager ) |
|
{ |
|
AssertMsg( 0, "Could not get current publish manager." ); |
|
return; |
|
} |
|
|
|
pCurrentPublishManager->AbortPublish(); |
|
|
|
m_bRecordingAborted = true; |
|
} |
|
|
|
void CSessionRecorder::SetCurrentRecordingStartTick( int nStartTick ) |
|
{ |
|
m_nCurrentRecordingStartTick = nStartTick; |
|
} |
|
|
|
void CSessionRecorder::PublishAllSynchronous() |
|
{ |
|
FOR_EACH_LL( m_lstPublishManagers, i ) |
|
{ |
|
m_lstPublishManagers[ i ]->PublishAllSynchronous(); |
|
} |
|
} |
|
|
|
void CSessionRecorder::StartRecording() |
|
{ |
|
m_bRecordingAborted = false; |
|
|
|
IServer *pServer = ReplayServerAsIServer(); |
|
if ( !pServer || !pServer->IsActive() ) |
|
{ |
|
ConMsg( "ERROR: Replay not active.\n" ); |
|
return; |
|
} |
|
|
|
// We only care about local fileserver path in the case that we aren't offloading files to an external sfileserver |
|
const char *pWritePath = g_pServerReplayContext->GetLocalFileServerPath(); |
|
if ( ( !pWritePath || !pWritePath[0] ) ) |
|
{ |
|
ConMsg( "\n*\n* ERROR: Failed to begin record: make sure \"replay_local_fileserver_path\" refers to a valid path!\n** replay_local_fileserver_path is currently set to: \"%s\"\n*\n\n", pWritePath ); |
|
return; |
|
} |
|
|
|
IReplayServer *pReplayServer = ReplayServer(); |
|
if ( pReplayServer->IsRecording() ) |
|
{ |
|
ConMsg( "ERROR: Replay already recording.\n" ); |
|
return; |
|
} |
|
|
|
// Tell the replay server to begin recording |
|
pReplayServer->StartRecording(); |
|
|
|
// Notify session manager |
|
CBaseRecordingSession *pSession = SV_GetRecordingSessionManager()->OnSessionStart( m_nCurrentRecordingStartTick, NULL ); |
|
|
|
// Create a new publish manager and add it. The dump interval and any additional setup is done there. |
|
CreateAndAddNewPublishManager( static_cast< CServerRecordingSession * >( pSession ) ); |
|
} |
|
|
|
void CSessionRecorder::CreateAndAddNewPublishManager( CServerRecordingSession *pSession ) |
|
{ |
|
CSessionPublishManager *pNewPublishManager = new CSessionPublishManager( pSession ); |
|
|
|
// Let the publish manager know that it is the 'current' publish manager. |
|
pNewPublishManager->OnStartRecording(); |
|
|
|
// Add to the head of the list, since the desired convention is for the list to be |
|
// sorted from newest to oldest. |
|
m_lstPublishManagers.AddToHead( pNewPublishManager ); |
|
} |
|
|
|
float CSessionRecorder::GetNextThinkTime() const |
|
{ |
|
return 0.0f; |
|
} |
|
|
|
void CSessionRecorder::Think() |
|
{ |
|
CBaseThinker::Think(); |
|
|
|
VPROF_BUDGET( "CSessionRecorder::Think", VPROF_BUDGETGROUP_REPLAY ); |
|
|
|
// This gets called even if replay is disabled. This is intentional. |
|
PublishThink(); |
|
} |
|
|
|
CSessionPublishManager *CSessionRecorder::GetCurrentPublishManager() const |
|
{ |
|
if ( !m_lstPublishManagers.Count() ) |
|
return NULL; |
|
|
|
return m_lstPublishManagers[ m_lstPublishManagers.Head() ]; |
|
} |
|
|
|
void CSessionRecorder::PublishThink() |
|
{ |
|
UpdateSessionLocks(); |
|
} |
|
|
|
void CSessionRecorder::UpdateSessionLocks() |
|
{ |
|
for ( int i = m_lstPublishManagers.Head(); i != m_lstPublishManagers.InvalidIndex(); ) |
|
{ |
|
CSessionPublishManager *pCurManager = m_lstPublishManagers[ i ]; |
|
|
|
// Cache off 'next' in case we delete the current object |
|
const int itNext = m_lstPublishManagers.Next( i ); |
|
|
|
if ( pCurManager->IsDone() ) |
|
{ |
|
#ifdef _DEBUG |
|
pCurManager->Validate(); |
|
#endif |
|
|
|
// We can unlock the associated session now. |
|
pCurManager->UnlockSession(); |
|
|
|
// Remove and delete it. |
|
m_lstPublishManagers.Remove( i ); |
|
delete pCurManager; |
|
|
|
IF_REPLAY_DBG( Warning( "\n---\n*\n* All publishing done for session. %i still publishing.\n*\n---\n", m_lstPublishManagers.Count() ) ); |
|
} |
|
else |
|
{ |
|
pCurManager->Think(); |
|
} |
|
|
|
i = itNext; |
|
} |
|
} |
|
|
|
void CSessionRecorder::StopRecording( bool bAborting ) |
|
{ |
|
#if !defined( DEDICATED ) |
|
if ( g_pEngineClient->IsPlayingReplayDemo() ) |
|
return; |
|
#endif |
|
if ( !ReplayServer() ) |
|
return; |
|
|
|
DBG( "StopRecording()\n" ); |
|
|
|
CServerRecordingSession *pSession = SV_GetRecordingSessionInProgress(); |
|
if ( pSession ) |
|
{ |
|
// Mark the session as not recording |
|
pSession->OnStopRecording(); |
|
|
|
// Get the current publish manager and notify it that recording has stopped. |
|
CSessionPublishManager *pManager = GetCurrentPublishManager(); |
|
if ( pManager ) |
|
{ |
|
pManager->OnStopRecord( bAborting ); |
|
} |
|
|
|
// Notify session manager - the session will be flagged for unload or deletion, but |
|
// will not actually be free'd until it is "unlocked" by the publish manager. |
|
SV_GetRecordingSessionManager()->OnSessionEnd(); |
|
} |
|
|
|
// Stop recording |
|
ReplayServer()->StopRecording(); |
|
|
|
// Clear replay_recording |
|
extern ConVar replay_recording; |
|
replay_recording.SetValue( 0 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------------------
|
|
|