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.
665 lines
18 KiB
665 lines
18 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
//=======================================================================================// |
|
|
|
#include "cl_replaymanager.h" |
|
#include "replay/ienginereplay.h" |
|
#include "replay/iclientreplay.h" |
|
#include "replay/ireplaymoviemanager.h" |
|
#include "replay/ireplayfactory.h" |
|
#include "replay/replayutils.h" |
|
#include "replay/ireplaymovierenderer.h" |
|
#include "replay/shared_defs.h" |
|
#include "baserecordingsession.h" |
|
#include "cl_screenshotmanager.h" |
|
#include "cl_recordingsession.h" |
|
#include "cl_recordingsessionblock.h" |
|
#include "replaysystem.h" |
|
#include "cl_replaymoviemanager.h" |
|
#include "replay_dbg.h" |
|
#include "inetchannel.h" |
|
#include "cl_replaycontext.h" |
|
#include <time.h> |
|
#include "vprof.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
extern IEngineClientReplay *g_pEngineClient; |
|
extern ConVar replay_postdeathrecordtime; |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
#define REPLAY_INDEX_VERSION 0 |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
CReplayManager::CReplayManager() |
|
: m_pPendingReplay( NULL ), |
|
m_pReplayLastLife( NULL ), |
|
m_pReplayThisLife( NULL ), |
|
m_flPlayerSpawnCreateReplayFailTime( 0.0f ) |
|
{ |
|
} |
|
|
|
CReplayManager::~CReplayManager() |
|
{ |
|
} |
|
|
|
bool CReplayManager::Init( CreateInterfaceFn fnCreateFactory ) |
|
{ |
|
// Get out if the user is running an unsupported mod or platform |
|
if ( !g_pEngine->IsSupportedModAndPlatform() ) |
|
return false; |
|
|
|
// Clear anything already loaded (since we reuse the same instance) |
|
Clear(); |
|
|
|
// Register replay factory |
|
m_pReplayFactory = GetReplayFactory( fnCreateFactory ); Assert( m_pReplayFactory ); |
|
|
|
// Load all replays from disk |
|
if ( !BaseClass::Init() ) |
|
{ |
|
Warning( "Failed to load replay history!\n" ); |
|
} |
|
|
|
// Session manager init'd by this point - go through and link up replays to sessions |
|
CL_GetRecordingSessionManager()->OnReplaysLoaded(); |
|
|
|
return true; |
|
} |
|
|
|
void CReplayManager::Shutdown() |
|
{ |
|
// Get out if the user is running an unsupported mod or platform |
|
if ( !g_pEngine->IsSupportedModAndPlatform() ) |
|
return; |
|
|
|
// Make sure we aren't waiting to write |
|
BaseClass::Shutdown(); // Saves |
|
} |
|
|
|
IReplayFactory *CReplayManager::GetReplayFactory( CreateInterfaceFn fnCreateFactory ) |
|
{ |
|
return (IReplayFactory *)fnCreateFactory( INTERFACE_VERSION_REPLAY_FACTORY, NULL ); |
|
} |
|
|
|
void CReplayManager::OnSessionStart() |
|
{ |
|
// The pending replay doesn't exist yet at this point as far as I've seen, since the "replay_sessioninfo" |
|
// event comes down a frame or more after the "replay_recording" replicated cvar is set to 1, which is |
|
// what triggers AttemptToSetupNewReplay(). |
|
if ( !m_pPendingReplay ) |
|
{ |
|
AttemptToSetupNewReplay(); |
|
} |
|
|
|
if ( m_pPendingReplay ) |
|
{ |
|
// Link up the pending replay to the recording session in progress |
|
if ( m_pPendingReplay->m_hSession == REPLAY_HANDLE_INVALID ) |
|
{ |
|
ReplayHandle_t hSessionInProgress = CL_GetRecordingSessionManager()->GetRecordingSessionInProgress()->GetHandle(); |
|
m_pPendingReplay->m_hSession = hSessionInProgress; |
|
} |
|
|
|
// Make sure the spawn tick has the proper server start tick subtracted out |
|
if ( m_pPendingReplay->m_nSpawnTick < 0 ) |
|
{ |
|
const int nServerStartTick = CL_GetRecordingSessionManager()->m_ServerRecordingState.m_nStartTick; Assert( nServerStartTick > 0 ); |
|
m_pPendingReplay->m_nSpawnTick = MAX( 0, -m_pPendingReplay->m_nSpawnTick - nServerStartTick ); |
|
} |
|
} |
|
} |
|
|
|
void CReplayManager::OnSessionEnd() |
|
{ |
|
// Complete the pending replay, if there is one |
|
CompletePendingReplay(); |
|
} |
|
|
|
const char *CReplayManager::GetRelativeIndexPath() const |
|
{ |
|
return Replay_va( "%s%c", SUBDIR_REPLAYS, CORRECT_PATH_SEPARATOR ); |
|
} |
|
|
|
CReplay *CReplayManager::Create() |
|
{ |
|
return m_pReplayFactory->Create(); |
|
} |
|
|
|
IReplayContext *CReplayManager::GetReplayContext() const |
|
{ |
|
return g_pClientReplayContextInternal; |
|
} |
|
|
|
bool CReplayManager::ShouldLoadObj( const CReplay *pReplay ) const |
|
{ |
|
return pReplay && pReplay->m_bComplete; |
|
} |
|
|
|
void CReplayManager::OnObjLoaded( CReplay *pReplay ) |
|
{ |
|
if ( !pReplay ) |
|
return; |
|
|
|
pReplay->m_bSavedDuringThisSession = false; |
|
} |
|
|
|
int CReplayManager::GetVersion() const |
|
{ |
|
return REPLAY_INDEX_VERSION; |
|
} |
|
|
|
void CReplayManager::ClearPendingReplay() |
|
{ |
|
m_pPendingReplay = NULL; |
|
} |
|
|
|
void CReplayManager::SanityCheckReplay( CReplay *pReplay ) |
|
{ |
|
if ( !pReplay ) |
|
return; |
|
|
|
// DEBUG: Make sure this replay does not already exist in the list |
|
FOR_EACH_VEC( Replays(), i ) |
|
{ |
|
if ( Replays()[ i ]->GetHandle() == pReplay->GetHandle() ) |
|
{ |
|
IF_REPLAY_DBG( Warning( "Replay %i already found in history!\n", pReplay->GetHandle() ) ); |
|
} |
|
} |
|
|
|
if ( pReplay->m_nDeathTick < pReplay->m_nSpawnTick ) |
|
{ |
|
IF_REPLAY_DBG( Warning( "Spawn tick (%i) is greater than death tick (%i)!\n", pReplay->m_nSpawnTick, pReplay->m_nDeathTick ) ); |
|
} |
|
} |
|
|
|
void CReplayManager::SaveDanglingReplay() |
|
{ |
|
if ( !m_pReplayThisLife ) |
|
return; |
|
|
|
if ( m_pReplayThisLife->m_bRequestedByUser ) |
|
{ |
|
CompletePendingReplay(); |
|
FlagReplayForFlush( m_pReplayThisLife, false ); |
|
} |
|
} |
|
|
|
void CReplayManager::FreeLifeIfNotSaved( CReplay *&pReplay ) |
|
{ |
|
if ( pReplay ) |
|
{ |
|
if ( !pReplay->m_bSaved && !IsDirty( pReplay ) ) |
|
{ |
|
CleanupReplay( pReplay ); |
|
} |
|
else |
|
{ |
|
// If it's been saved, don't free the memory, just clear the pointer |
|
pReplay = NULL; |
|
} |
|
} |
|
} |
|
|
|
void CReplayManager::CleanupReplay( CReplay *&pReplay ) |
|
{ |
|
if ( !pReplay ) |
|
return; |
|
|
|
// Get rid of a replay that was never committed: |
|
// Remove screenshots taken |
|
CL_GetScreenshotManager()->DeleteScreenshotsForReplay( pReplay ); |
|
|
|
// Free |
|
delete pReplay; |
|
pReplay = NULL; |
|
} |
|
|
|
void CReplayManager::OnReplayRecordingCvarChanged() |
|
{ |
|
DBG( "OnReplayRecordingCvarChanged()\n" ); |
|
|
|
// If set to 0, get out - we don't care |
|
extern ConVar replay_recording; |
|
if ( !replay_recording.GetBool() ) |
|
{ |
|
DBG( " replay_recording is false...aborting\n" ); |
|
return; |
|
} |
|
|
|
// If OnPlayerSpawn() hasn't failed to create the scratch replay, get out |
|
if ( m_flPlayerSpawnCreateReplayFailTime == 0.0f ) |
|
{ |
|
DBG( " m_flPlayerSpawnCreateReplayFailTime == 0.0f...aborting.\n" ); |
|
return; |
|
} |
|
|
|
DBG( " Calling AttemptToSetupNewReplay()\n" ); |
|
|
|
// Try to create & setup again |
|
AttemptToSetupNewReplay(); |
|
|
|
// Reset |
|
m_flPlayerSpawnCreateReplayFailTime = 0.0f; |
|
} |
|
|
|
void CReplayManager::OnClientSideDisconnect() |
|
{ |
|
SaveDanglingReplay(); |
|
ClearPendingReplay(); |
|
|
|
FreeLifeIfNotSaved( m_pReplayLastLife ); |
|
FreeLifeIfNotSaved( m_pReplayThisLife ); |
|
|
|
m_flPlayerSpawnCreateReplayFailTime = 0.0f; |
|
} |
|
|
|
void CReplayManager::CommitPendingReplayAndBeginDownload() |
|
{ |
|
// Update the last session block we should download |
|
CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( m_pReplayThisLife->m_hSession ) ); |
|
const int iPostDeathBlockIndex = pSession->UpdateLastBlockToDownload(); |
|
|
|
// Update the # of blocks required to reconstruct the replay |
|
m_pReplayThisLife->m_iMaxSessionBlockRequired = iPostDeathBlockIndex; |
|
|
|
Commit( m_pReplayThisLife ); |
|
} |
|
|
|
void CReplayManager::CompletePendingReplay() |
|
{ |
|
// Get out if no pending replay |
|
if ( !m_pPendingReplay ) |
|
return; |
|
|
|
// Get session associated w/ the replay |
|
CBaseRecordingSession *pSession = CL_GetRecordingSessionManager()->FindSession( m_pPendingReplay->m_hSession ); |
|
|
|
// Sometimes the session isn't valid here, like when we're first joining a server |
|
if ( !pSession ) |
|
return; |
|
|
|
Assert( pSession->m_nServerStartRecordTick >= 0 ); |
|
|
|
// Cache death tick |
|
m_pPendingReplay->m_nDeathTick = g_pEngineClient->GetLastServerTickTime() - pSession->m_nServerStartRecordTick; |
|
|
|
SanityCheckReplay( m_pPendingReplay ); |
|
|
|
// Calc replay length |
|
m_pPendingReplay->m_flLength = g_pEngine->TicksToTime( |
|
m_pPendingReplay->m_nDeathTick - |
|
m_pPendingReplay->m_nSpawnTick + |
|
g_pEngine->TimeToTicks( replay_postdeathrecordtime.GetFloat() ) |
|
); |
|
|
|
// Cache player slot so we can start playback of replays from recorder player's perspective |
|
m_pPendingReplay->m_nPlayerSlot = g_pEngineClient->GetPlayerSlot() + 1; |
|
|
|
// Setup status |
|
m_pPendingReplay->m_nStatus = CReplay::REPLAYSTATUS_DOWNLOADPHASE; |
|
|
|
// The replay is now "complete," ie has all the data needed |
|
m_pPendingReplay->m_bComplete = true; |
|
|
|
// Let derived classes do whatever it wants |
|
m_pPendingReplay->OnComplete(); |
|
|
|
// If the replay was requested by the user already, update the # of blocks we should download & commit the replay |
|
if ( m_pPendingReplay->m_bRequestedByUser ) |
|
{ |
|
#ifdef DBGFLAG_ASSERT |
|
Assert( m_pReplayThisLife->m_bComplete ); |
|
#endif |
|
CommitPendingReplayAndBeginDownload(); |
|
} |
|
|
|
// Before we copy the pointer to "this life," end recording so the replay can do any cleanup (eg listening for game events) |
|
m_pPendingReplay->OnEndRecording(); |
|
|
|
// Cache off scratch replay to "this life" |
|
m_pReplayThisLife = m_pPendingReplay; |
|
|
|
ClearPendingReplay(); |
|
} |
|
|
|
bool CReplayManager::Commit( CReplay *pNewReplay ) |
|
{ |
|
if ( !g_pClientReplayContextInternal->IsInitialized() || !pNewReplay ) |
|
return false; |
|
|
|
SanityCheckReplay( pNewReplay ); |
|
|
|
// NOTE: Marks index as dirty, as well as pNewReplay |
|
Add( pNewReplay ); |
|
|
|
// Save now |
|
Save(); |
|
|
|
return true; |
|
} |
|
|
|
// |
|
// IReplayManager implementation |
|
// |
|
CReplay *CReplayManager::GetReplay( ReplayHandle_t hReplay ) |
|
{ |
|
if ( m_pReplayThisLife && m_pReplayThisLife->GetHandle() == hReplay ) |
|
return m_pReplayThisLife; |
|
|
|
return Find( hReplay ); |
|
} |
|
|
|
void CReplayManager::DeleteReplay( ReplayHandle_t hReplay, bool bNotifyUI ) |
|
{ |
|
CReplay *pReplay = GetReplay( hReplay ); Assert( pReplay ); |
|
|
|
// The session manager will delete the .dem, the session .dmx and remove the session |
|
// item itself if this is the last replay associated with it. |
|
CL_GetRecordingSessionManager()->OnReplayDeleted( pReplay ); |
|
|
|
// Notify the replay browser if necessary |
|
if ( bNotifyUI ) |
|
{ |
|
extern IClientReplay *g_pClient; |
|
g_pClient->OnDeleteReplay( hReplay ); |
|
} |
|
|
|
// Remove it |
|
Remove( pReplay ); |
|
|
|
// If the replay deleted was just saved and we haven't respawned yet, |
|
// we need to clear out some stuff so GetReplay() doesn't crash. |
|
if ( m_pReplayThisLife == pReplay ) |
|
{ |
|
m_pReplayThisLife = NULL; |
|
m_pPendingReplay = NULL; |
|
} |
|
|
|
if ( m_pReplayLastLife == pReplay ) |
|
{ |
|
m_pReplayLastLife = NULL; |
|
} |
|
} |
|
|
|
void CReplayManager::FlagReplayForFlush( CReplay *pReplay, bool bForceImmediate ) |
|
{ |
|
FlagForFlush( pReplay, bForceImmediate ); |
|
} |
|
|
|
int CReplayManager::GetUnrenderedReplayCount() |
|
{ |
|
int nCount = 0; |
|
FOR_EACH_VEC( m_vecObjs, i ) |
|
{ |
|
CReplay *pCurReplay = m_vecObjs[ i ]; |
|
if ( !pCurReplay->m_bRendered && |
|
pCurReplay->m_nStatus == CReplay::REPLAYSTATUS_READYTOCONVERT ) |
|
{ |
|
++nCount; |
|
} |
|
} |
|
return nCount; |
|
} |
|
|
|
void CReplayManager::InitReplay( CReplay *pReplay ) |
|
{ |
|
// Setup record time right now |
|
pReplay->m_RecordTime.InitDateAndTimeToNow(); |
|
|
|
// Store start time |
|
pReplay->m_flStartTime = g_pEngine->GetHostTime(); |
|
|
|
// Get map name (w/o the path) |
|
V_FileBase( g_pEngineClient->GetLevelName(), m_pPendingReplay->m_szMapName, sizeof( m_pPendingReplay->m_szMapName ) ); |
|
|
|
// Give the replay a default name |
|
pReplay->AutoNameTitleIfEmpty(); |
|
} |
|
|
|
CReplay *CReplayManager::CreatePendingReplay() |
|
{ |
|
Assert( m_pPendingReplay == NULL ); |
|
m_pPendingReplay = CreateAndGenerateHandle(); |
|
|
|
// If we've already begun recording, link to the session now, otherwise link once |
|
// we start recording. |
|
CBaseRecordingSession *pSessionInProgress = CL_GetRecordingSessionInProgress(); |
|
if ( pSessionInProgress ) |
|
{ |
|
m_pPendingReplay->m_hSession = pSessionInProgress->GetHandle(); |
|
} |
|
|
|
InitReplay( m_pPendingReplay ); |
|
|
|
// Setup replay handle for screenshots |
|
CL_GetScreenshotManager()->SetScreenshotReplay( m_pPendingReplay->GetHandle() ); |
|
|
|
return m_pPendingReplay; |
|
} |
|
|
|
void CReplayManager::AttemptToSetupNewReplay() |
|
{ |
|
DBG( "AttemptToSetupNewReplay()\n" ); |
|
|
|
if ( !g_pReplay->IsRecording() || g_pEngineClient->IsPlayingReplayDemo() ) |
|
{ |
|
DBG( " Aborting...not recording, or playing back replay.\n" ); |
|
m_flPlayerSpawnCreateReplayFailTime = g_pEngine->GetHostTime(); |
|
return; |
|
} |
|
|
|
// Create the replay if necessary - we only do setup if we're creating |
|
// a new replay, because on a full update this function may be called |
|
// even though we're not actually spawning. |
|
if ( !m_pPendingReplay ) |
|
{ |
|
DBG( " Creating new replay...\n" ); |
|
|
|
// If there is a "last life" replay that was not saved already, delete it |
|
FreeLifeIfNotSaved( m_pReplayLastLife ); |
|
|
|
// Cache last life |
|
m_pReplayLastLife = m_pReplayThisLife; |
|
|
|
// Create the scratch replay (sets m_pPendingReplay and returns it) |
|
CReplay *pPendingReplay = CreatePendingReplay(); |
|
|
|
SanityCheckReplay( pPendingReplay ); |
|
|
|
// "This life" is the scratch replay |
|
m_pReplayThisLife = pPendingReplay; |
|
|
|
// Setup spawn tick |
|
const int nServerStartTick = CL_GetRecordingSessionManager()->m_ServerRecordingState.m_nStartTick; |
|
pPendingReplay->m_nSpawnTick = g_pEngineClient->GetLastServerTickTime() - nServerStartTick; |
|
if ( nServerStartTick == 0 ) |
|
{ |
|
// Didn't receive the replay_sessioninfo event yet - make spawn tick negative so when the |
|
// event IS received, we can detect that the server start tick still needs to be subtracted. |
|
pPendingReplay->m_nSpawnTick *= -1; |
|
} |
|
|
|
// Setup post-death record time |
|
extern ConVar replay_postdeathrecordtime; |
|
pPendingReplay->m_nPostDeathRecordTime = replay_postdeathrecordtime.GetFloat(); |
|
|
|
// Let the replay know we're recording |
|
pPendingReplay->OnBeginRecording(); |
|
} |
|
else |
|
{ |
|
DBG( " NOT creating new replay.\n" ); |
|
} |
|
|
|
// Served its purpose |
|
m_flPlayerSpawnCreateReplayFailTime = 0.0f; |
|
} |
|
|
|
void CReplayManager::Think() |
|
{ |
|
VPROF_BUDGET( "CReplayManager::Think", VPROF_BUDGETGROUP_REPLAY ); |
|
|
|
BaseClass::Think(); |
|
|
|
DebugThink(); |
|
|
|
// Only update pending replay, since it's recording |
|
// NOTE: we use Sys_FloatTime() here, since the client sets the next update time with engine->Time(), |
|
// which also uses Sys_FloatTime(). |
|
if ( m_pPendingReplay && m_pPendingReplay->m_flNextUpdateTime <= Sys_FloatTime() ) |
|
{ |
|
m_pPendingReplay->Update(); // Allow the replay's Update() function to set the next update time |
|
} |
|
} |
|
|
|
void CReplayManager::DebugThink() |
|
{ |
|
// Debugging |
|
if ( replay_debug.GetBool() ) |
|
{ |
|
const char *pReplayNames[] = { "Scratch", "This life", "Last life" }; |
|
CReplay *pReplays[] = { m_pPendingReplay, m_pReplayThisLife, m_pReplayLastLife }; |
|
for ( int i = 0; i < 3; ++i ) |
|
{ |
|
CReplay *pCurReplay = pReplays[ i ]; |
|
if ( !pCurReplay ) |
|
{ |
|
g_pEngineClient->Con_NPrintf( i, "%s: NULL", pReplayNames[ i ] ); |
|
continue; |
|
} |
|
|
|
g_pEngineClient->Con_NPrintf( i, "%s: handle=%i [%i, %i] C? %s R? %s MaxBlock: %i", pReplayNames[ i ], |
|
pCurReplay->GetHandle(), pCurReplay->m_nSpawnTick, |
|
pCurReplay->m_nDeathTick, pCurReplay->m_bComplete ? "YES" : "NO", |
|
pCurReplay->m_bRequestedByUser ? "YES" : "NO", |
|
pCurReplay->m_iMaxSessionBlockRequired |
|
); |
|
|
|
// Screenshot handle |
|
int nCurLine = 5; |
|
g_pEngineClient->Con_NPrintf( nCurLine, "Screenshot replay: handle=%i", CL_GetScreenshotManager()->GetScreenshotReplay() ); |
|
nCurLine += 2; |
|
|
|
// Saved replay handles |
|
g_pEngineClient->Con_NPrintf( nCurLine++, "REPLAYS:" ); |
|
FOR_EACH_REPLAY( j ) |
|
{ |
|
CReplay *pReplay = GET_REPLAY_AT( j ); |
|
g_pEngineClient->Con_NPrintf( nCurLine++, "%i: handle=%i ticks=[%i %i]", i, pReplay->GetHandle(), |
|
pReplay->m_nSpawnTick, pReplay->m_nDeathTick ); |
|
} |
|
|
|
// Current tick: |
|
g_pEngineClient->Con_NPrintf( ++nCurLine, "MAIN tick: %f", g_pEngineClient->GetLastServerTickTime() ); |
|
g_pEngineClient->Con_NPrintf( ++nCurLine, " server tick: %f", g_pEngineClient->GetLastServerTickTime() ); |
|
nCurLine += 2; |
|
} |
|
} |
|
} |
|
|
|
float CReplayManager::GetNextThinkTime() const |
|
{ |
|
return g_pEngine->GetHostTime() + 0.1f; |
|
} |
|
|
|
CReplay *CReplayManager::GetPlayingReplay() |
|
{ |
|
return g_pReplayDemoPlayer->GetCurrentReplay(); |
|
} |
|
|
|
CReplay *CReplayManager::GetReplayForCurrentLife() |
|
{ |
|
return m_pReplayThisLife; |
|
} |
|
|
|
void CReplayManager::GetReplays( CUtlLinkedList< CReplay *, int > &lstReplays ) |
|
{ |
|
lstReplays.RemoveAll(); |
|
FOR_EACH_REPLAY( i ) |
|
{ |
|
lstReplays.AddToTail( GET_REPLAY_AT( i ) ); |
|
} |
|
} |
|
|
|
void CReplayManager::GetReplaysAsQueryableItems( CUtlLinkedList< IQueryableReplayItem *, int > &lstReplays ) |
|
{ |
|
lstReplays.RemoveAll(); |
|
FOR_EACH_REPLAY( i ) |
|
{ |
|
lstReplays.AddToHead( dynamic_cast< IQueryableReplayItem * >( GET_REPLAY_AT( i ) ) ); |
|
} |
|
|
|
if ( m_pPendingReplay && |
|
!m_pPendingReplay->m_bComplete && |
|
m_pPendingReplay->m_bRequestedByUser ) |
|
{ |
|
Assert( lstReplays.Find( m_pPendingReplay ) == lstReplays.InvalidIndex() ); |
|
lstReplays.AddToHead( m_pPendingReplay ); |
|
} |
|
} |
|
|
|
int CReplayManager::GetNumReplaysDependentOnSession( ReplayHandle_t hSession ) |
|
{ |
|
int nResult = 0; |
|
FOR_EACH_REPLAY( i ) |
|
{ |
|
CReplay *pCurReplay = GET_REPLAY_AT( i ); |
|
if ( pCurReplay->m_hSession == hSession ) |
|
{ |
|
++nResult; |
|
} |
|
} |
|
return nResult; |
|
} |
|
|
|
const char *CReplayManager::GetReplaysDir() const |
|
{ |
|
return GetIndexPath(); |
|
} |
|
|
|
float CReplayManager::GetDownloadProgress( const CReplay *pReplay ) |
|
{ |
|
// Give each downloadable session block equal weight since we won't know the size of blocks that |
|
// have not been created/written yet on the server. |
|
|
|
// Go through all blocks in the replay and figure out how many bytes have been downloaded |
|
float flSum = 0.0f; |
|
|
|
CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pReplay->m_hSession ) ); Assert( pSession ); |
|
if ( !pSession ) |
|
return 0.0f; |
|
|
|
const CBaseRecordingSession::BlockContainer_t &vecBlocks = pSession->GetBlocks(); |
|
FOR_EACH_VEC( vecBlocks, i ) |
|
{ |
|
CClientRecordingSessionBlock *pCurBlock = CL_CastBlock( vecBlocks[ i ] ); |
|
if ( !pReplay->IsSignificantBlock( pCurBlock->m_iReconstruction ) ) |
|
continue; |
|
|
|
// Calculate progress for this block |
|
Assert( pCurBlock->m_uFileSize > 0 ); |
|
const float flSubProgress = pCurBlock->m_uFileSize == 0 ? 0.0f : clamp( (float)pCurBlock->m_uBytesDownloaded / pCurBlock->m_uFileSize, 0.0f, 1.0f ); |
|
|
|
flSum += flSubProgress; |
|
} |
|
|
|
// Account for blocks that haven't been created yet |
|
// NOTE: This will cause a bug in download progress if the round ends and cuts the number of |
|
// expected blocks down - but that situation is probably less likely to occur than the situation |
|
// where a client is expecting more blocks that *will* be created. To avoid pops in the latter |
|
// situation, we account for those blocks here. |
|
const int nTotalSubBlocks = pReplay->m_iMaxSessionBlockRequired + 1; |
|
|
|
// Calculate mean |
|
Assert( nTotalSubBlocks > 0 ); |
|
return nTotalSubBlocks == 0 ? 0.0f : ( flSum / (float)nTotalSubBlocks ); |
|
} |
|
|
|
//----------------------------------------------------------------------------------------
|
|
|