Counter Strike : Global Offensive Source Code
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.

532 lines
16 KiB

  1. //========= Copyright (c) 1996-2009, Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #if defined( REPLAY_ENABLED )
  5. #include "replayhistorymanager.h"
  6. #include "client.h"
  7. #include "net_chan.h"
  8. #include "dmxloader/dmxelement.h"
  9. #include <time.h>
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. //----------------------------------------------------------------------------------------
  13. #define REPLAY_HISTORY_FILE_CLIENT "client_replay_history.dmx"
  14. #define REPLAY_HISTORY_FILE_SERVER "server_replay_history.dmx"
  15. //----------------------------------------------------------------------------------------
  16. void CClientReplayHistoryEntryData::BeginDownload()
  17. {
  18. // Request the .dem file from the server
  19. GetBaseLocalClient().m_NetChannel->RequestFile( m_szFilename, true );
  20. m_bTransferring = true;
  21. }
  22. //----------------------------------------------------------------------------------------
  23. BEGIN_DMXELEMENT_UNPACK( CBaseReplayHistoryEntryData )
  24. DMXELEMENT_UNPACK_FIELD_STRING( "filename", "NONE", m_szFilename )
  25. DMXELEMENT_UNPACK_FIELD_STRING( "map" , "NONE", m_szMapName )
  26. DMXELEMENT_UNPACK_FIELD( "lifespan" , "0", int , m_nLifeSpan )
  27. DMXELEMENT_UNPACK_FIELD( "demo_length", "0", DmeTime_t, m_DemoLength )
  28. DMXELEMENT_UNPACK_FIELD( "transferred", "0", int , m_nBytesTransferred )
  29. DMXELEMENT_UNPACK_FIELD( "size" , "0", int , m_nSize )
  30. DMXELEMENT_UNPACK_FIELD( "transferid" , "0", int , m_nTransferId )
  31. DMXELEMENT_UNPACK_FIELD( "complete" , "0", bool , m_bTransferComplete )
  32. DMXELEMENT_UNPACK_FIELD( "downloading", "0", bool , m_bTransferring )
  33. END_DMXELEMENT_UNPACK( CBaseReplayHistoryEntryData, s_ClientEntryDataUnpack )
  34. //----------------------------------------------------------------------------------------
  35. template< class T >
  36. class CBaseReplayHistoryManager : public IReplayHistoryManager
  37. {
  38. public:
  39. CBaseReplayHistoryManager()
  40. : m_bInit( false )
  41. {
  42. }
  43. virtual void Init()
  44. {
  45. // Load all entries from disk
  46. if ( !LoadEntriesFromDisk() )
  47. {
  48. Warning( "Replay history file %s not found.\n", GetCacheFilename() );
  49. }
  50. m_bInit = true;
  51. }
  52. virtual bool IsInitialized() const { return m_bInit; }
  53. virtual void Shutdown()
  54. {
  55. m_bInit = false;
  56. m_lstEntries.PurgeAndDeleteElements();
  57. }
  58. virtual int GetNumEntries() const
  59. {
  60. return m_lstEntries.Count();
  61. }
  62. virtual const CBaseReplayHistoryEntryData *GetEntryAtIndex( int iIndex ) const
  63. {
  64. Assert( iIndex >= 0 && iIndex < GetNumEntries() );
  65. return static_cast< CBaseReplayHistoryEntryData *>( m_lstEntries[ iIndex ] );
  66. }
  67. virtual CBaseReplayHistoryEntryData *FindEntry( const char *pFilename )
  68. {
  69. FOR_EACH_LL( m_lstEntries, i )
  70. {
  71. if ( !V_stricmp( pFilename, m_lstEntries[ i ]->m_szFilename ) )
  72. {
  73. return static_cast< T *>( m_lstEntries[ i ] );
  74. }
  75. }
  76. return NULL;
  77. }
  78. virtual void FlushEntriesToDisk()
  79. {
  80. Assert( m_bInit );
  81. DECLARE_DMX_CONTEXT();
  82. CDmxElement* pEntries = CreateDmxElement( "Entries" );
  83. CDmxElementModifyScope modify( pEntries );
  84. int const nNumDemos = m_lstEntries.Count();
  85. pEntries->SetValue( "num_demos", nNumDemos );
  86. CDmxAttribute* pDemoEntriesAttr = pEntries->AddAttribute( "demos" );
  87. CUtlVector< CDmxElement* >& entries = pDemoEntriesAttr->GetArrayForEdit< CDmxElement* >();
  88. modify.Release();
  89. FOR_EACH_LL( m_lstEntries, i )
  90. {
  91. T *pEntryData = m_lstEntries[ i ];
  92. CDmxElement* pEntryElement = CreateDmxElement( "demo" );
  93. entries.AddToTail( pEntryElement );
  94. CDmxElementModifyScope modifyClass( pEntryElement );
  95. pEntryElement->AddAttributesFromStructure( pEntryData, s_ClientEntryDataUnpack );
  96. pEntryElement->SetValue( "record_time", pEntryData->m_nRecordTime );
  97. RecordAdditionalEntryData( pEntryData, pEntryElement );
  98. }
  99. {
  100. MEM_ALLOC_CREDIT();
  101. const char *pFilename = GetCacheFilename();
  102. if ( !SerializeDMX( pFilename, "GAME", false, pEntries ) )
  103. {
  104. Warning( "Replay: Failed to write ragdoll cache, %s.\n", pFilename );
  105. return;
  106. }
  107. }
  108. CleanupDMX( pEntries );
  109. }
  110. bool LoadEntriesFromDisk()
  111. {
  112. Assert( !m_bInit );
  113. const char* pFilename = GetCacheFilename();
  114. DECLARE_DMX_CONTEXT();
  115. // Attempt to read from disk
  116. CDmxElement* pDemos = NULL;
  117. if ( !UnserializeDMX( pFilename, "GAME", false, &pDemos ) )
  118. return false;
  119. CUtlVector< CDmxElement* > const& demos = pDemos->GetArray< CDmxElement* >( "demos" );
  120. for ( int i = 0; i < demos.Count(); ++i )
  121. {
  122. CDmxElement* pCurDemoInput = demos[ i ];
  123. // Create a new ragdoll entry and add to list
  124. T *pNewEntry = new T();
  125. m_lstEntries.AddToTail( pNewEntry );
  126. // Read
  127. pCurDemoInput->UnpackIntoStructure( pNewEntry, s_ClientEntryDataUnpack );
  128. // This should always be false
  129. pNewEntry->m_bTransferring = false;
  130. // Load record time
  131. pNewEntry->m_nRecordTime = pCurDemoInput->GetValue( "record_time", 0 );
  132. LoadAdditionalEntryData( pNewEntry, pCurDemoInput );
  133. }
  134. // Cleanup
  135. CleanupDMX( pDemos );
  136. PostLoadEntries();
  137. return true;
  138. }
  139. virtual void StopDownloads()
  140. {
  141. FOR_EACH_LL( m_lstEntries, i )
  142. {
  143. m_lstEntries[ i ]->m_bTransferring = false;
  144. }
  145. }
  146. virtual const char *GetCacheFilename() const = 0;
  147. protected:
  148. //
  149. // Called from FlushEntriesToDisk() for each entry - opportunity to record additional data
  150. //
  151. virtual void RecordAdditionalEntryData( const CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement ) {}
  152. //
  153. // Called from LoadEntriesFromDisk() for each entry - opportunity to load additional data
  154. //
  155. virtual void LoadAdditionalEntryData( CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement ) {}
  156. //
  157. // Called at the end of LoadEntriesFromDisk()
  158. //
  159. virtual void PostLoadEntries() {}
  160. virtual void Update() {}
  161. CUtlLinkedList< T* > m_lstEntries;
  162. private:
  163. bool m_bInit;
  164. };
  165. //----------------------------------------------------------------------------------------
  166. CON_COMMAND_F( replay_add_test_client_history_entry, "Add a test entry to the replay client history manager", 0 )
  167. {
  168. // Record in client history
  169. extern ConVar replay_demolifespan;
  170. CClientReplayHistoryEntryData *pNewEntry = new CClientReplayHistoryEntryData();
  171. if ( !pNewEntry )
  172. return;
  173. tm now;
  174. Plat_GetLocalTime( &now );
  175. time_t now_time_t = mktime( &now );
  176. pNewEntry->m_nRecordTime = static_cast< int >( now_time_t );
  177. pNewEntry->m_nLifeSpan = replay_demolifespan.GetInt() * 24 * 3600;
  178. pNewEntry->m_DemoLength.SetSeconds( 0 );
  179. V_strcpy( pNewEntry->m_szFilename, "test_filename.dem" );
  180. V_strcpy( pNewEntry->m_szMapName, "mapname" );
  181. V_strcpy( pNewEntry->m_szServerAddress, "192.168.0.1" );
  182. pNewEntry->m_nBytesTransferred = 0;
  183. pNewEntry->m_bTransferComplete = false;
  184. pNewEntry->m_nSize = atoi( args[3] );
  185. pNewEntry->m_bTransferring = false;
  186. pNewEntry->m_nTransferId = -1;
  187. if ( !g_pClientReplayHistoryManager->RecordEntry( pNewEntry ) )
  188. {
  189. Warning( "Replay: Failed to record entry.\n" );
  190. }
  191. }
  192. //----------------------------------------------------------------------------------------
  193. class CClientReplayHistoryManager : public CBaseReplayHistoryManager< CClientReplayHistoryEntryData >
  194. {
  195. public:
  196. virtual const char *GetCacheFilename() const { return REPLAY_HISTORY_FILE_CLIENT; }
  197. virtual void Update()
  198. {
  199. if ( !GetBaseLocalClient().m_NetChannel )
  200. return;
  201. FOR_EACH_LL( m_lstEntries, i )
  202. {
  203. CClientReplayHistoryEntryData *pEntry = m_lstEntries[ i ];
  204. if ( !pEntry->m_bTransferComplete )
  205. {
  206. GetBaseLocalClient().m_NetChannel->GetStreamProgress( FLOW_INCOMING, &pEntry->m_nBytesTransferred, &pEntry->m_nSize );
  207. }
  208. }
  209. }
  210. virtual bool RecordEntry( CBaseReplayHistoryEntryData *pNewEntry )
  211. {
  212. if ( !IsInitialized() || !pNewEntry )
  213. return false;
  214. m_lstEntries.AddToTail( static_cast< CClientReplayHistoryEntryData * >( pNewEntry ) );
  215. // Write all entries to disk now, just to be safe
  216. FlushEntriesToDisk();
  217. return true;
  218. }
  219. virtual void RecordAdditionalEntryData( const CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement )
  220. {
  221. const CClientReplayHistoryEntryData *pClientEntry = static_cast< const CClientReplayHistoryEntryData *>( pEntry );
  222. pElement->SetValue( "server", pClientEntry->m_szServerAddress );
  223. }
  224. virtual void LoadAdditionalEntryData( CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement )
  225. {
  226. CClientReplayHistoryEntryData *pClientEntry = static_cast< CClientReplayHistoryEntryData *>( pEntry );
  227. V_strcpy( pClientEntry->m_szServerAddress, pElement->GetValueString( "server" ) );
  228. }
  229. };
  230. //----------------------------------------------------------------------------------------
  231. class CServerReplayHistoryManager : public CBaseReplayHistoryManager< CServerReplayHistoryEntryData >
  232. {
  233. public:
  234. CServerReplayHistoryManager()
  235. : m_flNextScheduledCleanup( 0.0f )
  236. {
  237. }
  238. virtual const char *GetCacheFilename() const { return REPLAY_HISTORY_FILE_SERVER; }
  239. // To be used with UpdateDemoFileEntries()
  240. enum EUpdateDemoFileEntryFlags
  241. {
  242. UPDATE_DELETESTALEFROMDISK = 0x1, // Delete stale demos from disk
  243. UPDATE_PRINTSTATS = 0x2, // Print statistics on all files
  244. UPDATE_SYNC = 0x4, // If the file does not exist on disk anymore, remove it from the history file
  245. UPDATE_REMOVEEXPIREDENTRIES = 0x8, // Remove any expired entries and flush to disk
  246. };
  247. bool UpdateDemoFileEntries( int nFlags )
  248. {
  249. bool bRemovedAny = false;
  250. bool bFlushToDisk = false;
  251. time_t now = time( NULL );
  252. if ( nFlags & UPDATE_PRINTSTATS )
  253. {
  254. Msg( "\nReplay history stats\n" );
  255. Msg( "----------------------------------------------------\n" );
  256. }
  257. int i = m_lstEntries.Head();
  258. while ( i != m_lstEntries.InvalidIndex() )
  259. {
  260. CServerReplayHistoryEntryData *pEntry = static_cast< CServerReplayHistoryEntryData *>( m_lstEntries[ i ] );
  261. time_t recordtime = static_cast< time_t >( pEntry->m_nRecordTime + pEntry->m_nLifeSpan );
  262. double delta = difftime( recordtime, now );
  263. // If the file is no longer on disk and it should be
  264. if ( ( nFlags & UPDATE_SYNC ) &&
  265. ( pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXISTS ) &&
  266. !g_pFullFileSystem->FileExists( pEntry->m_szFilename ) )
  267. {
  268. pEntry->m_nFileStatus = CServerReplayHistoryEntryData::FILESTATUS_NOTONDISK;
  269. bFlushToDisk = true;
  270. }
  271. // Stale demo file?
  272. bool bStale = false;
  273. if ( pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXISTS && delta <= 0 )
  274. {
  275. bRemovedAny = true;
  276. bStale = true;
  277. // Delete the file from disk
  278. if ( g_pFullFileSystem->FileExists( pEntry->m_szFilename ) )
  279. {
  280. if ( nFlags & UPDATE_DELETESTALEFROMDISK )
  281. {
  282. Assert( 0 ); // Just making sure this gets hit...
  283. Msg( "Replay: Removing stale demo \"%s\" from disk.\n", pEntry->m_szFilename );
  284. // Remove the file from disk
  285. g_pFullFileSystem->RemoveFile( pEntry->m_szFilename );
  286. // Mark as deleted
  287. pEntry->m_nFileStatus = CServerReplayHistoryEntryData::FILESTATUS_EXPIRED;
  288. bFlushToDisk = true;
  289. }
  290. }
  291. }
  292. // Print stats if necessary
  293. if ( nFlags & UPDATE_PRINTSTATS )
  294. {
  295. static const int nSecsPerDay = 86400;
  296. int nDays = (int)delta / nSecsPerDay;
  297. int nHours = (int)delta % nSecsPerDay / 3600;
  298. int nMins = (int)delta % 60;
  299. Msg( "Demo \"%s\" ", pEntry->m_szFilename );
  300. if ( pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXPIRED )
  301. {
  302. Msg( "expired and was removed from disk.\n" );
  303. }
  304. else if ( pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXISTS )
  305. {
  306. Msg( "expires in %i days, %i hours, %i mins.\n", nDays, nHours, nMins );
  307. }
  308. else
  309. {
  310. Msg( "not found on disk.\n" );
  311. }
  312. }
  313. int itCurrent = i;
  314. // Update iterator before we do any syncing
  315. i = m_lstEntries.Next( i );
  316. // Sync what's in memory with what's actually on disk
  317. if ( ( nFlags & UPDATE_REMOVEEXPIREDENTRIES ) &&
  318. pEntry->m_nFileStatus == CServerReplayHistoryEntryData::FILESTATUS_EXPIRED )
  319. {
  320. // Remove the element
  321. m_lstEntries.Remove( itCurrent );
  322. AssertValidReadWritePtr( pEntry ); // TODO: Make sure this test fails!
  323. bFlushToDisk = true;
  324. }
  325. }
  326. // Flush?
  327. if ( bFlushToDisk )
  328. {
  329. FlushEntriesToDisk();
  330. }
  331. if ( nFlags & UPDATE_PRINTSTATS )
  332. {
  333. Msg( "\n" );
  334. }
  335. return bRemovedAny;
  336. }
  337. virtual void Update()
  338. {
  339. if ( host_time < m_flNextScheduledCleanup )
  340. return;
  341. extern ConVar replay_cleanup_time;
  342. m_flNextScheduledCleanup += replay_cleanup_time.GetInt() * 3600;
  343. UpdateDemoFileEntries( UPDATE_SYNC | UPDATE_DELETESTALEFROMDISK );
  344. }
  345. virtual bool RecordEntry( CBaseReplayHistoryEntryData *pNewEntry )
  346. {
  347. if ( !IsInitialized() || !pNewEntry )
  348. return false;
  349. m_lstEntries.AddToTail( static_cast< CServerReplayHistoryEntryData * >( pNewEntry ) );
  350. // Write all entries to disk now, just to be safe
  351. FlushEntriesToDisk();
  352. return true;
  353. }
  354. virtual void RecordAdditionalEntryData( const CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement )
  355. {
  356. const CServerReplayHistoryEntryData *pServerEntry = static_cast< const CServerReplayHistoryEntryData *>( pEntry );
  357. pElement->SetValue( "client_steam_id", pServerEntry->m_uClientSteamId );
  358. pElement->SetValue( "file_status", (int)pServerEntry->m_nFileStatus );
  359. }
  360. virtual void LoadAdditionalEntryData( CBaseReplayHistoryEntryData *pEntry, CDmxElement *pElement )
  361. {
  362. CServerReplayHistoryEntryData *pServerEntry = static_cast< CServerReplayHistoryEntryData *>( pEntry );
  363. pServerEntry->m_uClientSteamId = pElement->GetValue( "client_steam_id", (uint64)0 );
  364. pServerEntry->m_nFileStatus = (CServerReplayHistoryEntryData::EFileStatus)pElement->GetValue< int >( "file_status", (int)CServerReplayHistoryEntryData::FILESTATUS_EXISTS );
  365. }
  366. private:
  367. float m_flNextScheduledCleanup;
  368. };
  369. //----------------------------------------------------------------------------------------
  370. inline CServerReplayHistoryManager *GetServerReplayHistoryManager()
  371. {
  372. return static_cast< CServerReplayHistoryManager * >( g_pServerReplayHistoryManager );
  373. }
  374. //----------------------------------------------------------------------------------------
  375. CON_COMMAND_F( replay_delete_stale_demos, "Deletes stale replay demo files", FCVAR_GAMEDLL | FCVAR_DONTRECORD )
  376. {
  377. if ( GetServerReplayHistoryManager() &&
  378. !GetServerReplayHistoryManager()->UpdateDemoFileEntries( CServerReplayHistoryManager::UPDATE_DELETESTALEFROMDISK ) )
  379. {
  380. Msg( "No demos were deleted.\n" );
  381. }
  382. }
  383. //----------------------------------------------------------------------------------------
  384. CON_COMMAND_F( replay_print_history_stats, "Deletes stale replay demo files", FCVAR_GAMEDLL | FCVAR_DONTRECORD )
  385. {
  386. if ( GetServerReplayHistoryManager() )
  387. {
  388. GetServerReplayHistoryManager()->UpdateDemoFileEntries( CServerReplayHistoryManager::UPDATE_PRINTSTATS );
  389. }
  390. }
  391. //----------------------------------------------------------------------------------------
  392. CON_COMMAND_F( replay_remove_expired_entries, "Removes all expired entries from replay history", FCVAR_GAMEDLL | FCVAR_DONTRECORD )
  393. {
  394. if ( GetServerReplayHistoryManager() )
  395. {
  396. GetServerReplayHistoryManager()->UpdateDemoFileEntries( CServerReplayHistoryManager::UPDATE_REMOVEEXPIREDENTRIES );
  397. }
  398. }
  399. //----------------------------------------------------------------------------------------
  400. IReplayHistoryManager *CreateServerReplayHistoryManager()
  401. {
  402. return new CServerReplayHistoryManager();
  403. }
  404. //----------------------------------------------------------------------------------------
  405. static CClientReplayHistoryManager s_ClientReplayHistoryManager;
  406. IReplayHistoryManager *g_pClientReplayHistoryManager = &s_ClientReplayHistoryManager;
  407. IReplayHistoryManager *g_pServerReplayHistoryManager = NULL;
  408. // Expose interface to the client (needed by demo browser) - no need to do this for the server.
  409. EXPOSE_SINGLE_INTERFACE_GLOBALVAR(
  410. CClientReplayHistoryManager,
  411. IReplayHistoryManager,
  412. REPLAYHISTORYMANAGER_INTERFACE_VERSION,
  413. s_ClientReplayHistoryManager
  414. );
  415. //----------------------------------------------------------------------------------------
  416. #endif