12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151 |
- /*
- ===========================================================================
- Doom 3 BFG Edition GPL Source Code
- Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
- This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
- Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
- 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.
- 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.
- ===========================================================================
- */
- #pragma hdrstop
- #include "../idlib/precompiled.h"
- #include "File_SaveGame.h"
- /*
- TODO: CRC on each block
- */
- /*
- ========================
- ZlibAlloc
- ========================
- */
- void * ZlibAlloc( void *opaque, uInt items, uInt size ) {
- return Mem_Alloc( items * size, TAG_SAVEGAMES );
- }
- /*
- ========================
- ZlibFree
- ========================
- */
- void ZlibFree( void *opaque, void * address ) {
- Mem_Free( address );
- }
- idCVar sgf_threads( "sgf_threads", "2", CVAR_INTEGER, "0 = all foreground, 1 = background write, 2 = background write + compress" );
- idCVar sgf_checksums( "sgf_checksums", "1", CVAR_BOOL, "enable save game file checksums" );
- idCVar sgf_testCorruption( "sgf_testCorruption", "-1", CVAR_INTEGER, "test corruption at the 128 kB compressed block" );
- // this is supposed to get faster going from -15 to -9, but it gets slower as well as worse compression
- idCVar sgf_windowBits( "sgf_windowBits", "-15", CVAR_INTEGER, "zlib window bits" );
- bool idFile_SaveGamePipelined::cancelToTerminate = false;
- class idSGFcompressThread : public idSysThread {
- public:
- virtual int Run() { sgf->CompressBlock(); return 0; }
- idFile_SaveGamePipelined * sgf;
- };
- class idSGFdecompressThread : public idSysThread {
- public:
- virtual int Run() { sgf->DecompressBlock(); return 0; }
- idFile_SaveGamePipelined * sgf;
- };
- class idSGFwriteThread : public idSysThread {
- public:
- virtual int Run() { sgf->WriteBlock(); return 0; }
- idFile_SaveGamePipelined * sgf;
- };
- class idSGFreadThread : public idSysThread {
- public:
- virtual int Run() { sgf->ReadBlock(); return 0; }
- idFile_SaveGamePipelined * sgf;
- };
- /*
- ============================
- idFile_SaveGamePipelined::idFile_SaveGamePipelined
- ============================
- */
- idFile_SaveGamePipelined::idFile_SaveGamePipelined() :
- mode( CLOSED ),
- compressedLength( 0 ),
- uncompressedProducedBytes( 0 ),
- uncompressedConsumedBytes( 0 ),
- compressedProducedBytes( 0 ),
- compressedConsumedBytes( 0 ),
- dataZlib( NULL ),
- bytesZlib( 0 ),
- dataIO( NULL ),
- bytesIO( 0 ),
- zLibFlushType( Z_NO_FLUSH ),
- zStreamEndHit( false ),
- numChecksums( 0 ),
- nativeFile( NULL ),
- nativeFileEndHit( false ),
- finished( false ),
- readThread( NULL ),
- writeThread( NULL ),
- decompressThread( NULL ),
- compressThread( NULL ),
- blockFinished( true ),
- buildVersion( "" ),
- saveFormatVersion( 0 ) {
- memset( &zStream, 0, sizeof( zStream ) );
- memset( compressed, 0, sizeof( compressed ) );
- memset( uncompressed, 0, sizeof( uncompressed ) );
- zStream.zalloc = ZlibAlloc;
- zStream.zfree = ZlibFree;
- }
- /*
- ============================
- idFile_SaveGamePipelined::~idFile_SaveGamePipelined
- ============================
- */
- idFile_SaveGamePipelined::~idFile_SaveGamePipelined() {
- Finish();
- // free the threads
- if ( compressThread != NULL ) {
- delete compressThread;
- compressThread = NULL;
- }
- if ( decompressThread != NULL ) {
- delete decompressThread;
- decompressThread = NULL;
- }
- if ( readThread != NULL ) {
- delete readThread;
- readThread = NULL;
- }
- if ( writeThread != NULL ) {
- delete writeThread;
- writeThread = NULL;
- }
- // close the native file
- /* if ( nativeFile != NULL ) {
- delete nativeFile;
- nativeFile = NULL;
- } */
- dataZlib = NULL;
- dataIO = NULL;
- }
- /*
- ========================
- idFile_SaveGamePipelined::ReadBuildVersion
- ========================
- */
- bool idFile_SaveGamePipelined::ReadBuildVersion() {
- return ReadString( buildVersion ) != 0;
- }
- /*
- ========================
- idFile_SaveGamePipelined::ReadSaveFormatVersion
- ========================
- */
- bool idFile_SaveGamePipelined::ReadSaveFormatVersion() {
- if ( ReadBig( pointerSize ) <= 0 ) {
- return false;
- }
- return ReadBig( saveFormatVersion ) != 0;
- }
- /*
- ========================
- idFile_SaveGamePipelined::GetPointerSize
- ========================
- */
- int idFile_SaveGamePipelined::GetPointerSize() const {
- if ( pointerSize == 0 ) {
- // in original savegames we weren't saving the pointer size, so the 2 high bytes of the save version will be 0
- return 4;
- } else {
- return pointerSize;
- }
- }
- /*
- ============================
- idFile_SaveGamePipelined::Finish
- ============================
- */
- void idFile_SaveGamePipelined::Finish() {
- if ( mode == WRITE ) {
- // wait for the compression thread to complete, which may kick off a write
- if ( compressThread != NULL ) {
- compressThread->WaitForThread();
- }
- // force the next compression to emit everything
- zLibFlushType = Z_FINISH;
- FlushUncompressedBlock();
- if ( compressThread != NULL ) {
- compressThread->WaitForThread();
- }
- if ( writeThread != NULL ) {
- // wait for the IO thread to exit
- writeThread->WaitForThread();
- } else if ( nativeFile == NULL && !nativeFileEndHit ) {
- // wait for the last block to be consumed
- blockRequested.Wait();
- finished = true;
- blockAvailable.Raise();
- blockFinished.Wait();
- }
- // free zlib tables
- deflateEnd( &zStream );
- } else if ( mode == READ ) {
- // wait for the decompression thread to complete, which may kick off a read
- if ( decompressThread != NULL ) {
- decompressThread->WaitForThread();
- }
- if ( readThread != NULL ) {
- // wait for the IO thread to exit
- readThread->WaitForThread();
- } else if ( nativeFile == NULL && !nativeFileEndHit ) {
- // wait for the last block to be consumed
- blockAvailable.Wait();
- finished = true;
- blockRequested.Raise();
- blockFinished.Wait();
- }
- // free zlib tables
- inflateEnd( &zStream );
- }
- mode = CLOSED;
- }
- /*
- ============================
- idFile_SaveGamePipelined::Abort
- ============================
- */
- void idFile_SaveGamePipelined::Abort() {
- if ( mode == WRITE ) {
- if ( compressThread != NULL ) {
- compressThread->WaitForThread();
- }
- if ( writeThread != NULL ) {
- writeThread->WaitForThread();
- } else if ( nativeFile == NULL && !nativeFileEndHit ) {
- blockRequested.Wait();
- finished = true;
- dataIO = NULL;
- bytesIO = 0;
- blockAvailable.Raise();
- blockFinished.Wait();
- }
- } else if ( mode == READ ) {
-
- if ( decompressThread != NULL ) {
- decompressThread->WaitForThread();
- }
- if ( readThread != NULL ) {
- readThread->WaitForThread();
- } else if ( nativeFile == NULL && !nativeFileEndHit ) {
- blockAvailable.Wait();
- finished = true;
- dataIO = NULL;
- bytesIO = 0;
- blockRequested.Raise();
- blockFinished.Wait();
- }
- }
- mode = CLOSED;
- }
- /*
- ===================================================================================
- WRITE PATH
- ===================================================================================
- */
- /*
- ============================
- idFile_SaveGamePipelined::OpenForWriting
- ============================
- */
- bool idFile_SaveGamePipelined::OpenForWriting( const char * const filename, bool useNativeFile ) {
- assert( mode == CLOSED );
- name = filename;
- osPath = filename;
- mode = WRITE;
- nativeFile = NULL;
- numChecksums = 0;
- if ( useNativeFile ) {
- nativeFile = fileSystem->OpenFileWrite( filename );
- if ( nativeFile == NULL ) {
- return false;
- }
- }
- // raw deflate with no header / checksum
- // use max memory for fastest compression
- // optimize for higher speed
- //mem.PushHeap();
- int status = deflateInit2( &zStream, Z_BEST_SPEED, Z_DEFLATED, sgf_windowBits.GetInteger(), 9, Z_DEFAULT_STRATEGY );
- //mem.PopHeap();
- if ( status != Z_OK ) {
- idLib::FatalError( "idFile_SaveGamePipelined::OpenForWriting: deflateInit2() error %i", status );
- }
- // initial buffer setup
- zStream.avail_out = COMPRESSED_BLOCK_SIZE;
- zStream.next_out = (Bytef * )compressed;
- if ( sgf_checksums.GetBool() ) {
- zStream.avail_out -= sizeof( uint32 );
- }
- if ( sgf_threads.GetInteger() >= 1 ) {
- compressThread = new (TAG_IDFILE) idSGFcompressThread();
- compressThread->sgf = this;
- compressThread->StartWorkerThread( "SGF_CompressThread", CORE_2B, THREAD_NORMAL );
- }
- if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) {
- writeThread = new (TAG_IDFILE) idSGFwriteThread();
- writeThread->sgf = this;
- writeThread->StartWorkerThread( "SGF_WriteThread", CORE_2A, THREAD_NORMAL );
- }
- return true;
- }
- /*
- ============================
- idFile_SaveGamePipelined::OpenForWriting
- ============================
- */
- bool idFile_SaveGamePipelined::OpenForWriting( idFile * file ) {
- assert( mode == CLOSED );
- if ( file == NULL ) {
- return false;
- }
- name = file->GetName();
- osPath = file->GetFullPath();
- mode = WRITE;
- nativeFile = file;
- numChecksums = 0;
- // raw deflate with no header / checksum
- // use max memory for fastest compression
- // optimize for higher speed
- //mem.PushHeap();
- int status = deflateInit2( &zStream, Z_BEST_SPEED, Z_DEFLATED, sgf_windowBits.GetInteger(), 9, Z_DEFAULT_STRATEGY );
- //mem.PopHeap();
- if ( status != Z_OK ) {
- idLib::FatalError( "idFile_SaveGamePipelined::OpenForWriting: deflateInit2() error %i", status );
- }
- // initial buffer setup
- zStream.avail_out = COMPRESSED_BLOCK_SIZE;
- zStream.next_out = (Bytef * )compressed;
- if ( sgf_checksums.GetBool() ) {
- zStream.avail_out -= sizeof( uint32 );
- }
- if ( sgf_threads.GetInteger() >= 1 ) {
- compressThread = new (TAG_IDFILE) idSGFcompressThread();
- compressThread->sgf = this;
- compressThread->StartWorkerThread( "SGF_CompressThread", CORE_2B, THREAD_NORMAL );
- }
- if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) {
- writeThread = new (TAG_IDFILE) idSGFwriteThread();
- writeThread->sgf = this;
- writeThread->StartWorkerThread( "SGF_WriteThread", CORE_2A, THREAD_NORMAL );
- }
- return true;
- }
- /*
- ============================
- idFile_SaveGamePipelined::NextWriteBlock
- Modifies:
- dataIO
- bytesIO
- ============================
- */
- bool idFile_SaveGamePipelined::NextWriteBlock( blockForIO_t * block ) {
- assert( mode == WRITE );
- blockRequested.Raise(); // the background thread is done with the last block
- if ( nativeFileEndHit ) {
- return false;
- }
- blockAvailable.Wait(); // wait for a new block to come through the pipeline
- if ( finished || block == NULL ) {
- nativeFileEndHit = true;
- blockRequested.Raise();
- blockFinished.Raise();
- return false;
- }
- compressedLength += bytesIO;
- block->data = dataIO;
- block->bytes = bytesIO;
- dataIO = NULL;
- bytesIO = 0;
- return true;
- }
- /*
- ============================
- idFile_SaveGamePipelined::WriteBlock
- Modifies:
- dataIO
- bytesIO
- nativeFile
- ============================
- */
- void idFile_SaveGamePipelined::WriteBlock() {
- assert( nativeFile != NULL );
- compressedLength += bytesIO;
- nativeFile->Write( dataIO, bytesIO );
- dataIO = NULL;
- bytesIO = 0;
- }
- /*
- ============================
- idFile_SaveGamePipelined::FlushCompressedBlock
- Called when a compressed block fills up, and also to flush the final partial block.
- Flushes everything from [compressedConsumedBytes -> compressedProducedBytes)
- Reads:
- compressed
- compressedProducedBytes
- Modifies:
- dataZlib
- bytesZlib
- compressedConsumedBytes
- ============================
- */
- void idFile_SaveGamePipelined::FlushCompressedBlock() {
- // block until the background thread is done with the last block
- if ( writeThread != NULL ) {
- writeThread->WaitForThread();
- } if ( nativeFile == NULL ) {
- if ( !nativeFileEndHit ) {
- blockRequested.Wait();
- }
- }
- // prepare the next block to be written out
- dataIO = &compressed[ compressedConsumedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ];
- bytesIO = compressedProducedBytes - compressedConsumedBytes;
- compressedConsumedBytes = compressedProducedBytes;
- if ( writeThread != NULL ) {
- // signal a new block is available to be written out
- writeThread->SignalWork();
- } else if ( nativeFile != NULL ) {
- // write syncronously
- WriteBlock();
- } else {
- // signal a new block is available to be written out
- blockAvailable.Raise();
- }
- }
- /*
- ============================
- idFile_SaveGamePipelined::CompressBlock
- Called when an uncompressed block fills up, and also to flush the final partial block.
- Flushes everything from [uncompressedConsumedBytes -> uncompressedProducedBytes)
- Modifies:
- dataZlib
- bytesZlib
- compressed
- compressedProducedBytes
- zStream
- zStreamEndHit
- ============================
- */
- void idFile_SaveGamePipelined::CompressBlock() {
- zStream.next_in = (Bytef * )dataZlib;
- zStream.avail_in = (uInt) bytesZlib;
- dataZlib = NULL;
- bytesZlib = 0;
- // if this is the finish block, we may need to write
- // multiple buffers even after all input has been consumed
- while( zStream.avail_in > 0 || zLibFlushType == Z_FINISH ) {
- const int zstat = deflate( &zStream, zLibFlushType );
- if ( zstat != Z_OK && zstat != Z_STREAM_END ) {
- idLib::FatalError( "idFile_SaveGamePipelined::CompressBlock: deflate() returned %i", zstat );
- }
- if ( zStream.avail_out == 0 || zLibFlushType == Z_FINISH ) {
- if ( sgf_checksums.GetBool() ) {
- size_t blockSize = zStream.total_out + numChecksums * sizeof( uint32 ) - compressedProducedBytes;
- uint32 checksum = MD5_BlockChecksum( zStream.next_out - blockSize, blockSize );
- zStream.next_out[0] = ( ( checksum >> 0 ) & 0xFF );
- zStream.next_out[1] = ( ( checksum >> 8 ) & 0xFF );
- zStream.next_out[2] = ( ( checksum >> 16 ) & 0xFF );
- zStream.next_out[3] = ( ( checksum >> 24 ) & 0xFF );
- numChecksums++;
- }
- // flush the output buffer IO
- compressedProducedBytes = zStream.total_out + numChecksums * sizeof( uint32 );
- FlushCompressedBlock();
- if ( zstat == Z_STREAM_END ) {
- assert( zLibFlushType == Z_FINISH );
- zStreamEndHit = true;
- return;
- }
- assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) );
- zStream.avail_out = COMPRESSED_BLOCK_SIZE;
- zStream.next_out = (Bytef * )&compressed[ compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ];
- if ( sgf_checksums.GetBool() ) {
- zStream.avail_out -= sizeof( uint32 );
- }
- }
- }
- }
- /*
- ============================
- idFile_SaveGamePipelined::FlushUncompressedBlock
- Called when an uncompressed block fills up, and also to flush the final partial block.
- Flushes everything from [uncompressedConsumedBytes -> uncompressedProducedBytes)
- Reads:
- uncompressed
- uncompressedProducedBytes
- Modifies:
- dataZlib
- bytesZlib
- uncompressedConsumedBytes
- ============================
- */
- void idFile_SaveGamePipelined::FlushUncompressedBlock() {
- // block until the background thread has completed
- if ( compressThread != NULL ) {
- // make sure thread has completed the last work
- compressThread->WaitForThread();
- }
- // prepare the next block to be consumed by Zlib
- dataZlib = &uncompressed[ uncompressedConsumedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ];
- bytesZlib = uncompressedProducedBytes - uncompressedConsumedBytes;
- uncompressedConsumedBytes = uncompressedProducedBytes;
- if ( compressThread != NULL ) {
- // signal thread for more work
- compressThread->SignalWork();
- } else {
- // run syncronously
- CompressBlock();
- }
- }
- /*
- ============================
- idFile_SaveGamePipelined::Write
- Modifies:
- uncompressed
- uncompressedProducedBytes
- ============================
- */
- int idFile_SaveGamePipelined::Write( const void * buffer, int length ) {
- if ( buffer == NULL || length <= 0 ) {
- return 0;
- }
- #if 1 // quick and dirty fix for user-initiated forced shutdown during a savegame
- if ( cancelToTerminate ) {
- if ( mode != CLOSED ) {
- Abort();
- }
- return 0;
- }
- #endif
- assert( mode == WRITE );
- size_t lengthRemaining = length;
- const byte * buffer_p = (const byte *)buffer;
- while ( lengthRemaining > 0 ) {
- const size_t ofsInBuffer = uncompressedProducedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 );
- const size_t ofsInBlock = uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 );
- const size_t remainingInBlock = UNCOMPRESSED_BLOCK_SIZE - ofsInBlock;
- const size_t copyToBlock = ( lengthRemaining < remainingInBlock ) ? lengthRemaining : remainingInBlock;
- memcpy( uncompressed + ofsInBuffer, buffer_p, copyToBlock );
- uncompressedProducedBytes += copyToBlock;
- buffer_p += copyToBlock;
- lengthRemaining -= copyToBlock;
- if ( copyToBlock == remainingInBlock ) {
- FlushUncompressedBlock();
- }
- }
- return length;
- }
- /*
- ===================================================================================
- READ PATH
- ===================================================================================
- */
- /*
- ============================
- idFile_SaveGamePipelined::OpenForReading
- ============================
- */
- bool idFile_SaveGamePipelined::OpenForReading( const char * const filename, bool useNativeFile ) {
- assert( mode == CLOSED );
- name = filename;
- osPath = filename;
- mode = READ;
- nativeFile = NULL;
- numChecksums = 0;
- if ( useNativeFile ) {
- nativeFile = fileSystem->OpenFileRead( filename );
- if ( nativeFile == NULL ) {
- return false;
- }
- }
- // init zlib for raw inflate with a 32k dictionary
- //mem.PushHeap();
- int status = inflateInit2( &zStream, sgf_windowBits.GetInteger() );
- //mem.PopHeap();
- if ( status != Z_OK ) {
- idLib::FatalError( "idFile_SaveGamePipelined::OpenForReading: inflateInit2() error %i", status );
- }
- // spawn threads
- if ( sgf_threads.GetInteger() >= 1 ) {
- decompressThread = new (TAG_IDFILE) idSGFdecompressThread();
- decompressThread->sgf = this;
- decompressThread->StartWorkerThread( "SGF_DecompressThread", CORE_2B, THREAD_NORMAL );
- }
- if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) {
- readThread = new (TAG_IDFILE) idSGFreadThread();
- readThread->sgf = this;
- readThread->StartWorkerThread( "SGF_ReadThread", CORE_2A, THREAD_NORMAL );
- }
- return true;
- }
- /*
- ============================
- idFile_SaveGamePipelined::OpenForReading
- ============================
- */
- bool idFile_SaveGamePipelined::OpenForReading( idFile * file ) {
- assert( mode == CLOSED );
- if ( file == NULL ) {
- return false;
- }
- name = file->GetName();
- osPath = file->GetFullPath();
- mode = READ;
- nativeFile = file;
- numChecksums = 0;
- // init zlib for raw inflate with a 32k dictionary
- //mem.PushHeap();
- int status = inflateInit2( &zStream, sgf_windowBits.GetInteger() );
- //mem.PopHeap();
- if ( status != Z_OK ) {
- idLib::FatalError( "idFile_SaveGamePipelined::OpenForReading: inflateInit2() error %i", status );
- }
- // spawn threads
- if ( sgf_threads.GetInteger() >= 1 ) {
- decompressThread = new (TAG_IDFILE) idSGFdecompressThread();
- decompressThread->sgf = this;
- decompressThread->StartWorkerThread( "SGF_DecompressThread", CORE_1B, THREAD_NORMAL );
- }
- if ( nativeFile != NULL && sgf_threads.GetInteger() >= 2 ) {
- readThread = new (TAG_IDFILE) idSGFreadThread();
- readThread->sgf = this;
- readThread->StartWorkerThread( "SGF_ReadThread", CORE_1A, THREAD_NORMAL );
- }
- return true;
- }
- /*
- ============================
- idFile_SaveGamePipelined::NextReadBlock
- Reads the next data block from the filesystem into the memory buffer.
- Modifies:
- compressed
- compressedProducedBytes
- nativeFileEndHit
- ============================
- */
- bool idFile_SaveGamePipelined::NextReadBlock( blockForIO_t * block, size_t lastReadBytes ) {
- assert( mode == READ );
- assert( ( lastReadBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) == 0 || block == NULL );
- compressedProducedBytes += lastReadBytes;
- blockAvailable.Raise(); // a new block is available for the pipeline to consume
- if ( nativeFileEndHit ) {
- return false;
- }
- blockRequested.Wait(); // wait for the last block to be consumed by the pipeline
- if ( finished || block == NULL ) {
- nativeFileEndHit = true;
- blockAvailable.Raise();
- blockFinished.Raise();
- return false;
- }
- assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) );
- block->data = & compressed[compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 )];
- block->bytes = COMPRESSED_BLOCK_SIZE;
- return true;
- }
- /*
- ============================
- idFile_SaveGamePipelined::ReadBlock
- Reads the next data block from the filesystem into the memory buffer.
- Modifies:
- compressed
- compressedProducedBytes
- nativeFile
- nativeFileEndHit
- ============================
- */
- void idFile_SaveGamePipelined::ReadBlock() {
- assert( nativeFile != NULL );
- // normally run in a separate thread
- if ( nativeFileEndHit ) {
- return;
- }
- // when we are reading the last block of the file, we may not fill the entire block
- assert( 0 == ( compressedProducedBytes & ( COMPRESSED_BLOCK_SIZE - 1 ) ) );
- byte * dest = &compressed[ compressedProducedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ];
- size_t ioBytes = nativeFile->Read( dest, COMPRESSED_BLOCK_SIZE );
- compressedProducedBytes += ioBytes;
- if ( ioBytes != COMPRESSED_BLOCK_SIZE ) {
- nativeFileEndHit = true;
- }
- }
- /*
- ============================
- idFile_SaveGamePipelined::PumpCompressedBlock
- Reads:
- compressed
- compressedProducedBytes
- Modifies:
- dataIO
- byteIO
- compressedConsumedBytes
- ============================
- */
- void idFile_SaveGamePipelined::PumpCompressedBlock() {
- // block until the background thread is done with the last block
- if ( readThread != NULL ) {
- readThread->WaitForThread();
- } else if ( nativeFile == NULL ) {
- if ( !nativeFileEndHit ) {
- blockAvailable.Wait();
- }
- }
- // fetch the next block read in
- dataIO = &compressed[ compressedConsumedBytes & ( COMPRESSED_BUFFER_SIZE - 1 ) ];
- bytesIO = compressedProducedBytes - compressedConsumedBytes;
- compressedConsumedBytes = compressedProducedBytes;
- if ( readThread != NULL ) {
- // signal read thread to read another block
- readThread->SignalWork();
- } else if ( nativeFile != NULL ) {
- // run syncronously
- ReadBlock();
- } else {
- // request a new block
- blockRequested.Raise();
- }
- }
- /*
- ============================
- idFile_SaveGamePipelined::DecompressBlock
- Decompresses the next data block from the memory buffer
- Normally this runs in a separate thread when signalled, but
- can be called in the main thread for debugging.
- This will not exit until a complete block has been decompressed,
- unless end-of-file is reached.
- This may require additional compressed blocks to be read.
- Reads:
- nativeFileEndHit
- Modifies:
- dataIO
- bytesIO
- uncompressed
- uncompressedProducedBytes
- zStreamEndHit
- zStream
- ============================
- */
- void idFile_SaveGamePipelined::DecompressBlock() {
- if ( zStreamEndHit ) {
- return;
- }
- assert( ( uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 ) ) == 0 );
- zStream.next_out = (Bytef * )&uncompressed[ uncompressedProducedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ];
- zStream.avail_out = UNCOMPRESSED_BLOCK_SIZE;
- while( zStream.avail_out > 0 ) {
- if ( zStream.avail_in == 0 ) {
- do {
- PumpCompressedBlock();
- if ( bytesIO == 0 && nativeFileEndHit ) {
- // don't try to decompress any more if there is no more data
- zStreamEndHit = true;
- return;
- }
- } while ( bytesIO == 0 );
- zStream.next_in = (Bytef *) dataIO;
- zStream.avail_in = (uInt) bytesIO;
- dataIO = NULL;
- bytesIO = 0;
- if ( sgf_checksums.GetBool() ) {
- if ( sgf_testCorruption.GetInteger() == numChecksums ) {
- zStream.next_in[0] ^= 0xFF;
- }
- zStream.avail_in -= sizeof( uint32 );
- uint32 checksum = MD5_BlockChecksum( zStream.next_in, zStream.avail_in );
- if ( !verify( zStream.next_in[zStream.avail_in + 0] == ( ( checksum >> 0 ) & 0xFF ) ) ||
- !verify( zStream.next_in[zStream.avail_in + 1] == ( ( checksum >> 8 ) & 0xFF ) ) ||
- !verify( zStream.next_in[zStream.avail_in + 2] == ( ( checksum >> 16 ) & 0xFF ) ) ||
- !verify( zStream.next_in[zStream.avail_in + 3] == ( ( checksum >> 24 ) & 0xFF ) ) ) {
- // don't try to decompress any more if the checksum is wrong
- zStreamEndHit = true;
- return;
- }
- numChecksums++;
- }
- }
- const int zstat = inflate( &zStream, Z_SYNC_FLUSH );
- uncompressedProducedBytes = zStream.total_out;
- if ( zstat == Z_STREAM_END ) {
- // don't try to decompress any more
- zStreamEndHit = true;
- return;
- }
- if ( zstat != Z_OK ) {
- idLib::Warning( "idFile_SaveGamePipelined::DecompressBlock: inflate() returned %i", zstat );
- zStreamEndHit = true;
- return;
- }
- }
- assert( ( uncompressedProducedBytes & ( UNCOMPRESSED_BLOCK_SIZE - 1 ) ) == 0 );
- }
- /*
- ============================
- idFile_SaveGamePipelined::PumpUncompressedBlock
- Called when an uncompressed block is drained.
- Reads:
- uncompressed
- uncompressedProducedBytes
- Modifies:
- dataZlib
- bytesZlib
- uncompressedConsumedBytes
- ============================
- */
- void idFile_SaveGamePipelined::PumpUncompressedBlock() {
- if ( decompressThread != NULL ) {
- // make sure thread has completed the last work
- decompressThread->WaitForThread();
- }
- // fetch the next block produced by Zlib
- dataZlib = &uncompressed[ uncompressedConsumedBytes & ( UNCOMPRESSED_BUFFER_SIZE - 1 ) ];
- bytesZlib = uncompressedProducedBytes - uncompressedConsumedBytes;
- uncompressedConsumedBytes = uncompressedProducedBytes;
- if ( decompressThread != NULL ) {
- // signal thread for more work
- decompressThread->SignalWork();
- } else {
- // run syncronously
- DecompressBlock();
- }
- }
- /*
- ============================
- idFile_SaveGamePipelined::Read
- Modifies:
- dataZlib
- bytesZlib
- ============================
- */
- int idFile_SaveGamePipelined::Read( void * buffer, int length ) {
- if ( buffer == NULL || length <= 0 ) {
- return 0;
- }
- assert( mode == READ );
- size_t ioCount = 0;
- size_t lengthRemaining = length;
- byte * buffer_p = (byte *)buffer;
- while ( lengthRemaining > 0 ) {
- while ( bytesZlib == 0 ) {
- PumpUncompressedBlock();
- if ( bytesZlib == 0 && zStreamEndHit ) {
- return ioCount;
- }
- }
- const size_t copyFromBlock = ( lengthRemaining < bytesZlib ) ? lengthRemaining : bytesZlib;
- memcpy( buffer_p, dataZlib, copyFromBlock );
- dataZlib += copyFromBlock;
- bytesZlib -= copyFromBlock;
- buffer_p += copyFromBlock;
- ioCount += copyFromBlock;
- lengthRemaining -= copyFromBlock;
- }
- return ioCount;
- }
- /*
- ===================================================================================
- TEST CODE
- ===================================================================================
- */
- /*
- ============================
- TestProcessFile
- ============================
- */
- static void TestProcessFile( const char * const filename ) {
- idLib::Printf( "Processing %s:\n", filename );
- // load some test data
- void *testData;
- const int testDataLength = fileSystem->ReadFile( filename, &testData, NULL );
- const char * const outFileName = "junk/savegameTest.bin";
- idFile_SaveGamePipelined *saveFile = new (TAG_IDFILE) idFile_SaveGamePipelined;
- saveFile->OpenForWriting( outFileName, true );
- const uint64 startWriteMicroseconds = Sys_Microseconds();
- saveFile->Write( testData, testDataLength );
- delete saveFile; // final flush
- const int readDataLength = fileSystem->GetFileLength( outFileName );
- const uint64 endWriteMicroseconds = Sys_Microseconds();
- const uint64 writeMicroseconds = endWriteMicroseconds - startWriteMicroseconds;
- idLib::Printf( "%lld microseconds to compress %i bytes to %i written bytes = %4.1f MB/s\n",
- writeMicroseconds, testDataLength, readDataLength, (float)readDataLength / writeMicroseconds );
- void * readData = (void *)Mem_Alloc( testDataLength, TAG_SAVEGAMES );
- const uint64 startReadMicroseconds = Sys_Microseconds();
- idFile_SaveGamePipelined *loadFile = new (TAG_IDFILE) idFile_SaveGamePipelined;
- loadFile->OpenForReading( outFileName, true );
- loadFile->Read( readData, testDataLength );
- delete loadFile;
- const uint64 endReadMicroseconds = Sys_Microseconds();
- const uint64 readMicroseconds = endReadMicroseconds - startReadMicroseconds;
- idLib::Printf( "%lld microseconds to decompress = %4.1f MB/s\n", readMicroseconds, (float)testDataLength / readMicroseconds );
- int comparePoint;
- for ( comparePoint = 0; comparePoint < testDataLength; comparePoint++ ) {
- if ( ((byte *)readData)[comparePoint] != ((byte *)testData)[comparePoint] ) {
- break;
- }
- }
- if ( comparePoint != testDataLength ) {
- idLib::Printf( "Compare failed at %i.\n", comparePoint );
- assert( 0 );
- } else {
- idLib::Printf( "Compare succeeded.\n" );
- }
- Mem_Free( readData );
- Mem_Free( testData );
- }
- /*
- ============================
- TestSaveGameFile
- ============================
- */
- CONSOLE_COMMAND( TestSaveGameFile, "Exercises the pipelined savegame code", 0 ) {
- #if 1
- TestProcessFile( "maps/game/wasteland1/wasteland1.map" );
- #else
- // test every file in base (found a fencepost error >100 files in originally!)
- idFileList * fileList = fileSystem->ListFiles( "", "" );
- for ( int i = 0; i < fileList->GetNumFiles(); i++ ) {
- TestProcessFile( fileList->GetFile( i ) );
- common->UpdateConsoleDisplay();
- }
- delete fileList;
- #endif
- }
- /*
- ============================
- TestCompressionSpeeds
- ============================
- */
- CONSOLE_COMMAND( TestCompressionSpeeds, "Compares zlib and our code", 0 ) {
- const char * const filename = "-colorMap.tga";
- idLib::Printf( "Processing %s:\n", filename );
- // load some test data
- void *testData;
- const int testDataLength = fileSystem->ReadFile( filename, &testData, NULL );
- const int startWriteMicroseconds = Sys_Microseconds();
- idCompressor *compressor = idCompressor::AllocLZW();
- // idFile *f = fileSystem->OpenFileWrite( "junk/lzwTest.bin" );
- idFile_Memory *f = new (TAG_IDFILE) idFile_Memory( "junk/lzwTest.bin" );
- compressor->Init( f, true, 8 );
- compressor->Write( testData, testDataLength );
- const int readDataLength = f->Tell();
- delete compressor;
- delete f;
- const int endWriteMicroseconds = Sys_Microseconds();
- const int writeMicroseconds = endWriteMicroseconds - startWriteMicroseconds;
- idLib::Printf( "%i microseconds to compress %i bytes to %i written bytes = %4.1f MB/s\n",
- writeMicroseconds, testDataLength, readDataLength, (float)readDataLength / writeMicroseconds );
- }
|