//========= Copyright Valve Corporation, All rights reserved. ============// // //=======================================================================================// #include "baserecordingsessionmanager.h" #include "baserecordingsession.h" #include "baserecordingsessionblock.h" #include "replay/replayutils.h" #include "replay/shared_defs.h" #include "replaysystem.h" #include "KeyValues.h" #include "shared_replaycontext.h" #include "filesystem.h" #include "iserver.h" #include "vprof.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //---------------------------------------------------------------------------------------- inline const char *GetSessionsFullFilename() { return Replay_va( "%s" SUBDIR_SESSIONS "%c", Replay_GetBaseDir(), CORRECT_PATH_SEPARATOR ); } //---------------------------------------------------------------------------------------- CBaseRecordingSessionManager::CBaseRecordingSessionManager( IReplayContext *pContext ) : m_pContext( pContext ), m_pRecordingSession( NULL ), m_bLastSessionDitched( false ) { } CBaseRecordingSessionManager::~CBaseRecordingSessionManager() { } bool CBaseRecordingSessionManager::Init() { if ( !BaseClass::Init() ) return false; // Go through each block handle and attempt find the block in the block manager typedef CGenericPersistentManager< CBaseRecordingSessionBlock > BaseBlockManager_t; BaseBlockManager_t *pBlockManager = dynamic_cast< BaseBlockManager_t * >( m_pContext->GetRecordingSessionBlockManager() ); FOR_EACH_OBJ( pBlockManager, it ) { CBaseRecordingSessionBlock *pCurBlock = pBlockManager->m_vecObjs[ it ]; // Find the session for the current block CBaseRecordingSession *pSession = m_pContext->GetRecordingSessionManager()->FindSession( pCurBlock->m_hSession ); if ( !pSession ) { m_pContext->GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Load_CouldNotFindSession" ); continue; } // Add the block pSession->AddBlock( pCurBlock, false ); } return true; } CBaseRecordingSession *CBaseRecordingSessionManager::OnSessionStart( int nCurrentRecordingStartTick, const char *pSessionName ) { // Add a new session if one w/ the given name doesn't already exist. // This is necessary on the client, where a session may already exist if, for example, // the client reconnects to a server where they were already playing/saved replays. // On the server, NULL will always be passed in for pSessionName. CBaseRecordingSession *pNewSession = pSessionName ? FindSessionByName( pSessionName ) : NULL; if ( !pNewSession ) { pNewSession = CreateAndGenerateHandle(); Add( pNewSession ); } // Initialize pNewSession->PopulateWithRecordingData( nCurrentRecordingStartTick ); Save(); // Update recording session m_pRecordingSession = pNewSession; return m_pRecordingSession; } void CBaseRecordingSessionManager::OnSessionEnd() { if ( m_pRecordingSession ) { // If we don't care about the given session, ditch it // NOTE: ShouldDitchSession() checks auto-delete flag! if ( m_pRecordingSession->ShouldDitchSession() ) { m_bLastSessionDitched = true; DBG( "Marking session for ditch!\n" ); MarkSessionForDelete( m_pRecordingSession->GetHandle() ); } else { m_bLastSessionDitched = false; // Save FlagForFlush( m_pRecordingSession, false ); // Unload from memory? if ( ShouldUnloadSessions() ) { FlagForUnload( m_pRecordingSession ); } } } m_pRecordingSession = NULL; } void CBaseRecordingSessionManager::DeleteSession( ReplayHandle_t hSession, bool bForce ) { CBaseRecordingSession *pSession = Find( hSession ); if ( !pSession ) { AssertMsg( 0, "Trying to delete a non-existent session - should never happen!" ); return; } AssertMsg( !pSession->IsLocked(), "Shouldn't be free'ing a locked session!" ); // If the given session is recording, flag for delete but don't actually remove now if ( pSession == m_pRecordingSession && !bForce ) { pSession->m_bAutoDelete = true; return; } // Remove the session and save Remove( pSession ); Save(); } void CBaseRecordingSessionManager::MarkSessionForDelete( ReplayHandle_t hSession ) { m_lstSessionsToDelete.AddToTail( hSession ); } const char *CBaseRecordingSessionManager::GetCurrentSessionName() const { if ( !m_pRecordingSession ) { AssertMsg( 0, "GetCurrentSessionName() called w/o a session context" ); return NULL; } return m_pRecordingSession->m_strName.Get(); } int CBaseRecordingSessionManager::GetCurrentSessionBlockIndex() const { if ( !m_pRecordingSession ) { AssertMsg( 0, "GetCurrentPartialIndex() called w/o a session context" ); return -1; } // Need this MAX() here since GetNumBlocks() will return 0 until the first block is actually written. return MAX( 0, m_pRecordingSession->GetNumBlocks() - 1 ); } void CBaseRecordingSessionManager::FlagSessionForFlush( CBaseRecordingSession *pSession, bool bForceImmediate ) { FlagForFlush( pSession, bForceImmediate ); } int CBaseRecordingSessionManager::GetServerStartTickForSession( ReplayHandle_t hSession ) { CBaseRecordingSession *pSession = FindSession( hSession ); if ( !pSession ) return -1; return pSession->m_nServerStartRecordTick; } CBaseRecordingSession *CBaseRecordingSessionManager::FindSession( ReplayHandle_t hSession ) { return Find( hSession ); } const CBaseRecordingSession *CBaseRecordingSessionManager::FindSession( ReplayHandle_t hSession ) const { return const_cast< CBaseRecordingSessionManager * >( this )->Find( hSession ); } CBaseRecordingSession *CBaseRecordingSessionManager::FindSessionByName( const char *pSessionName ) { if ( !pSessionName || !pSessionName[0] ) return NULL; FOR_EACH_OBJ( this, i ) { CBaseRecordingSession *pCurSession = m_vecObjs[ i ]; if ( !V_stricmp( pSessionName, pCurSession->m_strName.Get() ) ) return pCurSession; } return NULL; } const char *CBaseRecordingSessionManager::GetRelativeIndexPath() const { return Replay_va( "%s%c", SUBDIR_SESSIONS, CORRECT_PATH_SEPARATOR ); } void CBaseRecordingSessionManager::Think() { VPROF_BUDGET( "CBaseRecordingSessionManager::Think", VPROF_BUDGETGROUP_REPLAY ); DeleteSessionThink(); BaseClass::Think(); } void CBaseRecordingSessionManager::DeleteSessionThink() { DoSessionCleanup(); } void CBaseRecordingSessionManager::DoSessionCleanup() { bool bDeletedASession = false; for ( int i = m_lstSessionsToDelete.Head(); i != m_lstSessionsToDelete.InvalidIndex(); ) { ReplayHandle_t hSession = m_lstSessionsToDelete[ i ]; const int itNext = m_lstSessionsToDelete.Next( i ); if ( CanDeleteSession( hSession ) ) { DBG( "Unloading session.\n" ); DeleteSession( hSession, true ); m_lstSessionsToDelete.Remove( i ); bDeletedASession = true; } i = itNext; } // If we just deleted the last session, let the derived class do any post-work if ( !m_lstSessionsToDelete.Count() && bDeletedASession ) { OnAllSessionsDeleted(); } } float CBaseRecordingSessionManager::GetNextThinkTime() const { return g_pEngine->GetHostTime() + 0.1f; } //----------------------------------------------------------------------------------------