123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- /*
- ===========================================================================
- 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"
- idCVar net_optimalSnapDeltaSize( "net_optimalSnapDeltaSize", "1000", CVAR_INTEGER, "Optimal size of snapshot delta msgs." );
- idCVar net_debugBaseStates( "net_debugBaseStates", "0", CVAR_BOOL, "Log out base state information" );
- idCVar net_skipClientDeltaAppend( "net_skipClientDeltaAppend", "0", CVAR_BOOL, "Simulate delta receive buffer overflowing" );
- /*
- ========================
- idSnapshotProcessor::idSnapshotProcessor
- ========================
- */
- idSnapshotProcessor::idSnapshotProcessor() {
- //assert( mem.IsGlobalHeap() );
- jobMemory = (jobMemory_t*)Mem_Alloc( sizeof( jobMemory_t) , TAG_NETWORKING );
- assert_16_byte_aligned( jobMemory );
- assert_16_byte_aligned( jobMemory->objParms.Ptr() );
- assert_16_byte_aligned( jobMemory->headers.Ptr() );
- assert_16_byte_aligned( jobMemory->lzwParms.Ptr() );
- Reset( true );
- }
- /*
- ========================
- idSnapshotProcessor::idSnapshotProcessor
- ========================
- */
- idSnapshotProcessor::~idSnapshotProcessor() {
- //mem.PushHeap();
- Mem_Free( jobMemory );
- //mem.PopHeap();
- }
- /*
- ========================
- idSnapshotProcessor::Reset
- ========================
- */
- void idSnapshotProcessor::Reset( bool cstor ) {
- hasPendingSnap = false;
- snapSequence = INITIAL_SNAP_SEQUENCE;
- baseSequence = -1;
- lastFullSnapBaseSequence = -1;
- if ( !cstor && net_debugBaseStates.GetBool() ) {
- idLib::Printf( "NET: Reset snapshot base");
- }
-
- baseState.Clear();
- submittedState.Clear();
- pendingSnap.Clear();
- deltas.Clear();
- partialBaseSequence = -1;
- memset( &jobMemory->lzwInOutData, 0, sizeof( jobMemory->lzwInOutData ) );
- }
- /*
- ========================
- idSnapshotProcessor::TrySetPendingSnapshot
- ========================
- */
- bool idSnapshotProcessor::TrySetPendingSnapshot( idSnapShot & ss ) {
- // Don't advance to the next snap until the last one was fully sent
- if ( hasPendingSnap ) {
- return false;
- }
- pendingSnap = ss;
- hasPendingSnap = true;
- return true;
- }
- /*
- ========================
- idSnapshotProcessor::PeekDeltaSequence
- ========================
- */
- void idSnapshotProcessor::PeekDeltaSequence( const char * deltaMem, int deltaSize, int & deltaSequence, int & deltaBaseSequence ) {
- idSnapShot::PeekDeltaSequence( deltaMem, deltaSize, deltaSequence, deltaBaseSequence );
- }
- /*
- ========================
- idSnapshotProcessor::ApplyDeltaToSnapshot
- ========================
- */
- bool idSnapshotProcessor::ApplyDeltaToSnapshot( idSnapShot & snap, const char * deltaMem, int deltaSize, int visIndex ) {
- return snap.ReadDeltaForJob( deltaMem, deltaSize, visIndex, &templateStates );
- }
- #ifdef STRESS_LZW_MEM
- // When this defined, we'll stress the lzw compressor with the smallest possible buffer, and detect when we need to grow it to make
- // sure we are gacefully detecting the situation.
- static int g_maxlwMem = 100;
- #endif
- /*
- ========================
- idSnapshotProcessor::SubmitPendingSnap
- ========================
- */
- void idSnapshotProcessor::SubmitPendingSnap( int visIndex, uint8 * objMemory, int objMemorySize, lzwCompressionData_t * lzwData ) {
- assert_16_byte_aligned( objMemory );
- assert_16_byte_aligned( lzwData );
- assert( hasPendingSnap );
- assert( jobMemory->lzwInOutData.numlzwDeltas == 0 );
-
- assert( net_optimalSnapDeltaSize.GetInteger() < jobMemory_t::MAX_LZW_MEM - 128 ); // Leave padding
- jobMemory->lzwInOutData.lzwDeltas = jobMemory->lzwDeltas.Ptr();
- jobMemory->lzwInOutData.maxlzwDeltas = jobMemory->lzwDeltas.Num();
- jobMemory->lzwInOutData.lzwMem = jobMemory->lzwMem.Ptr();
- #ifdef STRESS_LZW_MEM
- jobMemory->lzwInOutData.maxlzwMem = g_maxlwMem;
- #else
- jobMemory->lzwInOutData.maxlzwMem = jobMemory_t::MAX_LZW_MEM;
- #endif
- jobMemory->lzwInOutData.lzwDmaOut = jobMemory_t::MAX_LZW_MEM;
- jobMemory->lzwInOutData.numlzwDeltas = 0;
- jobMemory->lzwInOutData.lzwBytes = 0;
- jobMemory->lzwInOutData.optimalLength = net_optimalSnapDeltaSize.GetInteger();
- jobMemory->lzwInOutData.snapSequence = snapSequence;
- jobMemory->lzwInOutData.lastObjId = 0;
- jobMemory->lzwInOutData.lzwData = lzwData;
- idSnapShot::submitDeltaJobsInfo_t submitInfo;
- submitInfo.objParms = jobMemory->objParms.Ptr();
- submitInfo.maxObjParms = jobMemory->objParms.Num();
- submitInfo.headers = jobMemory->headers.Ptr();
- submitInfo.maxHeaders = jobMemory->headers.Num();
- submitInfo.objMemory = objMemory;
- submitInfo.maxObjMemory = objMemorySize;
- submitInfo.lzwParms = jobMemory->lzwParms.Ptr();
- submitInfo.maxDeltaParms = jobMemory->lzwParms.Num();
-
-
- // Use a copy of base state to avoid race conditions.
- // The main thread could change it behind the jobs backs.
- submittedState = baseState;
- submittedTemplateStates = templateStates;
- submitInfo.templateStates = &submittedTemplateStates;
-
- submitInfo.oldSnap = &submittedState;
- submitInfo.visIndex = visIndex;
- submitInfo.baseSequence = baseSequence;
-
- submitInfo.lzwInOutData = &jobMemory->lzwInOutData;
- pendingSnap.SubmitWriteDeltaToJobs( submitInfo );
- }
- /*
- ========================
- idSnapshotProcessor::GetPendingSnapDelta
- ========================
- */
- int idSnapshotProcessor::GetPendingSnapDelta( byte * outBuffer, int maxLength ) {
- assert( PendingSnapReadyToSend() );
- if ( !verify( jobMemory->lzwInOutData.numlzwDeltas == 1 ) ) {
- jobMemory->lzwInOutData.numlzwDeltas = 0;
- return 0; // No more deltas left to send
- }
- assert( hasPendingSnap );
- jobMemory->lzwInOutData.numlzwDeltas = 0;
- int size = jobMemory->lzwDeltas[0].size;
- if ( !verify( size != -1 ) ) {
- #ifdef STRESS_LZW_MEM
- if ( g_maxlwMem < MAX_LZW_MEM ) {
- g_maxlwMem += 50;
- g_maxlwMem = Min( g_maxlwMem, MAX_LZW_MEM );
- return 0;
- }
- #endif
- // This can happen if there wasn't enough maxlzwMem to process one full obj in a single delta
- idLib::Error( "GetPendingSnapDelta: Delta failed." );
- }
-
- uint8 * deltaData = &jobMemory->lzwMem[jobMemory->lzwDeltas[0].offset];
-
- int deltaSequence = 0;
- int deltaBaseSequence = 0;
- PeekDeltaSequence( (const char *)deltaData, size, deltaSequence, deltaBaseSequence );
- // sanity check: does the compressed data we are about to send have the sequence number we expect
- assert( deltaSequence == jobMemory->lzwDeltas[0].snapSequence );
- if ( !verify( size <= maxLength ) ) {
- idLib::Error( "GetPendingSnapDelta: Size overflow." );
- }
- // Copy to out buffer
- memcpy( outBuffer, deltaData, size );
-
- // Set the sequence to what this delta actually belongs to
- assert( jobMemory->lzwDeltas[0].snapSequence == snapSequence + 1 );
- snapSequence = jobMemory->lzwDeltas[0].snapSequence;
- //idLib::Printf( "deltas Num: %i, Size: %i\n", deltas.Num(), deltas.GetDataLength() );
- // Copy to delta buffer
- // NOTE - We don't need to save this delta off if peer has already ack'd this basestate.
- // This can happen due to the fact that we defer the processing of snap deltas on jobs.
- // When we start processing a delta, we use the currently ack'd basestate. If while we were processing
- // the delta, the client acks a new basestate, we can get into this situation. In this case, we simply don't
- // store the delta, since it will just take up space, and just get removed anyways during ApplySnapshotDelta.
- // (and cause lots of spam when it sees the delta's basestate doesn't match the current ack'd one)
- if ( deltaBaseSequence >= baseSequence ) {
- if ( !deltas.Append( snapSequence, deltaData, size ) ) {
- int resendLength = deltas.ItemLength( deltas.Num() - 1 );
- if ( !verify( resendLength <= maxLength ) ) {
- idLib::Error( "GetPendingSnapDelta: Size overflow for resend." );
- }
- memcpy( outBuffer, deltas.ItemData( deltas.Num() - 1 ), resendLength );
- size = -resendLength;
- }
- }
- if ( jobMemory->lzwInOutData.fullSnap ) {
- // We sent the full snap, we can stop sending this pending snap now...
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 5, va( " wrote enough deltas to a full snapshot\n" ) ); // FIXME: peer number?
- hasPendingSnap = false;
- partialBaseSequence = -1;
- } else {
- partialBaseSequence = deltaBaseSequence;
- }
- return size;
- }
- /*
- ========================
- idSnapshotProcessor::IsBusyConfirmingPartialSnap
- ========================
- */
- bool idSnapshotProcessor::IsBusyConfirmingPartialSnap() {
- if ( partialBaseSequence != -1 && baseSequence <= partialBaseSequence ) {
- return true;
- }
- return false;
- }
- /*
- ========================
- idSnapshotProcessor::ReceiveSnapshotDelta
- 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
- 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?
- ========================
- */
- bool idSnapshotProcessor::ReceiveSnapshotDelta( const byte * deltaData, int deltaLength, int visIndex, int & outSeq, int & outBaseSeq, idSnapShot & outSnap, bool & fullSnap ) {
- fullSnap = false;
- int deltaSequence = 0;
- int deltaBaseSequence = 0;
- // Get the sequence of this delta, and the base sequence it is delta'd from
- PeekDeltaSequence( (const char *)deltaData, deltaLength, deltaSequence, deltaBaseSequence );
- //idLib::Printf("Incoming snapshot: %i, %i\n", deltaSequence, deltaBaseSequence );
-
- if ( deltaSequence <= snapSequence ) {
- NET_VERBOSESNAPSHOT_PRINT( "Rejecting old delta: %d (snapSequence: %d \n", deltaSequence, snapSequence );
- return false; // Completely reject older out of order deltas
- }
-
- // Bring the base state up to date with the basestate this delta was compared to
- ApplySnapshotDelta( visIndex, deltaBaseSequence );
- // Once we get here, our base state should be caught up to that of the server
- assert( baseSequence == deltaBaseSequence );
- // Save the new delta
- if ( net_skipClientDeltaAppend.GetBool() || !deltas.Append( deltaSequence, deltaData, deltaLength ) ) {
- // This can happen if the delta queues get desync'd between the server and client.
- // With recent fixes, this should be extremely rare, or impossible.
- // Just in case this happens, we can recover by assuming we didn't even receive this delta.
- idLib::Printf( "NET: ReceiveSnapshotDelta: No room to append delta %d/%d \n", deltaSequence, deltaBaseSequence );
- return false;
- }
-
- // Update our snapshot sequence number to the newer one we just got (now that it's safe)
- snapSequence = deltaSequence;
- if ( deltas.Num() > 10 ) {
- NET_VERBOSESNAPSHOT_PRINT("NET: ReceiveSnapshotDelta: deltas.Num() > 10: %d\n ", deltas.Num() );
- for ( int i=0; i < deltas.Num(); i++ ) {
- NET_VERBOSESNAPSHOT_PRINT( "%d ", deltas.ItemSequence( i ) );
- }
- NET_VERBOSESNAPSHOT_PRINT( "\n" );
- }
-
- if ( baseSequence != deltaBaseSequence ) {
- // NOTE - With recent fixes, this should no longer be possible unless the delta is trashed
- // We should probably disconnect from the server when this happens now.
- static bool failed = false;
- if ( !failed ) {
- idLib::Printf( "NET: incorrect base state? not sure how this can happen... baseSequence: %d deltaBaseSequence: %d \n", baseSequence, deltaBaseSequence );
- }
- failed = true;
- return false;
- }
- // Copy out the current deltas sequence values to caller
- outSeq = deltaSequence;
- outBaseSeq = deltaBaseSequence;
- if ( baseSequence < 50 && net_debugBaseStates.GetBool() ) {
- idLib::Printf( "NET: Proper basestate... baseSequence: %d deltaBaseSequence: %d \n", baseSequence, deltaBaseSequence );
- }
- // Make a copy of the basestate the server used to create this delta, and then apply and return it
- outSnap = baseState;
-
- fullSnap = ApplyDeltaToSnapshot( outSnap, (const char *)deltaData, deltaLength, visIndex );
- // We received a new delta
- return true;
- }
- /*
- ========================
- idSnapshotProcessor::ApplySnapshotDelta
- Apply a snapshot delta to our current basestate, and make that the new base.
- We can remove all deltas that refer to the basetate we just removed.
- ========================
- */
- bool idSnapshotProcessor::ApplySnapshotDelta( int visIndex, int snapshotNumber ) {
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 6, va( "idSnapshotProcessor::ApplySnapshotDelta snapshotNumber: %d\n", snapshotNumber ) );
- // Sanity check deltas
- SanityCheckDeltas();
- // dump any deltas older than the acknoweledged snapshot, which should only happen if there is packet loss
- deltas.RemoveOlderThan( snapshotNumber );
- if ( deltas.Num() == 0 || deltas.ItemSequence( 0 ) != snapshotNumber ) {
- // this means the snapshot was either already acknowledged or came out of order
- // On the server, this can happen because the client is continuously/redundantly sending acks
- // Once the server has ack'd a certain base sequence, it will need to ignore all the redundant ones.
- // On the client, this will only happen due to out of order, or dropped packets.
-
- if ( !common->IsServer() ) {
- // these should be printed every time on the clients
- // printing on server is not useful / results in tons of spam
- if ( deltas.Num() == 0 ) {
- NET_VERBOSESNAPSHOT_PRINT("NET: Got snapshot but ignored... deltas.Num(): %d snapshotNumber: %d \n", deltas.Num(), snapshotNumber );
- } else {
- NET_VERBOSESNAPSHOT_PRINT("NET: Got snapshot but ignored... deltas.ItemSequence( 0 ): %d != snapshotNumber: %d \n ", deltas.ItemSequence( 0 ), snapshotNumber );
-
- for ( int i=0; i < deltas.Num(); i++ ) {
- NET_VERBOSESNAPSHOT_PRINT("%d ", deltas.ItemSequence(i) );
- }
- NET_VERBOSESNAPSHOT_PRINT("\n");
- }
- }
- return false;
- }
- int deltaSequence = 0;
- int deltaBaseSequence = 0;
- PeekDeltaSequence( (const char *)deltas.ItemData( 0 ), deltas.ItemLength( 0 ), deltaSequence, deltaBaseSequence );
- assert( deltaSequence == snapshotNumber ); // Make sure compressed sequence number matches that in data queue
- assert( baseSequence == deltaBaseSequence ); // If this delta isn't based off of our currently ack'd basestate, something is trashed...
- assert( deltaSequence > baseSequence );
- if ( baseSequence != deltaBaseSequence ) {
- // NOTE - This should no longer happen with recent fixes.
- // We should probably disconnect from the server if this happens. (packets are trashed most likely)
- NET_VERBOSESNAPSHOT_PRINT( "NET: Got snapshot %d but baseSequence does not match. baseSequence: %d deltaBaseSequence: %d. \n", snapshotNumber, baseSequence, deltaBaseSequence );
- return false;
- }
- // Apply this delta to our base state
- if ( ApplyDeltaToSnapshot( baseState, (const char *)deltas.ItemData( 0 ), deltas.ItemLength( 0 ), visIndex ) ) {
- lastFullSnapBaseSequence = deltaSequence;
- }
-
- baseSequence = deltaSequence; // This is now our new base sequence
- // Remove deltas that we no longer need
- RemoveDeltasForOldBaseSequence();
-
- // Sanity check deltas
- SanityCheckDeltas();
- return true;
- }
- /*
- ========================
- idSnapshotProcessor::RemoveDeltasForOldBaseSequence
- Remove deltas for basestate we no longer have. We know we can remove them, because we will never
- be able to apply them, since the basestate needed to generate a full snap from these deltas is gone.
- Ways we can get deltas based on basestate we no longer have:
- 1. Server sends sequence 50 based on 49. It then sends sequence 51 based on 49.
- Client acks 50, server applies it to 49, 50 is new base state.
- Server now has a delta sequence 51 based on 49 that it won't ever be able to apply (50 is new basestate).
- This is annoying, because it makes a lot of our sanity checks incorrectly fire off for benign issues.
- Here is a series of events that make the old ( baseSequence != deltaBaseSequence ) assert:
- Server:
- 49->50, 49->51, ack 50, 50->52, ack 51 (bam), 50->53
- Client
- 49->50, ack 50, 49->51, ack 51 (bam), 50->52, 50->53
- The client above will ack 51, even though he can't even apply that delta. To get around this, we simply don't
- allow delta to exist in the list, unless their basestate is the current basestate we maintain.
- This allows us to put sanity checks in place that don't fire off during benign conditions, and allow us
- to truly check for trashed conditions.
- ========================
- */
- void idSnapshotProcessor::RemoveDeltasForOldBaseSequence() {
- // Remove any deltas that would apply to the old base we no longer maintain
- // (we will never be able to apply these, since we don't have that base anymore)
- for ( int i = deltas.Num() - 1; i >= 0; i-- ) {
- int deltaSequence = 0;
- int deltaBaseSequence = 0;
- baseState.PeekDeltaSequence( (const char *)deltas.ItemData( i ), deltas.ItemLength( i ), deltaSequence, deltaBaseSequence );
- if ( deltaBaseSequence < baseSequence ) {
- // Remove this delta, and all deltas before this one
- deltas.RemoveOlderThan( deltas.ItemSequence( i ) + 1 );
- break;
- }
- }
- }
- /*
- ========================
- idSnapshotProcessor::SanityCheckDeltas
- Make sure delta sequence and basesequence values are valid, and in order, etc
- ========================
- */
- void idSnapshotProcessor::SanityCheckDeltas() {
- int deltaSequence = 0;
- int deltaBaseSequence = 0;
- int lastDeltaSequence = -1;
- int lastDeltaBaseSequence = -1;
-
- for ( int i = 0; i < deltas.Num(); i++ ) {
- baseState.PeekDeltaSequence( (const char *)deltas.ItemData( i ), deltas.ItemLength( i ), deltaSequence, deltaBaseSequence );
- assert( deltaSequence == deltas.ItemSequence( i ) ); // Make sure delta stored in compressed form matches the one stored in the data queue
- assert( deltaSequence > lastDeltaSequence ); // Make sure they are in order (we reject out of order sequences in ApplysnapshotDelta)
- 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)
- assert( deltaBaseSequence >= baseSequence ); // We should have removed old delta's that can no longer be applied
- assert( deltaBaseSequence == baseSequence || deltaBaseSequence == lastDeltaSequence ); // Make sure we still have a base (or eventually will have) that we can apply this delta to
- lastDeltaSequence = deltaSequence;
- lastDeltaBaseSequence = deltaBaseSequence;
- }
- }
- /*
- ========================
- idSnapshotProcessor::AddSnapObjTemplate
- ========================
- */
- void idSnapshotProcessor::AddSnapObjTemplate( int objID, idBitMsg & msg ) {
- extern idCVar net_ssTemplateDebug;
- idSnapShot::objectState_t * state = templateStates.S_AddObject( objID, MAX_UNSIGNED_TYPE( uint32 ), msg );
- if ( verify( state != NULL ) ) {
- if ( net_ssTemplateDebug.GetBool() ) {
- idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "InjectingSnapObjBaseState[%d] size: %d\n", objID, state->buffer.Size() );
- state->Print( "BASE STATE" );
- }
- state->expectedSequence = snapSequence;
- }
- }
|