1
This commit is contained in:
309
replay/cl_recordingsessionmanager.cpp
Normal file
309
replay/cl_recordingsessionmanager.cpp
Normal file
@ -0,0 +1,309 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
//=======================================================================================//
|
||||
|
||||
#include "cl_recordingsessionmanager.h"
|
||||
#include "replaysystem.h"
|
||||
#include "cl_replaymanager.h"
|
||||
#include "cl_recordingsession.h"
|
||||
#include "cl_sessionblockdownloader.h"
|
||||
#include "vprof.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
#define CLIENTRECORDINGSESSIONMANAGER_VERSION 0
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
CClientRecordingSessionManager::CClientRecordingSessionManager( IReplayContext *pContext )
|
||||
: CBaseRecordingSessionManager( pContext ),
|
||||
m_nNumSessionBlockDownloaders( 0 ),
|
||||
m_flNextBlockUpdateTime( 0.0f ),
|
||||
m_flNextPossibleDownloadTime( 0.0f )
|
||||
{
|
||||
}
|
||||
|
||||
CClientRecordingSessionManager::~CClientRecordingSessionManager()
|
||||
{
|
||||
}
|
||||
|
||||
bool CClientRecordingSessionManager::Init()
|
||||
{
|
||||
AddEventsForListen();
|
||||
|
||||
return BaseClass::Init();
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::CleanupUnneededBlocks()
|
||||
{
|
||||
Msg( "Cleaning up unneeded replay block data...\n" );
|
||||
FOR_EACH_OBJ( this, i )
|
||||
{
|
||||
CClientRecordingSession *pCurSession = CL_CastSession( m_vecObjs[ i ] );
|
||||
pCurSession->LoadBlocksForSession();
|
||||
pCurSession->DeleteBlocks();
|
||||
}
|
||||
Msg( "Replay cleanup done.\n" );
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::AddEventsForListen()
|
||||
{
|
||||
g_pGameEventManager->AddListener( this, "replay_endrecord", false );
|
||||
g_pGameEventManager->AddListener( this, "replay_sessioninfo", false );
|
||||
g_pGameEventManager->AddListener( this, "player_death", false );
|
||||
}
|
||||
|
||||
const char *CClientRecordingSessionManager::GetNewSessionName() const
|
||||
{
|
||||
return m_ServerRecordingState.m_strSessionName;
|
||||
}
|
||||
|
||||
CBaseRecordingSession *CClientRecordingSessionManager::OnSessionStart( int nCurrentRecordingStartTick, const char *pSessionName )
|
||||
{
|
||||
return BaseClass::OnSessionStart( nCurrentRecordingStartTick, pSessionName );
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::OnSessionEnd()
|
||||
{
|
||||
if ( m_pRecordingSession )
|
||||
{
|
||||
// Update whether all blocks have been downloaded
|
||||
AssertMsg( !m_pRecordingSession->m_bRecording, "This flag should have been cleared already! See CBaseRecordingSession::OnStopRecording()" );
|
||||
CL_CastSession( m_pRecordingSession )->UpdateAllBlocksDownloaded();
|
||||
}
|
||||
|
||||
BaseClass::OnSessionEnd();
|
||||
|
||||
m_ServerRecordingState.Clear();
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::FireGameEvent( IGameEvent *pEvent )
|
||||
{
|
||||
DBG( "CReplayHistoryManager::FireGameEvent()\n" );
|
||||
|
||||
if ( g_pEngineClient->IsDemoPlayingBack() )
|
||||
return;
|
||||
|
||||
const char *pEventName = pEvent->GetName();
|
||||
|
||||
if ( !V_stricmp( "replay_sessioninfo", pEventName ) )
|
||||
{
|
||||
DBG( " replay_sessioninfo\n" );
|
||||
|
||||
bool bDisableReplayOnClient = false;
|
||||
|
||||
const CUtlString strSessionName = pEvent->GetString( "sn" );
|
||||
if ( strSessionName.IsEmpty() )
|
||||
{
|
||||
CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_SessionInfo_BadSessionName" );
|
||||
bDisableReplayOnClient = true;
|
||||
}
|
||||
|
||||
const int nDumpInterval = pEvent->GetInt( "di", 0 );
|
||||
if ( nDumpInterval < MIN_SERVER_DUMP_INTERVAL ||
|
||||
nDumpInterval > MAX_SERVER_DUMP_INTERVAL )
|
||||
{
|
||||
CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_SessionInfo_BadDumpInterval" );
|
||||
bDisableReplayOnClient = true;
|
||||
}
|
||||
|
||||
const int nCurrentBlock = pEvent->GetInt( "cb", -1 );
|
||||
if ( nCurrentBlock < 0 )
|
||||
{
|
||||
CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_SessionInfo_BadCurrentBlock" );
|
||||
bDisableReplayOnClient = true;
|
||||
}
|
||||
|
||||
const int nStartTick = pEvent->GetInt( "st", -1 );
|
||||
if ( nStartTick < 0 )
|
||||
{
|
||||
CL_GetErrorSystem()->AddErrorFromTokenName( "Replay_Err_SessionInfo_BadStartTick" );
|
||||
bDisableReplayOnClient = true;
|
||||
}
|
||||
|
||||
// Cache session state
|
||||
m_ServerRecordingState.m_strSessionName = strSessionName;
|
||||
m_ServerRecordingState.m_nDumpInterval = nDumpInterval;
|
||||
m_ServerRecordingState.m_nCurrentBlock = nCurrentBlock + 1; // This will account for any different between when the server actually dumps a block and the client predicts a dump
|
||||
m_ServerRecordingState.m_nStartTick = nStartTick;
|
||||
|
||||
// If the server's in a weird state, disable replay on the client so they don't
|
||||
// create any replays that don't play, etc.
|
||||
g_pClientReplayContextInternal->DisableReplayOnClient( bDisableReplayOnClient );
|
||||
if ( bDisableReplayOnClient )
|
||||
return;
|
||||
|
||||
OnSessionStart( nStartTick, strSessionName.Get() );
|
||||
|
||||
CL_GetReplayManager()->OnSessionStart();
|
||||
|
||||
// Update the current block based on the dump interval passed down from
|
||||
// the server so the client stays in sync w/ the current block index.
|
||||
m_flNextBlockUpdateTime = g_pEngine->GetHostTime() + nDumpInterval;
|
||||
}
|
||||
|
||||
else if ( !V_stricmp( "replay_endrecord", pEventName ) )
|
||||
{
|
||||
DBG( " replay_stoprecord\n" );
|
||||
|
||||
// Clear pending replay URL cache, complete any pending replay
|
||||
CL_GetReplayManager()->OnSessionEnd();
|
||||
|
||||
// Notify the session - it will mark itself as no longer recording.
|
||||
if ( m_pRecordingSession )
|
||||
{
|
||||
m_pRecordingSession->OnStopRecording();
|
||||
}
|
||||
|
||||
// Resets current session pointer
|
||||
OnSessionEnd();
|
||||
}
|
||||
|
||||
// When the player dies, we fill out the rest of the data here
|
||||
else if ( !V_stricmp( "player_death", pEventName ) &&
|
||||
pEvent->GetInt( "victim_entindex" ) == g_pEngineClient->GetPlayerSlot() + 1 &&
|
||||
g_pClient->ShouldCompletePendingReplay( pEvent ) )
|
||||
{
|
||||
CL_GetReplayManager()->CompletePendingReplay();
|
||||
}
|
||||
}
|
||||
|
||||
int CClientRecordingSessionManager::GetVersion() const
|
||||
{
|
||||
return CLIENTRECORDINGSESSIONMANAGER_VERSION;
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::Think()
|
||||
{
|
||||
VPROF_BUDGET( "CClientRecordingSessionManager::Think", VPROF_BUDGETGROUP_REPLAY );
|
||||
|
||||
BaseClass::Think();
|
||||
|
||||
// Manage all session block downloads
|
||||
DownloadThink();
|
||||
|
||||
if ( !g_pReplay->IsRecording() )
|
||||
return;
|
||||
|
||||
if ( g_pEngineClient->IsDemoPlayingBack() )
|
||||
return;
|
||||
|
||||
const float flHostTime = g_pEngine->GetHostTime();
|
||||
|
||||
if ( replay_debug.GetBool() )
|
||||
{
|
||||
extern ConVar replay_postdeathrecordtime;
|
||||
g_pEngineClient->Con_NPrintf( 100, "Time until block dump: ~%f", m_flNextBlockUpdateTime - flHostTime );
|
||||
g_pEngineClient->Con_NPrintf( 101, "Post-death record time: %f", replay_postdeathrecordtime.GetFloat() );
|
||||
}
|
||||
|
||||
if ( m_flNextBlockUpdateTime <= flHostTime )
|
||||
{
|
||||
// Increment current block
|
||||
++m_ServerRecordingState.m_nCurrentBlock;
|
||||
|
||||
// NOTE: Now the number of blocks in the recording session in progress should be
|
||||
// different from the number of blocks in its list - so it should spawn a download
|
||||
// thread to grab the session info and create the new block.
|
||||
|
||||
IF_REPLAY_DBG( Warning( "# session blocks updating: %i\n", m_ServerRecordingState.m_nCurrentBlock ) );
|
||||
|
||||
// Setup next think
|
||||
m_flNextBlockUpdateTime = flHostTime + m_ServerRecordingState.m_nDumpInterval;
|
||||
}
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::DownloadThink()
|
||||
{
|
||||
bool bKickedOffDownload = false;
|
||||
const float flHostTime = g_pEngine->GetHostTime();
|
||||
|
||||
// For session in progress - check predicted # of blocks on the server based on current number
|
||||
// of blocks in our list - if different, download the session info and create any outstanding
|
||||
// blocks on the client.
|
||||
bool bEnoughTimeHasPassed = flHostTime >= m_flNextPossibleDownloadTime;
|
||||
if ( !bEnoughTimeHasPassed )
|
||||
return;
|
||||
|
||||
// Go through all sessions to see if we need to create session block downloaders
|
||||
FOR_EACH_OBJ( this, i )
|
||||
{
|
||||
CClientRecordingSession *pCurSession = CL_CastSession( m_vecObjs[ i ] );
|
||||
|
||||
// Already have a session block downloader? NOTE: The think manager calls its Think().
|
||||
if ( pCurSession->HasSessionInfoDownloader() )
|
||||
continue;
|
||||
|
||||
// If the # of blocks on the client is out of sync with the number of blocks we need
|
||||
// to eventually download, sync with the server, i.e. download the session info and
|
||||
// create blocks/sync block data as needed.
|
||||
if ( pCurSession->ShouldSyncBlocksWithServer() )
|
||||
{
|
||||
pCurSession->SyncSessionBlocks();
|
||||
bKickedOffDownload = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set next possible download time if we just kicked off a download
|
||||
if ( bKickedOffDownload )
|
||||
{
|
||||
m_flNextPossibleDownloadTime = flHostTime + MAX( MIN_SERVER_DUMP_INTERVAL, CL_GetRecordingSessionManager()->m_ServerRecordingState.m_nDumpInterval );
|
||||
}
|
||||
}
|
||||
|
||||
CBaseRecordingSession *CClientRecordingSessionManager::Create()
|
||||
{
|
||||
return new CClientRecordingSession( m_pContext );
|
||||
}
|
||||
|
||||
IReplayContext *CClientRecordingSessionManager::GetReplayContext() const
|
||||
{
|
||||
return g_pClientReplayContextInternal;
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::OnObjLoaded( CBaseRecordingSession *pSession )
|
||||
{
|
||||
// Make sure the session doesn't try to start downloading if it's done
|
||||
CL_CastSession( pSession )->UpdateAllBlocksDownloaded();
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::OnReplayDeleted( CReplay *pReplay )
|
||||
{
|
||||
// Notify the session that a replay has been deleted, in case it needs to do any cleanup.
|
||||
CClientRecordingSession *pSession = CL_CastSession( FindSession( pReplay->m_hSession ) );
|
||||
if ( pSession )
|
||||
{
|
||||
pSession->OnReplayDeleted( pReplay );
|
||||
}
|
||||
|
||||
// Get the # of replays that depend on the given session
|
||||
int nNumDependentReplays = CL_GetReplayManager()->GetNumReplaysDependentOnSession( pReplay->m_hSession );
|
||||
if ( nNumDependentReplays == 1 )
|
||||
{
|
||||
// Delete the session - remove the item from the manager itself, delete the
|
||||
// .dem file, and any .dmx.
|
||||
DeleteSession( pReplay->m_hSession, false );
|
||||
}
|
||||
}
|
||||
|
||||
void CClientRecordingSessionManager::OnReplaysLoaded()
|
||||
{
|
||||
// Cache replay pointers in sessions for quick access
|
||||
FOR_EACH_REPLAY( i )
|
||||
{
|
||||
CReplay *pCurReplay = GET_REPLAY_AT( i );
|
||||
CClientRecordingSession *pOwnerSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pCurReplay->m_hSession ) ); Assert( pOwnerSession );
|
||||
if ( !pOwnerSession )
|
||||
{
|
||||
CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Load_BadOwnerSession" );
|
||||
continue;
|
||||
}
|
||||
|
||||
pOwnerSession->CacheReplay( pCurReplay );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
Reference in New Issue
Block a user