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