DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Snapshot.cpp (40758B)


      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 idCVar net_verboseSnapshot( "net_verboseSnapshot", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" );
     32 idCVar net_verboseSnapshotCompression( "net_verboseSnapshotCompression", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" );
     33 idCVar net_verboseSnapshotReport( "net_verboseSnapshotReport", "0", CVAR_INTEGER|CVAR_NOCHEAT, "Verbose snapshot code to help debug snapshot problems. Greater the number greater the spam" );
     34 
     35 idCVar net_ssTemplateDebug( "net_ssTemplateDebug", "0", CVAR_BOOL, "Debug snapshot template states" );
     36 idCVar net_ssTemplateDebug_len( "net_ssTemplateDebug_len", "32", CVAR_INTEGER, "Offset to start template state debugging" );
     37 idCVar net_ssTemplateDebug_start( "net_ssTemplateDebug_start", "0", CVAR_INTEGER, "length of template state to print in debugging" );
     38 
     39 /*
     40 ========================
     41 InDebugRange
     42 Helper function for net_ssTemplateDebug debugging
     43 ========================
     44 */
     45 bool InDebugRange( int i ) {
     46 	return ( i >= net_ssTemplateDebug_start.GetInteger() && i < net_ssTemplateDebug_start.GetInteger() + net_ssTemplateDebug_len.GetInteger() );
     47 }
     48 /*
     49 ========================
     50 PrintAlign
     51 Helper function for net_ssTemplateDebug debugging
     52 ========================
     53 */
     54 void PrintAlign( const char * text ) {
     55 	idLib::Printf( "%25s: 0x", text );
     56 }
     57 
     58 /*
     59 ========================
     60 idSnapShot::objectState_t::Print
     61 Helper function for net_ssTemplateDebug debugging
     62 ========================
     63 */
     64 void idSnapShot::objectState_t::Print( const char * name ) {
     65 
     66 	unsigned int start = (unsigned int)net_ssTemplateDebug_start.GetInteger();
     67 	unsigned int end = Min( (unsigned int)buffer.Size(), start + net_ssTemplateDebug_len.GetInteger() );
     68 
     69 	PrintAlign( va( "%s: [sz %d]", name, buffer.Size() ) );
     70 
     71 	for ( unsigned int i = start; i < end; i++ ) {
     72 		idLib::Printf( "%02X", buffer[i] );
     73 	}
     74 	idLib::Printf("\n");
     75 }
     76 
     77 /*
     78 ========================
     79 idSnapShot::objectBuffer_t::Alloc
     80 ========================
     81 */
     82 void idSnapShot::objectBuffer_t::Alloc( int s ) {
     83 	//assert( mem.IsMapHeap() );
     84 	if ( !verify( s < SIZE_NOT_STALE ) ) {
     85 		idLib::FatalError( "s >= SIZE_NOT_STALE" );
     86 	}
     87 	_Release();
     88 	data = (byte *)Mem_Alloc( s + 1, TAG_NETWORKING );
     89 	size = s;
     90 	data[size] = 1;
     91 }
     92 
     93 /*
     94 ========================
     95 idSnapShot::objectBuffer_t::AddRef
     96 ========================
     97 */
     98 void idSnapShot::objectBuffer_t::_AddRef() {
     99 	if ( data != NULL ) {
    100 		assert( size > 0 );
    101 		assert( data[size] < 255 );
    102 		data[size]++;
    103 	}
    104 }
    105 
    106 /*
    107 ========================
    108 idSnapShot::objectBuffer_t::Release
    109 ========================
    110 */
    111 void idSnapShot::objectBuffer_t::_Release() {
    112 	//assert( mem.IsMapHeap() );
    113 	if ( data != NULL ) {
    114 		assert( size > 0 );
    115 		if ( --data[size] == 0 ) {
    116 			Mem_Free( data );
    117 		}
    118 		data = NULL;
    119 		size = 0;
    120 	}
    121 }
    122 
    123 /*
    124 ========================
    125 idSnapShot::objectBuffer_t::operator=
    126 ========================
    127 */
    128 void idSnapShot::objectBuffer_t::operator=( const idSnapShot::objectBuffer_t & other ) {
    129 	//assert( mem.IsMapHeap() );
    130 	if ( this != &other ) {
    131 		_Release();
    132 		data = other.data;
    133 		size = other.size;
    134 		_AddRef();
    135 	}
    136 }
    137 
    138 /*
    139 ========================
    140 idSnapShot::idSnapShot
    141 ========================
    142 */
    143 idSnapShot::idSnapShot() :
    144 	time( 0 ),
    145 	recvTime( 0 )
    146 {
    147 }
    148 
    149 /*
    150 ========================
    151 idSnapShot::idSnapShot
    152 ========================
    153 */
    154 idSnapShot::idSnapShot( const idSnapShot & other ) : time( 0 ), recvTime(0) {
    155 	*this = other;
    156 }
    157 
    158 /*
    159 ========================
    160 idSnapShot::~idSnapShot
    161 ========================
    162 */
    163 idSnapShot::~idSnapShot() {
    164 	Clear();
    165 }
    166 
    167 /*
    168 ========================
    169 idSnapShot::Clear
    170 ========================
    171 */
    172 void idSnapShot::Clear() {
    173 	time = 0;
    174 	recvTime = 0;
    175 	for ( int i = 0; i < objectStates.Num(); i++ ) {
    176 		FreeObjectState( i );
    177 	}
    178 	objectStates.Clear();
    179 	allocatedObjs.Shutdown();
    180 }
    181 
    182 /*
    183 ========================
    184 idSnapShot::operator=
    185 ========================
    186 */
    187 void idSnapShot::operator=( const idSnapShot & other ) {
    188 	//assert( mem.IsMapHeap() );
    189 
    190 	if ( this != &other ) {
    191 		for ( int i = other.objectStates.Num(); i < objectStates.Num(); i++ ) {
    192 			FreeObjectState( i );
    193 		}
    194 		objectStates.AssureSize( other.objectStates.Num(), NULL );
    195 		for ( int i = 0; i < objectStates.Num(); i++ ) {
    196 			const objectState_t & otherState = *other.objectStates[i];
    197 			if ( objectStates[i] == NULL ) {
    198 				objectStates[i] = allocatedObjs.Alloc();
    199 			}
    200 			objectState_t & state = *objectStates[i];
    201 			state.objectNum		= otherState.objectNum;
    202 			state.buffer		= otherState.buffer;
    203 			state.visMask		= otherState.visMask;
    204 			state.stale			= otherState.stale;
    205 			state.deleted		= otherState.deleted;
    206 			state.changedCount	= otherState.changedCount;
    207 			state.expectedSequence = otherState.expectedSequence;
    208 			state.createdFromTemplate = otherState.createdFromTemplate;
    209 		}
    210 		time = other.time;
    211 		recvTime = other.recvTime;
    212 	}
    213 }
    214 
    215 /*
    216 ========================
    217 idSnapShot::PeekDeltaSequence
    218 ========================
    219 */
    220 void idSnapShot::PeekDeltaSequence( const char * deltaMem, int deltaSize, int & sequence, int & baseSequence ) {
    221 	lzwCompressionData_t	lzwData;
    222 	idLZWCompressor			lzwCompressor( &lzwData );
    223 	
    224 	lzwCompressor.Start( (uint8*)deltaMem, deltaSize );	
    225 	lzwCompressor.ReadAgnostic( sequence );
    226 	lzwCompressor.ReadAgnostic( baseSequence );
    227 }
    228 
    229 /*
    230 ========================
    231 idSnapShot::ReadDeltaForJob
    232 ========================
    233 */
    234 bool idSnapShot::ReadDeltaForJob( const char * deltaMem, int deltaSize, int visIndex, idSnapShot * templateStates ) {
    235 
    236 	bool report = net_verboseSnapshotReport.GetBool();
    237 	net_verboseSnapshotReport.SetBool( false );
    238 
    239 	lzwCompressionData_t		lzwData;
    240 	idZeroRunLengthCompressor	rleCompressor;
    241 	idLZWCompressor				lzwCompressor( &lzwData );
    242 	int bytesRead = 0; // how many uncompressed bytes we read in. Used to figure out compression ratio
    243 
    244 	lzwCompressor.Start( (uint8*)deltaMem, deltaSize );
    245 
    246 	// Skip past sequence and baseSequence
    247 	int sequence		= 0;
    248 	int baseSequence	= 0;
    249 
    250 	lzwCompressor.ReadAgnostic( sequence );
    251 	lzwCompressor.ReadAgnostic( baseSequence );
    252 	lzwCompressor.ReadAgnostic( time );
    253 	bytesRead += sizeof( int ) * 3;
    254 
    255 	int objectNum = 0;
    256 	uint16 delta = 0;
    257 	
    258 
    259 	while ( lzwCompressor.ReadAgnostic( delta, true ) == sizeof( delta ) ) {
    260 		bytesRead += sizeof( delta );
    261 
    262 		objectNum += delta;
    263 		if ( objectNum >= 0xFFFF ) {
    264 			// full delta
    265 			if ( net_verboseSnapshotCompression.GetBool() ) {
    266 				float compRatio = static_cast<float>( deltaSize ) / static_cast<float>( bytesRead );
    267 				idLib::Printf( "Snapshot (%d/%d). ReadSize: %d DeltaSize: %d Ratio: %.3f\n", sequence, baseSequence, bytesRead, deltaSize, compRatio );
    268 			}
    269 			return true;
    270 		}
    271 
    272 		objectState_t & state = FindOrCreateObjectByID( objectNum );
    273 		
    274 		objectSize_t newsize = 0;
    275 		lzwCompressor.ReadAgnostic( newsize );
    276 		bytesRead += sizeof( newsize );
    277 
    278 		if ( newsize == SIZE_STALE ) {
    279 			NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum );
    280 			// sanity
    281 			bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0;
    282 			if ( !oldVisible ) {
    283 				NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" );
    284 			}
    285 			state.visMask &= ~( 1 << visIndex );
    286 			state.stale = true;
    287 			// We need to make sure we haven't freed stale objects.
    288 			assert( state.buffer.Size() > 0 );
    289 			// no more data
    290 			continue;
    291 		} else if ( newsize == SIZE_NOT_STALE ) {
    292 			NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum );
    293 			// sanity
    294 			bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0;
    295 			if ( oldVisible ) {
    296 				NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" );
    297 			}
    298 			state.visMask |= ( 1 << visIndex );
    299 			state.stale = false;
    300 			// the latest state is packed in, get the new size and continue reading the new state
    301 			lzwCompressor.ReadAgnostic( newsize );
    302 			bytesRead += sizeof( newsize );
    303 		}
    304 
    305 		objectState_t *	objTemplateState = templateStates->FindObjectByID( objectNum );
    306 
    307 		if ( newsize == 0 ) {
    308 			// object deleted: reset state now so next one to use it doesn't have old data
    309 			state.deleted = false;
    310 			state.stale = false;
    311 			state.changedCount = 0;
    312 			state.expectedSequence = 0;
    313 			state.visMask = 0;
    314 			state.buffer._Release();
    315 			state.createdFromTemplate = false;
    316 
    317 			if ( objTemplateState != NULL && objTemplateState->buffer.Size() && objTemplateState->expectedSequence < baseSequence ) {
    318 				idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "Clearing old template state[%d] [%d<%d]\n", objectNum, objTemplateState->expectedSequence, baseSequence );
    319 				objTemplateState->deleted = false;
    320 				objTemplateState->stale = false;
    321 				objTemplateState->changedCount = 0;
    322 				objTemplateState->expectedSequence = 0;
    323 				objTemplateState->visMask = 0;
    324 				objTemplateState->buffer._Release();
    325 			}
    326 
    327 		} else {
    328 
    329 			// new state?
    330 			bool debug = false;
    331 			if ( state.buffer.Size() == 0 ) {
    332 				state.createdFromTemplate = true;
    333 				// Brand new state
    334 				if ( objTemplateState != NULL && objTemplateState->buffer.Size() > 0 && sequence >= objTemplateState->expectedSequence ) {
    335 					idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "\nAdding basestate for new object %d (for SS %d/%d. obj base created in ss %d) deltaSize: %d\n", objectNum, sequence, baseSequence, objTemplateState->expectedSequence, deltaSize );
    336 					state.buffer = objTemplateState->buffer;
    337 
    338 					if ( net_ssTemplateDebug.GetBool() ) {
    339 						state.Print( "SPAWN STATE" );
    340 						debug = true;
    341 						PrintAlign( "DELTA STATE" );
    342 					}
    343 				} else if ( net_ssTemplateDebug.GetBool() ) {
    344 					idLib::Printf("\nNew snapobject[%d] in snapshot %d/%d but no basestate found locally so creating new\n", objectNum, sequence, baseSequence );
    345 				}
    346 			} else {
    347 				state.createdFromTemplate = false;
    348 			}
    349 
    350 			// the buffer shrank or stayed the same
    351 			objectBuffer_t newbuffer( newsize );
    352 			rleCompressor.Start( NULL, &lzwCompressor, newsize );
    353 			objectSize_t compareSize = Min( state.buffer.Size(), newsize );
    354 			for ( objectSize_t i = 0; i < compareSize; i++ ) {
    355 				byte b = rleCompressor.ReadByte();
    356 				newbuffer[i] = state.buffer[i] + b;
    357 
    358 				if ( debug && InDebugRange( i ) ) {
    359 					idLib::Printf( "%02X", b );
    360 				}
    361 			}
    362 			// Catch leftover
    363 			if ( newsize > compareSize ) {
    364 				rleCompressor.ReadBytes( newbuffer.Ptr() + compareSize, newsize - compareSize );
    365 
    366 				if ( debug ) {
    367 					for ( objectSize_t i = compareSize; i < newsize; i++ ) {
    368 						if ( InDebugRange( i ) ) {
    369 							idLib::Printf( "%02X", newbuffer[i] );
    370 						}
    371 					}
    372 				}
    373 
    374 			}
    375 			state.buffer = newbuffer;
    376 			state.changedCount = sequence;
    377 			bytesRead += sizeof( byte ) * newsize;
    378 			if ( debug ) {
    379 				idLib::Printf( "\n" );
    380 				state.Print( "NEW STATE" );	
    381 			}
    382 
    383 			if ( report ) {
    384 				idLib::Printf( "    Obj %d Compressed: Size %d \n", objectNum, rleCompressor.CompressedSize() );
    385 			}
    386 		}
    387 #ifdef SNAPSHOT_CHECKSUMS
    388 		extern uint32 SnapObjChecksum( const uint8 * data, int length );
    389 		if ( state.buffer.Size() > 0 ) {
    390 			uint32 checksum = 0;
    391 			lzwCompressor.ReadAgnostic( checksum );
    392 			bytesRead += sizeof( checksum );
    393 			if ( !verify( checksum == SnapObjChecksum( state.buffer.Ptr(), state.buffer.Size() ) ) ) {
    394 				idLib::Error(" Invalid snapshot checksum" );
    395 			}
    396 		}
    397 #endif
    398 	}
    399 	// partial delta
    400 	return false;
    401 }
    402 
    403 /*
    404 ========================
    405 idSnapShot::SubmitObjectJob
    406 ========================
    407 */
    408 
    409 void idSnapShot::SubmitObjectJob(	const submitDeltaJobsInfo_t &	submitDeltaJobsInfo,
    410 									objectState_t *					newState, 
    411 									objectState_t *					oldState, 
    412 									objParms_t *&					baseObjParm, 
    413 									objParms_t *&					curObjParm, 
    414 									objHeader_t *&					curHeader,
    415 									uint8 *&						curObjDest,
    416 									lzwParm_t *&					curlzwParm 
    417 ) {
    418 	assert( newState != NULL || oldState != NULL );
    419 	assert_16_byte_aligned( curHeader );
    420 	assert_16_byte_aligned( curObjDest );
    421 			
    422 	int32 dataSize = newState != NULL ? newState->buffer.Size() : 0;
    423 	int totalSize = OBJ_DEST_SIZE_ALIGN16( dataSize );
    424 	
    425 	if ( curObjParm - submitDeltaJobsInfo.objParms >= submitDeltaJobsInfo.maxObjParms ) {
    426 		idLib::Error( "Out of parms for snapshot jobs.\n");
    427 	}
    428 	
    429 	// Check to see if we are out of dest write space, and need to flush the jobs
    430 	bool needToSubmit = ( curObjDest - submitDeltaJobsInfo.objMemory ) + totalSize >= submitDeltaJobsInfo.maxObjMemory;
    431 	needToSubmit |= ( curHeader - submitDeltaJobsInfo.headers >= submitDeltaJobsInfo.maxHeaders );
    432 	
    433 	if ( needToSubmit ) {
    434 		// If this obj will put us over the limit, then submit the jobs now, and start over re-using the same buffers
    435 		SubmitLZWJob( submitDeltaJobsInfo, baseObjParm, curObjParm, curlzwParm, true );
    436 		curHeader	= submitDeltaJobsInfo.headers;
    437 		curObjDest	= submitDeltaJobsInfo.objMemory;
    438 	}
    439 
    440 	// Setup obj parms
    441 	assert( submitDeltaJobsInfo.visIndex < 256 );
    442 	curObjParm->visIndex	= submitDeltaJobsInfo.visIndex;
    443 	curObjParm->destHeader	= curHeader;
    444 	curObjParm->dest		= curObjDest;
    445 
    446 	memset( &curObjParm->newState, 0, sizeof( curObjParm->newState ) );
    447 	memset( &curObjParm->oldState, 0, sizeof( curObjParm->oldState ) );
    448 	
    449 	if ( newState != NULL ) {
    450 		assert( newState->buffer.Size() <= 65535 );
    451 				
    452 		curObjParm->newState.valid		= 1;
    453 		curObjParm->newState.data		= newState->buffer.Ptr();
    454 		curObjParm->newState.size		= newState->buffer.Size();
    455 		curObjParm->newState.objectNum	= newState->objectNum;
    456 		curObjParm->newState.visMask	= newState->visMask;
    457 	}
    458 	
    459 	if ( oldState != NULL ) {
    460 		assert( oldState->buffer.Size() <= 65535 );
    461 
    462 		curObjParm->oldState.valid		= 1;
    463 		curObjParm->oldState.data		= oldState->buffer.Ptr();
    464 		curObjParm->oldState.size		= oldState->buffer.Size();
    465 		curObjParm->oldState.objectNum	= oldState->objectNum;
    466 		curObjParm->oldState.visMask	= oldState->visMask;
    467 	}
    468 
    469 	assert_16_byte_aligned( curObjParm );
    470 	assert_16_byte_aligned( curObjParm->newState.data );
    471 	assert_16_byte_aligned( curObjParm->oldState.data );
    472 	
    473 	SnapshotObjectJob( curObjParm );
    474 
    475 	// Advance past header + data
    476 	curObjDest += totalSize;
    477 
    478 	// Advance parm pointer
    479 	curObjParm++;		
    480 	
    481 	// Advance header pointer
    482 	curHeader++;
    483 }
    484 
    485 /*
    486 ========================
    487 idSnapShot::SubmitLZWJob
    488 Take the current list of delta'd + zlre processed objects, and write them to the lzw stream.
    489 ========================
    490 */
    491 void idSnapShot::SubmitLZWJob( 
    492 	const submitDeltaJobsInfo_t &	writeDeltaInfo, 
    493 	objParms_t *&					baseObjParm,		// Pointer to the first obj parm for the current stream
    494 	objParms_t *&					curObjParm,			// Current obj parm
    495 	lzwParm_t *&					curlzwParm,			// Current delta parm
    496 	bool							saveDictionary
    497 ) {
    498 	int numObjects = curObjParm - baseObjParm;
    499 	
    500 	if ( numObjects == 0 ) {
    501 		return;		// Nothing to do
    502 	}
    503 			
    504 	if ( curlzwParm - writeDeltaInfo.lzwParms >= writeDeltaInfo.maxDeltaParms ) {
    505 		idLib::Error( "SubmitLZWJob: Not enough lzwParams.\n" );
    506 		return;		// Can't do anymore
    507 	}
    508 
    509 	curlzwParm->numObjects		= numObjects;
    510 	curlzwParm->headers			= writeDeltaInfo.headers;		// We always start grabbing from the beggining of the memory (it's reused, with fences to protect memory sharing)
    511 	curlzwParm->curTime			= this->GetTime();
    512 	curlzwParm->baseTime		= writeDeltaInfo.oldSnap->GetTime();
    513 	curlzwParm->baseSequence	= writeDeltaInfo.baseSequence;
    514 	curlzwParm->fragmented		= ( curlzwParm != writeDeltaInfo.lzwParms );
    515 	curlzwParm->saveDictionary	= saveDictionary;
    516 	
    517 	curlzwParm->ioData			= writeDeltaInfo.lzwInOutData;
    518 
    519 	LZWJob( curlzwParm );
    520 	
    521 	curlzwParm++;
    522 	
    523 	// Set base so it now points to where the parms start for the new stream
    524 	baseObjParm = curObjParm;
    525 }
    526 
    527 /*
    528 ========================
    529 idSnapShot::GetTemplateState
    530 Helper function for getting template objectState.
    531 newState parameter is optional and is just used for debugging/printf comparison of the template and actual state
    532 ========================
    533 */
    534 idSnapShot::objectState_t * idSnapShot::GetTemplateState( int objNum, idSnapShot * templateStates, idSnapShot::objectState_t * newState /*=NULL*/ ) {
    535 	objectState_t * oldState = NULL;
    536 	int spawnedStateIndex = templateStates->FindObjectIndexByID( objNum );
    537 	if ( spawnedStateIndex >= 0 ) {
    538 		oldState = templateStates->objectStates[ spawnedStateIndex ];
    539 		
    540 		if ( net_ssTemplateDebug.GetBool() ) {
    541 			idLib::Printf( "\nGetTemplateState[%d]\n", objNum );
    542 			oldState->Print( "SPAWN STATE" );
    543 			if ( newState != NULL ) {
    544 				newState->Print( "CUR STATE" );
    545 			}
    546 		}
    547 	}
    548 	return oldState;
    549 }
    550 
    551 /*
    552 ========================
    553 idSnapShot::SubmitWriteDeltaToJobs
    554 ========================
    555 */
    556 void idSnapShot::SubmitWriteDeltaToJobs( const submitDeltaJobsInfo_t & submitDeltaJobInfo ) {
    557 	objParms_t *	curObjParms		= submitDeltaJobInfo.objParms;
    558 	objParms_t *	baseObjParms	= submitDeltaJobInfo.objParms;
    559 	lzwParm_t *		curlzwParms		= submitDeltaJobInfo.lzwParms;
    560 	objHeader_t *	curHeader		= submitDeltaJobInfo.headers;
    561 	uint8 *			curObjMemory	= submitDeltaJobInfo.objMemory;
    562 	
    563 	submitDeltaJobInfo.lzwInOutData->numlzwDeltas	= 0;
    564 	submitDeltaJobInfo.lzwInOutData->lzwBytes		= 0;
    565 	submitDeltaJobInfo.lzwInOutData->fullSnap		= false;
    566 
    567 	int j = 0;
    568 	
    569 	int numOldStates = submitDeltaJobInfo.oldSnap->objectStates.Num();
    570 	
    571 	for ( int i = 0; i < objectStates.Num(); i++ ) {
    572 		objectState_t & newState = *objectStates[i];
    573 		if ( !verify( newState.buffer.Size() > 0 ) ) {
    574 			// you CANNOT have a valid ss obj state w/ size = 0: this will be interpreted as a delete in ::ReadDelta and this will completely throw
    575 			// off delta compression, eventually resulting in a checksum error that is a pain to track down.
    576 			idLib::Warning( "Snap obj [%d] state.size <= 0... skipping ", newState.objectNum );
    577 			continue;
    578 		}
    579 		
    580 		if ( j >= numOldStates ) {
    581 			// We no longer have old objects to compare to.
    582 			// All objects are new from this point on.
    583 
    584 			objectState_t * oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState );
    585 			SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
    586 			continue;
    587 		}
    588 
    589 		// write any deleted entities up to this one
    590 		for ( ; j < numOldStates && newState.objectNum > submitDeltaJobInfo.oldSnap->objectStates[j]->objectNum; j++ ) {
    591 			objectState_t & oldState = *submitDeltaJobInfo.oldSnap->objectStates[j];
    592 
    593 			if ( ( oldState.stale && !oldState.deleted ) || oldState.buffer.Size() <= 0 ) {
    594 				continue;		// Don't delete objects that are stale and not marked as deleted
    595 			}
    596 
    597 			SubmitObjectJob( submitDeltaJobInfo, NULL, &oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
    598 		}
    599 		
    600 		if ( j >= numOldStates ) {
    601 			continue;	// Went past end of old list deleting objects
    602 		}
    603 		
    604 		// Beyond this point, we may have old state to compare against
    605 		objectState_t & submittedOldState = *submitDeltaJobInfo.oldSnap->objectStates[j];
    606 		objectState_t * oldState = &submittedOldState;
    607 	
    608 		if ( newState.objectNum == oldState->objectNum ) {
    609 			if ( oldState->buffer.Size() == 0 ) {
    610 				// New state (even though snapObj existed, its size was zero)
    611 				oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState );
    612 			}
    613 
    614 			SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
    615 			j++;
    616 		} else {
    617 			// Different object, this one is new, 
    618 			// Spawned
    619 			oldState = GetTemplateState( newState.objectNum, submitDeltaJobInfo.templateStates, &newState );
    620 			SubmitObjectJob( submitDeltaJobInfo, &newState, oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
    621 		}
    622 	}
    623 	// Finally, remove any entities at the end
    624 	for ( ; j < submitDeltaJobInfo.oldSnap->objectStates.Num(); j++ ) {
    625 		objectState_t & oldState = *submitDeltaJobInfo.oldSnap->objectStates[j];
    626 
    627 		if ( ( oldState.stale && !oldState.deleted ) || oldState.buffer.Size() <= 0 ) {
    628 			continue;		// Don't delete objects that are stale and not marked as deleted
    629 		}
    630 
    631 		SubmitObjectJob( submitDeltaJobInfo, NULL, &oldState, baseObjParms, curObjParms, curHeader, curObjMemory, curlzwParms );
    632 	}
    633 				
    634 	// Submit any objects that are left over (will be all if they all fit up to this point)
    635 	SubmitLZWJob( submitDeltaJobInfo, baseObjParms, curObjParms, curlzwParms, false );
    636 }
    637 
    638 /*
    639 ========================
    640 idSnapShot::ReadDelta
    641 ========================
    642 */
    643 bool idSnapShot::ReadDelta( idFile * file, int visIndex ) {
    644 
    645 	file->ReadBig( time );
    646 
    647 	int objectNum = 0;
    648 	uint16 delta = 0;
    649 	while ( file->ReadBig( delta ) == sizeof( delta ) ) {	
    650 		objectNum += delta;
    651 		if ( objectNum >= 0xFFFF ) {
    652 			// full delta
    653 			return true;
    654 		}
    655 		objectState_t & state = FindOrCreateObjectByID( objectNum );
    656 		objectSize_t newsize = 0;
    657 		file->ReadBig( newsize );
    658 
    659 		if ( newsize == SIZE_STALE ) {
    660 			NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d goes stale\n", objectNum );
    661 			// sanity
    662 			bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0;
    663 			if ( !oldVisible ) {
    664 				NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected already stale\n" );
    665 			}
    666 			state.visMask &= ~( 1 << visIndex );
    667 			state.stale = true;
    668 			// We need to make sure we haven't freed stale objects.
    669 			assert( state.buffer.Size() > 0 );
    670 			// no more data
    671 			continue;
    672 		} else if ( newsize == SIZE_NOT_STALE ) {
    673 			NET_VERBOSESNAPSHOT_PRINT( "read delta: object %d no longer stale\n", objectNum );
    674 			// sanity
    675 			bool oldVisible = ( state.visMask & ( 1 << visIndex ) ) != 0;
    676 			if ( oldVisible ) {
    677 				NET_VERBOSESNAPSHOT_PRINT( "ERROR: unexpected not stale\n" );
    678 			}
    679 			state.visMask |= ( 1 << visIndex );
    680 			state.stale = false;
    681 			// the latest state is packed in, get the new size and continue reading the new state
    682 			file->ReadBig( newsize );
    683 		}
    684 		
    685 		if ( newsize == 0 ) {
    686 			// object deleted
    687 			state.buffer._Release();
    688 		} else {						
    689 			objectBuffer_t newbuffer( newsize );
    690 			objectSize_t compareSize = Min( newsize, state.buffer.Size() );
    691 
    692 			for ( objectSize_t i = 0; i < compareSize; i++ ) {
    693 				uint8 delta = 0;
    694 				file->ReadBig<byte>( delta );
    695 				newbuffer[i] = state.buffer[i] + delta;
    696 			}
    697 			
    698 			if ( newsize > compareSize ) {
    699 				file->Read( newbuffer.Ptr() + compareSize, newsize - compareSize );	
    700 			}
    701 			
    702 			state.buffer = newbuffer;			
    703 			state.changedCount++;
    704 		}
    705 		
    706 #ifdef SNAPSHOT_CHECKSUMS
    707 		if ( state.buffer.Size() > 0 ) {
    708 			unsigned int checksum = 0;
    709 			file->ReadBig( checksum );
    710 			assert( checksum == MD5_BlockChecksum( state.buffer.Ptr(), state.buffer.Size() ) );
    711 		}
    712 #endif		
    713 	}
    714 		
    715 	// partial delta
    716 	return false;
    717 }
    718 
    719 /*
    720 ========================
    721 idSnapShot::WriteObject
    722 ========================
    723 */
    724 void idSnapShot::WriteObject( idFile * file, int visIndex, objectState_t * newState, objectState_t * oldState, int & lastobjectNum ) {
    725 	assert( newState != NULL || oldState != NULL );
    726 		
    727 	bool visChange		= false; // visibility changes will be signified with a 0xffff state size
    728 	bool visSendState	= false; // the state is sent when an entity is no longer stale
    729 
    730 	// Compute visibility changes 
    731 	// (we need to do this before writing out object id, because we may not need to write out the id if we early out)
    732 	// (when we don't write out the id, we assume this is an "ack" when we deserialize the objects)
    733 	if ( newState != NULL && oldState != NULL ) {
    734 		// Check visibility
    735 		assert( newState->objectNum == oldState->objectNum );
    736 		
    737 		if ( visIndex > 0 ) {
    738 			bool oldVisible = ( oldState->visMask & ( 1 << visIndex ) ) != 0;
    739 			bool newVisible = ( newState->visMask & ( 1 << visIndex ) ) != 0;
    740 			
    741 			// Force visible if we need to either create or destroy this object
    742 			newVisible |= ( newState->buffer.Size() == 0 ) != ( oldState->buffer.Size() == 0 );	
    743 			
    744 			if ( !oldVisible && !newVisible ) {
    745 				// object is stale and ack'ed for this client, write nothing (see 'same object' below)					
    746 				return;
    747 			} else if ( oldVisible && !newVisible ) {
    748 				NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d goes stale\n", newState->objectNum, visIndex );
    749 				visChange = true;
    750 				visSendState = false;
    751 			} else if ( !oldVisible && newVisible ) {
    752 				NET_VERBOSESNAPSHOT_PRINT( "object %d to client %d no longer stale\n", newState->objectNum, visIndex );
    753 				visChange = true;
    754 				visSendState = true;
    755 			}				
    756 		}
    757 		
    758 		// Same object, write a delta (never early out during vis changes)
    759 		if ( !visChange && newState->buffer.Size() == oldState->buffer.Size() &&
    760 			( ( newState->buffer.Ptr() == oldState->buffer.Ptr() ) || memcmp( newState->buffer.Ptr(), oldState->buffer.Ptr(), newState->buffer.Size() ) == 0 ) ) {
    761 			// same state, write nothing
    762 			return;
    763 		}
    764 	}
    765 		
    766 	// Get the id of the object we are writing out
    767 	uint16 objectNum;
    768 	if ( newState != NULL ) {
    769 		objectNum = newState->objectNum;
    770 	} else if ( oldState != NULL ) {
    771 		objectNum = oldState->objectNum;
    772 	} else {
    773 		objectNum = 0;
    774 	}
    775 	
    776 	assert( objectNum == 0 || objectNum > lastobjectNum );
    777 		
    778 	// Write out object id (using delta)
    779 	uint16 objectDelta = objectNum - lastobjectNum;
    780 	file->WriteBig( objectDelta );
    781 	lastobjectNum = objectNum;
    782 
    783 	if ( newState == NULL ) {
    784 		// Deleted, write 0 size
    785 		assert( oldState != NULL );
    786 		file->WriteBig<objectSize_t>( 0 );
    787 	} else if ( oldState == NULL ) {
    788 		// New object, write out full state
    789 		assert( newState != NULL );
    790 		// delta against an empty snap
    791 		file->WriteBig( newState->buffer.Size() );
    792 		file->Write( newState->buffer.Ptr(), newState->buffer.Size() );
    793 	} else {
    794 		// Compare to last object
    795 		assert( newState != NULL && oldState != NULL );
    796 		assert( newState->objectNum == oldState->objectNum );
    797 		
    798 		if ( visChange ) {
    799 			// fake size indicates vis state change
    800 			// NOTE: we may still send a real size and a state below, for 'no longer stale' transitions
    801 			// TMP: send 0xFFFF for going stale and 0xFFFF - 1 for no longer stale
    802 			file->WriteBig<objectSize_t>( visSendState ? SIZE_NOT_STALE : SIZE_STALE );
    803 		}
    804 		if ( !visChange || visSendState ) {
    805 			
    806 			objectSize_t compareSize = Min( newState->buffer.Size(), oldState->buffer.Size() );		// Get the number of bytes that overlap
    807 			
    808 			file->WriteBig( newState->buffer.Size() );										// Write new size
    809 			
    810 			// Compare bytes that overlap
    811 			for ( objectSize_t b = 0; b < compareSize; b++ ) {
    812 				file->WriteBig<byte>( ( 0xFF + 1 + newState->buffer[b] - oldState->buffer[b] ) & 0xFF );
    813 			}
    814 			
    815 			// Write leftover
    816 			if ( newState->buffer.Size() > compareSize ) {
    817 				file->Write( newState->buffer.Ptr() + oldState->buffer.Size(), newState->buffer.Size() - compareSize );
    818 			}
    819 		}
    820 	}
    821 
    822 #ifdef SNAPSHOT_CHECKSUMS
    823 	if ( ( !visChange || visSendState ) && newState != NULL ) {
    824 		assert( newState->buffer.Size() > 0 );
    825 		unsigned int checksum = MD5_BlockChecksum( newState->buffer.Ptr(), newState->buffer.Size() );
    826 		file->WriteBig( checksum );
    827 	}
    828 #endif
    829 }
    830 
    831 /*
    832 ========================
    833 idSnapShot::PrintReport
    834 ========================
    835 */
    836 void idSnapShot::PrintReport() {
    837 }
    838 
    839 /*
    840 ========================
    841 idSnapShot::WriteDelta
    842 ========================
    843 */
    844 bool idSnapShot::WriteDelta( idSnapShot & old, int visIndex, idFile * file, int maxLength, int optimalLength ) {
    845 	file->WriteBig( time );
    846 
    847 	int objectHeaderSize = sizeof( uint16 ) + sizeof( objectSize_t );
    848 #ifdef SNAPSHOT_CHECKSUMS
    849 	objectHeaderSize += sizeof( unsigned int );
    850 #endif
    851 
    852 	int lastobjectNum = 0;
    853 	int j = 0;
    854 	
    855 	for ( int i = 0; i < objectStates.Num(); i++ ) {
    856 		objectState_t & newState = *objectStates[i];
    857 
    858 		if ( optimalLength > 0 && file->Length() >= optimalLength ) {
    859 			return false;
    860 		}
    861 
    862 		if ( !verify( newState.buffer.Size() < maxLength ) ) {
    863 			// If the new state's size is > the max packet size, we'll never be able to send it!
    864 			idLib::Warning( "Snap obj [%d] state.size > max packet length. Skipping... ", newState.objectNum );
    865 			continue;
    866 		} else if ( !verify( newState.buffer.Size() > 0 ) ) {
    867 			// you CANNOT have a valid ss obj state w/ size = 0: this will be interpreted as a delete in ::ReadDelta and this will completely throw
    868 			// off delta compression, eventually resulting in a checksum error that is a pain to track down.
    869 			idLib::Warning( "Snap obj [%d] state.size <= 0... skipping ", newState.objectNum );
    870 			continue;
    871 		}
    872 
    873 		if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) {
    874 			return false;
    875 		}
    876 		
    877 		if ( j >= old.objectStates.Num() ) {
    878 			// delta against an empty snap
    879 			WriteObject( file, visIndex, &newState, NULL, lastobjectNum );
    880 			continue;
    881 		}
    882 
    883 		// write any deleted entities up to this one
    884 		for ( ; newState.objectNum > old.objectStates[j]->objectNum; j++ ) {
    885 			if ( file->Length() + objectHeaderSize >= maxLength ) {
    886 				return false;
    887 			}
    888 			objectState_t & oldState = *old.objectStates[j];
    889 			WriteObject( file, visIndex, NULL, &oldState, lastobjectNum );
    890 		}
    891 		
    892 		// Beyond this point, we have old state to compare against
    893 		objectState_t & oldState = *old.objectStates[j];
    894 
    895 		if ( newState.objectNum == oldState.objectNum ) {
    896 			// FIXME: We don't need to early out if WriteObject determines that we won't send the object due to being stale
    897 			if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) {
    898 				return false;
    899 			}
    900 			WriteObject( file, visIndex, &newState, &oldState, lastobjectNum );
    901 			j++;
    902 		} else {
    903 			if ( file->Length() + objectHeaderSize + newState.buffer.Size() >= maxLength ) {
    904 				return false;
    905 			}
    906 
    907 			// Different object, this one is new, write the full state
    908 			WriteObject( file, visIndex, &newState, NULL, lastobjectNum );
    909 		}
    910 	}
    911 	// Finally, remove any entities at the end
    912 	for ( ; j < old.objectStates.Num(); j++ ) {
    913 		if ( file->Length() + objectHeaderSize >= maxLength ) {
    914 			return false;
    915 		}
    916 
    917 		if ( optimalLength > 0 && file->Length() >= optimalLength ) {
    918 			return false;
    919 		}
    920 
    921 		objectState_t & oldState = *old.objectStates[j];
    922 		WriteObject( file, visIndex, NULL, &oldState, lastobjectNum );
    923 	}
    924 	if ( file->Length() + 2 >= maxLength ) {
    925 		return false;
    926 	}
    927 	uint16 objectDelta = 0xFFFF - lastobjectNum;
    928 	file->WriteBig( objectDelta );
    929 
    930 	return true;
    931 }
    932 
    933 /*
    934 ========================
    935 idSnapShot::AddObject
    936 ========================
    937 */
    938 idSnapShot::objectState_t * idSnapShot::S_AddObject( int objectNum, uint32 visMask, const char * data, int _size, const char * tag ) {
    939 	objectSize_t size = _size;
    940 	objectState_t & state = FindOrCreateObjectByID( objectNum );
    941 	state.visMask = visMask;
    942 	if ( state.buffer.Size() == size && state.buffer.NumRefs() == 1 ) {
    943 		// re-use the same buffer
    944 		memcpy( state.buffer.Ptr(), data, size );
    945 	} else {
    946 		objectBuffer_t buffer( size );
    947 		memcpy( buffer.Ptr(), data, size );
    948 		state.buffer = buffer;
    949 	}
    950 	return &state;
    951 }
    952 
    953 /*
    954 ========================
    955 idSnapShot::CopyObject
    956 ========================
    957 */
    958 
    959 bool idSnapShot::CopyObject( const idSnapShot & oldss, int objectNum, bool forceStale ) {	
    960 
    961 	int oldIndex = oldss.FindObjectIndexByID( objectNum );
    962 	if ( oldIndex == -1 ) {
    963 		return false;
    964 	}
    965 
    966 	const objectState_t & oldState = *oldss.objectStates[oldIndex];
    967 	objectState_t & newState = FindOrCreateObjectByID( objectNum );
    968 
    969 	newState.buffer			= oldState.buffer;
    970 	newState.visMask		= oldState.visMask;
    971 	newState.stale			= oldState.stale;
    972 	newState.deleted		= oldState.deleted;
    973 	newState.changedCount	= oldState.changedCount;
    974 	newState.expectedSequence = oldState.expectedSequence;
    975 	newState.createdFromTemplate = oldState.createdFromTemplate;
    976 
    977 	if ( forceStale ) {
    978 		newState.visMask = 0;
    979 	}
    980 
    981 	return true;
    982 }
    983 
    984 /*
    985 ========================
    986 idSnapShot::CompareObject
    987 start, end, and oldStart can optionally be passed in to compare subsections of the object
    988 default parameters will compare entire object
    989 ========================
    990 */
    991 int idSnapShot::CompareObject( const idSnapShot * oldss, int objectNum, int start, int end, int oldStart ) {
    992 	if ( oldss == NULL ) {
    993 		return 0;
    994 	}
    995 	
    996 	assert( FindObjectIndexByID( objectNum ) >= 0 );
    997 
    998 	objectState_t & newState = FindOrCreateObjectByID( objectNum );
    999 
   1000 	int oldIndex = oldss->FindObjectIndexByID( objectNum );
   1001 	if ( oldIndex == -1 ) {
   1002 		return ( end == 0 ? newState.buffer.Size() : end - start );	// Didn't exist in the old state, so we take the hit on the entire size
   1003 	}
   1004 
   1005 	objectState_t & oldState = const_cast< objectState_t & >( *oldss->objectStates[oldIndex] );
   1006 
   1007 	int bytes = 0;
   1008 
   1009 	int oldOffset = oldStart - start;
   1010 	int commonSize = ( newState.buffer.Size() <= oldState.buffer.Size() - oldOffset ) ? newState.buffer.Size() : oldState.buffer.Size() - oldOffset;
   1011 	if ( end == 0 ) {
   1012 		// default 0 means compare the whole thing
   1013 		end = commonSize;
   1014 		
   1015 		// Get leftover (if any)
   1016 		bytes = ( newState.buffer.Size() > oldState.buffer.Size() ) ? ( newState.buffer.Size() - oldState.buffer.Size() ) : 0;
   1017 	} else {
   1018 
   1019 		// else only compare up to end or the max buffer and dont include leftover
   1020 		end = Min( commonSize, end );
   1021 	}
   1022 
   1023 	for ( int b = start; b < end; b++ ) {
   1024 		if ( verify( b >= 0 && b < (int)newState.buffer.Size() && b + oldOffset >= 0 && b + oldOffset < (int)oldState.buffer.Size() ) ) {
   1025 			bytes += ( newState.buffer[b] != oldState.buffer[b + oldOffset] ) ? 1 : 0;
   1026 		}
   1027 	}
   1028 
   1029 	return bytes;
   1030 }
   1031 
   1032 /*
   1033 ========================
   1034 idSnapShot::GetObjectMsgByIndex
   1035 ========================
   1036 */
   1037 int idSnapShot::GetObjectMsgByIndex( int i, idBitMsg & msg, bool ignoreIfStale ) const {
   1038 	if ( i < 0 || i >= objectStates.Num() ) {
   1039 		return -1;
   1040 	}
   1041 	objectState_t & state = *objectStates[i];
   1042 	if ( state.stale && ignoreIfStale ) {
   1043 		return -1;
   1044 	}
   1045 	msg.InitRead( state.buffer.Ptr(), state.buffer.Size() );
   1046 	return state.objectNum;
   1047 }
   1048 
   1049 /*
   1050 ========================
   1051 idSnapShot::ObjectIsStaleByIndex
   1052 ========================
   1053 */
   1054 bool idSnapShot::ObjectIsStaleByIndex( int i ) const {
   1055 	if ( i < 0 || i >= objectStates.Num() ) {
   1056 		return false;
   1057 	}
   1058 	return objectStates[i]->stale;
   1059 }
   1060 
   1061 /*
   1062 ========================
   1063 idSnapShot::ObjectChangedCountByIndex
   1064 ========================
   1065 */
   1066 int idSnapShot::ObjectChangedCountByIndex( int i ) const {
   1067 	if ( i < 0 || i >= objectStates.Num() ) {
   1068 		return false;
   1069 	}
   1070 	return objectStates[i]->changedCount;
   1071 }
   1072 
   1073 /*
   1074 ========================
   1075 idSnapShot::FindObjectIndexByID
   1076 ========================
   1077 */
   1078 int idSnapShot::FindObjectIndexByID( int objectNum ) const {
   1079 	int i = BinarySearch( objectNum );
   1080 	if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) {
   1081 		return i;
   1082 	}
   1083 	return -1;
   1084 }
   1085 
   1086 /*
   1087 ========================
   1088 idSnapShot::BinarySearch
   1089 ========================
   1090 */
   1091 int idSnapShot::BinarySearch( int objectNum ) const {
   1092 	int lo = 0;
   1093 	int hi = objectStates.Num();
   1094 	while ( hi != lo ) {
   1095 		int mid = ( hi + lo ) >> 1;
   1096 
   1097 		if ( objectStates[mid]->objectNum == objectNum ) {
   1098 			return mid;		// Early out if we can
   1099 		}
   1100 
   1101 		if ( objectStates[mid]->objectNum < objectNum ) {
   1102 			lo = mid + 1;
   1103 		} else {
   1104 			hi = mid;
   1105 		}
   1106 	}
   1107 	return hi;
   1108 }
   1109 
   1110 /*
   1111 ========================
   1112 idSnapShot::FindOrCreateObjectByID
   1113 ========================
   1114 */
   1115 idSnapShot::objectState_t & idSnapShot::FindOrCreateObjectByID( int objectNum ) {
   1116 	//assert( mem.IsMapHeap() );
   1117 
   1118 	int i = BinarySearch( objectNum );
   1119 
   1120 	if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) {
   1121 		return *objectStates[i];
   1122 	}
   1123 
   1124 	objectState_t * newstate = allocatedObjs.Alloc();
   1125 	newstate->objectNum = objectNum;
   1126 
   1127 	objectStates.Insert( newstate, i );
   1128 
   1129 	return *objectStates[i];
   1130 }
   1131 
   1132 /*
   1133 ========================
   1134 idSnapShot::FindObjectByID
   1135 ========================
   1136 */
   1137 idSnapShot::objectState_t * idSnapShot::FindObjectByID( int objectNum ) const {
   1138 
   1139 	//assert( mem.IsMapHeap() );
   1140 
   1141 	int i = BinarySearch( objectNum );
   1142 
   1143 	if ( i >= 0 && i < objectStates.Num() && objectStates[i]->objectNum == objectNum ) {
   1144 		return objectStates[i];
   1145 	}
   1146 
   1147 	return NULL;
   1148 }
   1149 
   1150 /*
   1151 ========================
   1152 idSnapShot::CleanupEmptyStates
   1153 ========================
   1154 */
   1155 void idSnapShot::CleanupEmptyStates() {
   1156 	for ( int i = objectStates.Num() - 1; i >= 0 ; i-- ) {
   1157 		if ( objectStates[i]->buffer.Size() == 0 ) {
   1158 			FreeObjectState( i );
   1159 			objectStates.RemoveIndex(i);
   1160 		}
   1161 	}
   1162 }
   1163 
   1164 /*
   1165 ========================
   1166 idSnapShot::UpdateExpectedSeq
   1167 ========================
   1168 */
   1169 void idSnapShot::UpdateExpectedSeq( int newSeq ) {
   1170 	for ( int i = 0; i < objectStates.Num(); i++ ) {
   1171 		if ( objectStates[i]->expectedSequence == -2 ) {
   1172 			objectStates[i]->expectedSequence = newSeq;
   1173 		}
   1174 	}
   1175 }
   1176 
   1177 /*
   1178 ========================
   1179 idSnapShot::FreeObjectState
   1180 ========================
   1181 */
   1182 void idSnapShot::FreeObjectState( int index ) {
   1183 	assert( objectStates[index] != NULL );
   1184 	//assert( mem.IsMapHeap() );
   1185 	objectStates[index]->buffer._Release();
   1186 	allocatedObjs.Free( objectStates[index] );
   1187 	objectStates[index] = NULL;
   1188 }
   1189 
   1190 /*
   1191 ========================
   1192 idSnapShot::ApplyToExistingState
   1193 Take uncompressed state in msg and add it to existing state
   1194 ========================
   1195 */
   1196 void idSnapShot::ApplyToExistingState( int objId, idBitMsg & msg ) {
   1197 	objectState_t *	objectState = FindObjectByID( objId );
   1198 	if ( !verify( objectState != NULL ) ) {
   1199 		return;	
   1200 	}
   1201 
   1202 	if ( !objectState->createdFromTemplate ) {
   1203 		// We were created this from a template, so we shouldn't be applying it again
   1204 		if ( net_ssTemplateDebug.GetBool() ) {
   1205 			idLib::Printf( "NOT ApplyToExistingState[%d] because object was created from existing base state. %d\n", objId, objectState->expectedSequence  );
   1206 			objectState->Print( "SS STATE" );
   1207 		}
   1208 		return;
   1209 	}
   1210 
   1211 	// Debug print the template (spawn) and delta state
   1212 	if ( net_ssTemplateDebug.GetBool() ) {
   1213 		idLib::Printf( "\nApplyToExistingState[%d]. buffer size: %d msg size: %d\n", objId, objectState->buffer.Size(), msg.GetSize() );
   1214 		objectState->Print( "DELTA STATE" );
   1215 		
   1216 		PrintAlign( "SPAWN STATE" );
   1217 		for ( int i = 0; i < msg.GetSize(); i++ ) {
   1218 			if ( InDebugRange( i ) ) {
   1219 				idLib::Printf( "%02X", msg.GetReadData()[i] );
   1220 			}
   1221 		}
   1222 		idLib::Printf( "\n" );
   1223 	}
   1224 
   1225 	// Actually apply it
   1226 	for ( objectSize_t i = 0; i < Min( objectState->buffer.Size(), msg.GetSize() ); i++ ) {
   1227 		objectState->buffer[i] += msg.GetReadData()[i];
   1228 	}
   1229 
   1230 	// Debug print the final state
   1231 	if ( net_ssTemplateDebug.GetBool() ) {
   1232 		objectState->Print( "NEW STATE" );
   1233 		idLib::Printf( "\n" );
   1234 	}
   1235 }
   1236 
   1237 #if 0
   1238 CONSOLE_COMMAND( serializeQTest, "Serialization Sanity Test", 0 ) {
   1239 
   1240 	byte buffer[1024];
   1241 	memset( buffer, 0, sizeof( buffer ) );	
   1242 
   1243 	float values[] = { 0.0001f, 0.001f, 0.01f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 0.999f, 
   1244 							1.0f, 1.01f, 1.1f, 10.0f, 10.1f, 10.101f, 100.0f, 101.0f, 101.1f, 101.101f };
   1245 	int num = sizeof(values) / sizeof(float);
   1246 
   1247 	idLib::Printf("\n^3Testing SerializeQ and SerializeUQ \n");
   1248 
   1249 	{
   1250 		idBitMsg writeBitMsg;
   1251 		writeBitMsg.InitWrite( buffer, sizeof(buffer) );
   1252 		idSerializer writeSerializer( writeBitMsg, true );
   1253 
   1254 		for( int i = 0; i < num; i++ ) {
   1255 			writeSerializer.SerializeUQ( values[i], 255.0f, 16 );
   1256 			writeSerializer.SerializeQ( values[i], 128.0f, 16 );
   1257 		}
   1258 	}
   1259 
   1260 	{
   1261 		idBitMsg readBitMsg;
   1262 		readBitMsg.InitRead( buffer, sizeof( buffer ) );
   1263 		idSerializer readSerializer( readBitMsg, false );
   1264 
   1265 		for( int i = 0; i < num; i++ ) {
   1266 
   1267 			float resultUQ = -999.0f;
   1268 			float resultQ  = -999.0f;
   1269 
   1270 			readSerializer.SerializeUQ( resultUQ, 255.0f, 16 );
   1271 			readSerializer.SerializeQ( resultQ, 128.0f, 16 );
   1272 
   1273 			float errorUQ = idMath::Fabs( (resultUQ - values[i]) ) / values[i];
   1274 			float errorQ  = idMath::Fabs( (resultQ - values[i]) ) / values[i];
   1275 
   1276 			idLib::Printf( "%s%f SerializeUQ: %f. Error: %f \n", errorUQ > 0.1f ? "^1": "", values[i], resultUQ, errorUQ );			
   1277 			idLib::Printf( "%s%f SerializeQ: %f. Error: %f \n",   errorQ > 0.1f ? "^1": "", values[i], resultQ, errorQ );
   1278 		}
   1279 	}
   1280 }
   1281 #endif