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 );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------
|