File_SaveGame.cpp 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
  17. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  18. ===========================================================================
  19. */
  20. #pragma hdrstop
  21. #include "../idlib/precompiled.h"
  22. #include "File_SaveGame.h"
  23. /*
  24. TODO: CRC on each block
  25. */
  26. /*
  27. ========================
  28. ZlibAlloc
  29. ========================
  30. */
  31. void * ZlibAlloc( void *opaque, uInt items, uInt size ) {
  32. return Mem_Alloc( items * size, TAG_SAVEGAMES );
  33. }
  34. /*
  35. ========================
  36. ZlibFree
  37. ========================
  38. */
  39. void ZlibFree( void *opaque, void * address ) {
  40. Mem_Free( address );
  41. }
  42. idCVar sgf_threads( "sgf_threads", "2", CVAR_INTEGER, "0 = all foreground, 1 = background write, 2 = background write + compress" );
  43. idCVar sgf_checksums( "sgf_checksums", "1", CVAR_BOOL, "enable save game file checksums" );
  44. idCVar sgf_testCorruption( "sgf_testCorruption", "-1", CVAR_INTEGER, "test corruption at the 128 kB compressed block" );
  45. // this is supposed to get faster going from -15 to -9, but it gets slower as well as worse compression
  46. idCVar sgf_windowBits( "sgf_windowBits", "-15", CVAR_INTEGER, "zlib window bits" );
  47. bool idFile_SaveGamePipelined::cancelToTerminate = false;
  48. class idSGFcompressThread : public idSysThread {
  49. public:
  50. virtual int Run() { sgf->CompressBlock(); return 0; }
  51. idFile_SaveGamePipelined * sgf;
  52. };
  53. class idSGFdecompressThread : public idSysThread {
  54. public:
  55. virtual int Run() { sgf->DecompressBlock(); return 0; }
  56. idFile_SaveGamePipelined * sgf;
  57. };
  58. class idSGFwriteThread : public idSysThread {
  59. public:
  60. virtual int Run() { sgf->WriteBlock(); return 0; }
  61. idFile_SaveGamePipelined * sgf;
  62. };
  63. class idSGFreadThread : public idSysThread {
  64. public:
  65. virtual int Run() { sgf->ReadBlock(); return 0; }
  66. idFile_SaveGamePipelined * sgf;
  67. };
  68. /*
  69. ============================
  70. idFile_SaveGamePipelined::idFile_SaveGamePipelined
  71. ============================
  72. */
  73. idFile_SaveGamePipelined::idFile_SaveGamePipelined() :
  74. mode( CLOSED ),
  75. compressedLength( 0 ),
  76. uncompressedProducedBytes( 0 ),
  77. uncompressedConsumedBytes( 0 ),
  78. compressedProducedBytes( 0 ),
  79. compressedConsumedBytes( 0 ),
  80. dataZlib( NULL ),
  81. bytesZlib( 0 ),
  82. dataIO( NULL ),
  83. bytesIO( 0 ),
  84. zLibFlushType( Z_NO_FLUSH ),
  85. zStreamEndHit( false ),
  86. numChecksums( 0 ),
  87. nativeFile( NULL ),
  88. nativeFileEndHit( false ),
  89. finished( false ),
  90. readThread( NULL ),
  91. writeThread( NULL ),
  92. decompressThread( NULL ),
  93. compressThread( NULL ),
  94. blockFinished( true ),
  95. buildVersion( "" ),
  96. saveFormatVersion( 0 ) {
  97. memset( &zStream, 0, sizeof( zStream ) );
  98. memset( compressed, 0, sizeof( compressed ) );
  99. memset( uncompressed, 0, sizeof( uncompressed ) );
  100. zStream.zalloc = ZlibAlloc;
  101. zStream.zfree = ZlibFree;
  102. }
  103. /*
  104. ============================
  105. idFile_SaveGamePipelined::~idFile_SaveGamePipelined
  106. ============================
  107. */
  108. idFile_SaveGamePipelined::~idFile_SaveGamePipelined() {
  109. Finish();
  110. // free the threads
  111. if ( compressThread != NULL ) {
  112. delete compressThread;
  113. compressThread = NULL;
  114. }
  115. if ( decompressThread != NULL ) {
  116. delete decompressThread;
  117. decompressThread = NULL;
  118. }
  119. if ( readThread != NULL ) {
  120. delete readThread;
  121. readThread = NULL;
  122. }
  123. if ( writeThread != NULL ) {
  124. delete writeThread;
  125. writeThread = NULL;
  126. }
  127. // close the native file
  128. /* if ( nativeFile != NULL ) {
  129. delete nativeFile;
  130. nativeFile = NULL;
  131. } */
  132. dataZlib = NULL;
  133. dataIO = NULL;
  134. }
  135. /*
  136. ========================
  137. idFile_SaveGamePipelined::ReadBuildVersion
  138. ========================
  139. */
  140. bool idFile_SaveGamePipelined::ReadBuildVersion() {
  141. return ReadString( buildVersion ) != 0;
  142. }
  143. /*
  144. ========================
  145. idFile_SaveGamePipelined::ReadSaveFormatVersion
  146. ========================
  147. */
  148. bool idFile_SaveGamePipelined::ReadSaveFormatVersion() {
  149. if ( ReadBig( pointerSize ) <= 0 ) {
  150. return false;
  151. }
  152. return ReadBig( saveFormatVersion ) != 0;
  153. }
  154. /*
  155. ========================
  156. idFile_SaveGamePipelined::GetPointerSize
  157. ========================
  158. */
  159. int idFile_SaveGamePipelined::GetPointerSize() const {
  160. if ( pointerSize == 0 ) {
  161. // in original savegames we weren't saving the pointer size, so the 2 high bytes of the save version will be 0
  162. return 4;
  163. } else {
  164. return pointerSize;
  165. }
  166. }
  167. /*
  168. ============================
  169. idFile_SaveGamePipelined::Finish
  170. ============================
  171. */
  172. void idFile_SaveGamePipelined::Finish() {
  173. if ( mode == WRITE ) {
  174. // wait for the compression thread to complete, which may kick off a write
  175. if ( compressThread != NULL ) {
  176. compressThread->WaitForThread();
  177. }
  178. // force the next compression to emit everything
  179. zLibFlushType = Z_FINISH;
  180. FlushUncompressedBlock();
  181. if ( compressThread != NULL ) {
  182. compressThread->WaitForThread();
  183. }
  184. if ( writeThread != NULL ) {
  185. // wait for the IO thread to exit
  186. writeThread->WaitForThread();
  187. } else if ( nativeFile == NULL && !nativeFileEndHit ) {
  188. // wait for the last block to be consumed
  189. blockRequested.Wait();
  190. finished = true;
  191. blockAvailable.Raise();
  192. blockFinished.Wait();
  193. }
  194. // free zlib tables
  195. deflateEnd( &zStream );
  196. } else if ( mode == READ ) {
  197. // wait for the decompression thread to complete, which may kick off a read
  198. if ( decompressThread != NULL ) {
  199. decompressThread->WaitForThread();
  200. }
  201. if ( readThread != NULL ) {
  202. // wait for the IO thread to exit
  203. readThread->WaitForThread();
  204. } else if ( nativeFile == NULL && !nativeFileEndHit ) {
  205. // wait for the last block to be consumed
  206. blockAvailable.Wait();
  207. finished = true;
  208. blockRequested.Raise();
  209. blockFinished.Wait();
  210. }
  211. // free zlib tables
  212. inflateEnd( &zStream );
  213. }
  214. mode = CLOSED;
  215. }
  216. /*
  217. ============================
  218. idFile_SaveGamePipelined::Abort
  219. ============================
  220. */
  221. void idFile_SaveGamePipelined::Abort() {
  222. if ( mode == WRITE ) {
  223. if ( compressThread != NULL ) {
  224. compressThread->WaitForThread();
  225. }
  226. if ( writeThread != NULL ) {
  227. writeThread->WaitForThread();
  228. } else if ( nativeFile == NULL && !nativeFileEndHit ) {
  229. blockRequested.Wait();
  230. finished = true;
  231. dataIO = NULL;
  232. bytesIO = 0;
  233. blockAvailable.Raise();
  234. blockFinished.Wait();
  235. }
  236. } else if ( mode == READ ) {
  237. if ( decompressThread != NULL ) {
  238. decompressThread->WaitForThread();
  239. }
  240. if ( readThread != NULL ) {
  241. readThread->WaitForThread();
  242. } else if ( nativeFile == NULL && !nativeFileEndHit ) {
  243. blockAvailable.Wait();
  244. finished = true;
  245. dataIO = NULL;
  246. bytesIO = 0;
  247. blockRequested.Raise();
  248. blockFinished.Wait();
  249. }
  250. }
  251. mode = CLOSED;
  252. }
  253. /*
  254. ===================================================================================
  255. WRITE PATH
  256. ===================================================================================
  257. */
  258. /*
  259. ============================
  260. idFile_SaveGamePipelined::OpenForWriting
  261. ============================
  262. */
  263. bool idFile_SaveGamePipelined::OpenForWriting( const char * const filename, bool useNativeFile ) {
  264. assert( mode == CLOSED );
  265. name = filename;
  266. osPath = filename;
  267. mode = WRITE;
  268. nativeFile = NULL;
  269. numChecksums = 0;
  270. if ( useNativeFile ) {
  271. nativeFile = fileSystem->OpenFileWrite( filename );
  272. if ( nativeFile == NULL ) {
  273. return false;
  274. }
  275. }
  276. // raw deflate with no header / checksum
  277. // use max memory for fastest compression
  278. // optimize for higher speed
  279. //mem.PushHeap();
  280. int status = deflateInit2( &zStream, Z_BEST_SPEED, Z_DEFLATED, sgf_windowBits.GetInteger(), 9, Z_DEFAULT_STRATEGY );
  281. //mem.PopHeap();
  282. if ( status != Z_OK ) {
  283. idLib::FatalError( "idFile_SaveGamePipelined::OpenForWriting: deflateInit2() error %i", status );
  284. }
  285. // initial buffer setup
  286. zStream.avail_out = COMPRESSED_BLOCK_SIZE;
  287. zStream.next_out = (Bytef * )compressed;
  288. if ( sgf_checksums.GetBool() ) {
  289. zStream.avail_out -= sizeof( uint32 );
  290. }
  291. if ( sgf_threads.GetInteger() >= 1 ) {
  292. compressThread = new (TAG_IDFILE) idSGFcompressThread();
  293. compressThread->sgf = this;
  294. compressThread->StartWorkerThread( "SGF_CompressThread", CORE_2B, THREAD_NORMAL );
  295. }
  296. if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) {
  297. writeThread = new (TAG_IDFILE) idSGFwriteThread();
  298. writeThread->sgf = this;
  299. writeThread->StartWorkerThread( "SGF_WriteThread", CORE_2A, THREAD_NORMAL );
  300. }
  301. return true;
  302. }
  303. /*
  304. ============================
  305. idFile_SaveGamePipelined::OpenForWriting
  306. ============================
  307. */
  308. bool idFile_SaveGamePipelined::OpenForWriting( idFile * file ) {
  309. assert( mode == CLOSED );
  310. if ( file == NULL ) {
  311. return false;
  312. }
  313. name = file->GetName();
  314. osPath = file->GetFullPath();
  315. mode = WRITE;
  316. nativeFile = file;
  317. numChecksums = 0;
  318. // raw deflate with no header / checksum
  319. // use max memory for fastest compression
  320. // optimize for higher speed
  321. //mem.PushHeap();
  322. int status = deflateInit2( &zStream, Z_BEST_SPEED, Z_DEFLATED, sgf_windowBits.GetInteger(), 9, Z_DEFAULT_STRATEGY );
  323. //mem.PopHeap();
  324. if ( status != Z_OK ) {
  325. idLib::FatalError( "idFile_SaveGamePipelined::OpenForWriting: deflateInit2() error %i", status );
  326. }
  327. // initial buffer setup
  328. zStream.avail_out = COMPRESSED_BLOCK_SIZE;
  329. zStream.next_out = (Bytef * )compressed;
  330. if ( sgf_checksums.GetBool() ) {
  331. zStream.avail_out -= sizeof( uint32 );
  332. }
  333. if ( sgf_threads.GetInteger() >= 1 ) {
  334. compressThread = new (TAG_IDFILE) idSGFcompressThread();
  335. compressThread->sgf = this;
  336. compressThread->StartWorkerThread( "SGF_CompressThread", CORE_2B, THREAD_NORMAL );
  337. }
  338. if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) {
  339. writeThread = new (TAG_IDFILE) idSGFwriteThread();
  340. writeThread->sgf = this;
  341. writeThread->StartWorkerThread( "SGF_WriteThread", CORE_2A, THREAD_NORMAL );
  342. }
  343. return true;
  344. }
  345. /*
  346. ============================
  347. idFile_SaveGamePipelined::NextWriteBlock
  348. Modifies:
  349. dataIO
  350. bytesIO
  351. ============================
  352. */
  353. bool idFile_SaveGamePipelined::NextWriteBlock( blockForIO_t * block ) {
  354. assert( mode == WRITE );
  355. blockRequested.Raise(); // the background thread is done with the last block
  356. if ( nativeFileEndHit ) {
  357. return false;
  358. }
  359. blockAvailable.Wait(); // wait for a new block to come through the pipeline
  360. if ( finished || block == NULL ) {
  361. nativeFileEndHit = true;
  362. blockRequested.Raise();
  363. blockFinished.Raise();
  364. return false;
  365. }
  366. compressedLength += bytesIO;
  367. block->data = dataIO;
  368. block->bytes = bytesIO;
  369. dataIO = NULL;
  370. bytesIO = 0;
  371. return true;
  372. }
  373. /*
  374. ============================
  375. idFile_SaveGamePipelined::WriteBlock
  376. Modifies:
  377. dataIO
  378. bytesIO
  379. nativeFile
  380. ============================
  381. */
  382. void idFile_SaveGamePipelined::WriteBlock() {
  383. assert( nativeFile != NULL );
  384. compressedLength += bytesIO;
  385. nativeFile->Write( dataIO, bytesIO );
  386. dataIO = NULL;
  387. bytesIO = 0;
  388. }
  389. /*
  390. ============================
  391. idFile_SaveGamePipelined::FlushCompressedBlock
  392. Called when a compressed block fills up, and also to flush the final partial block.
  393. Flushes everything from [compressedConsumedBytes -> compressedProducedBytes)
  394. Reads:
  395. compressed
  396. compressedProducedBytes
  397. Modifies:
  398. dataZlib
  399. bytesZlib
  400. compressedConsumedBytes
  401. ============================
  402. */
  403. void idFile_SaveGamePipelined::FlushCompressedBlock() {
  404. // block until the background thread is done with the last block
  405. if ( writeThread != NULL ) {
  406. writeThread->WaitForThread();
  407. } if ( nativeFile == NULL ) {
  408. if ( !nativeFileEndHit ) {
  409. blockRequested.Wait();
  410. }
  411. }
  412. // prepare the next block to be written out
  413. dataIO = &compressed[ compressedConsumedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ];
  414. bytesIO = compressedProducedBytes - compressedConsumedBytes;
  415. compressedConsumedBytes = compressedProducedBytes;
  416. if ( writeThread != NULL ) {
  417. // signal a new block is available to be written out
  418. writeThread->SignalWork();
  419. } else if ( nativeFile != NULL ) {
  420. // write syncronously
  421. WriteBlock();
  422. } else {
  423. // signal a new block is available to be written out
  424. blockAvailable.Raise();
  425. }
  426. }
  427. /*
  428. ============================
  429. idFile_SaveGamePipelined::CompressBlock
  430. Called when an uncompressed block fills up, and also to flush the final partial block.
  431. Flushes everything from [uncompressedConsumedBytes -> uncompressedProducedBytes)
  432. Modifies:
  433. dataZlib
  434. bytesZlib
  435. compressed
  436. compressedProducedBytes
  437. zStream
  438. zStreamEndHit
  439. ============================
  440. */
  441. void idFile_SaveGamePipelined::CompressBlock() {
  442. zStream.next_in = (Bytef * )dataZlib;
  443. zStream.avail_in = (uInt) bytesZlib;
  444. dataZlib = NULL;
  445. bytesZlib = 0;
  446. // if this is the finish block, we may need to write
  447. // multiple buffers even after all input has been consumed
  448. while( zStream.avail_in > 0 || zLibFlushType == Z_FINISH ) {
  449. const int zstat = deflate( &zStream, zLibFlushType );
  450. if ( zstat != Z_OK && zstat != Z_STREAM_END ) {
  451. idLib::FatalError( "idFile_SaveGamePipelined::CompressBlock: deflate() returned %i", zstat );
  452. }
  453. if ( zStream.avail_out == 0 || zLibFlushType == Z_FINISH ) {
  454. if ( sgf_checksums.GetBool() ) {
  455. size_t blockSize = zStream.total_out + numChecksums * sizeof( uint32 ) - compressedProducedBytes;
  456. uint32 checksum = MD5_BlockChecksum( zStream.next_out - blockSize, blockSize );
  457. zStream.next_out[0] = ( ( checksum >> 0 ) & 0xFF );
  458. zStream.next_out[1] = ( ( checksum >> 8 ) & 0xFF );
  459. zStream.next_out[2] = ( ( checksum >> 16 ) & 0xFF );
  460. zStream.next_out[3] = ( ( checksum >> 24 ) & 0xFF );
  461. numChecksums++;
  462. }
  463. // flush the output buffer IO
  464. compressedProducedBytes = zStream.total_out + numChecksums * sizeof( uint32 );
  465. FlushCompressedBlock();
  466. if ( zstat == Z_STREAM_END ) {
  467. assert( zLibFlushType == Z_FINISH );
  468. zStreamEndHit = true;
  469. return;
  470. }
  471. assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) );
  472. zStream.avail_out = COMPRESSED_BLOCK_SIZE;
  473. zStream.next_out = (Bytef * )&compressed[ compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ];
  474. if ( sgf_checksums.GetBool() ) {
  475. zStream.avail_out -= sizeof( uint32 );
  476. }
  477. }
  478. }
  479. }
  480. /*
  481. ============================
  482. idFile_SaveGamePipelined::FlushUncompressedBlock
  483. Called when an uncompressed block fills up, and also to flush the final partial block.
  484. Flushes everything from [uncompressedConsumedBytes -> uncompressedProducedBytes)
  485. Reads:
  486. uncompressed
  487. uncompressedProducedBytes
  488. Modifies:
  489. dataZlib
  490. bytesZlib
  491. uncompressedConsumedBytes
  492. ============================
  493. */
  494. void idFile_SaveGamePipelined::FlushUncompressedBlock() {
  495. // block until the background thread has completed
  496. if ( compressThread != NULL ) {
  497. // make sure thread has completed the last work
  498. compressThread->WaitForThread();
  499. }
  500. // prepare the next block to be consumed by Zlib
  501. dataZlib = &uncompressed[ uncompressedConsumedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ];
  502. bytesZlib = uncompressedProducedBytes - uncompressedConsumedBytes;
  503. uncompressedConsumedBytes = uncompressedProducedBytes;
  504. if ( compressThread != NULL ) {
  505. // signal thread for more work
  506. compressThread->SignalWork();
  507. } else {
  508. // run syncronously
  509. CompressBlock();
  510. }
  511. }
  512. /*
  513. ============================
  514. idFile_SaveGamePipelined::Write
  515. Modifies:
  516. uncompressed
  517. uncompressedProducedBytes
  518. ============================
  519. */
  520. int idFile_SaveGamePipelined::Write( const void * buffer, int length ) {
  521. if ( buffer == NULL || length <= 0 ) {
  522. return 0;
  523. }
  524. #if 1 // quick and dirty fix for user-initiated forced shutdown during a savegame
  525. if ( cancelToTerminate ) {
  526. if ( mode != CLOSED ) {
  527. Abort();
  528. }
  529. return 0;
  530. }
  531. #endif
  532. assert( mode == WRITE );
  533. size_t lengthRemaining = length;
  534. const byte * buffer_p = (const byte *)buffer;
  535. while ( lengthRemaining > 0 ) {
  536. const size_t ofsInBuffer = uncompressedProducedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 );
  537. const size_t ofsInBlock = uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 );
  538. const size_t remainingInBlock = UNCOMPRESSED_BLOCK_SIZE - ofsInBlock;
  539. const size_t copyToBlock = ( lengthRemaining < remainingInBlock ) ? lengthRemaining : remainingInBlock;
  540. memcpy( uncompressed + ofsInBuffer, buffer_p, copyToBlock );
  541. uncompressedProducedBytes += copyToBlock;
  542. buffer_p += copyToBlock;
  543. lengthRemaining -= copyToBlock;
  544. if ( copyToBlock == remainingInBlock ) {
  545. FlushUncompressedBlock();
  546. }
  547. }
  548. return length;
  549. }
  550. /*
  551. ===================================================================================
  552. READ PATH
  553. ===================================================================================
  554. */
  555. /*
  556. ============================
  557. idFile_SaveGamePipelined::OpenForReading
  558. ============================
  559. */
  560. bool idFile_SaveGamePipelined::OpenForReading( const char * const filename, bool useNativeFile ) {
  561. assert( mode == CLOSED );
  562. name = filename;
  563. osPath = filename;
  564. mode = READ;
  565. nativeFile = NULL;
  566. numChecksums = 0;
  567. if ( useNativeFile ) {
  568. nativeFile = fileSystem->OpenFileRead( filename );
  569. if ( nativeFile == NULL ) {
  570. return false;
  571. }
  572. }
  573. // init zlib for raw inflate with a 32k dictionary
  574. //mem.PushHeap();
  575. int status = inflateInit2( &zStream, sgf_windowBits.GetInteger() );
  576. //mem.PopHeap();
  577. if ( status != Z_OK ) {
  578. idLib::FatalError( "idFile_SaveGamePipelined::OpenForReading: inflateInit2() error %i", status );
  579. }
  580. // spawn threads
  581. if ( sgf_threads.GetInteger() >= 1 ) {
  582. decompressThread = new (TAG_IDFILE) idSGFdecompressThread();
  583. decompressThread->sgf = this;
  584. decompressThread->StartWorkerThread( "SGF_DecompressThread", CORE_2B, THREAD_NORMAL );
  585. }
  586. if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) {
  587. readThread = new (TAG_IDFILE) idSGFreadThread();
  588. readThread->sgf = this;
  589. readThread->StartWorkerThread( "SGF_ReadThread", CORE_2A, THREAD_NORMAL );
  590. }
  591. return true;
  592. }
  593. /*
  594. ============================
  595. idFile_SaveGamePipelined::OpenForReading
  596. ============================
  597. */
  598. bool idFile_SaveGamePipelined::OpenForReading( idFile * file ) {
  599. assert( mode == CLOSED );
  600. if ( file == NULL ) {
  601. return false;
  602. }
  603. name = file->GetName();
  604. osPath = file->GetFullPath();
  605. mode = READ;
  606. nativeFile = file;
  607. numChecksums = 0;
  608. // init zlib for raw inflate with a 32k dictionary
  609. //mem.PushHeap();
  610. int status = inflateInit2( &zStream, sgf_windowBits.GetInteger() );
  611. //mem.PopHeap();
  612. if ( status != Z_OK ) {
  613. idLib::FatalError( "idFile_SaveGamePipelined::OpenForReading: inflateInit2() error %i", status );
  614. }
  615. // spawn threads
  616. if ( sgf_threads.GetInteger() >= 1 ) {
  617. decompressThread = new (TAG_IDFILE) idSGFdecompressThread();
  618. decompressThread->sgf = this;
  619. decompressThread->StartWorkerThread( "SGF_DecompressThread", CORE_1B, THREAD_NORMAL );
  620. }
  621. if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) {
  622. readThread = new (TAG_IDFILE) idSGFreadThread();
  623. readThread->sgf = this;
  624. readThread->StartWorkerThread( "SGF_ReadThread", CORE_1A, THREAD_NORMAL );
  625. }
  626. return true;
  627. }
  628. /*
  629. ============================
  630. idFile_SaveGamePipelined::NextReadBlock
  631. Reads the next data block from the filesystem into the memory buffer.
  632. Modifies:
  633. compressed
  634. compressedProducedBytes
  635. nativeFileEndHit
  636. ============================
  637. */
  638. bool idFile_SaveGamePipelined::NextReadBlock( blockForIO_t * block, size_t lastReadBytes ) {
  639. assert( mode == READ );
  640. assert( ( lastReadBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) == 0 || block == NULL );
  641. compressedProducedBytes += lastReadBytes;
  642. blockAvailable.Raise(); // a new block is available for the pipeline to consume
  643. if ( nativeFileEndHit ) {
  644. return false;
  645. }
  646. blockRequested.Wait(); // wait for the last block to be consumed by the pipeline
  647. if ( finished || block == NULL ) {
  648. nativeFileEndHit = true;
  649. blockAvailable.Raise();
  650. blockFinished.Raise();
  651. return false;
  652. }
  653. assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) );
  654. block->data = & compressed[compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 )];
  655. block->bytes = COMPRESSED_BLOCK_SIZE;
  656. return true;
  657. }
  658. /*
  659. ============================
  660. idFile_SaveGamePipelined::ReadBlock
  661. Reads the next data block from the filesystem into the memory buffer.
  662. Modifies:
  663. compressed
  664. compressedProducedBytes
  665. nativeFile
  666. nativeFileEndHit
  667. ============================
  668. */
  669. void idFile_SaveGamePipelined::ReadBlock() {
  670. assert( nativeFile != NULL );
  671. // normally run in a separate thread
  672. if ( nativeFileEndHit ) {
  673. return;
  674. }
  675. // when we are reading the last block of the file, we may not fill the entire block
  676. assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) );
  677. byte * dest = &compressed[ compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ];
  678. size_t ioBytes = nativeFile->Read( dest, COMPRESSED_BLOCK_SIZE );
  679. compressedProducedBytes += ioBytes;
  680. if ( ioBytes != COMPRESSED_BLOCK_SIZE ) {
  681. nativeFileEndHit = true;
  682. }
  683. }
  684. /*
  685. ============================
  686. idFile_SaveGamePipelined::PumpCompressedBlock
  687. Reads:
  688. compressed
  689. compressedProducedBytes
  690. Modifies:
  691. dataIO
  692. byteIO
  693. compressedConsumedBytes
  694. ============================
  695. */
  696. void idFile_SaveGamePipelined::PumpCompressedBlock() {
  697. // block until the background thread is done with the last block
  698. if ( readThread != NULL ) {
  699. readThread->WaitForThread();
  700. } else if ( nativeFile == NULL ) {
  701. if ( !nativeFileEndHit ) {
  702. blockAvailable.Wait();
  703. }
  704. }
  705. // fetch the next block read in
  706. dataIO = &compressed[ compressedConsumedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ];
  707. bytesIO = compressedProducedBytes - compressedConsumedBytes;
  708. compressedConsumedBytes = compressedProducedBytes;
  709. if ( readThread != NULL ) {
  710. // signal read thread to read another block
  711. readThread->SignalWork();
  712. } else if ( nativeFile != NULL ) {
  713. // run syncronously
  714. ReadBlock();
  715. } else {
  716. // request a new block
  717. blockRequested.Raise();
  718. }
  719. }
  720. /*
  721. ============================
  722. idFile_SaveGamePipelined::DecompressBlock
  723. Decompresses the next data block from the memory buffer
  724. Normally this runs in a separate thread when signalled, but
  725. can be called in the main thread for debugging.
  726. This will not exit until a complete block has been decompressed,
  727. unless end-of-file is reached.
  728. This may require additional compressed blocks to be read.
  729. Reads:
  730. nativeFileEndHit
  731. Modifies:
  732. dataIO
  733. bytesIO
  734. uncompressed
  735. uncompressedProducedBytes
  736. zStreamEndHit
  737. zStream
  738. ============================
  739. */
  740. void idFile_SaveGamePipelined::DecompressBlock() {
  741. if ( zStreamEndHit ) {
  742. return;
  743. }
  744. assert( ( uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 ) ) == 0 );
  745. zStream.next_out = (Bytef * )&uncompressed[ uncompressedProducedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ];
  746. zStream.avail_out = UNCOMPRESSED_BLOCK_SIZE;
  747. while( zStream.avail_out > 0 ) {
  748. if ( zStream.avail_in == 0 ) {
  749. do {
  750. PumpCompressedBlock();
  751. if ( bytesIO == 0 && nativeFileEndHit ) {
  752. // don't try to decompress any more if there is no more data
  753. zStreamEndHit = true;
  754. return;
  755. }
  756. } while ( bytesIO == 0 );
  757. zStream.next_in = (Bytef *) dataIO;
  758. zStream.avail_in = (uInt) bytesIO;
  759. dataIO = NULL;
  760. bytesIO = 0;
  761. if ( sgf_checksums.GetBool() ) {
  762. if ( sgf_testCorruption.GetInteger() == numChecksums ) {
  763. zStream.next_in[0] ^= 0xFF;
  764. }
  765. zStream.avail_in -= sizeof( uint32 );
  766. uint32 checksum = MD5_BlockChecksum( zStream.next_in, zStream.avail_in );
  767. if ( !verify( zStream.next_in[zStream.avail_in + 0] == ( ( checksum >> 0 ) & 0xFF ) ) ||
  768. !verify( zStream.next_in[zStream.avail_in + 1] == ( ( checksum >> 8 ) & 0xFF ) ) ||
  769. !verify( zStream.next_in[zStream.avail_in + 2] == ( ( checksum >> 16 ) & 0xFF ) ) ||
  770. !verify( zStream.next_in[zStream.avail_in + 3] == ( ( checksum >> 24 ) & 0xFF ) ) ) {
  771. // don't try to decompress any more if the checksum is wrong
  772. zStreamEndHit = true;
  773. return;
  774. }
  775. numChecksums++;
  776. }
  777. }
  778. const int zstat = inflate( &zStream, Z_SYNC_FLUSH );
  779. uncompressedProducedBytes = zStream.total_out;
  780. if ( zstat == Z_STREAM_END ) {
  781. // don't try to decompress any more
  782. zStreamEndHit = true;
  783. return;
  784. }
  785. if ( zstat != Z_OK ) {
  786. idLib::Warning( "idFile_SaveGamePipelined::DecompressBlock: inflate() returned %i", zstat );
  787. zStreamEndHit = true;
  788. return;
  789. }
  790. }
  791. assert( ( uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 ) ) == 0 );
  792. }
  793. /*
  794. ============================
  795. idFile_SaveGamePipelined::PumpUncompressedBlock
  796. Called when an uncompressed block is drained.
  797. Reads:
  798. uncompressed
  799. uncompressedProducedBytes
  800. Modifies:
  801. dataZlib
  802. bytesZlib
  803. uncompressedConsumedBytes
  804. ============================
  805. */
  806. void idFile_SaveGamePipelined::PumpUncompressedBlock() {
  807. if ( decompressThread != NULL ) {
  808. // make sure thread has completed the last work
  809. decompressThread->WaitForThread();
  810. }
  811. // fetch the next block produced by Zlib
  812. dataZlib = &uncompressed[ uncompressedConsumedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ];
  813. bytesZlib = uncompressedProducedBytes - uncompressedConsumedBytes;
  814. uncompressedConsumedBytes = uncompressedProducedBytes;
  815. if ( decompressThread != NULL ) {
  816. // signal thread for more work
  817. decompressThread->SignalWork();
  818. } else {
  819. // run syncronously
  820. DecompressBlock();
  821. }
  822. }
  823. /*
  824. ============================
  825. idFile_SaveGamePipelined::Read
  826. Modifies:
  827. dataZlib
  828. bytesZlib
  829. ============================
  830. */
  831. int idFile_SaveGamePipelined::Read( void * buffer, int length ) {
  832. if ( buffer == NULL || length <= 0 ) {
  833. return 0;
  834. }
  835. assert( mode == READ );
  836. size_t ioCount = 0;
  837. size_t lengthRemaining = length;
  838. byte * buffer_p = (byte *)buffer;
  839. while ( lengthRemaining > 0 ) {
  840. while ( bytesZlib == 0 ) {
  841. PumpUncompressedBlock();
  842. if ( bytesZlib == 0 && zStreamEndHit ) {
  843. return ioCount;
  844. }
  845. }
  846. const size_t copyFromBlock = ( lengthRemaining < bytesZlib ) ? lengthRemaining : bytesZlib;
  847. memcpy( buffer_p, dataZlib, copyFromBlock );
  848. dataZlib += copyFromBlock;
  849. bytesZlib -= copyFromBlock;
  850. buffer_p += copyFromBlock;
  851. ioCount += copyFromBlock;
  852. lengthRemaining -= copyFromBlock;
  853. }
  854. return ioCount;
  855. }
  856. /*
  857. ===================================================================================
  858. TEST CODE
  859. ===================================================================================
  860. */
  861. /*
  862. ============================
  863. TestProcessFile
  864. ============================
  865. */
  866. static void TestProcessFile( const char * const filename ) {
  867. idLib::Printf( "Processing %s:\n", filename );
  868. // load some test data
  869. void *testData;
  870. const int testDataLength = fileSystem->ReadFile( filename, &testData, NULL );
  871. const char * const outFileName = "junk/savegameTest.bin";
  872. idFile_SaveGamePipelined *saveFile = new (TAG_IDFILE) idFile_SaveGamePipelined;
  873. saveFile->OpenForWriting( outFileName, true );
  874. const uint64 startWriteMicroseconds = Sys_Microseconds();
  875. saveFile->Write( testData, testDataLength );
  876. delete saveFile; // final flush
  877. const int readDataLength = fileSystem->GetFileLength( outFileName );
  878. const uint64 endWriteMicroseconds = Sys_Microseconds();
  879. const uint64 writeMicroseconds = endWriteMicroseconds - startWriteMicroseconds;
  880. idLib::Printf( "%lld microseconds to compress %i bytes to %i written bytes = %4.1f MB/s\n",
  881. writeMicroseconds, testDataLength, readDataLength, (float)readDataLength / writeMicroseconds );
  882. void * readData = (void *)Mem_Alloc( testDataLength, TAG_SAVEGAMES );
  883. const uint64 startReadMicroseconds = Sys_Microseconds();
  884. idFile_SaveGamePipelined *loadFile = new (TAG_IDFILE) idFile_SaveGamePipelined;
  885. loadFile->OpenForReading( outFileName, true );
  886. loadFile->Read( readData, testDataLength );
  887. delete loadFile;
  888. const uint64 endReadMicroseconds = Sys_Microseconds();
  889. const uint64 readMicroseconds = endReadMicroseconds - startReadMicroseconds;
  890. idLib::Printf( "%lld microseconds to decompress = %4.1f MB/s\n", readMicroseconds, (float)testDataLength / readMicroseconds );
  891. int comparePoint;
  892. for ( comparePoint = 0; comparePoint < testDataLength; comparePoint++ ) {
  893. if ( ((byte *)readData)[comparePoint] != ((byte *)testData)[comparePoint] ) {
  894. break;
  895. }
  896. }
  897. if ( comparePoint != testDataLength ) {
  898. idLib::Printf( "Compare failed at %i.\n", comparePoint );
  899. assert( 0 );
  900. } else {
  901. idLib::Printf( "Compare succeeded.\n" );
  902. }
  903. Mem_Free( readData );
  904. Mem_Free( testData );
  905. }
  906. /*
  907. ============================
  908. TestSaveGameFile
  909. ============================
  910. */
  911. CONSOLE_COMMAND( TestSaveGameFile, "Exercises the pipelined savegame code", 0 ) {
  912. #if 1
  913. TestProcessFile( "maps/game/wasteland1/wasteland1.map" );
  914. #else
  915. // test every file in base (found a fencepost error >100 files in originally!)
  916. idFileList * fileList = fileSystem->ListFiles( "", "" );
  917. for ( int i = 0; i < fileList->GetNumFiles(); i++ ) {
  918. TestProcessFile( fileList->GetFile( i ) );
  919. common->UpdateConsoleDisplay();
  920. }
  921. delete fileList;
  922. #endif
  923. }
  924. /*
  925. ============================
  926. TestCompressionSpeeds
  927. ============================
  928. */
  929. CONSOLE_COMMAND( TestCompressionSpeeds, "Compares zlib and our code", 0 ) {
  930. const char * const filename = "-colorMap.tga";
  931. idLib::Printf( "Processing %s:\n", filename );
  932. // load some test data
  933. void *testData;
  934. const int testDataLength = fileSystem->ReadFile( filename, &testData, NULL );
  935. const int startWriteMicroseconds = Sys_Microseconds();
  936. idCompressor *compressor = idCompressor::AllocLZW();
  937. // idFile *f = fileSystem->OpenFileWrite( "junk/lzwTest.bin" );
  938. idFile_Memory *f = new (TAG_IDFILE) idFile_Memory( "junk/lzwTest.bin" );
  939. compressor->Init( f, true, 8 );
  940. compressor->Write( testData, testDataLength );
  941. const int readDataLength = f->Tell();
  942. delete compressor;
  943. delete f;
  944. const int endWriteMicroseconds = Sys_Microseconds();
  945. const int writeMicroseconds = endWriteMicroseconds - startWriteMicroseconds;
  946. idLib::Printf( "%i microseconds to compress %i bytes to %i written bytes = %4.1f MB/s\n",
  947. writeMicroseconds, testDataLength, readDataLength, (float)readDataLength / writeMicroseconds );
  948. }