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.

679 lines
21 KiB

  1. //======= Copyright (c) 1996-2009, Valve Corporation, All rights reserved. ======
  2. //
  3. // TODO:
  4. // - USE A MEMPOOL OF SOME SORT
  5. // - GET STALE FRAME REMOVAL WORKING
  6. // - MAKE THREAD SAFE - NEED TO BE ABLE TO WRITE A .DEM FILE ON A SEP THREAD AND
  7. // NOT BE THROWING AWAY ELEMENTS THAT ARE BEING READ. NEED A FLAG IN DATACHUNK
  8. // FOR WHETHER AN INSTANCE IS "IN USE," IE BEING READ TO WRITE TO A DEM FILE.
  9. //
  10. //===============================================================================
  11. #include "demobuffer.h"
  12. #include "edict.h"
  13. #include "host.h"
  14. #include "tier1/mempool.h"
  15. #include "vstdlib/jobthread.h"
  16. #include "replayserver.h" // TODO: Remove
  17. #include "sv_client.h"
  18. #ifndef DEDICATED
  19. #include "cdll_int.h"
  20. #include "client.h"
  21. #endif
  22. #include "tier0/memdbgon.h" // NOTE: Must go last!
  23. #ifndef DEDICATED
  24. #if !defined( _DEBUG )
  25. // These are the buffers defining how demo data is flushed to disk:
  26. // We allocate 5 MB buffer (worth about 5 min of gameplay)
  27. // once over 4 MB is used up we will commit the first 1 MB to disk
  28. // This throttles disk IO, and ensures that the last 3 MB are always
  29. // contained in memory and not committed to disk to prevent file peeking
  30. // for cheating purposes during gameplay
  31. #define DISK_DEMO_BUFFER_TOTAL_SIZE (5*1024*1024)
  32. #define DISK_DEMO_BUFFER_FLUSH_CONSIDER_SIZE (4*1024*1024)
  33. #define DISK_DEMO_BUFFER_FLUSH_TODISK_SIZE (1*1024*1024)
  34. #else
  35. // Smaller sizes in debug engine.dll build to hammer on the subsystems involved
  36. #define DISK_DEMO_BUFFER_TOTAL_SIZE (5*20*1024)
  37. #define DISK_DEMO_BUFFER_FLUSH_CONSIDER_SIZE (4*20*1024)
  38. #define DISK_DEMO_BUFFER_FLUSH_TODISK_SIZE (1*20*1024)
  39. #endif
  40. #endif
  41. //-----------------------------------------------------------------------------
  42. // Specialty class with overrides for stream buffer
  43. //-----------------------------------------------------------------------------
  44. class CDiskDemoBuffer : public IDemoBuffer
  45. {
  46. public:
  47. CDiskDemoBuffer()
  48. : m_pBuffer( NULL )
  49. {
  50. m_nDecodedOffset = -1;
  51. }
  52. ~CDiskDemoBuffer()
  53. {
  54. m_pBuffer->Close();
  55. delete m_pBuffer;
  56. }
  57. virtual bool Init( DemoBufferInitParams_t const& params )
  58. {
  59. // Convert to proper type
  60. StreamDemoBufferInitParams_t const* pParams = dynamic_cast< StreamDemoBufferInitParams_t const* >( &params ); Assert( pParams );
  61. // Allocate buffer
  62. m_pBuffer = new CUtlStreamBuffer();
  63. if ( !m_pBuffer )
  64. return false;
  65. #ifndef DEDICATED
  66. // Force a very large memory buffer on the clients, this prevents peeking into the demo stream
  67. m_pBuffer->EnsureCapacity( DISK_DEMO_BUFFER_TOTAL_SIZE );
  68. #endif
  69. // Demo files are always little endian
  70. m_pBuffer->SetBigEndian( false );
  71. m_bufDecoded.SetBigEndian( false );
  72. // Open the file
  73. // m_pBuffer->Open( pParams->pFilename, pParams->pszPath, pParams->nFlags, pParams->nOpenFileFlags ); // For main integration...
  74. m_pBuffer->Open( pParams->pFilename, pParams->pszPath, pParams->nFlags );
  75. m_nDecodedOffset = -1;
  76. m_pPlaybackParams = NULL;
  77. #ifndef DEDICATED
  78. extern IDemoPlayer *demoplayer;
  79. extern IBaseClientDLL *g_ClientDLL;
  80. if ( demoplayer && g_ClientDLL )
  81. {
  82. m_pPlaybackParams = demoplayer->GetDemoPlaybackParameters();
  83. }
  84. #endif
  85. return IsInitialized();
  86. }
  87. virtual void NotifySignonComplete() {}
  88. virtual void WriteHeader( void const *pData, int nSize )
  89. {
  90. // Byteswap
  91. demoheader_t littleEndianHeader = *((demoheader_t*)pData);
  92. ByteSwap_demoheader_t( littleEndianHeader );
  93. // Goto file start
  94. SeekPut( true, 0 );
  95. // Write
  96. Put( pData, nSize );
  97. }
  98. virtual void NotifyBeginFrame() {}
  99. virtual void NotifyEndFrame() {}
  100. virtual void PutChar( char c ) { m_pBuffer->PutChar( c ); }
  101. virtual void PutUnsignedChar( unsigned char uc ) { m_pBuffer->PutUnsignedChar( uc ); }
  102. virtual void PutInt( int i ) { m_pBuffer->PutInt( i ); }
  103. virtual void WriteTick( int nTick ) { m_pBuffer->PutInt( nTick ); }
  104. virtual char GetChar() OVERRIDE
  105. {
  106. COnTheFlyDemoBufferReadInfo readRequest( m_pPlaybackParams, m_pBuffer, &m_bufDecoded, &m_nDecodedOffset, sizeof( char ) );
  107. return readRequest.GetReadBuffer()->GetChar();
  108. }
  109. virtual unsigned char GetUnsignedChar() OVERRIDE
  110. {
  111. COnTheFlyDemoBufferReadInfo readRequest( m_pPlaybackParams, m_pBuffer, &m_bufDecoded, &m_nDecodedOffset, sizeof( unsigned char ) );
  112. return readRequest.GetReadBuffer()->GetUnsignedChar();
  113. }
  114. virtual int GetInt() OVERRIDE
  115. {
  116. COnTheFlyDemoBufferReadInfo readRequest( m_pPlaybackParams, m_pBuffer, &m_bufDecoded, &m_nDecodedOffset, sizeof( int ) );
  117. return readRequest.GetReadBuffer()->GetInt();
  118. }
  119. virtual void Get( void* pMem, int size ) OVERRIDE
  120. {
  121. COnTheFlyDemoBufferReadInfo readRequest( m_pPlaybackParams, m_pBuffer, &m_bufDecoded, &m_nDecodedOffset, size );
  122. readRequest.GetReadBuffer()->Get( pMem, size );
  123. }
  124. virtual void Put( const void* pMem, int size )
  125. {
  126. m_pBuffer->Put( pMem, size );
  127. #ifndef DEDICATED
  128. if ( ( size > 0 ) && ( m_pBuffer->TellPut() > 0 ) &&
  129. ( ( ( ( char* ) m_pBuffer->PeekPut() ) - ( ( char * ) m_pBuffer->Base() ) ) > (
  130. ( GetBaseLocalClient().IsActive() && GetBaseLocalClient().ishltv ) ? 2048 : DISK_DEMO_BUFFER_FLUSH_CONSIDER_SIZE
  131. ) ) )
  132. { // Periodically try to flush the demo buffer to disk
  133. m_pBuffer->TryFlushToFile( DISK_DEMO_BUFFER_FLUSH_TODISK_SIZE );
  134. }
  135. #endif
  136. }
  137. virtual bool IsValid() const { return m_pBuffer && m_pBuffer->IsValid(); }
  138. virtual bool IsInitialized() const { return IsValid() && m_pBuffer->IsOpen(); }
  139. inline CUtlBuffer::SeekType_t GetSeekType( bool bAbsolute ) { return bAbsolute ? CUtlBuffer::SEEK_HEAD : CUtlBuffer::SEEK_CURRENT; }
  140. // Change where I'm writing (put)/reading (get)
  141. virtual void SeekPut( bool bAbsolute, int offset ) { m_pBuffer->SeekPut( GetSeekType( bAbsolute ), offset ); }
  142. virtual void SeekGet( bool bAbsolute, int offset ) { m_pBuffer->SeekGet( GetSeekType( bAbsolute ), offset ); }
  143. // Where am I writing (put)/reading (get)?
  144. virtual int TellPut( ) const { return m_pBuffer->TellPut(); }
  145. virtual int TellGet( ) const { return m_pBuffer->TellGet(); }
  146. virtual int TellMaxPut( ) const { return m_pBuffer->TellMaxPut(); }
  147. virtual void UpdateStartTick( int& nStartTick ) const {}
  148. virtual void DumpToFile( char const* pFilename, const demoheader_t &header ) const {}
  149. private:
  150. CUtlStreamBuffer *m_pBuffer;
  151. CUtlBuffer m_bufDecoded;
  152. int m_nDecodedOffset;
  153. CDemoPlaybackParameters_t const *m_pPlaybackParams;
  154. class COnTheFlyDemoBufferReadInfo
  155. {
  156. public:
  157. COnTheFlyDemoBufferReadInfo( CDemoPlaybackParameters_t const *pPlaybackParams, CUtlBuffer *pRawData, CUtlBuffer *pDecodeCache, int *pDecodedOffset, int numBytesRequired )
  158. {
  159. m_nReadFromBufferOriginalSeekPos = 0;
  160. #ifndef DEDICATED
  161. if ( pPlaybackParams )
  162. {
  163. // Read from the nearest 16-byte aligned location
  164. int nOriginalGet = pRawData->TellGet();
  165. if ( ( (*pDecodedOffset) < 0 ) || // nothing decoded
  166. ( nOriginalGet < (*pDecodedOffset) ) || // reading earlier
  167. ( nOriginalGet + numBytesRequired > (*pDecodedOffset) + pDecodeCache->TellPut() ) ) // could read beyond decoded buffer
  168. {
  169. int nNearestAlignedLocation = nOriginalGet &~0xF;
  170. int blockRead = ( nOriginalGet + numBytesRequired - nNearestAlignedLocation + 0xF )&~0xF;
  171. blockRead = MAX( 1024, blockRead ); // decrypt chunks of 1K bytes at a time
  172. *pDecodedOffset = nNearestAlignedLocation;
  173. pDecodeCache->EnsureCapacity( blockRead );
  174. int numBytesSeekBack = nOriginalGet - nNearestAlignedLocation;
  175. if ( numBytesSeekBack )
  176. pRawData->SeekGet( pRawData->SEEK_CURRENT, - numBytesSeekBack ); // seek back
  177. int numBytes = MIN( blockRead, pRawData->TellMaxPut() - nNearestAlignedLocation );
  178. pRawData->Get( pDecodeCache->Base(), numBytes );
  179. m_nReadFromBufferOriginalSeekPos += -numBytes+numBytesSeekBack;
  180. pDecodeCache->SeekPut( pDecodeCache->SEEK_HEAD, numBytes );
  181. // Decode the chunk
  182. extern IBaseClientDLL *g_ClientDLL;
  183. g_ClientDLL->PrepareSignedEvidenceData( pDecodeCache->Base(), numBytes, pPlaybackParams );
  184. }
  185. int nSeekInDecodedBuffer = nOriginalGet - *pDecodedOffset;
  186. pDecodeCache->SeekGet( pDecodeCache->SEEK_HEAD, nSeekInDecodedBuffer );
  187. //
  188. // Set the read state
  189. //
  190. m_pReadFromBuffer = pDecodeCache;
  191. m_pSeekSyncBuffer = pRawData;
  192. m_nReadFromBufferOriginalSeekPos += -nSeekInDecodedBuffer;
  193. return;
  194. }
  195. #endif
  196. //
  197. // Read raw state
  198. //
  199. m_pReadFromBuffer = pRawData;
  200. m_pSeekSyncBuffer = NULL;
  201. }
  202. ~COnTheFlyDemoBufferReadInfo()
  203. {
  204. if ( m_pSeekSyncBuffer && m_pReadFromBuffer )
  205. m_pSeekSyncBuffer->SeekGet( m_pSeekSyncBuffer->SEEK_CURRENT, m_pReadFromBuffer->TellGet() + m_nReadFromBufferOriginalSeekPos );
  206. }
  207. CUtlBuffer * GetReadBuffer() const { return m_pReadFromBuffer; }
  208. private:
  209. CUtlBuffer *m_pReadFromBuffer;
  210. CUtlBuffer *m_pSeekSyncBuffer;
  211. int m_nReadFromBufferOriginalSeekPos;
  212. };
  213. };
  214. //-----------------------------------------------------------------------------
  215. // Specialty class with overrides for stream buffer
  216. //-----------------------------------------------------------------------------
  217. #if defined( REPLAY_ENABLED )
  218. class CMemoryDemoBuffer : public IDemoBuffer
  219. {
  220. private:
  221. static int const CACHE_SIZE = 1024 * 512;
  222. uint8* m_pDataCache; // Data cache for temporary writing
  223. uint8* m_pWrite; // Current write position (based on m_pDataCache)
  224. int m_nBufferSize; // Total buffer size
  225. int m_nMaxPut; // What's the most I've ever written?
  226. int m_nCurrentTickOffset; // Offset (relative to m_pDataCache) of tick - needed so we can rewrite ticks before dumping to disk
  227. struct DataChunk_t
  228. {
  229. int nCurrentTickOffset;
  230. int nTickcount;
  231. int nDeltaTickcount;
  232. int nSize;
  233. uint8 pData[1];
  234. };
  235. inline DataChunk_t* AllocDataChunk( int nSize, int nCurrentTickOffset )
  236. {
  237. Assert( nCurrentTickOffset >= 0 );
  238. int nActualSize = sizeof(DataChunk_t) + nSize - 1; Assert( nActualSize < CACHE_SIZE );
  239. DataChunk_t* pNewFrame = (DataChunk_t*)new uint8[ nActualSize ];
  240. pNewFrame->nSize = nSize;
  241. pNewFrame->nCurrentTickOffset = nCurrentTickOffset;
  242. pNewFrame->nTickcount = -1;
  243. // TODO: pass in the delta tick - get rid of #include "replay" etc.
  244. pNewFrame->nDeltaTickcount = ( replay && replay->m_MasterClient ) ? replay->m_MasterClient->m_nDeltaTick : -1;
  245. return pNewFrame;
  246. }
  247. bool m_bSignonComplete;
  248. DataChunk_t* m_pSignonData;
  249. typedef unsigned short Iterator_t;
  250. CUtlLinkedList< DataChunk_t*, Iterator_t > m_lstFrames; // Represents a list of demo frames
  251. inline int GetTickCount()
  252. {
  253. extern CGlobalVars g_ServerGlobalVariables;
  254. return g_ServerGlobalVariables.tickcount;
  255. }
  256. void RemoveStaleFrames()
  257. {
  258. // Don't remove any frames in the midst of a write operation
  259. if ( m_nWriteCount > 0 )
  260. return;
  261. extern ConVar replay_movielength;
  262. #ifdef _DEBUG
  263. int nNumFramesRemoved = 0;
  264. #endif
  265. // Here we remove any frames that are beyond the length of the movie.
  266. Iterator_t i = m_lstFrames.Head();
  267. while ( i != m_lstFrames.InvalidIndex() )
  268. {
  269. if ( m_lstFrames[ i ]->nTickcount >= GetTickCount() - TIME_TO_TICKS( replay_movielength.GetInt() ) )
  270. break;
  271. m_lstFrames.Remove( i );
  272. i = m_lstFrames.Head();
  273. #ifdef _DEBUG
  274. ++nNumFramesRemoved;
  275. #endif
  276. }
  277. #ifdef _DEBUG
  278. if ( nNumFramesRemoved > 0 )
  279. {
  280. DevMsg( "Replay: Removed %d frames(s) from recording buffer.\n", nNumFramesRemoved );
  281. }
  282. #endif
  283. }
  284. public:
  285. CMemoryDemoBuffer()
  286. :
  287. m_nBufferSize( 0 ),
  288. m_nMaxPut( 0 ),
  289. m_nCurrentTickOffset( -1 ),
  290. m_pWrite( NULL ),
  291. m_pSignonData( NULL ),
  292. m_bSignonComplete( false )
  293. {
  294. }
  295. ~CMemoryDemoBuffer()
  296. {
  297. delete [] m_pDataCache;
  298. delete m_pSignonData;
  299. // Free all list entries
  300. m_lstFrames.PurgeAndDeleteElements();
  301. }
  302. virtual bool Init( DemoBufferInitParams_t const& params )
  303. {
  304. m_pDataCache = new uint8[ CACHE_SIZE ];
  305. m_pWrite = m_pDataCache;
  306. return true;
  307. }
  308. virtual bool IsInitialized() const { return m_pDataCache != NULL; }
  309. virtual bool IsValid() const { return IsInitialized(); }
  310. virtual void WriteHeader( const void *pData, int nSize )
  311. {
  312. // There is no need to write the header until we dump the file to disk.
  313. /*
  314. // NOTE: Byteswap happens in dump
  315. // The header gets written twice, once at demo start, and once at demo stop.
  316. // If this is the first time, just write to the beginning of the cache
  317. if ( !m_bSignonComplete )
  318. {
  319. Assert( m_pWrite == m_pDataCache ); // Make sure this is the first thing we're writing
  320. Put( pData, nSize );
  321. }
  322. else // Otherwise, write to the beginning of the header
  323. {
  324. AssertValidWritePtr( m_pHeaderData, nSize );
  325. V_memcpy( m_pHeaderData, pData, nSize );
  326. }
  327. */
  328. }
  329. virtual void NotifySignonComplete()
  330. {
  331. Assert( !m_pSignonData );
  332. // Compute size and allocate
  333. int nSize = m_pWrite - m_pDataCache; Assert( nSize >= 0 );
  334. m_pSignonData = AllocDataChunk( nSize, -1 );
  335. // NOTE: No need to set m_pSignonData->nTickcount.
  336. // We're done with signon data, copy it over from the cache
  337. V_memcpy( m_pSignonData->pData, m_pDataCache, nSize );
  338. m_pWrite = NULL;
  339. m_bSignonComplete = true;
  340. }
  341. virtual void NotifyBeginFrame()
  342. {
  343. if ( !m_bSignonComplete )
  344. return;
  345. Assert( m_pWrite == 0 );
  346. m_pWrite = m_pDataCache;
  347. }
  348. virtual void NotifyEndFrame()
  349. {
  350. if ( !m_bSignonComplete )
  351. return;
  352. RemoveStaleFrames();
  353. // Allocate a new data chunk
  354. int nSize = m_pWrite - m_pDataCache; Assert( nSize >= 0 );
  355. DataChunk_t* pNewFrame = AllocDataChunk( nSize, m_nCurrentTickOffset );
  356. // Set the time
  357. pNewFrame->nTickcount = GetTickCount();
  358. // Copy data from cache to new frame
  359. V_memcpy( pNewFrame->pData, m_pDataCache, nSize );
  360. // Add new frame to list
  361. m_lstFrames.AddToTail( pNewFrame );
  362. #ifdef _DEBUG
  363. m_pWrite = NULL;
  364. #endif
  365. }
  366. // Change where I'm writing (put)/reading (get)
  367. virtual void SeekGet( bool bAbsolute, int offset )
  368. {
  369. // Don't call this.
  370. Assert( 0 );
  371. }
  372. virtual void SeekPut( bool bAbsolute, int nOffset )
  373. {
  374. // The only time this should get called is if we are about to write the header.
  375. Assert( bAbsolute && nOffset == 0 );
  376. }
  377. // Where am I writing (put)/reading (get)?
  378. virtual int TellPut( ) const { return m_pWrite - m_pDataCache; }
  379. virtual int TellGet( ) const { Assert( 0 ); return 0; }
  380. // What's the most I've ever written?
  381. virtual int TellMaxPut( ) const { return m_nMaxPut; }
  382. // Get functions should never get called.
  383. virtual char GetChar() { Assert( 0 ); return 0; }
  384. virtual unsigned char GetUnsignedChar() { Assert( 0 ); return 0; }
  385. virtual int GetInt() { Assert( 0 ); return 0; }
  386. virtual void Get( void* pMem, int size ) { Assert( 0 ); }
  387. virtual void PutChar( char c ) { Put( &c, sizeof( c ) ); }
  388. virtual void PutUnsignedChar( unsigned char uc ) { Put( &uc, sizeof( uc ) ); }
  389. virtual void PutInt( int i ) { Put( &i, sizeof( i ) ); }
  390. virtual void Put( const void* pMem, int nSize )
  391. {
  392. Assert( m_pWrite - m_pDataCache + nSize < CACHE_SIZE );
  393. V_memcpy( m_pWrite, pMem, nSize );
  394. m_pWrite += nSize;
  395. m_nBufferSize += nSize;
  396. m_nMaxPut = MAX( m_nMaxPut, nSize );
  397. }
  398. virtual void WriteTick( int nTick )
  399. {
  400. // Cache the relative position of the tick in memory for the given frame
  401. m_nCurrentTickOffset = m_pWrite - m_pDataCache;
  402. // Write the tick
  403. PutInt( nTick );
  404. }
  405. //
  406. // For thread safety - this counter keeps us from removing stale frames while writing a .dem file.
  407. // The idea here is that if the counter is anything but zero we should not remove stale frames.
  408. // We increment before creating a new job, and decrement from within that job, at the end. We
  409. // pass an iterator as an argument into the job's constructor so we know where to stop iterating
  410. // across the frame list.
  411. //
  412. mutable CInterlockedIntT<int> m_nWriteCount;
  413. //
  414. // Threaded .dem file write
  415. //
  416. class CDemWriteJob : public CJob
  417. {
  418. public:
  419. CDemWriteJob( const CMemoryDemoBuffer *pDemobuffer, const char *pFilename, Iterator_t itTail, const demoheader_t &header )
  420. : m_pDemobuffer( pDemobuffer ), m_pFilename( pFilename ), m_itTail( itTail ), m_Header( header )
  421. {
  422. Assert( pFilename && pFilename[0] );
  423. }
  424. virtual JobStatus_t DoExecute()
  425. {
  426. // TODO: Does it make sense to return JOB_OK here even on failure?
  427. JobStatus_t nResult = JOB_OK;
  428. if ( m_pFilename && m_pFilename[0] != '\0' )
  429. {
  430. // Open the file
  431. CUtlStreamBuffer buf( m_pFilename, NULL );
  432. if ( !buf.IsOpen() )
  433. {
  434. Warning( "demobuffer: Failed to open file for writing, %s\n", m_pFilename );
  435. }
  436. else
  437. {
  438. // NOTE: We include the sync tick frame as part of our signon data, which makes the header signon length vary by 6 bytes
  439. // (2 chars and 1 int) from our own signon data size.
  440. const int nTickSyncFrameSize = 6;
  441. Assert( m_pDemobuffer->m_pSignonData->nSize == m_Header.signonlength + nTickSyncFrameSize );
  442. // Compute adjusted time/ticks/frames, since we may have removed stale frames
  443. const CUtlLinkedList< CMemoryDemoBuffer::DataChunk_t*, CMemoryDemoBuffer::Iterator_t > &lstFrames = m_pDemobuffer->m_lstFrames;
  444. Iterator_t itHead = lstFrames.Head();
  445. Iterator_t itTail = lstFrames.Tail();
  446. Assert( itHead != lstFrames.InvalidIndex() );
  447. Assert( itTail != lstFrames.InvalidIndex() );
  448. const DataChunk_t *pHead = lstFrames.Element( itHead );
  449. const DataChunk_t *pTail = lstFrames.Element( itTail );
  450. demoheader_t littleEndianHeader = m_Header;
  451. littleEndianHeader.playback_time = TICKS_TO_TIME( pTail->nTickcount - pHead->nTickcount );
  452. littleEndianHeader.playback_ticks = pTail->nTickcount - pHead->nTickcount;
  453. littleEndianHeader.playback_frames = lstFrames.Count();
  454. // Byteswap
  455. ByteSwap_demoheader_t( littleEndianHeader );
  456. // Write header
  457. buf.Put( &littleEndianHeader, sizeof( littleEndianHeader ) );
  458. // Write signon data
  459. AssertValidReadPtr( m_pDemobuffer->m_pSignonData );
  460. buf.Put( m_pDemobuffer->m_pSignonData->pData, m_pDemobuffer->m_pSignonData->nSize );
  461. #if 1
  462. Iterator_t itStart = m_pDemobuffer->m_lstFrames.Head();
  463. #else
  464. // TEST: Skip the first one
  465. Iterator_t itStart = m_pDemobuffer->m_lstFrames.Next( m_lstFrames.Head() );
  466. #endif
  467. // Get first recording tick (NOTE: not start global tick). Recording ticks start at 0 but
  468. // when we remove stale frames the first recording tick becomes greater than zero. We use
  469. // nStartTick here to shift all frame recording ticks down nStartTick ticks.
  470. int nStartTick;
  471. if ( itHead != m_pDemobuffer->m_lstFrames.InvalidIndex() )
  472. {
  473. DataChunk_t *pFrame = m_pDemobuffer->m_lstFrames[ itHead ];
  474. int *pTickData = reinterpret_cast< int * >( pFrame->pData + pFrame->nCurrentTickOffset );
  475. V_memcpy( &nStartTick, pTickData, sizeof( int ) );
  476. }
  477. // Write frames
  478. for ( Iterator_t i = itStart; i != m_pDemobuffer->m_lstFrames.InvalidIndex(); i = m_pDemobuffer->m_lstFrames.Next( i ) )
  479. {
  480. DataChunk_t *pFrame = m_pDemobuffer->m_lstFrames[ i ]; Assert( pFrame->nSize >= 0 );
  481. // Overwrite the tick for the given frame if one exists
  482. if ( nStartTick > 0 && pFrame->nCurrentTickOffset >= 0 )
  483. {
  484. int nNewTick;
  485. int *pTickData = reinterpret_cast< int * >( pFrame->pData + pFrame->nCurrentTickOffset );
  486. // Copy tick from buffer to nNewTick
  487. V_memcpy( &nNewTick, pTickData, sizeof( int ) );
  488. // Subtract out start tick
  489. nNewTick -= nStartTick;
  490. // Copy back to buffer
  491. V_memcpy( pTickData, &nNewTick, sizeof( int ) );
  492. }
  493. buf.Put( pFrame->pData, pFrame->nSize );
  494. }
  495. // Write dem_stop cmd
  496. buf.PutUnsignedChar( dem_stop );
  497. buf.PutInt( m_pDemobuffer->m_lstFrames.Element( m_pDemobuffer->m_lstFrames.Tail() )->nTickcount );
  498. buf.PutChar( 0 );
  499. buf.Close();
  500. }
  501. }
  502. // Decrement the write counter
  503. m_pDemobuffer->m_nWriteCount--;
  504. return nResult;
  505. }
  506. private:
  507. const CMemoryDemoBuffer *m_pDemobuffer;
  508. const char *m_pFilename;
  509. Iterator_t m_itTail;
  510. const demoheader_t &m_Header;
  511. };
  512. virtual void UpdateStartTick( int& nStartTick ) const
  513. {
  514. Iterator_t itHead = m_lstFrames.Head();
  515. if ( itHead == m_lstFrames.InvalidIndex() )
  516. return;
  517. nStartTick = m_lstFrames[ itHead ]->nTickcount;
  518. }
  519. virtual void DumpToFile( const char *pFilename, const demoheader_t &header ) const
  520. {
  521. Assert( !IsX360() ); // TODO: Not supporting 360 yet. Need alternate thread pool setup to do so.
  522. // HACK:
  523. int n = m_nWriteCount;
  524. // Critical section
  525. m_nWriteCount++;
  526. // Start a new thread
  527. CDemWriteJob* pJob = new CDemWriteJob( this, pFilename, m_lstFrames.Tail(), header );
  528. g_pThreadPool->AddJob( pJob );
  529. while ( m_nWriteCount > n )
  530. DevMsg( "Waiting for file dump to complete\n" );
  531. }
  532. };
  533. #endif
  534. //-----------------------------------------------------------------------------
  535. // Factory function
  536. //-----------------------------------------------------------------------------
  537. IDemoBuffer *CreateDemoBuffer( bool bMemoryBuffer, const DemoBufferInitParams_t& params )
  538. {
  539. IDemoBuffer *pRet;
  540. #if defined( REPLAY_ENABLED )
  541. if ( bMemoryBuffer )
  542. {
  543. pRet = static_cast< IDemoBuffer* >( new CMemoryDemoBuffer() );
  544. }
  545. else
  546. #endif
  547. {
  548. pRet = static_cast< IDemoBuffer* >( new CDiskDemoBuffer() );
  549. }
  550. if ( !pRet->Init( params ) )
  551. {
  552. delete pRet;
  553. return NULL;
  554. }
  555. return pRet;
  556. }