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.
309 lines
9.7 KiB
309 lines
9.7 KiB
//========= 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 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------------------
|
|
|