DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

SnapshotProcessor.cpp (20164B)


      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_optimalSnapDeltaSize( "net_optimalSnapDeltaSize", "1000", CVAR_INTEGER, "Optimal size of snapshot delta msgs." );
     32 idCVar net_debugBaseStates( "net_debugBaseStates", "0", CVAR_BOOL, "Log out base state information" );
     33 idCVar net_skipClientDeltaAppend( "net_skipClientDeltaAppend", "0", CVAR_BOOL, "Simulate delta receive buffer overflowing" );
     34 
     35 /*
     36 ========================
     37 idSnapshotProcessor::idSnapshotProcessor
     38 ========================
     39 */
     40 idSnapshotProcessor::idSnapshotProcessor() {
     41 
     42 	//assert( mem.IsGlobalHeap() );
     43 
     44 	jobMemory = (jobMemory_t*)Mem_Alloc( sizeof( jobMemory_t) , TAG_NETWORKING );
     45 
     46 	assert_16_byte_aligned( jobMemory );
     47 	assert_16_byte_aligned( jobMemory->objParms.Ptr() );
     48 	assert_16_byte_aligned( jobMemory->headers.Ptr() );
     49 	assert_16_byte_aligned( jobMemory->lzwParms.Ptr() );
     50 
     51 	Reset( true );
     52 }
     53 
     54 /*
     55 ========================
     56 idSnapshotProcessor::idSnapshotProcessor
     57 ========================
     58 */
     59 idSnapshotProcessor::~idSnapshotProcessor() {
     60 	//mem.PushHeap();
     61 	Mem_Free( jobMemory );
     62 	//mem.PopHeap();
     63 }
     64 
     65 /*
     66 ========================
     67 idSnapshotProcessor::Reset
     68 ========================
     69 */
     70 void idSnapshotProcessor::Reset( bool cstor ) {
     71 	hasPendingSnap	= false;
     72 	snapSequence	= INITIAL_SNAP_SEQUENCE;
     73 	baseSequence	= -1;	
     74 	lastFullSnapBaseSequence = -1;
     75 
     76 	if ( !cstor && net_debugBaseStates.GetBool() ) {
     77 		idLib::Printf( "NET: Reset snapshot base");
     78 	}
     79 	
     80 	baseState.Clear();
     81 	submittedState.Clear();
     82 	pendingSnap.Clear();
     83 	deltas.Clear();
     84 
     85 	partialBaseSequence = -1;
     86 
     87 	memset( &jobMemory->lzwInOutData, 0, sizeof( jobMemory->lzwInOutData ) );
     88 }
     89 
     90 /*
     91 ========================
     92 idSnapshotProcessor::TrySetPendingSnapshot
     93 ========================
     94 */
     95 bool idSnapshotProcessor::TrySetPendingSnapshot( idSnapShot & ss ) {
     96 	// Don't advance to the next snap until the last one was fully sent
     97 	if ( hasPendingSnap ) {
     98 		return false;
     99 	}
    100 	pendingSnap = ss;
    101 	hasPendingSnap = true;
    102 	return true;
    103 }
    104 
    105 /*
    106 ========================
    107 idSnapshotProcessor::PeekDeltaSequence
    108 ========================
    109 */
    110 void idSnapshotProcessor::PeekDeltaSequence( const char * deltaMem, int deltaSize, int & deltaSequence, int & deltaBaseSequence ) {
    111 	idSnapShot::PeekDeltaSequence( deltaMem, deltaSize, deltaSequence, deltaBaseSequence );
    112 }
    113 
    114 /*
    115 ========================
    116 idSnapshotProcessor::ApplyDeltaToSnapshot
    117 ========================
    118 */
    119 bool idSnapshotProcessor::ApplyDeltaToSnapshot( idSnapShot & snap, const char * deltaMem, int deltaSize, int visIndex ) {
    120 	return snap.ReadDeltaForJob( deltaMem, deltaSize, visIndex, &templateStates );
    121 }
    122 
    123 #ifdef STRESS_LZW_MEM
    124 // When this defined, we'll stress the lzw compressor with the smallest possible buffer, and detect when we need to grow it to make
    125 // sure we are gacefully detecting the situation.
    126 static int g_maxlwMem = 100;		
    127 #endif
    128 
    129 /*
    130 ========================
    131 idSnapshotProcessor::SubmitPendingSnap
    132 ========================
    133 */
    134 void idSnapshotProcessor::SubmitPendingSnap( int visIndex, uint8 * objMemory, int objMemorySize, lzwCompressionData_t * lzwData ) {
    135 
    136 	assert_16_byte_aligned( objMemory );
    137 	assert_16_byte_aligned( lzwData );
    138 
    139 	assert( hasPendingSnap );
    140 	assert( jobMemory->lzwInOutData.numlzwDeltas == 0 );
    141 	
    142 	assert( net_optimalSnapDeltaSize.GetInteger() < jobMemory_t::MAX_LZW_MEM - 128 );		// Leave padding
    143 
    144 	jobMemory->lzwInOutData.lzwDeltas		= jobMemory->lzwDeltas.Ptr();
    145 	jobMemory->lzwInOutData.maxlzwDeltas	= jobMemory->lzwDeltas.Num();
    146 	jobMemory->lzwInOutData.lzwMem			= jobMemory->lzwMem.Ptr();
    147 
    148 #ifdef STRESS_LZW_MEM
    149 	jobMemory->lzwInOutData.maxlzwMem		= g_maxlwMem;
    150 #else
    151 	jobMemory->lzwInOutData.maxlzwMem		= jobMemory_t::MAX_LZW_MEM;
    152 #endif
    153 
    154 	jobMemory->lzwInOutData.lzwDmaOut		= jobMemory_t::MAX_LZW_MEM;
    155 	jobMemory->lzwInOutData.numlzwDeltas	= 0;
    156 	jobMemory->lzwInOutData.lzwBytes		= 0;
    157 	jobMemory->lzwInOutData.optimalLength	= net_optimalSnapDeltaSize.GetInteger();
    158 	jobMemory->lzwInOutData.snapSequence	= snapSequence;
    159 	jobMemory->lzwInOutData.lastObjId		= 0;
    160 	jobMemory->lzwInOutData.lzwData			= lzwData;
    161 
    162 	idSnapShot::submitDeltaJobsInfo_t submitInfo;
    163 
    164 	submitInfo.objParms			= jobMemory->objParms.Ptr();
    165 	submitInfo.maxObjParms		= jobMemory->objParms.Num();
    166 	submitInfo.headers			= jobMemory->headers.Ptr();
    167 	submitInfo.maxHeaders		= jobMemory->headers.Num();
    168 	submitInfo.objMemory		= objMemory;
    169 	submitInfo.maxObjMemory		= objMemorySize;
    170 	submitInfo.lzwParms			= jobMemory->lzwParms.Ptr();
    171 	submitInfo.maxDeltaParms	= jobMemory->lzwParms.Num();
    172 	
    173 	
    174 	// Use a copy of base state to avoid race conditions. 
    175 	// The main thread could change it behind the jobs backs.
    176 	submittedState				= baseState;
    177 	submittedTemplateStates		= templateStates;
    178 
    179 	submitInfo.templateStates	= &submittedTemplateStates;
    180 	
    181 	submitInfo.oldSnap			= &submittedState;
    182 	submitInfo.visIndex			= visIndex;
    183 	submitInfo.baseSequence		= baseSequence;
    184 		
    185 	submitInfo.lzwInOutData		= &jobMemory->lzwInOutData;
    186 
    187 	pendingSnap.SubmitWriteDeltaToJobs( submitInfo );
    188 }
    189 
    190 /*
    191 ========================
    192 idSnapshotProcessor::GetPendingSnapDelta
    193 ========================
    194 */
    195 int idSnapshotProcessor::GetPendingSnapDelta( byte * outBuffer, int maxLength ) {
    196 
    197 	assert( PendingSnapReadyToSend() );
    198 
    199 	if ( !verify( jobMemory->lzwInOutData.numlzwDeltas == 1 ) ) {
    200 		jobMemory->lzwInOutData.numlzwDeltas = 0;
    201 		return 0;  // No more deltas left to send
    202 	}
    203 
    204 	assert( hasPendingSnap );
    205 
    206 	jobMemory->lzwInOutData.numlzwDeltas = 0;
    207 
    208 	int size = jobMemory->lzwDeltas[0].size;
    209 
    210 	if ( !verify( size != -1 ) ) {
    211 #ifdef STRESS_LZW_MEM
    212 		if ( g_maxlwMem < MAX_LZW_MEM ) {
    213 			g_maxlwMem += 50;
    214 			g_maxlwMem = Min( g_maxlwMem, MAX_LZW_MEM );
    215 			return 0;
    216 		}
    217 #endif
    218 
    219 		// This can happen if there wasn't enough maxlzwMem to process one full obj in a single delta
    220 		idLib::Error( "GetPendingSnapDelta: Delta failed." );
    221 	}
    222 		
    223 	uint8 * deltaData = &jobMemory->lzwMem[jobMemory->lzwDeltas[0].offset];
    224 	
    225 	int deltaSequence		= 0;
    226 	int deltaBaseSequence	= 0;
    227 	PeekDeltaSequence( (const char *)deltaData, size, deltaSequence, deltaBaseSequence );	
    228 	// sanity check: does the compressed data we are about to send have the sequence number we expect
    229 	assert( deltaSequence == jobMemory->lzwDeltas[0].snapSequence );
    230 
    231 	if ( !verify( size <= maxLength ) ) {
    232 		idLib::Error( "GetPendingSnapDelta: Size overflow." );
    233 	}
    234 
    235 	// Copy to out buffer
    236 	memcpy( outBuffer, deltaData, size );
    237 	
    238 	// Set the sequence to what this delta actually belongs to
    239 	assert( jobMemory->lzwDeltas[0].snapSequence == snapSequence + 1 );
    240 	snapSequence = jobMemory->lzwDeltas[0].snapSequence;
    241 
    242 	//idLib::Printf( "deltas Num: %i, Size: %i\n", deltas.Num(), deltas.GetDataLength() );
    243 
    244 	// Copy to delta buffer
    245 	// NOTE - We don't need to save this delta off if peer has already ack'd this basestate.
    246 	// This can happen due to the fact that we defer the processing of snap deltas on jobs.
    247 	// When we start processing a delta, we use the currently ack'd basestate.  If while we were processing 
    248 	// the delta, the client acks a new basestate, we can get into this situation.  In this case, we simply don't 
    249 	// store the delta, since it will just take up space, and just get removed anyways during ApplySnapshotDelta.
    250 	//	 (and cause lots of spam when it sees the delta's basestate doesn't match the current ack'd one)
    251 	if ( deltaBaseSequence >= baseSequence ) {	
    252 		if ( !deltas.Append( snapSequence, deltaData, size ) ) {
    253 			int resendLength = deltas.ItemLength( deltas.Num() - 1 );
    254 
    255 			if ( !verify( resendLength <= maxLength ) ) {
    256 				idLib::Error( "GetPendingSnapDelta: Size overflow for resend." );
    257 			}
    258 
    259 			memcpy( outBuffer, deltas.ItemData( deltas.Num() - 1 ), resendLength );
    260 			size = -resendLength;
    261 		}
    262 	}
    263 
    264 	if ( jobMemory->lzwInOutData.fullSnap ) {
    265 		// We sent the full snap, we can stop sending this pending snap now...
    266 		NET_VERBOSESNAPSHOT_PRINT_LEVEL( 5, va( "  wrote enough deltas to a full snapshot\n" ) ); // FIXME: peer number?
    267 
    268 		hasPendingSnap = false;
    269 		partialBaseSequence = -1;
    270 
    271 	} else {
    272 		partialBaseSequence = deltaBaseSequence;
    273 	}
    274 
    275 	return size;
    276 }
    277 
    278 /*
    279 ========================
    280 idSnapshotProcessor::IsBusyConfirmingPartialSnap
    281 ========================
    282 */
    283 bool idSnapshotProcessor::IsBusyConfirmingPartialSnap() {
    284 	if ( partialBaseSequence != -1 && baseSequence <= partialBaseSequence ) {
    285 		return true;
    286 	}
    287 
    288 	return false;
    289 }
    290 
    291 /*
    292 ========================
    293 idSnapshotProcessor::ReceiveSnapshotDelta
    294 NOTE: we use ReadDeltaForJob twice, once to build the same base as the server (based on server acks, down ApplySnapshotDelta), and another time to apply the snapshot we just received
    295 could we avoid the double apply by keeping outSnap cached in memory and avoid rebuilding it from a delta when the next one comes around?
    296 ========================
    297 */
    298 bool idSnapshotProcessor::ReceiveSnapshotDelta( const byte * deltaData, int deltaLength, int visIndex, int & outSeq, int & outBaseSeq, idSnapShot & outSnap, bool & fullSnap ) {
    299 
    300 	fullSnap = false;
    301 
    302 	int deltaSequence		= 0;
    303 	int deltaBaseSequence	= 0;
    304 
    305 	// Get the sequence of this delta, and the base sequence it is delta'd from
    306 	PeekDeltaSequence( (const char *)deltaData, deltaLength, deltaSequence, deltaBaseSequence );
    307 
    308 	//idLib::Printf("Incoming snapshot: %i, %i\n", deltaSequence, deltaBaseSequence );
    309 	
    310 	if ( deltaSequence <= snapSequence ) {
    311 		NET_VERBOSESNAPSHOT_PRINT( "Rejecting old delta: %d (snapSequence: %d \n", deltaSequence, snapSequence );
    312 		return false;		// Completely reject older out of order deltas
    313 	}
    314 	
    315 	// Bring the base state up to date with the basestate this delta was compared to
    316 	ApplySnapshotDelta( visIndex, deltaBaseSequence );
    317 
    318 	// Once we get here, our base state should be caught up to that of the server
    319 	assert( baseSequence == deltaBaseSequence );
    320 
    321 	// Save the new delta
    322 	if ( net_skipClientDeltaAppend.GetBool() || !deltas.Append( deltaSequence, deltaData, deltaLength ) ) {
    323 		// This can happen if the delta queues get desync'd between the server and client.
    324 		// With recent fixes, this should be extremely rare, or impossible.
    325 		// Just in case this happens, we can recover by assuming we didn't even receive this delta.
    326 		idLib::Printf( "NET: ReceiveSnapshotDelta: No room to append delta %d/%d \n", deltaSequence, deltaBaseSequence );
    327 		return false;
    328 	}
    329 	
    330 	// Update our snapshot sequence number to the newer one we just got (now that it's safe)
    331 	snapSequence = deltaSequence;
    332 
    333 	if ( deltas.Num() > 10 ) {
    334 		NET_VERBOSESNAPSHOT_PRINT("NET: ReceiveSnapshotDelta: deltas.Num() > 10: %d\n   ", deltas.Num() );
    335 		for ( int i=0; i < deltas.Num(); i++ ) {
    336 			NET_VERBOSESNAPSHOT_PRINT( "%d ", deltas.ItemSequence( i ) );
    337 		}
    338 		NET_VERBOSESNAPSHOT_PRINT( "\n" );
    339 	}
    340 
    341 		
    342 	if ( baseSequence != deltaBaseSequence ) {
    343 		// NOTE - With recent fixes, this should no longer be possible unless the delta is trashed
    344 		// We should probably disconnect from the server when this happens now.
    345 		static bool failed = false;
    346 		if ( !failed ) {
    347 			idLib::Printf( "NET: incorrect base state? not sure how this can happen... baseSequence: %d  deltaBaseSequence: %d \n", baseSequence, deltaBaseSequence );
    348 		}
    349 		failed = true;
    350 		return false;
    351 	}
    352 
    353 	// Copy out the current deltas sequence values to caller
    354 	outSeq		= deltaSequence;
    355 	outBaseSeq	= deltaBaseSequence;
    356 
    357 	if ( baseSequence < 50 && net_debugBaseStates.GetBool() ) {
    358 		idLib::Printf( "NET: Proper basestate...  baseSequence: %d  deltaBaseSequence: %d \n", baseSequence, deltaBaseSequence );
    359 	}
    360 
    361 	// Make a copy of the basestate the server used to create this delta, and then apply and return it
    362 	outSnap = baseState;
    363 	
    364 	fullSnap = ApplyDeltaToSnapshot( outSnap, (const char *)deltaData, deltaLength, visIndex );
    365 
    366 	// We received a new delta
    367 	return true;
    368 }
    369 
    370 /*
    371 ========================
    372 idSnapshotProcessor::ApplySnapshotDelta
    373 Apply a snapshot delta to our current basestate, and make that the new base.
    374 We can remove all deltas that refer to the basetate we just removed.
    375 ========================
    376 */
    377 bool idSnapshotProcessor::ApplySnapshotDelta( int visIndex, int snapshotNumber ) {
    378 
    379 	NET_VERBOSESNAPSHOT_PRINT_LEVEL( 6, va( "idSnapshotProcessor::ApplySnapshotDelta snapshotNumber: %d\n", snapshotNumber ) );
    380 
    381 	// Sanity check deltas
    382 	SanityCheckDeltas();
    383 
    384 	// dump any deltas older than the acknoweledged snapshot, which should only happen if there is packet loss
    385 	deltas.RemoveOlderThan( snapshotNumber );
    386 
    387 	if ( deltas.Num() == 0 || deltas.ItemSequence( 0 ) != snapshotNumber ) {
    388 		// this means the snapshot was either already acknowledged or came out of order
    389 		// On the server, this can happen because the client is continuously/redundantly sending acks
    390 		// Once the server has ack'd a certain base sequence, it will need to ignore all the redundant ones.
    391 		// On the client, this will only happen due to out of order, or dropped packets.
    392 		
    393 		if ( !common->IsServer() ) {
    394 			// these should be printed every time on the clients
    395 			// printing on server is not useful / results in tons of spam
    396 			if ( deltas.Num() == 0 ) {
    397 				NET_VERBOSESNAPSHOT_PRINT("NET: Got snapshot but ignored... deltas.Num(): %d snapshotNumber: %d \n", deltas.Num(), snapshotNumber );
    398 			} else {
    399 				NET_VERBOSESNAPSHOT_PRINT("NET: Got snapshot but ignored... deltas.ItemSequence( 0 ): %d != snapshotNumber: %d \n   ", deltas.ItemSequence( 0 ), snapshotNumber );
    400 				
    401 				for ( int i=0; i < deltas.Num(); i++ ) {
    402 					NET_VERBOSESNAPSHOT_PRINT("%d ", deltas.ItemSequence(i) );
    403 				}
    404 				NET_VERBOSESNAPSHOT_PRINT("\n");
    405 
    406 			}
    407 		}
    408 		return false;
    409 	}
    410 
    411 	int deltaSequence		= 0;
    412 	int deltaBaseSequence	= 0;
    413 
    414 	PeekDeltaSequence( (const char *)deltas.ItemData( 0 ), deltas.ItemLength( 0 ), deltaSequence, deltaBaseSequence );
    415 
    416 	assert( deltaSequence == snapshotNumber );		// Make sure compressed sequence number matches that in data queue
    417 	assert( baseSequence == deltaBaseSequence );	// If this delta isn't based off of our currently ack'd basestate, something is trashed...
    418 	assert( deltaSequence > baseSequence );
    419 
    420 	if ( baseSequence != deltaBaseSequence ) {
    421 		// NOTE - This should no longer happen with recent fixes.  
    422 		// We should probably disconnect from the server if this happens. (packets are trashed most likely)
    423 		NET_VERBOSESNAPSHOT_PRINT( "NET: Got snapshot %d but baseSequence does not match. baseSequence: %d deltaBaseSequence: %d. \n", snapshotNumber, baseSequence, deltaBaseSequence );
    424 		return false;
    425 	}
    426 
    427 	// Apply this delta to our base state
    428 	if ( ApplyDeltaToSnapshot( baseState, (const char *)deltas.ItemData( 0 ), deltas.ItemLength( 0 ), visIndex ) ) {
    429 		lastFullSnapBaseSequence = deltaSequence;
    430 	}
    431 	
    432 	baseSequence = deltaSequence;		// This is now our new base sequence
    433 
    434 	// Remove deltas that we no longer need
    435 	RemoveDeltasForOldBaseSequence();
    436 	
    437 	// Sanity check deltas
    438 	SanityCheckDeltas();
    439 
    440 	return true;
    441 }
    442 
    443 /*
    444 ========================
    445 idSnapshotProcessor::RemoveDeltasForOldBaseSequence
    446 Remove deltas for basestate we no longer have. We know we can remove them, because we will never 
    447 be able to apply them, since the basestate needed to generate a full snap from these deltas is gone.
    448 
    449 Ways we can get deltas based on basestate we no longer have:
    450 	1. Server sends sequence 50 based on 49.  It then sends sequence 51 based on 49.
    451 	   Client acks 50, server applies it to 49, 50 is new base state.
    452 	   Server now has a delta sequence 51 based on 49 that it won't ever be able to apply (50 is new basestate).	
    453 
    454 This is annoying, because it makes a lot of our sanity checks incorrectly fire off for benign issues.
    455 Here is a series of events that make the old ( baseSequence != deltaBaseSequence ) assert:
    456 
    457 Server:
    458 49->50, 49->51, ack 50, 50->52, ack 51 (bam), 50->53
    459 
    460 Client
    461 49->50, ack 50, 49->51, ack 51 (bam), 50->52, 50->53
    462 
    463 The client above will ack 51, even though he can't even apply that delta.  To get around this, we simply don't
    464 allow delta to exist in the list, unless their basestate is the current basestate we maintain.
    465 This allows us to put sanity checks in place that don't fire off during benign conditions, and allow us 
    466 to truly check for trashed conditions.
    467 
    468 ========================
    469 */
    470 void idSnapshotProcessor::RemoveDeltasForOldBaseSequence() {
    471 	// Remove any deltas that would apply to the old base we no longer maintain
    472 	// (we will never be able to apply these, since we don't have that base anymore)
    473 	for ( int i = deltas.Num() - 1; i >= 0; i-- ) {
    474 		int deltaSequence		= 0;
    475 		int deltaBaseSequence	= 0;
    476 		baseState.PeekDeltaSequence( (const char *)deltas.ItemData( i ), deltas.ItemLength( i ), deltaSequence, deltaBaseSequence );
    477 		if ( deltaBaseSequence < baseSequence ) {
    478 			// Remove this delta, and all deltas before this one 
    479 			deltas.RemoveOlderThan( deltas.ItemSequence( i ) + 1 );
    480 			break;
    481 		}
    482 	}
    483 }
    484 
    485 /*
    486 ========================
    487 idSnapshotProcessor::SanityCheckDeltas
    488 Make sure delta sequence and basesequence values are valid, and in order, etc
    489 ========================
    490 */
    491 void idSnapshotProcessor::SanityCheckDeltas() {
    492 	int deltaSequence			= 0;
    493 	int deltaBaseSequence		= 0;
    494 	int lastDeltaSequence		= -1;
    495 	int lastDeltaBaseSequence	= -1;
    496 	
    497 	for ( int i = 0; i < deltas.Num(); i++ ) {
    498 		baseState.PeekDeltaSequence( (const char *)deltas.ItemData( i ), deltas.ItemLength( i ), deltaSequence, deltaBaseSequence );
    499 		assert( deltaSequence == deltas.ItemSequence( i ) );	// Make sure delta stored in compressed form matches the one stored in the data queue
    500 		assert( deltaSequence > lastDeltaSequence );			// Make sure they are in order (we reject out of order sequences in ApplysnapshotDelta)
    501 		assert( deltaBaseSequence >= lastDeltaBaseSequence );	// Make sure they are in order (they can be the same, since base sequences don't change until they've been ack'd)
    502 		assert( deltaBaseSequence >= baseSequence );			// We should have removed old delta's that can no longer be applied
    503 		assert( deltaBaseSequence == baseSequence || deltaBaseSequence == lastDeltaSequence );	// Make sure we still have a base (or eventually will have) that we can apply this delta to
    504 		lastDeltaSequence = deltaSequence;
    505 		lastDeltaBaseSequence = deltaBaseSequence;
    506 	}
    507 }
    508 
    509 /*
    510 ========================
    511 idSnapshotProcessor::AddSnapObjTemplate
    512 ========================
    513 */
    514 void idSnapshotProcessor::AddSnapObjTemplate( int objID, idBitMsg & msg ) {
    515 	extern idCVar net_ssTemplateDebug;
    516 	idSnapShot::objectState_t * state = templateStates.S_AddObject( objID, MAX_UNSIGNED_TYPE( uint32 ), msg );
    517 	if ( verify( state != NULL ) ) {
    518 		if ( net_ssTemplateDebug.GetBool() ) {
    519 			idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "InjectingSnapObjBaseState[%d] size: %d\n", objID, state->buffer.Size() );
    520 			state->Print( "BASE STATE" );
    521 		}
    522 		state->expectedSequence = snapSequence;
    523 	}
    524 }