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