DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

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