summaryrefslogtreecommitdiff
path: root/replay/cl_recordingsessionmanager.cpp
blob: cda5eceb6d477395021db7a9a0694610c33c1b78 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
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 );
	}
}

//----------------------------------------------------------------------------------------